#include "dbc_parser.h" #include "dbc_tree_builder.h" #include "dbc_tree_model.h" #include #include #include #include #include #include #include static std::unique_ptr 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(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(NodeType::Frame)); QCOMPARE(model.data(signal, DbcTreeModel::NodeTypeRole).toInt(), static_cast(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::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(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"