This commit is contained in:
deeaitch committed 2026-10-20 13:21:43 -04:00
1 parent e555ebb571
commit 849ce31e2f
17 files changed
+675 -35

No files matched your search

+18 -1
View File
@@ -66,7 +66,7 @@ line numbers for skipped syntax and immediate/deferred errors; warnings precedin
an error are retained, and failed parses return an empty database. Common headers
and namespace symbol lists stay quiet. Tests also check file-open errors, fresh
parse sessions, and compatibility with the legacy throwing API.
The diagnostic audit also covers 15 unsupported-statement variants, all namespace
The diagnostic audit also covers 13 unsupported-statement variants, all namespace
termination forms, CRLF and missing final newlines, relative paths with spaces,
first-error stopping, deferred diagnostic ordering, duplicate definitions/values,
selector ranges, overlaps, and preservation of supported decoding and metadata.
@@ -220,3 +220,20 @@ Empty and headers-only fixtures produce an empty runtime database and a valid
empty tree root. Replacement fixtures change definitions, metadata and decoded
values, remove old lookup keys, and then clear both outputs through an empty
fixture. Saved decoded results and trees retain their original metadata.
## Attribute coverage
Parser slots `attributes`, `invalidAttributes`, and `attributeWarnings` cover
all five scopes and types, deferred definitions/defaults/targets, explicit
values overriding defaults, repeated assignments, escaped strings, typed
range/scope errors, duplicate definitions/defaults, unknown attributes and
missing targets. They check source diagnostics and the throwing API, distinct
standard/extended targets, frame cycle times and enum labels, unsupported
formats and ID conflicts, unchanged J1939 opt-in, and runtime/tree propagation.
Existing unsupported-statement checks now use relation forms, which remain
unsupported, instead of supported attribute forms.
Attribute changes were verified with `qmake6 ../dbc.pro`, `make -j4`, and
`make check` in `build/`: all six QtTest suites and the qmake tree target
passed. The standalone C++17 tree test also passed. The existing named
comma-decimal C locale test skipped because the locale is not installed.
+133 -4
View File
@@ -3,6 +3,7 @@
#include "dbc_parser.h"
#include "dbc_statement_handler.h"
#include "dbc_tree_builder.h"
#include <algorithm>
#include <clocale>
#include <limits>
#include <locale>
@@ -87,6 +88,9 @@ private:
}
}
private Q_SLOTS:
void attributes();
void invalidAttributes();
void attributeWarnings();
void diagnostics();
void diagnosticErrors();
void diagnosticUnsupportedStatements();
@@ -113,6 +117,133 @@ private Q_SLOTS:
void invalidValueDescriptions();
};
void DbcParserTest::attributes() {
Fixture fixture;
fixture.Write(
"BA_ \"GenMsgCycleTime\" BO_ 1 25;\n"
"BA_ \"VFrameFormat\" BO_ 2147483649 1;\n"
"BA_ \"Label\" SG_ 1 Value \"a\\\"b\\\\c\";\n"
"BA_ \"NodeLabel\" BU_ ECU \"node\";\n"
"BA_ \"Environment\" EV_ Env 2;\n"
"BA_DEF_DEF_ \"GenMsgCycleTime\" 100;\n"
"BA_DEF_DEF_ \"VFrameFormat\" \"StandardCAN\";\n"
"BA_DEF_DEF_ \"Label\" \"default\";\n"
"BA_DEF_DEF_ \"BusLabel\" \"CAN\";\n"
"BA_DEF_DEF_ \"Ratio\" 1.5e-1;\n"
"BA_DEF_ BO_ \"GenMsgCycleTime\" INT 0 10000;\n"
"BA_DEF_ BO_ \"VFrameFormat\" ENUM \"StandardCAN\",\"ExtendedCAN\",\"StandardCAN_FD\";\n"
"BA_DEF_ SG_ \"Label\" STRING;\n"
"BA_DEF_ \"BusLabel\" STRING;\n"
"BA_DEF_ BO_ \"Ratio\" FLOAT -1 1;\n"
"BA_DEF_ BU_ \"NodeLabel\" STRING;\n"
"BA_DEF_ EV_ \"Environment\" HEX 0 255;\n"
"BO_ 1 Standard: 1 ECU\n"
"SG_ Value : 0|8@1+ (1,0) [0|255] \"\" ECU\n"
"BO_ 2147483649 Extended: 1 ECU\n"
"SG_ Value : 0|8@1+ (1,0) [0|255] \"\" ECU\n");
const DbcParseResult result = DbcParser().ParseFileWithDiagnostics(fixture.path.string());
QVERIFY(!result.HasErrors());
QVERIFY(result.diagnostics.empty());
const DbcDatabase &database = result.database;
QCOMPARE(database.attributeDefinitions.size(), std::size_t(7));
QCOMPARE(database.attributeAssignments.size(), std::size_t(5));
QCOMPARE(std::get<std::string>(database.attributes.at("BusLabel")), std::string("CAN"));
QCOMPARE(database.frames[0].cycleTimeMs.value(), 25U);
QCOMPARE(database.frames[1].cycleTimeMs.value(), 100U);
QCOMPARE(database.frames[1].frameFormat.value(), std::string("ExtendedCAN"));
QVERIFY(!database.frames[1].hasPgn);
QCOMPARE(std::get<double>(database.frames[0].attributes.at("Ratio")), 0.15);
QCOMPARE(std::get<std::string>(database.frames[0].signals[0].attributes.at("Label")), std::string("a\"b\\c"));
QCOMPARE(std::get<std::string>(database.frames[1].signals[0].attributes.at("Label")), std::string("default"));
const DecodeDatabase runtime = DbcDecodeBuilder().Build(database);
QCOMPARE(runtime.GetFrames()[0].cycleTimeMs.value(), 25U);
QCOMPARE(runtime.GetFrames()[1].frameFormat.value(), std::string("ExtendedCAN"));
QCOMPARE(runtime.GetFrames()[0].signals[0].attributes, database.frames[0].signals[0].attributes);
const std::unique_ptr<TreeNode> tree = DbcTreeBuilder().Build(database);
QCOMPARE(tree->GetChild(0)->GetFrame()->cycleTimeMs.value(), 25U);
QCOMPARE(tree->GetChild(0)->GetChild(0)->GetSignal()->attributes, database.frames[0].signals[0].attributes);
fixture.Write("BA_DEF_ BO_ \"Period\" INT 0 10;\nBA_DEF_DEF_ \"Period\" 1;\n"
"BA_ \"Period\" BO_ 1 2;\nBA_ \"Period\" BO_ 1 3;\nBO_ 1 F: 0 ECU\n");
const DbcDatabase repeated = DbcParser().ParseFile(fixture.path.string());
QCOMPARE(std::get<std::int64_t>(repeated.frames[0].attributes.at("Period")), std::int64_t(3));
fixture.Write("BA_DEF_ BO_ \"GenMsgCycleTime\" INT 0 4294967295;\n"
"BA_DEF_DEF_ \"GenMsgCycleTime\" 0;\n"
"BA_ \"GenMsgCycleTime\" BO_ 1 4294967295;\n"
"BA_DEF_ \"Wide\" INT -9223372036854775808 9223372036854775807;\n"
"BA_DEF_DEF_ \"Wide\" -9223372036854775808;\n"
"BA_DEF_ SG_ \"State\" ENUM \"off\",\"on\";\n"
"BA_DEF_DEF_ \"State\" 1;\n"
"BO_ 1 F: 0 ECU\nBO_ 2 Empty: 0 ECU\n");
const DbcDatabase boundaries = DbcParser().ParseFile(fixture.path.string());
QCOMPARE(boundaries.frames[0].cycleTimeMs.value(), std::numeric_limits<std::uint32_t>::max());
QCOMPARE(boundaries.frames[1].cycleTimeMs.value(), 0U);
QVERIFY(!boundaries.frames[0].frameFormat);
QCOMPARE(std::get<std::int64_t>(boundaries.attributes.at("Wide")), std::numeric_limits<std::int64_t>::min());
QCOMPARE(std::get<std::int64_t>(*boundaries.attributeDefinitions.at("State").defaultValue), std::int64_t(1));
}
void DbcParserTest::invalidAttributes() {
Fixture fixture;
const std::vector<std::string> invalid = {
"BA_DEF_ BO_ \"A\" INT 2 1;",
"BA_DEF_ BO_ \"A\" FLOAT nan 1;",
"BA_DEF_ BO_ \"A\" FLOAT 0 inf;",
"BA_DEF_ BO_ \"A\" INT 0 9223372036854775808;",
"BA_DEF_ BO_ \"A\" STRING; trailing",
"BA_DEF_ BO_ \"A\" ENUM;",
"BA_DEF_ BO_ \"A\" ENUM \"one\",;",
"BA_DEF_ BO_ \"A\" ENUM \"one\",\"one\";",
"BA_DEF_ BO_ \"VFrameFormat\" STRING;",
"BA_DEF_ SG_ \"GenMsgCycleTime\" INT 0 10;",
"BA_ \"Unknown\" BO_ 1 0;",
"BA_DEF_DEF_ \"Unknown\" 0;",
"BA_DEF_ BO_ \"A\" INT 0 1;\nBA_ \"A\" BO_ 1 2;",
"BA_DEF_ BO_ \"A\" INT 0 1;\nBA_ \"A\" BO_ 1 \"0\";",
"BA_DEF_ BO_ \"A\" INT 0 1;\nBA_ \"A\" SG_ 1 S 0;",
"BA_DEF_ BO_ \"A\" INT 0 1;\nBA_ \"A\" BO_ 2 0;",
"BA_DEF_ SG_ \"A\" INT 0 1;\nBA_ \"A\" SG_ 1 Missing 0;",
"BA_DEF_ BO_ \"A\" INT 0 1;\nBA_DEF_ BO_ \"A\" INT 0 1;",
"BA_DEF_ BO_ \"A\" INT 0 1;\nBA_DEF_DEF_ \"A\" 0;\nBA_DEF_DEF_ \"A\" 1;",
"BA_DEF_ BO_ \"A\" STRING;\nBA_ \"A\" BO_ 1 \"bad\\q\";",
"BA_DEF_ BO_ \"A\" STRING;\nBA_ \"A\" BO_ 1 \"unterminated;",
"BA_DEF_ BO_ \"A\" ENUM \"one\";\nBA_DEF_DEF_ \"A\" \"missing\";",
"BA_DEF_ BO_ \"A\" ENUM \"one\";\nBA_ \"A\" BO_ 1 1;",
"BA_DEF_ BO_ \"GenMsgCycleTime\" INT -1 4294967296;\nBA_DEF_DEF_ \"GenMsgCycleTime\" -1;",
"BA_DEF_ BO_ \"GenMsgCycleTime\" INT 0 4294967296;\nBA_ \"GenMsgCycleTime\" BO_ 1 4294967296;"};
for (const std::string &statement: invalid) {
fixture.Write("BO_ 1 F: 0 ECU\n" + statement + '\n');
const DbcParseResult result = DbcParser().ParseFileWithDiagnostics(fixture.path.string());
QVERIFY2(result.HasErrors(), statement.c_str());
QVERIFY(result.database.frames.empty());
QCOMPARE(result.diagnostics.back().code, DbcDiagnosticCode::InvalidAttribute);
QCOMPARE(result.diagnostics.back().lineNumber, std::size_t(2 + std::count(statement.begin(), statement.end(), '\n')));
QVERIFY_THROWS_EXCEPTION(std::runtime_error, DbcParser().ParseFile(fixture.path.string()));
}
}
void DbcParserTest::attributeWarnings() {
Fixture fixture;
fixture.Write("BA_DEF_ BO_ \"VFrameFormat\" ENUM \"StandardCAN\",\"ExtendedCAN\",\"J1939PG\",\"StandardCAN_FD\";\n"
"BA_DEF_DEF_ \"VFrameFormat\" \"StandardCAN_FD\";\n"
"BA_ \"VFrameFormat\" BO_ 1 2;\n"
"BA_ \"VFrameFormat\" BO_ 2147483649 0;\n"
"BO_ 1 F: 0 ECU\nBO_ 2147483649 E: 0 ECU\nBO_ 2 Default: 0 ECU\n");
const DbcParseResult result = DbcParser().ParseFileWithDiagnostics(fixture.path.string());
QVERIFY(!result.HasErrors());
QCOMPARE(result.diagnostics.size(), std::size_t(3));
const std::size_t lines[] = {3U, 4U, 2U};
for (std::size_t index = 0; index < 3U; ++index) {
QCOMPARE(result.diagnostics[index].code, DbcDiagnosticCode::UnsupportedAttribute);
QCOMPARE(result.diagnostics[index].severity, DbcDiagnosticSeverity::Warning);
QCOMPARE(result.diagnostics[index].lineNumber, lines[index]);
QCOMPARE(result.diagnostics[index].filePath, fixture.path.string());
}
QCOMPARE(result.database.frames[0].frameFormat.value(), std::string("J1939PG"));
QVERIFY(!result.database.frames[0].hasPgn);
QVERIFY(result.database.frames[1].isExtended);
}
void DbcParserTest::diagnostics() {
Fixture fixture;
fixture.Write("VERSION \"test\"\n"
@@ -126,7 +257,7 @@ void DbcParserTest::diagnostics() {
"BO_ 1 Frame: 1 ECU\n"
"SG_ Value : 0|8@1+ (1,0) [0|255] \"\" ECU\n"
"SIG_VALTYPE_ 1 Value : 1;\n"
"BA_ \"SignalType\" SG_ 1 Value 1;\n"
"BA_REL_ \"SignalType\" SG_ 1 Value 1;\n"
"VENDOR_EXTENSION_ data;\n");
const DbcParseResult result = DbcParser().ParseFileWithDiagnostics(fixture.path.string());
CHECK(!result.HasErrors() && result.database.frames.size() == 1U,
@@ -264,9 +395,7 @@ void DbcParserTest::diagnosticUnsupportedStatements() {
"CM_ SG_ 1 Value \"Signal comment\";\nVAL_ 1 Value 1 \"On\";\n";
const std::vector<std::string> statements = {
"SIG_VALTYPE_ 1 Value : 1;", "SIG_VALTYPE_ 1 Value : 2;",
"BA_ \"SignalType\" SG_ 1 Value 1;",
"BA_DEF_ SG_ \"SignalType\" INT 0 1;",
"BA_DEF_DEF_ \"SignalType\" 0;",
"BA_REL_ \"SignalType\" SG_ 1 Value 1;",
"BA_DEF_REL_ BU_SG_REL_ \"Relation\" INT 0 1;",
"BA_REL_ \"Relation\" BU_SG_REL_ ECU SG_ 1 Value 1;",
"SGTYPE_ Type : 8@1+ (1,0) [0|255] \"\" 0, Table;",