1 /****************************************************************************
2 **
3 ** Copyright (C) 2007 Trolltech ASA. All rights reserved.
4 **
5 ** This file is part of the Qt Concurrent project on Trolltech Labs.
6 **
7 ** This file may be used under the terms of the GNU General Public
8 ** License version 2.0 as published by the Free Software Foundation
9 ** and appearing in the file LICENSE.GPL included in the packaging of
10 ** this file.  Please review the following information to ensure GNU
11 ** General Public Licensing requirements will be met:
12 ** http://www.trolltech.com/products/qt/opensource.html
13 **
14 ** If you are unsure which license is appropriate for your use, please
15 ** review the following information:
16 ** http://www.trolltech.com/products/qt/licensing.html or contact the
17 ** sales department at sales@trolltech.com.
18 **
19 ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
20 ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
21 **
22 ****************************************************************************/
23 
24 #include "modeltest.h"
25 
26 #include <QSize>
27 
Q_DECLARE_METATYPE(QModelIndex)28 Q_DECLARE_METATYPE(QModelIndex)
29 
30 /*!
31     Connect to all of the models signals.  Whenever anything happens recheck everything.
32 */
33 ModelTest::ModelTest(QAbstractItemModel *_model, QObject *parent) : QObject(parent), model(_model), fetchingMore(false)
34 {
35     Q_ASSERT(model);
36 
37     connect(model, &QAbstractItemModel::columnsAboutToBeInserted,
38             this, &ModelTest::runAllTests);
39     connect(model, &QAbstractItemModel::columnsAboutToBeRemoved,
40             this, &ModelTest::runAllTests);
41     connect(model, &QAbstractItemModel::columnsInserted,
42             this, &ModelTest::runAllTests);
43     connect(model, &QAbstractItemModel::columnsRemoved,
44             this, &ModelTest::runAllTests);
45     connect(model, &QAbstractItemModel::dataChanged,
46             this, &ModelTest::runAllTests);
47     connect(model, &QAbstractItemModel::headerDataChanged,
48             this, &ModelTest::runAllTests);
49     connect(model, &QAbstractItemModel::layoutAboutToBeChanged,
50             this, &ModelTest::runAllTests);
51     connect(model, &QAbstractItemModel::layoutChanged,
52             this, &ModelTest::runAllTests);
53     connect(model, &QAbstractItemModel::modelReset,
54             this, &ModelTest::runAllTests);
55     connect(model, &QAbstractItemModel::rowsAboutToBeInserted,
56             this, &ModelTest::runAllTests);
57     connect(model, &QAbstractItemModel::rowsAboutToBeRemoved,
58             this, &ModelTest::runAllTests);
59     connect(model, &QAbstractItemModel::rowsInserted,
60             this, &ModelTest::runAllTests);
61     connect(model, &QAbstractItemModel::rowsRemoved,
62             this, &ModelTest::runAllTests);
63 
64     // Special checks for inserting/removing
65     connect(model, &QAbstractItemModel::rowsAboutToBeInserted, this, &ModelTest::rowsAboutToBeInserted);
66     connect(model, &QAbstractItemModel::rowsAboutToBeRemoved, this, &ModelTest::rowsAboutToBeRemoved);
67     connect(model, &QAbstractItemModel::rowsInserted, this, &ModelTest::rowsInserted);
68     connect(model, &QAbstractItemModel::rowsRemoved, this, &ModelTest::rowsRemoved);
69 
70     runAllTests();
71 }
72 
runAllTests()73 void ModelTest::runAllTests()
74 {
75     if (fetchingMore) {
76         return;
77     }
78     nonDestructiveBasicTest();
79     rowCount();
80     columnCount();
81     hasIndex();
82     index();
83     parent();
84     data();
85 }
86 
87 /*!
88     nonDestructiveBasicTest tries to call a number of the basic functions (not all)
89     to make sure the model doesn't outright segfault, testing the functions that makes sense.
90 */
nonDestructiveBasicTest()91 void ModelTest::nonDestructiveBasicTest()
92 {
93     Q_ASSERT(model->buddy(QModelIndex()) == QModelIndex());
94     model->canFetchMore(QModelIndex());
95     Q_ASSERT(model->columnCount(QModelIndex()) >= 0);
96     Q_ASSERT(model->data(QModelIndex()) == QVariant());
97     fetchingMore = true;
98     model->fetchMore(QModelIndex());
99     fetchingMore = false;
100     Qt::ItemFlags flags = model->flags(QModelIndex());
101     Q_ASSERT(flags == Qt::ItemIsDropEnabled || flags == 0);
102     model->hasChildren(QModelIndex());
103     model->hasIndex(0, 0);
104     model->headerData(0, Qt::Horizontal);
105     model->index(0, 0);
106     model->itemData(QModelIndex());
107     QVariant cache;
108     model->match(QModelIndex(), -1, cache);
109     model->mimeTypes();
110     Q_ASSERT(model->parent(QModelIndex()) == QModelIndex());
111     Q_ASSERT(model->rowCount() >= 0);
112     QVariant variant;
113     model->setData(QModelIndex(), variant, -1);
114     model->setHeaderData(-1, Qt::Horizontal, QVariant());
115     model->setHeaderData(0, Qt::Horizontal, QVariant());
116     model->setHeaderData(999999, Qt::Horizontal, QVariant());
117     QMap<int, QVariant> roles;
118     model->sibling(0, 0, QModelIndex());
119     model->span(QModelIndex());
120     model->supportedDropActions();
121 }
122 
123 /*!
124     Tests model's implementation of QAbstractItemModel::rowCount() and hasChildren()
125 
126     Models that are dynamically populated are not as fully tested here.
127  */
rowCount()128 void ModelTest::rowCount()
129 {
130     // check top row
131     QModelIndex topIndex = model->index(0, 0, QModelIndex());
132     int rows = model->rowCount(topIndex);
133     Q_ASSERT(rows >= 0);
134     if (rows > 0) {
135         Q_ASSERT(model->hasChildren(topIndex) == true);
136     }
137 
138     QModelIndex secondLevelIndex = model->index(0, 0, topIndex);
139     if (secondLevelIndex.isValid()) { // not the top level
140         // check a row count where parent is valid
141         rows = model->rowCount(secondLevelIndex);
142         Q_ASSERT(rows >= 0);
143         if (rows > 0) {
144             Q_ASSERT(model->hasChildren(secondLevelIndex) == true);
145         }
146     }
147 
148     // The models rowCount() is tested more extensively in checkChildren(),
149     // but this catches the big mistakes
150 }
151 
152 /*!
153     Tests model's implementation of QAbstractItemModel::columnCount() and hasChildren()
154  */
columnCount()155 void ModelTest::columnCount()
156 {
157     // check top row
158     QModelIndex topIndex = model->index(0, 0, QModelIndex());
159     Q_ASSERT(model->columnCount(topIndex) >= 0);
160 
161     // check a column count where parent is valid
162     QModelIndex childIndex = model->index(0, 0, topIndex);
163     if (childIndex.isValid()) {
164         Q_ASSERT(model->columnCount(childIndex) >= 0);
165     }
166 
167     // columnCount() is tested more extensively in checkChildren(),
168     // but this catches the big mistakes
169 }
170 
171 /*!
172     Tests model's implementation of QAbstractItemModel::hasIndex()
173  */
hasIndex()174 void ModelTest::hasIndex()
175 {
176     // Make sure that invalid values returns an invalid index
177     Q_ASSERT(model->hasIndex(-2, -2) == false);
178     Q_ASSERT(model->hasIndex(-2, 0) == false);
179     Q_ASSERT(model->hasIndex(0, -2) == false);
180 
181     int rows = model->rowCount();
182     int columns = model->columnCount();
183 
184     // check out of bounds
185     Q_ASSERT(model->hasIndex(rows, columns) == false);
186     Q_ASSERT(model->hasIndex(rows + 1, columns + 1) == false);
187 
188     if (rows > 0) {
189         Q_ASSERT(model->hasIndex(0, 0) == true);
190     }
191 
192     // hasIndex() is tested more extensively in checkChildren(),
193     // but this catches the big mistakes
194 }
195 
196 /*!
197     Tests model's implementation of QAbstractItemModel::index()
198  */
index()199 void ModelTest::index()
200 {
201     // Make sure that invalid values returns an invalid index
202     Q_ASSERT(model->index(-2, -2) == QModelIndex());
203     Q_ASSERT(model->index(-2, 0) == QModelIndex());
204     Q_ASSERT(model->index(0, -2) == QModelIndex());
205 
206     int rows = model->rowCount();
207     int columns = model->columnCount();
208 
209     if (rows == 0) {
210         return;
211     }
212 
213     // Catch off by one errors
214     Q_ASSERT(model->index(rows, columns) == QModelIndex());
215     Q_ASSERT(model->index(0, 0).isValid() == true);
216 
217     // Make sure that the same index is *always* returned
218     QModelIndex a = model->index(0, 0);
219     QModelIndex b = model->index(0, 0);
220     Q_ASSERT(a == b);
221 
222     // index() is tested more extensively in checkChildren(),
223     // but this catches the big mistakes
224 }
225 
226 /*!
227     Tests model's implementation of QAbstractItemModel::parent()
228  */
parent()229 void ModelTest::parent()
230 {
231     // Make sure the model wont crash and will return an invalid QModelIndex
232     // when asked for the parent of an invalid index.
233     Q_ASSERT(model->parent(QModelIndex()) == QModelIndex());
234 
235     if (model->rowCount() == 0) {
236         return;
237     }
238 
239     // Column 0                | Column 1    |
240     // QModelIndex()           |             |
241     //    \- topIndex          | topIndex1   |
242     //         \- childIndex   | childIndex1 |
243 
244     // Common error test #1, make sure that a top level index has a parent
245     // that is a invalid QModelIndex.
246     QModelIndex topIndex = model->index(0, 0, QModelIndex());
247     Q_ASSERT(model->parent(topIndex) == QModelIndex());
248 
249     // Common error test #2, make sure that a second level index has a parent
250     // that is the first level index.
251     if (model->rowCount(topIndex) > 0) {
252         QModelIndex childIndex = model->index(0, 0, topIndex);
253         Q_ASSERT(model->parent(childIndex) == topIndex);
254     }
255 
256     // Common error test #3, the second column should NOT have the same children
257     // as the first column in a row.
258     // Usually the second column shouldn't have children.
259     QModelIndex topIndex1 = model->index(0, 1, QModelIndex());
260     if (model->rowCount(topIndex1) > 0) {
261         QModelIndex childIndex = model->index(0, 0, topIndex);
262         QModelIndex childIndex1 = model->index(0, 0, topIndex1);
263         Q_ASSERT(childIndex != childIndex1);
264     }
265 
266     // Full test, walk n levels deep through the model making sure that all
267     // parent's children correctly specify their parent.
268     checkChildren(QModelIndex());
269 }
270 
271 /*!
272     Called from the parent() test.
273 
274     A model that returns an index of parent X should also return X when asking
275     for the parent of the index.
276 
277     This recursive function does pretty extensive testing on the whole model in an
278     effort to catch edge cases.
279 
280     This function assumes that rowCount(), columnCount() and index() already work.
281     If they have a bug it will point it out, but the above tests should have already
282     found the basic bugs because it is easier to figure out the problem in
283     those tests then this one.
284  */
checkChildren(const QModelIndex & parent,int currentDepth)285 void ModelTest::checkChildren(const QModelIndex &parent, int currentDepth)
286 {
287     // First just try walking back up the tree.
288     QModelIndex p = parent;
289     while (p.isValid()) {
290         p = p.parent();
291     }
292 
293     // For models that are dynamically populated
294     if (model->canFetchMore(parent)) {
295         fetchingMore = true;
296         model->fetchMore(parent);
297         fetchingMore = false;
298     }
299 
300     int rows = model->rowCount(parent);
301     int columns = model->columnCount(parent);
302 
303     if (rows > 0) {
304         Q_ASSERT(model->hasChildren(parent));
305     }
306 
307     // Some further testing against rows(), columns(), and hasChildren()
308     Q_ASSERT(rows >= 0);
309     Q_ASSERT(columns >= 0);
310     if (rows > 0) {
311         Q_ASSERT(model->hasChildren(parent) == true);
312     }
313 
314     //qDebug() << "parent:" << model->data(parent).toString() << "rows:" << rows
315     //         << "columns:" << columns << "parent column:" << parent.column();
316 
317     Q_ASSERT(model->hasIndex(rows + 1, 0, parent) == false);
318     for (int r = 0; r < rows; ++r) {
319         if (model->canFetchMore(parent)) {
320             fetchingMore = true;
321             model->fetchMore(parent);
322             fetchingMore = false;
323         }
324         Q_ASSERT(model->hasIndex(r, columns + 1, parent) == false);
325         for (int c = 0; c < columns; ++c) {
326             Q_ASSERT(model->hasIndex(r, c, parent) == true);
327             QModelIndex index = model->index(r, c, parent);
328             // rowCount() and columnCount() said that it existed...
329             Q_ASSERT(index.isValid() == true);
330 
331             // index() should always return the same index when called twice in a row
332             QModelIndex modifiedIndex = model->index(r, c, parent);
333             Q_ASSERT(index == modifiedIndex);
334 
335             // Make sure we get the same index if we request it twice in a row
336             QModelIndex a = model->index(r, c, parent);
337             QModelIndex b = model->index(r, c, parent);
338             Q_ASSERT(a == b);
339 
340             // Some basic checking on the index that is returned
341             Q_ASSERT(index.model() == model);
342             Q_ASSERT(index.row() == r);
343             Q_ASSERT(index.column() == c);
344             // While you can technically return a QVariant usually this is a sign
345             // of an bug in data()  Disable if this really is ok in your model.
346             Q_ASSERT(model->data(index, Qt::DisplayRole).isValid() == true);
347 
348             // If the next test fails here is some somewhat useful debug you play with.
349             /*
350             if (model->parent(index) != parent) {
351                 qDebug() << r << c << currentDepth << model->data(index).toString()
352                          << model->data(parent).toString();
353                 qDebug() << index << parent << model->parent(index);
354                 // And a view that you can even use to show the model.
355                 //QTreeView view;
356                 //view.setModel(model);
357                 //view.show();
358             }*/
359 
360             // Check that we can get back our real parent.
361             Q_ASSERT(model->parent(index) == parent);
362 
363             // recursively go down the children
364             if (model->hasChildren(index) && currentDepth < 10) {
365                 //qDebug() << r << c << "has children" << model->rowCount(index);
366                 checkChildren(index, ++currentDepth);
367             }/* else { if (currentDepth >= 10) qDebug() << "checked 10 deep"; };*/
368 
369             // make sure that after testing the children that the index doesn't change.
370             QModelIndex newerIndex = model->index(r, c, parent);
371             Q_ASSERT(index == newerIndex);
372         }
373     }
374 }
375 
376 /*!
377     Tests model's implementation of QAbstractItemModel::data()
378  */
data()379 void ModelTest::data()
380 {
381     // Invalid index should return an invalid qvariant
382     Q_ASSERT(!model->data(QModelIndex()).isValid());
383 
384     if (model->rowCount() == 0) {
385         return;
386     }
387 
388     // A valid index should have a valid QVariant data
389     Q_ASSERT(model->index(0, 0).isValid());
390 
391     // shouldn't be able to set data on an invalid index
392     Q_ASSERT(model->setData(QModelIndex(), "foo", Qt::DisplayRole) == false);
393 
394     // General Purpose roles that should return a QString
395     QVariant variant = model->data(model->index(0, 0), Qt::ToolTipRole);
396     if (variant.isValid()) {
397         Q_ASSERT(variant.canConvert<QString>());
398     }
399     variant = model->data(model->index(0, 0), Qt::StatusTipRole);
400     if (variant.isValid()) {
401         Q_ASSERT(variant.canConvert<QString>());
402     }
403     variant = model->data(model->index(0, 0), Qt::WhatsThisRole);
404     if (variant.isValid()) {
405         Q_ASSERT(variant.canConvert<QString>());
406     }
407 
408     // General Purpose roles that should return a QSize
409     variant = model->data(model->index(0, 0), Qt::SizeHintRole);
410     if (variant.isValid()) {
411         Q_ASSERT(variant.canConvert<QSize>());
412     }
413 
414     // General Purpose roles that should return a QFont
415     QVariant fontVariant = model->data(model->index(0, 0), Qt::FontRole);
416     if (fontVariant.isValid()) {
417         Q_ASSERT(variant.canConvert<QFont>());
418     }
419 
420     // Check that the alignment is one we know about
421     QVariant textAlignmentVariant = model->data(model->index(0, 0), Qt::TextAlignmentRole);
422     if (textAlignmentVariant.isValid()) {
423         int alignment = textAlignmentVariant.toInt();
424         Q_ASSERT(alignment == Qt::AlignLeft ||
425                  alignment == Qt::AlignRight ||
426                  alignment == Qt::AlignHCenter ||
427                  alignment == Qt::AlignJustify);
428     }
429 
430     // General Purpose roles that should return a QColor
431     QVariant colorVariant = model->data(model->index(0, 0), Qt::BackgroundColorRole);
432     if (colorVariant.isValid()) {
433         Q_ASSERT(variant.canConvert<QColor>());
434     }
435 
436     colorVariant = model->data(model->index(0, 0), Qt::TextColorRole);
437     if (colorVariant.isValid()) {
438         Q_ASSERT(variant.canConvert<QColor>());
439     }
440 
441     // Check that the "check state" is one we know about.
442     QVariant checkStateVariant = model->data(model->index(0, 0), Qt::CheckStateRole);
443     if (checkStateVariant.isValid()) {
444         int state = checkStateVariant.toInt();
445         Q_ASSERT(state == Qt::Unchecked ||
446                  state == Qt::PartiallyChecked ||
447                  state == Qt::Checked);
448     }
449 }
450 
451 /*!
452     Store what is about to be inserted to make sure it actually happens
453 
454     \sa rowsInserted()
455  */
rowsAboutToBeInserted(const QModelIndex & parent,int start,int end)456 void ModelTest::rowsAboutToBeInserted(const QModelIndex &parent, int start, int end)
457 {
458     Q_UNUSED(end);
459     Changing c;
460     c.parent = parent;
461     c.oldSize = model->rowCount(parent);
462     c.last = model->data(model->index(start - 1, 0, parent));
463     c.next = model->data(model->index(start, 0, parent));
464     insert.push(c);
465 }
466 
467 /*!
468     Confirm that what was said was going to happen actually did
469 
470     \sa rowsAboutToBeInserted()
471  */
rowsInserted(const QModelIndex & parent,int start,int end)472 void ModelTest::rowsInserted(const QModelIndex &parent, int start, int end)
473 {
474     Changing c = insert.pop();
475     Q_ASSERT(c.parent == parent);
476     Q_ASSERT(c.oldSize + (end - start + 1) == model->rowCount(parent));
477     Q_ASSERT(c.last == model->data(model->index(start - 1, 0, c.parent)));
478     /*
479     if (c.next != model->data(model->index(end + 1, 0, c.parent))) {
480         qDebug() << start << end;
481         for (int i=0; i < model->rowCount(); ++i)
482             qDebug() << model->index(i, 0).data().toString();
483         qDebug() << c.next << model->data(model->index(end + 1, 0, c.parent));
484     }
485     */
486     Q_ASSERT(c.next == model->data(model->index(end + 1, 0, c.parent)));
487 }
488 
489 /*!
490     Store what is about to be inserted to make sure it actually happens
491 
492     \sa rowsRemoved()
493  */
rowsAboutToBeRemoved(const QModelIndex & parent,int start,int end)494 void ModelTest::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end)
495 {
496     Changing c;
497     c.parent = parent;
498     c.oldSize = model->rowCount(parent);
499     c.last = model->data(model->index(start - 1, 0, parent));
500     c.next = model->data(model->index(end + 1, 0, parent));
501     remove.push(c);
502 }
503 
504 /*!
505     Confirm that what was said was going to happen actually did
506 
507     \sa rowsAboutToBeRemoved()
508  */
rowsRemoved(const QModelIndex & parent,int start,int end)509 void ModelTest::rowsRemoved(const QModelIndex &parent, int start, int end)
510 {
511     Changing c = remove.pop();
512     Q_ASSERT(c.parent == parent);
513     Q_ASSERT(c.oldSize - (end - start + 1) == model->rowCount(parent));
514     Q_ASSERT(c.last == model->data(model->index(start - 1, 0, c.parent)));
515     Q_ASSERT(c.next == model->data(model->index(start, 0, c.parent)));
516 }
517 
518