OVMS3/OVMS.V3/main/test_framework.cpp

410 lines
13 KiB
C++

/*
; Project: Open Vehicle Monitor System
; Date: 14th March 2017
;
; Changes:
; 1.0 Initial release
;
; (C) 2011 Michael Stegen / Stegen Electronics
; (C) 2011-2017 Mark Webb-Johnson
; (C) 2011 Sonny Chen @ EPRO/DX
;
; Permission is hereby granted, free of charge, to any person obtaining a copy
; of this software and associated documentation files (the "Software"), to deal
; in the Software without restriction, including without limitation the rights
; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
; copies of the Software, and to permit persons to whom the Software is
; furnished to do so, subject to the following conditions:
;
; The above copyright notice and this permission notice shall be included in
; all copies or substantial portions of the Software.
;
; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
; THE SOFTWARE.
*/
#include "ovms_log.h"
static const char *TAG = "test";
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "esp_system.h"
#include "esp_event.h"
#include "esp_event_loop.h"
#include "esp_sleep.h"
#include "test_framework.h"
#include "ovms_command.h"
#include "ovms_peripherals.h"
#include "ovms_script.h"
#include "metrics_standard.h"
#include "ovms_config.h"
#include "can.h"
#include "strverscmp.h"
void test_deepsleep(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
{
int sleeptime = 60;
if (argc==1)
{
sleeptime = atoi(argv[0]);
}
writer->puts("Entering deep sleep...");
vTaskDelay(1000 / portTICK_PERIOD_MS);
esp_deep_sleep(1000000LL * sleeptime);
}
void test_javascript(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
{
#ifdef CONFIG_OVMS_SC_JAVASCRIPT_NONE
writer->puts("No javascript engine enabled");
#endif //#ifdef CONFIG_OVMS_SC_JAVASCRIPT_NONE
#ifdef CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE
writer->printf("Javascript 1+2=%d\n", MyDuktape.DuktapeEvalIntResult("1+2"));
#endif //#ifdef CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE
}
#ifdef CONFIG_OVMS_COMP_SDCARD
void test_sdcard(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
{
sdcard *sd = MyPeripherals->m_sdcard;
if (!sd->isinserted())
{
writer->puts("Error: No SD CARD inserted");
return;
}
if (!sd->ismounted())
{
writer->puts("Error: SD CARD not mounted");
return;
}
unlink("/sd/ovmstest.txt");
char buffer[512];
memset(buffer,'A',sizeof(buffer));
FILE *fd = fopen("/sd/ovmstest.txt","w");
if (fd == NULL)
{
writer->puts("Error: /sd/ovmstest.txt could not be opened for writing");
return;
}
writer->puts("SD CARD test starts...");
for (int k=0;k<2048;k++)
{
fwrite(buffer, sizeof(buffer), 1, fd);
if ((k % 128)==0)
writer->printf("SD CARD written %d/%d\n",k,2048);
}
fclose(fd);
writer->puts("Cleaning up");
unlink("/sd/ovmstest.txt");
writer->puts("SD CARD test completes");
}
#endif // #ifdef CONFIG_OVMS_COMP_SDCARD
// Spew lines of the ASCII printable characters in the style of RFC 864.
void test_chargen(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
{
int numlines = 1000;
int delay = 0;
if (argc>=1)
{
numlines = atoi(argv[0]);
}
if (argc>=2)
{
delay = atoi(argv[1]);
}
char buf[74];
buf[72] = '\n';
buf[73] = '\0';
char start = '!';
for (int line = 0; line < numlines; ++line)
{
char ch = start;
for (int col = 0; col < 72; ++col)
{
buf[col] = ch;
if (++ch == 0x7F)
ch = ' ';
}
if (writer->write(buf, 73) <= 0)
break;
if (delay)
vTaskDelay(delay/portTICK_PERIOD_MS);
if (++start == 0x7F)
start = ' ';
}
}
bool test_echo_insert(OvmsWriter* writer, void* ctx, char ch)
{
if (ch == '\n')
return false;
writer->write(&ch, 1);
return true;
}
void test_echo(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
{
writer->puts("Type characters to be echoed, end with newline.");
writer->RegisterInsertCallback(test_echo_insert, NULL);
}
void test_watchdog(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
{
writer->puts("Spinning now (watchdog should fire in a few minutes)");
for (;;) {}
writer->puts("Error: We should never get here");
}
void test_stackoverflow(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
{
uint8_t data[256];
memset(data, verbosity & 255, sizeof data);
writer->printf("Stack bytes remaining: %u\n", uxTaskGetStackHighWaterMark(NULL));
vTaskDelay(pdMS_TO_TICKS(250));
test_stackoverflow(verbosity+1, writer, cmd, argc, argv);
// never reached, just to inhibit tail recursion optimization:
writer->printf("%x\n", data[0]);
}
void test_realloc(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
{
void* buf;
void *interfere = NULL;
writer->puts("First check heap integrity...");
heap_caps_check_integrity_all(true);
writer->puts("Now allocate 4KB RAM...");
buf = ExternalRamMalloc(4096);
writer->puts("Check heap integrity...");
heap_caps_check_integrity_all(true);
writer->puts("Now re-allocate bigger, 1,000 times...");
for (int k=1; k<1001; k++)
{
buf = ExternalRamRealloc(buf, 4096+k);
if (interfere == NULL)
{
interfere = ExternalRamMalloc(1024);
}
else
{
free(interfere);
interfere = NULL;
}
}
writer->puts("Check heap integrity...");
heap_caps_check_integrity_all(true);
writer->puts("Now re-allocate smaller, 1,000 times...");
for (int k=1001; k>0; k--)
{
buf = ExternalRamRealloc(buf, 4096+k);
if (interfere == NULL)
{
interfere = ExternalRamMalloc(1024);
}
else
{
free(interfere);
interfere = NULL;
}
}
writer->puts("Check heap integrity...");
heap_caps_check_integrity_all(true);
writer->puts("And free the buffer...");
free(buf);
if (interfere != NULL) free(interfere);
writer->puts("Final check of heap integrity...");
heap_caps_check_integrity_all(true);
}
void test_spiram(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
{
writer->printf("Metrics (%p) are in %s RAM (%d bytes for a base metric)\n",
StandardMetrics.ms_m_version,
(((unsigned int)StandardMetrics.ms_m_version >= 0x3f800000)&&
((unsigned int)StandardMetrics.ms_m_version <= 0x3fbfffff))?
"SPI":"INTERNAL",
sizeof(OvmsMetric));
}
void test_strverscmp(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
{
int c = strverscmp(argv[0],argv[1]);
writer->printf("%s %s %s\n",
argv[0],
(c<0)?"<":((c==0)?"=":">"),
argv[1]
);
}
void test_can(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
{
int64_t started = esp_timer_get_time();
int64_t elapsed;
bool tx = (strcmp(cmd->GetName(), "cantx")==0);
int frames = 1000;
if (argc>1) frames = atoi(argv[1]);
canbus *can;
if (argc>0)
can = (canbus*)MyPcpApp.FindDeviceByName(argv[0]);
else
can = (canbus*)MyPcpApp.FindDeviceByName("can1");
if (can == NULL)
{
writer->puts("Error: Cannot find specified can bus");
return;
}
writer->printf("Testing %d frames on %s\n",frames,can->GetName());
CAN_frame_t frame;
memset(&frame,0,sizeof(frame));
frame.origin = can;
frame.FIR.U = 0;
frame.FIR.B.DLC = 8;
frame.FIR.B.FF = CAN_frame_std;
for (int k=0;k<frames;k++)
{
frame.MsgID = k % 2048;
frame.data.u64 = k+1;
if (tx)
can->Write(&frame, pdMS_TO_TICKS(10));
else
MyCan.IncomingFrame(&frame);
}
elapsed = esp_timer_get_time() - started;
int uspt = elapsed / frames;
writer->printf("Transmitted %d frames in %lld.%06llds = %dus/frame\n",
frames, elapsed / 1000000, elapsed % 1000000, uspt);
}
void test_mkstemp(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
{
int fd1, e1, fd2, e2;
char tn1[100], tn2[100];
snprintf(tn1, sizeof(tn1), "%s.XXXXXX", argv[0]);
snprintf(tn2, sizeof(tn2), "%s.XXXXXX", argv[0]);
errno = 0;
fd1 = mkstemp(tn1); e1 = errno;
fd2 = mkstemp(tn2); e2 = errno;
writer->printf("tempfile 1: '%s' => fd=%d error='%s'\n", tn1, fd1, (fd1<0) ? strerror(e1) : "-");
writer->printf("tempfile 2: '%s' => fd=%d error='%s'\n", tn2, fd2, (fd2<0) ? strerror(e2) : "-");
if (fd1 >= 0) { close(fd1); unlink(tn1); }
if (fd2 >= 0) { close(fd2); unlink(tn2); }
}
void test_string(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
{
int loopcnt = atoi(argv[0]);
int mode = (argc > 1) ? atoi(argv[1]) : 1;
int stdlen = 0, errcnt = 0;
size_t pos;
OvmsMetric* m;
const char* t5 = "0123456789-abcdefghijk-";
std::string t6(t5);
int64_t time_start_us = esp_timer_get_time();
for (int j = 1; j <= loopcnt; j++)
{
std::string msg;
msg.reserve(2048);
for (m = MyMetrics.m_first; msg.size() < 1024; m = m->m_next ? m->m_next : MyMetrics.m_first)
{
if (mode == 1)
msg += m->AsJSON();
else if (mode == 2)
msg += m->AsString();
else if (mode == 3)
msg += m->m_name;
else if (mode == 4)
msg += MyConfig.GetParamValue("module", "cfgversion", m->m_name);
else if (mode == 5)
msg += t5;
else if (mode == 6)
msg += t6;
// check for NUL bytes:
if ((pos = msg.rfind('\0')) != std::string::npos)
{
writer->printf("loop #%d: corruption detected at pos=%u size=%u\n%s\n\n", j, pos, msg.size(), msg.c_str());
if (++errcnt == 20)
{
writer->puts("too many errors, abort");
return;
}
break;
}
}
// display reference:
if (msg.size() > stdlen)
{
stdlen = msg.size();
writer->printf("#%d: stdlen = %d\n%s\n\n", j, stdlen, msg.c_str());
}
}
writer->printf("finished %d loops for mode %d using %s in %d ms\n", loopcnt, mode,
(t6.data() >= (void*)0x3F800000 && t6.data() < (void*)0x3FC00000) ? "extram" : "intram",
(int)((esp_timer_get_time() - time_start_us) / 1000));
}
void test_command(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
{
MyCommandApp.Display(writer);
}
class TestFrameworkInit
{
public: TestFrameworkInit();
} MyTestFrameworkInit __attribute__ ((init_priority (5000)));
TestFrameworkInit::TestFrameworkInit()
{
ESP_LOGI(TAG, "Initialising TEST (5000)");
OvmsCommand* cmd_test = MyCommandApp.RegisterCommand("test","Test framework");
cmd_test->RegisterCommand("sleep","Test Deep Sleep",test_deepsleep,"[<seconds>]",0,1);
#ifdef CONFIG_OVMS_COMP_SDCARD
cmd_test->RegisterCommand("sdcard","Test CD CARD",test_sdcard);
#endif // #ifdef CONFIG_OVMS_COMP_SDCARD
cmd_test->RegisterCommand("javascript","Test Javascript",test_javascript);
cmd_test->RegisterCommand("chargen","Character generator",test_chargen,"[<#lines>] [<delay_ms>]",0,2);
cmd_test->RegisterCommand("echo", "Test getchar", test_echo);
cmd_test->RegisterCommand("watchdog", "Test task spinning (and watchdog firing)", test_watchdog);
cmd_test->RegisterCommand("stackoverflow", "Test stack overflow detection (crashes)", test_stackoverflow);
cmd_test->RegisterCommand("realloc", "Test memory re-allocations", test_realloc);
cmd_test->RegisterCommand("spiram", "Test SPI RAM memory usage", test_spiram);
cmd_test->RegisterCommand("strverscmp", "Test strverscmp function", test_strverscmp, "", 2, 2);
cmd_test->RegisterCommand("cantx", "Test CAN bus transmission", test_can, "[<port>] [<number>]", 0, 2);
cmd_test->RegisterCommand("canrx", "Test CAN bus reception", test_can, "[<port>] [<number>]", 0, 2);
cmd_test->RegisterCommand("mkstemp", "Test mkstemp function", test_mkstemp, "<file>", 1, 1);
cmd_test->RegisterCommand("string", "Test std::string memory corruption", test_string, "<loopcnt> <mode>\n"
"mode: 1=m.AsJSON, 2=m.AsString, 3=m.name, 4=const cfg string, 5=const local cstr, 6=const local string", 2, 2);
cmd_test->RegisterCommand("commands", "List command tree", test_command);
}