This commit is contained in:
deeaitch committed 2026-10-11 06:19:42 -04:00
1 parent bce707daf6
commit 51fcdb88ac
174 files changed
+5220 -2213

No files matched your search

+265
View File
@@ -63,6 +63,11 @@ private:
}
}
private Q_SLOTS:
void diagnostics();
void diagnosticErrors();
void diagnosticUnsupportedStatements();
void diagnosticNamespaceBoundaries();
void diagnosticFormatting();
void multiplexing();
void multiplexingBoundaries();
void validFrames();
@@ -79,6 +84,266 @@ private Q_SLOTS:
void invalidValueDescriptions();
};
void DbcParserTest::diagnostics() {
Fixture fixture;
fixture.Write("VERSION \"test\"\n"
"NS_ :\n"
" CM_\n"
" BA_\n"
" SIG_VALTYPE_\n"
" SG_MUL_VAL_\n\n"
"BS_:\n"
"BU_: ECU\n"
"BO_ 1 Frame: 1 ECU\n"
"SG_ Value : 0|8@1+ (1,0) [0|255] \"\" ECU\n"
"SIG_VALTYPE_ 1 Value : 1;\n"
"BA_ \"SignalType\" SG_ 1 Value 1;\n"
"VENDOR_EXTENSION_ data;\n");
const DbcParseResult result = DbcParser().ParseFileWithDiagnostics(fixture.path.string());
CHECK(!result.HasErrors() && result.database.frames.size() == 1U,
"Warnings preserve the supported database");
QCOMPARE(result.diagnostics.size(), std::size_t(3U));
for (std::size_t index = 0U; index < result.diagnostics.size(); ++index) {
const DbcDiagnostic &diagnostic = result.diagnostics[index];
CHECK(diagnostic.severity == DbcDiagnosticSeverity::Warning &&
diagnostic.code == DbcDiagnosticCode::UnsupportedStatement,
"Unsupported statements have structured warning severity and code");
QCOMPARE(diagnostic.filePath, fixture.path.string());
QCOMPARE(diagnostic.lineNumber, 12U + index);
CHECK(diagnostic.message.find("ignored") != std::string::npos,
"Warning explains unsupported syntax");
}
CHECK(DbcParser().ParseFile(fixture.path.string()).frames.size() == 1U,
"Legacy API accepts files with warnings");
fixture.Write("VERSION \"\"\nNS_:\nSIG_VALTYPE_\n\tBA_\n BS_ :\n BU_ : ECU\n");
const DbcParseResult headers = DbcParser().ParseFileWithDiagnostics(fixture.path.string());
CHECK(headers.diagnostics.empty(), "Common header variants stay quiet");
fixture.Write("NS_ :\n BA_\n BO_\n");
CHECK(DbcParser().ParseFileWithDiagnostics(fixture.path.string()).HasErrors(),
"Namespace handling does not swallow a malformed frame");
}
void DbcParserTest::diagnosticErrors() {
Fixture fixture;
struct ErrorCase {
const char *text;
DbcDiagnosticCode code;
std::size_t line;
};
const ErrorCase cases[] = {
{"UNKNOWN_ data;\n\nBO_ invalid Frame: 1 ECU\n",
DbcDiagnosticCode::InvalidStatement, 3U},
{"BO_ 1 Frame: 1 ECU\nSG_MUL_VAL_ 1 Value Mode 1-2;\n",
DbcDiagnosticCode::UnsupportedStatement, 2U},
{"BO_ 1 Frame: 1 ECU\nVAL_ 1 Missing 0 \"Off\";\n",
DbcDiagnosticCode::InvalidValueDescription, 2U},
{"BO_ 1 Frame: 1 ECU\nSG_ Value m1 : 0|8@1+ (1,0) [0|255] \"\" ECU\n",
DbcDiagnosticCode::InvalidMultiplexing, 2U},
{"BO_ 1 Frame: 1 ECU\nSG_ Mode M : 0|8@1- (1,0) [-128|127] \"\" ECU\n",
DbcDiagnosticCode::InvalidMultiplexing, 2U},
{"VAL_ broken\n", DbcDiagnosticCode::InvalidValueDescription, 1U},
{"SG_ Value : 0|8@1+ (1,0) [0|255] \"\" ECU\n",
DbcDiagnosticCode::InvalidStatement, 1U},
{"BO_ 1 Frame: 1 ECU\nBO_ 1 Duplicate: 1 ECU\n",
DbcDiagnosticCode::InvalidStatement, 2U},
{"BO_ 1 Frame: 1 ECU\nSG_ Value : 0|8@1+ (1,0) [0|255] \"\" ECU\n"
"SG_ Value : 0|8@1+ (1,0) [0|255] \"\" ECU\n",
DbcDiagnosticCode::InvalidStatement, 3U},
{"BO_ 1 Frame: 1 ECU\nSG_ Value : 8|8@1+ (1,0) [0|255] \"\" ECU\n",
DbcDiagnosticCode::InvalidStatement, 2U},
{"BO_ 1 Frame: 1 ECU\nVAL_ 2 Missing 0 \"Off\";\n",
DbcDiagnosticCode::InvalidValueDescription, 2U},
{"BO_ 1 Frame: 1 ECU\nSG_ Value : 0|8@1+ (1,0) [0|255] \"\" ECU\n"
"VAL_ 1 Value 0 \"Off\";\nVAL_ 1 Value 0 \"Duplicate\";\n",
DbcDiagnosticCode::InvalidValueDescription, 4U},
{"BO_ 1 Frame: 2 ECU\nSG_ Mode M : 0|1@1+ (1,0) [0|1] \"\" ECU\n"
"SG_ Value m2 : 8|8@1+ (1,0) [0|255] \"\" ECU\n",
DbcDiagnosticCode::InvalidMultiplexing, 3U},
{"BO_ 1 Frame: 2 ECU\nSG_ Mode M : 0|8@1+ (1,0) [0|255] \"\" ECU\n"
"SG_ Other M : 8|8@1+ (1,0) [0|255] \"\" ECU\n",
DbcDiagnosticCode::InvalidMultiplexing, 3U},
{"BO_ 1 Frame: 1 ECU\nSG_ First : 0|8@1+ (1,0) [0|255] \"\" ECU\n"
"SG_ Second : 7|8@0+ (1,0) [0|255] \"\" ECU\n",
DbcDiagnosticCode::InvalidMultiplexing, 3U}
};
for (const ErrorCase &test : cases) {
fixture.Write(test.text);
const DbcParseResult result = DbcParser().ParseFileWithDiagnostics(fixture.path.string());
CHECK(result.HasErrors() && result.database.frames.empty(),
"Failed parse never returns a partial database");
const DbcDiagnostic &diagnostic = result.diagnostics.back();
CHECK(diagnostic.severity == DbcDiagnosticSeverity::Error && diagnostic.code == test.code,
"Error has expected severity and code");
QCOMPARE(diagnostic.filePath, fixture.path.string());
QCOMPARE(diagnostic.lineNumber, test.line);
CHECK(!diagnostic.message.empty(), "Error includes a message");
const bool hasWarning = std::string(test.text).find("UNKNOWN_") == 0U;
QCOMPARE(result.diagnostics.size(), hasWarning ? std::size_t(2U) : std::size_t(1U));
try {
DbcParser().ParseFile(fixture.path.string());
CHECK(false, "Legacy parser must throw");
} catch (const std::runtime_error &error) {
QCOMPARE(std::string(error.what()), fixture.path.string() + ":" +
std::to_string(test.line) + ": " + diagnostic.message);
}
}
const std::string missingPath = fixture.path.string() + ".missing";
const DbcParseResult missing = DbcParser().ParseFileWithDiagnostics(missingPath);
CHECK(missing.HasErrors() && missing.database.frames.empty(), "Missing file returns error");
QCOMPARE(missing.diagnostics.size(), std::size_t(1U));
CHECK(missing.diagnostics[0].code == DbcDiagnosticCode::FileOpenError,
"Missing file has a distinct code");
QCOMPARE(missing.diagnostics[0].lineNumber, std::size_t(0U));
QCOMPARE(missing.diagnostics[0].filePath, missingPath);
QCOMPARE(missing.diagnostics[0].message, "Failed to open DBC file: " + missingPath);
try {
DbcParser().ParseFile(missingPath);
CHECK(false, "Legacy API throws on missing file");
} catch (const std::runtime_error &error) {
QCOMPARE(std::string(error.what()), missing.diagnostics[0].message);
}
const std::string directoryPath = fixture.path.parent_path().string();
const DbcParseResult directory = DbcParser().ParseFileWithDiagnostics(directoryPath);
CHECK(directory.HasErrors() && directory.database.frames.empty(),
"Directory cannot be parsed as a file");
QCOMPARE(directory.diagnostics.size(), std::size_t(1U));
CHECK(directory.diagnostics[0].code == DbcDiagnosticCode::FileReadError ||
directory.diagnostics[0].code == DbcDiagnosticCode::FileOpenError,
"Directory produces a file-level error on supported platforms");
QCOMPARE(directory.diagnostics[0].lineNumber, std::size_t(0U));
QCOMPARE(directory.diagnostics[0].filePath, directoryPath);
#ifdef __linux__
CHECK(directory.diagnostics[0].code == DbcDiagnosticCode::FileReadError,
"Linux directory input exercises the read-error path");
QCOMPARE(directory.diagnostics[0].message, "Failed to read DBC file: " + directoryPath);
#endif
try {
DbcParser().ParseFile(directoryPath);
CHECK(false, "Legacy API throws on unreadable input");
} catch (const std::runtime_error &error) {
QCOMPARE(std::string(error.what()), directory.diagnostics[0].message);
}
fixture.Write("BO_ 1 Frame: 1 ECU\n");
CHECK(DbcParser().ParseFileWithDiagnostics(fixture.path.string()).diagnostics.empty(),
"Diagnostics belong to an independent parse session");
}
void DbcParserTest::diagnosticUnsupportedStatements() {
Fixture fixture;
const std::string definitions =
"BO_ 1 Frame: 1 ECU\nSG_ Value : 0|8@1+ (2,10) [10|520] \"V\" ECU\n"
"CM_ SG_ 1 Value \"Signal comment\";\nVAL_ 1 Value 1 \"On\";\n";
const std::vector<std::string> statements = {
"SIG_VALTYPE_ 1 Value : 1;", "SIG_VALTYPE_ 1 Value : 2;",
"BA_ \"SignalType\" SG_ 1 Value 1;",
"BA_DEF_ SG_ \"SignalType\" INT 0 1;",
"BA_DEF_DEF_ \"SignalType\" 0;",
"BA_DEF_REL_ BU_SG_REL_ \"Relation\" INT 0 1;",
"BA_REL_ \"Relation\" BU_SG_REL_ ECU SG_ 1 Value 1;",
"SGTYPE_ Type : 8@1+ (1,0) [0|255] \"\" 0, Table;",
"SIG_TYPE_REF_ 1 Value : Type;",
"EV_ Environment : 0 [0|1] \"\" 0 1 DUMMY_NODE_VECTOR0 ECU;",
"ENVVAR_DATA_ Environment : 4;",
"VAL_TABLE_ Table 0 \"Off\" 1 \"On\";",
"BO_TX_BU_ 1 : ECU;", "VENDOR_EXTENSION_ data;", "UNKNOWN_"
};
for (const std::string &statement : statements) {
fixture.Write(definitions + statement + '\n');
const DbcParseResult result = DbcParser().ParseFileWithDiagnostics(fixture.path.string());
CHECK(!result.HasErrors(), statement);
QCOMPARE(result.diagnostics.size(), std::size_t(1U));
const DbcDiagnostic &warning = result.diagnostics[0];
CHECK(warning.severity == DbcDiagnosticSeverity::Warning &&
warning.code == DbcDiagnosticCode::UnsupportedStatement, statement);
QCOMPARE(warning.filePath, fixture.path.string());
QCOMPARE(warning.lineNumber, std::size_t(5U));
const std::string keyword = statement.substr(0U, statement.find(' '));
CHECK(warning.message.find("'" + keyword + "'") != std::string::npos, statement);
const DecodeDatabase runtime = DbcDecodeBuilder().Build(result.database);
RawCanFrame raw;
raw.canId = 1U;
raw.data = {1U};
const DecodedFrameValue decoded = DbcDecoder().Decode(runtime, raw);
CHECK(decoded.valid && decoded.signals.size() == 1U && decoded.signals[0].valid,
"Warnings preserve supported signal decoding");
QCOMPARE(decoded.signals[0].physicalValue, 12.0);
CHECK(decoded.signals[0].GetValueDescription() &&
*decoded.signals[0].GetValueDescription() == "On",
"Warnings preserve VAL_ descriptions");
QCOMPARE(runtime.frames[0].signals[0].comment, std::string("Signal comment"));
}
}
void DbcParserTest::diagnosticNamespaceBoundaries() {
Fixture fixture;
const std::vector<std::string> endings = {
"BS_\n", "BS_:\n", "BS_ :\n", "BU_\n", "BU_:\n", "BU_ : ECU\n",
"BO_ 1 Frame: 1 ECU\n", "VENDOR_EXTENSION_ data;\n"
};
for (const std::string &ending : endings) {
fixture.Write("NS_ :\n\tSG_MUL_VAL_\n\nBA_\n" + ending + "SIG_VALTYPE_\n");
const DbcParseResult result = DbcParser().ParseFileWithDiagnostics(fixture.path.string());
CHECK(!result.HasErrors(), ending);
const std::size_t expectedWarnings = ending.find("VENDOR_") == 0U ? 2U : 1U;
QCOMPARE(result.diagnostics.size(), expectedWarnings);
QCOMPARE(result.diagnostics.back().lineNumber, std::size_t(6U));
CHECK(result.diagnostics.back().message.find("SIG_VALTYPE_") != std::string::npos,
"Unsupported keyword outside NS_ must produce a warning");
}
for (const std::string &definition : {std::string("BO_"), std::string("SG_")}) {
fixture.Write("NS_:\n\tBA_\n\t" + definition + '\n');
const DbcParseResult result = DbcParser().ParseFileWithDiagnostics(fixture.path.string());
CHECK(result.HasErrors(), "Malformed definition ends NS_ instead of being hidden");
QCOMPARE(result.diagnostics.size(), std::size_t(1U));
QCOMPARE(result.diagnostics[0].lineNumber, std::size_t(3U));
}
}
void DbcParserTest::diagnosticFormatting() {
Fixture fixture;
fixture.path += " with spaces";
const DbcParser parser;
for (const std::string &text : {std::string(), std::string(" \t\r\n\n")}) {
fixture.Write(text);
const DbcParseResult empty = parser.ParseFileWithDiagnostics(fixture.path.string());
CHECK(!empty.HasErrors() && empty.diagnostics.empty() && empty.database.frames.empty(),
"Empty and whitespace-only inputs are valid");
}
fixture.Write("\r\n\tVENDOR_EXTENSION_ data;\r\n\r\nUNKNOWN_");
const std::string relativePath = std::filesystem::relative(fixture.path).string();
const DbcParseResult warnings = parser.ParseFileWithDiagnostics(relativePath);
CHECK(!warnings.HasErrors() && warnings.database.frames.empty(), "Warnings-only input");
QCOMPARE(warnings.diagnostics.size(), std::size_t(2U));
QCOMPARE(warnings.diagnostics[0].lineNumber, std::size_t(2U));
QCOMPARE(warnings.diagnostics[1].lineNumber, std::size_t(4U));
for (const DbcDiagnostic &warning : warnings.diagnostics)
QCOMPARE(warning.filePath, relativePath);
fixture.Write("UNKNOWN_ first;\nBO_ invalid Frame: 1 ECU\n"
"UNKNOWN_ after_error;\nSG_ broken\n");
const DbcParseResult stopped = parser.ParseFileWithDiagnostics(relativePath);
CHECK(stopped.HasErrors() && stopped.database.frames.empty(), "First error stops parsing");
QCOMPARE(stopped.diagnostics.size(), std::size_t(2U));
QCOMPARE(stopped.diagnostics[0].lineNumber, std::size_t(1U));
QCOMPARE(stopped.diagnostics[1].lineNumber, std::size_t(2U));
QCOMPARE(stopped.diagnostics[1].filePath, relativePath);
fixture.Write("VAL_ 1 Missing 0 \"Off\";\nBO_ 1 Frame: 1 ECU\nUNKNOWN_ data;\n");
const DbcParseResult deferred = parser.ParseFileWithDiagnostics(relativePath);
CHECK(deferred.HasErrors() && deferred.database.frames.empty(), "Deferred error rejects database");
QCOMPARE(deferred.diagnostics.size(), std::size_t(2U));
CHECK(deferred.diagnostics[0].severity == DbcDiagnosticSeverity::Warning &&
deferred.diagnostics[1].severity == DbcDiagnosticSeverity::Error,
"Deferred errors follow warnings in detection order");
QCOMPARE(deferred.diagnostics[0].lineNumber, std::size_t(3U));
QCOMPARE(deferred.diagnostics[1].lineNumber, std::size_t(1U));
QCOMPARE(deferred.diagnostics[1].filePath, relativePath);
fixture.Write("BO_ 1 Frame: 1 ECU");
const DbcParseResult recovered = parser.ParseFileWithDiagnostics(relativePath);
CHECK(!recovered.HasErrors() && recovered.diagnostics.empty() && recovered.database.frames.size() == 1U,
"Same parser has clean state after errors and accepts missing final newline");
}
void DbcParserTest::validFrames() {
Fixture fixture;
struct ValidCase {