DBC Framework
DBC parsing and CAN signal decoding framework
Loading...
Searching...
No Matches
dbc_coverage_test.cpp
1// Additional regression coverage. Kept separate for review before the first run.
3#include "dbc_decoder.h"
4#include "dbc_parser.h"
5#include "dbc_statement_handler.h"
6#include "dbc_tree_builder.h"
7#include "typefactory.h"
8#include <QTemporaryFile>
9#include <QtTest>
10#include <array>
11#include <limits>
12#include <stdexcept>
13
14namespace {
15 DbcDatabase ParseText(const std::string &text, bool enableJ1939 = false) {
16 QTemporaryFile file;
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");
20 return DbcParser(DbcParserOptions{enableJ1939}).ParseFile(file.fileName().toStdString());
21 }
22
23 DecodeDatabase SingleSignal(const DecodeSignal &signal) {
24 DecodeFrame frame;
25 frame.canId = 17U;
26 frame.dlc = 16U;
27 frame.signals.push_back(signal);
28 return DecodeDatabase({frame});
29 }
30
31 // Independent reference: collect the visited bits first, then weight them.
32 // Motorola uses a linear MSB-first coordinate instead of sawtooth traversal.
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
38 ? start + i
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;
43 }
44 return expected;
45 }
46
47 struct FactoryBase {
48 virtual ~FactoryBase() = default;
49 virtual int Value() const = 0;
50 };
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;
55 };
56 struct FactorySecond : FactoryFirst {
57 using FactoryFirst::FactoryFirst;
58 int Value() const override { return *value + 1; }
59 };
60}// namespace
61
62class CoverageTest : public QObject {
63 Q_OBJECT
64private Q_SLOTS:
65 void frameLookup();
66 void signedBoundaries_data();
67 void signedBoundaries();
68 void extraction_data();
69 void extraction();
70 void invalidSignalStatus_data();
71 void invalidSignalStatus();
72 void malformedRuntimeMultiplexing_data();
73 void malformedRuntimeMultiplexing();
74 void pgn_data();
75 void pgn();
76 void j1939Lookup_data();
77 void j1939Lookup();
78 void j1939Ambiguity_data();
79 void j1939Ambiguity();
80 void j1939MetadataValidation();
81 void comments_data();
82 void comments();
83 void ignoredCommentStatements_data();
84 void ignoredCommentStatements();
85 void ambiguousValueReferences_data();
86 void ambiguousValueReferences();
87 void valueReferenceScope();
88 void fileFormatting_data();
89 void fileFormatting();
90 void missingFile();
91 void builders();
92 void physicalConversion_data();
93 void physicalConversion();
94 void multiplexingScenarios();
95 void multiplexingErrorLocations_data();
96 void multiplexingErrorLocations();
97 void escapedValueDescriptions();
98 void factory();
99};
100
101void CoverageTest::frameLookup() {
102 DbcDecoder decoder;
103 RawCanFrame raw;
104 raw.canId = 17U;
105 DecodeDatabase empty;
106 QVERIFY(decoder.FindFrame(empty, 17U, false) == nullptr);
107 const DecodedFrameValue absent = decoder.Decode(empty, raw);
108 QVERIFY(!absent.valid);
109 QVERIFY(absent.definition == nullptr);
110 QVERIFY(absent.signals.empty());
111 DecodeSignal signal;
112 signal.length = 8U;
113 DecodeDatabase database = SingleSignal(signal);
114 QVERIFY(decoder.FindFrame(database, 18U, false) == nullptr);
115 QVERIFY(decoder.FindFrame(database, 17U, true) == nullptr);
116 raw.isExtended = true;
117 QVERIFY(!decoder.Decode(database, raw).valid);
118 raw.isExtended = false;
119 raw.canId = 18U;
120 QVERIFY(!decoder.Decode(database, raw).valid);
121 raw.canId = 17U;
122 DecodeFrame noSignals;
123 noSignals.canId = 17U;
124 database = DecodeDatabase({noSignals});
125 const DecodedFrameValue matched = decoder.Decode(database, raw);
126 QVERIFY(matched.valid);
127 QCOMPARE(matched.definition.get(), &database.GetFrames()[0]);
128 QVERIFY(matched.signals.empty());
129}
130
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]);
142 }
143 }
144 }
145}
146
147void CoverageTest::signedBoundaries() {
148 QFETCH(int, order);
149 QFETCH(unsigned, length);
150 QFETCH(qlonglong, expected);
151 DecodeSignal signal;
152 signal.byteOrder = order == 0 ? ByteOrder::Intel : ByteOrder::Motorola;
153 signal.startBit = order == 0 ? 0U : 7U;
154 signal.length = length;
155 signal.valueType = ValueType::Signed;
156 RawCanFrame raw;
157 raw.canId = 17U;
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);
164 }
165 const DecodeDatabase database = SingleSignal(signal);
166 const DecodedFrameValue result = DbcDecoder().Decode(database, raw);
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));
170}
171
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;
181 }
182}
183
184void CoverageTest::extraction() {
185 QFETCH(int, order);
186 QFETCH(unsigned, start);
187 QFETCH(unsigned, length);
188 DecodeSignal signal;
189 signal.byteOrder = order == 0 ? ByteOrder::Intel : ByteOrder::Motorola;
190 signal.startBit = start;
191 signal.length = length;
192 const DecodeDatabase database = SingleSignal(signal);
193 RawCanFrame raw;
194 raw.canId = 17U;
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) {
200 raw.data = data;
201 const unsigned firstCoordinate = order == 0 ? start : (start / 8U) * 8U + 7U - start % 8U;
202 raw.data.resize((firstCoordinate + length + 7U) / 8U);
203 const DecodedFrameValue decoded = DbcDecoder().Decode(database, raw);
204 QVERIFY(decoded.signals[0].valid);
205 QCOMPARE(std::get<std::uint64_t>(decoded.signals[0].rawValue), ReferenceExtract(data, signal.byteOrder, start, length));
206 }
207}
208
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);
223 }
224}
225
226void CoverageTest::invalidSignalStatus() {
227 QFETCH(int, order);
228 QFETCH(unsigned, start);
229 QFETCH(unsigned, length);
230 QFETCH(unsigned, bytes);
231 QFETCH(int, status);
232 DecodeSignal signal;
233 signal.startBit = start;
234 signal.length = length;
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));
238 return;
239 }
240 const DecodeDatabase database = SingleSignal(signal);
241 RawCanFrame raw;
242 raw.canId = 17U;
243 raw.data.assign(bytes, 255U);
244 const DecodedFrameValue decoded = DbcDecoder().Decode(database, raw);
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);
249}
250
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;
257}
258
259void CoverageTest::malformedRuntimeMultiplexing() {
260 QFETCH(int, kind);
261 DecodeSignal selector;
262 selector.length = 1U;
263 selector.multiplexing.role = MultiplexRole::Selector;
264 selector.name = "Selector";
265 std::vector<DecodeFrame> definitions = SingleSignal(selector).GetFrames();
266 DecodeSignal branch;
267 branch.name = "Branch";
268 branch.startBit = 8U;
269 branch.length = 8U;
270 branch.multiplexing.role = MultiplexRole::Branch;
271 branch.multiplexing.value = 1U;
272 definitions[0].signals.push_back(branch);
273 DecodeSignal ordinary;
274 ordinary.name = "Ordinary";
275 ordinary.startBit = 16U;
276 ordinary.length = 8U;
277 definitions[0].signals.push_back(ordinary);
278 if (kind == 0) {
279 selector.name = "SecondSelector";
280 definitions[0].signals.push_back(selector);
281 }
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));
286}
287
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;
300 }
301}
302
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";
308 const DbcDatabase generic = ParseText(text);
309 QVERIFY(!generic.frames[0].hasPgn);
310 QCOMPARE(generic.frames[0].pgn, 0U);
311 const DbcDatabase parsed = ParseText(text, true);
312 QCOMPARE(parsed.frames[0].hasPgn, hasPgn);
313 QCOMPARE(parsed.frames[0].pgn, expected);
314}
315
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;
328}
329
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";
337 const DbcDecoder decoder;
338 RawCanFrame raw;
339 raw.canId = receivedId;
340 raw.isExtended = true;
341 raw.data = {7U};
342 const DecodeDatabase generic = DbcDecodeBuilder().Build(ParseText(text));
343 QVERIFY(!decoder.FindFrame(generic, receivedId, true));
344 QCOMPARE(decoder.Decode(generic, raw).status, FrameDecodeStatus::UnknownFrame);
345 QVERIFY(!generic.FindFrameByPgn(pgn).definition);
346
347 DecodeDatabase database = DbcDecodeBuilder().Build(ParseText(text, true));
348 const FrameKey key(definitionId & 0x1FFFFFFFU, true);
349 const PgnLookupResult lookup = database.FindFrameByPgn(pgn);
350 QVERIFY(!lookup.ambiguous);
351 QVERIFY(lookup.definition != nullptr);
352 QCOMPARE(lookup.definition.get(), database.FindFrame(key).get());
353 const DecodedFrameValue result = decoder.Decode(database, raw);
354 QCOMPARE(result.valid, matches);
355 QCOMPARE(result.status, matches ? FrameDecodeStatus::Valid : FrameDecodeStatus::UnknownFrame);
356 QCOMPARE(bool(decoder.FindFrame(database, receivedId, true)), matches);
357 if (matches) {
358 QVERIFY(result.key == key);
359 QCOMPARE(result.definition.get(), lookup.definition.get());
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);
364 const DecodedFrameValue selected = decoder.Decode(database, raw, {SignalKey(key, "Value")});
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")}));
369 } else {
370 QVERIFY(result.key == FrameKey(receivedId, true));
371 QVERIFY(!result.definition);
372 QVERIFY(result.signals.empty());
373 }
374 raw.isExtended = false;
375 QCOMPARE(decoder.Decode(database, raw).status, FrameDecodeStatus::UnknownFrame);
376 raw.isExtended = true;
377 raw.canId = receivedId | 0x80000000U;
378 QCOMPARE(decoder.Decode(database, raw).status, FrameDecodeStatus::UnknownFrame);
379
380 database = DecodeDatabase();
381 QCOMPARE(lookup.definition->pgn, pgn); // PGN handles own the old snapshot.
382 QVERIFY(!database.FindFrameByPgn(pgn).definition);
383 QVERIFY(!database.FindFrameByPgn(pgn).ambiguous);
384}
385
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;
393}
394
395void CoverageTest::j1939Ambiguity() {
396 QFETCH(unsigned, firstId);
397 QFETCH(unsigned, secondId);
398 QFETCH(unsigned, receivedId);
399 QFETCH(unsigned, pgn);
400 DecodeFrame first;
401 first.canId = firstId;
402 first.isExtended = true;
403 first.hasPgn = true;
404 first.pgn = pgn;
405 DecodeFrame second = first;
406 second.canId = secondId;
407 RawCanFrame raw;
408 raw.canId = receivedId;
409 raw.isExtended = true;
410 const DbcDecoder decoder;
411 for (const std::vector<DecodeFrame> &frames: {std::vector<DecodeFrame>{first, second},
412 std::vector<DecodeFrame>{second, first}}) {
413 const DecodeDatabase database(frames);
414 const PgnLookupResult lookup = database.FindFrameByPgn(pgn);
415 QVERIFY(lookup.ambiguous);
416 QVERIFY(!lookup.definition);
417 QVERIFY(!decoder.FindFrame(database, receivedId, true));
418 const DecodedFrameValue result = decoder.Decode(database, raw);
419 QCOMPARE(result.status, FrameDecodeStatus::AmbiguousFrame);
420 QVERIFY(!result.valid);
421 QVERIFY(!result.definition);
422 QVERIFY(result.signals.empty());
423 QVERIFY(result.key == FrameKey(receivedId, true));
424 QCOMPARE(decoder.Decode(database, raw, {SignalKey(first.GetKey(), "Missing")}).status,
425 FrameDecodeStatus::AmbiguousFrame);
426 raw.canId = firstId;
427 const DecodedFrameValue exact = decoder.Decode(database, raw);
428 QCOMPARE(exact.status, FrameDecodeStatus::Valid);
429 QVERIFY(exact.key == first.GetKey());
430 raw.canId = receivedId;
431 }
432 // A generic extended definition neither creates ambiguity nor loses exact-ID precedence.
433 second.hasPgn = false;
434 second.canId = receivedId;
435 const DecodeDatabase mixed({first, second});
436 QVERIFY(!mixed.FindFrameByPgn(pgn).ambiguous);
437 QCOMPARE(mixed.FindFrameByPgn(pgn).definition->canId, firstId);
438 QCOMPARE(decoder.Decode(mixed, raw).definition->canId, receivedId);
439 raw.canId = receivedId ^ 1U;
440 QCOMPARE(decoder.Decode(mixed, raw).definition->canId, firstId);
441}
442
443void CoverageTest::j1939MetadataValidation() {
444 DecodeFrame frame;
445 frame.canId = 0x18F00442U;
446 frame.isExtended = true;
447 frame.hasPgn = true;
448 frame.pgn = 0xF005U;
449 QVERIFY_THROWS_EXCEPTION(std::invalid_argument, (void) DecodeDatabase({frame}));
450 DbcDatabase source;
451 FrameInfo metadata;
452 metadata.canId = frame.canId;
453 metadata.isExtended = true;
454 metadata.hasPgn = true;
455 metadata.pgn = frame.pgn;
456 source.frames.push_back(metadata);
457 QVERIFY_THROWS_EXCEPTION(std::invalid_argument, DbcDecodeBuilder().Build(source));
458 frame.canId = 1U;
459 frame.isExtended = false;
460 frame.pgn = 0U;
461 QVERIFY_THROWS_EXCEPTION(std::invalid_argument, (void) DecodeDatabase({frame}));
462}
463
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");
473}
474
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());
483}
484
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\";");
496}
497
498void CoverageTest::ignoredCommentStatements() {
499 QFETCH(QString, statement);
500 DbcDatabase database;
501 FrameInfo frame;
502 frame.canId = 1U;
503 frame.name = "Frame";
504 frame.comment = "Existing frame comment";
505 SignalInfo signal;
506 signal.name = "State";
507 signal.comment = "Existing signal comment";
508 frame.signals.push_back(signal);
509 database.frames.push_back(frame);
510 DbcParseContext context{database, &database.frames[0]};
511
512 // The handler is public: callers may bypass keyword dispatch. Ignored input
513 // must preserve existing metadata, including comments already assigned.
514 DbcCommentHandler().Handle(statement.toStdString(), context);
515 QCOMPARE(database.frames.size(), std::size_t(1));
516 QCOMPARE(database.frames[0].canId, 1U);
517 QCOMPARE(database.frames[0].name, frame.name);
518 QCOMPARE(database.frames[0].comment, frame.comment);
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]);
523}
524
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;
532}
533
534void CoverageTest::ambiguousValueReferences() {
535 QFETCH(bool, duplicateFrames);
536 QFETCH(bool, extended);
537 DbcDatabase database;
538 FrameInfo frame;
539 frame.canId = 1U;
540 frame.name = "Frame";
541 frame.isExtended = extended;
542 SignalInfo signal;
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);
548
549 // ParseFile rejects duplicate definitions earlier. Construct the database
550 // through the public metadata API to exercise the resolver's own checks.
551 DbcParseContext context{database, nullptr};
552 context.lineNumber = 37U;
553 const std::string rawId = extended ? "2147483649" : "1";
554 DbcValueDescriptionHandler().Handle("VAL_ " + rawId + " State 0 \"Off\";", context);
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";
558 try {
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);
565 }
566 // Ambiguity must be detected before applying any descriptions.
567 for (const FrameInfo &candidate: database.frames)
568 for (const SignalInfo &candidateSignal: candidate.signals)
569 QVERIFY(candidateSignal.valueDescriptions.empty());
570}
571
572void CoverageTest::valueReferenceScope() {
573 DbcDatabase database;
574 FrameInfo frame;
575 frame.canId = 1U;
576 SignalInfo signal;
577 signal.name = "State";
578 frame.signals.push_back(signal);
579 database.frames.push_back(frame);
580 frame.isExtended = true;
581 database.frames.push_back(frame);
582 frame.canId = 2U;
583 frame.isExtended = false;
584 database.frames.push_back(frame);
585
586 DbcParseContext context{database, nullptr};
587 const DbcValueDescriptionHandler handler;
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");
593
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());
601}
602
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;
611}
612
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));
618 if (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"));
625 }
626 }
627}
628
629void CoverageTest::missingFile() {
630 QTemporaryFile file;
631 QVERIFY(file.open());
632 const QString path = file.fileName();
633 QVERIFY(file.remove());
634 try {
635 DbcParser().ParseFile(path.toStdString());
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());
639 }
640}
641
642void CoverageTest::builders() {
643 const DecodeDatabase emptyRuntime = DbcDecodeBuilder().Build(DbcDatabase());
644 QVERIFY(emptyRuntime.GetFrames().empty());
645 QVERIFY(!emptyRuntime.FindFrame(FrameKey(0U, false)));
646 const std::unique_ptr<TreeNode> emptyTree = DbcTreeBuilder().Build(DbcDatabase());
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);
651 const DbcDatabase expected = ParseText(
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");
659 DecodeDatabase runtime;
660 std::unique_ptr<TreeNode> tree;
661 {
662 DbcDatabase source = expected;
663 runtime = DbcDecodeBuilder().Build(source);
664 tree = DbcTreeBuilder().Build(source);
665 source.frames[0].name = "Changed";
666 source.frames[0].signals[1].receivers.clear();
667 source.frames[0].signals[1].valueDescriptions.clear();
668 source.frames.clear();
669 }
670 QCOMPARE(runtime.GetFrames().size(), expected.frames.size());
671 QCOMPARE(tree->GetChildCount(), expected.frames.size());
672 for (std::size_t i = 0; i < expected.frames.size(); ++i) {
673 const FrameInfo &frame = expected.frames[i];
674 const DecodeFrame &decodedFrame = runtime.GetFrames()[i];
675 const TreeNode *node = tree->GetChild(i);
676 QVERIFY(node && node->GetFrame());
677 QCOMPARE(node->GetType(), NodeType::Frame);
678 QCOMPARE(node->GetName(), frame.name);
679 QVERIFY(node->GetSignal() == nullptr);
680 QCOMPARE(runtime.FindFrame(FrameKey(frame.canId, frame.isExtended)).get(), &decodedFrame);
681 const FrameInfo &copied = *node->GetFrame();
682 QCOMPARE(copied.name, frame.name);
683 QCOMPARE(decodedFrame.name, frame.name);
684 QCOMPARE(copied.canId, frame.canId);
685 QCOMPARE(decodedFrame.canId, frame.canId);
686 QCOMPARE(copied.isExtended, frame.isExtended);
687 QCOMPARE(decodedFrame.isExtended, frame.isExtended);
688 QCOMPARE(copied.dlc, frame.dlc);
689 QCOMPARE(decodedFrame.dlc, frame.dlc);
690 QCOMPARE(copied.pgn, frame.pgn);
691 QCOMPARE(decodedFrame.pgn, frame.pgn);
692 QCOMPARE(copied.hasPgn, frame.hasPgn);
693 QCOMPARE(decodedFrame.hasPgn, frame.hasPgn);
694 QCOMPARE(copied.transmitter, frame.transmitter);
695 QCOMPARE(decodedFrame.transmitter, frame.transmitter);
696 QCOMPARE(copied.comment, frame.comment);
697 QCOMPARE(decodedFrame.comment, frame.comment);
698 QCOMPARE(node->GetChildCount(), frame.signals.size());
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];
703 const DecodeSignal &target = decodedFrame.signals[j];
704 const TreeNode *leaf = node->GetChild(j);
705 QVERIFY(leaf && leaf->GetSignal());
706 QCOMPARE(leaf->GetType(), NodeType::Signal);
707 QCOMPARE(leaf->GetName(), original.name);
708 QVERIFY(leaf->GetFrame() == nullptr);
709 // Check both the frame payload copy and each child payload copy.
710 for (const SignalInfo *copy: {&copied.signals[j], leaf->GetSignal()}) {
711 QCOMPARE(copy->name, original.name);
712 QCOMPARE(copy->startBit, original.startBit);
713 QCOMPARE(copy->length, original.length);
714 QCOMPARE(copy->isLittleEndian, original.isLittleEndian);
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);
723 QVERIFY(copy->valueDescriptions == original.valueDescriptions);
724 QCOMPARE(copy->multiplexing.role, original.multiplexing.role);
725 QCOMPARE(copy->multiplexing.value, original.multiplexing.value);
726 }
727 QCOMPARE(target.name, original.name);
728 QCOMPARE(target.startBit, original.startBit);
729 QCOMPARE(target.length, original.length);
730 QCOMPARE(target.byteOrder, original.isLittleEndian ? ByteOrder::Intel : ByteOrder::Motorola);
731 QCOMPARE(target.valueType, original.isSigned ? ValueType::Signed : ValueType::Unsigned);
732 QCOMPARE(target.factor, original.factor);
733 QCOMPARE(target.offset, original.offset);
734 QCOMPARE(target.minimum, original.minimum);
735 QCOMPARE(target.maximum, original.maximum);
736 QCOMPARE(target.unit, original.unit);
737 QCOMPARE(target.comment, original.comment);
738 QVERIFY(target.receivers == original.receivers);
739 QVERIFY(target.valueDescriptions == original.valueDescriptions);
740 QCOMPARE(target.multiplexing.role, original.multiplexing.role);
741 QCOMPARE(target.multiplexing.value, original.multiplexing.value);
742 }
743 }
744}
745
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;
753}
754
755void CoverageTest::physicalConversion() {
756 QFETCH(double, factor);
757 QFETCH(double, offset);
758 QFETCH(double, expected);
759 DecodeSignal signal;
760 signal.length = 8U;
761 signal.factor = factor;
762 signal.offset = offset;
763 signal.minimum = 0.0;
764 signal.maximum = 1.0;
765 const DecodeDatabase database = SingleSignal(signal);
766 RawCanFrame raw;
767 raw.canId = 17U;
768 raw.data = {10U};
769 const DecodedFrameValue decoded = DbcDecoder().Decode(database, raw);
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);
773}
774
775void CoverageTest::multiplexingScenarios() {
776 const DecodeDatabase selectorOnly = DbcDecodeBuilder().Build(ParseText(
777 "BO_ 1 Frame: 1 ECU\nSG_ Mode M : 0|1@1+ (1,0) [0|1] \"\" ECU\n"));
778 RawCanFrame raw;
779 raw.canId = 1U;
780 raw.data = {1U};
781 const DecodedFrameValue only = DbcDecoder().Decode(selectorOnly, raw);
782 QCOMPARE(only.signals.size(), std::size_t(1));
783 QVERIFY(only.signals[0].valid);
784 const DecodeDatabase database = DbcDecodeBuilder().Build(ParseText(
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};
793 const DecodedFrameValue first = DbcDecoder().Decode(database, raw);
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));
797 raw.canId = 2U;
798 const DecodedFrameValue secondInactive = DbcDecoder().Decode(database, raw);
799 QCOMPARE(secondInactive.signals[1].status, SignalDecodeStatus::Inactive);
800 raw.data[0] = 1U;
801 QVERIFY(DbcDecoder().Decode(database, raw).signals[1].valid);
802 raw.canId = 1U;
803 const DecodedFrameValue firstInactive = DbcDecoder().Decode(database, raw);
804 QCOMPARE(firstInactive.signals[1].status, SignalDecodeStatus::Inactive);
805 QCOMPARE(firstInactive.signals[2].status, SignalDecodeStatus::Inactive);
806}
807
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.");
833}
834
835void CoverageTest::multiplexingErrorLocations() {
836 QFETCH(QString, text);
837 QFETCH(unsigned, line);
838 QFETCH(QString, message);
839 QTemporaryFile file;
840 QVERIFY(file.open());
841 const QByteArray content = text.toUtf8();
842 QCOMPARE(file.write(content), qint64(content.size()));
843 QVERIFY(file.flush());
844 try {
845 DbcParser().ParseFile(file.fileName().toStdString());
846 QFAIL("Expected multiplexing rejection");
847 } catch (const std::runtime_error &error) {
848 QCOMPARE(QString::fromStdString(error.what()), file.fileName() + ":" + QString::number(line) + ": " + message);
849 }
850}
851
852void CoverageTest::escapedValueDescriptions() {
853 const DbcDatabase parsed = ParseText(
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"
856 "\n");
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)));
869}
870
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))));
876 factory.registerType<FactoryFirst>("value");
877 QVERIFY(factory.contains("value"));
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);
882 const TypeFactory<std::string, FactoryBase, std::unique_ptr<int>>::CreatorFn creator = factory.creator("value");
883 QCOMPARE(creator(std::make_unique<int>(7))->Value(), 7);
884 factory.registerType<FactorySecond>("value");
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);
888}
889
890int RunCoverageTests(int argc, char **argv) {
891 CoverageTest tests;
892 return QTest::qExec(&tests, argc, argv);
893}
894#include "dbc_coverage_test.moc"
Defer frame and signal comments for target resolution.
void Handle(const std::string &line, DbcParseContext &context) const override
Process a trimmed statement using state belonging to this parse.
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.
Definition dbc_decoder.h:86
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.
Minimal DBC parser.
Definition dbc_parser.h:41
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.
Definition tree_node.h:37
const SignalInfo * GetSignal() const
Get signal payload if node is signal.
Definition tree_node.cpp:71
const TreeNode * GetChild(std::size_t index) const
Get child by index.
Definition tree_node.cpp:45
const std::string & GetName() const
Get display name.
Definition tree_node.cpp:63
std::size_t GetChildCount() const
Get child count.
Definition tree_node.cpp:41
NodeType GetType() const
Get node type.
Definition tree_node.cpp:59
const FrameInfo * GetFrame() const
Get frame payload if node is frame.
Definition tree_node.cpp:67
Registry-based factory for creating objects by runtime identifier.
Definition typefactory.h:59
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.
Definition typefactory.h:99
void registerType(const ClassId &id)
Registers a Derived type under the given id.
Definition typefactory.h:78
CreatorFn creator(const ClassId &id) const
Returns creator function for the given id.
Definition typefactory.h:91
Runtime decode database builder.
CAN frame decoding API and result types.
FrameDecodeStatus
Definition dbc_decoder.h:43
DBC file parser and interpretation options.
Metadata tree builder.
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.
std::uint32_t pgn
std::string comment
std::uint8_t dlc
std::uint32_t canId
std::string name
std::string transmitter
Runtime-ready signal definition.
std::string unit
std::string name
ValueDescriptions valueDescriptions
ValueType valueType
std::uint32_t startBit
std::vector< std::string > receivers
ByteOrder byteOrder
std::uint32_t length
std::string comment
Multiplexing multiplexing
Fully decoded frame.
Definition dbc_decoder.h:73
std::shared_ptr< const DecodeFrame > definition
Definition dbc_decoder.h:74
FrameDecodeStatus status
Definition dbc_decoder.h:78
Describes one CAN frame from a DBC file.
Definition frame_info.h:25
bool isExtended
Definition frame_info.h:28
bool hasPgn
Definition frame_info.h:30
std::string transmitter
Definition frame_info.h:32
std::uint8_t dlc
Definition frame_info.h:31
std::string name
Definition frame_info.h:26
std::string comment
Definition frame_info.h:33
std::uint32_t pgn
Definition frame_info.h:29
std::uint32_t canId
Definition frame_info.h:27
Stable frame identifier: normalized CAN ID and frame type.
MultiplexRole role
std::uint64_t value
std::shared_ptr< const DecodeFrame > definition
Raw CAN frame used for runtime or trace decoding.
Definition dbc_decoder.h:24
std::vector< std::uint8_t > data
Definition dbc_decoder.h:27
std::uint32_t canId
Definition dbc_decoder.h:25
bool isExtended
Definition dbc_decoder.h:26
Describes one signal inside a DBC frame.
Definition signal_info.h:25
std::uint32_t length
Definition signal_info.h:29
std::string unit
Definition signal_info.h:36
std::vector< std::string > receivers
Definition signal_info.h:37
std::string name
Definition signal_info.h:27
ValueDescriptions valueDescriptions
Definition signal_info.h:38
bool isSigned
Definition signal_info.h:31
Multiplexing multiplexing
Definition signal_info.h:26
double offset
Definition signal_info.h:33
std::string comment
Definition signal_info.h:39
std::uint32_t startBit
Definition signal_info.h:28
double minimum
Definition signal_info.h:34
double maximum
Definition signal_info.h:35
double factor
Definition signal_info.h:32
bool isLittleEndian
Definition signal_info.h:30