5#include "dbc_statement_handler.h"
7#include "typefactory.h"
8#include <QTemporaryFile>
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);
28 database.frames.push_back(frame);
29 database.frameIndexByKey.emplace(
FrameKey(17U,
false), 0U);
35 std::uint64_t ReferenceExtract(
const std::vector<std::uint8_t> &data,
36 ByteOrder order,
unsigned start,
unsigned length) {
37 std::uint64_t expected = 0;
38 for (
unsigned i = 0; i < length; ++i) {
39 const unsigned coordinate = order == ByteOrder::Intel
41 : (start / 8U) * 8U + (7U - start % 8U) + i;
42 const unsigned bit = order == ByteOrder::Intel ? coordinate % 8U : 7U - coordinate % 8U;
43 const unsigned weight = order == ByteOrder::Intel ? i : length - 1U - i;
44 expected |= std::uint64_t((data.at(coordinate / 8U) >> bit) & 1U) << weight;
50 virtual ~FactoryBase() =
default;
51 virtual int Value()
const = 0;
53 struct FactoryFirst : FactoryBase {
54 explicit FactoryFirst(std::unique_ptr<int> value) : value(std::move(value)) {}
55 int Value()
const override {
return *value; }
56 std::unique_ptr<int> value;
58 struct FactorySecond : FactoryFirst {
59 using FactoryFirst::FactoryFirst;
60 int Value()
const override {
return *value + 1; }
64class CoverageTest :
public QObject {
68 void signedBoundaries_data();
69 void signedBoundaries();
70 void extraction_data();
72 void invalidSignalStatus_data();
73 void invalidSignalStatus();
74 void malformedRuntimeMultiplexing_data();
75 void malformedRuntimeMultiplexing();
80 void ignoredCommentStatements_data();
81 void ignoredCommentStatements();
82 void ambiguousValueReferences_data();
83 void ambiguousValueReferences();
84 void valueReferenceScope();
85 void fileFormatting_data();
86 void fileFormatting();
89 void physicalConversion_data();
90 void physicalConversion();
91 void multiplexingScenarios();
92 void multiplexingErrorLocations_data();
93 void multiplexingErrorLocations();
94 void escapedValueDescriptions();
98void CoverageTest::frameLookup() {
103 QVERIFY(decoder.
FindFrame(empty, 17U,
false) ==
nullptr);
105 QVERIFY(!absent.
valid);
107 QVERIFY(absent.signals.empty());
110 QVERIFY(decoder.
FindFrame(database, 18U,
false) ==
nullptr);
111 QVERIFY(decoder.
FindFrame(database, 17U,
true) ==
nullptr);
118 database.frameIndexByKey.at(
FrameKey(17U,
false)) = database.frames.size();
119 QVERIFY(decoder.
FindFrame(database, 17U,
false) ==
nullptr);
121 database.frameIndexByKey.at(
FrameKey(17U,
false)) = 0U;
122 database.frames[0].signals.clear();
124 QVERIFY(matched.
valid);
125 QCOMPARE(matched.
definition, &database.frames[0]);
126 QVERIFY(matched.signals.empty());
129void CoverageTest::signedBoundaries_data() {
130 QTest::addColumn<int>(
"order");
131 QTest::addColumn<unsigned>(
"length");
132 QTest::addColumn<qlonglong>(
"expected");
133 for (
int order = 0; order < 2; ++order) {
134 for (
unsigned length: {1U, 2U, 7U, 8U, 31U, 32U, 63U}) {
135 const std::int64_t half = std::int64_t(1) << (length - 1U);
136 const std::array<std::int64_t, 4> values{{0, -1, -half, half - 1}};
137 for (
unsigned index = 0; index < values.size(); ++index) {
138 const QByteArray name = QByteArray::number(order) +
"-" + QByteArray::number(length) +
"-" + QByteArray::number(index);
139 QTest::newRow(name.constData()) << order << length << qlonglong(values[index]);
145void CoverageTest::signedBoundaries() {
147 QFETCH(
unsigned, length);
148 QFETCH(qlonglong, expected);
150 signal.
byteOrder = order == 0 ? ByteOrder::Intel : ByteOrder::Motorola;
151 signal.
startBit = order == 0 ? 0U : 7U;
156 raw.
data.assign(8U, 0U);
157 const std::uint64_t bits =
static_cast<std::uint64_t
>(expected);
158 for (
unsigned i = 0; i < length; ++i) {
159 const unsigned weight = order == 0 ? i : length - 1U - i;
160 const unsigned position = order == 0 ? i % 8U : 7U - i % 8U;
161 raw.
data[i / 8U] |=
static_cast<std::uint8_t
>(((bits >> weight) & 1U) << position);
165 QVERIFY(result.signals[0].
valid);
166 QVERIFY(std::holds_alternative<std::int64_t>(result.signals[0].rawValue));
167 QCOMPARE(std::get<std::int64_t>(result.signals[0].rawValue),
static_cast<std::int64_t
>(expected));
170void CoverageTest::extraction_data() {
171 QTest::addColumn<int>(
"order");
172 QTest::addColumn<unsigned>(
"start");
173 QTest::addColumn<unsigned>(
"length");
174 for (
int order = 0; order < 2; ++order)
175 for (
unsigned start = 0; start < 16U; ++start)
176 for (
unsigned length = 1; length <= 64U; ++length) {
177 const QByteArray name = QByteArray::number(order) +
"-" + QByteArray::number(start) +
"-" + QByteArray::number(length);
178 QTest::newRow(name.constData()) << order << start << length;
182void CoverageTest::extraction() {
184 QFETCH(
unsigned, start);
185 QFETCH(
unsigned, length);
187 signal.
byteOrder = order == 0 ? ByteOrder::Intel : ByteOrder::Motorola;
193 const std::vector<std::vector<std::uint8_t>> patterns = {
194 std::vector<std::uint8_t>(10U, 0U),
195 std::vector<std::uint8_t>(10U, 255U),
196 {0xA5, 0x3C, 0x81, 0x7E, 0x12, 0xD4, 0x69, 0xB0, 0x5A, 0xC3}};
197 for (
const std::vector<std::uint8_t> &data: patterns) {
199 const unsigned firstCoordinate = order == 0 ? start : (start / 8U) * 8U + 7U - start % 8U;
200 raw.
data.resize((firstCoordinate + length + 7U) / 8U);
202 QVERIFY(decoded.signals[0].
valid);
203 QCOMPARE(std::get<std::uint64_t>(decoded.signals[0].rawValue), ReferenceExtract(data, signal.
byteOrder, start, length));
207void CoverageTest::invalidSignalStatus_data() {
208 QTest::addColumn<int>(
"order");
209 QTest::addColumn<unsigned>(
"start");
210 QTest::addColumn<unsigned>(
"length");
211 QTest::addColumn<unsigned>(
"bytes");
212 QTest::addColumn<int>(
"status");
213 for (
int order = 0; order < 2; ++order) {
214 const unsigned start = order == 0 ? 0U : 7U;
215 const QByteArray prefix = QByteArray::number(order);
216 QTest::newRow((prefix +
"-zero").constData()) << order << start << 0U << 1U << int(SignalDecodeStatus::InvalidDefinition);
217 QTest::newRow((prefix +
"-65").constData()) << order << start << 65U << 9U << int(SignalDecodeStatus::InvalidDefinition);
218 QTest::newRow((prefix +
"-empty").constData()) << order << start << 8U << 0U << int(SignalDecodeStatus::InsufficientPayload);
219 QTest::newRow((prefix +
"-short").constData()) << order << start << 16U << 1U << int(SignalDecodeStatus::InsufficientPayload);
220 QTest::newRow((prefix +
"-large-start").constData()) << order << std::numeric_limits<unsigned>::max() << 8U << 1U <<
int(SignalDecodeStatus::InsufficientPayload);
224void CoverageTest::invalidSignalStatus() {
226 QFETCH(
unsigned, start);
227 QFETCH(
unsigned, length);
228 QFETCH(
unsigned, bytes);
233 signal.
byteOrder = order == 0 ? ByteOrder::Intel : ByteOrder::Motorola;
237 raw.
data.assign(bytes, 255U);
239 QVERIFY(decoded.
valid);
240 QVERIFY(!decoded.signals[0].
valid);
241 QCOMPARE(
int(decoded.signals[0].status), status);
242 QVERIFY(decoded.signals[0].GetValueDescription() ==
nullptr);
245void CoverageTest::malformedRuntimeMultiplexing_data() {
246 QTest::addColumn<int>(
"kind");
247 QTest::newRow(
"two-selectors") << 0;
248 QTest::newRow(
"signed-selector") << 1;
249 QTest::newRow(
"65-bit-selector") << 2;
250 QTest::newRow(
"branch-out-of-range") << 3;
253void CoverageTest::malformedRuntimeMultiplexing() {
257 selector.multiplexing.role = MultiplexRole::Selector;
262 branch.multiplexing.role = MultiplexRole::Branch;
263 branch.multiplexing.value = 1U;
264 database.frames[0].signals.push_back(branch);
268 database.frames[0].signals.push_back(ordinary);
269 if (kind == 0) database.frames[0].signals.push_back(selector);
270 if (kind == 1) database.frames[0].signals[0].valueType = ValueType::Signed;
271 if (kind == 2) database.frames[0].signals[0].length = 65U;
272 if (kind == 3) database.frames[0].signals[1].multiplexing.value = 2U;
275 raw.
data = {1U, 42U, 99U};
277 QCOMPARE(
int(result.signals[1].status),
int(SignalDecodeStatus::InvalidDefinition));
278 QVERIFY(!result.signals[1].
valid);
279 QVERIFY(result.signals[2].
valid);
280 QCOMPARE(std::get<std::uint64_t>(result.signals[2].rawValue), std::uint64_t(99));
282 QCOMPARE(
int(result.signals[0].status),
int(SignalDecodeStatus::InvalidDefinition));
283 if (kind == 0) QCOMPARE(
int(result.signals[3].status),
int(SignalDecodeStatus::InvalidDefinition));
287void CoverageTest::pgn_data() {
288 QTest::addColumn<unsigned>(
"id");
289 QTest::addColumn<unsigned>(
"expected");
290 QTest::addColumn<bool>(
"hasPgn");
291 QTest::newRow(
"standard") << 0x7FFU << 0U <<
false;
292 for (
unsigned dp: {0U, 1U})
293 for (
unsigned pf: {239U, 240U})
294 for (
unsigned ps: {0U, 0xABU}) {
295 const unsigned id = 0x80000000U | (dp << 24U) | (pf << 16U) | (ps << 8U) | 0x42U;
296 const unsigned expected = (dp << 16U) | (pf == 239U ? 0xEF00U : 0xF000U | ps);
297 const QByteArray name = QByteArray::number(dp) +
"-" + QByteArray::number(pf) +
"-" + QByteArray::number(ps);
298 QTest::newRow(name.constData()) <<
id << expected <<
true;
302void CoverageTest::pgn() {
303 QFETCH(
unsigned,
id);
304 QFETCH(
unsigned, expected);
305 QFETCH(
bool, hasPgn);
306 const DbcDatabase parsed = ParseText(
"BO_ " + std::to_string(
id) +
" Frame: 8 ECU\n");
307 QCOMPARE(parsed.
frames[0].hasPgn, hasPgn);
308 QCOMPARE(parsed.
frames[0].pgn, expected);
311void CoverageTest::comments_data() {
312 QTest::addColumn<QString>(
"before");
313 QTest::addColumn<QString>(
"after");
314 QTest::addColumn<QString>(
"frameComment");
315 QTest::addColumn<QString>(
"signalComment");
316 QTest::newRow(
"tabs") << QString::fromUtf8(
"") << QString::fromUtf8(
"CM_\tBO_ 1 \"Frame\";\nCM_\tSG_ 1 State \"Signal\";\n") << QString::fromUtf8(
"Frame") << QString::fromUtf8(
"Signal");
317 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(
"");
318 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");
319 QTest::newRow(
"missing-targets") << QString::fromUtf8(
"") << QString::fromUtf8(
"CM_ BO_ 2 \"Unknown\";\nCM_ SG_ 2 State \"Unknown\";\nCM_ SG_ 1 Missing \"Unknown\";\n") << QString::fromUtf8(
"") << QString::fromUtf8(
"");
321 QTest::newRow(
"before-definitions") << QString::fromUtf8(
"CM_ BO_ 1 \"Early\";\nCM_ SG_ 1 State \"Early\";\n") << QString::fromUtf8(
"") << QString::fromUtf8(
"") << QString::fromUtf8(
"");
322 QTest::newRow(
"missing-quotes") << QString::fromUtf8(
"") << QString::fromUtf8(
"CM_ BO_ 1 Missing;\nCM_ SG_ 1 State \"Unclosed;\n") << QString::fromUtf8(
"") << QString::fromUtf8(
"");
325void CoverageTest::comments() {
326 QFETCH(QString, before);
327 QFETCH(QString, after);
328 QFETCH(QString, frameComment);
329 QFETCH(QString, signalComment);
330 const DbcDatabase parsed = ParseText(before.toStdString() +
"BO_ 1 Frame: 1 ECU\nSG_ State : 0|8@1+ (1,0) [0|255] \"\" ECU\n" + after.toStdString());
331 QCOMPARE(parsed.
frames[0].comment, frameComment.toStdString());
332 QCOMPARE(parsed.
frames[0].signals[0].comment, signalComment.toStdString());
335void CoverageTest::ignoredCommentStatements_data() {
336 QTest::addColumn<QString>(
"statement");
337 QTest::newRow(
"empty") << QString();
338 QTest::newRow(
"wrong-keyword") << QStringLiteral(
"OTHER_ BO_ 1 \"Ignored\";");
339 QTest::newRow(
"keyword-prefix") << QStringLiteral(
"CM_OTHER BO_ 1 \"Ignored\";");
340 QTest::newRow(
"frame-without-cm") << QStringLiteral(
"BO_ 1 \"Ignored\";");
341 QTest::newRow(
"missing-subtype") << QStringLiteral(
"CM_");
342 QTest::newRow(
"global-comment") << QStringLiteral(
"CM_ \"Ignored\";");
343 QTest::newRow(
"node-comment") << QStringLiteral(
"CM_ BU_ ECU \"Ignored\";");
344 QTest::newRow(
"environment-comment") << QStringLiteral(
"CM_ EV_ State \"Ignored\";");
345 QTest::newRow(
"unknown-subtype") << QStringLiteral(
"CM_ OTHER_ 1 \"Ignored\";");
348void CoverageTest::ignoredCommentStatements() {
349 QFETCH(QString, statement);
353 frame.
name =
"Frame";
354 frame.
comment =
"Existing frame comment";
356 signal.
name =
"State";
357 signal.
comment =
"Existing signal comment";
358 frame.signals.push_back(signal);
359 database.
frames.push_back(frame);
365 QCOMPARE(database.
frames.size(), std::size_t(1));
366 QCOMPARE(database.
frames[0].canId, 1U);
367 QCOMPARE(database.
frames[0].name, frame.
name);
369 QCOMPARE(database.
frames[0].signals.size(), std::size_t(1));
370 QCOMPARE(database.
frames[0].signals[0].name, signal.
name);
371 QCOMPARE(database.
frames[0].signals[0].comment, signal.
comment);
372 QCOMPARE(context.currentFrame, &database.
frames[0]);
375void CoverageTest::ambiguousValueReferences_data() {
376 QTest::addColumn<bool>(
"duplicateFrames");
377 QTest::addColumn<bool>(
"extended");
378 QTest::newRow(
"duplicate-standard-frames") <<
true <<
false;
379 QTest::newRow(
"duplicate-extended-frames") <<
true <<
true;
380 QTest::newRow(
"duplicate-signals-in-standard-frame") <<
false <<
false;
381 QTest::newRow(
"duplicate-signals-in-extended-frame") <<
false <<
true;
384void CoverageTest::ambiguousValueReferences() {
385 QFETCH(
bool, duplicateFrames);
386 QFETCH(
bool, extended);
390 frame.
name =
"Frame";
393 signal.
name =
"State";
394 frame.signals.push_back(signal);
395 if (!duplicateFrames) frame.signals.push_back(signal);
396 database.
frames.push_back(frame);
397 if (duplicateFrames) database.
frames.push_back(frame);
402 context.lineNumber = 37U;
403 const std::string rawId = extended ?
"2147483649" :
"1";
405 QCOMPARE(context.pendingValues.size(), std::size_t(1));
406 QCOMPARE(context.pendingValues[0].lineNumber, std::size_t(37));
407 const std::string path =
"manual database.dbc";
409 context.ResolveValueDescriptions(path);
410 QFAIL(
"The resolver must reject an ambiguous target");
411 }
catch (
const std::runtime_error &error) {
412 const std::string message = duplicateFrames ?
"Ambiguous VAL_ frame reference."
413 :
"Ambiguous VAL_ signal reference: State";
414 QCOMPARE(std::string(error.what()), path +
":37: " + message);
417 for (
const FrameInfo &candidate: database.frames)
418 for (const
SignalInfo &candidateSignal: candidate.signals)
419 QVERIFY(candidateSignal.valueDescriptions.empty());
422void CoverageTest::valueReferenceScope() {
427 signal.
name =
"State";
428 frame.signals.push_back(signal);
429 database.
frames.push_back(frame);
431 database.
frames.push_back(frame);
434 database.
frames.push_back(frame);
438 context.lineNumber = 3U;
439 handler.Handle(
"VAL_ 1 State 0 \"Standard\";", context);
440 context.lineNumber = 7U;
441 handler.Handle(
"VAL_ 2147483649 State 0 \"Extended\";", context);
442 context.ResolveValueDescriptions(
"manual database.dbc");
444 const std::string *standard = FindValueDescription(database.
frames[0].signals[0].valueDescriptions, std::uint64_t(0));
445 const std::string *extended = FindValueDescription(database.
frames[1].signals[0].valueDescriptions, std::uint64_t(0));
446 QVERIFY(standard !=
nullptr);
447 QVERIFY(extended !=
nullptr);
448 QCOMPARE(*standard, std::string(
"Standard"));
449 QCOMPARE(*extended, std::string(
"Extended"));
450 QVERIFY(database.
frames[2].signals[0].valueDescriptions.empty());
453void CoverageTest::fileFormatting_data() {
454 QTest::addColumn<QString>(
"text");
455 QTest::addColumn<unsigned>(
"frames");
456 QTest::newRow(
"empty") << QString::fromUtf8(
"") << 0U;
457 QTest::newRow(
"whitespace") << QString::fromUtf8(
" \t\r\n\n\t") << 0U;
458 QTest::newRow(
"unknown-only") << QString::fromUtf8(
"VERSION \"test\"\nBU_: ECU\nUNKNOWN_ data\n") << 0U;
459 QTest::newRow(
"no-final-newline") << QString::fromUtf8(
"BO_ 1 Frame: 1 ECU\nSG_ State : 0|8@1+ (1,0) [0|255] \"\" ECU") << 1U;
460 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;
463void CoverageTest::fileFormatting() {
464 QFETCH(QString, text);
465 QFETCH(
unsigned, frames);
466 const DbcDatabase parsed = ParseText(text.toStdString());
467 QCOMPARE(parsed.
frames.size(), std::size_t(frames));
469 QCOMPARE(parsed.
frames[0].signals.size(), std::size_t(1));
470 if (text.contains(
"\r\nBO_")) {
471 QCOMPARE(parsed.
frames[0].comment, std::string(
"Comment"));
472 const std::string *description = FindValueDescription(parsed.
frames[0].signals[0].valueDescriptions, std::uint64_t(0));
473 QVERIFY(description !=
nullptr);
474 QCOMPARE(*description, std::string(
"Off"));
479void CoverageTest::missingFile() {
481 QVERIFY(file.open());
482 const QString path = file.fileName();
483 QVERIFY(file.remove());
486 QFAIL(
"Missing file must throw");
487 }
catch (
const std::runtime_error &error) {
488 QCOMPARE(std::string(error.what()),
"Failed to open DBC file: " + path.toStdString());
492void CoverageTest::builders() {
494 QVERIFY(emptyRuntime.frames.empty());
495 QVERIFY(emptyRuntime.frameIndexByKey.empty());
497 QCOMPARE(emptyTree->GetType(), NodeType::Root);
498 QCOMPARE(emptyTree->GetName(), std::string(
"dbc"));
499 QCOMPARE(emptyTree->GetChildCount(), std::size_t(0));
500 QVERIFY(emptyTree->GetFrame() ==
nullptr && emptyTree->GetSignal() ==
nullptr);
502 "BO_ 256 Standard: 3 ECU1\n"
503 "SG_ Mode M : 0|8@1+ (2,3) [0|513] \"mode\" ECU1,ECU2\n"
504 "SG_ Branch m1 : 15|8@0- (-2,5) [-249|261] \"rpm\" ECU2,ECU3\n"
505 "SG_ Always : 16|8@1+ (0.25,-1) [-1|62.75] \"V\" ECU3\n"
506 "CM_ BO_ 256 \"Frame comment\";\nCM_ SG_ 256 Branch \"Branch comment\";\n"
507 "VAL_ 256 Branch -1 \"Negative\" 1 \"Positive\";\n"
508 "BO_ 2163212866 Extended: 8 ECU4\n");
510 std::unique_ptr<TreeNode> tree;
515 source.
frames[0].name =
"Changed";
516 source.
frames[0].signals[1].receivers.clear();
517 source.
frames[0].signals[1].valueDescriptions.clear();
520 QCOMPARE(runtime.frames.size(), expected.
frames.size());
521 QCOMPARE(tree->GetChildCount(), expected.
frames.size());
522 QCOMPARE(runtime.frameIndexByKey.size(), expected.
frames.size());
523 for (std::size_t i = 0; i < expected.
frames.size(); ++i) {
525 const DecodeFrame &decodedFrame = runtime.frames[i];
528 QCOMPARE(node->
GetType(), NodeType::Frame);
534 QCOMPARE(decodedFrame.
name, frame.
name);
539 QCOMPARE(copied.
dlc, frame.
dlc);
540 QCOMPARE(decodedFrame.
dlc, frame.
dlc);
541 QCOMPARE(copied.
pgn, frame.
pgn);
542 QCOMPARE(decodedFrame.
pgn, frame.
pgn);
550 QCOMPARE(copied.signals.size(), frame.signals.size());
551 QCOMPARE(decodedFrame.signals.size(), frame.signals.size());
552 for (std::size_t j = 0; j < frame.signals.size(); ++j) {
553 const SignalInfo &original = frame.signals[j];
557 QCOMPARE(leaf->
GetType(), NodeType::Signal);
559 QVERIFY(leaf->
GetFrame() ==
nullptr);
562 QCOMPARE(copy->name, original.
name);
563 QCOMPARE(copy->startBit, original.
startBit);
564 QCOMPARE(copy->length, original.
length);
566 QCOMPARE(copy->isSigned, original.
isSigned);
567 QCOMPARE(copy->factor, original.
factor);
568 QCOMPARE(copy->offset, original.
offset);
569 QCOMPARE(copy->minimum, original.
minimum);
570 QCOMPARE(copy->maximum, original.
maximum);
571 QCOMPARE(copy->unit, original.
unit);
572 QCOMPARE(copy->comment, original.
comment);
573 QVERIFY(copy->receivers == original.
receivers);
575 QCOMPARE(copy->multiplexing.role, original.multiplexing.role);
576 QCOMPARE(copy->multiplexing.value, original.multiplexing.value);
578 QCOMPARE(target.
name, original.
name);
587 QCOMPARE(target.
unit, original.
unit);
591 QCOMPARE(target.multiplexing.role, original.multiplexing.role);
592 QCOMPARE(target.multiplexing.value, original.multiplexing.value);
597void CoverageTest::physicalConversion_data() {
598 QTest::addColumn<double>(
"factor");
599 QTest::addColumn<double>(
"offset");
600 QTest::addColumn<double>(
"expected");
601 QTest::newRow(
"zero-factor") << 0.0 << 7.0 << 7.0;
602 QTest::newRow(
"negative-factor") << -2.0 << 5.0 << -15.0;
603 QTest::newRow(
"above-declared-range") << 2.0 << 3.0 << 23.0;
606void CoverageTest::physicalConversion() {
607 QFETCH(
double, factor);
608 QFETCH(
double, offset);
609 QFETCH(
double, expected);
621 QVERIFY(decoded.signals[0].
valid);
622 QCOMPARE(std::get<std::uint64_t>(decoded.signals[0].rawValue), std::uint64_t(10));
623 QCOMPARE(decoded.signals[0].physicalValue, expected);
626void CoverageTest::multiplexingScenarios() {
628 "BO_ 1 Frame: 1 ECU\nSG_ Mode M : 0|1@1+ (1,0) [0|1] \"\" ECU\n"));
633 QCOMPARE(only.signals.size(), std::size_t(1));
634 QVERIFY(only.signals[0].
valid);
636 "BO_ 1 First: 3 ECU\n"
637 "SG_ Mode M : 0|1@1+ (1,0) [0|1] \"\" ECU\n"
638 "SG_ A m0 : 8|8@1+ (1,0) [0|255] \"\" ECU\n"
639 "SG_ B m0 : 16|8@1+ (1,0) [0|255] \"\" ECU\n"
640 "BO_ 2 Second: 2 ECU\n"
641 "SG_ Mode M : 0|1@1+ (1,0) [0|1] \"\" ECU\n"
642 "SG_ A m1 : 8|8@1+ (1,0) [0|255] \"\" ECU\n"));
643 raw.
data = {0U, 42U, 99U};
645 QVERIFY(first.signals[1].
valid && first.signals[2].
valid);
646 QCOMPARE(std::get<std::uint64_t>(first.signals[1].rawValue), std::uint64_t(42));
647 QCOMPARE(std::get<std::uint64_t>(first.signals[2].rawValue), std::uint64_t(99));
650 QCOMPARE(secondInactive.signals[1].status, SignalDecodeStatus::Inactive);
652 QVERIFY(
DbcDecoder().Decode(database, raw).signals[1].valid);
655 QCOMPARE(firstInactive.signals[1].status, SignalDecodeStatus::Inactive);
656 QCOMPARE(firstInactive.signals[2].status, SignalDecodeStatus::Inactive);
659void CoverageTest::multiplexingErrorLocations_data() {
660 QTest::addColumn<QString>(
"text");
661 QTest::addColumn<unsigned>(
"line");
662 QTest::addColumn<QString>(
"message");
663 const QString header =
"BO_ 1 Frame: 3 ECU\n\n";
664 const QString selector =
"SG_ Mode M : 0|1@1+ (1,0) [0|1] \"\" ECU\n";
665 const QString branch =
"SG_ A m1 : 8|8@1+ (1,0) [0|255] \"\" ECU\n";
666 QTest::newRow(
"missing-selector") << header + branch << 3U << QString::fromUtf8(
"Multiplexed branch has no selector.");
667 QTest::newRow(
"second-selector") << header + selector +
"\nSG_ Other M : 16|1@1+ (1,0) [0|1] \"\" ECU\n"
668 << 5U << QString::fromUtf8(
"Multiplexing requires one unsigned selector per frame.");
669 QTest::newRow(
"signed-selector") << header +
"SG_ Mode M : 0|1@1- (1,0) [-1|0] \"\" ECU\n"
670 << 3U << QString::fromUtf8(
"Multiplexing requires one unsigned selector per frame.");
671 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.");
672 QTest::newRow(
"same-branch-overlap") << header + selector + branch +
"SG_ B m1 : 15|8@0+ (1,0) [0|255] \"\" ECU\n"
673 << 5U << QString::fromUtf8(
"Signals overlap without mutually exclusive multiplexing conditions.");
674 QTest::newRow(
"selector-overlap") << header + selector +
"SG_ A m1 : 0|1@1+ (1,0) [0|1] \"\" ECU\n"
675 << 4U << QString::fromUtf8(
"Signals overlap without mutually exclusive multiplexing conditions.");
676 QTest::newRow(
"unconditional-overlap") << header + selector + branch +
"SG_ Always : 8|8@1+ (1,0) [0|255] \"\" ECU\n"
677 << 5U << QString::fromUtf8(
"Signals overlap without mutually exclusive multiplexing conditions.");
678 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"
679 << 4U << QString::fromUtf8(
"Signals overlap without mutually exclusive multiplexing conditions.");
680 QTest::newRow(
"extended-multiplexing") << header +
"SG_MUL_VAL_ 1 A Mode 1-2;\n"
681 << 3U << QString::fromUtf8(
"Extended and nested multiplexing are unsupported.");
682 QTest::newRow(
"nested-marker") << header +
"SG_ A m1M : 8|8@1+ (1,0) [0|255] \"\" ECU\n"
683 << 3U << QString::fromUtf8(
"Invalid or unsupported multiplexing marker.");
686void CoverageTest::multiplexingErrorLocations() {
687 QFETCH(QString, text);
688 QFETCH(
unsigned, line);
689 QFETCH(QString, message);
691 QVERIFY(file.open());
692 const QByteArray content = text.toUtf8();
693 QCOMPARE(file.write(content), qint64(content.size()));
694 QVERIFY(file.flush());
697 QFAIL(
"Expected multiplexing rejection");
698 }
catch (
const std::runtime_error &error) {
699 QCOMPARE(QString::fromStdString(error.what()), file.fileName() +
":" + QString::number(line) +
": " + message);
703void CoverageTest::escapedValueDescriptions() {
705 "BO_ 1 Frame: 1 ECU\nSG_ State : 0|8@1+ (1,0) [0|255] \"\" ECU\n"
706 R
"dbc(VAL_ 1 State -0 "Ноль" 1 "C:\\trace" 2 "\\\"quoted\"\\";)dbc"
708 const ValueDescriptions &values = parsed.
frames[0].signals[0].valueDescriptions;
709 QCOMPARE(values.size(), std::size_t(3));
710 QVERIFY(values.find(RawSignalValue(std::int64_t(0))) == values.end());
711 const std::string *zero = FindValueDescription(values, std::int64_t(0));
712 const std::string *path = FindValueDescription(values, std::uint64_t(1));
713 const std::string *quoted = FindValueDescription(values, std::uint64_t(2));
714 QVERIFY(zero && path && quoted);
715 QCOMPARE(*zero, std::string(
"Ноль"));
716 QCOMPARE(*path, std::string(
"C:\\trace"));
717 QCOMPARE(*quoted, std::string(
"\\\"quoted\"\\"));
718 QVERIFY(NormalizeValueKey(RawSignalValue(std::int64_t(0))) == RawSignalValue(std::uint64_t(0)));
719 QVERIFY(NormalizeValueKey(RawSignalValue(std::int64_t(-1))) == RawSignalValue(std::int64_t(-1)));
722void CoverageTest::factory() {
724 QVERIFY(!factory.
contains(
"value"));
725 QVERIFY_THROWS_EXCEPTION(std::out_of_range,
static_cast<void>(factory.
creator(
"missing")));
726 QVERIFY_THROWS_EXCEPTION(std::out_of_range,
static_cast<void>(factory.
create(
"missing", std::make_unique<int>(1))));
729 std::unique_ptr<int> argument = std::make_unique<int>(42);
730 const std::shared_ptr<FactoryBase> first = factory.
create(
"value", std::move(argument));
731 QVERIFY(argument ==
nullptr);
732 QCOMPARE(first->Value(), 42);
734 QCOMPARE(creator(std::make_unique<int>(7))->Value(), 7);
736 QCOMPARE(factory.
create(
"value", std::make_unique<int>(42))->Value(), 43);
737 QCOMPARE(first->Value(), 42);
738 QCOMPARE(creator(std::make_unique<int>(7))->Value(), 7);
741int RunCoverageTests(
int argc,
char **argv) {
743 return QTest::qExec(&tests, argc, argv);
745#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.
const DecodeFrame * FindFrame(const DecodeDatabase &database, std::uint32_t canId, bool isExtended) const
Find frame definition by CAN ID.
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.
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.
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)
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 decode database with fast lookup by CAN ID.
Runtime-ready frame definition.
Runtime-ready signal definition.
ValueDescriptions valueDescriptions
std::vector< std::string > receivers
const DecodeFrame * definition
Describes one CAN frame from a DBC file.
Key for fast frame lookup.
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