746 lines
35 KiB
C++
746 lines
35 KiB
C++
// Additional regression coverage. Kept separate for review before the first run.
|
|
#include "dbc_decode_builder.h"
|
|
#include "dbc_decoder.h"
|
|
#include "dbc_parser.h"
|
|
#include "dbc_statement_handler.h"
|
|
#include "dbc_tree_builder.h"
|
|
#include "typefactory.h"
|
|
#include <QTemporaryFile>
|
|
#include <QtTest>
|
|
#include <array>
|
|
#include <limits>
|
|
#include <stdexcept>
|
|
|
|
namespace {
|
|
DbcDatabase ParseText(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().ParseFile(file.fileName().toStdString());
|
|
}
|
|
|
|
DecodeDatabase SingleSignal(const DecodeSignal &signal) {
|
|
DecodeDatabase database;
|
|
DecodeFrame frame;
|
|
frame.canId = 17U;
|
|
frame.signals.push_back(signal);
|
|
database.frames.push_back(frame);
|
|
database.frameIndexByKey.emplace(FrameKey(17U, false), 0U);
|
|
return database;
|
|
}
|
|
|
|
// Independent reference: collect the visited bits first, then weight them.
|
|
// Motorola uses a linear MSB-first coordinate instead of sawtooth traversal.
|
|
std::uint64_t ReferenceExtract(const std::vector<std::uint8_t> &data,
|
|
ByteOrder order, unsigned start, unsigned length) {
|
|
std::uint64_t expected = 0;
|
|
for (unsigned i = 0; i < length; ++i) {
|
|
const unsigned coordinate = order == ByteOrder::Intel
|
|
? start + i
|
|
: (start / 8U) * 8U + (7U - start % 8U) + i;
|
|
const unsigned bit = order == ByteOrder::Intel ? coordinate % 8U : 7U - coordinate % 8U;
|
|
const unsigned weight = order == ByteOrder::Intel ? i : length - 1U - i;
|
|
expected |= std::uint64_t((data.at(coordinate / 8U) >> bit) & 1U) << weight;
|
|
}
|
|
return expected;
|
|
}
|
|
|
|
struct FactoryBase {
|
|
virtual ~FactoryBase() = default;
|
|
virtual int Value() const = 0;
|
|
};
|
|
struct FactoryFirst : FactoryBase {
|
|
explicit FactoryFirst(std::unique_ptr<int> value) : value(std::move(value)) {}
|
|
int Value() const override { return *value; }
|
|
std::unique_ptr<int> value;
|
|
};
|
|
struct FactorySecond : FactoryFirst {
|
|
using FactoryFirst::FactoryFirst;
|
|
int Value() const override { return *value + 1; }
|
|
};
|
|
}// namespace
|
|
|
|
class CoverageTest : public QObject {
|
|
Q_OBJECT
|
|
private Q_SLOTS:
|
|
void frameLookup();
|
|
void signedBoundaries_data();
|
|
void signedBoundaries();
|
|
void extraction_data();
|
|
void extraction();
|
|
void invalidSignalStatus_data();
|
|
void invalidSignalStatus();
|
|
void malformedRuntimeMultiplexing_data();
|
|
void malformedRuntimeMultiplexing();
|
|
void pgn_data();
|
|
void pgn();
|
|
void comments_data();
|
|
void comments();
|
|
void ignoredCommentStatements_data();
|
|
void ignoredCommentStatements();
|
|
void ambiguousValueReferences_data();
|
|
void ambiguousValueReferences();
|
|
void valueReferenceScope();
|
|
void fileFormatting_data();
|
|
void fileFormatting();
|
|
void missingFile();
|
|
void builders();
|
|
void physicalConversion_data();
|
|
void physicalConversion();
|
|
void multiplexingScenarios();
|
|
void multiplexingErrorLocations_data();
|
|
void multiplexingErrorLocations();
|
|
void escapedValueDescriptions();
|
|
void factory();
|
|
};
|
|
|
|
void CoverageTest::frameLookup() {
|
|
DbcDecoder decoder;
|
|
RawCanFrame raw;
|
|
raw.canId = 17U;
|
|
DecodeDatabase empty;
|
|
QVERIFY(decoder.FindFrame(empty, 17U, false) == nullptr);
|
|
const DecodedFrameValue absent = decoder.Decode(empty, raw);
|
|
QVERIFY(!absent.valid);
|
|
QVERIFY(absent.definition == nullptr);
|
|
QVERIFY(absent.signals.empty());
|
|
DecodeSignal signal;
|
|
DecodeDatabase database = SingleSignal(signal);
|
|
QVERIFY(decoder.FindFrame(database, 18U, false) == nullptr);
|
|
QVERIFY(decoder.FindFrame(database, 17U, true) == nullptr);
|
|
raw.isExtended = true;
|
|
QVERIFY(!decoder.Decode(database, raw).valid);
|
|
raw.isExtended = false;
|
|
raw.canId = 18U;
|
|
QVERIFY(!decoder.Decode(database, raw).valid);
|
|
raw.canId = 17U;
|
|
database.frameIndexByKey.at(FrameKey(17U, false)) = database.frames.size();
|
|
QVERIFY(decoder.FindFrame(database, 17U, false) == nullptr);
|
|
QVERIFY(!decoder.Decode(database, raw).valid);
|
|
database.frameIndexByKey.at(FrameKey(17U, false)) = 0U;
|
|
database.frames[0].signals.clear();
|
|
const DecodedFrameValue matched = decoder.Decode(database, raw);
|
|
QVERIFY(matched.valid);
|
|
QCOMPARE(matched.definition, &database.frames[0]);
|
|
QVERIFY(matched.signals.empty());
|
|
}
|
|
|
|
void CoverageTest::signedBoundaries_data() {
|
|
QTest::addColumn<int>("order");
|
|
QTest::addColumn<unsigned>("length");
|
|
QTest::addColumn<qlonglong>("expected");
|
|
for (int order = 0; order < 2; ++order) {
|
|
for (unsigned length: {1U, 2U, 7U, 8U, 31U, 32U, 63U}) {
|
|
const std::int64_t half = std::int64_t(1) << (length - 1U);
|
|
const std::array<std::int64_t, 4> values{{0, -1, -half, half - 1}};
|
|
for (unsigned index = 0; index < values.size(); ++index) {
|
|
const QByteArray name = QByteArray::number(order) + "-" + QByteArray::number(length) + "-" + QByteArray::number(index);
|
|
QTest::newRow(name.constData()) << order << length << qlonglong(values[index]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CoverageTest::signedBoundaries() {
|
|
QFETCH(int, order);
|
|
QFETCH(unsigned, length);
|
|
QFETCH(qlonglong, expected);
|
|
DecodeSignal signal;
|
|
signal.byteOrder = order == 0 ? ByteOrder::Intel : ByteOrder::Motorola;
|
|
signal.startBit = order == 0 ? 0U : 7U;
|
|
signal.length = length;
|
|
signal.valueType = ValueType::Signed;
|
|
RawCanFrame raw;
|
|
raw.canId = 17U;
|
|
raw.data.assign(8U, 0U);
|
|
const std::uint64_t bits = static_cast<std::uint64_t>(expected);
|
|
for (unsigned i = 0; i < length; ++i) {
|
|
const unsigned weight = order == 0 ? i : length - 1U - i;
|
|
const unsigned position = order == 0 ? i % 8U : 7U - i % 8U;
|
|
raw.data[i / 8U] |= static_cast<std::uint8_t>(((bits >> weight) & 1U) << position);
|
|
}
|
|
const DecodeDatabase database = SingleSignal(signal);
|
|
const DecodedFrameValue result = DbcDecoder().Decode(database, raw);
|
|
QVERIFY(result.signals[0].valid);
|
|
QVERIFY(std::holds_alternative<std::int64_t>(result.signals[0].rawValue));
|
|
QCOMPARE(std::get<std::int64_t>(result.signals[0].rawValue), static_cast<std::int64_t>(expected));
|
|
}
|
|
|
|
void CoverageTest::extraction_data() {
|
|
QTest::addColumn<int>("order");
|
|
QTest::addColumn<unsigned>("start");
|
|
QTest::addColumn<unsigned>("length");
|
|
for (int order = 0; order < 2; ++order)
|
|
for (unsigned start = 0; start < 16U; ++start)
|
|
for (unsigned length = 1; length <= 64U; ++length) {
|
|
const QByteArray name = QByteArray::number(order) + "-" + QByteArray::number(start) + "-" + QByteArray::number(length);
|
|
QTest::newRow(name.constData()) << order << start << length;
|
|
}
|
|
}
|
|
|
|
void CoverageTest::extraction() {
|
|
QFETCH(int, order);
|
|
QFETCH(unsigned, start);
|
|
QFETCH(unsigned, length);
|
|
DecodeSignal signal;
|
|
signal.byteOrder = order == 0 ? ByteOrder::Intel : ByteOrder::Motorola;
|
|
signal.startBit = start;
|
|
signal.length = length;
|
|
const DecodeDatabase database = SingleSignal(signal);
|
|
RawCanFrame raw;
|
|
raw.canId = 17U;
|
|
const std::vector<std::vector<std::uint8_t>> patterns = {
|
|
std::vector<std::uint8_t>(10U, 0U),
|
|
std::vector<std::uint8_t>(10U, 255U),
|
|
{0xA5, 0x3C, 0x81, 0x7E, 0x12, 0xD4, 0x69, 0xB0, 0x5A, 0xC3}};
|
|
for (const std::vector<std::uint8_t> &data: patterns) {
|
|
raw.data = data;
|
|
const unsigned firstCoordinate = order == 0 ? start : (start / 8U) * 8U + 7U - start % 8U;
|
|
raw.data.resize((firstCoordinate + length + 7U) / 8U);
|
|
const DecodedFrameValue decoded = DbcDecoder().Decode(database, raw);
|
|
QVERIFY(decoded.signals[0].valid);
|
|
QCOMPARE(std::get<std::uint64_t>(decoded.signals[0].rawValue), ReferenceExtract(data, signal.byteOrder, start, length));
|
|
}
|
|
}
|
|
|
|
void CoverageTest::invalidSignalStatus_data() {
|
|
QTest::addColumn<int>("order");
|
|
QTest::addColumn<unsigned>("start");
|
|
QTest::addColumn<unsigned>("length");
|
|
QTest::addColumn<unsigned>("bytes");
|
|
QTest::addColumn<int>("status");
|
|
for (int order = 0; order < 2; ++order) {
|
|
const unsigned start = order == 0 ? 0U : 7U;
|
|
const QByteArray prefix = QByteArray::number(order);
|
|
QTest::newRow((prefix + "-zero").constData()) << order << start << 0U << 1U << int(SignalDecodeStatus::InvalidDefinition);
|
|
QTest::newRow((prefix + "-65").constData()) << order << start << 65U << 9U << int(SignalDecodeStatus::InvalidDefinition);
|
|
QTest::newRow((prefix + "-empty").constData()) << order << start << 8U << 0U << int(SignalDecodeStatus::InsufficientPayload);
|
|
QTest::newRow((prefix + "-short").constData()) << order << start << 16U << 1U << int(SignalDecodeStatus::InsufficientPayload);
|
|
QTest::newRow((prefix + "-large-start").constData()) << order << std::numeric_limits<unsigned>::max() << 8U << 1U << int(SignalDecodeStatus::InsufficientPayload);
|
|
}
|
|
}
|
|
|
|
void CoverageTest::invalidSignalStatus() {
|
|
QFETCH(int, order);
|
|
QFETCH(unsigned, start);
|
|
QFETCH(unsigned, length);
|
|
QFETCH(unsigned, bytes);
|
|
QFETCH(int, status);
|
|
DecodeSignal signal;
|
|
signal.startBit = start;
|
|
signal.length = length;
|
|
signal.byteOrder = order == 0 ? ByteOrder::Intel : ByteOrder::Motorola;
|
|
const DecodeDatabase database = SingleSignal(signal);
|
|
RawCanFrame raw;
|
|
raw.canId = 17U;
|
|
raw.data.assign(bytes, 255U);
|
|
const DecodedFrameValue decoded = DbcDecoder().Decode(database, raw);
|
|
QVERIFY(decoded.valid);
|
|
QVERIFY(!decoded.signals[0].valid);
|
|
QCOMPARE(int(decoded.signals[0].status), status);
|
|
QVERIFY(decoded.signals[0].GetValueDescription() == nullptr);
|
|
}
|
|
|
|
void CoverageTest::malformedRuntimeMultiplexing_data() {
|
|
QTest::addColumn<int>("kind");
|
|
QTest::newRow("two-selectors") << 0;
|
|
QTest::newRow("signed-selector") << 1;
|
|
QTest::newRow("65-bit-selector") << 2;
|
|
QTest::newRow("branch-out-of-range") << 3;
|
|
}
|
|
|
|
void CoverageTest::malformedRuntimeMultiplexing() {
|
|
QFETCH(int, kind);
|
|
DecodeSignal selector;
|
|
selector.length = 1U;
|
|
selector.multiplexing.role = MultiplexRole::Selector;
|
|
DecodeDatabase database = SingleSignal(selector);
|
|
DecodeSignal branch;
|
|
branch.startBit = 8U;
|
|
branch.length = 8U;
|
|
branch.multiplexing.role = MultiplexRole::Branch;
|
|
branch.multiplexing.value = 1U;
|
|
database.frames[0].signals.push_back(branch);
|
|
DecodeSignal ordinary;
|
|
ordinary.startBit = 16U;
|
|
ordinary.length = 8U;
|
|
database.frames[0].signals.push_back(ordinary);
|
|
if (kind == 0) database.frames[0].signals.push_back(selector);
|
|
if (kind == 1) database.frames[0].signals[0].valueType = ValueType::Signed;
|
|
if (kind == 2) database.frames[0].signals[0].length = 65U;
|
|
if (kind == 3) database.frames[0].signals[1].multiplexing.value = 2U;
|
|
RawCanFrame raw;
|
|
raw.canId = 17U;
|
|
raw.data = {1U, 42U, 99U};
|
|
const DecodedFrameValue result = DbcDecoder().Decode(database, raw);
|
|
QCOMPARE(int(result.signals[1].status), int(SignalDecodeStatus::InvalidDefinition));
|
|
QVERIFY(!result.signals[1].valid);
|
|
QVERIFY(result.signals[2].valid);
|
|
QCOMPARE(std::get<std::uint64_t>(result.signals[2].rawValue), std::uint64_t(99));
|
|
if (kind != 3) {
|
|
QCOMPARE(int(result.signals[0].status), int(SignalDecodeStatus::InvalidDefinition));
|
|
if (kind == 0) QCOMPARE(int(result.signals[3].status), int(SignalDecodeStatus::InvalidDefinition));
|
|
}
|
|
}
|
|
|
|
void CoverageTest::pgn_data() {
|
|
QTest::addColumn<unsigned>("id");
|
|
QTest::addColumn<unsigned>("expected");
|
|
QTest::addColumn<bool>("hasPgn");
|
|
QTest::newRow("standard") << 0x7FFU << 0U << false;
|
|
for (unsigned dp: {0U, 1U})
|
|
for (unsigned pf: {239U, 240U})
|
|
for (unsigned ps: {0U, 0xABU}) {
|
|
const unsigned id = 0x80000000U | (dp << 24U) | (pf << 16U) | (ps << 8U) | 0x42U;
|
|
const unsigned expected = (dp << 16U) | (pf == 239U ? 0xEF00U : 0xF000U | ps);
|
|
const QByteArray name = QByteArray::number(dp) + "-" + QByteArray::number(pf) + "-" + QByteArray::number(ps);
|
|
QTest::newRow(name.constData()) << id << expected << true;
|
|
}
|
|
}
|
|
|
|
void CoverageTest::pgn() {
|
|
QFETCH(unsigned, id);
|
|
QFETCH(unsigned, expected);
|
|
QFETCH(bool, hasPgn);
|
|
const DbcDatabase parsed = ParseText("BO_ " + std::to_string(id) + " Frame: 8 ECU\n");
|
|
QCOMPARE(parsed.frames[0].hasPgn, hasPgn);
|
|
QCOMPARE(parsed.frames[0].pgn, expected);
|
|
}
|
|
|
|
void CoverageTest::comments_data() {
|
|
QTest::addColumn<QString>("before");
|
|
QTest::addColumn<QString>("after");
|
|
QTest::addColumn<QString>("frameComment");
|
|
QTest::addColumn<QString>("signalComment");
|
|
QTest::newRow("tabs") << QString::fromUtf8("") << QString::fromUtf8("CM_\tBO_ 1 \"Frame\";\nCM_\tSG_ 1 State \"Signal\";\n") << QString::fromUtf8("Frame") << QString::fromUtf8("Signal");
|
|
QTest::newRow("empty-overwrite") << QString::fromUtf8("") << QString::fromUtf8("CM_ BO_ 1 \"Old\";\nCM_ BO_ 1 \"\";\nCM_ SG_ 1 State \"Old\";\nCM_ SG_ 1 State \"\";\n") << QString::fromUtf8("") << QString::fromUtf8("");
|
|
QTest::newRow("last-wins") << QString::fromUtf8("") << QString::fromUtf8("CM_ BO_ 1 \"First\";\nCM_ BO_ 1 \"Last\";\nCM_ SG_ 1 State \"First\";\nCM_ SG_ 1 State \"Last\";\n") << QString::fromUtf8("Last") << QString::fromUtf8("Last");
|
|
QTest::newRow("missing-targets") << QString::fromUtf8("") << QString::fromUtf8("CM_ BO_ 2 \"Unknown\";\nCM_ SG_ 2 State \"Unknown\";\nCM_ SG_ 1 Missing \"Unknown\";\n") << QString::fromUtf8("") << QString::fromUtf8("");
|
|
// Current documented limitation: comments resolve only already parsed targets.
|
|
QTest::newRow("before-definitions") << QString::fromUtf8("CM_ BO_ 1 \"Early\";\nCM_ SG_ 1 State \"Early\";\n") << QString::fromUtf8("") << QString::fromUtf8("") << QString::fromUtf8("");
|
|
QTest::newRow("missing-quotes") << QString::fromUtf8("") << QString::fromUtf8("CM_ BO_ 1 Missing;\nCM_ SG_ 1 State \"Unclosed;\n") << QString::fromUtf8("") << QString::fromUtf8("");
|
|
}
|
|
|
|
void CoverageTest::comments() {
|
|
QFETCH(QString, before);
|
|
QFETCH(QString, after);
|
|
QFETCH(QString, frameComment);
|
|
QFETCH(QString, signalComment);
|
|
const DbcDatabase parsed = ParseText(before.toStdString() + "BO_ 1 Frame: 1 ECU\nSG_ State : 0|8@1+ (1,0) [0|255] \"\" ECU\n" + after.toStdString());
|
|
QCOMPARE(parsed.frames[0].comment, frameComment.toStdString());
|
|
QCOMPARE(parsed.frames[0].signals[0].comment, signalComment.toStdString());
|
|
}
|
|
|
|
void CoverageTest::ignoredCommentStatements_data() {
|
|
QTest::addColumn<QString>("statement");
|
|
QTest::newRow("empty") << QString();
|
|
QTest::newRow("wrong-keyword") << QStringLiteral("OTHER_ BO_ 1 \"Ignored\";");
|
|
QTest::newRow("keyword-prefix") << QStringLiteral("CM_OTHER BO_ 1 \"Ignored\";");
|
|
QTest::newRow("frame-without-cm") << QStringLiteral("BO_ 1 \"Ignored\";");
|
|
QTest::newRow("missing-subtype") << QStringLiteral("CM_");
|
|
QTest::newRow("global-comment") << QStringLiteral("CM_ \"Ignored\";");
|
|
QTest::newRow("node-comment") << QStringLiteral("CM_ BU_ ECU \"Ignored\";");
|
|
QTest::newRow("environment-comment") << QStringLiteral("CM_ EV_ State \"Ignored\";");
|
|
QTest::newRow("unknown-subtype") << QStringLiteral("CM_ OTHER_ 1 \"Ignored\";");
|
|
}
|
|
|
|
void CoverageTest::ignoredCommentStatements() {
|
|
QFETCH(QString, statement);
|
|
DbcDatabase database;
|
|
FrameInfo frame;
|
|
frame.canId = 1U;
|
|
frame.name = "Frame";
|
|
frame.comment = "Existing frame comment";
|
|
SignalInfo signal;
|
|
signal.name = "State";
|
|
signal.comment = "Existing signal comment";
|
|
frame.signals.push_back(signal);
|
|
database.frames.push_back(frame);
|
|
DbcParseContext context{database, &database.frames[0]};
|
|
|
|
// The handler is public: callers may bypass keyword dispatch. Ignored input
|
|
// must preserve existing metadata, including comments already assigned.
|
|
DbcCommentHandler().Handle(statement.toStdString(), context);
|
|
QCOMPARE(database.frames.size(), std::size_t(1));
|
|
QCOMPARE(database.frames[0].canId, 1U);
|
|
QCOMPARE(database.frames[0].name, frame.name);
|
|
QCOMPARE(database.frames[0].comment, frame.comment);
|
|
QCOMPARE(database.frames[0].signals.size(), std::size_t(1));
|
|
QCOMPARE(database.frames[0].signals[0].name, signal.name);
|
|
QCOMPARE(database.frames[0].signals[0].comment, signal.comment);
|
|
QCOMPARE(context.currentFrame, &database.frames[0]);
|
|
}
|
|
|
|
void CoverageTest::ambiguousValueReferences_data() {
|
|
QTest::addColumn<bool>("duplicateFrames");
|
|
QTest::addColumn<bool>("extended");
|
|
QTest::newRow("duplicate-standard-frames") << true << false;
|
|
QTest::newRow("duplicate-extended-frames") << true << true;
|
|
QTest::newRow("duplicate-signals-in-standard-frame") << false << false;
|
|
QTest::newRow("duplicate-signals-in-extended-frame") << false << true;
|
|
}
|
|
|
|
void CoverageTest::ambiguousValueReferences() {
|
|
QFETCH(bool, duplicateFrames);
|
|
QFETCH(bool, extended);
|
|
DbcDatabase database;
|
|
FrameInfo frame;
|
|
frame.canId = 1U;
|
|
frame.name = "Frame";
|
|
frame.isExtended = extended;
|
|
SignalInfo signal;
|
|
signal.name = "State";
|
|
frame.signals.push_back(signal);
|
|
if (!duplicateFrames) frame.signals.push_back(signal);
|
|
database.frames.push_back(frame);
|
|
if (duplicateFrames) database.frames.push_back(frame);
|
|
|
|
// ParseFile rejects duplicate definitions earlier. Construct the database
|
|
// through the public metadata API to exercise the resolver's own checks.
|
|
DbcParseContext context{database, nullptr};
|
|
context.lineNumber = 37U;
|
|
const std::string rawId = extended ? "2147483649" : "1";
|
|
DbcValueDescriptionHandler().Handle("VAL_ " + rawId + " State 0 \"Off\";", context);
|
|
QCOMPARE(context.pendingValues.size(), std::size_t(1));
|
|
QCOMPARE(context.pendingValues[0].lineNumber, std::size_t(37));
|
|
const std::string path = "manual database.dbc";
|
|
try {
|
|
context.ResolveValueDescriptions(path);
|
|
QFAIL("The resolver must reject an ambiguous target");
|
|
} catch (const std::runtime_error &error) {
|
|
const std::string message = duplicateFrames ? "Ambiguous VAL_ frame reference."
|
|
: "Ambiguous VAL_ signal reference: State";
|
|
QCOMPARE(std::string(error.what()), path + ":37: " + message);
|
|
}
|
|
// Ambiguity must be detected before applying any descriptions.
|
|
for (const FrameInfo &candidate: database.frames)
|
|
for (const SignalInfo &candidateSignal: candidate.signals)
|
|
QVERIFY(candidateSignal.valueDescriptions.empty());
|
|
}
|
|
|
|
void CoverageTest::valueReferenceScope() {
|
|
DbcDatabase database;
|
|
FrameInfo frame;
|
|
frame.canId = 1U;
|
|
SignalInfo signal;
|
|
signal.name = "State";
|
|
frame.signals.push_back(signal);
|
|
database.frames.push_back(frame);
|
|
frame.isExtended = true;
|
|
database.frames.push_back(frame);
|
|
frame.canId = 2U;
|
|
frame.isExtended = false;
|
|
database.frames.push_back(frame);
|
|
|
|
DbcParseContext context{database, nullptr};
|
|
const DbcValueDescriptionHandler handler;
|
|
context.lineNumber = 3U;
|
|
handler.Handle("VAL_ 1 State 0 \"Standard\";", context);
|
|
context.lineNumber = 7U;
|
|
handler.Handle("VAL_ 2147483649 State 0 \"Extended\";", context);
|
|
context.ResolveValueDescriptions("manual database.dbc");
|
|
|
|
const std::string *standard = FindValueDescription(database.frames[0].signals[0].valueDescriptions, std::uint64_t(0));
|
|
const std::string *extended = FindValueDescription(database.frames[1].signals[0].valueDescriptions, std::uint64_t(0));
|
|
QVERIFY(standard != nullptr);
|
|
QVERIFY(extended != nullptr);
|
|
QCOMPARE(*standard, std::string("Standard"));
|
|
QCOMPARE(*extended, std::string("Extended"));
|
|
QVERIFY(database.frames[2].signals[0].valueDescriptions.empty());
|
|
}
|
|
|
|
void CoverageTest::fileFormatting_data() {
|
|
QTest::addColumn<QString>("text");
|
|
QTest::addColumn<unsigned>("frames");
|
|
QTest::newRow("empty") << QString::fromUtf8("") << 0U;
|
|
QTest::newRow("whitespace") << QString::fromUtf8(" \t\r\n\n\t") << 0U;
|
|
QTest::newRow("unknown-only") << QString::fromUtf8("VERSION \"test\"\nBU_: ECU\nUNKNOWN_ data\n") << 0U;
|
|
QTest::newRow("no-final-newline") << QString::fromUtf8("BO_ 1 Frame: 1 ECU\nSG_ State : 0|8@1+ (1,0) [0|255] \"\" ECU") << 1U;
|
|
QTest::newRow("crlf") << QString::fromUtf8("\r\nBO_ 1 Frame: 1 ECU\r\nSG_ State : 0|8@1+ (1,0) [0|255] \"\" ECU\r\nCM_ BO_ 1 \"Comment\";\r\nVAL_ 1 State 0 \"Off\";\r\n") << 1U;
|
|
}
|
|
|
|
void CoverageTest::fileFormatting() {
|
|
QFETCH(QString, text);
|
|
QFETCH(unsigned, frames);
|
|
const DbcDatabase parsed = ParseText(text.toStdString());
|
|
QCOMPARE(parsed.frames.size(), std::size_t(frames));
|
|
if (frames) {
|
|
QCOMPARE(parsed.frames[0].signals.size(), std::size_t(1));
|
|
if (text.contains("\r\nBO_")) {
|
|
QCOMPARE(parsed.frames[0].comment, std::string("Comment"));
|
|
const std::string *description = FindValueDescription(parsed.frames[0].signals[0].valueDescriptions, std::uint64_t(0));
|
|
QVERIFY(description != nullptr);
|
|
QCOMPARE(*description, std::string("Off"));
|
|
}
|
|
}
|
|
}
|
|
|
|
void CoverageTest::missingFile() {
|
|
QTemporaryFile file;
|
|
QVERIFY(file.open());
|
|
const QString path = file.fileName();
|
|
QVERIFY(file.remove());
|
|
try {
|
|
DbcParser().ParseFile(path.toStdString());
|
|
QFAIL("Missing file must throw");
|
|
} catch (const std::runtime_error &error) {
|
|
QCOMPARE(std::string(error.what()), "Failed to open DBC file: " + path.toStdString());
|
|
}
|
|
}
|
|
|
|
void CoverageTest::builders() {
|
|
const DecodeDatabase emptyRuntime = DbcDecodeBuilder().Build(DbcDatabase());
|
|
QVERIFY(emptyRuntime.frames.empty());
|
|
QVERIFY(emptyRuntime.frameIndexByKey.empty());
|
|
const std::unique_ptr<TreeNode> emptyTree = DbcTreeBuilder().Build(DbcDatabase());
|
|
QCOMPARE(emptyTree->GetType(), NodeType::Root);
|
|
QCOMPARE(emptyTree->GetName(), std::string("dbc"));
|
|
QCOMPARE(emptyTree->GetChildCount(), std::size_t(0));
|
|
QVERIFY(emptyTree->GetFrame() == nullptr && emptyTree->GetSignal() == nullptr);
|
|
const DbcDatabase expected = ParseText(
|
|
"BO_ 256 Standard: 3 ECU1\n"
|
|
"SG_ Mode M : 0|8@1+ (2,3) [0|513] \"mode\" ECU1,ECU2\n"
|
|
"SG_ Branch m1 : 15|8@0- (-2,5) [-249|261] \"rpm\" ECU2,ECU3\n"
|
|
"SG_ Always : 16|8@1+ (0.25,-1) [-1|62.75] \"V\" ECU3\n"
|
|
"CM_ BO_ 256 \"Frame comment\";\nCM_ SG_ 256 Branch \"Branch comment\";\n"
|
|
"VAL_ 256 Branch -1 \"Negative\" 1 \"Positive\";\n"
|
|
"BO_ 2163212866 Extended: 8 ECU4\n");
|
|
DecodeDatabase runtime;
|
|
std::unique_ptr<TreeNode> tree;
|
|
{
|
|
DbcDatabase source = expected;
|
|
runtime = DbcDecodeBuilder().Build(source);
|
|
tree = DbcTreeBuilder().Build(source);
|
|
source.frames[0].name = "Changed";
|
|
source.frames[0].signals[1].receivers.clear();
|
|
source.frames[0].signals[1].valueDescriptions.clear();
|
|
source.frames.clear();
|
|
}
|
|
QCOMPARE(runtime.frames.size(), expected.frames.size());
|
|
QCOMPARE(tree->GetChildCount(), expected.frames.size());
|
|
QCOMPARE(runtime.frameIndexByKey.size(), expected.frames.size());
|
|
for (std::size_t i = 0; i < expected.frames.size(); ++i) {
|
|
const FrameInfo &frame = expected.frames[i];
|
|
const DecodeFrame &decodedFrame = runtime.frames[i];
|
|
const TreeNode *node = tree->GetChild(i);
|
|
QVERIFY(node && node->GetFrame());
|
|
QCOMPARE(node->GetType(), NodeType::Frame);
|
|
QCOMPARE(node->GetName(), frame.name);
|
|
QVERIFY(node->GetSignal() == nullptr);
|
|
QCOMPARE(runtime.frameIndexByKey.at(FrameKey(frame.canId, frame.isExtended)), i);
|
|
const FrameInfo &copied = *node->GetFrame();
|
|
QCOMPARE(copied.name, frame.name);
|
|
QCOMPARE(decodedFrame.name, frame.name);
|
|
QCOMPARE(copied.canId, frame.canId);
|
|
QCOMPARE(decodedFrame.canId, frame.canId);
|
|
QCOMPARE(copied.isExtended, frame.isExtended);
|
|
QCOMPARE(decodedFrame.isExtended, frame.isExtended);
|
|
QCOMPARE(copied.dlc, frame.dlc);
|
|
QCOMPARE(decodedFrame.dlc, frame.dlc);
|
|
QCOMPARE(copied.pgn, frame.pgn);
|
|
QCOMPARE(decodedFrame.pgn, frame.pgn);
|
|
QCOMPARE(copied.hasPgn, frame.hasPgn);
|
|
QCOMPARE(decodedFrame.hasPgn, frame.hasPgn);
|
|
QCOMPARE(copied.transmitter, frame.transmitter);
|
|
QCOMPARE(decodedFrame.transmitter, frame.transmitter);
|
|
QCOMPARE(copied.comment, frame.comment);
|
|
QCOMPARE(decodedFrame.comment, frame.comment);
|
|
QCOMPARE(node->GetChildCount(), frame.signals.size());
|
|
QCOMPARE(copied.signals.size(), frame.signals.size());
|
|
QCOMPARE(decodedFrame.signals.size(), frame.signals.size());
|
|
for (std::size_t j = 0; j < frame.signals.size(); ++j) {
|
|
const SignalInfo &original = frame.signals[j];
|
|
const DecodeSignal &target = decodedFrame.signals[j];
|
|
const TreeNode *leaf = node->GetChild(j);
|
|
QVERIFY(leaf && leaf->GetSignal());
|
|
QCOMPARE(leaf->GetType(), NodeType::Signal);
|
|
QCOMPARE(leaf->GetName(), original.name);
|
|
QVERIFY(leaf->GetFrame() == nullptr);
|
|
// Check both the frame payload copy and each child payload copy.
|
|
for (const SignalInfo *copy: {&copied.signals[j], leaf->GetSignal()}) {
|
|
QCOMPARE(copy->name, original.name);
|
|
QCOMPARE(copy->startBit, original.startBit);
|
|
QCOMPARE(copy->length, original.length);
|
|
QCOMPARE(copy->isLittleEndian, original.isLittleEndian);
|
|
QCOMPARE(copy->isSigned, original.isSigned);
|
|
QCOMPARE(copy->factor, original.factor);
|
|
QCOMPARE(copy->offset, original.offset);
|
|
QCOMPARE(copy->minimum, original.minimum);
|
|
QCOMPARE(copy->maximum, original.maximum);
|
|
QCOMPARE(copy->unit, original.unit);
|
|
QCOMPARE(copy->comment, original.comment);
|
|
QVERIFY(copy->receivers == original.receivers);
|
|
QVERIFY(copy->valueDescriptions == original.valueDescriptions);
|
|
QCOMPARE(copy->multiplexing.role, original.multiplexing.role);
|
|
QCOMPARE(copy->multiplexing.value, original.multiplexing.value);
|
|
}
|
|
QCOMPARE(target.name, original.name);
|
|
QCOMPARE(target.startBit, original.startBit);
|
|
QCOMPARE(target.length, original.length);
|
|
QCOMPARE(target.byteOrder, original.isLittleEndian ? ByteOrder::Intel : ByteOrder::Motorola);
|
|
QCOMPARE(target.valueType, original.isSigned ? ValueType::Signed : ValueType::Unsigned);
|
|
QCOMPARE(target.factor, original.factor);
|
|
QCOMPARE(target.offset, original.offset);
|
|
QCOMPARE(target.minimum, original.minimum);
|
|
QCOMPARE(target.maximum, original.maximum);
|
|
QCOMPARE(target.unit, original.unit);
|
|
QCOMPARE(target.comment, original.comment);
|
|
QVERIFY(target.receivers == original.receivers);
|
|
QVERIFY(target.valueDescriptions == original.valueDescriptions);
|
|
QCOMPARE(target.multiplexing.role, original.multiplexing.role);
|
|
QCOMPARE(target.multiplexing.value, original.multiplexing.value);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CoverageTest::physicalConversion_data() {
|
|
QTest::addColumn<double>("factor");
|
|
QTest::addColumn<double>("offset");
|
|
QTest::addColumn<double>("expected");
|
|
QTest::newRow("zero-factor") << 0.0 << 7.0 << 7.0;
|
|
QTest::newRow("negative-factor") << -2.0 << 5.0 << -15.0;
|
|
QTest::newRow("above-declared-range") << 2.0 << 3.0 << 23.0;
|
|
}
|
|
|
|
void CoverageTest::physicalConversion() {
|
|
QFETCH(double, factor);
|
|
QFETCH(double, offset);
|
|
QFETCH(double, expected);
|
|
DecodeSignal signal;
|
|
signal.length = 8U;
|
|
signal.factor = factor;
|
|
signal.offset = offset;
|
|
signal.minimum = 0.0;
|
|
signal.maximum = 1.0;
|
|
const DecodeDatabase database = SingleSignal(signal);
|
|
RawCanFrame raw;
|
|
raw.canId = 17U;
|
|
raw.data = {10U};
|
|
const DecodedFrameValue decoded = DbcDecoder().Decode(database, raw);
|
|
QVERIFY(decoded.signals[0].valid);
|
|
QCOMPARE(std::get<std::uint64_t>(decoded.signals[0].rawValue), std::uint64_t(10));
|
|
QCOMPARE(decoded.signals[0].physicalValue, expected);
|
|
}
|
|
|
|
void CoverageTest::multiplexingScenarios() {
|
|
const DecodeDatabase selectorOnly = DbcDecodeBuilder().Build(ParseText(
|
|
"BO_ 1 Frame: 1 ECU\nSG_ Mode M : 0|1@1+ (1,0) [0|1] \"\" ECU\n"));
|
|
RawCanFrame raw;
|
|
raw.canId = 1U;
|
|
raw.data = {1U};
|
|
const DecodedFrameValue only = DbcDecoder().Decode(selectorOnly, raw);
|
|
QCOMPARE(only.signals.size(), std::size_t(1));
|
|
QVERIFY(only.signals[0].valid);
|
|
const DecodeDatabase database = DbcDecodeBuilder().Build(ParseText(
|
|
"BO_ 1 First: 3 ECU\n"
|
|
"SG_ Mode M : 0|1@1+ (1,0) [0|1] \"\" ECU\n"
|
|
"SG_ A m0 : 8|8@1+ (1,0) [0|255] \"\" ECU\n"
|
|
"SG_ B m0 : 16|8@1+ (1,0) [0|255] \"\" ECU\n"
|
|
"BO_ 2 Second: 2 ECU\n"
|
|
"SG_ Mode M : 0|1@1+ (1,0) [0|1] \"\" ECU\n"
|
|
"SG_ A m1 : 8|8@1+ (1,0) [0|255] \"\" ECU\n"));
|
|
raw.data = {0U, 42U, 99U};
|
|
const DecodedFrameValue first = DbcDecoder().Decode(database, raw);
|
|
QVERIFY(first.signals[1].valid && first.signals[2].valid);
|
|
QCOMPARE(std::get<std::uint64_t>(first.signals[1].rawValue), std::uint64_t(42));
|
|
QCOMPARE(std::get<std::uint64_t>(first.signals[2].rawValue), std::uint64_t(99));
|
|
raw.canId = 2U;
|
|
const DecodedFrameValue secondInactive = DbcDecoder().Decode(database, raw);
|
|
QCOMPARE(secondInactive.signals[1].status, SignalDecodeStatus::Inactive);
|
|
raw.data[0] = 1U;
|
|
QVERIFY(DbcDecoder().Decode(database, raw).signals[1].valid);
|
|
raw.canId = 1U;
|
|
const DecodedFrameValue firstInactive = DbcDecoder().Decode(database, raw);
|
|
QCOMPARE(firstInactive.signals[1].status, SignalDecodeStatus::Inactive);
|
|
QCOMPARE(firstInactive.signals[2].status, SignalDecodeStatus::Inactive);
|
|
}
|
|
|
|
void CoverageTest::multiplexingErrorLocations_data() {
|
|
QTest::addColumn<QString>("text");
|
|
QTest::addColumn<unsigned>("line");
|
|
QTest::addColumn<QString>("message");
|
|
const QString header = "BO_ 1 Frame: 3 ECU\n\n";
|
|
const QString selector = "SG_ Mode M : 0|1@1+ (1,0) [0|1] \"\" ECU\n";
|
|
const QString branch = "SG_ A m1 : 8|8@1+ (1,0) [0|255] \"\" ECU\n";
|
|
QTest::newRow("missing-selector") << header + branch << 3U << QString::fromUtf8("Multiplexed branch has no selector.");
|
|
QTest::newRow("second-selector") << header + selector + "\nSG_ Other M : 16|1@1+ (1,0) [0|1] \"\" ECU\n"
|
|
<< 5U << QString::fromUtf8("Multiplexing requires one unsigned selector per frame.");
|
|
QTest::newRow("signed-selector") << header + "SG_ Mode M : 0|1@1- (1,0) [-1|0] \"\" ECU\n"
|
|
<< 3U << QString::fromUtf8("Multiplexing requires one unsigned selector per frame.");
|
|
QTest::newRow("branch-range") << header + "SG_ A m2 : 8|8@1+ (1,0) [0|255] \"\" ECU\n" + selector << 3U << QString::fromUtf8("Branch value exceeds selector raw range.");
|
|
QTest::newRow("same-branch-overlap") << header + selector + branch + "SG_ B m1 : 15|8@0+ (1,0) [0|255] \"\" ECU\n"
|
|
<< 5U << QString::fromUtf8("Signals overlap without mutually exclusive multiplexing conditions.");
|
|
QTest::newRow("selector-overlap") << header + selector + "SG_ A m1 : 0|1@1+ (1,0) [0|1] \"\" ECU\n"
|
|
<< 4U << QString::fromUtf8("Signals overlap without mutually exclusive multiplexing conditions.");
|
|
QTest::newRow("unconditional-overlap") << header + selector + branch + "SG_ Always : 8|8@1+ (1,0) [0|255] \"\" ECU\n"
|
|
<< 5U << QString::fromUtf8("Signals overlap without mutually exclusive multiplexing conditions.");
|
|
QTest::newRow("ordinary-overlap") << header + "SG_ A : 8|8@1+ (1,0) [0|255] \"\" ECU\nSG_ B : 15|8@0+ (1,0) [0|255] \"\" ECU\n"
|
|
<< 4U << QString::fromUtf8("Signals overlap without mutually exclusive multiplexing conditions.");
|
|
QTest::newRow("extended-multiplexing") << header + "SG_MUL_VAL_ 1 A Mode 1-2;\n"
|
|
<< 3U << QString::fromUtf8("Extended and nested multiplexing are unsupported.");
|
|
QTest::newRow("nested-marker") << header + "SG_ A m1M : 8|8@1+ (1,0) [0|255] \"\" ECU\n"
|
|
<< 3U << QString::fromUtf8("Invalid or unsupported multiplexing marker.");
|
|
}
|
|
|
|
void CoverageTest::multiplexingErrorLocations() {
|
|
QFETCH(QString, text);
|
|
QFETCH(unsigned, line);
|
|
QFETCH(QString, message);
|
|
QTemporaryFile file;
|
|
QVERIFY(file.open());
|
|
const QByteArray content = text.toUtf8();
|
|
QCOMPARE(file.write(content), qint64(content.size()));
|
|
QVERIFY(file.flush());
|
|
try {
|
|
DbcParser().ParseFile(file.fileName().toStdString());
|
|
QFAIL("Expected multiplexing rejection");
|
|
} catch (const std::runtime_error &error) {
|
|
QCOMPARE(QString::fromStdString(error.what()), file.fileName() + ":" + QString::number(line) + ": " + message);
|
|
}
|
|
}
|
|
|
|
void CoverageTest::escapedValueDescriptions() {
|
|
const DbcDatabase parsed = ParseText(
|
|
"BO_ 1 Frame: 1 ECU\nSG_ State : 0|8@1+ (1,0) [0|255] \"\" ECU\n"
|
|
R"dbc(VAL_ 1 State -0 "Ноль" 1 "C:\\trace" 2 "\\\"quoted\"\\";)dbc"
|
|
"\n");
|
|
const ValueDescriptions &values = parsed.frames[0].signals[0].valueDescriptions;
|
|
QCOMPARE(values.size(), std::size_t(3));
|
|
QVERIFY(values.find(RawSignalValue(std::int64_t(0))) == values.end());
|
|
const std::string *zero = FindValueDescription(values, std::int64_t(0));
|
|
const std::string *path = FindValueDescription(values, std::uint64_t(1));
|
|
const std::string *quoted = FindValueDescription(values, std::uint64_t(2));
|
|
QVERIFY(zero && path && quoted);
|
|
QCOMPARE(*zero, std::string("Ноль"));
|
|
QCOMPARE(*path, std::string("C:\\trace"));
|
|
QCOMPARE(*quoted, std::string("\\\"quoted\"\\"));
|
|
QVERIFY(NormalizeValueKey(RawSignalValue(std::int64_t(0))) == RawSignalValue(std::uint64_t(0)));
|
|
QVERIFY(NormalizeValueKey(RawSignalValue(std::int64_t(-1))) == RawSignalValue(std::int64_t(-1)));
|
|
}
|
|
|
|
void CoverageTest::factory() {
|
|
TypeFactory<std::string, FactoryBase, std::unique_ptr<int>> factory;
|
|
QVERIFY(!factory.contains("value"));
|
|
QVERIFY_THROWS_EXCEPTION(std::out_of_range, static_cast<void>(factory.creator("missing")));
|
|
QVERIFY_THROWS_EXCEPTION(std::out_of_range, static_cast<void>(factory.create("missing", std::make_unique<int>(1))));
|
|
factory.registerType<FactoryFirst>("value");
|
|
QVERIFY(factory.contains("value"));
|
|
std::unique_ptr<int> argument = std::make_unique<int>(42);
|
|
const std::shared_ptr<FactoryBase> first = factory.create("value", std::move(argument));
|
|
QVERIFY(argument == nullptr);
|
|
QCOMPARE(first->Value(), 42);
|
|
const TypeFactory<std::string, FactoryBase, std::unique_ptr<int>>::CreatorFn creator = factory.creator("value");
|
|
QCOMPARE(creator(std::make_unique<int>(7))->Value(), 7);
|
|
factory.registerType<FactorySecond>("value");
|
|
QCOMPARE(factory.create("value", std::make_unique<int>(42))->Value(), 43);
|
|
QCOMPARE(first->Value(), 42);
|
|
QCOMPARE(creator(std::make_unique<int>(7))->Value(), 7);
|
|
}
|
|
|
|
int RunCoverageTests(int argc, char **argv) {
|
|
CoverageTest tests;
|
|
return QTest::qExec(&tests, argc, argv);
|
|
}
|
|
#include "dbc_coverage_test.moc"
|