216 lines
12 KiB
C++
216 lines
12 KiB
C++
#include "dbc_parser.h"
|
|
#include "dbc_tree_builder.h"
|
|
#include "dbc_tree_model.h"
|
|
#include <QAbstractItemModelTester>
|
|
#include <QGuiApplication>
|
|
#include <QSignalSpy>
|
|
#include <QStringList>
|
|
#include <QTemporaryFile>
|
|
#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 QModelIndex frameIndex = model.index(0, column);
|
|
const QModelIndex 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 QVariant 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);
|
|
}
|
|
};
|
|
int main(int argc, char **argv) {
|
|
// Model tests need QtGui's metatypes, but do not need a display server.
|
|
if (qEnvironmentVariableIsEmpty("QT_QPA_PLATFORM"))
|
|
qputenv("QT_QPA_PLATFORM", "offscreen");
|
|
QGuiApplication application(argc, argv);
|
|
ModelTest tests;
|
|
return QTest::qExec(&tests, argc, argv);
|
|
}
|
|
#include "dbc_tree_model_test.moc"
|