Files
dbc/tests/dbc_pipeline_test.cpp
2026-10-14 09:38:14 -04:00

392 lines
17 KiB
C++

#include "dbc_decode_builder.h"
#include "dbc_decoder.h"
#include "dbc_parser.h"
#include "dbc_tree_builder.h"
#include <QTemporaryFile>
#include <QtTest>
#include <stdexcept>
#include <utility>
namespace {
const std::string fixture = R"DBC(BO_ 291 Standard: 2 SenderA
SG_ Mode M : 0|8@1+ (1,0) [0|255] "" ReceiverA
SG_ State m3 : 8|8@1+ (2,1) [1|511] "km/h" ReceiverA,ReceiverB
BO_ 2147483939 Extended: 1 SenderB
SG_ State : 7|8@0- (0.5,-3) [-67|60.5] "deg C" ReceiverB
BO_ 2565866562 J1939: 0 SenderC
CM_ BO_ 291 "Standard frame comment";
CM_ SG_ 291 Mode "Selector comment";
CM_ SG_ 291 State "Standard signal comment";
CM_ BO_ 2147483939 "Extended frame comment";
CM_ SG_ 2147483939 State "Extended signal comment";
VAL_ 291 Mode 3 "Selected";
VAL_ 291 State 4 "Standard four";
VAL_ 2147483939 State -2 "Extended minus two";
)DBC";
DbcParseResult ParseFixture(const std::string &text) {
QTemporaryFile file;
if (!file.open()) throw std::runtime_error("Cannot create DBC fixture");
if (file.write(text.data(), static_cast<qint64>(text.size())) != static_cast<qint64>(text.size()) || !file.flush())
throw std::runtime_error("Cannot write DBC fixture");
return DbcParser().ParseFileWithDiagnostics(file.fileName().toStdString());
}
// Independent expectations ensure a parser regression cannot be hidden by
// comparing builders only with the same incorrect parsed input.
DbcDatabase ExpectedMetadata() {
FrameInfo standard;
standard.name = "Standard";
standard.canId = 291U;
standard.dlc = 2U;
standard.transmitter = "SenderA";
standard.comment = "Standard frame comment";
SignalInfo mode;
mode.name = "Mode";
mode.multiplexing.role = MultiplexRole::Selector;
mode.length = 8U;
mode.maximum = 255;
mode.receivers = {"ReceiverA"};
mode.comment = "Selector comment";
mode.valueDescriptions = {{std::uint64_t(3), "Selected"}};
SignalInfo state;
state.name = "State";
state.multiplexing.role = MultiplexRole::Branch;
state.multiplexing.value = 3U;
state.startBit = 8U;
state.length = 8U;
state.factor = 2;
state.offset = 1;
state.minimum = 1;
state.maximum = 511;
state.unit = "km/h";
state.receivers = {"ReceiverA", "ReceiverB"};
state.comment = "Standard signal comment";
state.valueDescriptions = {{std::uint64_t(4), "Standard four"}};
standard.signals = {mode, state};
FrameInfo extended;
extended.name = "Extended";
extended.canId = 291U;
extended.isExtended = true;
extended.hasPgn = true;
extended.dlc = 1U;
extended.transmitter = "SenderB";
extended.comment = "Extended frame comment";
state.multiplexing = Multiplexing();
state.startBit = 7U;
state.isLittleEndian = false;
state.isSigned = true;
state.factor = 0.5;
state.offset = -3;
state.minimum = -67;
state.maximum = 60.5;
state.unit = "deg C";
state.receivers = {"ReceiverB"};
state.comment = "Extended signal comment";
state.valueDescriptions = {{std::int64_t(-2), "Extended minus two"}};
extended.signals = {state};
FrameInfo j1939;
j1939.name = "J1939";
j1939.canId = 0x18F00442U;
j1939.isExtended = true;
j1939.hasPgn = true;
j1939.pgn = 0xF004U;
j1939.transmitter = "SenderC";
return DbcDatabase{{standard, extended, j1939}};
}
template<class Signal>
void CheckSignalMetadata(const Signal &actual, const SignalInfo &expected) {
QCOMPARE(actual.name, expected.name);
QCOMPARE(actual.startBit, expected.startBit);
QCOMPARE(actual.length, expected.length);
QCOMPARE(actual.factor, expected.factor);
QCOMPARE(actual.offset, expected.offset);
QCOMPARE(actual.minimum, expected.minimum);
QCOMPARE(actual.maximum, expected.maximum);
QCOMPARE(actual.unit, expected.unit);
QVERIFY(actual.receivers == expected.receivers);
QCOMPARE(actual.comment, expected.comment);
QVERIFY(actual.valueDescriptions == expected.valueDescriptions);
QCOMPARE(actual.multiplexing.role, expected.multiplexing.role);
QCOMPARE(actual.multiplexing.value, expected.multiplexing.value);
}
void CheckSignal(const SignalInfo &actual, const SignalInfo &expected) {
CheckSignalMetadata(actual, expected);
QCOMPARE(actual.isLittleEndian, expected.isLittleEndian);
QCOMPARE(actual.isSigned, expected.isSigned);
}
void CheckSignal(const DecodeSignal &actual, const SignalInfo &expected) {
CheckSignalMetadata(actual, expected);
QCOMPARE(actual.byteOrder, expected.isLittleEndian ? ByteOrder::Intel : ByteOrder::Motorola);
QCOMPARE(actual.valueType, expected.isSigned ? ValueType::Signed : ValueType::Unsigned);
}
template<class Frame>
void CheckFrame(const Frame &actual, const FrameInfo &expected) {
QCOMPARE(actual.name, expected.name);
QCOMPARE(actual.canId, expected.canId);
QCOMPARE(actual.isExtended, expected.isExtended);
QCOMPARE(actual.dlc, expected.dlc);
QCOMPARE(actual.pgn, expected.pgn);
QCOMPARE(actual.hasPgn, expected.hasPgn);
QCOMPARE(actual.transmitter, expected.transmitter);
QCOMPARE(actual.comment, expected.comment);
QCOMPARE(actual.signals.size(), expected.signals.size());
for (std::size_t i = 0; i < expected.signals.size(); ++i)
CheckSignal(actual.signals[i], expected.signals[i]);
}
void CheckTree(const TreeNode &root, const DbcDatabase &expected) {
QCOMPARE(root.GetType(), NodeType::Root);
QVERIFY(root.GetParent() == nullptr);
QVERIFY(root.GetFrame() == nullptr);
QVERIFY(root.GetSignal() == nullptr);
QCOMPARE(root.GetChildCount(), expected.frames.size());
for (std::size_t i = 0; i < expected.frames.size(); ++i) {
const TreeNode *frame = root.GetChild(i);
QVERIFY(frame != nullptr);
QCOMPARE(frame->GetType(), NodeType::Frame);
QCOMPARE(frame->GetParent(), &root);
QCOMPARE(frame->GetRow(), i);
QCOMPARE(frame->GetName(), expected.frames[i].name);
QVERIFY(frame->GetFrame() != nullptr);
QVERIFY(frame->GetSignal() == nullptr);
CheckFrame(*frame->GetFrame(), expected.frames[i]);
QCOMPARE(frame->GetChildCount(), expected.frames[i].signals.size());
for (std::size_t j = 0; j < frame->GetChildCount(); ++j) {
const TreeNode *signal = frame->GetChild(j);
QVERIFY(signal != nullptr);
QCOMPARE(signal->GetType(), NodeType::Signal);
QCOMPARE(signal->GetParent(), frame);
QCOMPARE(signal->GetRow(), j);
QCOMPARE(signal->GetName(), expected.frames[i].signals[j].name);
QVERIFY(signal->GetSignal() != nullptr);
QVERIFY(signal->GetFrame() == nullptr);
QCOMPARE(signal->GetChildCount(), std::size_t(0));
CheckSignal(*signal->GetSignal(), expected.frames[i].signals[j]);
}
}
QVERIFY(root.GetChild(expected.frames.size()) == nullptr);
}
void CheckUnknown(const DecodeDatabase &runtime, bool extended) {
const FrameKey key(291U, extended);
QVERIFY(runtime.FindFrame(key) == nullptr);
QVERIFY(runtime.FindSignal(SignalKey(key, "State")) == nullptr);
QVERIFY(DbcDecoder().FindFrame(runtime, key.canId, key.isExtended) == nullptr);
RawCanFrame raw;
raw.canId = key.canId;
raw.isExtended = extended;
raw.data = {3U, 4U};
const DecodedFrameValue decoded = DbcDecoder().Decode(runtime, raw);
QVERIFY(!decoded.valid);
QCOMPARE(decoded.status, FrameDecodeStatus::UnknownFrame);
QVERIFY(decoded.definition == nullptr);
QVERIFY(decoded.signals.empty());
QVERIFY(decoded.key == key);
}
}// namespace
class PipelineTest : public QObject {
Q_OBJECT
private Q_SLOTS:
void metadataLookupDecodeAndTree();
void emptyDatabase_data();
void emptyDatabase();
void databaseReplacement();
};
void PipelineTest::metadataLookupDecodeAndTree() {
const DbcParseResult parsed = ParseFixture(fixture);
QVERIFY(!parsed.HasErrors());
QVERIFY(parsed.diagnostics.empty());
const DbcDatabase expected = ExpectedMetadata();
QCOMPARE(parsed.database.frames.size(), expected.frames.size());
const DecodeDatabase runtime = DbcDecodeBuilder().Build(parsed.database);
QCOMPARE(runtime.GetFrames().size(), expected.frames.size());
const std::unique_ptr<TreeNode> tree = DbcTreeBuilder().Build(parsed.database);
QVERIFY(tree != nullptr);
CheckTree(*tree, expected);
for (std::size_t i = 0; i < expected.frames.size(); ++i) {
CheckFrame(parsed.database.frames[i], expected.frames[i]);
CheckFrame(runtime.GetFrames()[i], expected.frames[i]);
const FrameKey key(expected.frames[i].canId, expected.frames[i].isExtended);
const std::shared_ptr<const DecodeFrame> found = runtime.FindFrame(key);
QVERIFY(found != nullptr);
QCOMPARE(found.get(), &runtime.GetFrames()[i]);
QCOMPARE(DbcDecoder().FindFrame(runtime, key.canId, key.isExtended).get(), found.get());
for (std::size_t j = 0; j < found->signals.size(); ++j) {
const std::shared_ptr<const DecodeSignal> signal = runtime.FindSignal(SignalKey(key, found->signals[j].name));
QVERIFY(signal != nullptr);
QCOMPARE(signal.get(), &found->signals[j]);
}
}
QVERIFY(runtime.FindSignal(SignalKey(FrameKey(291U, true), "Mode")) == nullptr);
QVERIFY(runtime.FindFrame(FrameKey(292U, false)) == nullptr);
RawCanFrame raw;
raw.canId = 291U;
raw.data = {3U, 4U};
const DecodedFrameValue standard = DbcDecoder().Decode(runtime, raw);
QVERIFY(standard.valid);
QCOMPARE(standard.status, FrameDecodeStatus::Valid);
QVERIFY(standard.key == FrameKey(291U, false));
QCOMPARE(standard.definition.get(), runtime.FindFrame(standard.key).get());
QCOMPARE(standard.signals.size(), std::size_t(2));
for (std::size_t i = 0; i < standard.signals.size(); ++i) {
const DecodedSignalValue &signal = standard.signals[i];
QVERIFY(signal.valid);
QCOMPARE(signal.status, SignalDecodeStatus::Valid);
QVERIFY(signal.key == SignalKey(standard.key, i == 0 ? "Mode" : "State"));
QCOMPARE(signal.definition.get(), runtime.FindSignal(signal.key).get());
QCOMPARE(std::get<std::uint64_t>(signal.rawValue), i == 0 ? 3U : 4U);
QCOMPARE(signal.physicalValue, i == 0 ? 3.0 : 9.0);
QVERIFY(signal.GetValueDescription() != nullptr);
QCOMPARE(*signal.GetValueDescription(), std::string(i == 0 ? "Selected" : "Standard four"));
}
raw.isExtended = true;
raw.data = {0xFEU};
const DecodedFrameValue extended = DbcDecoder().Decode(runtime, raw);
QVERIFY(extended.valid);
QCOMPARE(extended.status, FrameDecodeStatus::Valid);
QVERIFY(extended.key == FrameKey(291U, true));
QCOMPARE(extended.definition.get(), runtime.FindFrame(extended.key).get());
QCOMPARE(extended.signals.size(), std::size_t(1));
const DecodedSignalValue &signal = extended.signals[0];
QVERIFY(signal.valid);
QCOMPARE(signal.status, SignalDecodeStatus::Valid);
QVERIFY(signal.key == SignalKey(extended.key, "State"));
QCOMPARE(signal.definition.get(), runtime.FindSignal(signal.key).get());
QCOMPARE(std::get<std::int64_t>(signal.rawValue), std::int64_t(-2));
QCOMPARE(signal.physicalValue, -4.0);
QVERIFY(signal.GetValueDescription() != nullptr);
QCOMPARE(*signal.GetValueDescription(), std::string("Extended minus two"));
raw.canId = 0x18F00442U;
raw.data.clear();
const DecodedFrameValue j1939 = DbcDecoder().Decode(runtime, raw);
QVERIFY(j1939.valid);
QCOMPARE(j1939.status, FrameDecodeStatus::Valid);
QVERIFY(j1939.key == FrameKey(0x18F00442U, true));
QCOMPARE(j1939.definition.get(), runtime.FindFrame(j1939.key).get());
QVERIFY(j1939.signals.empty());
}
void PipelineTest::emptyDatabase_data() {
QTest::addColumn<QString>("text");
QTest::newRow("empty-file") << QString();
QTest::newRow("headers-only") << QString::fromUtf8("VERSION \"empty\"\nNS_ :\nBS_:\nBU_: Sender\n");
}
void PipelineTest::emptyDatabase() {
QFETCH(QString, text);
const DbcParseResult parsed = ParseFixture(text.toStdString());
QVERIFY(!parsed.HasErrors());
QVERIFY(parsed.diagnostics.empty());
QVERIFY(parsed.database.frames.empty());
const DecodeDatabase runtime = DbcDecodeBuilder().Build(parsed.database);
QVERIFY(runtime.GetFrames().empty());
CheckUnknown(runtime, false);
CheckUnknown(runtime, true);
const std::unique_ptr<TreeNode> tree = DbcTreeBuilder().Build(parsed.database);
QVERIFY(tree != nullptr);
CheckTree(*tree, DbcDatabase());
}
void PipelineTest::databaseReplacement() {
DbcParseResult parsed = ParseFixture(fixture);
QVERIFY(!parsed.HasErrors());
DecodeDatabase runtime = DbcDecodeBuilder().Build(parsed.database);
std::unique_ptr<TreeNode> tree = DbcTreeBuilder().Build(parsed.database);
RawCanFrame raw;
raw.canId = 291U;
raw.data = {3U, 4U};
const DecodedFrameValue saved = DbcDecoder().Decode(runtime, raw);
QVERIFY(saved.valid);
QCOMPARE(saved.signals.size(), std::size_t(2));
QVERIFY(tree != nullptr);
std::unique_ptr<TreeNode> savedTree = std::move(tree);
parsed = ParseFixture(R"DBC(BO_ 291 Replacement: 1 NewSender
SG_ NewState : 0|8@1+ (3,-1) [-1|764] "V" NewReceiver
CM_ BO_ 291 "Replacement frame";
CM_ SG_ 291 NewState "Replacement signal";
VAL_ 291 NewState 4 "New four";
)DBC");
QVERIFY(!parsed.HasErrors());
QVERIFY(parsed.diagnostics.empty());
FrameInfo replacement;
replacement.name = "Replacement";
replacement.canId = 291U;
replacement.dlc = 1U;
replacement.transmitter = "NewSender";
replacement.comment = "Replacement frame";
SignalInfo state;
state.name = "NewState";
state.length = 8U;
state.factor = 3;
state.offset = -1;
state.minimum = -1;
state.maximum = 764;
state.unit = "V";
state.receivers = {"NewReceiver"};
state.comment = "Replacement signal";
state.valueDescriptions = {{std::uint64_t(4), "New four"}};
replacement.signals = {state};
const DbcDatabase expected{{replacement}};
QCOMPARE(parsed.database.frames.size(), std::size_t(1));
CheckFrame(parsed.database.frames[0], replacement);
runtime = DbcDecodeBuilder().Build(parsed.database);
tree = DbcTreeBuilder().Build(parsed.database);
QVERIFY(tree != nullptr);
QCOMPARE(runtime.GetFrames().size(), std::size_t(1));
CheckFrame(runtime.GetFrames()[0], replacement);
CheckTree(*tree, expected);
CheckUnknown(runtime, true);
QVERIFY(runtime.FindFrame(FrameKey(0x18F00442U, true)) == nullptr);
QVERIFY(runtime.FindSignal(SignalKey(FrameKey(291U, false), "State")) == nullptr);
QVERIFY(runtime.FindSignal(SignalKey(FrameKey(291U, false), "Mode")) == nullptr);
raw.data = {4U};
const DecodedFrameValue current = DbcDecoder().Decode(runtime, raw);
QVERIFY(current.valid);
QCOMPARE(current.status, FrameDecodeStatus::Valid);
QCOMPARE(current.signals.size(), std::size_t(1));
QVERIFY(current.signals[0].valid);
QVERIFY(current.signals[0].key == SignalKey(FrameKey(291U, false), "NewState"));
QCOMPARE(current.signals[0].definition.get(), runtime.FindSignal(current.signals[0].key).get());
QCOMPARE(std::get<std::uint64_t>(current.signals[0].rawValue), std::uint64_t(4));
QCOMPARE(current.signals[0].physicalValue, 11.0);
QVERIFY(current.signals[0].GetValueDescription() != nullptr);
QCOMPARE(*current.signals[0].GetValueDescription(), std::string("New four"));
parsed = ParseFixture("");
QVERIFY(!parsed.HasErrors());
QVERIFY(parsed.diagnostics.empty());
runtime = DbcDecodeBuilder().Build(parsed.database);
tree = DbcTreeBuilder().Build(parsed.database);
QVERIFY(tree != nullptr);
QVERIFY(runtime.GetFrames().empty());
CheckUnknown(runtime, false);
CheckUnknown(runtime, true);
CheckTree(*tree, DbcDatabase());
QVERIFY(runtime.FindSignal(current.signals[0].key) == nullptr);
CheckFrame(*current.definition, replacement);
CheckFrame(*saved.definition, ExpectedMetadata().frames[0]);
CheckTree(*savedTree, ExpectedMetadata());
QCOMPARE(saved.signals[1].physicalValue, 9.0);
QVERIFY(saved.signals[1].GetValueDescription() != nullptr);
QCOMPARE(*saved.signals[1].GetValueDescription(), std::string("Standard four"));
}
int RunPipelineTests(int argc, char **argv) {
PipelineTest test;
return QTest::qExec(&test, argc, argv);
}
#include "dbc_pipeline_test.moc"