Files
dbc/tests/dbc_tree_model_test.cpp
T
2026-10-07 19:43:17 -04:00

208 lines
11 KiB
C++

#include "dbc_parser.h"
#include "dbc_tree_builder.h"
#include "dbc_tree_model.h"
#include <QAbstractItemModelTester>
#include <QSignalSpy>
#include <QTemporaryFile>
#include <QStringList>
#include <QtTest>
#include <limits>
static std::unique_ptr<TreeNode> ParsedTree() {
QTemporaryFile fixture;
if (!fixture.open()) throw std::runtime_error("Fixture creation failed");
fixture.write("BO_ 256 Status: 8 ECU\n SG_ State : 0|8@1+ (1,0) [0|255] \"rpm\" ECU\n"
" SG_ Other : 8|8@1+ (1,0) [0|255] \"\" ECU\n"
"BO_ 257 Second: 8 ECU\n SG_ Value : 0|8@1+ (1,0) [0|255] \"\" ECU\n"
"CM_ BO_ 256 \"Frame comment\";\nCM_ SG_ 256 State \"Signal comment\";\n"
"VAL_ 256 State -1 \"Negative\" 0 \"Off\" 18446744073709551615 \"Maximum\";\n");
fixture.flush();
return DbcTreeBuilder().Build(DbcParser().ParseFile(fixture.fileName().toStdString()));
}
class ModelTest : public QObject {
Q_OBJECT
private Q_SLOTS:
void additionalMetadata() {
DbcDatabase database;
FrameInfo frame;
frame.name = "Extended";
frame.canId = 0x18F00442U;
frame.isExtended = true;
frame.dlc = 8U;
frame.comment = "Frame tooltip";
SignalInfo signal;
signal.name = "SignedMotorola";
signal.startBit = 7U;
signal.length = 16U;
signal.isLittleEndian = false;
signal.isSigned = true;
signal.unit = "rpm";
signal.comment = "Signal tooltip";
frame.signals.push_back(signal);
database.frames.push_back(frame);
DbcTreeModel model;
model.SetTree(DbcTreeBuilder().Build(database));
const QModelIndex parent = model.index(0, 0);
QCOMPARE(model.data(model.index(0, DbcTreeModel::ByteOrder, parent)).toString(), QString("Motorola"));
QCOMPARE(model.data(model.index(0, DbcTreeModel::Signed, parent)).toString(), QString("Signed"));
QCOMPARE(model.data(model.index(0, DbcTreeModel::CanId)).toString(), QString("0X18F00442"));
const QStringList headers = {"Name", "CAN ID", "Payload (bytes)", "Start bit",
"Length (bits)", "Byte order", "Signedness", "Unit", "Comment"};
for (int column = 0; column < model.columnCount(); ++column) {
const auto frameIndex = model.index(0, column);
const auto signalIndex = model.index(0, column, parent);
QCOMPARE(model.headerData(column, Qt::Horizontal).toString(), headers.at(column));
QVERIFY(!model.headerData(column, Qt::Horizontal, Qt::ToolTipRole).isValid());
QCOMPARE(model.data(frameIndex, Qt::ToolTipRole).toString(), QString("Frame tooltip"));
QCOMPARE(model.data(signalIndex, Qt::ToolTipRole).toString(), QString("Signal tooltip"));
QCOMPARE(model.data(frameIndex, DbcTreeModel::CommentRole).toString(), QString("Frame tooltip"));
QCOMPARE(model.data(signalIndex, DbcTreeModel::CommentRole).toString(), QString("Signal tooltip"));
QCOMPARE(model.data(frameIndex, DbcTreeModel::CanIdRole).toUInt(), frame.canId);
QCOMPARE(model.data(frameIndex, DbcTreeModel::PayloadLengthRole).toInt(), 8);
QCOMPARE(model.data(frameIndex, DbcTreeModel::ExtendedRole).toBool(), true);
QCOMPARE(model.data(signalIndex, DbcTreeModel::StartBitRole).toUInt(), 7U);
QCOMPARE(model.data(signalIndex, DbcTreeModel::BitLengthRole).toUInt(), 16U);
QCOMPARE(model.data(signalIndex, DbcTreeModel::LittleEndianRole).toBool(), false);
QCOMPARE(model.data(signalIndex, DbcTreeModel::SignedRole).toBool(), true);
QCOMPARE(model.data(signalIndex, DbcTreeModel::UnitRole).toString(), QString("rpm"));
const auto descriptions = model.data(signalIndex, DbcTreeModel::ValueDescriptionsRole);
QVERIFY(descriptions.isValid());
QVERIFY(descriptions.toList().isEmpty());
QVERIFY(!model.data(frameIndex, Qt::UserRole + 1000).isValid());
QVERIFY(!model.data(signalIndex, Qt::UserRole + 1000).isValid());
QVERIFY(!model.setData(frameIndex, "Changed", Qt::EditRole));
QVERIFY(!model.setData(signalIndex, "Changed", Qt::EditRole));
}
QCOMPARE(model.data(parent).toString(), QString("Extended"));
QCOMPARE(model.data(model.index(0, 0, parent)).toString(), QString("SignedMotorola"));
}
void emptyRootAndRejectedReplacement() {
DbcTreeModel model;
QAbstractItemModelTester tester(&model, QAbstractItemModelTester::FailureReportingMode::QtTest);
model.SetTree(ParsedTree());
QPersistentModelIndex retained(model.index(0, 0));
QSignalSpy resets(&model, &QAbstractItemModel::modelReset);
QVERIFY_THROWS_EXCEPTION(std::invalid_argument, model.SetTree(std::make_unique<TreeNode>(SignalInfo())));
QCOMPARE(resets.count(), 0);
QVERIFY(retained.isValid());
QCOMPARE(model.rowCount(), 2);
model.SetTree(DbcTreeBuilder().Build(DbcDatabase()));
QCOMPARE(resets.count(), 1);
QVERIFY(!retained.isValid());
QCOMPARE(model.rowCount(), 0);
QVERIFY(!model.index(0, 0).isValid());
}
void navigation() {
DbcTreeModel model;
QAbstractItemModelTester tester(&model, QAbstractItemModelTester::FailureReportingMode::QtTest);
QCOMPARE(model.rowCount(), 0);
model.SetTree(ParsedTree());
QCOMPARE(model.rowCount(), 2);
for (int row = 0; row < model.rowCount(); ++row) {
const QModelIndex frame = model.index(row, 0);
for (int column = 0; column < model.columnCount(); ++column) {
const QModelIndex frameColumn = model.index(row, column);
QVERIFY(!model.parent(frameColumn).isValid());
QCOMPARE(model.rowCount(frameColumn), column == 0 ? (row == 0 ? 2 : 1) : 0);
for (int signalRow = 0; signalRow < model.rowCount(frame); ++signalRow) {
const QModelIndex signal = model.index(signalRow, column, frame);
QCOMPARE(model.parent(signal), frame);
QCOMPARE(model.rowCount(signal), 0);
QVERIFY(!model.index(0, 0, signal).isValid());
QCOMPARE(model.flags(signal), Qt::ItemIsEnabled | Qt::ItemIsSelectable);
}
}
}
QVERIFY(!model.index(-1, 0).isValid());
QVERIFY(!model.index(2, 0).isValid());
QVERIFY(!model.index(0, -1).isValid());
QVERIFY(!model.index(0, model.columnCount()).isValid());
QVERIFY(!model.index(0, 0, model.index(0, 1)).isValid());
QVERIFY(!model.parent({}).isValid());
QVERIFY(!model.data({}).isValid());
QCOMPARE(model.flags({}), Qt::NoItemFlags);
DbcTreeModel other;
other.SetTree(ParsedTree());
const QModelIndex foreign = other.index(0, 0);
QVERIFY(!model.index(0, 0, foreign).isValid());
QVERIFY(!model.parent(foreign).isValid());
QCOMPARE(model.rowCount(foreign), 0);
QCOMPARE(model.columnCount(foreign), 0);
QVERIFY(!model.data(foreign).isValid());
QCOMPARE(model.flags(foreign), Qt::NoItemFlags);
}
void metadata() {
DbcTreeModel model;
model.SetTree(ParsedTree());
const QModelIndex frame = model.index(0, 0);
const QModelIndex signal = model.index(0, 0, frame);
QCOMPARE(model.data(frame).toString(), QString("Status"));
QCOMPARE(model.data(model.index(0, DbcTreeModel::CanId)).toString(), QString("0X100"));
QCOMPARE(model.data(frame, DbcTreeModel::CanIdRole).toUInt(), 256U);
QCOMPARE(model.data(frame, DbcTreeModel::PayloadLengthRole).toInt(), 8);
QCOMPARE(model.data(signal, DbcTreeModel::UnitRole).toString(), QString("rpm"));
const QVariantList expectedSignal = {"State", QVariant(), QVariant(), 0U, 8U,
"Intel", "Unsigned", "rpm", "Signal comment"};
const QVariantList expectedFrame = {"Status", "0X100", 8, QVariant(), QVariant(),
QVariant(), QVariant(), QVariant(), "Frame comment"};
for (int column = 0; column < model.columnCount(); ++column) {
QCOMPARE(model.data(model.index(0, column, frame)), expectedSignal.at(column));
QCOMPARE(model.data(model.index(0, column)), expectedFrame.at(column));
}
QCOMPARE(model.data(frame, DbcTreeModel::NodeTypeRole).toInt(), static_cast<int>(NodeType::Frame));
QCOMPARE(model.data(signal, DbcTreeModel::NodeTypeRole).toInt(), static_cast<int>(NodeType::Signal));
QCOMPARE(model.data(frame, DbcTreeModel::ExtendedRole).toBool(), false);
QVERIFY(!model.data(frame, DbcTreeModel::ValueDescriptionsRole).isValid());
QVERIFY(model.data(model.index(1, 0, frame), DbcTreeModel::ValueDescriptionsRole).toList().isEmpty());
QCOMPARE(model.data(signal, Qt::ToolTipRole).toString(), QString("Signal comment"));
QCOMPARE(model.data(signal, DbcTreeModel::StartBitRole).toUInt(), 0U);
QCOMPARE(model.data(signal, DbcTreeModel::BitLengthRole).toUInt(), 8U);
QCOMPARE(model.data(signal, DbcTreeModel::LittleEndianRole).toBool(), true);
QCOMPARE(model.data(signal, DbcTreeModel::SignedRole).toBool(), false);
QVERIFY(!model.data(signal, DbcTreeModel::CanIdRole).isValid());
QVERIFY(!model.data(frame, DbcTreeModel::UnitRole).isValid());
QVERIFY(!model.data(frame, Qt::EditRole).isValid());
const QVariantList values = model.data(signal, DbcTreeModel::ValueDescriptionsRole).toList();
QCOMPARE(values.size(), 3);
QCOMPARE(values.at(0).toMap().value("value").toLongLong(), -1LL);
QCOMPARE(values.at(2).toMap().value("value").toULongLong(), std::numeric_limits<qulonglong>::max());
QCOMPARE(values.at(2).toMap().value("description").toString(), QString("Maximum"));
for (int column = 0; column < model.columnCount(); ++column) {
QVERIFY(model.headerData(column, Qt::Horizontal).isValid());
QCOMPARE(model.data(model.index(0, column, frame), DbcTreeModel::ValueDescriptionsRole), QVariant(values));
}
QVERIFY(!model.headerData(-1, Qt::Horizontal).isValid());
QVERIFY(!model.headerData(model.columnCount(), Qt::Horizontal).isValid());
QVERIFY(!model.headerData(0, Qt::Vertical).isValid());
}
void replacement() {
DbcTreeModel model;
QAbstractItemModelTester tester(&model, QAbstractItemModelTester::FailureReportingMode::QtTest);
QSignalSpy resets(&model, &QAbstractItemModel::modelReset);
model.SetTree(ParsedTree());
QPersistentModelIndex frame(model.index(0, 0));
QPersistentModelIndex signal(model.index(0, 4, frame));
model.SetTree(ParsedTree());
QVERIFY(!frame.isValid());
QVERIFY(!signal.isValid());
QCOMPARE(model.rowCount(), 2);
QPersistentModelIndex replacement(model.index(1, 0));
model.SetTree(nullptr);
QVERIFY(!replacement.isValid());
QCOMPARE(model.rowCount(), 0);
QVERIFY(!model.index(0, 0).isValid());
model.SetTree(nullptr);
QCOMPARE(resets.count(), 4);
try {
model.SetTree(std::unique_ptr<TreeNode>(new TreeNode(FrameInfo())));
QFAIL("A frame cannot be the invisible model root");
} catch (const std::invalid_argument &) {
}
QCOMPARE(resets.count(), 4);
}
};
QTEST_GUILESS_MAIN(ModelTest)
#include "dbc_tree_model_test.moc"