DBC Framework
DBC parsing and CAN signal decoding framework
Loading...
Searching...
No Matches
dbc_lifetime_test.cpp
1// SPDX-License-Identifier: MIT
2// Copyright (c) 2026 Dim Himro
3
5#include "dbc_decoder.h"
6#include "dbc_parser.h"
7#include <QTemporaryFile>
8#include <QtTest>
9#include <stdexcept>
10#include <type_traits>
11#include <unordered_map>
12#include <utility>
13
14static_assert(std::is_same_v<decltype(std::declval<const DecodeDatabase &>().GetFrames()),
15 const std::vector<DecodeFrame> &>);
16static_assert(std::is_same_v<decltype(DecodedFrameValue::definition), std::shared_ptr<const DecodeFrame>>);
17static_assert(std::is_same_v<decltype(DecodedSignalValue::definition), std::shared_ptr<const DecodeSignal>>);
18
19namespace {
20 DecodeDatabase BuildText(const std::string &text) {
21 QTemporaryFile file;
22 if (!file.open() ||
23 file.write(text.data(), static_cast<qint64>(text.size())) != static_cast<qint64>(text.size()) ||
24 !file.flush())
25 throw std::runtime_error("Cannot write reload fixture");
26 return DbcDecodeBuilder().Build(DbcParser().ParseFile(file.fileName().toStdString()));
27 }
28
29 const std::string firstDbc =
30 "BO_ 17 OldFrame: 2 ECU\n"
31 "SG_ State : 0|8@1+ (2,1) [1|511] \"old\" ECU\n"
32 "SG_ Removed : 8|8@1+ (1,0) [0|255] \"\" ECU\n"
33 "VAL_ 17 State 1 \"Old label\";\n"
34 "BO_ 18 Deleted: 1 ECU\n"
35 "SG_ State : 0|8@1+ (1,0) [0|255] \"\" ECU\n";
36 const std::string secondDbc =
37 "BO_ 2147483665 Extended: 1 ECU\n"
38 "SG_ State : 0|8@1+ (1,0) [0|255] \"\" ECU\n"
39 "BO_ 17 NewFrame: 2 ECU\n"
40 "SG_ Renamed : 8|8@1+ (1,0) [0|255] \"\" ECU\n"
41 "SG_ State : 0|8@1+ (4,0) [0|1020] \"new\" ECU\n"
42 "VAL_ 17 State 1 \"New label\";\n";
43}// namespace
44
45class DbcLifetimeTest : public QObject {
46 Q_OBJECT
47private Q_SLOTS:
48 void reload();
49 void independentSignalLifetime();
50 void lookupLifetime();
51 void keys();
52 void duplicateKeys();
53 void failedReload();
54 void invalidResults();
55 void retainedMultiplexStatuses();
56};
57
58void DbcLifetimeTest::reload() {
59 DecodeDatabase database = BuildText(firstDbc);
60 RawCanFrame raw;
61 raw.canId = 17U;
62 raw.data = {1U, 42U};
63 DecodedFrameValue oldResult = DbcDecoder().Decode(database, raw);
64 const DecodedFrameValue copied = oldResult;
65 DecodedFrameValue moved = std::move(oldResult);
66 std::weak_ptr<const DecodeFrame> oldDefinition = moved.definition;
67 database = BuildText(secondDbc);
68 const DecodedFrameValue current = DbcDecoder().Decode(database, raw);
69 QVERIFY(current.valid && moved.valid && copied.valid);
70 QVERIFY(current.definition != moved.definition);
71 QVERIFY(current.key == moved.key);
72 QVERIFY(current.signals[1].key == moved.signals[0].key);
73 QCOMPARE(moved.definition->name, std::string("OldFrame"));
74 QCOMPARE(copied.definition, moved.definition);
75 QCOMPARE(moved.signals[0].physicalValue, 3.0);
76 QCOMPARE(current.signals[1].physicalValue, 4.0);
77 QCOMPARE(moved.signals[0].definition->unit, std::string("old"));
78 QCOMPARE(current.signals[1].definition->unit, std::string("new"));
79 QCOMPARE(*moved.signals[0].GetValueDescription(), std::string("Old label"));
80 QCOMPARE(*current.signals[1].GetValueDescription(), std::string("New label"));
81 QCOMPARE(moved.signals[1].definition->name, std::string("Removed"));
82 QVERIFY(!database.FindSignal(moved.signals[1].key));
83 database = DecodeDatabase();
84 QVERIFY(!oldDefinition.expired());
85 QCOMPARE(current.definition->name, std::string("NewFrame"));
86 QCOMPARE(*current.signals[1].GetValueDescription(), std::string("New label"));
87}
88
89void DbcLifetimeTest::independentSignalLifetime() {
91 std::weak_ptr<const DecodeSignal> definition;
92 const std::string *label = nullptr;
93 {
94 DecodeDatabase database = BuildText(firstDbc);
95 RawCanFrame raw;
96 raw.canId = 17U;
97 raw.data = {1U, 0U};
98 DecodedFrameValue result = DbcDecoder().Decode(database, raw);
99 saved = result.signals[0];
100 definition = saved.definition;
101 label = saved.GetValueDescription();
102 result = DecodedFrameValue();
103 database = BuildText(secondDbc);
104 }
105 QVERIFY(!definition.expired());
106 QVERIFY(label != nullptr);
107 QCOMPARE(*label, std::string("Old label"));
108 QCOMPARE(saved.definition->factor, 2.0);
109 DecodedSignalValue copied = saved;
110 DecodedSignalValue moved = std::move(saved);
111 QCOMPARE(moved.definition, copied.definition);
112 saved = DecodedSignalValue();
113 moved = DecodedSignalValue();
114 QVERIFY(!definition.expired());
115 copied = DecodedSignalValue();
116 QVERIFY(definition.expired());
117}
118
119void DbcLifetimeTest::lookupLifetime() {
120 std::shared_ptr<const DecodeFrame> frame;
121 std::shared_ptr<const DecodeSignal> signal;
122 std::weak_ptr<const DecodeFrame> released;
123 {
124 DecodeDatabase database = BuildText(firstDbc);
125 DecodeDatabase copy = database;
126 frame = DbcDecoder().FindFrame(database, 17U, false);
127 signal = copy.FindSignal(SignalKey(FrameKey(17U, false), "State"));
128 QVERIFY(frame && signal);
129 QCOMPARE(frame.get(), &copy.GetFrames()[0]);
130 QCOMPARE(signal.get(), &frame->signals[0]);
131 released = frame;
132 database = BuildText(secondDbc);
133 DecodeDatabase moved = std::move(copy);
134 QVERIFY(copy.GetFrames().empty());
135 QVERIFY(!copy.FindFrame(FrameKey(17U, false)));
136 QCOMPARE(moved.FindFrame(FrameKey(17U, false)), frame);
137 }
138 QCOMPARE(frame->name, std::string("OldFrame"));
139 frame.reset();
140 QVERIFY(!released.expired());
141 QCOMPARE(signal->name, std::string("State"));
142 signal.reset();
143 QVERIFY(released.expired());
144}
145
146void DbcLifetimeTest::keys() {
147 const FrameKey frame(17U, false);
148 const SignalKey state(frame, "State");
149 const DecodeDatabase first = BuildText(firstDbc);
150 const DecodeDatabase second = BuildText(secondDbc);
151 QVERIFY(first.FindFrame(frame)->GetKey() == second.FindFrame(frame)->GetKey());
152 QVERIFY(first.FindSignal(state) && second.FindSignal(state));
153 QVERIFY(!second.FindFrame(FrameKey(18U, false)));
154 QVERIFY(!second.FindSignal(SignalKey(frame, "Removed")));
155 QVERIFY(second.FindSignal(SignalKey(frame, "Renamed")));
156 QVERIFY(second.FindSignal(SignalKey(FrameKey(17U, true), "State")));
157 QVERIFY(!second.FindSignal(SignalKey(FrameKey(999U, false), "State")));
158 QVERIFY(first.FindSignal(SignalKey(FrameKey(18U, false), "State")));
159 std::unordered_map<FrameKey, int, FrameKeyHasher> frames;
160 frames.emplace(frame, 1);
161 frames.emplace(FrameKey(17U, true), 2);
162 QCOMPARE(frames.size(), std::size_t(2U));
163 std::unordered_map<SignalKey, int, SignalKeyHasher> signals;
164 signals.emplace(state, 1);
165 signals.emplace(SignalKey(FrameKey(17U, true), "State"), 2);
166 signals.emplace(SignalKey(frame, "Renamed"), 3);
167 signals.emplace(SignalKey(FrameKey(18U, false), "State"), 4);
168 QCOMPARE(signals.at(SignalKey(FrameKey(17U, false), "State")), 1);
169 QCOMPARE(signals.size(), std::size_t(4U));
170}
171
172void DbcLifetimeTest::duplicateKeys() {
173 DecodeFrame frame;
174 frame.canId = 17U;
175 QVERIFY_THROWS_EXCEPTION(std::invalid_argument, (void) DecodeDatabase({frame, frame}));
176 DecodeSignal signal;
177 signal.name = "State";
178 frame.signals = {signal, signal};
179 QVERIFY_THROWS_EXCEPTION(std::invalid_argument, (void) DecodeDatabase({frame}));
180 DbcDatabase parsed;
181 FrameInfo source;
182 source.canId = 17U;
183 parsed.frames = {source, source};
184 QVERIFY_THROWS_EXCEPTION(std::invalid_argument, (void) DbcDecodeBuilder().Build(parsed));
185}
186
187void DbcLifetimeTest::failedReload() {
188 DecodeDatabase database = BuildText(firstDbc);
189 const std::shared_ptr<const DecodeFrame> original = database.FindFrame(FrameKey(17U, false));
190 QVERIFY_THROWS_EXCEPTION(std::runtime_error, database = BuildText("BO_ invalid\n"));
191 QCOMPARE(database.FindFrame(FrameKey(17U, false)), original);
192 database = BuildText("");
193 QVERIFY(database.GetFrames().empty());
194 QVERIFY(!database.FindFrame(FrameKey(17U, false)));
195 QCOMPARE(original->name, std::string("OldFrame"));
196}
197
198void DbcLifetimeTest::invalidResults() {
199 RawCanFrame raw;
200 raw.canId = 17U;
201 DecodedFrameValue result = DbcDecoder().Decode(BuildText(firstDbc), raw);
202 QVERIFY(result.valid);
203 QVERIFY(!result.signals[0].valid);
204 QCOMPARE(result.signals[0].status, SignalDecodeStatus::InsufficientPayload);
205 QVERIFY(!result.signals[0].GetValueDescription());
206 QCOMPARE(result.signals[0].definition->name, std::string("State"));
207 result = DbcDecoder().Decode(DecodeDatabase(), raw);
208 QVERIFY(!result.valid && !result.definition && result.signals.empty());
209 QVERIFY(result.key == FrameKey(17U, false));
210 QVERIFY(!DecodedSignalValue().GetValueDescription());
211 QVERIFY(!DecodeDatabase().FindSignal(SignalKey(FrameKey(17U, false), "State")));
212}
213
214void DbcLifetimeTest::retainedMultiplexStatuses() {
215 const std::string text =
216 "BO_ 17 Frame: 2 ECU\n"
217 "SG_ Mode M : 0|1@1+ (1,0) [0|1] \"\" ECU\n"
218 "SG_ State m1 : 8|8@1+ (1,0) [0|255] \"\" ECU\n";
219 const std::vector<std::vector<std::uint8_t>> payloads = {{0U}, {}, {1U}};
220 const SignalDecodeStatus statuses[] = {SignalDecodeStatus::Inactive,
221 SignalDecodeStatus::SelectorUnavailable,
222 SignalDecodeStatus::InsufficientPayload};
223 for (std::size_t index = 0U; index < payloads.size(); ++index) {
224 DecodeDatabase database = BuildText(text);
225 RawCanFrame raw;
226 raw.canId = 17U;
227 raw.data = payloads[index];
228 const DecodedSignalValue saved = DbcDecoder().Decode(database, raw).signals[1];
229 database = BuildText(secondDbc);
230 QCOMPARE(saved.status, statuses[index]);
231 QVERIFY(!saved.valid && !saved.GetValueDescription());
232 QCOMPARE(saved.definition->multiplexing.role, MultiplexRole::Branch);
233 QVERIFY(saved.key == SignalKey(FrameKey(17U, false), "State"));
234 }
235}
236
237int RunLifetimeTests(int argc, char **argv) {
238 DbcLifetimeTest test;
239 return QTest::qExec(&test, argc, argv);
240}
241
242#include "dbc_lifetime_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.
Definition dbc_decoder.h:74
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 frame definition by CAN ID.
Minimal DBC parser.
Definition dbc_parser.h:33
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
Created: 2026-03-13 Author: Deeaitch (Dim. Himro)
Created: 2026-03-13 Author: Deeaitch (Dim. Himro)
Created: 2026-03-13 Author: Deeaitch (Dim. Himro)
Parsed DBC content stored in a simple internal form.
std::vector< FrameInfo > frames
Runtime-ready frame definition.
std::uint32_t canId
Runtime-ready signal definition.
std::string name
Fully decoded frame.
Definition dbc_decoder.h:62
std::shared_ptr< const DecodeFrame > definition
Definition dbc_decoder.h:63
One decoded signal value.
Definition dbc_decoder.h:43
std::shared_ptr< const DecodeSignal > definition
Definition dbc_decoder.h:44
const std::string * GetValueDescription() const
Definition dbc_decoder.h:52
Describes one CAN frame from a DBC file.
Definition frame_info.h:24
std::uint32_t canId
Definition frame_info.h:26
Stable frame identifier: normalized CAN ID and frame type.
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