404 lines
16 KiB
C++
404 lines
16 KiB
C++
// SPDX-License-Identifier: MIT
|
|
#include "dbc_decode_builder.h"
|
|
#include "dbc_decoder.h"
|
|
#include "signal_layout.h"
|
|
#include <QtTest>
|
|
#include <limits>
|
|
#include <stdexcept>
|
|
#include <utility>
|
|
|
|
namespace {
|
|
DecodeFrame ValidFrame() {
|
|
DecodeFrame frame;
|
|
frame.canId = 0x123U;
|
|
frame.dlc = 8U;
|
|
DecodeSignal signal;
|
|
signal.name = "Value";
|
|
signal.length = 8U;
|
|
frame.signals.push_back(signal);
|
|
return frame;
|
|
}
|
|
|
|
DbcDatabase ParsedDefinitions(const std::vector<DecodeFrame> &frames) {
|
|
DbcDatabase database;
|
|
for (const DecodeFrame &frame: frames) {
|
|
FrameInfo target;
|
|
target.canId = frame.canId;
|
|
target.isExtended = frame.isExtended;
|
|
target.dlc = frame.dlc;
|
|
for (const DecodeSignal &signal: frame.signals) {
|
|
SignalInfo copy;
|
|
copy.name = signal.name;
|
|
copy.startBit = signal.startBit;
|
|
copy.length = signal.length;
|
|
copy.isLittleEndian = signal.byteOrder == ByteOrder::Intel;
|
|
copy.isSigned = signal.valueType == ValueType::Signed;
|
|
copy.factor = signal.factor;
|
|
copy.offset = signal.offset;
|
|
copy.minimum = signal.minimum;
|
|
copy.maximum = signal.maximum;
|
|
copy.multiplexing = signal.multiplexing;
|
|
target.signals.push_back(copy);
|
|
}
|
|
database.frames.push_back(target);
|
|
}
|
|
return database;
|
|
}
|
|
}// namespace
|
|
|
|
class DbcValidationTest : public QObject {
|
|
Q_OBJECT
|
|
private Q_SLOTS:
|
|
void invalidMetadata_data();
|
|
void invalidMetadata();
|
|
void numericParameters_data();
|
|
void numericParameters();
|
|
void validBoundaries();
|
|
void layoutBoundaries();
|
|
void builderSnapshot();
|
|
void multiplexingBoundaries();
|
|
void independentSnapshot();
|
|
void failedReplacement();
|
|
};
|
|
|
|
void DbcValidationTest::invalidMetadata_data() {
|
|
QTest::addColumn<int>("kind");
|
|
QTest::newRow("standard-id") << 0;
|
|
QTest::newRow("extended-id") << 1;
|
|
QTest::newRow("encoded-id") << 2;
|
|
QTest::newRow("duplicate-frame") << 3;
|
|
QTest::newRow("duplicate-signal") << 4;
|
|
QTest::newRow("zero-length") << 5;
|
|
QTest::newRow("65-bits") << 6;
|
|
QTest::newRow("length-overflow") << 7;
|
|
QTest::newRow("intel-start-overflow") << 8;
|
|
QTest::newRow("motorola-start-overflow") << 9;
|
|
QTest::newRow("intel-outside-payload") << 10;
|
|
QTest::newRow("motorola-outside-payload") << 11;
|
|
QTest::newRow("zero-payload") << 12;
|
|
QTest::newRow("inverted-range") << 13;
|
|
QTest::newRow("byte-order") << 14;
|
|
QTest::newRow("value-type") << 15;
|
|
QTest::newRow("multiplex-role") << 16;
|
|
QTest::newRow("missing-selector") << 17;
|
|
QTest::newRow("signed-selector") << 18;
|
|
QTest::newRow("two-selectors") << 19;
|
|
QTest::newRow("branch-range") << 20;
|
|
QTest::newRow("unconditional-overlap") << 21;
|
|
QTest::newRow("mixed-order-overlap") << 22;
|
|
QTest::newRow("same-branch-overlap") << 23;
|
|
QTest::newRow("selector-branch-overlap") << 24;
|
|
QTest::newRow("maximum-can-id") << 25;
|
|
}
|
|
|
|
void DbcValidationTest::invalidMetadata() {
|
|
QFETCH(int, kind);
|
|
std::vector<DecodeFrame> frames = {ValidFrame()};
|
|
DecodeFrame &frame = frames[0];
|
|
DecodeSignal &signal = frame.signals[0];
|
|
DecodeSignal other = signal;
|
|
other.name = "Other";
|
|
switch (kind) {
|
|
case 0:
|
|
frame.canId = 0x800U;
|
|
break;
|
|
case 1:
|
|
frame.isExtended = true;
|
|
frame.canId = 0x20000000U;
|
|
break;
|
|
case 2:
|
|
frame.isExtended = true;
|
|
frame.canId = 0x80000123U;
|
|
break;
|
|
case 3:
|
|
frames.push_back(frame);
|
|
break;
|
|
case 4:
|
|
frame.signals.push_back(signal);
|
|
break;
|
|
case 5:
|
|
signal.length = 0U;
|
|
break;
|
|
case 6:
|
|
signal.length = 65U;
|
|
break;
|
|
case 7:
|
|
signal.length = std::numeric_limits<std::uint32_t>::max();
|
|
break;
|
|
case 8:
|
|
signal.startBit = std::numeric_limits<std::uint32_t>::max();
|
|
break;
|
|
case 9:
|
|
signal.byteOrder = ByteOrder::Motorola;
|
|
signal.startBit = std::numeric_limits<std::uint32_t>::max();
|
|
break;
|
|
case 10:
|
|
signal.startBit = 57U;
|
|
break;
|
|
case 11:
|
|
signal.byteOrder = ByteOrder::Motorola;
|
|
signal.startBit = 56U;
|
|
break;
|
|
case 12:
|
|
frame.dlc = 0U;
|
|
break;
|
|
case 13:
|
|
signal.minimum = 2.0;
|
|
signal.maximum = 1.0;
|
|
break;
|
|
case 14:
|
|
signal.byteOrder = static_cast<ByteOrder>(99);
|
|
break;
|
|
case 15:
|
|
signal.valueType = static_cast<ValueType>(99);
|
|
break;
|
|
case 16:
|
|
signal.multiplexing.role = static_cast<MultiplexRole>(99);
|
|
break;
|
|
case 17:
|
|
signal.multiplexing.role = MultiplexRole::Branch;
|
|
break;
|
|
case 18:
|
|
signal.multiplexing.role = MultiplexRole::Selector;
|
|
signal.valueType = ValueType::Signed;
|
|
break;
|
|
case 19:
|
|
signal.multiplexing.role = MultiplexRole::Selector;
|
|
other.multiplexing.role = MultiplexRole::Selector;
|
|
other.startBit = 8U;
|
|
frame.signals.push_back(other);
|
|
break;
|
|
case 20:
|
|
case 23:
|
|
case 24:
|
|
signal.multiplexing.role = MultiplexRole::Selector;
|
|
other.multiplexing.role = MultiplexRole::Branch;
|
|
other.startBit = kind == 24 ? 0U : 8U;
|
|
other.multiplexing.value = kind == 20 ? 256U : 1U;
|
|
frame.signals.push_back(other);
|
|
if (kind == 23) {
|
|
other.name = "Third";
|
|
frame.signals.push_back(other);
|
|
}
|
|
break;
|
|
case 21:
|
|
frame.signals.push_back(other);
|
|
break;
|
|
case 22:
|
|
other.byteOrder = ByteOrder::Motorola;
|
|
other.startBit = 7U;
|
|
frame.signals.push_back(other);
|
|
break;
|
|
case 25:
|
|
frame.canId = std::numeric_limits<std::uint32_t>::max();
|
|
break;
|
|
}
|
|
QVERIFY_THROWS_EXCEPTION(std::invalid_argument, (void) DecodeDatabase(frames));
|
|
// Parsed metadata uses bools for byte order and signedness, so invalid enum
|
|
// values exist only in direct runtime definitions.
|
|
if (kind != 14 && kind != 15)
|
|
QVERIFY_THROWS_EXCEPTION(std::invalid_argument, (void) DbcDecodeBuilder().Build(ParsedDefinitions(frames)));
|
|
}
|
|
|
|
void DbcValidationTest::numericParameters_data() {
|
|
QTest::addColumn<int>("field");
|
|
QTest::addColumn<double>("value");
|
|
const double values[] = {std::numeric_limits<double>::quiet_NaN(),
|
|
std::numeric_limits<double>::infinity(),
|
|
-std::numeric_limits<double>::infinity()};
|
|
for (int field = 0; field < 4; ++field) {
|
|
for (int index = 0; index < 3; ++index) {
|
|
const QByteArray name = QByteArray::number(field) + "-" + QByteArray::number(index);
|
|
QTest::newRow(name.constData()) << field << values[index];
|
|
}
|
|
}
|
|
}
|
|
|
|
void DbcValidationTest::numericParameters() {
|
|
QFETCH(int, field);
|
|
QFETCH(double, value);
|
|
DecodeFrame frame = ValidFrame();
|
|
DecodeSignal &signal = frame.signals[0];
|
|
switch (field) {
|
|
case 0:
|
|
signal.factor = value;
|
|
break;
|
|
case 1:
|
|
signal.offset = value;
|
|
break;
|
|
case 2:
|
|
signal.minimum = value;
|
|
break;
|
|
case 3:
|
|
signal.maximum = value;
|
|
break;
|
|
}
|
|
QVERIFY_THROWS_EXCEPTION(std::invalid_argument, (void) DecodeDatabase({frame}));
|
|
QVERIFY_THROWS_EXCEPTION(std::invalid_argument, (void) DbcDecodeBuilder().Build(ParsedDefinitions({frame})));
|
|
}
|
|
|
|
void DbcValidationTest::validBoundaries() {
|
|
for (ByteOrder order: {ByteOrder::Intel, ByteOrder::Motorola}) {
|
|
for (std::uint8_t dlc: {std::uint8_t(8U), std::uint8_t(64U), std::uint8_t(255U)}) {
|
|
DecodeFrame standard = ValidFrame();
|
|
standard.canId = 0x7FFU;
|
|
standard.dlc = dlc;
|
|
DecodeSignal &signal = standard.signals[0];
|
|
signal.byteOrder = order;
|
|
signal.startBit = (std::uint32_t(dlc) - 8U) * 8U + (order == ByteOrder::Intel ? 0U : 7U);
|
|
signal.length = 64U;
|
|
signal.factor = -2.0;
|
|
signal.offset = std::numeric_limits<double>::max();
|
|
signal.minimum = -std::numeric_limits<double>::max();
|
|
signal.maximum = std::numeric_limits<double>::max();
|
|
DecodeFrame extended = standard;
|
|
extended.isExtended = true;
|
|
DecodeFrame maximum = extended;
|
|
maximum.canId = 0x1FFFFFFFU;
|
|
const std::vector<DecodeFrame> frames = {standard, extended, maximum};
|
|
const DecodeDatabase direct(frames);
|
|
const DecodeDatabase built = DbcDecodeBuilder().Build(ParsedDefinitions(frames));
|
|
for (const DecodeFrame &frame: frames) {
|
|
QVERIFY(direct.FindFrame(frame.GetKey()));
|
|
QVERIFY(built.FindSignal(SignalKey(frame.GetKey(), "Value")));
|
|
}
|
|
}
|
|
}
|
|
DecodeFrame empty;
|
|
empty.canId = 0U;
|
|
QVERIFY(DecodeDatabase({empty}).FindFrame(empty.GetKey()));
|
|
QVERIFY(DbcDecodeBuilder().Build(DbcDatabase()).GetFrames().empty());
|
|
}
|
|
|
|
void DbcValidationTest::layoutBoundaries() {
|
|
// Compare the shared coordinate calculation with independent DBC bit traversal.
|
|
for (const std::uint8_t dlc: {std::uint8_t(0U), std::uint8_t(1U), std::uint8_t(8U),
|
|
std::uint8_t(64U), std::uint8_t(255U)}) {
|
|
const std::uint64_t payloadBits = std::uint64_t(dlc) * 8U;
|
|
for (const bool littleEndian: {true, false}) {
|
|
for (std::uint32_t start = 0U; start < payloadBits + 8U; ++start) {
|
|
std::uint64_t bit = start;
|
|
bool fits = true;
|
|
QVERIFY(!IsValidSignalLayout(start, 0U, littleEndian, dlc));
|
|
for (std::uint32_t length = 1U; length <= 64U; ++length) {
|
|
fits = fits && bit < payloadBits;
|
|
QCOMPARE(IsValidSignalLayout(start, length, littleEndian, dlc), fits);
|
|
if (littleEndian)
|
|
++bit;
|
|
else
|
|
bit = bit % 8U == 0U ? bit + 15U : bit - 1U;
|
|
}
|
|
QVERIFY(!IsValidSignalLayout(start, 65U, littleEndian, dlc));
|
|
}
|
|
const std::uint32_t maximum = std::numeric_limits<std::uint32_t>::max();
|
|
QVERIFY(!IsValidSignalLayout(maximum, 1U, littleEndian, dlc));
|
|
QVERIFY(!IsValidSignalLayout(maximum, maximum, littleEndian, dlc));
|
|
QVERIFY(!IsValidSignalLayout(0U, maximum, littleEndian, dlc));
|
|
}
|
|
}
|
|
}
|
|
|
|
void DbcValidationTest::builderSnapshot() {
|
|
DbcDatabase source = ParsedDefinitions({ValidFrame()});
|
|
source.frames[0].signals[0].valueDescriptions.emplace(std::uint64_t(1U), "One");
|
|
const FrameKey key(source.frames[0].canId, source.frames[0].isExtended);
|
|
const DecodeDatabase database = DbcDecodeBuilder().Build(source);
|
|
source.frames[0].canId = 0x800U;
|
|
source.frames[0].signals[0].name = "Changed";
|
|
source.frames[0].signals[0].valueDescriptions.begin()->second = "Changed";
|
|
source.frames.clear();
|
|
const std::shared_ptr<const DecodeSignal> signal = database.FindSignal(SignalKey(key, "Value"));
|
|
QVERIFY(signal);
|
|
QCOMPARE(signal->valueDescriptions.begin()->second, std::string("One"));
|
|
QVERIFY(database.FindFrame(key));
|
|
QVERIFY(!database.FindFrame(FrameKey(0x800U, false)));
|
|
}
|
|
|
|
void DbcValidationTest::multiplexingBoundaries() {
|
|
DecodeFrame frame = ValidFrame();
|
|
frame.dlc = 9U;
|
|
DecodeSignal &selector = frame.signals[0];
|
|
selector.length = 64U;
|
|
selector.multiplexing.role = MultiplexRole::Selector;
|
|
DecodeSignal first;
|
|
first.name = "First";
|
|
first.startBit = 64U;
|
|
first.length = 8U;
|
|
first.multiplexing.role = MultiplexRole::Branch;
|
|
first.multiplexing.value = std::numeric_limits<std::uint64_t>::max();
|
|
DecodeSignal second = first;
|
|
second.name = "Second";
|
|
second.byteOrder = ByteOrder::Motorola;
|
|
second.startBit = 71U;
|
|
second.multiplexing.value = 0U;
|
|
frame.signals.push_back(first);
|
|
frame.signals.push_back(second);
|
|
const DecodeDatabase direct({frame});
|
|
const DecodeDatabase built = DbcDecodeBuilder().Build(ParsedDefinitions({frame}));
|
|
RawCanFrame raw;
|
|
raw.canId = frame.canId;
|
|
raw.data.assign(9U, 255U);
|
|
for (const DecodeDatabase &database: {direct, built}) {
|
|
const DecodedFrameValue result = DbcDecoder().Decode(database, raw);
|
|
QCOMPARE(result.signals[1].status, SignalDecodeStatus::Valid);
|
|
QCOMPARE(result.signals[2].status, SignalDecodeStatus::Inactive);
|
|
}
|
|
}
|
|
|
|
void DbcValidationTest::independentSnapshot() {
|
|
std::vector<DecodeFrame> frames = {ValidFrame()};
|
|
DecodeFrame &alias = frames[0];
|
|
DecodeSignal &signalAlias = alias.signals[0];
|
|
signalAlias.valueDescriptions.emplace(std::uint64_t(1U), "One");
|
|
std::string &descriptionAlias = signalAlias.valueDescriptions.begin()->second;
|
|
const FrameKey key = alias.GetKey();
|
|
const DecodeDatabase database(std::move(frames));
|
|
alias.canId = 456U;
|
|
signalAlias.name = "Changed";
|
|
signalAlias.startBit = std::numeric_limits<std::uint32_t>::max();
|
|
descriptionAlias = "Changed";
|
|
const std::shared_ptr<const DecodeFrame> saved = database.FindFrame(key);
|
|
QVERIFY(saved);
|
|
QCOMPARE(saved->canId, key.canId);
|
|
QVERIFY(!database.FindFrame(FrameKey(456U, false)));
|
|
const std::shared_ptr<const DecodeSignal> signal = database.FindSignal(SignalKey(key, "Value"));
|
|
QVERIFY(signal);
|
|
QCOMPARE(signal->startBit, 0U);
|
|
QCOMPARE(signal->valueDescriptions.begin()->second, std::string("One"));
|
|
}
|
|
|
|
void DbcValidationTest::failedReplacement() {
|
|
const DecodeFrame valid = ValidFrame();
|
|
DecodeDatabase database({valid});
|
|
const std::shared_ptr<const DecodeFrame> original = database.FindFrame(valid.GetKey());
|
|
DecodeFrame invalid = valid;
|
|
invalid.canId = 0x800U;
|
|
QVERIFY_THROWS_EXCEPTION(std::invalid_argument, database = DecodeDatabase({invalid}));
|
|
QCOMPARE(database.FindFrame(valid.GetKey()).get(), original.get());
|
|
invalid = valid;
|
|
invalid.signals[0].factor = std::numeric_limits<double>::infinity();
|
|
QVERIFY_THROWS_EXCEPTION(std::invalid_argument,
|
|
database = DbcDecodeBuilder().Build(ParsedDefinitions({invalid})));
|
|
QCOMPARE(database.FindFrame(valid.GetKey()).get(), original.get());
|
|
QVERIFY_THROWS_EXCEPTION(std::invalid_argument, database = DecodeDatabase({valid, valid}));
|
|
QCOMPARE(database.FindFrame(valid.GetKey()).get(), original.get());
|
|
invalid = valid;
|
|
invalid.signals.push_back(invalid.signals[0]);
|
|
QVERIFY_THROWS_EXCEPTION(std::invalid_argument,
|
|
database = DbcDecodeBuilder().Build(ParsedDefinitions({invalid})));
|
|
QCOMPARE(database.FindFrame(valid.GetKey()).get(), original.get());
|
|
QVERIFY(database.FindSignal(SignalKey(valid.GetKey(), "Value")));
|
|
QCOMPARE(database.GetFrames().size(), std::size_t(1U));
|
|
database = DecodeDatabase();
|
|
QVERIFY(database.GetFrames().empty());
|
|
QVERIFY(!database.FindFrame(valid.GetKey()));
|
|
QVERIFY(original);
|
|
}
|
|
|
|
int RunValidationTests(int argc, char **argv) {
|
|
DbcValidationTest tests;
|
|
return QTest::qExec(&tests, argc, argv);
|
|
}
|
|
|
|
#include "dbc_validation_test.moc"
|