// SPDX-License-Identifier: MIT // Copyright (c) 2026 Dim Himro #include "dbc_decode_builder.h" #include "dbc_decoder.h" #include "dbc_parser.h" #include #include #include #include #include #include static_assert(std::is_same_v().GetFrames()), const std::vector &>); static_assert(std::is_same_v>); static_assert(std::is_same_v>); namespace { DecodeDatabase BuildText(const std::string &text) { QTemporaryFile file; if (!file.open() || file.write(text.data(), static_cast(text.size())) != static_cast(text.size()) || !file.flush()) throw std::runtime_error("Cannot write reload fixture"); return DbcDecodeBuilder().Build(DbcParser().ParseFile(file.fileName().toStdString())); } const std::string firstDbc = "BO_ 17 OldFrame: 2 ECU\n" "SG_ State : 0|8@1+ (2,1) [1|511] \"old\" ECU\n" "SG_ Removed : 8|8@1+ (1,0) [0|255] \"\" ECU\n" "VAL_ 17 State 1 \"Old label\";\n" "BO_ 18 Deleted: 1 ECU\n" "SG_ State : 0|8@1+ (1,0) [0|255] \"\" ECU\n"; const std::string secondDbc = "BO_ 2147483665 Extended: 1 ECU\n" "SG_ State : 0|8@1+ (1,0) [0|255] \"\" ECU\n" "BO_ 17 NewFrame: 2 ECU\n" "SG_ Renamed : 8|8@1+ (1,0) [0|255] \"\" ECU\n" "SG_ State : 0|8@1+ (4,0) [0|1020] \"new\" ECU\n" "VAL_ 17 State 1 \"New label\";\n"; }// namespace class DbcLifetimeTest : public QObject { Q_OBJECT private Q_SLOTS: void reload(); void independentSignalLifetime(); void lookupLifetime(); void keys(); void duplicateKeys(); void failedReload(); void invalidResults(); void retainedMultiplexStatuses(); }; void DbcLifetimeTest::reload() { DecodeDatabase database = BuildText(firstDbc); RawCanFrame raw; raw.canId = 17U; raw.data = {1U, 42U}; DecodedFrameValue oldResult = DbcDecoder().Decode(database, raw); const DecodedFrameValue copied = oldResult; DecodedFrameValue moved = std::move(oldResult); std::weak_ptr oldDefinition = moved.definition; database = BuildText(secondDbc); const DecodedFrameValue current = DbcDecoder().Decode(database, raw); QVERIFY(current.valid && moved.valid && copied.valid); QVERIFY(current.definition != moved.definition); QVERIFY(current.key == moved.key); QVERIFY(current.signals[1].key == moved.signals[0].key); QCOMPARE(moved.definition->name, std::string("OldFrame")); QCOMPARE(copied.definition, moved.definition); QCOMPARE(moved.signals[0].physicalValue, 3.0); QCOMPARE(current.signals[1].physicalValue, 4.0); QCOMPARE(moved.signals[0].definition->unit, std::string("old")); QCOMPARE(current.signals[1].definition->unit, std::string("new")); QCOMPARE(*moved.signals[0].GetValueDescription(), std::string("Old label")); QCOMPARE(*current.signals[1].GetValueDescription(), std::string("New label")); QCOMPARE(moved.signals[1].definition->name, std::string("Removed")); QVERIFY(!database.FindSignal(moved.signals[1].key)); database = DecodeDatabase(); QVERIFY(!oldDefinition.expired()); QCOMPARE(current.definition->name, std::string("NewFrame")); QCOMPARE(*current.signals[1].GetValueDescription(), std::string("New label")); } void DbcLifetimeTest::independentSignalLifetime() { DecodedSignalValue saved; std::weak_ptr definition; const std::string *label = nullptr; { DecodeDatabase database = BuildText(firstDbc); RawCanFrame raw; raw.canId = 17U; raw.data = {1U, 0U}; DecodedFrameValue result = DbcDecoder().Decode(database, raw); saved = result.signals[0]; definition = saved.definition; label = saved.GetValueDescription(); result = DecodedFrameValue(); database = BuildText(secondDbc); } QVERIFY(!definition.expired()); QVERIFY(label != nullptr); QCOMPARE(*label, std::string("Old label")); QCOMPARE(saved.definition->factor, 2.0); DecodedSignalValue copied = saved; DecodedSignalValue moved = std::move(saved); QCOMPARE(moved.definition, copied.definition); saved = DecodedSignalValue(); moved = DecodedSignalValue(); QVERIFY(!definition.expired()); copied = DecodedSignalValue(); QVERIFY(definition.expired()); } void DbcLifetimeTest::lookupLifetime() { std::shared_ptr frame; std::shared_ptr signal; std::weak_ptr released; { DecodeDatabase database = BuildText(firstDbc); DecodeDatabase copy = database; frame = DbcDecoder().FindFrame(database, 17U, false); signal = copy.FindSignal(SignalKey(FrameKey(17U, false), "State")); QVERIFY(frame && signal); QCOMPARE(frame.get(), ©.GetFrames()[0]); QCOMPARE(signal.get(), &frame->signals[0]); released = frame; database = BuildText(secondDbc); DecodeDatabase moved = std::move(copy); QVERIFY(copy.GetFrames().empty()); QVERIFY(!copy.FindFrame(FrameKey(17U, false))); QCOMPARE(moved.FindFrame(FrameKey(17U, false)), frame); } QCOMPARE(frame->name, std::string("OldFrame")); frame.reset(); QVERIFY(!released.expired()); QCOMPARE(signal->name, std::string("State")); signal.reset(); QVERIFY(released.expired()); } void DbcLifetimeTest::keys() { const FrameKey frame(17U, false); const SignalKey state(frame, "State"); const DecodeDatabase first = BuildText(firstDbc); const DecodeDatabase second = BuildText(secondDbc); QVERIFY(first.FindFrame(frame)->GetKey() == second.FindFrame(frame)->GetKey()); QVERIFY(first.FindSignal(state) && second.FindSignal(state)); QVERIFY(!second.FindFrame(FrameKey(18U, false))); QVERIFY(!second.FindSignal(SignalKey(frame, "Removed"))); QVERIFY(second.FindSignal(SignalKey(frame, "Renamed"))); QVERIFY(second.FindSignal(SignalKey(FrameKey(17U, true), "State"))); QVERIFY(!second.FindSignal(SignalKey(FrameKey(999U, false), "State"))); QVERIFY(first.FindSignal(SignalKey(FrameKey(18U, false), "State"))); std::unordered_map frames; frames.emplace(frame, 1); frames.emplace(FrameKey(17U, true), 2); QCOMPARE(frames.size(), std::size_t(2U)); std::unordered_map signals; signals.emplace(state, 1); signals.emplace(SignalKey(FrameKey(17U, true), "State"), 2); signals.emplace(SignalKey(frame, "Renamed"), 3); signals.emplace(SignalKey(FrameKey(18U, false), "State"), 4); QCOMPARE(signals.at(SignalKey(FrameKey(17U, false), "State")), 1); QCOMPARE(signals.size(), std::size_t(4U)); } void DbcLifetimeTest::duplicateKeys() { DecodeFrame frame; frame.canId = 17U; QVERIFY_THROWS_EXCEPTION(std::invalid_argument, (void) DecodeDatabase({frame, frame})); DecodeSignal signal; signal.name = "State"; signal.length = 1U; frame.dlc = 1U; frame.signals = {signal, signal}; QVERIFY_THROWS_EXCEPTION(std::invalid_argument, (void) DecodeDatabase({frame})); DbcDatabase parsed; FrameInfo source; source.canId = 17U; parsed.frames = {source, source}; QVERIFY_THROWS_EXCEPTION(std::invalid_argument, (void) DbcDecodeBuilder().Build(parsed)); } void DbcLifetimeTest::failedReload() { DecodeDatabase database = BuildText(firstDbc); const std::shared_ptr original = database.FindFrame(FrameKey(17U, false)); QVERIFY_THROWS_EXCEPTION(std::runtime_error, database = BuildText("BO_ invalid\n")); QCOMPARE(database.FindFrame(FrameKey(17U, false)), original); database = BuildText(""); QVERIFY(database.GetFrames().empty()); QVERIFY(!database.FindFrame(FrameKey(17U, false))); QCOMPARE(original->name, std::string("OldFrame")); } void DbcLifetimeTest::invalidResults() { RawCanFrame raw; raw.canId = 17U; DecodedFrameValue result = DbcDecoder().Decode(BuildText(firstDbc), raw); QVERIFY(result.valid); QVERIFY(!result.signals[0].valid); QCOMPARE(result.signals[0].status, SignalDecodeStatus::InsufficientPayload); QVERIFY(!result.signals[0].GetValueDescription()); QCOMPARE(result.signals[0].definition->name, std::string("State")); result = DbcDecoder().Decode(DecodeDatabase(), raw); QVERIFY(!result.valid && !result.definition && result.signals.empty()); QVERIFY(result.key == FrameKey(17U, false)); QVERIFY(!DecodedSignalValue().GetValueDescription()); QVERIFY(!DecodeDatabase().FindSignal(SignalKey(FrameKey(17U, false), "State"))); } void DbcLifetimeTest::retainedMultiplexStatuses() { const std::string text = "BO_ 17 Frame: 2 ECU\n" "SG_ Mode M : 0|1@1+ (1,0) [0|1] \"\" ECU\n" "SG_ State m1 : 8|8@1+ (1,0) [0|255] \"\" ECU\n"; const std::vector> payloads = {{0U}, {}, {1U}}; const SignalDecodeStatus statuses[] = {SignalDecodeStatus::Inactive, SignalDecodeStatus::SelectorUnavailable, SignalDecodeStatus::InsufficientPayload}; for (std::size_t index = 0U; index < payloads.size(); ++index) { DecodeDatabase database = BuildText(text); RawCanFrame raw; raw.canId = 17U; raw.data = payloads[index]; const DecodedSignalValue saved = DbcDecoder().Decode(database, raw).signals[1]; database = BuildText(secondDbc); QCOMPARE(saved.status, statuses[index]); QVERIFY(!saved.valid && !saved.GetValueDescription()); QCOMPARE(saved.definition->multiplexing.role, MultiplexRole::Branch); QVERIFY(saved.key == SignalKey(FrameKey(17U, false), "State")); } } int RunLifetimeTests(int argc, char **argv) { DbcLifetimeTest test; return QTest::qExec(&test, argc, argv); } #include "dbc_lifetime_test.moc"