Closes #13
This commit is contained in:
1 parent
bce707daf6
commit
51fcdb88ac
174 files changed
+5220
-2213
No files matched your search
@@ -54,6 +54,17 @@ found; insufficient payloads are checked through signal validity.
|
||||
|
||||
## Parser coverage
|
||||
|
||||
Structured diagnostic tests cover severity, code, supplied path, and physical
|
||||
line numbers for skipped syntax and immediate/deferred errors; warnings preceding
|
||||
an error are retained, and failed parses return an empty database. Common headers
|
||||
and namespace symbol lists stay quiet. Tests also check file-open errors, fresh
|
||||
parse sessions, and compatibility with the legacy throwing API.
|
||||
The diagnostic audit also covers 15 unsupported-statement variants, all namespace
|
||||
termination forms, CRLF and missing final newlines, relative paths with spaces,
|
||||
first-error stopping, deferred diagnostic ordering, duplicate definitions/values,
|
||||
selector ranges, overlaps, and preservation of supported decoding and metadata.
|
||||
On Linux, reading a directory exercises the distinct `FileReadError` path.
|
||||
|
||||
Parser tests cover standard and extended CAN IDs, whitespace, payload storage
|
||||
boundaries, missing fields, malformed and overflowing numbers, reserved ID bits,
|
||||
error paths and line numbers, and integration with signals and comments.
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in new issue
Block a user