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

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