DBC Framework
DBC parsing and CAN signal decoding framework
Loading...
Searching...
No Matches
dbc_parser_test.cpp
1#include "dbc_parser.h"
2#include "dbc_statement_handler.h"
3
4#include <chrono>
5#include <filesystem>
6#include <fstream>
7#include <iostream>
8#include <stdexcept>
9
10struct Fixture {
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");
13 ~Fixture() {
14 std::error_code error;
15 std::filesystem::remove(path, error);
16 }
17 void Write(const std::string &text) const {
18 std::ofstream output(path);
19 output << text;
20 if (!output)
21 throw std::runtime_error("Cannot write test fixture.");
22 }
23};
24
25class CountingHandler : public DbcStatementHandler {
26public:
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 {
31 ++calls;
32 context.database.frames.emplace_back();
33 }
34};
35
36int main() {
37 Fixture fixture;
38 int failures = 0;
39 std::size_t count = 0U;
40 const auto check = [&](bool passed, const std::string &name) {
41 ++count;
42 if (!passed) {
43 ++failures;
44 std::cerr << "FAIL: " << name << '\n';
45 }
46 };
47 struct ValidCase {
48 const char *line;
49 std::uint32_t id;
50 bool extended;
51 unsigned length;
52 };
53 const ValidCase valid[] = {
54 {"BO_ 0 Frame: 0 ECU", 0U, false, 0U},
55 {"BO_ 2047 Frame: 8 ECU", 2047U, false, 8U},
56 {"BO_ 2048 Frame: 64 ECU", 2048U, true, 64U},
57 {"BO_ 536870911 Frame: 255 ECU", 0x1FFFFFFFU, true, 255U},
58 {"BO_ 2147483649 Frame: 8 ECU", 1U, true, 8U},
59 {"BO_ 2684354559 Frame: 8 ECU", 0x1FFFFFFFU, true, 8U},
60 {" BO_\t256\tFrame :\t8\tECU \r", 256U, false, 8U}};
61 for (const auto &test: valid) {
62 fixture.Write(std::string(test.line) + '\n');
63 try {
64 const auto database = DbcParser().ParseFile(fixture.path.string());
65 check(database.frames.size() == 1U && database.frames[0].name == "Frame" &&
66 database.frames[0].canId == test.id &&
67 database.frames[0].isExtended == test.extended &&
68 database.frames[0].dlc == test.length &&
69 database.frames[0].transmitter == "ECU",
70 test.line);
71 } catch (const std::exception &error) { check(false, error.what()); }
72 }
73 const char *invalid[] = {
74 "BO_ invalid", "BO_", "BO_ 1", "BO_ 1 : 8 ECU", "BO_ 1 Frame 8 ECU",
75 "BO_ 1 Frame:", "BO_ 1 Frame: 8", "BO_ 1 Frame: 8 ECU extra",
76 "BO_ 1 Two Names: 8 ECU", "BO_ -1 Frame: 8 ECU", "BO_ +1 Frame: 8 ECU",
77 "BO_ 1x Frame: 8 ECU", "BO_ 0x123 Frame: 8 ECU", "BO_ 1.5 Frame: 8 ECU",
78 "BO_ 4294967296 Frame: 8 ECU", "BO_ 999999999999999999999 Frame: 8 ECU",
79 "BO_ 536870912 Frame: 8 ECU", "BO_ 1073741824 Frame: 8 ECU",
80 "BO_ 2684354560 Frame: 8 ECU", "BO_ 4294967295 Frame: 8 ECU",
81 "BO_ 1 Frame: -1 ECU", "BO_ 1 Frame: +8 ECU", "BO_ 1 Frame: 8x ECU",
82 "BO_ 1 Frame: 8.5 ECU", "BO_ 1 Frame: 256 ECU",
83 "BO_ 1 Frame: 4294967296 ECU", "BO_\tinvalid"};
84 for (const auto *line: invalid) {
85 fixture.Write(std::string("VERSION \"test\"\n\n") + line + '\n');
86 try {
87 DbcParser().ParseFile(fixture.path.string());
88 check(false, line);
89 } catch (const std::runtime_error &error) {
90 const std::string prefix = fixture.path.string() + ":3: ";
91 check(std::string(error.what()).find(prefix) == 0U &&
92 std::string(error.what()).size() > prefix.size(),
93 line);
94 }
95 }
96 fixture.Write(
97 "BO_TX_BU_ 256 : ECU;\nBO_ 256 Frame: 8 ECU\n"
98 " SG_ Speed : 0|16@1+ (0.5,1) [0|100] \"rpm\" ECU\n"
99 "CM_ BO_ 256 \"Frame comment\";\nCM_ SG_ 256 Speed \"Signal comment\";\n"
100 "BO_ 257 Second: 8 ECU\n");
101 const auto database = DbcParser().ParseFile(fixture.path.string());
102 check(database.frames.size() == 2U && database.frames[0].signals.size() == 1U &&
103 database.frames[0].comment == "Frame comment" &&
104 database.frames[0].signals[0].comment == "Signal comment",
105 "Integration");
106 fixture.Write("\nSG_ Speed : invalid\n");
107 try {
108 DbcParser().ParseFile(fixture.path.string());
109 check(false, "Signal error location");
110 } catch (const std::runtime_error &error) {
111 check(std::string(error.what()).find(fixture.path.string() + ":2: ") == 0U,
112 "Signal error location");
113 }
114 const DbcParser signalParser;
115 const auto signalLine = [](const std::string &start, const std::string &length,
116 const std::string &order, const std::string &factor,
117 const std::string &offset, const std::string &minimum,
118 const std::string &maximum) {
119 return "SG_ Value : " + start + "|" + length + "@" + order +
120 " (" + factor + "," + offset + ") [" + minimum + "|" + maximum + "] \"\" ECU";
121 };
122 const auto rejectSignal = [&](const std::string &line, unsigned payload = 8U) {
123 fixture.Write("BO_ 1 Frame: " + std::to_string(payload) + " ECU\n\n" + line + "\n");
124 try {
125 signalParser.ParseFile(fixture.path.string());
126 check(false, "Reject " + line);
127 } catch (const std::runtime_error &error) {
128 const std::string prefix = fixture.path.string() + ":3: ";
129 check(std::string(error.what()).find(prefix) == 0U &&
130 std::string(error.what()).size() > prefix.size(),
131 "Reject " + line);
132 }
133 };
134 const std::string ordinary = signalLine("0", "8", "1+", "1", "0", "0", "255");
135 for (const std::string value: {"-1", "+1", "0junk", "0x10", "1.5",
136 "4294967296", "999999999999999999999"}) {
137 rejectSignal(signalLine(value, "8", "1+", "1", "0", "0", "255"));
138 rejectSignal(signalLine("0", value, "1+", "1", "0", "0", "255"));
139 }
140 for (const std::string length: {"0", "65"})
141 rejectSignal(signalLine("0", length, "1+", "1", "0", "0", "255"));
142 for (const std::string order: {"2+", "9-", "1", "1x", "1++", "01+"})
143 rejectSignal(signalLine("0", "8", order, "1", "0", "0", "255"));
144 for (const std::string value: {"1junk", "nan", "NaN", "inf", "-inf", "1e999", "1e-999", "0x1p2", "", "1e", "1.2.3"}) {
145 rejectSignal(signalLine("0", "8", "1+", value, "0", "0", "255"));
146 rejectSignal(signalLine("0", "8", "1+", "1", value, "0", "255"));
147 rejectSignal(signalLine("0", "8", "1+", "1", "0", value, "255"));
148 rejectSignal(signalLine("0", "8", "1+", "1", "0", "0", value));
149 }
150 rejectSignal(signalLine("0", "8", "1+", "1", "0", "2", "1"));
151 for (const std::string name: {"", "Two Names", "Value M", "Value m1", "Value m1M"}) {
152 std::string line = ordinary;
153 line.replace(4U, 5U, name);
154 rejectSignal(line);
155 }
156 for (const std::string broken: {
157 "SG_", "SG_ Value", "SG_ Value : 0|8@1+ (1,0) [0|255] ECU",
158 "SG_ Value : 0|8@1+ (1,0) [0|255] \"\"",
159 "SG_ Value : 0|8@1+ (1,0) [0|255] \"\" ECU,",
160 "SG_ Value : 0|8@1+ (1,0) [0|255] \"\" ECU extra"})
161 rejectSignal(broken);
162 rejectSignal(signalLine("64", "1", "1+", "1", "0", "0", "1"));
163 rejectSignal(signalLine("63", "2", "1+", "1", "0", "0", "3"));
164 rejectSignal(signalLine("56", "2", "0+", "1", "0", "0", "3"));
165 rejectSignal(signalLine("4294967295", "64", "1+", "1", "0", "0", "1"));
166 rejectSignal(ordinary, 0U);
167 for (const char delimiter: {'|', '@', '(', ')', '[', ']', '"'}) {
168 std::string line = ordinary;
169 line.erase(line.find(delimiter), 1U);
170 rejectSignal(line);
171 }
172 fixture.Write("BO_ 1 Frame: 8 ECU\n" +
173 signalLine("0", "8", "1+", "0", "0", "0", "0") + "\n" +
174 signalLine("8", "8", "1-", "-1", "0", "-255", "0") + "\n");
175 const auto factors = signalParser.ParseFile(fixture.path.string());
176 check(factors.frames[0].signals.size() == 2U &&
177 factors.frames[0].signals[0].factor == 0.0 &&
178 factors.frames[0].signals[1].factor == -1.0,
179 "Zero and negative factors and equal range boundaries");
180
181 struct SignalCase {
182 const char *start;
183 const char *length;
184 const char *order;
185 unsigned payload;
186 };
187 const SignalCase validSignals[] = {
188 {"0", "1", "1+", 1U},
189 {"7", "1", "0-", 1U},
190 {"0", "64", "1+", 8U},
191 {"7", "64", "0-", 8U},
192 {"63", "1", "1+", 8U},
193 {"56", "1", "0+", 8U},
194 {"3", "12", "0+", 2U},
195 {"2039", "1", "1+", 255U}};
196 for (const auto &test: validSignals) {
197 fixture.Write("BO_ 1 Frame: " + std::to_string(test.payload) + " ECU\n" +
198 signalLine(test.start, test.length, test.order, ".5e+1", "-2.5E-1", "-1.", "+2e2") + "\n");
199 try {
200 const auto parsed = signalParser.ParseFile(fixture.path.string());
201 const auto &signal = parsed.frames.at(0).signals.at(0);
202 check(signal.startBit == std::stoul(test.start) &&
203 signal.length == std::stoul(test.length) &&
204 signal.isLittleEndian == (test.order[0] == '1') &&
205 signal.isSigned == (test.order[1] == '-') &&
206 signal.factor == 5.0 && signal.offset == -0.25 &&
207 signal.minimum == -1.0 && signal.maximum == 200.0,
208 "Valid signal boundary and numeric metadata");
209 } catch (const std::exception &error) { check(false, error.what()); }
210 }
211 fixture.Write("BO_ 1 Frame: 8 ECU\n"
212 "SG_\tValue\t:\t0 | 8 @1+ ( 1 , 0 ) [ 0 | 255 ] \"rpm\" ECU1,\tECU2\n");
213 try {
214 const auto parsed = signalParser.ParseFile(fixture.path.string());
215 const auto &signal = parsed.frames.at(0).signals.at(0);
216 check(signal.name == "Value" && signal.unit == "rpm" &&
217 signal.receivers == std::vector<std::string>({"ECU1", "ECU2"}),
218 "Signal tabs, spaces, and receiver list");
219 } catch (const std::exception &error) { check(false, error.what()); }
220 DbcDatabase dispatchDatabase;
221 DbcParseContext context{dispatchDatabase, nullptr};
222 DbcStatementDispatcher dispatcher;
223 dispatcher.RegisterType<CountingHandler>("TEST_");
224 check(dispatcher.Dispatch("TEST_ first", context) &&
225 dispatcher.Dispatch("TEST_ second", context) &&
226 CountingHandler::instances == 1 && CountingHandler::calls == 2 &&
227 dispatchDatabase.frames.size() == 2U,
228 "Factory dispatch and handler reuse");
229 check(!dispatcher.Dispatch("TEST_OTHER data", context) &&
230 !dispatcher.Dispatch("BO_TX_BU_ 1 : ECU;", context) &&
231 !dispatcher.Dispatch("VAL_ 1 Speed 0 \"Stopped\";", context) &&
232 CountingHandler::calls == 2,
233 "Exact and unknown keyword dispatch");
234 const DbcParser parser;
235 fixture.Write("BO_ 1 First: 8 ECU\n");
236 check(parser.ParseFile(fixture.path.string()).frames.size() == 1U, "First parse");
237 fixture.Write("SG_ Speed : invalid\n");
238 try {
239 parser.ParseFile(fixture.path.string());
240 check(false, "Independent parse context");
241 } catch (const std::runtime_error &error) {
242 check(std::string(error.what()).find("Signal found before any frame") !=
243 std::string::npos,
244 "Independent parse context");
245 }
246 fixture.Write("BO_ 2 Second: 8 ECU\n");
247 const auto recovered = parser.ParseFile(fixture.path.string());
248 check(recovered.frames.size() == 1U && recovered.frames[0].canId == 2U,
249 "Fresh parse after error");
250 if (failures != 0) return 1;
251 std::cout << "All " << count << " parser cases passed.\n";
252 return 0;
253}
Minimal DBC parser.
Definition dbc_parser.h:33
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