forked from IQ.Lvbs/IQ.Pilot
IQ.Pilot Release Commit @ 0b96bd5
This commit is contained in:
@@ -1,10 +1,15 @@
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
#include <ctime>
|
||||
#include <filesystem>
|
||||
#include <sstream>
|
||||
|
||||
#include "common/tests/native_test.h"
|
||||
#include "tools/cabana/dbc/dbcfile.h"
|
||||
#include "tools/cabana/dbc/dbcmanager.h"
|
||||
#include "tools/cabana/routes.h"
|
||||
#include "tools/cabana/utils/strings.h"
|
||||
|
||||
const std::string TEST_RLOG_URL = "https://commadataci.blob.core.windows.net/openpilotci/0c94aa1e1296d7c6/2021-05-05--19-48-37/0/rlog.bz2";
|
||||
|
||||
@@ -30,7 +35,7 @@ void test_generate_dbc() {
|
||||
}
|
||||
|
||||
void test_comment_order() {
|
||||
// Ensure that message comments are followed by signal comments and in the correct order
|
||||
|
||||
std::string content = R"(BO_ 160 message_1: 8 EON
|
||||
SG_ signal_1 : 0|12@1+ (1,0) [0|4095] "unit" XXX
|
||||
|
||||
@@ -91,7 +96,7 @@ VAL_ 160 signal_1 0 "disabled" 1.2 "initializing" 2 "fault";
|
||||
|
||||
CM_ BO_ 160 "message comment" ;
|
||||
CM_ SG_ 160 signal_1 "signal comment";
|
||||
CM_ SG_ 160 signal_2 "multiple line comment
|
||||
CM_ SG_ 160 signal_2 "multiple line comment
|
||||
1
|
||||
2
|
||||
";
|
||||
@@ -125,9 +130,9 @@ CM_ SG_ 162 signal_1 "signal comment with \"escaped quotes\"";
|
||||
REQUIRE(sig_1->val_desc[2] == std::pair<double, std::string>{2, "fault"});
|
||||
|
||||
auto &sig_2 = msg->sigs[1];
|
||||
REQUIRE(sig_2->comment == "multiple line comment \n1\n2");
|
||||
REQUIRE(sig_2->comment == "multiple line comment\n1\n2");
|
||||
|
||||
|
||||
// multiplexed signals
|
||||
msg = file.msg(162);
|
||||
REQUIRE(msg != nullptr);
|
||||
REQUIRE(msg->sigs.size() == 2);
|
||||
@@ -138,42 +143,20 @@ CM_ SG_ 162 signal_1 "signal comment with \"escaped quotes\"";
|
||||
REQUIRE(msg->sigs[1]->size == 1);
|
||||
REQUIRE(msg->sigs[1]->receiver_name == "XXX");
|
||||
|
||||
// escaped quotes
|
||||
|
||||
REQUIRE(msg->comment == "message comment with \"escaped quotes\"");
|
||||
REQUIRE(msg->sigs[0]->comment == "signal comment with \"escaped quotes\"");
|
||||
}
|
||||
|
||||
void test_parse_iqdbc() {
|
||||
std::vector<std::string> errors;
|
||||
int parsed = 0;
|
||||
for (const auto &entry : std::filesystem::recursive_directory_iterator(OPENDBC_FILE_PATH)) {
|
||||
if (!entry.is_regular_file() || entry.path().extension() != ".dbc") continue;
|
||||
try {
|
||||
auto dbc = DBCFile(entry.path().string());
|
||||
++parsed;
|
||||
} catch (std::exception &e) {
|
||||
errors.push_back(e.what());
|
||||
}
|
||||
}
|
||||
// guard against OPENDBC_FILE_PATH pointing somewhere empty, which would make the
|
||||
// loop above pass vacuously
|
||||
REQUIRE(parsed > 100);
|
||||
std::ostringstream details;
|
||||
for (const auto &error : errors) details << error << '\n';
|
||||
if (!errors.empty()) std::cerr << details.str();
|
||||
REQUIRE(errors.empty());
|
||||
}
|
||||
|
||||
void test_dbc_manager() {
|
||||
DBCManager manager;
|
||||
int files_changed = 0;
|
||||
int signals_added = 0;
|
||||
int masks_updated = 0;
|
||||
manager.setCallbacks({
|
||||
.signal_added = [&](MessageId, const cabana::Signal *) { ++signals_added; },
|
||||
.file_changed = [&]() { ++files_changed; },
|
||||
.mask_updated = [&]() { ++masks_updated; },
|
||||
});
|
||||
Connections connections;
|
||||
connections.push_back(manager.signalAdded.connect([&](MessageId, const cabana::Signal *) { ++signals_added; }));
|
||||
connections.push_back(manager.fileChanged.connect([&]() { ++files_changed; }));
|
||||
connections.push_back(manager.maskUpdated.connect([&]() { ++masks_updated; }));
|
||||
|
||||
std::string error;
|
||||
REQUIRE(manager.open(SOURCE_ALL, "test", "BO_ 160 message: 8 XXX\n", &error));
|
||||
@@ -191,14 +174,129 @@ void test_dbc_manager() {
|
||||
REQUIRE(manager.msg({.source = 0, .address = 160})->sig("speed") != nullptr);
|
||||
}
|
||||
|
||||
void test_format_seconds() {
|
||||
REQUIRE(utils::formatSeconds(0) == "00:00");
|
||||
REQUIRE(utils::formatSeconds(59.4) == "00:59");
|
||||
REQUIRE(utils::formatSeconds(-1) == "00:00");
|
||||
REQUIRE(utils::formatSeconds(61.234, true) == "01:01.234");
|
||||
REQUIRE(utils::formatSeconds(3599.9) == "59:59");
|
||||
REQUIRE(utils::formatSeconds(3601) == "01:00:01");
|
||||
REQUIRE(utils::formatSeconds(3601.5, true) == "01:00:01.500");
|
||||
|
||||
const char *tz = getenv("TZ");
|
||||
const bool had_tz = tz != nullptr;
|
||||
const std::string saved_tz = had_tz ? tz : "";
|
||||
setenv("TZ", "UTC", 1);
|
||||
tzset();
|
||||
REQUIRE(utils::formatSeconds(0, false, true) == "1970-01-01 00:00:00");
|
||||
REQUIRE(utils::formatSeconds(1700000000.123, true, true) == "2023-11-14 22:13:20.123");
|
||||
if (had_tz) {
|
||||
setenv("TZ", saved_tz.c_str(), 1);
|
||||
} else {
|
||||
unsetenv("TZ");
|
||||
}
|
||||
tzset();
|
||||
}
|
||||
|
||||
void test_to_hex() {
|
||||
REQUIRE(utils::toHex({}) == "");
|
||||
REQUIRE(utils::toHex({0x00, 0x0f, 0xab, 0xff}) == "000FABFF");
|
||||
REQUIRE(utils::toHex({0x01, 0x02, 0x03}, ' ') == "01 02 03");
|
||||
|
||||
REQUIRE(utils::toHexString(0) == "0x00");
|
||||
REQUIRE(utils::toHexString(0xf) == "0x0F");
|
||||
REQUIRE(utils::toHexString(0x1ab) == "0x1AB");
|
||||
REQUIRE(utils::toHexString(0x1fffffff) == "0x1FFFFFFF");
|
||||
}
|
||||
|
||||
void test_signal_tooltip() {
|
||||
cabana::Signal sig{};
|
||||
sig.name = "speed";
|
||||
sig.start_bit = 3;
|
||||
sig.size = 12;
|
||||
sig.msb = 14;
|
||||
sig.lsb = 3;
|
||||
sig.is_little_endian = true;
|
||||
sig.is_signed = false;
|
||||
REQUIRE(utils::signalToolTip(&sig) == R"(
|
||||
speed<br /><span font-size:small">
|
||||
Start Bit: 3 Size: 12<br />
|
||||
MSB: 14 LSB: 3<br />
|
||||
Little Endian: Y Signed: N</span>
|
||||
)");
|
||||
}
|
||||
|
||||
void test_route_timestamps() {
|
||||
REQUIRE(routes::parseIsoToUnixMs("2024-01-02T03:04:05Z") == 1704164645000);
|
||||
REQUIRE(routes::parseIsoToUnixMs("2024-01-02T03:04:05") == 1704164645000);
|
||||
REQUIRE(routes::parseIsoToUnixMs("2024-01-02 03:04:05") == 1704164645000);
|
||||
REQUIRE(routes::parseIsoToUnixMs("2024-01-02T03:04:05.123Z") == 1704164645123);
|
||||
REQUIRE(routes::parseIsoToUnixMs("2024-01-02T03:04:05.4Z") == 1704164645400);
|
||||
REQUIRE(routes::parseIsoToUnixMs("2024-01-02T03:04:05.123456Z") == 1704164645123);
|
||||
REQUIRE(routes::parseIsoToUnixMs("") == 0);
|
||||
REQUIRE(routes::parseIsoToUnixMs("not a timestamp") == 0);
|
||||
|
||||
|
||||
const char *tz = getenv("TZ");
|
||||
const std::string prev_tz = tz ? tz : "";
|
||||
setenv("TZ", "UTC", 1);
|
||||
tzset();
|
||||
REQUIRE(routes::formatUnixMs(1704164645123) == "2024-01-02 03:04:05");
|
||||
if (tz) {
|
||||
setenv("TZ", prev_tz.c_str(), 1);
|
||||
} else {
|
||||
unsetenv("TZ");
|
||||
}
|
||||
tzset();
|
||||
}
|
||||
|
||||
void test_route_api_response() {
|
||||
REQUIRE(routes::checkApiResponse("") == std::make_pair(false, 500));
|
||||
REQUIRE(routes::checkApiResponse("not json") == std::make_pair(false, 500));
|
||||
REQUIRE(routes::checkApiResponse(R"({"error": "unauthorized"})") == std::make_pair(false, 401));
|
||||
REQUIRE(routes::checkApiResponse(R"({"error": "server error"})") == std::make_pair(false, 500));
|
||||
REQUIRE(routes::checkApiResponse("[]") == std::make_pair(true, 0));
|
||||
REQUIRE(routes::checkApiResponse(R"({"dongle_id": "aaaa"})") == std::make_pair(true, 0));
|
||||
}
|
||||
|
||||
void test_route_json() {
|
||||
auto devices = routes::parseDevices(R"([{"dongle_id": "aaaa"}, {"dongle_id": "bbbb"}])");
|
||||
REQUIRE(devices.size() == 2);
|
||||
REQUIRE(devices[0].dongle_id == "aaaa");
|
||||
REQUIRE(devices[1].dongle_id == "bbbb");
|
||||
REQUIRE(routes::parseDevices("not json").empty());
|
||||
REQUIRE(routes::parseDevices(R"({"error": "unauthorized"})").empty());
|
||||
|
||||
auto list = routes::parseRoutes(
|
||||
R"([{"fullname": "aaaa|2024-01-02--03-04-05", "start_time_utc_millis": 1704164645000, "end_time_utc_millis": 1704165245000}])", false);
|
||||
REQUIRE(list.size() == 1);
|
||||
REQUIRE(list[0].name == "aaaa|2024-01-02--03-04-05");
|
||||
REQUIRE(list[0].start_ms == 1704164645000);
|
||||
REQUIRE(list[0].end_ms == 1704165245000);
|
||||
|
||||
|
||||
auto preserved = routes::parseRoutes(
|
||||
R"([{"fullname": "aaaa|2024-01-02--03-04-05", "start_time": "2024-01-02T03:04:05Z", "end_time": "2024-01-02T03:14:05Z"}])", true);
|
||||
REQUIRE(preserved.size() == 1);
|
||||
REQUIRE(preserved[0].start_ms == 1704164645000);
|
||||
REQUIRE(preserved[0].end_ms == 1704165245000);
|
||||
|
||||
REQUIRE(routes::parseRoutes("not json", false).empty());
|
||||
}
|
||||
|
||||
void test_cabana_core() {
|
||||
test_format_seconds();
|
||||
test_to_hex();
|
||||
test_signal_tooltip();
|
||||
test_generate_dbc();
|
||||
test_comment_order();
|
||||
test_preserve_original_header();
|
||||
test_escaped_quotes();
|
||||
test_parse_dbc();
|
||||
test_parse_iqdbc();
|
||||
test_dbc_manager();
|
||||
test_route_timestamps();
|
||||
test_route_api_response();
|
||||
test_route_json();
|
||||
}
|
||||
|
||||
int main() {
|
||||
|
||||
11
iqpilot/tools/cabana/tests/test_cabana_ui.py
Normal file
11
iqpilot/tools/cabana/tests/test_cabana_ui.py
Normal file
@@ -0,0 +1,11 @@
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
CABANA_DIR = Path(__file__).parent.parent
|
||||
|
||||
|
||||
class TestCabanaUi:
|
||||
def test_help(self):
|
||||
result = subprocess.run(["./_cabana", "-h"], cwd=CABANA_DIR, capture_output=True, text=True)
|
||||
assert result.returncode == 0, result.stderr
|
||||
assert "Usage:" in result.stderr
|
||||
Reference in New Issue
Block a user