4#include "dbc_statement_handler.h"
13#define CHECK(condition, name) QVERIFY2((condition), std::string(name).c_str())
17 std::filesystem::path path = std::filesystem::temp_directory_path() /
18 (
"dbc_parser_test_" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()) +
".dbc");
20 std::error_code error;
21 std::filesystem::remove(path, error);
23 void Write(
const std::string &text)
const {
24 std::ofstream output(path);
27 throw std::runtime_error(
"Cannot write test fixture.");
33 inline static int instances = 0;
34 inline static int calls = 0;
35 CountingHandler() { ++instances; }
36 void Handle(
const std::string &,
DbcParseContext &context)
const override {
38 context.database.
frames.emplace_back();
42std::string SignalLine(
const std::string &start,
const std::string &length,
43 const std::string &order,
const std::string &factor,
44 const std::string &offset,
const std::string &minimum,
45 const std::string &maximum,
const std::string &name =
"Value") {
46 return "SG_ " + name +
" : " + start +
"|" + length +
"@" + order +
47 " (" + factor +
"," + offset +
") [" + minimum +
"|" + maximum +
"] \"\" ECU";
50class DbcParserTest :
public QObject {
53 void RejectSignal(
const Fixture &fixture,
const std::string &line,
unsigned payload = 8U) {
54 fixture.Write(
"BO_ 1 Frame: " + std::to_string(payload) +
" ECU\n\n" + line +
"\n");
57 CHECK(
false,
"Reject " + line);
58 }
catch (
const std::runtime_error &error) {
59 const std::string prefix = fixture.path.string() +
":3: ";
60 CHECK(std::string(error.what()).find(prefix) == 0U &&
61 std::string(error.what()).size() > prefix.size(),
67 void multiplexingBoundaries();
70 void duplicateFrames();
71 void duplicateSignals();
72 void distinctDefinitions();
74 void invalidSignals();
77 void independentSessions();
78 void valueDescriptions();
79 void invalidValueDescriptions();
82void DbcParserTest::validFrames() {
90 const ValidCase valid[] = {
91 {
"BO_ 0 Frame: 0 ECU", 0U,
false, 0U},
92 {
"BO_ 2047 Frame: 8 ECU", 2047U,
false, 8U},
93 {
"BO_ 2048 Frame: 64 ECU", 2048U,
true, 64U},
94 {
"BO_ 536870911 Frame: 255 ECU", 0x1FFFFFFFU,
true, 255U},
95 {
"BO_ 2147483649 Frame: 8 ECU", 1U,
true, 8U},
96 {
"BO_ 2684354559 Frame: 8 ECU", 0x1FFFFFFFU,
true, 8U},
97 {
" BO_\t256\tFrame :\t8\tECU \r", 256U,
false, 8U}};
98 for (
const ValidCase &test: valid) {
99 fixture.Write(std::string(test.line) +
'\n');
102 QCOMPARE(database.
frames.size(), std::size_t(1U));
104 QCOMPARE(frame.
name, std::string(
"Frame"));
105 QCOMPARE(frame.
canId, test.id);
107 QCOMPARE(
static_cast<unsigned>(frame.
dlc), test.length);
109 }
catch (
const std::exception &error) { CHECK(
false, error.what()); }
113void DbcParserTest::invalidFrames() {
115 const char *invalid[] = {
116 "BO_ invalid",
"BO_",
"BO_ 1",
"BO_ 1 : 8 ECU",
"BO_ 1 Frame 8 ECU",
117 "BO_ 1 Frame:",
"BO_ 1 Frame: 8",
"BO_ 1 Frame: 8 ECU extra",
118 "BO_ 1 Two Names: 8 ECU",
"BO_ -1 Frame: 8 ECU",
"BO_ +1 Frame: 8 ECU",
119 "BO_ 1x Frame: 8 ECU",
"BO_ 0x123 Frame: 8 ECU",
"BO_ 1.5 Frame: 8 ECU",
120 "BO_ 4294967296 Frame: 8 ECU",
"BO_ 999999999999999999999 Frame: 8 ECU",
121 "BO_ 536870912 Frame: 8 ECU",
"BO_ 1073741824 Frame: 8 ECU",
122 "BO_ 2684354560 Frame: 8 ECU",
"BO_ 4294967295 Frame: 8 ECU",
123 "BO_ 1 Frame: -1 ECU",
"BO_ 1 Frame: +8 ECU",
"BO_ 1 Frame: 8x ECU",
124 "BO_ 1 Frame: 8.5 ECU",
"BO_ 1 Frame: 256 ECU",
125 "BO_ 1 Frame: 4294967296 ECU",
"BO_\tinvalid"};
126 for (
const char *line: invalid) {
127 fixture.Write(std::string(
"VERSION \"test\"\n\n") + line +
'\n');
131 }
catch (
const std::runtime_error &error) {
132 const std::string prefix = fixture.path.string() +
":3: ";
133 CHECK(std::string(error.what()).find(prefix) == 0U &&
134 std::string(error.what()).size() > prefix.size(),
140void DbcParserTest::duplicateFrames() {
142 struct DuplicateCase {
144 const char *duplicateId;
147 const DuplicateCase cases[] = {
148 {
"1",
"1",
"Duplicate standard frame CAN ID: 1"},
149 {
"2147483649",
"2147483649",
"Duplicate extended frame CAN ID: 1"},
150 {
"2048",
"2147485696",
"Duplicate extended frame CAN ID: 2048"},
151 {
"2147485696",
"2048",
"Duplicate extended frame CAN ID: 2048"},
152 {
"536870911",
"2684354559",
"Duplicate extended frame CAN ID: 536870911"}
155 for (
const DuplicateCase &test : cases) {
156 fixture.Write(
"BO_ " + std::string(test.firstId) +
" First: 8 ECU\n"
157 "BO_ 2 Other: 8 ECU\n\n"
158 " BO_\t" + test.duplicateId +
"\tSecond: 8 ECU\n");
161 CHECK(
false,
"Duplicate frame must be rejected");
162 }
catch (
const std::runtime_error &error) {
163 QCOMPARE(std::string(error.what()), fixture.path.string() +
":4: " + test.message);
166 fixture.Write(
"BO_ 1 First: 8 ECU\n");
167 QCOMPARE(parser.
ParseFile(fixture.path.string()).
frames.size(), std::size_t(1U));
170void DbcParserTest::duplicateSignals() {
172 const std::string first =
"SG_ State : 0|8@1+ (1,0) [0|255] \"\" ECU\n";
173 const std::string duplicates[] = {
175 " SG_\tState : 16|8@1+ (2,1) [0|511] \"V\" ECU\n",
176 "SG_ State m1 : 16|8@1+ (1,0) [0|255] \"\" ECU\n"
178 for (
const std::string &duplicate : duplicates) {
179 fixture.Write(
"BO_ 1 Frame: 8 ECU\n" + first +
180 "SG_ Other : 8|8@1+ (1,0) [0|255] \"\" ECU\n\n" + duplicate);
183 CHECK(
false,
"Duplicate signal must be rejected");
184 }
catch (
const std::runtime_error &error) {
185 QCOMPARE(std::string(error.what()), fixture.path.string() +
186 ":5: Duplicate signal name 'State' in frame 'Frame'.");
191void DbcParserTest::distinctDefinitions() {
193 fixture.Write(
"BO_ 1 Shared: 1 ECU\n"
194 "SG_ State : 0|8@1+ (1,0) [0|255] \"\" ECU\n"
195 "BO_ 2147483649 Shared: 1 ECU\n"
196 "SG_ State : 0|8@1+ (1,0) [0|255] \"\" ECU\n"
197 "BO_ 2 Shared: 2 ECU\n"
198 "SG_ State : 0|8@1+ (1,0) [0|255] \"\" ECU\n"
199 "SG_ state : 8|8@1+ (1,0) [0|255] \"\" ECU\n"
200 "CM_ BO_ 1 \"Standard\";\n"
201 "CM_ BO_ 2147483649 \"Extended\";\n"
202 "CM_ SG_ 1 State \"Standard state\";\n"
203 "CM_ SG_ 2147483649 State \"Extended state\";\n");
205 QCOMPARE(parsed.
frames.size(), std::size_t(3U));
206 QCOMPARE(parsed.
frames[0].canId, parsed.
frames[1].canId);
207 CHECK(!parsed.
frames[0].isExtended && parsed.
frames[1].isExtended,
208 "Standard and extended frames with the same numeric ID remain distinct");
209 QCOMPARE(parsed.
frames[0].comment, std::string(
"Standard"));
210 QCOMPARE(parsed.
frames[1].comment, std::string(
"Extended"));
211 QCOMPARE(parsed.
frames[0].signals[0].comment, std::string(
"Standard state"));
212 QCOMPARE(parsed.
frames[1].signals[0].comment, std::string(
"Extended state"));
213 QCOMPARE(parsed.
frames[2].signals.size(), std::size_t(2U));
219 QVERIFY(standard.
valid);
223 QVERIFY(extended.
valid);
227void DbcParserTest::integration() {
230 "BO_TX_BU_ 256 : ECU;\nBO_ 256 Frame: 8 ECU\n"
231 " SG_ Speed : 0|16@1+ (0.5,1) [0|100] \"rpm\" ECU\n"
232 "CM_ BO_ 256 \"Frame comment\";\nCM_ SG_ 256 Speed \"Signal comment\";\n"
233 "BO_ 257 Second: 8 ECU\n");
235 CHECK(database.
frames.size() == 2U && database.
frames[0].signals.size() == 1U &&
236 database.
frames[0].comment ==
"Frame comment" &&
237 database.
frames[0].signals[0].comment ==
"Signal comment",
239 fixture.Write(
"\nSG_ Speed : invalid\n");
242 CHECK(
false,
"Signal error location");
243 }
catch (
const std::runtime_error &error) {
244 CHECK(std::string(error.what()).find(fixture.path.string() +
":2: ") == 0U,
245 "Signal error location");
249void DbcParserTest::multiplexing() {
251 const std::string selector =
"SG_ Mode M : 0|8@1+ (10,3) [0|255] \"\" ECU\n";
252 const std::string first =
"SG_ First m1 : 8|8@1- (2,0) [-128|127] \"V\" ECU\n";
253 const std::string second =
"SG_ Second m2 : 15|8@0+ (1,0) [0|255] \"\" ECU\n";
254 const std::string common =
"SG_ Common : 16|8@1+ (1,0) [0|255] \"\" ECU\n";
255 fixture.Write(
"BO_ 1 Frame: 3 ECU\n" + first + second + common + selector +
256 "VAL_ 1 First -1 \"Negative\";\n");
259 CHECK(parsed.
frames[0].signals[3].multiplexing.role == MultiplexRole::Selector,
260 "Selector declared after branches");
262 CHECK(tree->GetChild(0U)->GetChild(0U)->GetSignal()->multiplexing.value == 1U,
263 "Tree preserves multiplexing");
266 raw.
data = {1U, 255U, 7U};
268 CHECK(result.signals[0].
valid && std::get<std::int64_t>(result.signals[0].rawValue) == -1 &&
269 result.signals[0].physicalValue == -2 && *result.signals[0].GetValueDescription() ==
"Negative",
270 "Active signed branch and VAL_ use exact raw selector");
271 CHECK(!result.signals[1].
valid && result.signals[1].status == SignalDecodeStatus::Inactive &&
272 result.signals[1].GetValueDescription() ==
nullptr && result.signals[2].
valid,
273 "Inactive branch and unconditional signal");
276 CHECK(result.signals[0].status == SignalDecodeStatus::Inactive && result.signals[1].
valid &&
277 std::get<std::uint64_t>(result.signals[1].rawValue) == 255U,
278 "Motorola branch becomes active");
281 CHECK(result.signals[0].status == SignalDecodeStatus::Inactive &&
282 result.signals[1].status == SignalDecodeStatus::Inactive &&
283 result.signals[2].status == SignalDecodeStatus::InsufficientPayload,
284 "Unmapped selector leaves branches inactive even with missing bytes");
287 CHECK(result.signals[0].status == SignalDecodeStatus::InsufficientPayload,
288 "Active branch requires payload");
291 CHECK(result.signals[0].status == SignalDecodeStatus::SelectorUnavailable &&
292 result.signals[3].status == SignalDecodeStatus::InsufficientPayload,
293 "Unavailable selector does not imply inactive branch");
295 const std::vector<std::string> invalid = {
297 selector +
"SG_ Other M : 16|8@1+ (1,0) [0|255] \"\" ECU\n",
298 "SG_ Mode M : 0|8@1- (1,0) [-128|127] \"\" ECU\n",
299 selector +
"SG_ TooLarge m256 : 8|8@1+ (1,0) [0|255] \"\" ECU\n",
300 selector + first +
"SG_ Same m1 : 15|8@0+ (1,0) [0|255] \"\" ECU\n",
301 selector +
"SG_ Collision m1 : 0|8@1+ (1,0) [0|255] \"\" ECU\n",
302 selector + first +
"SG_ Always : 8|8@1+ (1,0) [0|255] \"\" ECU\n",
303 "SG_MUL_VAL_ 1 First Mode 1-2;\n"
305 for (
const std::string &signals : invalid) {
306 fixture.Write(
"BO_ 1 Frame: 3 ECU\n" + signals);
307 bool rejected =
false;
310 }
catch (
const std::runtime_error &error) {
311 rejected = std::string(error.what()).find(fixture.path.string() +
":") == 0U;
313 CHECK(rejected,
"Invalid multiplexing rejected with location");
315 for (
const std::string marker : {
"m",
"m-1",
"m+1",
"m1M",
"m18446744073709551616",
"M m1"})
316 RejectSignal(fixture,
"SG_ Bad " + marker +
" : 8|8@1+ (1,0) [0|255] \"\" ECU");
319void DbcParserTest::multiplexingBoundaries() {
321 fixture.Write(
"BO_ 1 Frame: 9 ECU\n"
322 "SG_ Branch m18446744073709551615 : 64|8@1+ (1,0) [0|255] \"\" ECU\n"
323 "SG_ Mode M : 7|64@0+ (0,5) [0|1] \"\" ECU\n");
326 CHECK(runtime.frames[0].signals[0].multiplexing.value == std::numeric_limits<std::uint64_t>::max(),
327 "Exact uint64 branch metadata");
330 raw.
data = std::vector<std::uint8_t>(9U, 255U);
332 CHECK(result.signals[0].
valid && result.signals[1].
valid &&
333 std::get<std::uint64_t>(result.signals[1].rawValue) == std::numeric_limits<std::uint64_t>::max(),
334 "Motorola uint64 selector compares raw value before zero scaling");
337 CHECK(result.signals[0].status == SignalDecodeStatus::Inactive,
338 "Adjacent large integers remain distinct");
339 runtime.frames[0].signals[1].length = 0U;
341 CHECK(result.signals[0].status == SignalDecodeStatus::InvalidDefinition &&
342 result.signals[1].status == SignalDecodeStatus::InvalidDefinition,
343 "Malformed runtime selector distinguished from payload errors");
344 runtime.frames[0].signals.pop_back();
346 CHECK(result.signals[0].status == SignalDecodeStatus::InvalidDefinition,
347 "Missing runtime selector");
348 fixture.Write(
"BO_ 1 Frame: 2 ECU\n\n"
349 "SG_ Branch m2 : 8|8@1+ (1,0) [0|255] \"\" ECU\n"
350 "SG_ Mode M : 0|1@1+ (1,0) [0|1] \"\" ECU\n");
351 bool rejected =
false;
354 }
catch (
const std::runtime_error &error) {
355 rejected = std::string(error.what()).find(fixture.path.string() +
":3: ") == 0U;
357 CHECK(rejected,
"Deferred validation reports branch source line");
358 fixture.Write(
"BO_ 1 Frame: 2 ECU\n"
359 "SG_ A : 8|8@1+ (1,0) [0|255] \"\" ECU\n"
360 "SG_ B : 15|8@0+ (1,0) [0|255] \"\" ECU\n");
364 }
catch (
const std::runtime_error &) {
367 CHECK(rejected,
"Ordinary overlapping signals rejected");
370void DbcParserTest::invalidSignals() {
372 const std::string ordinary = SignalLine(
"0",
"8",
"1+",
"1",
"0",
"0",
"255");
373 for (
const std::string value: {
"-1",
"+1",
"0junk",
"0x10",
"1.5",
374 "4294967296",
"999999999999999999999"}) {
375 RejectSignal(fixture, SignalLine(value,
"8",
"1+",
"1",
"0",
"0",
"255"));
376 RejectSignal(fixture, SignalLine(
"0", value,
"1+",
"1",
"0",
"0",
"255"));
378 for (
const std::string length: {
"0",
"65"})
379 RejectSignal(fixture, SignalLine(
"0", length,
"1+",
"1",
"0",
"0",
"255"));
380 for (
const std::string order: {
"2+",
"9-",
"1",
"1x",
"1++",
"01+"})
381 RejectSignal(fixture, SignalLine(
"0",
"8", order,
"1",
"0",
"0",
"255"));
382 for (
const std::string value: {
"1junk",
"nan",
"NaN",
"inf",
"-inf",
"1e999",
"1e-999",
"0x1p2",
"",
"1e",
"1.2.3"}) {
383 RejectSignal(fixture, SignalLine(
"0",
"8",
"1+", value,
"0",
"0",
"255"));
384 RejectSignal(fixture, SignalLine(
"0",
"8",
"1+",
"1", value,
"0",
"255"));
385 RejectSignal(fixture, SignalLine(
"0",
"8",
"1+",
"1",
"0", value,
"255"));
386 RejectSignal(fixture, SignalLine(
"0",
"8",
"1+",
"1",
"0",
"0", value));
388 RejectSignal(fixture, SignalLine(
"0",
"8",
"1+",
"1",
"0",
"2",
"1"));
389 for (
const std::string name: {
"",
"Two Names",
"Value m1M"}) {
390 std::string line = ordinary;
391 line.replace(4U, 5U, name);
392 RejectSignal(fixture, line);
394 for (
const std::string broken: {
395 "SG_",
"SG_ Value",
"SG_ Value : 0|8@1+ (1,0) [0|255] ECU",
396 "SG_ Value : 0|8@1+ (1,0) [0|255] \"\"",
397 "SG_ Value : 0|8@1+ (1,0) [0|255] \"\" ECU,",
398 "SG_ Value : 0|8@1+ (1,0) [0|255] \"\" ECU extra"})
399 RejectSignal(fixture, broken);
400 RejectSignal(fixture, SignalLine(
"64",
"1",
"1+",
"1",
"0",
"0",
"1"));
401 RejectSignal(fixture, SignalLine(
"63",
"2",
"1+",
"1",
"0",
"0",
"3"));
402 RejectSignal(fixture, SignalLine(
"56",
"2",
"0+",
"1",
"0",
"0",
"3"));
403 RejectSignal(fixture, SignalLine(
"4294967295",
"64",
"1+",
"1",
"0",
"0",
"1"));
404 RejectSignal(fixture, ordinary, 0U);
405 for (
const char delimiter: {
'|',
'@',
'(',
')',
'[',
']',
'"'}) {
406 std::string line = ordinary;
407 line.erase(line.find(delimiter), 1U);
408 RejectSignal(fixture, line);
412void DbcParserTest::validSignals() {
415 fixture.Write(
"BO_ 1 Frame: 8 ECU\n" +
416 SignalLine(
"0",
"8",
"1+",
"0",
"0",
"0",
"0") +
"\n" +
417 SignalLine(
"8",
"8",
"1-",
"-1",
"0",
"-255",
"0",
"Negative") +
"\n");
419 CHECK(factors.
frames[0].signals.size() == 2U &&
420 factors.
frames[0].signals[0].factor == 0.0 &&
421 factors.
frames[0].signals[1].factor == -1.0,
422 "Zero and negative factors and equal range boundaries");
430 const SignalCase validSignals[] = {
431 {
"0",
"1",
"1+", 1U},
432 {
"7",
"1",
"0-", 1U},
433 {
"0",
"64",
"1+", 8U},
434 {
"7",
"64",
"0-", 8U},
435 {
"63",
"1",
"1+", 8U},
436 {
"56",
"1",
"0+", 8U},
437 {
"3",
"12",
"0+", 2U},
438 {
"2039",
"1",
"1+", 255U}};
439 for (
const SignalCase &test: validSignals) {
440 fixture.Write(
"BO_ 1 Frame: " + std::to_string(test.payload) +
" ECU\n" +
441 SignalLine(test.start, test.length, test.order,
".5e+1",
"-2.5E-1",
"-1.",
"+2e2") +
"\n");
445 QCOMPARE(signal.
startBit,
static_cast<std::uint32_t
>(std::stoul(test.start)));
446 QCOMPARE(signal.
length,
static_cast<std::uint32_t
>(std::stoul(test.length)));
448 QCOMPARE(signal.
isSigned, test.order[1] ==
'-');
449 QCOMPARE(signal.
factor, 5.0);
450 QCOMPARE(signal.
offset, -0.25);
451 QCOMPARE(signal.
minimum, -1.0);
452 QCOMPARE(signal.
maximum, 200.0);
453 }
catch (
const std::exception &error) { CHECK(
false, error.what()); }
455 fixture.Write(
"BO_ 1 Frame: 8 ECU\n"
456 "SG_\tValue\t:\t0 | 8 @1+ ( 1 , 0 ) [ 0 | 255 ] \"rpm\" ECU1,\tECU2\n");
460 CHECK(signal.
name ==
"Value" && signal.
unit ==
"rpm" &&
461 signal.
receivers == std::vector<std::string>({
"ECU1",
"ECU2"}),
462 "Signal tabs, spaces, and receiver list");
463 }
catch (
const std::exception &error) { CHECK(
false, error.what()); }
466void DbcParserTest::dispatch() {
467 CountingHandler::instances = 0;
468 CountingHandler::calls = 0;
472 dispatcher.RegisterType<CountingHandler>(
"TEST_");
473 CHECK(dispatcher.
Dispatch(
"TEST_ first", context) &&
474 dispatcher.
Dispatch(
"TEST_ second", context) &&
475 CountingHandler::instances == 1 && CountingHandler::calls == 2 &&
476 dispatchDatabase.
frames.size() == 2U,
477 "Factory dispatch and handler reuse");
478 CHECK(!dispatcher.
Dispatch(
"TEST_OTHER data", context) &&
479 !dispatcher.
Dispatch(
"BO_TX_BU_ 1 : ECU;", context) &&
480 !dispatcher.
Dispatch(
"VAL_TABLE_ States 0 \"Stopped\";", context) &&
481 CountingHandler::calls == 2,
482 "Exact and unknown keyword dispatch");
485void DbcParserTest::independentSessions() {
488 fixture.Write(
"BO_ 1 First: 8 ECU\n");
489 CHECK(parser.
ParseFile(fixture.path.string()).
frames.size() == 1U,
"First parse");
490 fixture.Write(
"SG_ Speed : invalid\n");
493 CHECK(
false,
"Independent parse context");
494 }
catch (
const std::runtime_error &error) {
495 CHECK(std::string(error.what()).find(
"Signal found before any frame") !=
497 "Independent parse context");
499 fixture.Write(
"BO_ 2 Second: 8 ECU\n");
501 CHECK(recovered.
frames.size() == 1U && recovered.
frames[0].canId == 2U,
502 "Fresh parse after error");
506void DbcParserTest::valueDescriptions() {
509 "VAL_\t256\tState\t0\t\"Off\"\t1 \"On\" 2 \"Error with spaces; and \\\"quotes\\\"\";\n"
510 "BO_ 256 Status: 8 ECU\n"
511 "SG_ State : 0|8@1+ (2,10) [0|520] \"\" ECU\n"
512 "VAL_ 256 State 3 \"\";\n"
513 "VAL_ 2147483904 State -9223372036854775808 \"Minimum\" -1 \"Negative\" 9223372036854775807 \"Maximum\";\n"
514 "BO_ 2147483904 Extended: 8 ECU\n"
515 "SG_ State : 0|64@1- (1,0) [-1e20|1e20] \"\" ECU\n"
516 "BO_ 2048 Unsigned: 8 ECU\n"
517 "SG_ State : 0|64@1+ (1,0) [0|2e20] \"\" ECU\n"
518 "VAL_ 2147485696 State 9223372036854775808 \"High\" 18446744073709551615 \"Full\";\n");
522 CHECK(tree->GetChild(0)->GetSignal() ==
nullptr,
"Frame node");
523 for (std::size_t index = 0; index < parsed.
frames.size(); ++index) {
524 CHECK(tree->GetChild(index)->GetChild(0)->GetSignal()->valueDescriptions ==
525 parsed.
frames[index].signals[0].valueDescriptions &&
526 runtime.frames[index].signals[0].valueDescriptions ==
527 parsed.
frames[index].signals[0].valueDescriptions,
528 "All mappings survive both builders");
530 CHECK(tree->GetChild(0)->GetChild(0)->GetSignal()->valueDescriptions ==
531 runtime.frames[0].signals[0].valueDescriptions,
532 "Both builders preserve descriptions");
535 frame.
data.resize(8);
536 const std::string expected[] = {
"Off",
"On",
"Error with spaces; and \"quotes\"",
""};
537 for (
unsigned value = 0; value < 5; ++value) {
538 frame.
data[0] = value;
540 CHECK(decoded.
valid && decoded.
physicalValue == value * 2 + 10,
"Numeric scaling unchanged");
542 CHECK(value < 4 ? description && *description == expected[value] : !description,
543 "Raw lookup and unknown value");
548 const char *description;
550 for (
const Boundary &test: {Boundary{0x8000000000000000ULL,
"Minimum"},
551 Boundary{0xFFFFFFFFFFFFFFFFULL,
"Negative"},
552 Boundary{0x7FFFFFFFFFFFFFFFULL,
"Maximum"}}) {
553 for (
unsigned byte = 0;
byte < 8; ++byte)
554 frame.
data[
byte] =
static_cast<std::uint8_t
>(test.bits >> (
byte * 8));
557 "Signed boundaries and distinct extended frame");
560 for (
const Boundary &test: {Boundary{0x8000000000000000ULL,
"High"},
561 Boundary{0xFFFFFFFFFFFFFFFFULL,
"Full"}}) {
562 for (
unsigned byte = 0;
byte < 8; ++byte)
563 frame.
data[
byte] =
static_cast<std::uint8_t
>(test.bits >> (
byte * 8));
566 "Unsigned boundaries");
568 CHECK(FindValueDescription(runtime.frames[0].signals[0].valueDescriptions, std::int64_t(1)) !=
nullptr,
569 "Nonnegative signed lookup matches unsigned key");
571 CHECK(!
DbcDecoder().Decode(runtime, frame).signals[0].GetValueDescription(),
"Invalid decode has no description");
574void DbcParserTest::invalidValueDescriptions() {
576 const std::string definitions =
"BO_ 1 Frame: 8 ECU\nSG_ State : 0|64@1+ (1,0) [0|2e20] \"\" ECU\n";
577 for (
const std::string statement: {
578 "VAL_",
"VAL_ 1 State;",
"VAL_ 1 State ;",
"VAL_ 1 State 0 \"Off\"",
579 "VAL_ 1 State 0 \"Off\"; junk",
"VAL_ 1 State 0 Off;",
580 "VAL_ 1 State 0 \"Unclosed;",
"VAL_ 1 State 0 \"Off\"1 \"On\";",
581 "VAL_ 1 State 0 \"Off\" 00 \"Duplicate\";",
582 "VAL_ 1 State 0 \"Off\" -0 \"Duplicate\";",
583 "VAL_ 1 State 0 \"Off\";\nVAL_ 1 State 0 \"Duplicate\";",
584 "VAL_ 2 State 0 \"Missing frame\";",
"VAL_ 1 Missing 0 \"Missing signal\";",
585 "VAL_ -1 State 0 \"Bad ID\";",
"VAL_ 536870912 State 0 \"Bad ID\";",
586 "VAL_ 4294967296 State 0 \"Bad ID\";",
"VAL_ Environment 0 \"Unsupported\";",
587 "VAL_ 1 State 0 \"Bad\\q escape\";"}) {
588 fixture.Write(definitions + statement +
'\n');
589 const int line = statement.find(
'\n') == std::string::npos ? 3 : 4;
592 CHECK(
false, statement);
593 }
catch (
const std::runtime_error &error) {
594 CHECK(std::string(error.what()).find(fixture.path.string() +
":" + std::to_string(line) +
": ") == 0,
595 statement +
" " + error.what());
598 for (
const std::string suffix: {
599 "BO_ 1 Other: 8 ECU\n",
600 "SG_ State : 0|8@1+ (1,0) [0|255] \"\" ECU\n",
602 fixture.Write(
"VAL_ 1 " + std::string(suffix.empty() ?
"Missing" :
"State") +
603 " 0 \"Description\";\n" + definitions + suffix);
606 CHECK(
false,
"Missing reference or duplicate definition");
607 }
catch (
const std::runtime_error &error) {
608 const std::string line = suffix.empty() ?
"1" :
"4";
609 CHECK(std::string(error.what()).find(fixture.path.string() +
":" + line +
": ") == 0,
610 "Error retains the reference or duplicate definition source location");
613 for (
const std::string number: {
"+1",
"1x",
"0x10",
"1.5",
"1e2",
"--1",
614 "-9223372036854775809",
"18446744073709551616"}) {
615 fixture.Write(
"VAL_ 1 State " + number +
" \"Bad\";\n" + definitions);
618 CHECK(
false, number);
619 }
catch (
const std::runtime_error &error) {
620 CHECK(std::string(error.what()).find(fixture.path.string() +
":1: ") == 0, number);
625int RunParserTests(
int argc,
char **argv) {
627 return QTest::qExec(&tests, argc, argv);
630#include "dbc_parser_test.moc"
Converts parsed DBC data into runtime decode database.
DecodeDatabase Build(const DbcDatabase &source) const
Build runtime decode database.
Runtime CAN decoder using prebuilt decode database.
DecodedFrameValue Decode(const DecodeDatabase &database, const RawCanFrame &frame) const
Decode one raw CAN frame.
DbcDatabase ParseFile(const std::string &filePath) const
Parse DBC file.
bool Dispatch(const std::string &line, DbcParseContext &context) const
Builds a simple tree from parsed DBC database.
std::unique_ptr< TreeNode > Build(const DbcDatabase &database) const
Build tree representation of parsed DBC data.
Created: 2026-03-13 Author: Deeaitch (Dim. Himro)
Created: 2026-03-13 Author: Deeaitch (Dim. Himro)
Created: 2026-03-13 Author: Deeaitch (Dim. Himro)
Created: 2026-03-13 Author: Deeaitch (Dim. Himro)
Parsed DBC content stored in a simple internal form.
std::vector< FrameInfo > frames
Runtime decode database with fast lookup by CAN ID.
const DecodeFrame * definition
One decoded signal value.
const std::string * GetValueDescription() const
Describes one CAN frame from a DBC file.
Raw CAN frame used for runtime or trace decoding.
std::vector< std::uint8_t > data
Describes one signal inside a DBC frame.
std::vector< std::string > receivers