DBC Framework
DBC parsing and CAN signal decoding framework
Loading...
Searching...
No Matches
dbc_tree_model_test.cpp
1#include "dbc_parser.h"
2#include "dbc_tree_builder.h"
3#include "dbc_tree_model.h"
4#include <QAbstractItemModelTester>
5#include <QGuiApplication>
6#include <QSignalSpy>
7#include <QStringList>
8#include <QTemporaryFile>
9#include <QtTest>
10#include <limits>
11
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");
20 fixture.flush();
21 return DbcTreeBuilder().Build(DbcParser().ParseFile(fixture.fileName().toStdString()));
22}
23
24class ModelTest : public QObject {
25 Q_OBJECT
26private Q_SLOTS:
27 void additionalMetadata() {
28 DbcDatabase database;
29 FrameInfo frame;
30 frame.name = "Extended";
31 frame.canId = 0x18F00442U;
32 frame.isExtended = true;
33 frame.dlc = 8U;
34 frame.comment = "Frame tooltip";
35 SignalInfo signal;
36 signal.name = "SignedMotorola";
37 signal.startBit = 7U;
38 signal.length = 16U;
39 signal.isLittleEndian = false;
40 signal.isSigned = true;
41 signal.unit = "rpm";
42 signal.comment = "Signal tooltip";
43 frame.signals.push_back(signal);
44 database.frames.push_back(frame);
45 DbcTreeModel model;
46 model.SetTree(DbcTreeBuilder().Build(database));
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));
77 }
78 QCOMPARE(model.data(parent).toString(), QString("Extended"));
79 QCOMPARE(model.data(model.index(0, 0, parent)).toString(), QString("SignedMotorola"));
80 }
81 void emptyRootAndRejectedReplacement() {
82 DbcTreeModel model;
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);
91 model.SetTree(DbcTreeBuilder().Build(DbcDatabase()));
92 QCOMPARE(resets.count(), 1);
93 QVERIFY(!retained.isValid());
94 QCOMPARE(model.rowCount(), 0);
95 QVERIFY(!model.index(0, 0).isValid());
96 }
97 void navigation() {
98 DbcTreeModel model;
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);
115 }
116 }
117 }
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);
126 DbcTreeModel other;
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);
135 }
136 void metadata() {
137 DbcTreeModel model;
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));
153 }
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());
159
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));
176 }
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());
180 }
181 void replacement() {
182 DbcTreeModel model;
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);
199 try {
200 model.SetTree(std::unique_ptr<TreeNode>(new TreeNode(FrameInfo())));
201 QFAIL("A frame cannot be the invisible model root");
202 } catch (const std::invalid_argument &) {
203 }
204 QCOMPARE(resets.count(), 4);
205 }
206};
207int main(int argc, char **argv) {
208 // Model tests need QtGui's metatypes, but do not need a display server.
209 if (qEnvironmentVariableIsEmpty("QT_QPA_PLATFORM"))
210 qputenv("QT_QPA_PLATFORM", "offscreen");
211 QGuiApplication application(argc, argv);
212 ModelTest tests;
213 return QTest::qExec(&tests, argc, argv);
214}
215#include "dbc_tree_model_test.moc"
Minimal DBC parser.
Definition dbc_parser.h:33
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.
Definition tree_node.h:35
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.
Definition frame_info.h:24
bool isExtended
Definition frame_info.h:27
std::uint8_t dlc
Definition frame_info.h:30
std::string name
Definition frame_info.h:25
std::string comment
Definition frame_info.h:32
std::uint32_t canId
Definition frame_info.h:26
Describes one signal inside a DBC frame.
Definition signal_info.h:24
std::uint32_t length
Definition signal_info.h:28
std::string unit
Definition signal_info.h:35
std::string name
Definition signal_info.h:26
bool isSigned
Definition signal_info.h:30
std::string comment
Definition signal_info.h:38
std::uint32_t startBit
Definition signal_info.h:27
bool isLittleEndian
Definition signal_info.h:29