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