245 lines
10 KiB
C++
245 lines
10 KiB
C++
// SPDX-License-Identifier: MIT
|
|
// Copyright (c) 2026 Dim Himro
|
|
|
|
#include "dbc_decode_builder.h"
|
|
#include "dbc_decoder.h"
|
|
#include "dbc_parser.h"
|
|
#include <QTemporaryFile>
|
|
#include <QtTest>
|
|
#include <stdexcept>
|
|
#include <type_traits>
|
|
#include <unordered_map>
|
|
#include <utility>
|
|
|
|
static_assert(std::is_same_v<decltype(std::declval<const DecodeDatabase &>().GetFrames()),
|
|
const std::vector<DecodeFrame> &>);
|
|
static_assert(std::is_same_v<decltype(DecodedFrameValue::definition), std::shared_ptr<const DecodeFrame>>);
|
|
static_assert(std::is_same_v<decltype(DecodedSignalValue::definition), std::shared_ptr<const DecodeSignal>>);
|
|
|
|
namespace {
|
|
DecodeDatabase BuildText(const std::string &text) {
|
|
QTemporaryFile file;
|
|
if (!file.open() ||
|
|
file.write(text.data(), static_cast<qint64>(text.size())) != static_cast<qint64>(text.size()) ||
|
|
!file.flush())
|
|
throw std::runtime_error("Cannot write reload fixture");
|
|
return DbcDecodeBuilder().Build(DbcParser().ParseFile(file.fileName().toStdString()));
|
|
}
|
|
|
|
const std::string firstDbc =
|
|
"BO_ 17 OldFrame: 2 ECU\n"
|
|
"SG_ State : 0|8@1+ (2,1) [1|511] \"old\" ECU\n"
|
|
"SG_ Removed : 8|8@1+ (1,0) [0|255] \"\" ECU\n"
|
|
"VAL_ 17 State 1 \"Old label\";\n"
|
|
"BO_ 18 Deleted: 1 ECU\n"
|
|
"SG_ State : 0|8@1+ (1,0) [0|255] \"\" ECU\n";
|
|
const std::string secondDbc =
|
|
"BO_ 2147483665 Extended: 1 ECU\n"
|
|
"SG_ State : 0|8@1+ (1,0) [0|255] \"\" ECU\n"
|
|
"BO_ 17 NewFrame: 2 ECU\n"
|
|
"SG_ Renamed : 8|8@1+ (1,0) [0|255] \"\" ECU\n"
|
|
"SG_ State : 0|8@1+ (4,0) [0|1020] \"new\" ECU\n"
|
|
"VAL_ 17 State 1 \"New label\";\n";
|
|
}// namespace
|
|
|
|
class DbcLifetimeTest : public QObject {
|
|
Q_OBJECT
|
|
private Q_SLOTS:
|
|
void reload();
|
|
void independentSignalLifetime();
|
|
void lookupLifetime();
|
|
void keys();
|
|
void duplicateKeys();
|
|
void failedReload();
|
|
void invalidResults();
|
|
void retainedMultiplexStatuses();
|
|
};
|
|
|
|
void DbcLifetimeTest::reload() {
|
|
DecodeDatabase database = BuildText(firstDbc);
|
|
RawCanFrame raw;
|
|
raw.canId = 17U;
|
|
raw.data = {1U, 42U};
|
|
DecodedFrameValue oldResult = DbcDecoder().Decode(database, raw);
|
|
const DecodedFrameValue copied = oldResult;
|
|
DecodedFrameValue moved = std::move(oldResult);
|
|
std::weak_ptr<const DecodeFrame> oldDefinition = moved.definition;
|
|
database = BuildText(secondDbc);
|
|
const DecodedFrameValue current = DbcDecoder().Decode(database, raw);
|
|
QVERIFY(current.valid && moved.valid && copied.valid);
|
|
QVERIFY(current.definition != moved.definition);
|
|
QVERIFY(current.key == moved.key);
|
|
QVERIFY(current.signals[1].key == moved.signals[0].key);
|
|
QCOMPARE(moved.definition->name, std::string("OldFrame"));
|
|
QCOMPARE(copied.definition, moved.definition);
|
|
QCOMPARE(moved.signals[0].physicalValue, 3.0);
|
|
QCOMPARE(current.signals[1].physicalValue, 4.0);
|
|
QCOMPARE(moved.signals[0].definition->unit, std::string("old"));
|
|
QCOMPARE(current.signals[1].definition->unit, std::string("new"));
|
|
QCOMPARE(*moved.signals[0].GetValueDescription(), std::string("Old label"));
|
|
QCOMPARE(*current.signals[1].GetValueDescription(), std::string("New label"));
|
|
QCOMPARE(moved.signals[1].definition->name, std::string("Removed"));
|
|
QVERIFY(!database.FindSignal(moved.signals[1].key));
|
|
database = DecodeDatabase();
|
|
QVERIFY(!oldDefinition.expired());
|
|
QCOMPARE(current.definition->name, std::string("NewFrame"));
|
|
QCOMPARE(*current.signals[1].GetValueDescription(), std::string("New label"));
|
|
}
|
|
|
|
void DbcLifetimeTest::independentSignalLifetime() {
|
|
DecodedSignalValue saved;
|
|
std::weak_ptr<const DecodeSignal> definition;
|
|
const std::string *label = nullptr;
|
|
{
|
|
DecodeDatabase database = BuildText(firstDbc);
|
|
RawCanFrame raw;
|
|
raw.canId = 17U;
|
|
raw.data = {1U, 0U};
|
|
DecodedFrameValue result = DbcDecoder().Decode(database, raw);
|
|
saved = result.signals[0];
|
|
definition = saved.definition;
|
|
label = saved.GetValueDescription();
|
|
result = DecodedFrameValue();
|
|
database = BuildText(secondDbc);
|
|
}
|
|
QVERIFY(!definition.expired());
|
|
QVERIFY(label != nullptr);
|
|
QCOMPARE(*label, std::string("Old label"));
|
|
QCOMPARE(saved.definition->factor, 2.0);
|
|
DecodedSignalValue copied = saved;
|
|
DecodedSignalValue moved = std::move(saved);
|
|
QCOMPARE(moved.definition, copied.definition);
|
|
saved = DecodedSignalValue();
|
|
moved = DecodedSignalValue();
|
|
QVERIFY(!definition.expired());
|
|
copied = DecodedSignalValue();
|
|
QVERIFY(definition.expired());
|
|
}
|
|
|
|
void DbcLifetimeTest::lookupLifetime() {
|
|
std::shared_ptr<const DecodeFrame> frame;
|
|
std::shared_ptr<const DecodeSignal> signal;
|
|
std::weak_ptr<const DecodeFrame> released;
|
|
{
|
|
DecodeDatabase database = BuildText(firstDbc);
|
|
DecodeDatabase copy = database;
|
|
frame = DbcDecoder().FindFrame(database, 17U, false);
|
|
signal = copy.FindSignal(SignalKey(FrameKey(17U, false), "State"));
|
|
QVERIFY(frame && signal);
|
|
QCOMPARE(frame.get(), ©.GetFrames()[0]);
|
|
QCOMPARE(signal.get(), &frame->signals[0]);
|
|
released = frame;
|
|
database = BuildText(secondDbc);
|
|
DecodeDatabase moved = std::move(copy);
|
|
QVERIFY(copy.GetFrames().empty());
|
|
QVERIFY(!copy.FindFrame(FrameKey(17U, false)));
|
|
QCOMPARE(moved.FindFrame(FrameKey(17U, false)), frame);
|
|
}
|
|
QCOMPARE(frame->name, std::string("OldFrame"));
|
|
frame.reset();
|
|
QVERIFY(!released.expired());
|
|
QCOMPARE(signal->name, std::string("State"));
|
|
signal.reset();
|
|
QVERIFY(released.expired());
|
|
}
|
|
|
|
void DbcLifetimeTest::keys() {
|
|
const FrameKey frame(17U, false);
|
|
const SignalKey state(frame, "State");
|
|
const DecodeDatabase first = BuildText(firstDbc);
|
|
const DecodeDatabase second = BuildText(secondDbc);
|
|
QVERIFY(first.FindFrame(frame)->GetKey() == second.FindFrame(frame)->GetKey());
|
|
QVERIFY(first.FindSignal(state) && second.FindSignal(state));
|
|
QVERIFY(!second.FindFrame(FrameKey(18U, false)));
|
|
QVERIFY(!second.FindSignal(SignalKey(frame, "Removed")));
|
|
QVERIFY(second.FindSignal(SignalKey(frame, "Renamed")));
|
|
QVERIFY(second.FindSignal(SignalKey(FrameKey(17U, true), "State")));
|
|
QVERIFY(!second.FindSignal(SignalKey(FrameKey(999U, false), "State")));
|
|
QVERIFY(first.FindSignal(SignalKey(FrameKey(18U, false), "State")));
|
|
std::unordered_map<FrameKey, int, FrameKeyHasher> frames;
|
|
frames.emplace(frame, 1);
|
|
frames.emplace(FrameKey(17U, true), 2);
|
|
QCOMPARE(frames.size(), std::size_t(2U));
|
|
std::unordered_map<SignalKey, int, SignalKeyHasher> signals;
|
|
signals.emplace(state, 1);
|
|
signals.emplace(SignalKey(FrameKey(17U, true), "State"), 2);
|
|
signals.emplace(SignalKey(frame, "Renamed"), 3);
|
|
signals.emplace(SignalKey(FrameKey(18U, false), "State"), 4);
|
|
QCOMPARE(signals.at(SignalKey(FrameKey(17U, false), "State")), 1);
|
|
QCOMPARE(signals.size(), std::size_t(4U));
|
|
}
|
|
|
|
void DbcLifetimeTest::duplicateKeys() {
|
|
DecodeFrame frame;
|
|
frame.canId = 17U;
|
|
QVERIFY_THROWS_EXCEPTION(std::invalid_argument, (void) DecodeDatabase({frame, frame}));
|
|
DecodeSignal signal;
|
|
signal.name = "State";
|
|
signal.length = 1U;
|
|
frame.dlc = 1U;
|
|
frame.signals = {signal, signal};
|
|
QVERIFY_THROWS_EXCEPTION(std::invalid_argument, (void) DecodeDatabase({frame}));
|
|
DbcDatabase parsed;
|
|
FrameInfo source;
|
|
source.canId = 17U;
|
|
parsed.frames = {source, source};
|
|
QVERIFY_THROWS_EXCEPTION(std::invalid_argument, (void) DbcDecodeBuilder().Build(parsed));
|
|
}
|
|
|
|
void DbcLifetimeTest::failedReload() {
|
|
DecodeDatabase database = BuildText(firstDbc);
|
|
const std::shared_ptr<const DecodeFrame> original = database.FindFrame(FrameKey(17U, false));
|
|
QVERIFY_THROWS_EXCEPTION(std::runtime_error, database = BuildText("BO_ invalid\n"));
|
|
QCOMPARE(database.FindFrame(FrameKey(17U, false)), original);
|
|
database = BuildText("");
|
|
QVERIFY(database.GetFrames().empty());
|
|
QVERIFY(!database.FindFrame(FrameKey(17U, false)));
|
|
QCOMPARE(original->name, std::string("OldFrame"));
|
|
}
|
|
|
|
void DbcLifetimeTest::invalidResults() {
|
|
RawCanFrame raw;
|
|
raw.canId = 17U;
|
|
DecodedFrameValue result = DbcDecoder().Decode(BuildText(firstDbc), raw);
|
|
QVERIFY(result.valid);
|
|
QVERIFY(!result.signals[0].valid);
|
|
QCOMPARE(result.signals[0].status, SignalDecodeStatus::InsufficientPayload);
|
|
QVERIFY(!result.signals[0].GetValueDescription());
|
|
QCOMPARE(result.signals[0].definition->name, std::string("State"));
|
|
result = DbcDecoder().Decode(DecodeDatabase(), raw);
|
|
QVERIFY(!result.valid && !result.definition && result.signals.empty());
|
|
QVERIFY(result.key == FrameKey(17U, false));
|
|
QVERIFY(!DecodedSignalValue().GetValueDescription());
|
|
QVERIFY(!DecodeDatabase().FindSignal(SignalKey(FrameKey(17U, false), "State")));
|
|
}
|
|
|
|
void DbcLifetimeTest::retainedMultiplexStatuses() {
|
|
const std::string text =
|
|
"BO_ 17 Frame: 2 ECU\n"
|
|
"SG_ Mode M : 0|1@1+ (1,0) [0|1] \"\" ECU\n"
|
|
"SG_ State m1 : 8|8@1+ (1,0) [0|255] \"\" ECU\n";
|
|
const std::vector<std::vector<std::uint8_t>> payloads = {{0U}, {}, {1U}};
|
|
const SignalDecodeStatus statuses[] = {SignalDecodeStatus::Inactive,
|
|
SignalDecodeStatus::SelectorUnavailable,
|
|
SignalDecodeStatus::InsufficientPayload};
|
|
for (std::size_t index = 0U; index < payloads.size(); ++index) {
|
|
DecodeDatabase database = BuildText(text);
|
|
RawCanFrame raw;
|
|
raw.canId = 17U;
|
|
raw.data = payloads[index];
|
|
const DecodedSignalValue saved = DbcDecoder().Decode(database, raw).signals[1];
|
|
database = BuildText(secondDbc);
|
|
QCOMPARE(saved.status, statuses[index]);
|
|
QVERIFY(!saved.valid && !saved.GetValueDescription());
|
|
QCOMPARE(saved.definition->multiplexing.role, MultiplexRole::Branch);
|
|
QVERIFY(saved.key == SignalKey(FrameKey(17U, false), "State"));
|
|
}
|
|
}
|
|
|
|
int RunLifetimeTests(int argc, char **argv) {
|
|
DbcLifetimeTest test;
|
|
return QTest::qExec(&test, argc, argv);
|
|
}
|
|
|
|
#include "dbc_lifetime_test.moc"
|