Files
dbc/tests/dbc_lifetime_test.cpp
T
2026-10-13 08:17:49 -04:00

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(), &copy.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"