#include "dbc_decode_builder.h" #include "dbc_decoder.h" #include "dbc_parser.h" #include #include #include #include #include namespace { std::string WriteFixture(QTemporaryFile &file, const std::string &text) { if (!file.open() || file.write(text.data(), static_cast(text.size())) != static_cast(text.size()) || !file.flush()) throw std::runtime_error("Cannot write statement fixture"); return file.fileName().toStdString(); } std::string Quote(const std::string &text) { std::ostringstream stream; stream << std::quoted(text); return stream.str(); } std::string RepeatedRanges() { std::string ranges = "1-1"; for (unsigned index = 1U; index < 10000U; ++index) ranges += ",1-1"; return ranges; } const std::string definitions = "BO_ 1 Frame: 2 ECU\n" "SG_ Mode M : 0|8@1+ (1,0) [0|255] \"\" ECU\n" "SG_ Value m1 : 8|8@1+ (1,0) [0|255] \"\" ECU\n"; }// namespace class StatementParserTest : public QObject { Q_OBJECT private Q_SLOTS: void longValueDescriptions_data(); void longValueDescriptions(); void longMultiplexRanges_data(); void longMultiplexRanges(); void invalidLongStatements_data(); void invalidLongStatements(); }; void StatementParserTest::longValueDescriptions_data() { QTest::addColumn("description"); QTest::addColumn("count"); QTest::addColumn("beforeDefinitions"); QTest::newRow("50000-character-description") << std::string(50000U, 'x') << 1U << false; std::string escaped; for (unsigned index = 0U; index < 12500U; ++index) escaped += "\"\\; "; QTest::newRow("50000-escaped-characters") << escaped << 1U << true; QTest::newRow("10000-pairs") << std::string("Description") << 10000U << true; QTest::newRow("50000-spaces") << std::string(50000U, ' ') << 1U << false; } void StatementParserTest::longValueDescriptions() { QFETCH(std::string, description); QFETCH(unsigned, count); QFETCH(bool, beforeDefinitions); std::string statement = "VAL_ 1 Value"; for (unsigned index = 0U; index < count; ++index) statement += " " + std::to_string(index) + " " + Quote(description); statement += ";\n"; const std::string frame = "BO_ 1 Frame: 4 ECU\nSG_ Value : 0|32@1+ (1,0) [0|1e10] \"\" ECU\n"; QTemporaryFile file; const std::string path = WriteFixture(file, beforeDefinitions ? statement + frame : frame + statement); const DbcParser parser; const DbcParseResult result = parser.ParseFileWithDiagnostics(path); QVERIFY(!result.HasErrors()); QVERIFY(result.diagnostics.empty()); const std::vector databases = {result.database, parser.ParseFile(path)}; for (const DbcDatabase &database: databases) { const ValueDescriptions &values = database.frames.at(0).signals.at(0).valueDescriptions; QCOMPARE(values.size(), static_cast(count)); for (unsigned index = 0U; index < count; ++index) QCOMPARE(values.at(std::uint64_t(index)), description); const DecodeDatabase runtime = DbcDecodeBuilder().Build(database); RawCanFrame raw; raw.canId = 1U; const unsigned last = count - 1U; raw.data = {static_cast(last), static_cast(last >> 8U), 0U, 0U}; const DecodedFrameValue decoded = DbcDecoder().Decode(runtime, raw); QCOMPARE(decoded.status, FrameDecodeStatus::Valid); const std::string *label = decoded.signals.at(0).GetValueDescription(); QVERIFY(label); QCOMPARE(*label, description); } } void StatementParserTest::longMultiplexRanges_data() { QTest::addColumn("ranges"); QTest::addColumn("count"); QTest::addColumn("beforeDefinitions"); QTest::newRow("10000-ranges") << RepeatedRanges() << 10000U << false; const std::string paddedOne = std::string(50000U, '0') + "1"; QTest::newRow("long-bounds") << paddedOne + "-" + paddedOne << 1U << true; QTest::newRow("long-whitespace") << std::string(50000U, ' ') + "1\t-\t1" + std::string(50000U, ' ') << 1U << false; } void StatementParserTest::longMultiplexRanges() { QFETCH(std::string, ranges); QFETCH(unsigned, count); QFETCH(bool, beforeDefinitions); const std::string statement = "SG_MUL_VAL_ 1 Value Mode " + ranges + ";\n"; QTemporaryFile file; const std::string path = WriteFixture(file, beforeDefinitions ? statement + definitions : definitions + statement); const DbcParser parser; const DbcParseResult result = parser.ParseFileWithDiagnostics(path); QVERIFY(!result.HasErrors()); QVERIFY(result.diagnostics.empty()); const std::vector databases = {result.database, parser.ParseFile(path)}; for (const DbcDatabase &database: databases) { const Multiplexing &mux = database.frames.at(0).signals.at(1).multiplexing; QCOMPARE(mux.selector, std::string("Mode")); QCOMPARE(mux.ranges.size(), static_cast(count)); for (const MultiplexRange &range: mux.ranges) { QCOMPARE(range.minimum, std::uint64_t(1)); QCOMPARE(range.maximum, std::uint64_t(1)); } const DecodeDatabase runtime = DbcDecodeBuilder().Build(database); RawCanFrame raw; raw.canId = 1U; raw.data = {1U, 42U}; const DecodedFrameValue active = DbcDecoder().Decode(runtime, raw); QCOMPARE(active.status, FrameDecodeStatus::Valid); QVERIFY(active.signals.at(1).valid); QCOMPARE(std::get(active.signals[1].rawValue), std::uint64_t(42)); raw.data[0] = 2U; const DecodedFrameValue inactive = DbcDecoder().Decode(runtime, raw); QCOMPARE(inactive.signals.at(1).status, SignalDecodeStatus::Inactive); } } void StatementParserTest::invalidLongStatements_data() { QTest::addColumn("statement"); QTest::addColumn("code"); const std::string text(50000U, 'x'); const std::string value = "VAL_ 1 Value 0 \"" + text; QTest::newRow("val-missing-quote") << value + ";" << DbcDiagnosticCode::InvalidValueDescription; QTest::newRow("val-dangling-escape") << value + "\\" << DbcDiagnosticCode::InvalidValueDescription; QTest::newRow("val-invalid-escape") << value + "\\q\";" << DbcDiagnosticCode::InvalidValueDescription; QTest::newRow("val-missing-semicolon") << value + "\"" << DbcDiagnosticCode::InvalidValueDescription; QTest::newRow("val-trailing-text") << value + "\"; junk" << DbcDiagnosticCode::InvalidValueDescription; QTest::newRow("val-missing-pair-separator") << value + "\"1 \"On\";" << DbcDiagnosticCode::InvalidValueDescription; QTest::newRow("val-missing-number-separator") << "VAL_ 1 Value 0\"" + text + "\";" << DbcDiagnosticCode::InvalidValueDescription; QTest::newRow("val-duplicate") << value + "\" 00 \"Duplicate\";" << DbcDiagnosticCode::InvalidValueDescription; QTest::newRow("val-overflow") << "VAL_ 1 Value " + std::string(50000U, '9') + " \"Bad\";" << DbcDiagnosticCode::InvalidValueDescription; QTest::newRow("val-long-missing-reference") << "VAL_ 1 " + text + " 0 \"Bad\";" << DbcDiagnosticCode::InvalidValueDescription; const std::string mux = "SG_MUL_VAL_ 1 Value Mode " + RepeatedRanges(); QTest::newRow("mux-missing-semicolon") << mux << DbcDiagnosticCode::InvalidMultiplexing; QTest::newRow("mux-trailing-comma") << mux + ",;" << DbcDiagnosticCode::InvalidMultiplexing; QTest::newRow("mux-empty-range") << mux + ",,1-1;" << DbcDiagnosticCode::InvalidMultiplexing; QTest::newRow("mux-reversed-range") << mux + ",2-1;" << DbcDiagnosticCode::InvalidMultiplexing; QTest::newRow("mux-signed-bound") << mux + ",+1-1;" << DbcDiagnosticCode::InvalidMultiplexing; QTest::newRow("mux-suffix") << mux + "x;" << DbcDiagnosticCode::InvalidMultiplexing; QTest::newRow("mux-overflow") << mux + ",0-18446744073709551616;" << DbcDiagnosticCode::InvalidMultiplexing; QTest::newRow("mux-long-overflow") << "SG_MUL_VAL_ 1 Value Mode 0-" + std::string(50000U, '9') + ";" << DbcDiagnosticCode::InvalidMultiplexing; QTest::newRow("mux-trailing-text") << mux + "; junk" << DbcDiagnosticCode::InvalidMultiplexing; QTest::newRow("mux-long-missing-reference") << "SG_MUL_VAL_ 1 " + text + " Mode 1-1;" << DbcDiagnosticCode::InvalidMultiplexing; } void StatementParserTest::invalidLongStatements() { QFETCH(std::string, statement); QFETCH(DbcDiagnosticCode, code); QTemporaryFile file; const std::string path = WriteFixture(file, "\n" + statement + "\n" + definitions); const DbcParser parser; const DbcParseResult result = parser.ParseFileWithDiagnostics(path); QVERIFY(result.HasErrors()); QVERIFY(result.database.frames.empty()); QCOMPARE(result.diagnostics.size(), std::size_t(1)); const DbcDiagnostic &error = result.diagnostics[0]; QCOMPARE(error.severity, DbcDiagnosticSeverity::Error); QCOMPARE(error.code, code); QCOMPARE(error.filePath, path); QCOMPARE(error.lineNumber, std::size_t(2)); try { parser.ParseFile(path); QFAIL("Malformed statements must throw through ParseFile"); } catch (const std::runtime_error &exception) { QCOMPARE(std::string(exception.what()), path + ":2: " + error.message); } } int RunStatementParserTests(int argc, char **argv) { StatementParserTest tests; return QTest::qExec(&tests, argc, argv); } #include "dbc_statement_test.moc"