This commit is contained in:
deeaitch committed 2026-10-23 20:28:55 -04:00
1 parent a702cffd93
commit 8d06165979
18 files changed
+611 -215

No files matched your search

+237 -44
View File
@@ -103,6 +103,8 @@ private Q_SLOTS:
void diagnosticFormatting();
void multiplexing();
void multiplexingBoundaries();
void extendedMultiplexing();
void extendedMultiplexingErrors();
void validFrames();
void invalidFrames();
void duplicateFrames();
@@ -458,41 +460,40 @@ void DbcParserTest::diagnosticErrors() {
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}
};
{"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::InvalidMultiplexing, 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) [-128|127] \"\" 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());
@@ -980,15 +981,14 @@ void DbcParserTest::multiplexing() {
"Unavailable selector does not imply inactive branch");
const std::vector<std::string> invalid = {
first,
selector + "SG_ Other M : 16|8@1+ (1,0) [0|255] \"\" ECU\n",
"SG_ Mode M : 0|8@1- (1,0) [-128|127] \"\" ECU\n",
selector + "SG_ TooLarge m256 : 8|8@1+ (1,0) [0|255] \"\" ECU\n",
selector + first + "SG_ Same m1 : 15|8@0+ (1,0) [0|255] \"\" ECU\n",
selector + "SG_ Collision m1 : 0|8@1+ (1,0) [0|255] \"\" ECU\n",
selector + first + "SG_ Always : 8|8@1+ (1,0) [0|255] \"\" ECU\n",
"SG_MUL_VAL_ 1 First Mode 1-2;\n"
};
first,
selector + "SG_ Other M : 16|8@1- (1,0) [-128|127] \"\" ECU\n",
"SG_ Mode M : 0|8@1- (1,0) [-128|127] \"\" ECU\n",
selector + "SG_ TooLarge m256 : 8|8@1+ (1,0) [0|255] \"\" ECU\n",
selector + first + "SG_ Same m1 : 15|8@0+ (1,0) [0|255] \"\" ECU\n",
selector + "SG_ Collision m1 : 0|8@1+ (1,0) [0|255] \"\" ECU\n",
selector + first + "SG_ Always : 8|8@1+ (1,0) [0|255] \"\" ECU\n",
"SG_MUL_VAL_ 1 First Mode 1-2;\n"};
for (const std::string &signals : invalid) {
fixture.Write("BO_ 1 Frame: 3 ECU\n" + signals);
bool rejected = false;
@@ -1374,6 +1374,199 @@ void DbcParserTest::invalidValueDescriptions() {
}
}
void DbcParserTest::extendedMultiplexing() {
Fixture fixture;
// Forward references, multiple ranges, repeated range statements, and nested siblings.
fixture.Write(
"SG_MUL_VAL_ 1 Child Root 1-2, 4-4;\n"
"SG_MUL_VAL_ 1 Leaf Child 3-5;\n"
"SG_MUL_VAL_ 1 Leaf Child 7-7;\n"
"SG_MUL_VAL_ 1 Sibling Root 6-6;\n"
"SG_MUL_VAL_ 1 Other Sibling 3-5;\n"
"BO_ 1 Frame: 4 ECU\n"
"SG_ Leaf m99 : 16|8@1- (2,1) [-128|127] \"\" ECU\n"
"SG_ Other m0 : 23|8@0+ (1,0) [0|255] \"\" ECU\n"
"SG_ Child m1M : 8|8@1+ (10,3) [0|255] \"\" ECU\n"
"SG_ Sibling m6M : 15|8@0+ (1,0) [0|255] \"\" ECU\n"
"SG_ Root M : 0|8@1+ (10,3) [0|255] \"\" ECU\n"
"SG_ Common : 24|8@1+ (1,0) [0|255] \"\" ECU\n");
const DbcDatabase parsed = DbcParser().ParseFile(fixture.path.string());
const DecodeDatabase database = DbcDecodeBuilder().Build(parsed);
const std::unique_ptr<TreeNode> tree = DbcTreeBuilder().Build(parsed);
const Multiplexing &metadata = tree->GetChild(0U)->GetChild(0U)->GetSignal()->multiplexing;
CHECK(metadata.selector == "Child" && metadata.ranges.size() == 2U &&
metadata.ranges[1].minimum == 7U,
"Tree preserves ranges and selector name");
CHECK(database.GetFrames()[0].signals[2].multiplexing.role == MultiplexRole::BranchSelector,
"Runtime preserves nested selector role");
RawCanFrame raw;
raw.canId = 1U;
raw.data = {2U, 4U, 255U, 8U};
DecodedFrameValue result = DbcDecoder().Decode(database, raw);
CHECK(result.signals[0].valid && result.signals[0].physicalValue == -1.0 &&
result.signals[1].status == SignalDecodeStatus::Inactive && result.signals[2].valid &&
result.signals[3].status == SignalDecodeStatus::Inactive && result.signals[5].valid,
"Nested raw conditions select one path and preserve definition order");
const std::vector<SignalKey> selection = {SignalKey(FrameKey(1U, false), "Leaf")};
result = DbcDecoder().Decode(database, raw, selection);
CHECK(result.signals.size() == 1U && result.signals[0].valid,
"Selection reads all ancestors internally");
for (std::uint8_t root: {1U, 2U, 4U}) {
for (std::uint8_t child: {3U, 5U, 7U}) {
raw.data = {root, child, 9U, 8U};
CHECK(DbcDecoder().Decode(database, raw, selection).signals[0].valid,
"Inclusive endpoints and separated ranges");
}
}
raw.data = {6U, 4U, 9U, 8U};
result = DbcDecoder().Decode(database, raw);
CHECK(result.signals[0].status == SignalDecodeStatus::Inactive && result.signals[1].valid &&
result.signals[3].valid,
"Overlapping sibling selector path becomes active");
raw.data = {0U};
CHECK(DbcDecoder().Decode(database, raw, selection).signals[0].status == SignalDecodeStatus::Inactive,
"Inactive root does not require child selector bytes");
raw.data = {1U};
CHECK(DbcDecoder().Decode(database, raw, selection).signals[0].status == SignalDecodeStatus::SelectorUnavailable,
"Active root with missing child selector");
raw.data = {1U, 3U};
CHECK(DbcDecoder().Decode(database, raw, selection).signals[0].status == SignalDecodeStatus::InsufficientPayload,
"Active nested leaf needs its bytes");
raw.data = {1U, 6U};
CHECK(DbcDecoder().Decode(database, raw, selection).signals[0].status == SignalDecodeStatus::Inactive,
"Gap between explicit ranges does not fall back to mN");
raw.data.clear();
CHECK(DbcDecoder().Decode(database, raw, selection).signals[0].status == SignalDecodeStatus::SelectorUnavailable,
"Missing root selector");
fixture.Write("BO_ 1 Frame: 9 ECU\n"
"SG_ Root M : 0|64@1+ (1,0) [0|1] \"\" ECU\n"
"SG_ Leaf m0 : 64|8@1+ (1,0) [0|255] \"\" ECU\n"
"SG_MUL_VAL_ 1 Leaf Root 18446744073709551614-18446744073709551615;\n");
raw.data.assign(9U, 255U);
CHECK(DbcDecoder().Decode(DbcDecodeBuilder().Build(DbcParser().ParseFile(fixture.path.string())), raw).signals[1].valid,
"64-bit ranges retain exact UINT64_MAX without expansion");
fixture.Write("BO_ 1 Frame: 4 ECU\n"
"SG_ Root M : 0|8@1+ (1,0) [0|255] \"\" ECU\n"
"SG_ Second M : 8|8@1+ (1,0) [0|255] \"\" ECU\n"
"SG_ A m0 : 16|8@1+ (1,0) [0|255] \"\" ECU\n"
"SG_ B m0 : 24|8@1+ (1,0) [0|255] \"\" ECU\n"
"SG_MUL_VAL_ 1 A Root 1-1;\nSG_MUL_VAL_ 1 B Second 2-2;\n");
raw.data = {1U, 2U, 9U, 10U};
result = DbcDecoder().Decode(DbcDecodeBuilder().Build(DbcParser().ParseFile(fixture.path.string())), raw);
CHECK(result.signals[2].valid && result.signals[3].valid, "Independent explicit selectors");
fixture.Write("BO_ 1 Deep: 5 ECU\n"
"SG_ Root M : 0|8@1+ (1,0) [0|255] \"\" ECU\n"
"SG_ Child m1M : 8|8@1+ (1,0) [0|255] \"\" ECU\n"
"SG_ Grandchild m2M : 16|8@1+ (1,0) [0|255] \"\" ECU\n"
"SG_ Leaf : 24|8@1+ (1,0) [0|255] \"\" ECU\n"
"SG_MUL_VAL_ 1 Grandchild Child 2-2;\n"
"SG_MUL_VAL_ 1 Leaf Grandchild 3-3;\n");
const DecodeDatabase deep = DbcDecodeBuilder().Build(DbcParser().ParseFile(fixture.path.string()));
raw.data = {1U, 2U, 3U, 9U, 0U};
CHECK(DbcDecoder().Decode(deep, raw).signals[3].valid, "Three selector levels and an unmarked extended branch");
raw.data = {1U, 0U};
CHECK(DbcDecoder().Decode(deep, raw).signals[3].status == SignalDecodeStatus::Inactive,
"Inactive middle ancestor skips missing descendant bytes");
// Marker fallback also works on a nested selector without its own SG_MUL_VAL_.
fixture.Write("BO_ 1 Frame: 3 ECU\n"
"SG_ Root M : 0|8@1+ (1,0) [0|255] \"\" ECU\n"
"SG_ Child m1M : 8|8@1+ (1,0) [0|255] \"\" ECU\n"
"SG_ Leaf m2 : 16|8@1+ (1,0) [0|255] \"\" ECU\n"
"SG_MUL_VAL_ 1 Leaf Child 2-2;\n");
raw.data = {1U, 2U, 9U};
CHECK(DbcDecoder().Decode(DbcDecodeBuilder().Build(DbcParser().ParseFile(fixture.path.string())), raw).signals[2].valid,
"mNM retains implicit root fallback");
}
void DbcParserTest::extendedMultiplexingErrors() {
Fixture fixture;
const std::string definitions =
"BO_ 1 Frame: 3 ECU\n"
"SG_ Root M : 0|8@1+ (1,0) [0|255] \"\" ECU\n"
"SG_ Child m1M : 8|8@1+ (1,0) [0|255] \"\" ECU\n"
"SG_ Leaf m2 : 16|8@1+ (1,0) [0|255] \"\" ECU\n";
for (const std::string statement: {
"SG_MUL_VAL_ 1 Leaf Root 3-2;", "SG_MUL_VAL_ 1 Leaf Root -1-2;",
"SG_MUL_VAL_ 1 Leaf Root 1-256;", "SG_MUL_VAL_ 1 Leaf Root 1-2,;",
"SG_MUL_VAL_ 1 Leaf Root 1-2 trailing;", "SG_MUL_VAL_ 1 Leaf Root 1-2",
"SG_MUL_VAL_ 1 Leaf Root 0-18446744073709551616;",
"SG_MUL_VAL_ 1 Leaf Missing 1-2;", "SG_MUL_VAL_ 1 Missing Root 1-2;",
"SG_MUL_VAL_ 2 Leaf Root 1-2;", "SG_MUL_VAL_ 1 Leaf Leaf 1-2;"}) {
fixture.Write(statement + "\n" + definitions);
const DbcParseResult result = DbcParser().ParseFileWithDiagnostics(fixture.path.string());
CHECK(result.HasErrors() && result.database.frames.empty(), statement);
CHECK(result.diagnostics.back().code == DbcDiagnosticCode::InvalidMultiplexing &&
result.diagnostics.back().lineNumber == 1U,
"Extended error retains source statement location");
}
for (const std::string statements: {
"SG_MUL_VAL_ 1 Root Child 1-1;\nSG_MUL_VAL_ 1 Child Root 1-1;\n",
"SG_MUL_VAL_ 1 Child Child 1-1;\n",
"SG_MUL_VAL_ 1 Leaf Root 1-1;\nSG_MUL_VAL_ 1 Leaf Child 1-1;\n"}) {
fixture.Write(definitions + statements);
CHECK(DbcParser().ParseFileWithDiagnostics(fixture.path.string()).HasErrors(), "Cyclic or conflicting parents rejected");
}
// Range intersection and inherited root conditions must both protect overlaps.
const std::string overlapping =
"BO_ 1 Frame: 4 ECU\n"
"SG_ Root M : 0|8@1+ (1,0) [0|255] \"\" ECU\n"
"SG_ Child m1M : 8|8@1+ (1,0) [0|255] \"\" ECU\n"
"SG_ A m0 : 16|8@1+ (1,0) [0|255] \"\" ECU\n"
"SG_ B m0 : 23|8@0+ (1,0) [0|255] \"\" ECU\n"
"SG_MUL_VAL_ 1 A Child 2-4;\n"
"SG_MUL_VAL_ 1 Child Root 1-1;\n";
for (const std::string condition: {"SG_MUL_VAL_ 1 B Child 4-6;\n",
"SG_MUL_VAL_ 1 B Root 1-1;\n"}) {
fixture.Write(overlapping + condition);
CHECK(DbcParser().ParseFileWithDiagnostics(fixture.path.string()).HasErrors(),
"Overlapping ranges or compatible ancestors cannot share bits");
}
fixture.Write(overlapping + "SG_MUL_VAL_ 1 B Child 5-6;\n");
CHECK(!DbcParser().ParseFileWithDiagnostics(fixture.path.string()).HasErrors(),
"Disjoint adjacent ranges may share bits");
fixture.Write(overlapping + "SG_ Second M : 24|8@1+ (1,0) [0|255] \"\" ECU\n"
"SG_MUL_VAL_ 1 B Second 2-4;\n");
const DbcParseResult independent = DbcParser().ParseFileWithDiagnostics(fixture.path.string());
CHECK(independent.HasErrors() && independent.diagnostics.back().message ==
"Signals overlap without mutually exclusive multiplexing conditions.",
"Independent selector conditions do not imply exclusion");
// Validate manually constructed runtime graphs with the same rules.
fixture.Write(definitions + "SG_MUL_VAL_ 1 Leaf Child 2-3;\n");
const DecodeDatabase valid = DbcDecodeBuilder().Build(DbcParser().ParseFile(fixture.path.string()));
for (unsigned kind = 0U; kind < 6U; ++kind) {
std::vector<DecodeFrame> frames = valid.GetFrames();
Multiplexing &leaf = frames[0].signals[2].multiplexing;
switch (kind) {
case 0:
leaf.selector = "Missing";
break;
case 1:
leaf.ranges[0] = {4U, 3U};
break;
case 2:
leaf.ranges[0] = {0U, 256U};
break;
case 3:
leaf.ranges.clear();
break;
case 4:
frames[0].signals[0].multiplexing.selector = "Child";
frames[0].signals[0].multiplexing.ranges = {{1U, 1U}};
break;
case 5:
frames[0].signals[1].valueType = ValueType::Signed;
break;
}
QVERIFY_THROWS_EXCEPTION(std::invalid_argument, (void) DecodeDatabase(frames));
}
}
int RunParserTests(int argc, char **argv) {
DbcParserTest tests;
return QTest::qExec(&tests, argc, argv);