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 
49 QT_BEGIN_NAMESPACE
50 
51 Q_LOGGING_CATEGORY(lcModelTest, "qt.modeltest")
52 
53 #define MODELTESTER_VERIFY(statement) \
54 do { \
55     if (!verify(static_cast<bool>(statement), #statement, "", __FILE__, __LINE__)) \
56         return; \
57 } while (false)
58 
59 #define MODELTESTER_COMPARE(actual, expected) \
60 do { \
61     if (!compare((actual), (expected), #actual, #expected, __FILE__, __LINE__)) \
62         return; \
63 } while (false)
64 
65 class QAbstractItemModelTesterPrivate : public QObjectPrivate
66 {
67     Q_DECLARE_PUBLIC(QAbstractItemModelTester)
68 public:
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 
88 private:
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 */
QAbstractItemModelTester(QAbstractItemModel * model,QObject * parent)201 QAbstractItemModelTester::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 */
QAbstractItemModelTester(QAbstractItemModel * model,FailureReportingMode mode,QObject * parent)212 QAbstractItemModelTester::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 */
model() const274 QAbstractItemModel *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 */
failureReportingMode() const285 QAbstractItemModelTester::FailureReportingMode QAbstractItemModelTester::failureReportingMode() const
286 {
287     Q_D(const QAbstractItemModelTester);
288     return d->failureReportingMode;
289 }
290 
verify(bool statement,const char * statementStr,const char * description,const char * file,int line)291 bool 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 
QAbstractItemModelTesterPrivate(QAbstractItemModel * model,QAbstractItemModelTester::FailureReportingMode failureReportingMode)297 QAbstractItemModelTesterPrivate::QAbstractItemModelTesterPrivate(QAbstractItemModel *model, QAbstractItemModelTester::FailureReportingMode failureReportingMode)
298     : model(model),
299       failureReportingMode(failureReportingMode),
300       fetchingMore(false)
301 {
302 }
303 
runAllTests()304 void 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 */
nonDestructiveBasicTest()320 void 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  */
rowAndColumnCount()349 void 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  */
hasIndex()388 void 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  */
index()412 void 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  */
parent()435 void 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(0, 0, 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(topIndex) > 0 && model->columnCount(topIndex) > 0) {
458         QModelIndex childIndex = model->index(0, 0, 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(0, 1)) {
467         QModelIndex topIndex1 = model->index(0, 1, QModelIndex());
468         MODELTESTER_VERIFY(topIndex1.isValid());
469         if (model->rowCount(topIndex) > 0 && model->rowCount(topIndex1) > 0) {
470             QModelIndex childIndex = model->index(0, 0, topIndex);
471             MODELTESTER_VERIFY(childIndex.isValid());
472             QModelIndex childIndex1 = model->index(0, 0, 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(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  */
checkChildren(const QModelIndex & parent,int currentDepth)497 void 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(0, 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(r, 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(r, c, parent);
541             MODELTESTER_COMPARE(index, modifiedIndex);
542 
543             {
544                 const QModelIndex sibling = model->sibling(r, c, topLeftChild);
545                 MODELTESTER_COMPARE(index, sibling);
546             }
547             {
548                 const QModelIndex sibling = topLeftChild.sibling(r, 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(index) != parent) {
559                 qCWarning(lcModelTest) << "Inconsistent parent() implementation detected:";
560                 qCWarning(lcModelTest) << "    index=" << index << "exp. parent=" << parent << "act. parent=" << model->parent(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(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(index) && currentDepth < 10)
573                 checkChildren(index, ++currentDepth);
574 
575             // make sure that after testing the children that the index doesn't change.
576             QModelIndex newerIndex = model->index(r, c, parent);
577             MODELTESTER_COMPARE(persistentIndex, newerIndex);
578         }
579     }
580 }
581 
582 /*
583     Tests model's implementation of QAbstractItemModel::data()
584  */
data()585 void 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(model->index(0, 0), Qt::DisplayRole);
595     if (variant.isValid())
596         MODELTESTER_VERIFY(variant.canConvert<QString>());
597     variant = model->data(model->index(0, 0), Qt::ToolTipRole);
598     if (variant.isValid())
599         MODELTESTER_VERIFY(variant.canConvert<QString>());
600     variant = model->data(model->index(0, 0), Qt::StatusTipRole);
601     if (variant.isValid())
602         MODELTESTER_VERIFY(variant.canConvert<QString>());
603     variant = model->data(model->index(0, 0), 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(model->index(0, 0), 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(model->index(0, 0), Qt::TextAlignmentRole);
614     if (textAlignmentVariant.isValid()) {
615         Qt::Alignment alignment = qvariant_cast<Qt::Alignment>(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(model->index(0, 0), 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(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  */
rowsAboutToBeInserted(const QModelIndex & parent,int start,int end)639 void 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(parent).toString()
644                          << "current count of parent=" << model->rowCount(parent)
645                          << "last before insertion=" << model->index(start - 1, 0, parent) << model->data(model->index(start - 1, 0, parent));
646 
647     Changing c;
648     c.parent = parent;
649     c.oldSize = model->rowCount(parent);
650     c.last = (start - 1 >= 0) ? model->index(start - 1, 0, parent).data() : QVariant();
651     c.next = (start < c.oldSize) ? model->index(start, 0, parent).data() : QVariant();
652     insert.push(c);
653 }
654 
655 /*
656     Confirm that what was said was going to happen actually did
657 
658     \sa rowsAboutToBeInserted()
659  */
rowsInserted(const QModelIndex & parent,int start,int end)660 void 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(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(i, 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(c.parent)) {
681         if (c.next != model->data(model->index(end + 1, 0, c.parent))) {
682             qDebug() << start << end;
683             for (int i = 0; i < model->rowCount(); ++i)
684                 qDebug() << model->index(i, 0).data().toString();
685             qDebug() << c.next << model->data(model->index(end + 1, 0, c.parent));
686         }
687 
688         MODELTESTER_COMPARE(model->data(model->index(end + 1, 0, c.parent)), c.next);
689     }
690 }
691 
layoutAboutToBeChanged()692 void QAbstractItemModelTesterPrivate::layoutAboutToBeChanged()
693 {
694     for (int i = 0; i < qBound(0, model->rowCount(), 100); ++i)
695         changing.append(QPersistentModelIndex(model->index(i, 0)));
696 }
697 
layoutChanged()698 void 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  */
rowsAboutToBeRemoved(const QModelIndex & parent,int start,int end)712 void 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(parent).toString()
717                          << "current count of parent=" << model->rowCount(parent)
718                          << "last before removal=" << model->index(start - 1, 0, parent) << model->data(model->index(start - 1, 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(start - 1, 0, parent);
725         MODELTESTER_VERIFY(startIndex.isValid());
726         c.last = model->data(startIndex);
727     }
728     if (end < c.oldSize - 1 && model->columnCount(parent) > 0) {
729         const QModelIndex endIndex = model->index(end + 1, 0, parent);
730         MODELTESTER_VERIFY(endIndex.isValid());
731         c.next = model->data(endIndex);
732     }
733 
734     remove.push(c);
735 }
736 
737 /*
738     Confirm that what was said was going to happen actually did
739 
740     \sa rowsAboutToBeRemoved()
741  */
rowsRemoved(const QModelIndex & parent,int start,int end)742 void 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(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 
dataChanged(const QModelIndex & topLeft,const QModelIndex & bottomRight)758 void 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(commonParent);
767     int columnCount = model->columnCount(commonParent);
768     MODELTESTER_VERIFY(bottomRight.row() < rowCount);
769     MODELTESTER_VERIFY(bottomRight.column() < columnCount);
770 }
771 
headerDataChanged(Qt::Orientation orientation,int start,int end)772 void 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 
verify(bool statement,const char * statementStr,const char * description,const char * file,int line)782 bool 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(formatString, statementStr, description, file, line);
801         break;
802     }
803 
804     return statement;
805 }
806 
807 
808 template<typename T1, typename T2>
compare(const T1 & t1,const T2 & t2,const char * actual,const char * expected,const char * file,int line)809 bool 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(formatString, actual, t1string, expected, t2string, file, line);
837             delete [] t1string;
838             delete [] t2string;
839         }
840         break;
841     }
842 
843     return result;
844 }
845 
846 
847 QT_END_NAMESPACE
848