1// Copyright (C) 2016 The Qt Company Ltd.
2// Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Giuseppe D'Angelo <giuseppe.dangelo@kdab.com>
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
4
5#include "qabstractitemmodeltester.h"
6
7#include <private/qobject_p.h>
8#include <private/qabstractitemmodel_p.h>
9#include <QtCore/QPointer>
10#include <QtCore/QAbstractItemModel>
11#include <QtCore/QStack>
12#include <QTest>
13#include <QLoggingCategory>
14
15QT_BEGIN_NAMESPACE
16
17Q_LOGGING_CATEGORY(lcModelTest, "qt.modeltest")
18
19#define MODELTESTER_VERIFY(statement) \
20do { \
21 if (!verify(static_cast<bool>(statement), #statement, "", __FILE__, __LINE__)) \
22 return; \
23} while (false)
24
25#define MODELTESTER_COMPARE(actual, expected) \
26do { \
27 if (!compare((actual), (expected), #actual, #expected, __FILE__, __LINE__)) \
28 return; \
29} while (false)
30
31class QAbstractItemModelTesterPrivate : public QObjectPrivate
32{
33 Q_DECLARE_PUBLIC(QAbstractItemModelTester)
34public:
35 QAbstractItemModelTesterPrivate(QAbstractItemModel *model, QAbstractItemModelTester::FailureReportingMode failureReportingMode);
36
37 void nonDestructiveBasicTest();
38 void rowAndColumnCount();
39 void hasIndex();
40 void index();
41 void parent();
42 void data();
43
44 void runAllTests();
45
46 void layoutAboutToBeChanged();
47 void layoutChanged();
48
49 void modelAboutToBeReset();
50 void modelReset();
51
52 void columnsAboutToBeInserted(const QModelIndex &parent, int first, int last);
53 void columnsInserted(const QModelIndex &parent, int first, int last);
54 void columnsAboutToBeMoved(const QModelIndex &sourceParent, int sourceStart, int sourceEnd,
55 const QModelIndex &destinationParent, int destinationColumn);
56 void columnsMoved(const QModelIndex &parent, int start, int end, const QModelIndex &destination,
57 int column);
58 void columnsAboutToBeRemoved(const QModelIndex &parent, int first, int last);
59 void columnsRemoved(const QModelIndex &parent, int first, int last);
60
61 void rowsAboutToBeInserted(const QModelIndex &parent, int start, int end);
62 void rowsInserted(const QModelIndex &parent, int start, int end);
63 void rowsAboutToBeMoved(const QModelIndex &sourceParent, int sourceStart, int sourceEnd,
64 const QModelIndex &destinationParent, int destinationRow);
65 void rowsMoved(const QModelIndex &parent, int start, int end, const QModelIndex &destination,
66 int row);
67 void rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end);
68 void rowsRemoved(const QModelIndex &parent, int start, int end);
69 void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight);
70 void headerDataChanged(Qt::Orientation orientation, int start, int end);
71
72private:
73 void checkChildren(const QModelIndex &parent, int currentDepth = 0);
74
75 bool verify(bool statement, const char *statementStr, const char *description, const char *file, int line);
76
77 template<typename T1, typename T2>
78 bool compare(const T1 &t1, const T2 &t2,
79 const char *actual, const char *expected,
80 const char *file, int line);
81
82 QPointer<QAbstractItemModel> model;
83 QAbstractItemModelTester::FailureReportingMode failureReportingMode;
84
85 struct Changing {
86 QModelIndex parent;
87 int oldSize;
88 QVariant last;
89 QVariant next;
90 };
91 QStack<Changing> insert;
92 QStack<Changing> remove;
93
94 bool useFetchMore = true;
95 bool fetchingMore;
96
97 enum class ChangeInFlight {
98 None,
99 ColumnsInserted,
100 ColumnsMoved,
101 ColumnsRemoved,
102 LayoutChanged,
103 ModelReset,
104 RowsInserted,
105 RowsMoved,
106 RowsRemoved
107 };
108 ChangeInFlight changeInFlight = ChangeInFlight::None;
109
110 QList<QPersistentModelIndex> changing;
111};
112
113/*!
114 \class QAbstractItemModelTester
115 \since 5.11
116 \inmodule QtTest
117
118 \brief The QAbstractItemModelTester class helps testing QAbstractItemModel subclasses.
119
120 The QAbstractItemModelTester class is a utility class to test item models.
121
122 When implementing an item model (that is, a concrete QAbstractItemModel
123 subclass) one must abide to a very strict set of rules that ensure
124 consistency for users of the model (views, proxy models, and so on).
125
126 For instance, for a given index, a model's reimplementation of
127 \l{QAbstractItemModel::hasChildren()}{hasChildren()} must be consistent
128 with the values returned by \l{QAbstractItemModel::rowCount()}{rowCount()}
129 and \l{QAbstractItemModel::columnCount()}{columnCount()}.
130
131 QAbstractItemModelTester helps catching the most common errors in custom
132 item model classes. By performing a series of tests, it
133 will try to check that the model status is consistent at all times. The
134 tests will be repeated automatically every time the model is modified.
135
136 QAbstractItemModelTester employs non-destructive tests, which typically
137 consist in reading data and metadata out of a given item model.
138 QAbstractItemModelTester will also attempt illegal modifications of
139 the model. In models which are properly implemented, such attempts
140 should be rejected, and no data should be changed as a consequence.
141
142 \section1 Usage
143
144 Using QAbstractItemModelTester is straightforward. In a \l{Qt Test Overview}{test case}
145 it is sufficient to create an instance, passing the model that
146 needs to be tested to the constructor:
147
148 \code
149 MyModel *modelToBeTested = ...;
150 auto tester = new QAbstractItemModelTester(modelToBeTested);
151 \endcode
152
153 QAbstractItemModelTester will report testing failures through the
154 Qt Test logging mechanisms.
155
156 It is also possible to use QAbstractItemModelTester outside of a test case.
157 For instance, it may be useful to test an item model used by an application
158 without the need of building an explicit unit test for such a model (which
159 might be challenging). In order to use QAbstractItemModelTester outside of
160 a test case, pass one of the \c QAbstractItemModelTester::FailureReportingMode
161 enumerators to its constructor, therefore specifying how failures should
162 be logged.
163
164 QAbstractItemModelTester may also report additional debugging information
165 as logging messages under the \c qt.modeltest logging category. Such
166 debug logging is disabled by default; refer to the
167 QLoggingCategory documentation to learn how to enable it.
168
169 \note While QAbstractItemModelTester is a valid help for development and
170 testing of custom item models, it does not (and cannot) catch all possible
171 problems in QAbstractItemModel subclasses. Notably, it will never perform
172 meaningful destructive testing of a model, which must be therefore tested
173 separately.
174
175 \sa {Model/View Programming}, QAbstractItemModel
176*/
177
178/*!
179 \enum QAbstractItemModelTester::FailureReportingMode
180
181 This enumeration specifies how QAbstractItemModelTester should report
182 a failure when it tests a QAbstractItemModel subclass.
183
184 \value QtTest The failures will be reported as QtTest test failures.
185
186 \value Warning The failures will be reported as
187 warning messages in the \c{qt.modeltest} logging category.
188
189 \value Fatal A failure will cause immediate and
190 abnormal program termination. The reason for the failure will be reported
191 using \c{qFatal()}.
192*/
193
194/*!
195 Creates a model tester instance, with the given \a parent, that will test
196 the model \a model.
197*/
198QAbstractItemModelTester::QAbstractItemModelTester(QAbstractItemModel *model, QObject *parent)
199 : QAbstractItemModelTester(model, FailureReportingMode::QtTest, parent)
200{
201}
202
203/*!
204 Creates a model tester instance, with the given \a parent, that will test
205 the model \a model, using the specified \a mode to report test failures.
206
207 \sa QAbstractItemModelTester::FailureReportingMode
208*/
209QAbstractItemModelTester::QAbstractItemModelTester(QAbstractItemModel *model, FailureReportingMode mode, QObject *parent)
210 : QObject(*new QAbstractItemModelTesterPrivate(model, mode), parent)
211{
212 if (!model)
213 qFatal(msg: "%s: model must not be null", Q_FUNC_INFO);
214
215 Q_D(QAbstractItemModelTester);
216
217 auto runAllTests = [d] { d->runAllTests(); };
218
219 connect(sender: model, signal: &QAbstractItemModel::columnsAboutToBeInserted,
220 context: this, slot&: runAllTests);
221 connect(sender: model, signal: &QAbstractItemModel::columnsAboutToBeRemoved,
222 context: this, slot&: runAllTests);
223 connect(sender: model, signal: &QAbstractItemModel::columnsInserted,
224 context: this, slot&: runAllTests);
225 connect(sender: model, signal: &QAbstractItemModel::columnsRemoved,
226 context: this, slot&: runAllTests);
227 connect(sender: model, signal: &QAbstractItemModel::dataChanged,
228 context: this, slot&: runAllTests);
229 connect(sender: model, signal: &QAbstractItemModel::headerDataChanged,
230 context: this, slot&: runAllTests);
231 connect(sender: model, signal: &QAbstractItemModel::layoutAboutToBeChanged,
232 context: this, slot&: runAllTests);
233 connect(sender: model, signal: &QAbstractItemModel::layoutChanged,
234 context: this, slot&: runAllTests);
235 connect(sender: model, signal: &QAbstractItemModel::modelReset,
236 context: this, slot&: runAllTests);
237 connect(sender: model, signal: &QAbstractItemModel::rowsAboutToBeInserted,
238 context: this, slot&: runAllTests);
239 connect(sender: model, signal: &QAbstractItemModel::rowsAboutToBeRemoved,
240 context: this, slot&: runAllTests);
241 connect(sender: model, signal: &QAbstractItemModel::rowsInserted,
242 context: this, slot&: runAllTests);
243 connect(sender: model, signal: &QAbstractItemModel::rowsRemoved,
244 context: this, slot&: runAllTests);
245
246 // Special checks for changes
247 connect(sender: model, signal: &QAbstractItemModel::layoutAboutToBeChanged,
248 context: this, slot: [d]{ d->layoutAboutToBeChanged(); });
249 connect(sender: model, signal: &QAbstractItemModel::layoutChanged,
250 context: this, slot: [d]{ d->layoutChanged(); });
251
252 // column operations
253 connect(sender: model, signal: &QAbstractItemModel::columnsAboutToBeInserted,
254 context: this, slot: [d](const QModelIndex &parent, int start, int end) { d->columnsAboutToBeInserted(parent, first: start, last: end); });
255 connect(sender: model, signal: &QAbstractItemModel::columnsAboutToBeMoved,
256 context: this, slot: [d](const QModelIndex &sourceParent, int sourceStart, int sourceEnd, const QModelIndex &destinationParent, int destinationColumn) {
257 d->columnsAboutToBeMoved(sourceParent, sourceStart, sourceEnd, destinationParent, destinationColumn); });
258 connect(sender: model, signal: &QAbstractItemModel::columnsAboutToBeRemoved,
259 context: this, slot: [d](const QModelIndex &parent, int start, int end) { d->columnsAboutToBeRemoved(parent, first: start, last: end); });
260 connect(sender: model, signal: &QAbstractItemModel::columnsInserted,
261 context: this, slot: [d](const QModelIndex &parent, int start, int end) { d->columnsInserted(parent, first: start, last: end); });
262 connect(sender: model, signal: &QAbstractItemModel::columnsMoved,
263 context: this, slot: [d](const QModelIndex &parent, int start, int end, const QModelIndex &destination, int col) {
264 d->columnsMoved(parent, start, end, destination, column: col); });
265 connect(sender: model, signal: &QAbstractItemModel::columnsRemoved,
266 context: this, slot: [d](const QModelIndex &parent, int start, int end) { d->columnsRemoved(parent, first: start, last: end); });
267
268 // row operations
269 connect(sender: model, signal: &QAbstractItemModel::rowsAboutToBeInserted,
270 context: this, slot: [d](const QModelIndex &parent, int start, int end) { d->rowsAboutToBeInserted(parent, start, end); });
271 connect(sender: model, signal: &QAbstractItemModel::rowsAboutToBeMoved,
272 context: this, slot: [d](const QModelIndex &sourceParent, int sourceStart, int sourceEnd, const QModelIndex &destinationParent, int destinationRow) {
273 d->rowsAboutToBeMoved(sourceParent, sourceStart, sourceEnd, destinationParent, destinationRow); });
274 connect(sender: model, signal: &QAbstractItemModel::rowsAboutToBeRemoved,
275 context: this, slot: [d](const QModelIndex &parent, int start, int end) { d->rowsAboutToBeRemoved(parent, start, end); });
276 connect(sender: model, signal: &QAbstractItemModel::rowsInserted,
277 context: this, slot: [d](const QModelIndex &parent, int start, int end) { d->rowsInserted(parent, start, end); });
278 connect(sender: model, signal: &QAbstractItemModel::rowsMoved,
279 context: this, slot: [d](const QModelIndex &parent, int start, int end, const QModelIndex &destination, int row) {
280 d->rowsMoved(parent, start, end, destination, row); });
281 connect(sender: model, signal: &QAbstractItemModel::rowsRemoved,
282 context: this, slot: [d](const QModelIndex &parent, int start, int end) { d->rowsRemoved(parent, start, end); });
283
284 // reset
285 connect(sender: model, signal: &QAbstractItemModel::modelAboutToBeReset,
286 context: this, slot: [d]() { d->modelAboutToBeReset(); });
287 connect(sender: model, signal: &QAbstractItemModel::modelReset,
288 context: this, slot: [d]() { d->modelReset(); });
289
290 // data
291 connect(sender: model, signal: &QAbstractItemModel::dataChanged,
292 context: this, slot: [d](const QModelIndex &topLeft, const QModelIndex &bottomRight) { d->dataChanged(topLeft, bottomRight); });
293 connect(sender: model, signal: &QAbstractItemModel::headerDataChanged,
294 context: this, slot: [d](Qt::Orientation orientation, int start, int end) { d->headerDataChanged(orientation, start, end); });
295
296 runAllTests();
297}
298
299/*!
300 Returns the model that this instance is testing.
301*/
302QAbstractItemModel *QAbstractItemModelTester::model() const
303{
304 Q_D(const QAbstractItemModelTester);
305 return d->model.data();
306}
307
308/*!
309 Returns the mode that this instancing is using to report test failures.
310
311 \sa QAbstractItemModelTester::FailureReportingMode
312*/
313QAbstractItemModelTester::FailureReportingMode QAbstractItemModelTester::failureReportingMode() const
314{
315 Q_D(const QAbstractItemModelTester);
316 return d->failureReportingMode;
317}
318
319/*!
320 If \a value is true, enables dynamic population of the
321 tested model, which is the default.
322 If \a value is false, it disables it.
323
324 \since 6.4
325 \sa QAbstractItemModel::fetchMore()
326*/
327void QAbstractItemModelTester::setUseFetchMore(bool value)
328{
329 Q_D(QAbstractItemModelTester);
330 d->useFetchMore = value;
331}
332
333bool QAbstractItemModelTester::verify(bool statement, const char *statementStr, const char *description, const char *file, int line)
334{
335 Q_D(QAbstractItemModelTester);
336 return d->verify(statement, statementStr, description, file, line);
337}
338
339QAbstractItemModelTesterPrivate::QAbstractItemModelTesterPrivate(QAbstractItemModel *model, QAbstractItemModelTester::FailureReportingMode failureReportingMode)
340 : model(model),
341 failureReportingMode(failureReportingMode),
342 fetchingMore(false)
343{
344}
345
346void QAbstractItemModelTesterPrivate::runAllTests()
347{
348 if (fetchingMore)
349 return;
350 nonDestructiveBasicTest();
351 rowAndColumnCount();
352 hasIndex();
353 index();
354 parent();
355 data();
356}
357
358/*
359 nonDestructiveBasicTest tries to call a number of the basic functions (not all)
360 to make sure the model doesn't outright segfault, testing the functions that makes sense.
361*/
362void QAbstractItemModelTesterPrivate::nonDestructiveBasicTest()
363{
364 MODELTESTER_VERIFY(!model->buddy(QModelIndex()).isValid());
365 model->canFetchMore(parent: QModelIndex());
366 MODELTESTER_VERIFY(model->columnCount(QModelIndex()) >= 0);
367 if (useFetchMore) {
368 fetchingMore = true;
369 model->fetchMore(parent: QModelIndex());
370 fetchingMore = false;
371 }
372 Qt::ItemFlags flags = model->flags(index: QModelIndex());
373 MODELTESTER_VERIFY(flags == Qt::ItemIsDropEnabled || flags == 0);
374 model->hasChildren(parent: QModelIndex());
375 const bool hasRow = model->hasIndex(row: 0, column: 0);
376 QVariant cache;
377 if (hasRow)
378 model->match(start: model->index(row: 0, column: 0), role: -1, value: cache);
379 model->mimeTypes();
380 MODELTESTER_VERIFY(!model->parent(QModelIndex()).isValid());
381 MODELTESTER_VERIFY(model->rowCount() >= 0);
382 model->span(index: QModelIndex());
383 model->supportedDropActions();
384 model->roleNames();
385}
386
387/*
388 Tests model's implementation of QAbstractItemModel::rowCount(),
389 columnCount() and hasChildren().
390
391 Models that are dynamically populated are not as fully tested here.
392 */
393void QAbstractItemModelTesterPrivate::rowAndColumnCount()
394{
395 if (!model->hasChildren())
396 return;
397
398 QModelIndex topIndex = model->index(row: 0, column: 0, parent: QModelIndex());
399
400 // check top row
401 int rows = model->rowCount(parent: topIndex);
402 MODELTESTER_VERIFY(rows >= 0);
403
404 int columns = model->columnCount(parent: topIndex);
405 MODELTESTER_VERIFY(columns >= 0);
406
407 if (rows == 0 || columns == 0)
408 return;
409
410 MODELTESTER_VERIFY(model->hasChildren(topIndex));
411
412 QModelIndex secondLevelIndex = model->index(row: 0, column: 0, parent: topIndex);
413 MODELTESTER_VERIFY(secondLevelIndex.isValid());
414
415 rows = model->rowCount(parent: secondLevelIndex);
416 MODELTESTER_VERIFY(rows >= 0);
417
418 columns = model->columnCount(parent: secondLevelIndex);
419 MODELTESTER_VERIFY(columns >= 0);
420
421 if (rows == 0 || columns == 0)
422 return;
423
424 MODELTESTER_VERIFY(model->hasChildren(secondLevelIndex));
425
426 // rowCount() / columnCount() are tested more extensively in checkChildren()
427}
428
429/*
430 Tests model's implementation of QAbstractItemModel::hasIndex()
431 */
432void QAbstractItemModelTesterPrivate::hasIndex()
433{
434 // Make sure that invalid values returns an invalid index
435 MODELTESTER_VERIFY(!model->hasIndex(-2, -2));
436 MODELTESTER_VERIFY(!model->hasIndex(-2, 0));
437 MODELTESTER_VERIFY(!model->hasIndex(0, -2));
438
439 const int rows = model->rowCount();
440 const int columns = model->columnCount();
441
442 // check out of bounds
443 MODELTESTER_VERIFY(!model->hasIndex(rows, columns));
444 MODELTESTER_VERIFY(!model->hasIndex(rows + 1, columns + 1));
445
446 if (rows > 0 && columns > 0)
447 MODELTESTER_VERIFY(model->hasIndex(0, 0));
448
449 // hasIndex() is tested more extensively in checkChildren(),
450 // but this catches the big mistakes
451}
452
453/*
454 Tests model's implementation of QAbstractItemModel::index()
455 */
456void QAbstractItemModelTesterPrivate::index()
457{
458 const int rows = model->rowCount();
459 const int columns = model->columnCount();
460
461 for (int row = 0; row < rows; ++row) {
462 for (int column = 0; column < columns; ++column) {
463 // Make sure that the same index is *always* returned
464 QModelIndex a = model->index(row, column);
465 QModelIndex b = model->index(row, column);
466 MODELTESTER_VERIFY(a.isValid());
467 MODELTESTER_VERIFY(b.isValid());
468 MODELTESTER_COMPARE(a, b);
469 }
470 }
471
472 // index() is tested more extensively in checkChildren(),
473 // but this catches the big mistakes
474}
475
476/*
477 Tests model's implementation of QAbstractItemModel::parent()
478 */
479void QAbstractItemModelTesterPrivate::parent()
480{
481 // Make sure the model won't crash and will return an invalid QModelIndex
482 // when asked for the parent of an invalid index.
483 MODELTESTER_VERIFY(!model->parent(QModelIndex()).isValid());
484
485 if (model->rowCount() == 0 || model->columnCount() == 0)
486 return;
487
488 // Column 0 | Column 1 |
489 // QModelIndex() | |
490 // \- topIndex | topIndex1 |
491 // \- childIndex | childIndex1 |
492
493 // Common error test #1, make sure that a top level index has a parent
494 // that is a invalid QModelIndex.
495 QModelIndex topIndex = model->index(row: 0, column: 0, parent: QModelIndex());
496 MODELTESTER_VERIFY(topIndex.isValid());
497 MODELTESTER_VERIFY(!model->parent(topIndex).isValid());
498
499 // Common error test #2, make sure that a second level index has a parent
500 // that is the first level index.
501 if (model->rowCount(parent: topIndex) > 0 && model->columnCount(parent: topIndex) > 0) {
502 QModelIndex childIndex = model->index(row: 0, column: 0, parent: topIndex);
503 MODELTESTER_VERIFY(childIndex.isValid());
504 MODELTESTER_COMPARE(model->parent(childIndex), topIndex);
505 }
506
507 // Common error test #3, the second column should NOT have the same children
508 // as the first column in a row.
509 // Usually the second column shouldn't have children.
510 if (model->hasIndex(row: 0, column: 1)) {
511 QModelIndex topIndex1 = model->index(row: 0, column: 1, parent: QModelIndex());
512 MODELTESTER_VERIFY(topIndex1.isValid());
513 if (model->rowCount(parent: topIndex) > 0 && model->rowCount(parent: topIndex1) > 0) {
514 QModelIndex childIndex = model->index(row: 0, column: 0, parent: topIndex);
515 MODELTESTER_VERIFY(childIndex.isValid());
516 QModelIndex childIndex1 = model->index(row: 0, column: 0, parent: topIndex1);
517 MODELTESTER_VERIFY(childIndex1.isValid());
518 MODELTESTER_VERIFY(childIndex != childIndex1);
519 }
520 }
521
522 // Full test, walk n levels deep through the model making sure that all
523 // parent's children correctly specify their parent.
524 checkChildren(parent: QModelIndex());
525}
526
527/*
528 Called from the parent() test.
529
530 A model that returns an index of parent X should also return X when asking
531 for the parent of the index.
532
533 This recursive function does pretty extensive testing on the whole model in an
534 effort to catch edge cases.
535
536 This function assumes that rowCount(), columnCount() and index() already work.
537 If they have a bug it will point it out, but the above tests should have already
538 found the basic bugs because it is easier to figure out the problem in
539 those tests then this one.
540 */
541void QAbstractItemModelTesterPrivate::checkChildren(const QModelIndex &parent, int currentDepth)
542{
543 // First just try walking back up the tree.
544 QModelIndex p = parent;
545 while (p.isValid())
546 p = p.parent();
547
548 // For models that are dynamically populated
549 if (model->canFetchMore(parent) && useFetchMore) {
550 fetchingMore = true;
551 model->fetchMore(parent);
552 fetchingMore = false;
553 }
554
555 const int rows = model->rowCount(parent);
556 const int columns = model->columnCount(parent);
557
558 if (rows > 0)
559 MODELTESTER_VERIFY(model->hasChildren(parent));
560
561 // Some further testing against rows(), columns(), and hasChildren()
562 MODELTESTER_VERIFY(rows >= 0);
563 MODELTESTER_VERIFY(columns >= 0);
564 if (rows > 0 && columns > 0)
565 MODELTESTER_VERIFY(model->hasChildren(parent));
566
567 const QModelIndex topLeftChild = model->index(row: 0, column: 0, parent);
568
569 MODELTESTER_VERIFY(!model->hasIndex(rows, 0, parent));
570 MODELTESTER_VERIFY(!model->hasIndex(rows + 1, 0, parent));
571
572 for (int r = 0; r < rows; ++r) {
573 MODELTESTER_VERIFY(!model->hasIndex(r, columns, parent));
574 MODELTESTER_VERIFY(!model->hasIndex(r, columns + 1, parent));
575 for (int c = 0; c < columns; ++c) {
576 MODELTESTER_VERIFY(model->hasIndex(r, c, parent));
577 QModelIndex index = model->index(row: r, column: c, parent);
578 // rowCount() and columnCount() said that it existed...
579 if (!index.isValid())
580 qCWarning(lcModelTest) << "Got invalid index at row=" << r << "col=" << c << "parent=" << parent;
581 MODELTESTER_VERIFY(index.isValid());
582
583 // index() should always return the same index when called twice in a row
584 QModelIndex modifiedIndex = model->index(row: r, column: c, parent);
585 MODELTESTER_COMPARE(index, modifiedIndex);
586
587 {
588 const QModelIndex sibling = model->sibling(row: r, column: c, idx: topLeftChild);
589 MODELTESTER_COMPARE(index, sibling);
590 }
591 {
592 const QModelIndex sibling = topLeftChild.sibling(arow: r, acolumn: c);
593 MODELTESTER_COMPARE(index, sibling);
594 }
595
596 // Some basic checking on the index that is returned
597 MODELTESTER_COMPARE(index.model(), model);
598 MODELTESTER_COMPARE(index.row(), r);
599 MODELTESTER_COMPARE(index.column(), c);
600
601 // If the next test fails here is some somewhat useful debug you play with.
602 if (model->parent(child: index) != parent) {
603 qCWarning(lcModelTest) << "Inconsistent parent() implementation detected:";
604 qCWarning(lcModelTest) << " index=" << index << "exp. parent=" << parent << "act. parent=" << model->parent(child: index);
605 qCWarning(lcModelTest) << " row=" << r << "col=" << c << "depth=" << currentDepth;
606 qCWarning(lcModelTest) << " data for child" << model->data(index).toString();
607 qCWarning(lcModelTest) << " data for parent" << model->data(index: parent).toString();
608 }
609
610 // Check that we can get back our real parent.
611 MODELTESTER_COMPARE(model->parent(index), parent);
612
613 QPersistentModelIndex persistentIndex = index;
614
615 // recursively go down the children
616 if (model->hasChildren(parent: index) && currentDepth < 10)
617 checkChildren(parent: index, currentDepth: currentDepth + 1);
618
619 // make sure that after testing the children that the index doesn't change.
620 QModelIndex newerIndex = model->index(row: r, column: c, parent);
621 MODELTESTER_COMPARE(persistentIndex, newerIndex);
622 }
623 }
624}
625
626/*
627 Tests model's implementation of QAbstractItemModel::data()
628 */
629void QAbstractItemModelTesterPrivate::data()
630{
631 if (model->rowCount() == 0 || model->columnCount() == 0)
632 return;
633
634 MODELTESTER_VERIFY(model->index(0, 0).isValid());
635
636 // General Purpose roles that should return a QString
637 QVariant variant;
638 variant = model->data(index: model->index(row: 0, column: 0), role: Qt::DisplayRole);
639 if (variant.isValid())
640 MODELTESTER_VERIFY(variant.canConvert<QString>());
641 variant = model->data(index: model->index(row: 0, column: 0), role: Qt::ToolTipRole);
642 if (variant.isValid())
643 MODELTESTER_VERIFY(variant.canConvert<QString>());
644 variant = model->data(index: model->index(row: 0, column: 0), role: Qt::StatusTipRole);
645 if (variant.isValid())
646 MODELTESTER_VERIFY(variant.canConvert<QString>());
647 variant = model->data(index: model->index(row: 0, column: 0), role: Qt::WhatsThisRole);
648 if (variant.isValid())
649 MODELTESTER_VERIFY(variant.canConvert<QString>());
650
651 // General Purpose roles that should return a QSize
652 variant = model->data(index: model->index(row: 0, column: 0), role: Qt::SizeHintRole);
653 if (variant.isValid())
654 MODELTESTER_VERIFY(variant.canConvert<QSize>());
655
656 // Check that the alignment is one we know about
657 QVariant textAlignmentVariant = model->data(index: model->index(row: 0, column: 0), role: Qt::TextAlignmentRole);
658 if (textAlignmentVariant.isValid()) {
659 Qt::Alignment alignment = QtPrivate::legacyFlagValueFromModelData<Qt::Alignment>(data: textAlignmentVariant);
660 MODELTESTER_COMPARE(alignment, (alignment & (Qt::AlignHorizontal_Mask | Qt::AlignVertical_Mask)));
661 }
662
663 // Check that the "check state" is one we know about.
664 QVariant checkStateVariant = model->data(index: model->index(row: 0, column: 0), role: Qt::CheckStateRole);
665 if (checkStateVariant.isValid()) {
666 Qt::CheckState state = QtPrivate::legacyEnumValueFromModelData<Qt::CheckState>(data: checkStateVariant);
667 MODELTESTER_VERIFY(state == Qt::Unchecked
668 || state == Qt::PartiallyChecked
669 || state == Qt::Checked);
670 }
671
672 Q_Q(QAbstractItemModelTester);
673
674 if (!QTestPrivate::testDataGuiRoles(tester: q))
675 return;
676}
677
678void QAbstractItemModelTesterPrivate::columnsAboutToBeInserted(const QModelIndex &parent, int start,
679 int end)
680{
681 MODELTESTER_COMPARE(changeInFlight, ChangeInFlight::None);
682 changeInFlight = ChangeInFlight::ColumnsInserted;
683
684 qCDebug(lcModelTest) << "columnsAboutToBeInserted"
685 << "start=" << start << "end=" << end << "parent=" << parent
686 << "parent data=" << model->data(index: parent).toString()
687 << "current count of parent=" << model->rowCount(parent)
688 << "last before insertion=" << model->index(row: start - 1, column: 0, parent)
689 << model->data(index: model->index(row: start - 1, column: 0, parent));
690}
691
692void QAbstractItemModelTesterPrivate::columnsInserted(const QModelIndex &parent, int first,
693 int last)
694{
695 MODELTESTER_COMPARE(changeInFlight, ChangeInFlight::ColumnsInserted);
696 changeInFlight = ChangeInFlight::None;
697
698 qCDebug(lcModelTest) << "columnsInserted"
699 << "start=" << first << "end=" << last << "parent=" << parent
700 << "parent data=" << model->data(index: parent).toString()
701 << "current count of parent=" << model->rowCount(parent);
702}
703
704void QAbstractItemModelTesterPrivate::columnsAboutToBeMoved(const QModelIndex &sourceParent,
705 int sourceStart, int sourceEnd,
706 const QModelIndex &destinationParent,
707 int destinationColumn)
708{
709 MODELTESTER_COMPARE(changeInFlight, ChangeInFlight::None);
710 changeInFlight = ChangeInFlight::ColumnsMoved;
711
712 qCDebug(lcModelTest) << "columnsAboutToBeMoved"
713 << "sourceStart=" << sourceStart << "sourceEnd=" << sourceEnd
714 << "sourceParent=" << sourceParent
715 << "sourceParent data=" << model->data(index: sourceParent).toString()
716 << "destinationParent=" << destinationParent
717 << "destinationColumn=" << destinationColumn;
718}
719
720void QAbstractItemModelTesterPrivate::columnsMoved(const QModelIndex &sourceParent, int sourceStart,
721 int sourceEnd,
722 const QModelIndex &destinationParent,
723 int destinationColumn)
724{
725 MODELTESTER_COMPARE(changeInFlight, ChangeInFlight::ColumnsMoved);
726 changeInFlight = ChangeInFlight::None;
727
728 qCDebug(lcModelTest) << "columnsMoved"
729 << "sourceStart=" << sourceStart << "sourceEnd=" << sourceEnd
730 << "sourceParent=" << sourceParent
731 << "sourceParent data=" << model->data(index: sourceParent).toString()
732 << "destinationParent=" << destinationParent
733 << "destinationColumn=" << destinationColumn;
734}
735
736void QAbstractItemModelTesterPrivate::columnsAboutToBeRemoved(const QModelIndex &parent, int first,
737 int last)
738{
739 MODELTESTER_COMPARE(changeInFlight, ChangeInFlight::None);
740 changeInFlight = ChangeInFlight::ColumnsRemoved;
741
742 qCDebug(lcModelTest) << "columnsAboutToBeRemoved"
743 << "start=" << first << "end=" << last << "parent=" << parent
744 << "parent data=" << model->data(index: parent).toString()
745 << "current count of parent=" << model->rowCount(parent)
746 << "last before removal=" << model->index(row: first - 1, column: 0, parent)
747 << model->data(index: model->index(row: first - 1, column: 0, parent));
748}
749
750void QAbstractItemModelTesterPrivate::columnsRemoved(const QModelIndex &parent, int first, int last)
751{
752 MODELTESTER_COMPARE(changeInFlight, ChangeInFlight::ColumnsRemoved);
753 changeInFlight = ChangeInFlight::None;
754
755 qCDebug(lcModelTest) << "columnsRemoved"
756 << "start=" << first << "end=" << last << "parent=" << parent
757 << "parent data=" << model->data(index: parent).toString()
758 << "current count of parent=" << model->rowCount(parent);
759}
760
761/*
762 Store what is about to be inserted to make sure it actually happens
763
764 \sa rowsInserted()
765 */
766void QAbstractItemModelTesterPrivate::rowsAboutToBeInserted(const QModelIndex &parent, int start, int end)
767{
768 MODELTESTER_COMPARE(changeInFlight, ChangeInFlight::None);
769 changeInFlight = ChangeInFlight::RowsInserted;
770
771 qCDebug(lcModelTest) << "rowsAboutToBeInserted"
772 << "start=" << start << "end=" << end << "parent=" << parent
773 << "parent data=" << model->data(index: parent).toString()
774 << "current count of parent=" << model->rowCount(parent)
775 << "last before insertion=" << model->index(row: start - 1, column: 0, parent) << model->data(index: model->index(row: start - 1, column: 0, parent));
776
777 Changing c;
778 c.parent = parent;
779 c.oldSize = model->rowCount(parent);
780 c.last = (start - 1 >= 0) ? model->index(row: start - 1, column: 0, parent).data() : QVariant();
781 c.next = (start < c.oldSize) ? model->index(row: start, column: 0, parent).data() : QVariant();
782 insert.push(t: c);
783}
784
785/*
786 Confirm that what was said was going to happen actually did
787
788 \sa rowsAboutToBeInserted()
789 */
790void QAbstractItemModelTesterPrivate::rowsInserted(const QModelIndex &parent, int start, int end)
791{
792 MODELTESTER_COMPARE(changeInFlight, ChangeInFlight::RowsInserted);
793 changeInFlight = ChangeInFlight::None;
794
795 qCDebug(lcModelTest) << "rowsInserted"
796 << "start=" << start << "end=" << end << "parent=" << parent
797 << "parent data=" << model->data(index: parent).toString()
798 << "current count of parent=" << model->rowCount(parent);
799
800 for (int i = start; i <= end; ++i) {
801 qCDebug(lcModelTest) << " itemWasInserted:" << i
802 << model->index(row: i, column: 0, parent).data();
803 }
804
805
806 Changing c = insert.pop();
807 MODELTESTER_COMPARE(parent, c.parent);
808
809 MODELTESTER_COMPARE(model->rowCount(parent), c.oldSize + (end - start + 1));
810 if (start - 1 >= 0)
811 MODELTESTER_COMPARE(model->data(model->index(start - 1, 0, c.parent)), c.last);
812
813 if (end + 1 < model->rowCount(parent: c.parent)) {
814 if (c.next != model->data(index: model->index(row: end + 1, column: 0, parent: c.parent))) {
815 qDebug() << start << end;
816 for (int i = 0; i < model->rowCount(); ++i)
817 qDebug() << model->index(row: i, column: 0).data().toString();
818 qDebug() << c.next << model->data(index: model->index(row: end + 1, column: 0, parent: c.parent));
819 }
820
821 MODELTESTER_COMPARE(model->data(model->index(end + 1, 0, c.parent)), c.next);
822 }
823}
824
825void QAbstractItemModelTesterPrivate::rowsAboutToBeMoved(const QModelIndex &sourceParent,
826 int sourceStart, int sourceEnd,
827 const QModelIndex &destinationParent,
828 int destinationRow)
829{
830 MODELTESTER_COMPARE(changeInFlight, ChangeInFlight::None);
831 changeInFlight = ChangeInFlight::RowsMoved;
832
833 qCDebug(lcModelTest) << "rowsAboutToBeMoved"
834 << "sourceStart=" << sourceStart << "sourceEnd=" << sourceEnd
835 << "sourceParent=" << sourceParent
836 << "sourceParent data=" << model->data(index: sourceParent).toString()
837 << "destinationParent=" << destinationParent
838 << "destinationRow=" << destinationRow;
839}
840
841void QAbstractItemModelTesterPrivate::rowsMoved(const QModelIndex &sourceParent, int sourceStart,
842 int sourceEnd, const QModelIndex &destinationParent,
843 int destinationRow)
844{
845 MODELTESTER_COMPARE(changeInFlight, ChangeInFlight::RowsMoved);
846 changeInFlight = ChangeInFlight::None;
847
848 qCDebug(lcModelTest) << "rowsMoved"
849 << "sourceStart=" << sourceStart << "sourceEnd=" << sourceEnd
850 << "sourceParent=" << sourceParent
851 << "sourceParent data=" << model->data(index: sourceParent).toString()
852 << "destinationParent=" << destinationParent
853 << "destinationRow=" << destinationRow;
854}
855
856void QAbstractItemModelTesterPrivate::layoutAboutToBeChanged()
857{
858 MODELTESTER_COMPARE(changeInFlight, ChangeInFlight::None);
859 changeInFlight = ChangeInFlight::LayoutChanged;
860
861 for (int i = 0; i < qBound(min: 0, val: model->rowCount(), max: 100); ++i)
862 changing.append(t: QPersistentModelIndex(model->index(row: i, column: 0)));
863}
864
865void QAbstractItemModelTesterPrivate::layoutChanged()
866{
867 MODELTESTER_COMPARE(changeInFlight, ChangeInFlight::LayoutChanged);
868 changeInFlight = ChangeInFlight::None;
869
870 for (int i = 0; i < changing.size(); ++i) {
871 QPersistentModelIndex p = changing[i];
872 MODELTESTER_COMPARE(model->index(p.row(), p.column(), p.parent()), QModelIndex(p));
873 }
874 changing.clear();
875}
876
877void QAbstractItemModelTesterPrivate::modelAboutToBeReset()
878{
879 MODELTESTER_COMPARE(changeInFlight, ChangeInFlight::None);
880 changeInFlight = ChangeInFlight::ModelReset;
881}
882
883void QAbstractItemModelTesterPrivate::modelReset()
884{
885 MODELTESTER_COMPARE(changeInFlight, ChangeInFlight::ModelReset);
886 changeInFlight = ChangeInFlight::None;
887}
888
889/*
890 Store what is about to be inserted to make sure it actually happens
891
892 \sa rowsRemoved()
893 */
894void QAbstractItemModelTesterPrivate::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end)
895{
896 MODELTESTER_COMPARE(changeInFlight, ChangeInFlight::None);
897 changeInFlight = ChangeInFlight::RowsRemoved;
898
899 qCDebug(lcModelTest) << "rowsAboutToBeRemoved"
900 << "start=" << start << "end=" << end << "parent=" << parent
901 << "parent data=" << model->data(index: parent).toString()
902 << "current count of parent=" << model->rowCount(parent)
903 << "last before removal=" << model->index(row: start - 1, column: 0, parent) << model->data(index: model->index(row: start - 1, column: 0, parent));
904
905 Changing c;
906 c.parent = parent;
907 c.oldSize = model->rowCount(parent);
908 if (start > 0 && model->columnCount(parent) > 0) {
909 const QModelIndex startIndex = model->index(row: start - 1, column: 0, parent);
910 MODELTESTER_VERIFY(startIndex.isValid());
911 c.last = model->data(index: startIndex);
912 }
913 if (end < c.oldSize - 1 && model->columnCount(parent) > 0) {
914 const QModelIndex endIndex = model->index(row: end + 1, column: 0, parent);
915 MODELTESTER_VERIFY(endIndex.isValid());
916 c.next = model->data(index: endIndex);
917 }
918
919 remove.push(t: c);
920}
921
922/*
923 Confirm that what was said was going to happen actually did
924
925 \sa rowsAboutToBeRemoved()
926 */
927void QAbstractItemModelTesterPrivate::rowsRemoved(const QModelIndex &parent, int start, int end)
928{
929 MODELTESTER_COMPARE(changeInFlight, ChangeInFlight::RowsRemoved);
930 changeInFlight = ChangeInFlight::None;
931
932 qCDebug(lcModelTest) << "rowsRemoved"
933 << "start=" << start << "end=" << end << "parent=" << parent
934 << "parent data=" << model->data(index: parent).toString()
935 << "current count of parent=" << model->rowCount(parent);
936
937 Changing c = remove.pop();
938 MODELTESTER_COMPARE(parent, c.parent);
939 MODELTESTER_COMPARE(model->rowCount(parent), c.oldSize - (end - start + 1));
940 if (start > 0)
941 MODELTESTER_COMPARE(model->data(model->index(start - 1, 0, c.parent)), c.last);
942 if (end < c.oldSize - 1)
943 MODELTESTER_COMPARE(model->data(model->index(start, 0, c.parent)), c.next);
944}
945
946void QAbstractItemModelTesterPrivate::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
947{
948 MODELTESTER_VERIFY(topLeft.isValid());
949 MODELTESTER_VERIFY(bottomRight.isValid());
950 QModelIndex commonParent = bottomRight.parent();
951 MODELTESTER_COMPARE(topLeft.parent(), commonParent);
952 MODELTESTER_VERIFY(topLeft.row() <= bottomRight.row());
953 MODELTESTER_VERIFY(topLeft.column() <= bottomRight.column());
954 int rowCount = model->rowCount(parent: commonParent);
955 int columnCount = model->columnCount(parent: commonParent);
956 MODELTESTER_VERIFY(bottomRight.row() < rowCount);
957 MODELTESTER_VERIFY(bottomRight.column() < columnCount);
958}
959
960void QAbstractItemModelTesterPrivate::headerDataChanged(Qt::Orientation orientation, int start, int end)
961{
962 MODELTESTER_VERIFY(start >= 0);
963 MODELTESTER_VERIFY(end >= 0);
964 MODELTESTER_VERIFY(start <= end);
965 int itemCount = orientation == Qt::Vertical ? model->rowCount() : model->columnCount();
966 MODELTESTER_VERIFY(start < itemCount);
967 MODELTESTER_VERIFY(end < itemCount);
968}
969
970bool QAbstractItemModelTesterPrivate::verify(bool statement,
971 const char *statementStr, const char *description,
972 const char *file, int line)
973{
974 static const char formatString[] = "FAIL! %s (%s) returned FALSE (%s:%d)";
975
976 switch (failureReportingMode) {
977 case QAbstractItemModelTester::FailureReportingMode::QtTest:
978 return QTest::qVerify(statement, statementStr, description, file, line);
979 break;
980
981 case QAbstractItemModelTester::FailureReportingMode::Warning:
982 if (!statement)
983 qCWarning(lcModelTest, formatString, statementStr, description, file, line);
984 break;
985
986 case QAbstractItemModelTester::FailureReportingMode::Fatal:
987 if (!statement)
988 qFatal(msg: formatString, statementStr, description, file, line);
989 break;
990 }
991
992 return statement;
993}
994
995
996template<typename T1, typename T2>
997bool QAbstractItemModelTesterPrivate::compare(const T1 &t1, const T2 &t2,
998 const char *actual, const char *expected,
999 const char *file, int line)
1000{
1001 const bool result = static_cast<bool>(t1 == t2);
1002
1003 static const char formatString[] = "FAIL! Compared values are not the same:\n Actual (%s) %s\n Expected (%s) %s\n (%s:%d)";
1004
1005 switch (failureReportingMode) {
1006 case QAbstractItemModelTester::FailureReportingMode::QtTest:
1007 return QTest::qCompare(t1, t2, actual, expected, file, line);
1008 break;
1009
1010 case QAbstractItemModelTester::FailureReportingMode::Warning:
1011 if (!result) {
1012 auto t1string = QTest::toString(t1);
1013 auto t2string = QTest::toString(t2);
1014 qCWarning(lcModelTest, formatString, actual, t1string ? t1string : "(nullptr)",
1015 expected, t2string ? t2string : "(nullptr)",
1016 file, line);
1017 delete [] t1string;
1018 delete [] t2string;
1019 }
1020 break;
1021
1022 case QAbstractItemModelTester::FailureReportingMode::Fatal:
1023 if (!result) {
1024 auto t1string = QTest::toString(t1);
1025 auto t2string = QTest::toString(t2);
1026 qFatal(formatString, actual, t1string ? t1string : "(nullptr)",
1027 expected, t2string ? t2string : "(nullptr)",
1028 file, line);
1029 delete [] t1string;
1030 delete [] t2string;
1031 }
1032 break;
1033 }
1034
1035 return result;
1036}
1037
1038
1039QT_END_NAMESPACE
1040
1041#include "moc_qabstractitemmodeltester.cpp"
1042

source code of qtbase/src/testlib/qabstractitemmodeltester.cpp