2#include "dbc_statement_handler.h"
11 std::filesystem::path path = std::filesystem::temp_directory_path() /
12 (
"dbc_parser_test_" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()) +
".dbc");
14 std::error_code error;
15 std::filesystem::remove(path, error);
17 void Write(
const std::string &text)
const {
18 std::ofstream output(path);
21 throw std::runtime_error(
"Cannot write test fixture.");
27 inline static int instances = 0;
28 inline static int calls = 0;
29 CountingHandler() { ++instances; }
30 void Handle(
const std::string &,
DbcParseContext &context)
const override {
32 context.database.
frames.emplace_back();
36std::string SignalLine(
const std::string &start,
const std::string &length,
37 const std::string &order,
const std::string &factor,
38 const std::string &offset,
const std::string &minimum,
39 const std::string &maximum) {
40 return "SG_ Value : " + start +
"|" + length +
"@" + order +
41 " (" + factor +
"," + offset +
") [" + minimum +
"|" + maximum +
"] \"\" ECU";
46 std::size_t count = 0U;
47 void Check(
bool passed,
const std::string &name) {
51 std::cerr <<
"FAIL: " << name <<
'\n';
54 void RejectSignal(
const Fixture &fixture,
const std::string &line,
unsigned payload = 8U) {
55 fixture.Write(
"BO_ 1 Frame: " + std::to_string(payload) +
" ECU\n\n" + line +
"\n");
58 Check(
false,
"Reject " + line);
59 }
catch (
const std::runtime_error &error) {
60 const std::string prefix = fixture.path.string() +
":3: ";
61 Check(std::string(error.what()).find(prefix) == 0U &&
62 std::string(error.what()).size() > prefix.size(),
77 const ValidCase valid[] = {
78 {
"BO_ 0 Frame: 0 ECU", 0U,
false, 0U},
79 {
"BO_ 2047 Frame: 8 ECU", 2047U,
false, 8U},
80 {
"BO_ 2048 Frame: 64 ECU", 2048U,
true, 64U},
81 {
"BO_ 536870911 Frame: 255 ECU", 0x1FFFFFFFU,
true, 255U},
82 {
"BO_ 2147483649 Frame: 8 ECU", 1U,
true, 8U},
83 {
"BO_ 2684354559 Frame: 8 ECU", 0x1FFFFFFFU,
true, 8U},
84 {
" BO_\t256\tFrame :\t8\tECU \r", 256U,
false, 8U}};
85 for (
const auto &test: valid) {
86 fixture.Write(std::string(test.line) +
'\n');
89 tests.Check(database.frames.size() == 1U && database.frames[0].name ==
"Frame" &&
90 database.frames[0].canId == test.id &&
91 database.frames[0].isExtended == test.extended &&
92 database.frames[0].dlc == test.length &&
93 database.frames[0].transmitter ==
"ECU",
95 }
catch (
const std::exception &error) { tests.Check(
false, error.what()); }
97 const char *invalid[] = {
98 "BO_ invalid",
"BO_",
"BO_ 1",
"BO_ 1 : 8 ECU",
"BO_ 1 Frame 8 ECU",
99 "BO_ 1 Frame:",
"BO_ 1 Frame: 8",
"BO_ 1 Frame: 8 ECU extra",
100 "BO_ 1 Two Names: 8 ECU",
"BO_ -1 Frame: 8 ECU",
"BO_ +1 Frame: 8 ECU",
101 "BO_ 1x Frame: 8 ECU",
"BO_ 0x123 Frame: 8 ECU",
"BO_ 1.5 Frame: 8 ECU",
102 "BO_ 4294967296 Frame: 8 ECU",
"BO_ 999999999999999999999 Frame: 8 ECU",
103 "BO_ 536870912 Frame: 8 ECU",
"BO_ 1073741824 Frame: 8 ECU",
104 "BO_ 2684354560 Frame: 8 ECU",
"BO_ 4294967295 Frame: 8 ECU",
105 "BO_ 1 Frame: -1 ECU",
"BO_ 1 Frame: +8 ECU",
"BO_ 1 Frame: 8x ECU",
106 "BO_ 1 Frame: 8.5 ECU",
"BO_ 1 Frame: 256 ECU",
107 "BO_ 1 Frame: 4294967296 ECU",
"BO_\tinvalid"};
108 for (
const auto *line: invalid) {
109 fixture.Write(std::string(
"VERSION \"test\"\n\n") + line +
'\n');
112 tests.Check(
false, line);
113 }
catch (
const std::runtime_error &error) {
114 const std::string prefix = fixture.path.string() +
":3: ";
115 tests.Check(std::string(error.what()).find(prefix) == 0U &&
116 std::string(error.what()).size() > prefix.size(),
121 "BO_TX_BU_ 256 : ECU;\nBO_ 256 Frame: 8 ECU\n"
122 " SG_ Speed : 0|16@1+ (0.5,1) [0|100] \"rpm\" ECU\n"
123 "CM_ BO_ 256 \"Frame comment\";\nCM_ SG_ 256 Speed \"Signal comment\";\n"
124 "BO_ 257 Second: 8 ECU\n");
126 tests.Check(database.frames.size() == 2U && database.frames[0].signals.size() == 1U &&
127 database.frames[0].comment ==
"Frame comment" &&
128 database.frames[0].signals[0].comment ==
"Signal comment",
130 fixture.Write(
"\nSG_ Speed : invalid\n");
133 tests.Check(
false,
"Signal error location");
134 }
catch (
const std::runtime_error &error) {
135 tests.Check(std::string(error.what()).find(fixture.path.string() +
":2: ") == 0U,
136 "Signal error location");
139 const std::string ordinary = SignalLine(
"0",
"8",
"1+",
"1",
"0",
"0",
"255");
140 for (
const std::string value: {
"-1",
"+1",
"0junk",
"0x10",
"1.5",
141 "4294967296",
"999999999999999999999"}) {
142 tests.RejectSignal(fixture, SignalLine(value,
"8",
"1+",
"1",
"0",
"0",
"255"));
143 tests.RejectSignal(fixture, SignalLine(
"0", value,
"1+",
"1",
"0",
"0",
"255"));
145 for (
const std::string length: {
"0",
"65"})
146 tests.RejectSignal(fixture, SignalLine(
"0", length,
"1+",
"1",
"0",
"0",
"255"));
147 for (
const std::string order: {
"2+",
"9-",
"1",
"1x",
"1++",
"01+"})
148 tests.RejectSignal(fixture, SignalLine(
"0",
"8", order,
"1",
"0",
"0",
"255"));
149 for (
const std::string value: {
"1junk",
"nan",
"NaN",
"inf",
"-inf",
"1e999",
"1e-999",
"0x1p2",
"",
"1e",
"1.2.3"}) {
150 tests.RejectSignal(fixture, SignalLine(
"0",
"8",
"1+", value,
"0",
"0",
"255"));
151 tests.RejectSignal(fixture, SignalLine(
"0",
"8",
"1+",
"1", value,
"0",
"255"));
152 tests.RejectSignal(fixture, SignalLine(
"0",
"8",
"1+",
"1",
"0", value,
"255"));
153 tests.RejectSignal(fixture, SignalLine(
"0",
"8",
"1+",
"1",
"0",
"0", value));
155 tests.RejectSignal(fixture, SignalLine(
"0",
"8",
"1+",
"1",
"0",
"2",
"1"));
156 for (
const std::string name: {
"",
"Two Names",
"Value M",
"Value m1",
"Value m1M"}) {
157 std::string line = ordinary;
158 line.replace(4U, 5U, name);
159 tests.RejectSignal(fixture, line);
161 for (
const std::string broken: {
162 "SG_",
"SG_ Value",
"SG_ Value : 0|8@1+ (1,0) [0|255] ECU",
163 "SG_ Value : 0|8@1+ (1,0) [0|255] \"\"",
164 "SG_ Value : 0|8@1+ (1,0) [0|255] \"\" ECU,",
165 "SG_ Value : 0|8@1+ (1,0) [0|255] \"\" ECU extra"})
166 tests.RejectSignal(fixture, broken);
167 tests.RejectSignal(fixture, SignalLine(
"64",
"1",
"1+",
"1",
"0",
"0",
"1"));
168 tests.RejectSignal(fixture, SignalLine(
"63",
"2",
"1+",
"1",
"0",
"0",
"3"));
169 tests.RejectSignal(fixture, SignalLine(
"56",
"2",
"0+",
"1",
"0",
"0",
"3"));
170 tests.RejectSignal(fixture, SignalLine(
"4294967295",
"64",
"1+",
"1",
"0",
"0",
"1"));
171 tests.RejectSignal(fixture, ordinary, 0U);
172 for (
const char delimiter: {
'|',
'@',
'(',
')',
'[',
']',
'"'}) {
173 std::string line = ordinary;
174 line.erase(line.find(delimiter), 1U);
175 tests.RejectSignal(fixture, line);
177 fixture.Write(
"BO_ 1 Frame: 8 ECU\n" +
178 SignalLine(
"0",
"8",
"1+",
"0",
"0",
"0",
"0") +
"\n" +
179 SignalLine(
"8",
"8",
"1-",
"-1",
"0",
"-255",
"0") +
"\n");
180 const auto factors = signalParser.
ParseFile(fixture.path.string());
181 tests.Check(factors.frames[0].signals.size() == 2U &&
182 factors.frames[0].signals[0].factor == 0.0 &&
183 factors.frames[0].signals[1].factor == -1.0,
184 "Zero and negative factors and equal range boundaries");
192 const SignalCase validSignals[] = {
193 {
"0",
"1",
"1+", 1U},
194 {
"7",
"1",
"0-", 1U},
195 {
"0",
"64",
"1+", 8U},
196 {
"7",
"64",
"0-", 8U},
197 {
"63",
"1",
"1+", 8U},
198 {
"56",
"1",
"0+", 8U},
199 {
"3",
"12",
"0+", 2U},
200 {
"2039",
"1",
"1+", 255U}};
201 for (
const auto &test: validSignals) {
202 fixture.Write(
"BO_ 1 Frame: " + std::to_string(test.payload) +
" ECU\n" +
203 SignalLine(test.start, test.length, test.order,
".5e+1",
"-2.5E-1",
"-1.",
"+2e2") +
"\n");
205 const auto parsed = signalParser.
ParseFile(fixture.path.string());
206 const auto &signal = parsed.
frames.at(0).signals.at(0);
207 tests.Check(signal.startBit == std::stoul(test.start) &&
208 signal.length == std::stoul(test.length) &&
209 signal.isLittleEndian == (test.order[0] ==
'1') &&
210 signal.isSigned == (test.order[1] ==
'-') &&
211 signal.factor == 5.0 && signal.offset == -0.25 &&
212 signal.minimum == -1.0 && signal.maximum == 200.0,
213 "Valid signal boundary and numeric metadata");
214 }
catch (
const std::exception &error) { tests.Check(
false, error.what()); }
216 fixture.Write(
"BO_ 1 Frame: 8 ECU\n"
217 "SG_\tValue\t:\t0 | 8 @1+ ( 1 , 0 ) [ 0 | 255 ] \"rpm\" ECU1,\tECU2\n");
219 const auto parsed = signalParser.
ParseFile(fixture.path.string());
220 const auto &signal = parsed.
frames.at(0).signals.at(0);
221 tests.Check(signal.name ==
"Value" && signal.unit ==
"rpm" &&
222 signal.receivers == std::vector<std::string>({
"ECU1",
"ECU2"}),
223 "Signal tabs, spaces, and receiver list");
224 }
catch (
const std::exception &error) { tests.Check(
false, error.what()); }
228 dispatcher.RegisterType<CountingHandler>(
"TEST_");
229 tests.Check(dispatcher.
Dispatch(
"TEST_ first", context) &&
230 dispatcher.
Dispatch(
"TEST_ second", context) &&
231 CountingHandler::instances == 1 && CountingHandler::calls == 2 &&
232 dispatchDatabase.
frames.size() == 2U,
233 "Factory dispatch and handler reuse");
234 tests.Check(!dispatcher.
Dispatch(
"TEST_OTHER data", context) &&
235 !dispatcher.
Dispatch(
"BO_TX_BU_ 1 : ECU;", context) &&
236 !dispatcher.
Dispatch(
"VAL_ 1 Speed 0 \"Stopped\";", context) &&
237 CountingHandler::calls == 2,
238 "Exact and unknown keyword dispatch");
240 fixture.Write(
"BO_ 1 First: 8 ECU\n");
241 tests.Check(parser.
ParseFile(fixture.path.string()).
frames.size() == 1U,
"First parse");
242 fixture.Write(
"SG_ Speed : invalid\n");
245 tests.Check(
false,
"Independent parse context");
246 }
catch (
const std::runtime_error &error) {
247 tests.Check(std::string(error.what()).find(
"Signal found before any frame") !=
249 "Independent parse context");
251 fixture.Write(
"BO_ 2 Second: 8 ECU\n");
252 const auto recovered = parser.
ParseFile(fixture.path.string());
253 tests.Check(recovered.frames.size() == 1U && recovered.frames[0].canId == 2U,
254 "Fresh parse after error");
255 if (tests.failures != 0)
return 1;
256 std::cout <<
"All " << tests.count <<
" parser cases passed.\n";
DbcDatabase ParseFile(const std::string &filePath) const
Parse DBC file.
bool Dispatch(const std::string &line, DbcParseContext &context) const
Created: 2026-03-13 Author: Deeaitch (Dim. Himro)
Parsed DBC content stored in a simple internal form.
std::vector< FrameInfo > frames