5#include "dbc_statement_handler.h"
7#include "typefactory.h"
8#include <QTemporaryFile>
15 DbcDatabase ParseText(
const std::string &text,
bool enableJ1939 =
false) {
17 if (!file.open())
throw std::runtime_error(
"Cannot create DBC fixture");
18 if (file.write(text.data(),
static_cast<qint64
>(text.size())) !=
static_cast<qint64
>(text.size()) || !file.flush())
19 throw std::runtime_error(
"Cannot write DBC fixture");
27 frame.signals.push_back(signal);
33 std::uint64_t ReferenceExtract(
const std::vector<std::uint8_t> &data,
34 ByteOrder order,
unsigned start,
unsigned length) {
35 std::uint64_t expected = 0;
36 for (
unsigned i = 0; i < length; ++i) {
37 const unsigned coordinate = order == ByteOrder::Intel
39 : (start / 8U) * 8U + (7U - start % 8U) + i;
40 const unsigned bit = order == ByteOrder::Intel ? coordinate % 8U : 7U - coordinate % 8U;
41 const unsigned weight = order == ByteOrder::Intel ? i : length - 1U - i;
42 expected |= std::uint64_t((data.at(coordinate / 8U) >> bit) & 1U) << weight;
48 virtual ~FactoryBase() =
default;
49 virtual int Value()
const = 0;
51 struct FactoryFirst : FactoryBase {
52 explicit FactoryFirst(std::unique_ptr<int> value) : value(std::move(value)) {}
53 int Value()
const override {
return *value; }
54 std::unique_ptr<int> value;
56 struct FactorySecond : FactoryFirst {
57 using FactoryFirst::FactoryFirst;
58 int Value()
const override {
return *value + 1; }
62class CoverageTest :
public QObject {
66 void signedBoundaries_data();
67 void signedBoundaries();
68 void extraction_data();
70 void invalidSignalStatus_data();
71 void invalidSignalStatus();
72 void malformedRuntimeMultiplexing_data();
73 void malformedRuntimeMultiplexing();
76 void j1939Lookup_data();
78 void j1939Ambiguity_data();
79 void j1939Ambiguity();
80 void j1939MetadataValidation();
83 void ignoredCommentStatements_data();
84 void ignoredCommentStatements();
85 void ambiguousValueReferences_data();
86 void ambiguousValueReferences();
87 void valueReferenceScope();
88 void fileFormatting_data();
89 void fileFormatting();
92 void physicalConversion_data();
93 void physicalConversion();
94 void multiplexingScenarios();
95 void multiplexingErrorLocations_data();
96 void multiplexingErrorLocations();
97 void escapedValueDescriptions();
101void CoverageTest::frameLookup() {
106 QVERIFY(decoder.
FindFrame(empty, 17U,
false) ==
nullptr);
108 QVERIFY(!absent.
valid);
110 QVERIFY(absent.signals.empty());
114 QVERIFY(decoder.
FindFrame(database, 18U,
false) ==
nullptr);
115 QVERIFY(decoder.
FindFrame(database, 17U,
true) ==
nullptr);
123 noSignals.
canId = 17U;
126 QVERIFY(matched.valid);
127 QCOMPARE(matched.definition.get(), &database.
GetFrames()[0]);
128 QVERIFY(matched.signals.empty());
131void CoverageTest::signedBoundaries_data() {
132 QTest::addColumn<int>(
"order");
133 QTest::addColumn<unsigned>(
"length");
134 QTest::addColumn<qlonglong>(
"expected");
135 for (
int order = 0; order < 2; ++order) {
136 for (
unsigned length: {1U, 2U, 7U, 8U, 31U, 32U, 63U}) {
137 const std::int64_t half = std::int64_t(1) << (length - 1U);
138 const std::array<std::int64_t, 4> values{{0, -1, -half, half - 1}};
139 for (
unsigned index = 0; index < values.size(); ++index) {
140 const QByteArray name = QByteArray::number(order) +
"-" + QByteArray::number(length) +
"-" + QByteArray::number(index);
141 QTest::newRow(name.constData()) << order << length << qlonglong(values[index]);
147void CoverageTest::signedBoundaries() {
149 QFETCH(
unsigned, length);
150 QFETCH(qlonglong, expected);
152 signal.
byteOrder = order == 0 ? ByteOrder::Intel : ByteOrder::Motorola;
153 signal.
startBit = order == 0 ? 0U : 7U;
158 raw.
data.assign(8U, 0U);
159 const std::uint64_t bits =
static_cast<std::uint64_t
>(expected);
160 for (
unsigned i = 0; i < length; ++i) {
161 const unsigned weight = order == 0 ? i : length - 1U - i;
162 const unsigned position = order == 0 ? i % 8U : 7U - i % 8U;
163 raw.
data[i / 8U] |=
static_cast<std::uint8_t
>(((bits >> weight) & 1U) << position);
167 QVERIFY(result.signals[0].
valid);
168 QVERIFY(std::holds_alternative<std::int64_t>(result.signals[0].rawValue));
169 QCOMPARE(std::get<std::int64_t>(result.signals[0].rawValue),
static_cast<std::int64_t
>(expected));
172void CoverageTest::extraction_data() {
173 QTest::addColumn<int>(
"order");
174 QTest::addColumn<unsigned>(
"start");
175 QTest::addColumn<unsigned>(
"length");
176 for (
int order = 0; order < 2; ++order)
177 for (
unsigned start = 0; start < 16U; ++start)
178 for (
unsigned length = 1; length <= 64U; ++length) {
179 const QByteArray name = QByteArray::number(order) +
"-" + QByteArray::number(start) +
"-" + QByteArray::number(length);
180 QTest::newRow(name.constData()) << order << start << length;
184void CoverageTest::extraction() {
186 QFETCH(
unsigned, start);
187 QFETCH(
unsigned, length);
189 signal.
byteOrder = order == 0 ? ByteOrder::Intel : ByteOrder::Motorola;
195 const std::vector<std::vector<std::uint8_t>> patterns = {
196 std::vector<std::uint8_t>(10U, 0U),
197 std::vector<std::uint8_t>(10U, 255U),
198 {0xA5, 0x3C, 0x81, 0x7E, 0x12, 0xD4, 0x69, 0xB0, 0x5A, 0xC3}};
199 for (
const std::vector<std::uint8_t> &data: patterns) {
201 const unsigned firstCoordinate = order == 0 ? start : (start / 8U) * 8U + 7U - start % 8U;
202 raw.
data.resize((firstCoordinate + length + 7U) / 8U);
204 QVERIFY(decoded.signals[0].
valid);
205 QCOMPARE(std::get<std::uint64_t>(decoded.signals[0].rawValue), ReferenceExtract(data, signal.
byteOrder, start, length));
209void CoverageTest::invalidSignalStatus_data() {
210 QTest::addColumn<int>(
"order");
211 QTest::addColumn<unsigned>(
"start");
212 QTest::addColumn<unsigned>(
"length");
213 QTest::addColumn<unsigned>(
"bytes");
214 QTest::addColumn<int>(
"status");
215 for (
int order = 0; order < 2; ++order) {
216 const unsigned start = order == 0 ? 0U : 7U;
217 const QByteArray prefix = QByteArray::number(order);
218 QTest::newRow((prefix +
"-zero").constData()) << order << start << 0U << 1U << int(SignalDecodeStatus::InvalidDefinition);
219 QTest::newRow((prefix +
"-65").constData()) << order << start << 65U << 9U << int(SignalDecodeStatus::InvalidDefinition);
220 QTest::newRow((prefix +
"-empty").constData()) << order << start << 8U << 0U << int(SignalDecodeStatus::InsufficientPayload);
221 QTest::newRow((prefix +
"-short").constData()) << order << start << 16U << 1U << int(SignalDecodeStatus::InsufficientPayload);
222 QTest::newRow((prefix +
"-large-start").constData()) << order << std::numeric_limits<unsigned>::max() << 8U << 1U <<
int(SignalDecodeStatus::InvalidDefinition);
226void CoverageTest::invalidSignalStatus() {
228 QFETCH(
unsigned, start);
229 QFETCH(
unsigned, length);
230 QFETCH(
unsigned, bytes);
235 signal.
byteOrder = order == 0 ? ByteOrder::Intel : ByteOrder::Motorola;
236 if (status ==
int(SignalDecodeStatus::InvalidDefinition)) {
237 QVERIFY_THROWS_EXCEPTION(std::invalid_argument, (
void) SingleSignal(signal));
243 raw.
data.assign(bytes, 255U);
245 QVERIFY(decoded.
valid);
246 QVERIFY(!decoded.signals[0].
valid);
247 QCOMPARE(
int(decoded.signals[0].
status), status);
248 QVERIFY(decoded.signals[0].GetValueDescription() ==
nullptr);
251void CoverageTest::malformedRuntimeMultiplexing_data() {
252 QTest::addColumn<int>(
"kind");
253 QTest::newRow(
"two-selectors") << 0;
254 QTest::newRow(
"signed-selector") << 1;
255 QTest::newRow(
"65-bit-selector") << 2;
256 QTest::newRow(
"branch-out-of-range") << 3;
259void CoverageTest::malformedRuntimeMultiplexing() {
264 selector.
name =
"Selector";
265 std::vector<DecodeFrame> definitions = SingleSignal(selector).GetFrames();
267 branch.
name =
"Branch";
272 definitions[0].signals.push_back(branch);
274 ordinary.
name =
"Ordinary";
277 definitions[0].signals.push_back(ordinary);
279 selector.
name =
"SecondSelector";
280 definitions[0].signals.push_back(selector);
282 if (kind == 1) definitions[0].signals[0].valueType = ValueType::Signed;
283 if (kind == 2) definitions[0].signals[0].length = 65U;
284 if (kind == 3) definitions[0].signals[1].multiplexing.value = 2U;
285 QVERIFY_THROWS_EXCEPTION(std::invalid_argument, (
void)
DecodeDatabase(definitions));
288void CoverageTest::pgn_data() {
289 QTest::addColumn<unsigned>(
"id");
290 QTest::addColumn<unsigned>(
"expected");
291 QTest::addColumn<bool>(
"hasPgn");
292 QTest::newRow(
"standard") << 0x7FFU << 0U <<
false;
293 for (
unsigned dp: {0U, 1U, 2U, 3U})
294 for (
unsigned pf: {239U, 240U})
295 for (
unsigned ps: {0U, 0xABU}) {
296 const unsigned id = 0x80000000U | (dp << 24U) | (pf << 16U) | (ps << 8U) | 0x42U;
297 const unsigned expected = (dp << 16U) | (pf == 239U ? 0xEF00U : 0xF000U | ps);
298 const QByteArray name = QByteArray::number(dp) +
"-" + QByteArray::number(pf) +
"-" + QByteArray::number(ps);
299 QTest::newRow(name.constData()) <<
id << expected <<
true;
303void CoverageTest::pgn() {
304 QFETCH(
unsigned,
id);
305 QFETCH(
unsigned, expected);
306 QFETCH(
bool, hasPgn);
307 const std::string text =
"BO_ " + std::to_string(
id) +
" Frame: 8 ECU\n";
309 QVERIFY(!generic.frames[0].hasPgn);
310 QCOMPARE(generic.frames[0].pgn, 0U);
312 QCOMPARE(parsed.
frames[0].hasPgn, hasPgn);
313 QCOMPARE(parsed.
frames[0].pgn, expected);
316void CoverageTest::j1939Lookup_data() {
317 QTest::addColumn<unsigned>(
"definitionId");
318 QTest::addColumn<unsigned>(
"receivedId");
319 QTest::addColumn<unsigned>(
"pgn");
320 QTest::addColumn<bool>(
"matches");
321 QTest::newRow(
"pdu1-source") << 0x18EF1242U << 0x18EF1299U << 0xEF00U <<
true;
322 QTest::newRow(
"pdu1-destination-priority") << 0x18EF1242U << 0x0CEF3499U << 0xEF00U <<
true;
323 QTest::newRow(
"pdu2-source-priority") << 0x18F00442U << 0x0CF00499U << 0xF004U <<
true;
324 QTest::newRow(
"pdu2-group-extension") << 0x18F00442U << 0x18F00599U << 0xF004U <<
false;
325 QTest::newRow(
"data-page") << 0x18F00442U << 0x19F00499U << 0xF004U <<
false;
326 QTest::newRow(
"extended-data-page") << 0x18F00442U << 0x1AF00499U << 0xF004U <<
false;
327 QTest::newRow(
"pgn-zero") << 0x80000123U << 0x00000456U << 0U <<
true;
330void CoverageTest::j1939Lookup() {
331 QFETCH(
unsigned, definitionId);
332 QFETCH(
unsigned, receivedId);
333 QFETCH(
unsigned, pgn);
334 QFETCH(
bool, matches);
335 const std::string text =
"BO_ " + std::to_string(definitionId) +
" Frame: 1 ECU\n"
336 "SG_ Value : 0|8@1+ (2,1) [0|511] \"\" ECU\n";
339 raw.
canId = receivedId;
343 QVERIFY(!decoder.
FindFrame(generic, receivedId,
true));
344 QCOMPARE(decoder.
Decode(generic, raw).
status, FrameDecodeStatus::UnknownFrame);
345 QVERIFY(!generic.FindFrameByPgn(pgn).definition);
348 const FrameKey key(definitionId & 0x1FFFFFFFU,
true);
354 QCOMPARE(result.
valid, matches);
356 QCOMPARE(
bool(decoder.
FindFrame(database, receivedId,
true)), matches);
358 QVERIFY(result.
key == key);
360 QCOMPARE(result.signals.size(), std::size_t(1));
361 QVERIFY(result.signals[0].
key ==
SignalKey(key,
"Value"));
362 QCOMPARE(std::get<std::uint64_t>(result.signals[0].rawValue), std::uint64_t(7));
363 QCOMPARE(result.signals[0].physicalValue, 15.0);
365 QCOMPARE(selected.
status, FrameDecodeStatus::Valid);
366 QVERIFY(selected.signals[0].
key == result.signals[0].
key);
367 QVERIFY_THROWS_EXCEPTION(std::invalid_argument,
368 decoder.
Decode(database, raw, {SignalKey(FrameKey(receivedId, true),
"Value")}));
372 QVERIFY(result.signals.empty());
375 QCOMPARE(decoder.
Decode(database, raw).
status, FrameDecodeStatus::UnknownFrame);
377 raw.
canId = receivedId | 0x80000000U;
378 QCOMPARE(decoder.
Decode(database, raw).
status, FrameDecodeStatus::UnknownFrame);
386void CoverageTest::j1939Ambiguity_data() {
387 QTest::addColumn<unsigned>(
"firstId");
388 QTest::addColumn<unsigned>(
"secondId");
389 QTest::addColumn<unsigned>(
"receivedId");
390 QTest::addColumn<unsigned>(
"pgn");
391 QTest::newRow(
"pdu1") << 0x18EF1242U << 0x18EF3443U << 0x18EF5699U << 0xEF00U;
392 QTest::newRow(
"pdu2") << 0x18F00442U << 0x0CF00443U << 0x18F00499U << 0xF004U;
395void CoverageTest::j1939Ambiguity() {
396 QFETCH(
unsigned, firstId);
397 QFETCH(
unsigned, secondId);
398 QFETCH(
unsigned, receivedId);
399 QFETCH(
unsigned, pgn);
401 first.
canId = firstId;
406 second.
canId = secondId;
408 raw.
canId = receivedId;
411 for (
const std::vector<DecodeFrame> &frames: {std::vector<DecodeFrame>{first, second},
412 std::vector<DecodeFrame>{second, first}}) {
417 QVERIFY(!decoder.
FindFrame(database, receivedId,
true));
419 QCOMPARE(result.
status, FrameDecodeStatus::AmbiguousFrame);
420 QVERIFY(!result.
valid);
422 QVERIFY(result.signals.empty());
424 QCOMPARE(decoder.
Decode(database, raw, {SignalKey(first.GetKey(),
"Missing")}).
status,
425 FrameDecodeStatus::AmbiguousFrame);
428 QCOMPARE(exact.
status, FrameDecodeStatus::Valid);
430 raw.
canId = receivedId;
434 second.
canId = receivedId;
436 QVERIFY(!mixed.FindFrameByPgn(pgn).ambiguous);
437 QCOMPARE(mixed.FindFrameByPgn(pgn).definition->canId, firstId);
439 raw.
canId = receivedId ^ 1U;
443void CoverageTest::j1939MetadataValidation() {
445 frame.
canId = 0x18F00442U;
449 QVERIFY_THROWS_EXCEPTION(std::invalid_argument, (
void)
DecodeDatabase({frame}));
456 source.
frames.push_back(metadata);
457 QVERIFY_THROWS_EXCEPTION(std::invalid_argument,
DbcDecodeBuilder().Build(source));
461 QVERIFY_THROWS_EXCEPTION(std::invalid_argument, (
void)
DecodeDatabase({frame}));
464void CoverageTest::comments_data() {
465 QTest::addColumn<QString>(
"before");
466 QTest::addColumn<QString>(
"after");
467 QTest::addColumn<QString>(
"frameComment");
468 QTest::addColumn<QString>(
"signalComment");
469 QTest::newRow(
"tabs") << QString::fromUtf8(
"") << QString::fromUtf8(
"CM_\tBO_ 1 \"Frame\";\nCM_\tSG_ 1 State \"Signal\";\n") << QString::fromUtf8(
"Frame") << QString::fromUtf8(
"Signal");
470 QTest::newRow(
"empty-overwrite") << QString::fromUtf8(
"") << QString::fromUtf8(
"CM_ BO_ 1 \"Old\";\nCM_ BO_ 1 \"\";\nCM_ SG_ 1 State \"Old\";\nCM_ SG_ 1 State \"\";\n") << QString::fromUtf8(
"") << QString::fromUtf8(
"");
471 QTest::newRow(
"last-wins") << QString::fromUtf8(
"") << QString::fromUtf8(
"CM_ BO_ 1 \"First\";\nCM_ BO_ 1 \"Last\";\nCM_ SG_ 1 State \"First\";\nCM_ SG_ 1 State \"Last\";\n") << QString::fromUtf8(
"Last") << QString::fromUtf8(
"Last");
472 QTest::newRow(
"before-definitions") << QString::fromUtf8(
"CM_ BO_ 1 \"Early\";\nCM_ SG_ 1 State \"Early\";\n") << QString::fromUtf8(
"") << QString::fromUtf8(
"Early") << QString::fromUtf8(
"Early");
475void CoverageTest::comments() {
476 QFETCH(QString, before);
477 QFETCH(QString, after);
478 QFETCH(QString, frameComment);
479 QFETCH(QString, signalComment);
480 const DbcDatabase parsed = ParseText(before.toStdString() +
"BO_ 1 Frame: 1 ECU\nSG_ State : 0|8@1+ (1,0) [0|255] \"\" ECU\n" + after.toStdString());
481 QCOMPARE(parsed.
frames[0].comment, frameComment.toStdString());
482 QCOMPARE(parsed.
frames[0].signals[0].comment, signalComment.toStdString());
485void CoverageTest::ignoredCommentStatements_data() {
486 QTest::addColumn<QString>(
"statement");
487 QTest::newRow(
"empty") << QString();
488 QTest::newRow(
"wrong-keyword") << QStringLiteral(
"OTHER_ BO_ 1 \"Ignored\";");
489 QTest::newRow(
"keyword-prefix") << QStringLiteral(
"CM_OTHER BO_ 1 \"Ignored\";");
490 QTest::newRow(
"frame-without-cm") << QStringLiteral(
"BO_ 1 \"Ignored\";");
491 QTest::newRow(
"missing-subtype") << QStringLiteral(
"CM_");
492 QTest::newRow(
"global-comment") << QStringLiteral(
"CM_ \"Ignored\";");
493 QTest::newRow(
"node-comment") << QStringLiteral(
"CM_ BU_ ECU \"Ignored\";");
494 QTest::newRow(
"environment-comment") << QStringLiteral(
"CM_ EV_ State \"Ignored\";");
495 QTest::newRow(
"unknown-subtype") << QStringLiteral(
"CM_ OTHER_ 1 \"Ignored\";");
498void CoverageTest::ignoredCommentStatements() {
499 QFETCH(QString, statement);
503 frame.
name =
"Frame";
504 frame.
comment =
"Existing frame comment";
506 signal.
name =
"State";
507 signal.
comment =
"Existing signal comment";
508 frame.signals.push_back(signal);
509 database.
frames.push_back(frame);
515 QCOMPARE(database.
frames.size(), std::size_t(1));
516 QCOMPARE(database.
frames[0].canId, 1U);
517 QCOMPARE(database.
frames[0].name, frame.
name);
519 QCOMPARE(database.
frames[0].signals.size(), std::size_t(1));
520 QCOMPARE(database.
frames[0].signals[0].name, signal.
name);
521 QCOMPARE(database.
frames[0].signals[0].comment, signal.
comment);
522 QCOMPARE(context.currentFrame, &database.
frames[0]);
525void CoverageTest::ambiguousValueReferences_data() {
526 QTest::addColumn<bool>(
"duplicateFrames");
527 QTest::addColumn<bool>(
"extended");
528 QTest::newRow(
"duplicate-standard-frames") <<
true <<
false;
529 QTest::newRow(
"duplicate-extended-frames") <<
true <<
true;
530 QTest::newRow(
"duplicate-signals-in-standard-frame") <<
false <<
false;
531 QTest::newRow(
"duplicate-signals-in-extended-frame") <<
false <<
true;
534void CoverageTest::ambiguousValueReferences() {
535 QFETCH(
bool, duplicateFrames);
536 QFETCH(
bool, extended);
540 frame.
name =
"Frame";
543 signal.
name =
"State";
544 frame.signals.push_back(signal);
545 if (!duplicateFrames) frame.signals.push_back(signal);
546 database.
frames.push_back(frame);
547 if (duplicateFrames) database.
frames.push_back(frame);
552 context.lineNumber = 37U;
553 const std::string rawId = extended ?
"2147483649" :
"1";
555 QCOMPARE(context.pendingValues.size(), std::size_t(1));
556 QCOMPARE(context.pendingValues[0].lineNumber, std::size_t(37));
557 const std::string path =
"manual database.dbc";
559 context.ResolveValueDescriptions(path);
560 QFAIL(
"The resolver must reject an ambiguous target");
561 }
catch (
const std::runtime_error &error) {
562 const std::string message = duplicateFrames ?
"Ambiguous VAL_ frame reference."
563 :
"Ambiguous VAL_ signal reference: State";
564 QCOMPARE(std::string(error.what()), path +
":37: " + message);
567 for (
const FrameInfo &candidate: database.frames)
568 for (const
SignalInfo &candidateSignal: candidate.signals)
569 QVERIFY(candidateSignal.valueDescriptions.empty());
572void CoverageTest::valueReferenceScope() {
577 signal.
name =
"State";
578 frame.signals.push_back(signal);
579 database.
frames.push_back(frame);
581 database.
frames.push_back(frame);
584 database.
frames.push_back(frame);
588 context.lineNumber = 3U;
589 handler.
Handle(
"VAL_ 1 State 0 \"Standard\";", context);
590 context.lineNumber = 7U;
591 handler.
Handle(
"VAL_ 2147483649 State 0 \"Extended\";", context);
592 context.ResolveValueDescriptions(
"manual database.dbc");
594 const std::string *standard = FindValueDescription(database.
frames[0].signals[0].valueDescriptions, std::uint64_t(0));
595 const std::string *extended = FindValueDescription(database.
frames[1].signals[0].valueDescriptions, std::uint64_t(0));
596 QVERIFY(standard !=
nullptr);
597 QVERIFY(extended !=
nullptr);
598 QCOMPARE(*standard, std::string(
"Standard"));
599 QCOMPARE(*extended, std::string(
"Extended"));
600 QVERIFY(database.
frames[2].signals[0].valueDescriptions.empty());
603void CoverageTest::fileFormatting_data() {
604 QTest::addColumn<QString>(
"text");
605 QTest::addColumn<unsigned>(
"frames");
606 QTest::newRow(
"empty") << QString::fromUtf8(
"") << 0U;
607 QTest::newRow(
"whitespace") << QString::fromUtf8(
" \t\r\n\n\t") << 0U;
608 QTest::newRow(
"unknown-only") << QString::fromUtf8(
"VERSION \"test\"\nBU_: ECU\nUNKNOWN_ data\n") << 0U;
609 QTest::newRow(
"no-final-newline") << QString::fromUtf8(
"BO_ 1 Frame: 1 ECU\nSG_ State : 0|8@1+ (1,0) [0|255] \"\" ECU") << 1U;
610 QTest::newRow(
"crlf") << QString::fromUtf8(
"\r\nBO_ 1 Frame: 1 ECU\r\nSG_ State : 0|8@1+ (1,0) [0|255] \"\" ECU\r\nCM_ BO_ 1 \"Comment\";\r\nVAL_ 1 State 0 \"Off\";\r\n") << 1U;
613void CoverageTest::fileFormatting() {
614 QFETCH(QString, text);
615 QFETCH(
unsigned, frames);
616 const DbcDatabase parsed = ParseText(text.toStdString());
617 QCOMPARE(parsed.
frames.size(), std::size_t(frames));
619 QCOMPARE(parsed.
frames[0].signals.size(), std::size_t(1));
620 if (text.contains(
"\r\nBO_")) {
621 QCOMPARE(parsed.
frames[0].comment, std::string(
"Comment"));
622 const std::string *description = FindValueDescription(parsed.
frames[0].signals[0].valueDescriptions, std::uint64_t(0));
623 QVERIFY(description !=
nullptr);
624 QCOMPARE(*description, std::string(
"Off"));
629void CoverageTest::missingFile() {
631 QVERIFY(file.open());
632 const QString path = file.fileName();
633 QVERIFY(file.remove());
636 QFAIL(
"Missing file must throw");
637 }
catch (
const std::runtime_error &error) {
638 QCOMPARE(std::string(error.what()),
"Failed to open DBC file: " + path.toStdString());
642void CoverageTest::builders() {
644 QVERIFY(emptyRuntime.
GetFrames().empty());
647 QCOMPARE(emptyTree->GetType(), NodeType::Root);
648 QCOMPARE(emptyTree->GetName(), std::string(
"dbc"));
649 QCOMPARE(emptyTree->GetChildCount(), std::size_t(0));
650 QVERIFY(emptyTree->GetFrame() ==
nullptr && emptyTree->GetSignal() ==
nullptr);
652 "BO_ 256 Standard: 3 ECU1\n"
653 "SG_ Mode M : 0|8@1+ (2,3) [0|513] \"mode\" ECU1,ECU2\n"
654 "SG_ Branch m1 : 15|8@0- (-2,5) [-249|261] \"rpm\" ECU2,ECU3\n"
655 "SG_ Always : 16|8@1+ (0.25,-1) [-1|62.75] \"V\" ECU3\n"
656 "CM_ BO_ 256 \"Frame comment\";\nCM_ SG_ 256 Branch \"Branch comment\";\n"
657 "VAL_ 256 Branch -1 \"Negative\" 1 \"Positive\";\n"
658 "BO_ 2163212866 Extended: 8 ECU4\n");
660 std::unique_ptr<TreeNode> tree;
665 source.
frames[0].name =
"Changed";
666 source.
frames[0].signals[1].receivers.clear();
667 source.
frames[0].signals[1].valueDescriptions.clear();
671 QCOMPARE(tree->GetChildCount(), expected.
frames.size());
672 for (std::size_t i = 0; i < expected.
frames.size(); ++i) {
677 QCOMPARE(node->
GetType(), NodeType::Frame);
683 QCOMPARE(decodedFrame.
name, frame.
name);
688 QCOMPARE(copied.
dlc, frame.
dlc);
689 QCOMPARE(decodedFrame.
dlc, frame.
dlc);
690 QCOMPARE(copied.
pgn, frame.
pgn);
691 QCOMPARE(decodedFrame.
pgn, frame.
pgn);
699 QCOMPARE(copied.signals.size(), frame.signals.size());
700 QCOMPARE(decodedFrame.signals.size(), frame.signals.size());
701 for (std::size_t j = 0; j < frame.signals.size(); ++j) {
702 const SignalInfo &original = frame.signals[j];
706 QCOMPARE(leaf->
GetType(), NodeType::Signal);
708 QVERIFY(leaf->
GetFrame() ==
nullptr);
711 QCOMPARE(copy->name, original.
name);
712 QCOMPARE(copy->startBit, original.
startBit);
713 QCOMPARE(copy->length, original.
length);
715 QCOMPARE(copy->isSigned, original.
isSigned);
716 QCOMPARE(copy->factor, original.
factor);
717 QCOMPARE(copy->offset, original.
offset);
718 QCOMPARE(copy->minimum, original.
minimum);
719 QCOMPARE(copy->maximum, original.
maximum);
720 QCOMPARE(copy->unit, original.
unit);
721 QCOMPARE(copy->comment, original.
comment);
722 QVERIFY(copy->receivers == original.
receivers);
727 QCOMPARE(target.
name, original.
name);
736 QCOMPARE(target.
unit, original.
unit);
746void CoverageTest::physicalConversion_data() {
747 QTest::addColumn<double>(
"factor");
748 QTest::addColumn<double>(
"offset");
749 QTest::addColumn<double>(
"expected");
750 QTest::newRow(
"zero-factor") << 0.0 << 7.0 << 7.0;
751 QTest::newRow(
"negative-factor") << -2.0 << 5.0 << -15.0;
752 QTest::newRow(
"above-declared-range") << 2.0 << 3.0 << 23.0;
755void CoverageTest::physicalConversion() {
756 QFETCH(
double, factor);
757 QFETCH(
double, offset);
758 QFETCH(
double, expected);
770 QVERIFY(decoded.signals[0].
valid);
771 QCOMPARE(std::get<std::uint64_t>(decoded.signals[0].rawValue), std::uint64_t(10));
772 QCOMPARE(decoded.signals[0].physicalValue, expected);
775void CoverageTest::multiplexingScenarios() {
777 "BO_ 1 Frame: 1 ECU\nSG_ Mode M : 0|1@1+ (1,0) [0|1] \"\" ECU\n"));
782 QCOMPARE(only.signals.size(), std::size_t(1));
783 QVERIFY(only.signals[0].
valid);
785 "BO_ 1 First: 3 ECU\n"
786 "SG_ Mode M : 0|1@1+ (1,0) [0|1] \"\" ECU\n"
787 "SG_ A m0 : 8|8@1+ (1,0) [0|255] \"\" ECU\n"
788 "SG_ B m0 : 16|8@1+ (1,0) [0|255] \"\" ECU\n"
789 "BO_ 2 Second: 2 ECU\n"
790 "SG_ Mode M : 0|1@1+ (1,0) [0|1] \"\" ECU\n"
791 "SG_ A m1 : 8|8@1+ (1,0) [0|255] \"\" ECU\n"));
792 raw.
data = {0U, 42U, 99U};
794 QVERIFY(first.signals[1].
valid && first.signals[2].
valid);
795 QCOMPARE(std::get<std::uint64_t>(first.signals[1].rawValue), std::uint64_t(42));
796 QCOMPARE(std::get<std::uint64_t>(first.signals[2].rawValue), std::uint64_t(99));
799 QCOMPARE(secondInactive.signals[1].
status, SignalDecodeStatus::Inactive);
801 QVERIFY(
DbcDecoder().Decode(database, raw).signals[1].valid);
804 QCOMPARE(firstInactive.signals[1].
status, SignalDecodeStatus::Inactive);
805 QCOMPARE(firstInactive.signals[2].
status, SignalDecodeStatus::Inactive);
808void CoverageTest::multiplexingErrorLocations_data() {
809 QTest::addColumn<QString>(
"text");
810 QTest::addColumn<unsigned>(
"line");
811 QTest::addColumn<QString>(
"message");
812 const QString header =
"BO_ 1 Frame: 3 ECU\n\n";
813 const QString selector =
"SG_ Mode M : 0|1@1+ (1,0) [0|1] \"\" ECU\n";
814 const QString branch =
"SG_ A m1 : 8|8@1+ (1,0) [0|255] \"\" ECU\n";
815 QTest::newRow(
"missing-selector") << header + branch << 3U << QString::fromUtf8(
"Multiplexed branch has no selector.");
816 QTest::newRow(
"second-selector") << header + selector +
"\nSG_ Other M : 16|1@1+ (1,0) [0|1] \"\" ECU\n"
817 << 5U << QString::fromUtf8(
"Multiplexing requires one unsigned selector per frame.");
818 QTest::newRow(
"signed-selector") << header +
"SG_ Mode M : 0|1@1- (1,0) [-1|0] \"\" ECU\n"
819 << 3U << QString::fromUtf8(
"Multiplexing requires one unsigned selector per frame.");
820 QTest::newRow(
"branch-range") << header +
"SG_ A m2 : 8|8@1+ (1,0) [0|255] \"\" ECU\n" + selector << 3U << QString::fromUtf8(
"Branch value exceeds selector raw range.");
821 QTest::newRow(
"same-branch-overlap") << header + selector + branch +
"SG_ B m1 : 15|8@0+ (1,0) [0|255] \"\" ECU\n"
822 << 5U << QString::fromUtf8(
"Signals overlap without mutually exclusive multiplexing conditions.");
823 QTest::newRow(
"selector-overlap") << header + selector +
"SG_ A m1 : 0|1@1+ (1,0) [0|1] \"\" ECU\n"
824 << 4U << QString::fromUtf8(
"Signals overlap without mutually exclusive multiplexing conditions.");
825 QTest::newRow(
"unconditional-overlap") << header + selector + branch +
"SG_ Always : 8|8@1+ (1,0) [0|255] \"\" ECU\n"
826 << 5U << QString::fromUtf8(
"Signals overlap without mutually exclusive multiplexing conditions.");
827 QTest::newRow(
"ordinary-overlap") << header +
"SG_ A : 8|8@1+ (1,0) [0|255] \"\" ECU\nSG_ B : 15|8@0+ (1,0) [0|255] \"\" ECU\n"
828 << 4U << QString::fromUtf8(
"Signals overlap without mutually exclusive multiplexing conditions.");
829 QTest::newRow(
"extended-multiplexing") << header +
"SG_MUL_VAL_ 1 A Mode 1-2;\n"
830 << 3U << QString::fromUtf8(
"Extended and nested multiplexing are unsupported.");
831 QTest::newRow(
"nested-marker") << header +
"SG_ A m1M : 8|8@1+ (1,0) [0|255] \"\" ECU\n"
832 << 3U << QString::fromUtf8(
"Invalid or unsupported multiplexing marker.");
835void CoverageTest::multiplexingErrorLocations() {
836 QFETCH(QString, text);
837 QFETCH(
unsigned, line);
838 QFETCH(QString, message);
840 QVERIFY(file.open());
841 const QByteArray content = text.toUtf8();
842 QCOMPARE(file.write(content), qint64(content.size()));
843 QVERIFY(file.flush());
846 QFAIL(
"Expected multiplexing rejection");
847 }
catch (
const std::runtime_error &error) {
848 QCOMPARE(QString::fromStdString(error.what()), file.fileName() +
":" + QString::number(line) +
": " + message);
852void CoverageTest::escapedValueDescriptions() {
854 "BO_ 1 Frame: 1 ECU\nSG_ State : 0|8@1+ (1,0) [0|255] \"\" ECU\n"
855 R
"dbc(VAL_ 1 State -0 "Ноль" 1 "C:\\trace" 2 "\\\"quoted\"\\";)dbc"
857 const ValueDescriptions &values = parsed.
frames[0].signals[0].valueDescriptions;
858 QCOMPARE(values.size(), std::size_t(3));
859 QVERIFY(values.find(RawSignalValue(std::int64_t(0))) == values.end());
860 const std::string *zero = FindValueDescription(values, std::int64_t(0));
861 const std::string *path = FindValueDescription(values, std::uint64_t(1));
862 const std::string *quoted = FindValueDescription(values, std::uint64_t(2));
863 QVERIFY(zero && path && quoted);
864 QCOMPARE(*zero, std::string(
"Ноль"));
865 QCOMPARE(*path, std::string(
"C:\\trace"));
866 QCOMPARE(*quoted, std::string(
"\\\"quoted\"\\"));
867 QVERIFY(NormalizeValueKey(RawSignalValue(std::int64_t(0))) == RawSignalValue(std::uint64_t(0)));
868 QVERIFY(NormalizeValueKey(RawSignalValue(std::int64_t(-1))) == RawSignalValue(std::int64_t(-1)));
871void CoverageTest::factory() {
873 QVERIFY(!factory.
contains(
"value"));
874 QVERIFY_THROWS_EXCEPTION(std::out_of_range,
static_cast<void>(factory.
creator(
"missing")));
875 QVERIFY_THROWS_EXCEPTION(std::out_of_range,
static_cast<void>(factory.
create(
"missing", std::make_unique<int>(1))));
878 std::unique_ptr<int> argument = std::make_unique<int>(42);
879 const std::shared_ptr<FactoryBase> first = factory.
create(
"value", std::move(argument));
880 QVERIFY(argument ==
nullptr);
881 QCOMPARE(first->Value(), 42);
883 QCOMPARE(creator(std::make_unique<int>(7))->Value(), 7);
885 QCOMPARE(factory.
create(
"value", std::make_unique<int>(42))->Value(), 43);
886 QCOMPARE(first->Value(), 42);
887 QCOMPARE(creator(std::make_unique<int>(7))->Value(), 7);
890int RunCoverageTests(
int argc,
char **argv) {
892 return QTest::qExec(&tests, argc, argv);
894#include "dbc_coverage_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.
std::shared_ptr< const DecodeFrame > FindFrame(const DecodeDatabase &database, std::uint32_t canId, bool isExtended) const
Find exact CAN ID first, then unique PGN among explicitly J1939 definitions.
DbcDatabase ParseFile(const std::string &filePath) const
Parse DBC file.
Builds a simple tree from parsed DBC database.
std::unique_ptr< TreeNode > Build(const DbcDatabase &database) const
Build tree representation of parsed DBC data.
Defer raw value descriptions for target resolution.
void Handle(const std::string &line, DbcParseContext &context) const override
Process a trimmed statement using state belonging to this parse.
Immutable runtime snapshot. Copies share storage; reload replaces a snapshot. Owning lookup handles a...
std::shared_ptr< const DecodeFrame > FindFrame(const FrameKey &key) const
const std::vector< DecodeFrame > & GetFrames() const
PgnLookupResult FindFrameByPgn(std::uint32_t pgn) const
Tree node for later use in model/view or other hierarchy consumers.
const SignalInfo * GetSignal() const
Get signal payload if node is signal.
const TreeNode * GetChild(std::size_t index) const
Get child by index.
const std::string & GetName() const
Get display name.
std::size_t GetChildCount() const
Get child count.
NodeType GetType() const
Get node type.
const FrameInfo * GetFrame() const
Get frame payload if node is frame.
Registry-based factory for creating objects by runtime identifier.
bool contains(const ClassId &id) const
Checks whether an id is registered.
BasePtr create(const ClassId &id, Args... args) const
Creates an instance for the given id using provided arguments.
void registerType(const ClassId &id)
Registers a Derived type under the given id.
CreatorFn creator(const ClassId &id) const
Returns creator function for the given id.
Runtime decode database builder.
CAN frame decoding API and result types.
DBC file parser and interpretation options.
ByteOrder
Signal byte order used for runtime decoding.
ValueType
Signal numeric type.
Parsed DBC content stored in a simple internal form.
std::vector< FrameInfo > frames
Runtime-ready frame definition.
FrameKey GetKey() const
Return the stable normalized frame identifier.
Runtime-ready signal definition.
ValueDescriptions valueDescriptions
std::vector< std::string > receivers
Multiplexing multiplexing
std::shared_ptr< const DecodeFrame > definition
Describes one CAN frame from a DBC file.
Stable frame identifier: normalized CAN ID and frame type.
std::shared_ptr< const DecodeFrame > definition
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
ValueDescriptions valueDescriptions
Multiplexing multiplexing