Add model test.
[dorian] / modeltest / modeltest.cpp
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 <QtGui/QtGui>
25
26 #include "modeltest.h"
27
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, SIGNAL(columnsAboutToBeInserted(const QModelIndex &, int, int)),
38             this, SLOT(runAllTests()));
39     connect(model, SIGNAL(columnsAboutToBeRemoved(const QModelIndex &, int, int)),
40             this, SLOT(runAllTests()));
41     connect(model, SIGNAL(columnsInserted(const QModelIndex &, int, int)),
42             this, SLOT(runAllTests()));
43     connect(model, SIGNAL(columnsRemoved(const QModelIndex &, int, int)),
44             this, SLOT(runAllTests()));
45     connect(model, SIGNAL(dataChanged(const QModelIndex &, const QModelIndex &)),
46             this, SLOT(runAllTests()));
47     connect(model, SIGNAL(headerDataChanged(Qt::Orientation, int, int)),
48             this, SLOT(runAllTests()));
49     connect(model, SIGNAL(layoutAboutToBeChanged ()), this, SLOT(runAllTests()));
50     connect(model, SIGNAL(layoutChanged ()), this, SLOT(runAllTests()));
51     connect(model, SIGNAL(modelReset ()), this, SLOT(runAllTests()));
52     connect(model, SIGNAL(rowsAboutToBeInserted(const QModelIndex &, int, int)),
53             this, SLOT(runAllTests()));
54     connect(model, SIGNAL(rowsAboutToBeRemoved(const QModelIndex &, int, int)),
55             this, SLOT(runAllTests()));
56     connect(model, SIGNAL(rowsInserted(const QModelIndex &, int, int)),
57             this, SLOT(runAllTests()));
58     connect(model, SIGNAL(rowsRemoved(const QModelIndex &, int, int)),
59             this, SLOT(runAllTests()));
60
61     // Special checks for inserting/removing
62     connect(model, SIGNAL(layoutAboutToBeChanged()),
63             this, SLOT(layoutAboutToBeChanged()));
64     connect(model, SIGNAL(layoutChanged()),
65             this, SLOT(layoutChanged()));
66
67     connect(model, SIGNAL(rowsAboutToBeInserted(const QModelIndex &, int, int)),
68             this, SLOT(rowsAboutToBeInserted(const QModelIndex &, int, int)));
69     connect(model, SIGNAL(rowsAboutToBeRemoved(const QModelIndex &, int, int)),
70             this, SLOT(rowsAboutToBeRemoved(const QModelIndex &, int, int)));
71     connect(model, SIGNAL(rowsInserted(const QModelIndex &, int, int)),
72             this, SLOT(rowsInserted(const QModelIndex &, int, int)));
73     connect(model, SIGNAL(rowsRemoved(const QModelIndex &, int, int)),
74             this, SLOT(rowsRemoved(const QModelIndex &, int, int)));
75
76     runAllTests();
77 }
78
79 void ModelTest::runAllTests()
80 {
81     if (fetchingMore)
82         return;
83     nonDestructiveBasicTest();
84     rowCount();
85     columnCount();
86     hasIndex();
87     index();
88     parent();
89     data();
90 }
91
92 /*!
93     nonDestructiveBasicTest tries to call a number of the basic functions (not all)
94     to make sure the model doesn't outright segfault, testing the functions that makes sense.
95 */
96 void ModelTest::nonDestructiveBasicTest()
97 {
98     Q_ASSERT(model->buddy(QModelIndex()) == QModelIndex());
99     model->canFetchMore(QModelIndex());
100     Q_ASSERT(model->columnCount(QModelIndex()) >= 0);
101     Q_ASSERT(model->data(QModelIndex()) == QVariant());
102     fetchingMore = true;
103     model->fetchMore(QModelIndex());
104     fetchingMore = false;
105     Qt::ItemFlags flags = model->flags(QModelIndex());
106     Q_ASSERT(flags == Qt::ItemIsDropEnabled || flags == 0);
107     model->hasChildren(QModelIndex());
108     model->hasIndex(0, 0);
109     model->headerData(0, Qt::Horizontal);
110     model->index(0, 0);
111     Q_ASSERT(model->index(-1, -1) == QModelIndex());
112     model->itemData(QModelIndex());
113     QVariant cache;
114     model->match(QModelIndex(), -1, cache);
115     model->mimeTypes();
116     Q_ASSERT(model->parent(QModelIndex()) == QModelIndex());
117     Q_ASSERT(model->rowCount() >= 0);
118     QVariant variant;
119     model->setData(QModelIndex(), variant, -1);
120     model->setHeaderData(-1, Qt::Horizontal, QVariant());
121     model->setHeaderData(0, Qt::Horizontal, QVariant());
122     model->setHeaderData(999999, Qt::Horizontal, QVariant());
123     QMap<int, QVariant> roles;
124     model->sibling(0, 0, QModelIndex());
125     model->span(QModelIndex());
126     model->supportedDropActions();
127 }
128
129 /*!
130     Tests model's implementation of QAbstractItemModel::rowCount() and hasChildren()
131
132     Models that are dynamically populated are not as fully tested here.
133  */
134 void ModelTest::rowCount()
135 {
136     // check top row
137     QModelIndex topIndex = model->index(0, 0, QModelIndex());
138     int rows = model->rowCount(topIndex);
139     Q_ASSERT(rows >= 0);
140     if (rows > 0)
141         Q_ASSERT(model->hasChildren(topIndex) == true);
142
143     QModelIndex secondLevelIndex = model->index(0, 0, topIndex);
144     if (secondLevelIndex.isValid()) { // not the top level
145         // check a row count where parent is valid
146         rows = model->rowCount(secondLevelIndex);
147         Q_ASSERT(rows >= 0);
148         if (rows > 0)
149             Q_ASSERT(model->hasChildren(secondLevelIndex) == true);
150     }
151
152     // The models rowCount() is tested more extensively in checkChildren(),
153     // but this catches the big mistakes
154 }
155
156 /*!
157     Tests model's implementation of QAbstractItemModel::columnCount() and hasChildren()
158  */
159 void ModelTest::columnCount()
160 {
161     // check top row
162     QModelIndex topIndex = model->index(0, 0, QModelIndex());
163     Q_ASSERT(model->columnCount(topIndex) >= 0);
164
165     // check a column count where parent is valid
166     QModelIndex childIndex = model->index(0, 0, topIndex);
167     if (childIndex.isValid())
168         Q_ASSERT(model->columnCount(childIndex) >= 0);
169
170     // columnCount() is tested more extensively in checkChildren(),
171     // but this catches the big mistakes
172 }
173
174 /*!
175     Tests model's implementation of QAbstractItemModel::hasIndex()
176  */
177 void ModelTest::hasIndex()
178 {
179     // Make sure that invalid values returns an invalid index
180     Q_ASSERT(model->hasIndex(-2, -2) == false);
181     Q_ASSERT(model->hasIndex(-2, 0) == false);
182     Q_ASSERT(model->hasIndex(0, -2) == false);
183
184     int rows = model->rowCount();
185     int columns = model->columnCount();
186
187     // check out of bounds
188     Q_ASSERT(model->hasIndex(rows, columns) == false);
189     Q_ASSERT(model->hasIndex(rows + 1, columns + 1) == false);
190
191     if (rows > 0)
192         Q_ASSERT(model->hasIndex(0, 0) == true);
193
194     // hasIndex() is tested more extensively in checkChildren(),
195     // but this catches the big mistakes
196 }
197
198 /*!
199     Tests model's implementation of QAbstractItemModel::index()
200  */
201 void ModelTest::index()
202 {
203     // Make sure that invalid values returns an invalid index
204     Q_ASSERT(model->index(-2, -2) == QModelIndex());
205     Q_ASSERT(model->index(-2, 0) == QModelIndex());
206     Q_ASSERT(model->index(0, -2) == QModelIndex());
207
208     int rows = model->rowCount();
209     int columns = model->columnCount();
210
211     if (rows == 0)
212         return;
213
214     // Catch off by one errors
215     Q_ASSERT(model->index(rows, columns) == QModelIndex());
216     Q_ASSERT(model->index(0, 0).isValid() == true);
217
218     // Make sure that the same index is *always* returned
219     QModelIndex a = model->index(0, 0);
220     QModelIndex b = model->index(0, 0);
221     Q_ASSERT(a == b);
222
223     // index() is tested more extensively in checkChildren(),
224     // but this catches the big mistakes
225 }
226
227 /*!
228     Tests model's implementation of QAbstractItemModel::parent()
229  */
230 void ModelTest::parent()
231 {
232     // Make sure the model wont crash and will return an invalid QModelIndex
233     // when asked for the parent of an invalid index.
234     Q_ASSERT(model->parent(QModelIndex()) == QModelIndex());
235
236     if (model->rowCount() == 0)
237         return;
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  */
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     // For models that are dynamically populated
293     if (model->canFetchMore(parent)) {
294         fetchingMore = true;
295         model->fetchMore(parent);
296         fetchingMore = false;
297     }
298
299     int rows = model->rowCount(parent);
300     int columns = model->columnCount(parent);
301
302     if (rows > 0)
303         Q_ASSERT(model->hasChildren(parent));
304
305     // Some further testing against rows(), columns(), and hasChildren()
306     Q_ASSERT(rows >= 0);
307     Q_ASSERT(columns >= 0);
308     if (rows > 0)
309         Q_ASSERT(model->hasChildren(parent) == true);
310
311     //qDebug() << "parent:" << model->data(parent).toString() << "rows:" << rows
312     //         << "columns:" << columns << "parent column:" << parent.column();
313
314     Q_ASSERT(model->hasIndex(rows + 1, 0, parent) == false);
315     for (int r = 0; r < rows; ++r) {
316         if (model->canFetchMore(parent)) {
317             fetchingMore = true;
318             model->fetchMore(parent);
319             fetchingMore = false;
320         }
321         Q_ASSERT(model->hasIndex(r, columns + 1, parent) == false);
322         for (int c = 0; c < columns; ++c) {
323             Q_ASSERT(model->hasIndex(r, c, parent) == true);
324             QModelIndex index = model->index(r, c, parent);
325             // rowCount() and columnCount() said that it existed...
326             Q_ASSERT(index.isValid() == true);
327
328             // index() should always return the same index when called twice in a row
329             QModelIndex modifiedIndex = model->index(r, c, parent);
330             Q_ASSERT(index == modifiedIndex);
331
332             // Make sure we get the same index if we request it twice in a row
333             QModelIndex a = model->index(r, c, parent);
334             QModelIndex b = model->index(r, c, parent);
335             Q_ASSERT(a == b);
336
337             // Some basic checking on the index that is returned
338             Q_ASSERT(index.model() == model);
339             Q_ASSERT(index.row() == r);
340             Q_ASSERT(index.column() == c);
341             // While you can technically return a QVariant usually this is a sign
342             // of an bug in data()  Disable if this really is ok in your model.
343             //Q_ASSERT(model->data(index, Qt::DisplayRole).isValid() == true);
344
345             // If the next test fails here is some somewhat useful debug you play with.
346             /*
347             if (model->parent(index) != parent) {
348                 qDebug() << r << c << currentDepth << model->data(index).toString()
349                          << model->data(parent).toString();
350                 qDebug() << index << parent << model->parent(index);
351                 // And a view that you can even use to show the model.
352                 //QTreeView view;
353                 //view.setModel(model);
354                 //view.show();
355             }*/
356
357             // Check that we can get back our real parent.
358             QModelIndex p = model->parent(index);
359             //qDebug() << "child:" << index;
360             //qDebug() << p;
361             //qDebug() << parent;
362             Q_ASSERT(model->parent(index) == parent);
363
364             // recursively go down the children
365             if (model->hasChildren(index) && currentDepth < 10 ) {
366                 //qDebug() << r << c << "has children" << model->rowCount(index);
367                 checkChildren(index, ++currentDepth);
368             }/* else { if (currentDepth >= 10) qDebug() << "checked 10 deep"; };*/
369
370             // make sure that after testing the children that the index doesn't change.
371             QModelIndex newerIndex = model->index(r, c, parent);
372             Q_ASSERT(index == newerIndex);
373         }
374     }
375 }
376
377 /*!
378     Tests model's implementation of QAbstractItemModel::data()
379  */
380 void ModelTest::data()
381 {
382     // Invalid index should return an invalid qvariant
383     Q_ASSERT(!model->data(QModelIndex()).isValid());
384
385     if (model->rowCount() == 0)
386         return;
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(), QLatin1String("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(qVariantCanConvert<QString>(variant));
398     }
399     variant = model->data(model->index(0, 0), Qt::StatusTipRole);
400     if (variant.isValid()) {
401         Q_ASSERT(qVariantCanConvert<QString>(variant));
402     }
403     variant = model->data(model->index(0, 0), Qt::WhatsThisRole);
404     if (variant.isValid()) {
405         Q_ASSERT(qVariantCanConvert<QString>(variant));
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(qVariantCanConvert<QSize>(variant));
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(qVariantCanConvert<QFont>(fontVariant));
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                  alignment == Qt::AlignTop ||
429                  alignment == Qt::AlignBottom ||
430                  alignment == Qt::AlignVCenter ||
431                  alignment == Qt::AlignCenter ||
432                  alignment == Qt::AlignAbsolute ||
433                  alignment == Qt::AlignLeading ||
434                  alignment == Qt::AlignTrailing);
435     }
436
437     // General Purpose roles that should return a QColor
438     QVariant colorVariant = model->data(model->index(0, 0), Qt::BackgroundColorRole);
439     if (colorVariant.isValid()) {
440         Q_ASSERT(qVariantCanConvert<QColor>(colorVariant));
441     }
442
443     colorVariant = model->data(model->index(0, 0), Qt::TextColorRole);
444     if (colorVariant.isValid()) {
445         Q_ASSERT(qVariantCanConvert<QColor>(colorVariant));
446     }
447
448     // Check that the "check state" is one we know about.
449     QVariant checkStateVariant = model->data(model->index(0, 0), Qt::CheckStateRole);
450     if (checkStateVariant.isValid()) {
451         int state = checkStateVariant.toInt();
452         Q_ASSERT(state == Qt::Unchecked ||
453                  state == Qt::PartiallyChecked ||
454                  state == Qt::Checked);
455     }
456 }
457
458 /*!
459     Store what is about to be inserted to make sure it actually happens
460
461     \sa rowsInserted()
462  */
463 void ModelTest::rowsAboutToBeInserted(const QModelIndex &parent, int start, int end)
464 {
465     Q_UNUSED(end);
466     Changing c;
467     c.parent = parent;
468     c.oldSize = model->rowCount(parent);
469     c.last = model->data(model->index(start - 1, 0, parent));
470     c.next = model->data(model->index(start, 0, parent));
471     insert.push(c);
472 }
473
474 /*!
475     Confirm that what was said was going to happen actually did
476
477     \sa rowsAboutToBeInserted()
478  */
479 void ModelTest::rowsInserted(const QModelIndex & parent, int start, int end)
480 {
481     Changing c = insert.pop();
482     Q_ASSERT(c.parent == parent);
483     Q_ASSERT(c.oldSize + (end - start + 1) == model->rowCount(parent));
484     Q_ASSERT(c.last == model->data(model->index(start - 1, 0, c.parent)));
485     /*
486     if (c.next != model->data(model->index(end + 1, 0, c.parent))) {
487         qDebug() << start << end;
488         for (int i=0; i < model->rowCount(); ++i)
489             qDebug() << model->index(i, 0).data().toString();
490         qDebug() << c.next << model->data(model->index(end + 1, 0, c.parent));
491     }
492     */
493     Q_ASSERT(c.next == model->data(model->index(end + 1, 0, c.parent)));
494 }
495
496 void ModelTest::layoutAboutToBeChanged()
497 {
498     for (int i = 0; i < qBound(0, model->rowCount(), 100); ++i)
499         changing.append(QPersistentModelIndex(model->index(i, 0)));
500 }
501
502 void ModelTest::layoutChanged()
503 {
504     for (int i = 0; i < changing.count(); ++i) {
505         QPersistentModelIndex p = changing[i];
506         Q_ASSERT(p == model->index(p.row(), p.column(), p.parent()));
507     }
508     changing.clear();
509 }
510
511 /*!
512     Store what is about to be inserted to make sure it actually happens
513
514     \sa rowsRemoved()
515  */
516 void ModelTest::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end)
517 {
518     Changing c;
519     c.parent = parent;
520     c.oldSize = model->rowCount(parent);
521     c.last = model->data(model->index(start - 1, 0, parent));
522     c.next = model->data(model->index(end + 1, 0, parent));
523     remove.push(c);
524 }
525
526 /*!
527     Confirm that what was said was going to happen actually did
528
529     \sa rowsAboutToBeRemoved()
530  */
531 void ModelTest::rowsRemoved(const QModelIndex & parent, int start, int end)
532 {
533     Changing c = remove.pop();
534     Q_ASSERT(c.parent == parent);
535     Q_ASSERT(c.oldSize - (end - start + 1) == model->rowCount(parent));
536     Q_ASSERT(c.last == model->data(model->index(start - 1, 0, c.parent)));
537     Q_ASSERT(c.next == model->data(model->index(start, 0, c.parent)));
538 }
539