This commit is contained in:
deeaitch committed 2026-10-20 13:12:30 -04:00
1 parent fc97682b37
commit e555ebb571
16 files changed
+337 -52

No files matched your search

+12
View File
@@ -152,6 +152,18 @@ and source lifetime, physical conversion, and TypeFactory registration/creation.
Direct resolver tests construct ambiguous databases through the public metadata
API, verify source locations, and check that ambiguous targets receive no values.
J1939 regressions in `dbc_coverage_test.cpp` verify explicit parser opt-in and
ordinary extended CAN defaults, PF 239/240 and all data-page combinations, PGN
zero, PDU1 destination exclusion, PDU2 group-extension matching, changed source
and priority, standard/invalid-ID exclusion, and inconsistent manual metadata.
They also cover exact-ID precedence, order-independent ambiguous PGNs for both
PDU formats, mixed ordinary/J1939 definitions, selected-signal definition keys,
raw/physical values, and owning PGN handles across snapshot replacement.
The pipeline fixture explicitly enables J1939 through the diagnostic parser API.
Verified in `build/` with `qmake6 ../dbc.pro`, `make -j4`, and `make check`:
all six QtTest suites and the Qt-free tree target passed. The existing named
comma-decimal C locale regression skipped because that locale is not installed.
All QtTest targets use Qt 6.8.2 and `QVERIFY_THROWS_EXCEPTION`. Exception tests
explicitly discard `nodiscard` results when only the exception is being checked.
The standalone tree test requires only a C++17 compiler.
+161 -4
View File
@@ -12,12 +12,12 @@
#include <stdexcept>
namespace {
DbcDatabase ParseText(const std::string &text) {
DbcDatabase ParseText(const std::string &text, bool enableJ1939 = false) {
QTemporaryFile file;
if (!file.open()) throw std::runtime_error("Cannot create DBC fixture");
if (file.write(text.data(), static_cast<qint64>(text.size())) != static_cast<qint64>(text.size()) || !file.flush())
throw std::runtime_error("Cannot write DBC fixture");
return DbcParser().ParseFile(file.fileName().toStdString());
return DbcParser(DbcParserOptions{enableJ1939}).ParseFile(file.fileName().toStdString());
}
DecodeDatabase SingleSignal(const DecodeSignal &signal) {
@@ -73,6 +73,11 @@ private Q_SLOTS:
void malformedRuntimeMultiplexing();
void pgn_data();
void pgn();
void j1939Lookup_data();
void j1939Lookup();
void j1939Ambiguity_data();
void j1939Ambiguity();
void j1939MetadataValidation();
void comments_data();
void comments();
void ignoredCommentStatements_data();
@@ -285,7 +290,7 @@ void CoverageTest::pgn_data() {
QTest::addColumn<unsigned>("expected");
QTest::addColumn<bool>("hasPgn");
QTest::newRow("standard") << 0x7FFU << 0U << false;
for (unsigned dp: {0U, 1U})
for (unsigned dp: {0U, 1U, 2U, 3U})
for (unsigned pf: {239U, 240U})
for (unsigned ps: {0U, 0xABU}) {
const unsigned id = 0x80000000U | (dp << 24U) | (pf << 16U) | (ps << 8U) | 0x42U;
@@ -299,11 +304,163 @@ void CoverageTest::pgn() {
QFETCH(unsigned, id);
QFETCH(unsigned, expected);
QFETCH(bool, hasPgn);
const DbcDatabase parsed = ParseText("BO_ " + std::to_string(id) + " Frame: 8 ECU\n");
const std::string text = "BO_ " + std::to_string(id) + " Frame: 8 ECU\n";
const DbcDatabase generic = ParseText(text);
QVERIFY(!generic.frames[0].hasPgn);
QCOMPARE(generic.frames[0].pgn, 0U);
const DbcDatabase parsed = ParseText(text, true);
QCOMPARE(parsed.frames[0].hasPgn, hasPgn);
QCOMPARE(parsed.frames[0].pgn, expected);
}
void CoverageTest::j1939Lookup_data() {
QTest::addColumn<unsigned>("definitionId");
QTest::addColumn<unsigned>("receivedId");
QTest::addColumn<unsigned>("pgn");
QTest::addColumn<bool>("matches");
QTest::newRow("pdu1-source") << 0x18EF1242U << 0x18EF1299U << 0xEF00U << true;
QTest::newRow("pdu1-destination-priority") << 0x18EF1242U << 0x0CEF3499U << 0xEF00U << true;
QTest::newRow("pdu2-source-priority") << 0x18F00442U << 0x0CF00499U << 0xF004U << true;
QTest::newRow("pdu2-group-extension") << 0x18F00442U << 0x18F00599U << 0xF004U << false;
QTest::newRow("data-page") << 0x18F00442U << 0x19F00499U << 0xF004U << false;
QTest::newRow("extended-data-page") << 0x18F00442U << 0x1AF00499U << 0xF004U << false;
QTest::newRow("pgn-zero") << 0x80000123U << 0x00000456U << 0U << true;
}
void CoverageTest::j1939Lookup() {
QFETCH(unsigned, definitionId);
QFETCH(unsigned, receivedId);
QFETCH(unsigned, pgn);
QFETCH(bool, matches);
const std::string text = "BO_ " + std::to_string(definitionId) + " Frame: 1 ECU\n"
"SG_ Value : 0|8@1+ (2,1) [0|511] \"\" ECU\n";
const DbcDecoder decoder;
RawCanFrame raw;
raw.canId = receivedId;
raw.isExtended = true;
raw.data = {7U};
const DecodeDatabase generic = DbcDecodeBuilder().Build(ParseText(text));
QVERIFY(!decoder.FindFrame(generic, receivedId, true));
QCOMPARE(decoder.Decode(generic, raw).status, FrameDecodeStatus::UnknownFrame);
QVERIFY(!generic.FindFrameByPgn(pgn).definition);
DecodeDatabase database = DbcDecodeBuilder().Build(ParseText(text, true));
const FrameKey key(definitionId & 0x1FFFFFFFU, true);
const PgnLookupResult lookup = database.FindFrameByPgn(pgn);
QVERIFY(!lookup.ambiguous);
QVERIFY(lookup.definition != nullptr);
QCOMPARE(lookup.definition.get(), database.FindFrame(key).get());
const DecodedFrameValue result = decoder.Decode(database, raw);
QCOMPARE(result.valid, matches);
QCOMPARE(result.status, matches ? FrameDecodeStatus::Valid : FrameDecodeStatus::UnknownFrame);
QCOMPARE(bool(decoder.FindFrame(database, receivedId, true)), matches);
if (matches) {
QVERIFY(result.key == key);
QCOMPARE(result.definition.get(), lookup.definition.get());
QCOMPARE(result.signals.size(), std::size_t(1));
QVERIFY(result.signals[0].key == SignalKey(key, "Value"));
QCOMPARE(std::get<std::uint64_t>(result.signals[0].rawValue), std::uint64_t(7));
QCOMPARE(result.signals[0].physicalValue, 15.0);
const DecodedFrameValue selected = decoder.Decode(database, raw, {SignalKey(key, "Value")});
QCOMPARE(selected.status, FrameDecodeStatus::Valid);
QVERIFY(selected.signals[0].key == result.signals[0].key);
QVERIFY_THROWS_EXCEPTION(std::invalid_argument,
decoder.Decode(database, raw, {SignalKey(FrameKey(receivedId, true), "Value")}));
} else {
QVERIFY(result.key == FrameKey(receivedId, true));
QVERIFY(!result.definition);
QVERIFY(result.signals.empty());
}
raw.isExtended = false;
QCOMPARE(decoder.Decode(database, raw).status, FrameDecodeStatus::UnknownFrame);
raw.isExtended = true;
raw.canId = receivedId | 0x80000000U;
QCOMPARE(decoder.Decode(database, raw).status, FrameDecodeStatus::UnknownFrame);
database = DecodeDatabase();
QCOMPARE(lookup.definition->pgn, pgn); // PGN handles own the old snapshot.
QVERIFY(!database.FindFrameByPgn(pgn).definition);
QVERIFY(!database.FindFrameByPgn(pgn).ambiguous);
}
void CoverageTest::j1939Ambiguity_data() {
QTest::addColumn<unsigned>("firstId");
QTest::addColumn<unsigned>("secondId");
QTest::addColumn<unsigned>("receivedId");
QTest::addColumn<unsigned>("pgn");
QTest::newRow("pdu1") << 0x18EF1242U << 0x18EF3443U << 0x18EF5699U << 0xEF00U;
QTest::newRow("pdu2") << 0x18F00442U << 0x0CF00443U << 0x18F00499U << 0xF004U;
}
void CoverageTest::j1939Ambiguity() {
QFETCH(unsigned, firstId);
QFETCH(unsigned, secondId);
QFETCH(unsigned, receivedId);
QFETCH(unsigned, pgn);
DecodeFrame first;
first.canId = firstId;
first.isExtended = true;
first.hasPgn = true;
first.pgn = pgn;
DecodeFrame second = first;
second.canId = secondId;
RawCanFrame raw;
raw.canId = receivedId;
raw.isExtended = true;
const DbcDecoder decoder;
for (const std::vector<DecodeFrame> &frames: {std::vector<DecodeFrame>{first, second},
std::vector<DecodeFrame>{second, first}}) {
const DecodeDatabase database(frames);
const PgnLookupResult lookup = database.FindFrameByPgn(pgn);
QVERIFY(lookup.ambiguous);
QVERIFY(!lookup.definition);
QVERIFY(!decoder.FindFrame(database, receivedId, true));
const DecodedFrameValue result = decoder.Decode(database, raw);
QCOMPARE(result.status, FrameDecodeStatus::AmbiguousFrame);
QVERIFY(!result.valid);
QVERIFY(!result.definition);
QVERIFY(result.signals.empty());
QVERIFY(result.key == FrameKey(receivedId, true));
QCOMPARE(decoder.Decode(database, raw, {SignalKey(first.GetKey(), "Missing")}).status,
FrameDecodeStatus::AmbiguousFrame);
raw.canId = firstId;
const DecodedFrameValue exact = decoder.Decode(database, raw);
QCOMPARE(exact.status, FrameDecodeStatus::Valid);
QVERIFY(exact.key == first.GetKey());
raw.canId = receivedId;
}
// A generic extended definition neither creates ambiguity nor loses exact-ID precedence.
second.hasPgn = false;
second.canId = receivedId;
const DecodeDatabase mixed({first, second});
QVERIFY(!mixed.FindFrameByPgn(pgn).ambiguous);
QCOMPARE(mixed.FindFrameByPgn(pgn).definition->canId, firstId);
QCOMPARE(decoder.Decode(mixed, raw).definition->canId, receivedId);
raw.canId = receivedId ^ 1U;
QCOMPARE(decoder.Decode(mixed, raw).definition->canId, firstId);
}
void CoverageTest::j1939MetadataValidation() {
DecodeFrame frame;
frame.canId = 0x18F00442U;
frame.isExtended = true;
frame.hasPgn = true;
frame.pgn = 0xF005U;
QVERIFY_THROWS_EXCEPTION(std::invalid_argument, (void) DecodeDatabase({frame}));
DbcDatabase source;
FrameInfo metadata;
metadata.canId = frame.canId;
metadata.isExtended = true;
metadata.hasPgn = true;
metadata.pgn = frame.pgn;
source.frames.push_back(metadata);
QVERIFY_THROWS_EXCEPTION(std::invalid_argument, DbcDecodeBuilder().Build(source));
frame.canId = 1U;
frame.isExtended = false;
frame.pgn = 0U;
QVERIFY_THROWS_EXCEPTION(std::invalid_argument, (void) DecodeDatabase({frame}));
}
void CoverageTest::comments_data() {
QTest::addColumn<QString>("before");
QTest::addColumn<QString>("after");
+1 -1
View File
@@ -29,7 +29,7 @@ VAL_ 2147483939 State -2 "Extended minus two";
if (!file.open()) throw std::runtime_error("Cannot create DBC fixture");
if (file.write(text.data(), static_cast<qint64>(text.size())) != static_cast<qint64>(text.size()) || !file.flush())
throw std::runtime_error("Cannot write DBC fixture");
return DbcParser().ParseFileWithDiagnostics(file.fileName().toStdString());
return DbcParser(DbcParserOptions{true}).ParseFileWithDiagnostics(file.fileName().toStdString());
}
// Independent expectations ensure a parser regression cannot be hidden by