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("%s: model must not be null", Q_FUNC_INFO);
217
218 Q_D(QAbstractItemModelTester);
219
220 auto runAllTests = [d] { d->runAllTests(); };
221
222 connect(model, &QAbstractItemModel::columnsAboutToBeInserted,
223 this, runAllTests);
224 connect(model, &QAbstractItemModel::columnsAboutToBeRemoved,
225 this, runAllTests);
226 connect(model, &QAbstractItemModel::columnsInserted,
227 this, runAllTests);
228 connect(model, &QAbstractItemModel::columnsRemoved,
229 this, runAllTests);
230 connect(model, &QAbstractItemModel::dataChanged,
231 this, runAllTests);
232 connect(model, &QAbstractItemModel::headerDataChanged,
233 this, runAllTests);
234 connect(model, &QAbstractItemModel::layoutAboutToBeChanged,
235 this, runAllTests);
236 connect(model, &QAbstractItemModel::layoutChanged,
237 this, runAllTests);
238 connect(model, &QAbstractItemModel::modelReset,
239 this, runAllTests);
240 connect(model, &QAbstractItemModel::rowsAboutToBeInserted,
241 this, runAllTests);
242 connect(model, &QAbstractItemModel::rowsAboutToBeRemoved,
243 this, runAllTests);
244 connect(model, &QAbstractItemModel::rowsInserted,
245 this, runAllTests);
246 connect(model, &QAbstractItemModel::rowsRemoved,
247 this, runAllTests);
248
249 // Special checks for changes
250 connect(model, &QAbstractItemModel::layoutAboutToBeChanged,
251 this, [d]{ d->layoutAboutToBeChanged(); });
252 connect(model, &QAbstractItemModel::layoutChanged,
253 this, [d]{ d->layoutChanged(); });
254
255 connect(model, &QAbstractItemModel::rowsAboutToBeInserted,
256 this, [d](const QModelIndex &parent, int start, int end) { d->rowsAboutToBeInserted(parent, start, end); });
257 connect(model, &QAbstractItemModel::rowsAboutToBeRemoved,
258 this, [d](const QModelIndex &parent, int start, int end) { d->rowsAboutToBeRemoved(parent, start, end); });
259 connect(model, &QAbstractItemModel::rowsInserted,
260 this, [d](const QModelIndex &parent, int start, int end) { d->rowsInserted(parent, start, end); });
261 connect(model, &QAbstractItemModel::rowsRemoved,
262 this, [d](const QModelIndex &parent, int start, int end) { d->rowsRemoved(parent, start, end); });
263 connect(model, &QAbstractItemModel::dataChanged,
264 this, [d](const QModelIndex &topLeft, const QModelIndex &bottomRight) { d->dataChanged(topLeft, bottomRight); });
265 connect(model, &QAbstractItemModel::headerDataChanged,
266 this, [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(QModelIndex());
324 MODELTESTER_VERIFY(model->columnCount(QModelIndex()) >= 0);
325 fetchingMore = true;
326 model->fetchMore(QModelIndex());
327 fetchingMore = false;
328 Qt::ItemFlags flags = model->flags(QModelIndex());
329 MODELTESTER_VERIFY(flags == Qt::ItemIsDropEnabled || flags == 0);
330 model->hasChildren(QModelIndex());
331 const bool hasRow = model->hasIndex(0, 0);
332 QVariant cache;
333 if (hasRow)
334 model->match(model->index(0, 0), -1, cache);
335 model->mimeTypes();
336 MODELTESTER_VERIFY(!model->parent(QModelIndex()).isValid());
337 MODELTESTER_VERIFY(model->rowCount() >= 0);
338 model->span(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(0, 0, QModelIndex());
355
356 // check top row
357 int rows = model->rowCount(topIndex);
358 MODELTESTER_VERIFY(rows >= 0);
359
360 int columns = model->columnCount(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(0, 0, topIndex);
369 MODELTESTER_VERIFY(secondLevelIndex.isValid());
370
371 rows = model->rowCount(secondLevelIndex);
372 MODELTESTER_VERIFY(rows >= 0);
373
374 columns = model->columnCount(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->hasChildren())
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(0, 0, QModelIndex());
452 MODELTESTER_VERIFY(!model->parent(topIndex).isValid());
453
454 // Common error test #2, make sure that a second level index has a parent
455 // that is the first level index.
456 if (model->hasChildren(topIndex)) {
457 QModelIndex childIndex = model->index(0, 0, topIndex);
458 MODELTESTER_VERIFY(childIndex.isValid());
459 MODELTESTER_COMPARE(model->parent(childIndex), topIndex);
460 }
461
462 // Common error test #3, the second column should NOT have the same children
463 // as the first column in a row.
464 // Usually the second column shouldn't have children.
465 if (model->hasIndex(0, 1)) {
466 QModelIndex topIndex1 = model->index(0, 1, QModelIndex());
467 MODELTESTER_VERIFY(topIndex1.isValid());
468 if (model->hasChildren(topIndex) && model->hasChildren(topIndex1)) {
469 QModelIndex childIndex = model->index(0, 0, topIndex);
470 MODELTESTER_VERIFY(childIndex.isValid());
471 QModelIndex childIndex1 = model->index(0, 0, topIndex1);
472 MODELTESTER_VERIFY(childIndex1.isValid());
473 MODELTESTER_VERIFY(childIndex != childIndex1);
474 }
475 }
476
477 // Full test, walk n levels deep through the model making sure that all
478 // parent's children correctly specify their parent.
479 checkChildren(QModelIndex());
480}
481
482/*
483 Called from the parent() test.
484
485 A model that returns an index of parent X should also return X when asking
486 for the parent of the index.
487
488 This recursive function does pretty extensive testing on the whole model in an
489 effort to catch edge cases.
490
491 This function assumes that rowCount(), columnCount() and index() already work.
492 If they have a bug it will point it out, but the above tests should have already
493 found the basic bugs because it is easier to figure out the problem in
494 those tests then this one.
495 */
496void QAbstractItemModelTesterPrivate::checkChildren(const QModelIndex &parent, int currentDepth)
497{
498 // First just try walking back up the tree.
499 QModelIndex p = parent;
500 while (p.isValid())
501 p = p.parent();
502
503 // For models that are dynamically populated
504 if (model->canFetchMore(parent)) {
505 fetchingMore = true;
506 model->fetchMore(parent);
507 fetchingMore = false;
508 }
509
510 const int rows = model->rowCount(parent);
511 const int columns = model->columnCount(parent);
512
513 if (rows > 0)
514 MODELTESTER_VERIFY(model->hasChildren(parent));
515
516 // Some further testing against rows(), columns(), and hasChildren()
517 MODELTESTER_VERIFY(rows >= 0);
518 MODELTESTER_VERIFY(columns >= 0);
519 if (rows > 0 && columns > 0)
520 MODELTESTER_VERIFY(model->hasChildren(parent));
521
522 const QModelIndex topLeftChild = model->index(0, 0, parent);
523
524 MODELTESTER_VERIFY(!model->hasIndex(rows, 0, parent));
525 MODELTESTER_VERIFY(!model->hasIndex(rows + 1, 0, parent));
526
527 for (int r = 0; r < rows; ++r) {
528 MODELTESTER_VERIFY(!model->hasIndex(r, columns, parent));
529 MODELTESTER_VERIFY(!model->hasIndex(r, columns + 1, parent));
530 for (int c = 0; c < columns; ++c) {
531 MODELTESTER_VERIFY(model->hasIndex(r, c, parent));
532 QModelIndex index = model->index(r, c, parent);
533 // rowCount() and columnCount() said that it existed...
534 if (!index.isValid())
535 qCWarning(lcModelTest) << "Got invalid index at row=" << r << "col=" << c << "parent=" << parent;
536 MODELTESTER_VERIFY(index.isValid());
537
538 // index() should always return the same index when called twice in a row
539 QModelIndex modifiedIndex = model->index(r, c, parent);
540 MODELTESTER_COMPARE(index, modifiedIndex);
541
542 {
543 const QModelIndex sibling = model->sibling(r, c, topLeftChild);
544 MODELTESTER_COMPARE(index, sibling);
545 }
546 {
547 const QModelIndex sibling = topLeftChild.sibling(r, c);
548 MODELTESTER_COMPARE(index, sibling);
549 }
550
551 // Some basic checking on the index that is returned
552 MODELTESTER_COMPARE(index.model(), model);
553 MODELTESTER_COMPARE(index.row(), r);
554 MODELTESTER_COMPARE(index.column(), c);
555
556 // If the next test fails here is some somewhat useful debug you play with.
557 if (model->parent(index) != parent) {
558 qCWarning(lcModelTest) << "Inconsistent parent() implementation detected:";
559 qCWarning(lcModelTest) << " index=" << index << "exp. parent=" << parent << "act. parent=" << model->parent(index);
560 qCWarning(lcModelTest) << " row=" << r << "col=" << c << "depth=" << currentDepth;
561 qCWarning(lcModelTest) << " data for child" << model->data(index).toString();
562 qCWarning(lcModelTest) << " data for parent" << model->data(parent).toString();
563 }
564
565 // Check that we can get back our real parent.
566 MODELTESTER_COMPARE(model->parent(index), parent);
567
568 QPersistentModelIndex persistentIndex = index;
569
570 // recursively go down the children
571 if (model->hasChildren(index) && currentDepth < 10)
572 checkChildren(index, ++currentDepth);
573
574 // make sure that after testing the children that the index doesn't change.
575 QModelIndex newerIndex = model->index(r, c, parent);
576 MODELTESTER_COMPARE(persistentIndex, newerIndex);
577 }
578 }
579}
580
581/*
582 Tests model's implementation of QAbstractItemModel::data()
583 */
584void QAbstractItemModelTesterPrivate::data()
585{
586 if (!model->hasChildren())
587 return;
588
589 MODELTESTER_VERIFY(model->index(0, 0).isValid());
590
591 // General Purpose roles that should return a QString
592 QVariant variant;
593 variant = model->data(model->index(0, 0), Qt::DisplayRole);
594 if (variant.isValid())
595 MODELTESTER_VERIFY(variant.canConvert<QString>());
596 variant = model->data(model->index(0, 0), Qt::ToolTipRole);
597 if (variant.isValid())
598 MODELTESTER_VERIFY(variant.canConvert<QString>());
599 variant = model->data(model->index(0, 0), Qt::StatusTipRole);
600 if (variant.isValid())
601 MODELTESTER_VERIFY(variant.canConvert<QString>());
602 variant = model->data(model->index(0, 0), Qt::WhatsThisRole);
603 if (variant.isValid())
604 MODELTESTER_VERIFY(variant.canConvert<QString>());
605
606 // General Purpose roles that should return a QSize
607 variant = model->data(model->index(0, 0), Qt::SizeHintRole);
608 if (variant.isValid())
609 MODELTESTER_VERIFY(variant.canConvert<QSize>());
610
611 // Check that the alignment is one we know about
612 QVariant textAlignmentVariant = model->data(model->index(0, 0), Qt::TextAlignmentRole);
613 if (textAlignmentVariant.isValid()) {
614 Qt::Alignment alignment = qvariant_cast<Qt::Alignment>(textAlignmentVariant);
615 MODELTESTER_COMPARE(alignment, (alignment & (Qt::AlignHorizontal_Mask | Qt::AlignVertical_Mask)));
616 }
617
618 // Check that the "check state" is one we know about.
619 QVariant checkStateVariant = model->data(model->index(0, 0), Qt::CheckStateRole);
620 if (checkStateVariant.isValid()) {
621 int state = checkStateVariant.toInt();
622 MODELTESTER_VERIFY(state == Qt::Unchecked
623 || state == Qt::PartiallyChecked
624 || state == Qt::Checked);
625 }
626
627 Q_Q(QAbstractItemModelTester);
628
629 if (!QTestPrivate::testDataGuiRoles(q))
630 return;
631}
632
633/*
634 Store what is about to be inserted to make sure it actually happens
635
636 \sa rowsInserted()
637 */
638void QAbstractItemModelTesterPrivate::rowsAboutToBeInserted(const QModelIndex &parent, int start, int end)
639{
640 qCDebug(lcModelTest) << "rowsAboutToBeInserted"
641 << "start=" << start << "end=" << end << "parent=" << parent
642 << "parent data=" << model->data(parent).toString()
643 << "current count of parent=" << model->rowCount(parent)
644 << "last before insertion=" << model->index(start - 1, 0, parent) << model->data(model->index(start - 1, 0, parent));
645
646 Changing c;
647 c.parent = parent;
648 c.oldSize = model->rowCount(parent);
649 c.last = (start - 1 >= 0) ? model->index(start - 1, 0, parent).data() : QVariant();
650 c.next = (start < c.oldSize) ? model->index(start, 0, parent).data() : QVariant();
651 insert.push(c);
652}
653
654/*
655 Confirm that what was said was going to happen actually did
656
657 \sa rowsAboutToBeInserted()
658 */
659void QAbstractItemModelTesterPrivate::rowsInserted(const QModelIndex &parent, int start, int end)
660{
661 qCDebug(lcModelTest) << "rowsInserted"
662 << "start=" << start << "end=" << end << "parent=" << parent
663 << "parent data=" << model->data(parent).toString()
664 << "current count of parent=" << model->rowCount(parent);
665
666 for (int i = start; i <= end; ++i) {
667 qCDebug(lcModelTest) << " itemWasInserted:" << i
668 << model->index(i, 0, parent).data();
669 }
670
671
672 Changing c = insert.pop();
673 MODELTESTER_COMPARE(parent, c.parent);
674
675 MODELTESTER_COMPARE(model->rowCount(parent), c.oldSize + (end - start + 1));
676 if (start - 1 >= 0)
677 MODELTESTER_COMPARE(model->data(model->index(start - 1, 0, c.parent)), c.last);
678
679 if (end + 1 < model->rowCount(c.parent)) {
680 if (c.next != model->data(model->index(end + 1, 0, c.parent))) {
681 qDebug() << start << end;
682 for (int i = 0; i < model->rowCount(); ++i)
683 qDebug() << model->index(i, 0).data().toString();
684 qDebug() << c.next << model->data(model->index(end + 1, 0, c.parent));
685 }
686
687 MODELTESTER_COMPARE(model->data(model->index(end + 1, 0, c.parent)), c.next);
688 }
689}
690
691void QAbstractItemModelTesterPrivate::layoutAboutToBeChanged()
692{
693 for (int i = 0; i < qBound(0, model->rowCount(), 100); ++i)
694 changing.append(QPersistentModelIndex(model->index(i, 0)));
695}
696
697void QAbstractItemModelTesterPrivate::layoutChanged()
698{
699 for (int i = 0; i < changing.count(); ++i) {
700 QPersistentModelIndex p = changing[i];
701 MODELTESTER_COMPARE(model->index(p.row(), p.column(), p.parent()), QModelIndex(p));
702 }
703 changing.clear();
704}
705
706/*
707 Store what is about to be inserted to make sure it actually happens
708
709 \sa rowsRemoved()
710 */
711void QAbstractItemModelTesterPrivate::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end)
712{
713 qCDebug(lcModelTest) << "rowsAboutToBeRemoved"
714 << "start=" << start << "end=" << end << "parent=" << parent
715 << "parent data=" << model->data(parent).toString()
716 << "current count of parent=" << model->rowCount(parent)
717 << "last before removal=" << model->index(start - 1, 0, parent) << model->data(model->index(start - 1, 0, parent));
718
719 Changing c;
720 c.parent = parent;
721 c.oldSize = model->rowCount(parent);
722 if (start > 0) {
723 const QModelIndex startIndex = model->index(start - 1, 0, parent);
724 MODELTESTER_VERIFY(startIndex.isValid());
725 c.last = model->data(startIndex);
726 }
727 if (end < c.oldSize - 1) {
728 const QModelIndex endIndex = model->index(end + 1, 0, parent);
729 MODELTESTER_VERIFY(endIndex.isValid());
730 c.next = model->data(endIndex);
731 }
732
733 remove.push(c);
734}
735
736/*
737 Confirm that what was said was going to happen actually did
738
739 \sa rowsAboutToBeRemoved()
740 */
741void QAbstractItemModelTesterPrivate::rowsRemoved(const QModelIndex &parent, int start, int end)
742{
743 qCDebug(lcModelTest) << "rowsRemoved"
744 << "start=" << start << "end=" << end << "parent=" << parent
745 << "parent data=" << model->data(parent).toString()
746 << "current count of parent=" << model->rowCount(parent);
747
748 Changing c = remove.pop();
749 MODELTESTER_COMPARE(parent, c.parent);
750 MODELTESTER_COMPARE(model->rowCount(parent), c.oldSize - (end - start + 1));
751 if (start > 0)
752 MODELTESTER_COMPARE(model->data(model->index(start - 1, 0, c.parent)), c.last);
753 if (end < c.oldSize - 1)
754 MODELTESTER_COMPARE(model->data(model->index(start, 0, c.parent)), c.next);
755}
756
757void QAbstractItemModelTesterPrivate::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
758{
759 MODELTESTER_VERIFY(topLeft.isValid());
760 MODELTESTER_VERIFY(bottomRight.isValid());
761 QModelIndex commonParent = bottomRight.parent();
762 MODELTESTER_COMPARE(topLeft.parent(), commonParent);
763 MODELTESTER_VERIFY(topLeft.row() <= bottomRight.row());
764 MODELTESTER_VERIFY(topLeft.column() <= bottomRight.column());
765 int rowCount = model->rowCount(commonParent);
766 int columnCount = model->columnCount(commonParent);
767 MODELTESTER_VERIFY(bottomRight.row() < rowCount);
768 MODELTESTER_VERIFY(bottomRight.column() < columnCount);
769}
770
771void QAbstractItemModelTesterPrivate::headerDataChanged(Qt::Orientation orientation, int start, int end)
772{
773 MODELTESTER_VERIFY(start >= 0);
774 MODELTESTER_VERIFY(end >= 0);
775 MODELTESTER_VERIFY(start <= end);
776 int itemCount = orientation == Qt::Vertical ? model->rowCount() : model->columnCount();
777 MODELTESTER_VERIFY(start < itemCount);
778 MODELTESTER_VERIFY(end < itemCount);
779}
780
781bool QAbstractItemModelTesterPrivate::verify(bool statement,
782 const char *statementStr, const char *description,
783 const char *file, int line)
784{
785 static const char formatString[] = "FAIL! %s (%s) returned FALSE (%s:%d)";
786
787 switch (failureReportingMode) {
788 case QAbstractItemModelTester::FailureReportingMode::QtTest:
789 return QTest::qVerify(statement, statementStr, description, file, line);
790 break;
791
792 case QAbstractItemModelTester::FailureReportingMode::Warning:
793 if (!statement)
794 qCWarning(lcModelTest, formatString, statementStr, description, file, line);
795 break;
796
797 case QAbstractItemModelTester::FailureReportingMode::Fatal:
798 if (!statement)
799 qFatal(formatString, statementStr, description, file, line);
800 break;
801 }
802
803 return statement;
804}
805
806
807template<typename T1, typename T2>
808bool QAbstractItemModelTesterPrivate::compare(const T1 &t1, const T2 &t2,
809 const char *actual, const char *expected,
810 const char *file, int line)
811{
812 const bool result = static_cast<bool>(t1 == t2);
813
814 static const char formatString[] = "FAIL! Compared values are not the same:\n Actual (%s) %s\n Expected (%s) %s\n (%s:%d)";
815
816 switch (failureReportingMode) {
817 case QAbstractItemModelTester::FailureReportingMode::QtTest:
818 return QTest::qCompare(t1, t2, actual, expected, file, line);
819 break;
820
821 case QAbstractItemModelTester::FailureReportingMode::Warning:
822 if (!result) {
823 auto t1string = QTest::toString(t1);
824 auto t2string = QTest::toString(t2);
825 qCWarning(lcModelTest, formatString, actual, t1string ? t1string : "(nullptr)",
826 expected, t2string ? t2string : "(nullptr)",
827 file, line);
828 delete [] t1string;
829 delete [] t2string;
830 }
831 break;
832
833 case QAbstractItemModelTester::FailureReportingMode::Fatal:
834 if (!result) {
835 auto t1string = QTest::toString(t1);
836 auto t2string = QTest::toString(t2);
837 qFatal(formatString, actual, t1string ? t1string : "(nullptr)",
838 expected, t2string ? t2string : "(nullptr)",
839 file, line);
840 delete [] t1string;
841 delete [] t2string;
842 }
843 break;
844 }
845
846 return result;
847}
848
849
850QT_END_NAMESPACE
851