3#include "dbc_tree_model.h"
4#include <QAbstractItemModelTester>
5#include <QGuiApplication>
8#include <QTemporaryFile>
12static std::unique_ptr<TreeNode> ParsedTree() {
13 QTemporaryFile fixture;
14 if (!fixture.open())
throw std::runtime_error(
"Fixture creation failed");
15 fixture.write(
"BO_ 256 Status: 8 ECU\n SG_ State : 0|8@1+ (1,0) [0|255] \"rpm\" ECU\n"
16 " SG_ Other : 8|8@1+ (1,0) [0|255] \"\" ECU\n"
17 "BO_ 257 Second: 8 ECU\n SG_ Value : 0|8@1+ (1,0) [0|255] \"\" ECU\n"
18 "CM_ BO_ 256 \"Frame comment\";\nCM_ SG_ 256 State \"Signal comment\";\n"
19 "VAL_ 256 State -1 \"Negative\" 0 \"Off\" 18446744073709551615 \"Maximum\";\n");
24class ModelTest :
public QObject {
27 void additionalMetadata() {
30 frame.
name =
"Extended";
31 frame.
canId = 0x18F00442U;
34 frame.
comment =
"Frame tooltip";
36 signal.
name =
"SignedMotorola";
42 signal.
comment =
"Signal tooltip";
43 frame.signals.push_back(signal);
44 database.
frames.push_back(frame);
47 const QModelIndex parent = model.index(0, 0);
48 QCOMPARE(model.data(model.index(0, DbcTreeModel::ByteOrder, parent)).toString(), QString(
"Motorola"));
49 QCOMPARE(model.data(model.index(0, DbcTreeModel::Signed, parent)).toString(), QString(
"Signed"));
50 QCOMPARE(model.data(model.index(0, DbcTreeModel::CanId)).toString(), QString(
"0X18F00442"));
51 const QStringList headers = {
"Name",
"CAN ID",
"Payload (bytes)",
"Start bit",
52 "Length (bits)",
"Byte order",
"Signedness",
"Unit",
"Comment"};
53 for (
int column = 0; column < model.columnCount(); ++column) {
54 const QModelIndex frameIndex = model.index(0, column);
55 const QModelIndex signalIndex = model.index(0, column, parent);
56 QCOMPARE(model.headerData(column, Qt::Horizontal).toString(), headers.at(column));
57 QVERIFY(!model.headerData(column, Qt::Horizontal, Qt::ToolTipRole).isValid());
58 QCOMPARE(model.data(frameIndex, Qt::ToolTipRole).toString(), QString(
"Frame tooltip"));
59 QCOMPARE(model.data(signalIndex, Qt::ToolTipRole).toString(), QString(
"Signal tooltip"));
60 QCOMPARE(model.data(frameIndex, DbcTreeModel::CommentRole).toString(), QString(
"Frame tooltip"));
61 QCOMPARE(model.data(signalIndex, DbcTreeModel::CommentRole).toString(), QString(
"Signal tooltip"));
62 QCOMPARE(model.data(frameIndex, DbcTreeModel::CanIdRole).toUInt(), frame.
canId);
63 QCOMPARE(model.data(frameIndex, DbcTreeModel::PayloadLengthRole).toInt(), 8);
64 QCOMPARE(model.data(frameIndex, DbcTreeModel::ExtendedRole).toBool(),
true);
65 QCOMPARE(model.data(signalIndex, DbcTreeModel::StartBitRole).toUInt(), 7U);
66 QCOMPARE(model.data(signalIndex, DbcTreeModel::BitLengthRole).toUInt(), 16U);
67 QCOMPARE(model.data(signalIndex, DbcTreeModel::LittleEndianRole).toBool(),
false);
68 QCOMPARE(model.data(signalIndex, DbcTreeModel::SignedRole).toBool(),
true);
69 QCOMPARE(model.data(signalIndex, DbcTreeModel::UnitRole).toString(), QString(
"rpm"));
70 const QVariant descriptions = model.data(signalIndex, DbcTreeModel::ValueDescriptionsRole);
71 QVERIFY(descriptions.isValid());
72 QVERIFY(descriptions.toList().isEmpty());
73 QVERIFY(!model.data(frameIndex, Qt::UserRole + 1000).isValid());
74 QVERIFY(!model.data(signalIndex, Qt::UserRole + 1000).isValid());
75 QVERIFY(!model.setData(frameIndex,
"Changed", Qt::EditRole));
76 QVERIFY(!model.setData(signalIndex,
"Changed", Qt::EditRole));
78 QCOMPARE(model.data(parent).toString(), QString(
"Extended"));
79 QCOMPARE(model.data(model.index(0, 0, parent)).toString(), QString(
"SignedMotorola"));
81 void emptyRootAndRejectedReplacement() {
83 QAbstractItemModelTester tester(&model, QAbstractItemModelTester::FailureReportingMode::QtTest);
84 model.SetTree(ParsedTree());
85 QPersistentModelIndex retained(model.index(0, 0));
86 QSignalSpy resets(&model, &QAbstractItemModel::modelReset);
87 QVERIFY_THROWS_EXCEPTION(std::invalid_argument, model.SetTree(std::make_unique<TreeNode>(
SignalInfo())));
88 QCOMPARE(resets.count(), 0);
89 QVERIFY(retained.isValid());
90 QCOMPARE(model.rowCount(), 2);
92 QCOMPARE(resets.count(), 1);
93 QVERIFY(!retained.isValid());
94 QCOMPARE(model.rowCount(), 0);
95 QVERIFY(!model.index(0, 0).isValid());
99 QAbstractItemModelTester tester(&model, QAbstractItemModelTester::FailureReportingMode::QtTest);
100 QCOMPARE(model.rowCount(), 0);
101 model.SetTree(ParsedTree());
102 QCOMPARE(model.rowCount(), 2);
103 for (
int row = 0; row < model.rowCount(); ++row) {
104 const QModelIndex frame = model.index(row, 0);
105 for (
int column = 0; column < model.columnCount(); ++column) {
106 const QModelIndex frameColumn = model.index(row, column);
107 QVERIFY(!model.parent(frameColumn).isValid());
108 QCOMPARE(model.rowCount(frameColumn), column == 0 ? (row == 0 ? 2 : 1) : 0);
109 for (
int signalRow = 0; signalRow < model.rowCount(frame); ++signalRow) {
110 const QModelIndex signal = model.index(signalRow, column, frame);
111 QCOMPARE(model.parent(signal), frame);
112 QCOMPARE(model.rowCount(signal), 0);
113 QVERIFY(!model.index(0, 0, signal).isValid());
114 QCOMPARE(model.flags(signal), Qt::ItemIsEnabled | Qt::ItemIsSelectable);
118 QVERIFY(!model.index(-1, 0).isValid());
119 QVERIFY(!model.index(2, 0).isValid());
120 QVERIFY(!model.index(0, -1).isValid());
121 QVERIFY(!model.index(0, model.columnCount()).isValid());
122 QVERIFY(!model.index(0, 0, model.index(0, 1)).isValid());
123 QVERIFY(!model.parent({}).isValid());
124 QVERIFY(!model.data({}).isValid());
125 QCOMPARE(model.flags({}), Qt::NoItemFlags);
127 other.SetTree(ParsedTree());
128 const QModelIndex foreign = other.index(0, 0);
129 QVERIFY(!model.index(0, 0, foreign).isValid());
130 QVERIFY(!model.parent(foreign).isValid());
131 QCOMPARE(model.rowCount(foreign), 0);
132 QCOMPARE(model.columnCount(foreign), 0);
133 QVERIFY(!model.data(foreign).isValid());
134 QCOMPARE(model.flags(foreign), Qt::NoItemFlags);
138 model.SetTree(ParsedTree());
139 const QModelIndex frame = model.index(0, 0);
140 const QModelIndex signal = model.index(0, 0, frame);
141 QCOMPARE(model.data(frame).toString(), QString(
"Status"));
142 QCOMPARE(model.data(model.index(0, DbcTreeModel::CanId)).toString(), QString(
"0X100"));
143 QCOMPARE(model.data(frame, DbcTreeModel::CanIdRole).toUInt(), 256U);
144 QCOMPARE(model.data(frame, DbcTreeModel::PayloadLengthRole).toInt(), 8);
145 QCOMPARE(model.data(signal, DbcTreeModel::UnitRole).toString(), QString(
"rpm"));
146 const QVariantList expectedSignal = {
"State", QVariant(), QVariant(), 0U, 8U,
147 "Intel",
"Unsigned",
"rpm",
"Signal comment"};
148 const QVariantList expectedFrame = {
"Status",
"0X100", 8, QVariant(), QVariant(),
149 QVariant(), QVariant(), QVariant(),
"Frame comment"};
150 for (
int column = 0; column < model.columnCount(); ++column) {
151 QCOMPARE(model.data(model.index(0, column, frame)), expectedSignal.at(column));
152 QCOMPARE(model.data(model.index(0, column)), expectedFrame.at(column));
154 QCOMPARE(model.data(frame, DbcTreeModel::NodeTypeRole).toInt(),
static_cast<int>(NodeType::Frame));
155 QCOMPARE(model.data(signal, DbcTreeModel::NodeTypeRole).toInt(),
static_cast<int>(NodeType::Signal));
156 QCOMPARE(model.data(frame, DbcTreeModel::ExtendedRole).toBool(),
false);
157 QVERIFY(!model.data(frame, DbcTreeModel::ValueDescriptionsRole).isValid());
158 QVERIFY(model.data(model.index(1, 0, frame), DbcTreeModel::ValueDescriptionsRole).toList().isEmpty());
160 QCOMPARE(model.data(signal, Qt::ToolTipRole).toString(), QString(
"Signal comment"));
161 QCOMPARE(model.data(signal, DbcTreeModel::StartBitRole).toUInt(), 0U);
162 QCOMPARE(model.data(signal, DbcTreeModel::BitLengthRole).toUInt(), 8U);
163 QCOMPARE(model.data(signal, DbcTreeModel::LittleEndianRole).toBool(),
true);
164 QCOMPARE(model.data(signal, DbcTreeModel::SignedRole).toBool(),
false);
165 QVERIFY(!model.data(signal, DbcTreeModel::CanIdRole).isValid());
166 QVERIFY(!model.data(frame, DbcTreeModel::UnitRole).isValid());
167 QVERIFY(!model.data(frame, Qt::EditRole).isValid());
168 const QVariantList values = model.data(signal, DbcTreeModel::ValueDescriptionsRole).toList();
169 QCOMPARE(values.size(), 3);
170 QCOMPARE(values.at(0).toMap().value(
"value").toLongLong(), -1LL);
171 QCOMPARE(values.at(2).toMap().value(
"value").toULongLong(), std::numeric_limits<qulonglong>::max());
172 QCOMPARE(values.at(2).toMap().value(
"description").toString(), QString(
"Maximum"));
173 for (
int column = 0; column < model.columnCount(); ++column) {
174 QVERIFY(model.headerData(column, Qt::Horizontal).isValid());
175 QCOMPARE(model.data(model.index(0, column, frame), DbcTreeModel::ValueDescriptionsRole), QVariant(values));
177 QVERIFY(!model.headerData(-1, Qt::Horizontal).isValid());
178 QVERIFY(!model.headerData(model.columnCount(), Qt::Horizontal).isValid());
179 QVERIFY(!model.headerData(0, Qt::Vertical).isValid());
183 QAbstractItemModelTester tester(&model, QAbstractItemModelTester::FailureReportingMode::QtTest);
184 QSignalSpy resets(&model, &QAbstractItemModel::modelReset);
185 model.SetTree(ParsedTree());
186 QPersistentModelIndex frame(model.index(0, 0));
187 QPersistentModelIndex signal(model.index(0, 4, frame));
188 model.SetTree(ParsedTree());
189 QVERIFY(!frame.isValid());
190 QVERIFY(!signal.isValid());
191 QCOMPARE(model.rowCount(), 2);
192 QPersistentModelIndex replacement(model.index(1, 0));
193 model.SetTree(
nullptr);
194 QVERIFY(!replacement.isValid());
195 QCOMPARE(model.rowCount(), 0);
196 QVERIFY(!model.index(0, 0).isValid());
197 model.SetTree(
nullptr);
198 QCOMPARE(resets.count(), 4);
201 QFAIL(
"A frame cannot be the invisible model root");
202 }
catch (
const std::invalid_argument &) {
204 QCOMPARE(resets.count(), 4);
207int main(
int argc,
char **argv) {
209 if (qEnvironmentVariableIsEmpty(
"QT_QPA_PLATFORM"))
210 qputenv(
"QT_QPA_PLATFORM",
"offscreen");
211 QGuiApplication application(argc, argv);
213 return QTest::qExec(&tests, argc, argv);
215#include "dbc_tree_model_test.moc"
Builds a simple tree from parsed DBC database.
std::unique_ptr< TreeNode > Build(const DbcDatabase &database) const
Build tree representation of parsed DBC data.
Tree node for later use in model/view or other hierarchy consumers.
Created: 2026-03-13 Author: Deeaitch (Dim. Himro)
Created: 2026-03-13 Author: Deeaitch (Dim. Himro)
Parsed DBC content stored in a simple internal form.
std::vector< FrameInfo > frames
Describes one CAN frame from a DBC file.
Describes one signal inside a DBC frame.