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 <QTest> |
48 | #include <QLoggingCategory> |
49 | |
50 | QT_BEGIN_NAMESPACE |
51 | |
52 | Q_LOGGING_CATEGORY(lcModelTest, "qt.modeltest" ) |
53 | |
54 | #define MODELTESTER_VERIFY(statement) \ |
55 | do { \ |
56 | if (!verify(static_cast<bool>(statement), #statement, "", __FILE__, __LINE__)) \ |
57 | return; \ |
58 | } while (false) |
59 | |
60 | #define MODELTESTER_COMPARE(actual, expected) \ |
61 | do { \ |
62 | if (!compare((actual), (expected), #actual, #expected, __FILE__, __LINE__)) \ |
63 | return; \ |
64 | } while (false) |
65 | |
66 | class QAbstractItemModelTesterPrivate : public QObjectPrivate |
67 | { |
68 | Q_DECLARE_PUBLIC(QAbstractItemModelTester) |
69 | public: |
70 | QAbstractItemModelTesterPrivate(QAbstractItemModel *model, QAbstractItemModelTester::FailureReportingMode failureReportingMode); |
71 | |
72 | void nonDestructiveBasicTest(); |
73 | void rowAndColumnCount(); |
74 | void hasIndex(); |
75 | void index(); |
76 | void parent(); |
77 | void data(); |
78 | |
79 | void runAllTests(); |
80 | void layoutAboutToBeChanged(); |
81 | void layoutChanged(); |
82 | void rowsAboutToBeInserted(const QModelIndex &parent, int start, int end); |
83 | void rowsInserted(const QModelIndex &parent, int start, int end); |
84 | void rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end); |
85 | void rowsRemoved(const QModelIndex &parent, int start, int end); |
86 | void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); |
87 | void headerDataChanged(Qt::Orientation orientation, int start, int end); |
88 | |
89 | private: |
90 | void checkChildren(const QModelIndex &parent, int currentDepth = 0); |
91 | |
92 | bool verify(bool statement, const char *statementStr, const char *description, const char *file, int line); |
93 | |
94 | template<typename T1, typename T2> |
95 | bool compare(const T1 &t1, const T2 &t2, |
96 | const char *actual, const char *expected, |
97 | const char *file, int line); |
98 | |
99 | QPointer<QAbstractItemModel> model; |
100 | QAbstractItemModelTester::FailureReportingMode failureReportingMode; |
101 | |
102 | struct Changing { |
103 | QModelIndex parent; |
104 | int oldSize; |
105 | QVariant last; |
106 | QVariant next; |
107 | }; |
108 | QStack<Changing> insert; |
109 | QStack<Changing> remove; |
110 | |
111 | bool fetchingMore; |
112 | |
113 | QList<QPersistentModelIndex> changing; |
114 | }; |
115 | |
116 | /*! |
117 | \class QAbstractItemModelTester |
118 | \since 5.11 |
119 | \inmodule QtTest |
120 | |
121 | \brief The QAbstractItemModelTester class helps testing QAbstractItemModel subclasses. |
122 | |
123 | The QAbstractItemModelTester class is a utility class to test item models. |
124 | |
125 | When implementing an item model (that is, a concrete QAbstractItemModel |
126 | subclass) one must abide to a very strict set of rules that ensure |
127 | consistency for users of the model (views, proxy models, and so on). |
128 | |
129 | For instance, for a given index, a model's reimplementation of |
130 | \l{QAbstractItemModel::hasChildren()}{hasChildren()} must be consistent |
131 | with the values returned by \l{QAbstractItemModel::rowCount()}{rowCount()} |
132 | and \l{QAbstractItemModel::columnCount()}{columnCount()}. |
133 | |
134 | QAbstractItemModelTester helps catching the most common errors in custom |
135 | item model classes. By performing a series of tests, it |
136 | will try to check that the model status is consistent at all times. The |
137 | tests will be repeated automatically every time the model is modified. |
138 | |
139 | QAbstractItemModelTester employs non-destructive tests, which typically |
140 | consist in reading data and metadata out of a given item model. |
141 | QAbstractItemModelTester will also attempt illegal modifications of |
142 | the model. In models which are properly implemented, such attempts |
143 | should be rejected, and no data should be changed as a consequence. |
144 | |
145 | \section1 Usage |
146 | |
147 | Using QAbstractItemModelTester is straightforward. In a \l{Qt Test Overview}{test case} |
148 | it is sufficient to create an instance, passing the model that |
149 | needs to be tested to the constructor: |
150 | |
151 | \code |
152 | MyModel *modelToBeTested = ...; |
153 | auto tester = new QAbstractItemModelTester(modelToBeTested); |
154 | \endcode |
155 | |
156 | QAbstractItemModelTester will report testing failures through the |
157 | Qt Test logging mechanisms. |
158 | |
159 | It is also possible to use QAbstractItemModelTester outside of a test case. |
160 | For instance, it may be useful to test an item model used by an application |
161 | without the need of building an explicit unit test for such a model (which |
162 | might be challenging). In order to use QAbstractItemModelTester outside of |
163 | a test case, pass one of the \c QAbstractItemModelTester::FailureReportingMode |
164 | enumerators to its constructor, therefore specifying how failures should |
165 | be logged. |
166 | |
167 | QAbstractItemModelTester may also report additional debugging information |
168 | as logging messages under the \c qt.modeltest logging category. Such |
169 | debug logging is disabled by default; refer to the |
170 | QLoggingCategory documentation to learn how to enable it. |
171 | |
172 | \note While QAbstractItemModelTester is a valid help for development and |
173 | testing of custom item models, it does not (and cannot) catch all possible |
174 | problems in QAbstractItemModel subclasses. Notably, it will never perform |
175 | meaningful destructive testing of a model, which must be therefore tested |
176 | separately. |
177 | |
178 | \sa {Model/View Programming}, QAbstractItemModel |
179 | */ |
180 | |
181 | /*! |
182 | \enum QAbstractItemModelTester::FailureReportingMode |
183 | |
184 | This enumeration specifies how QAbstractItemModelTester should report |
185 | a failure when it tests a QAbstractItemModel subclass. |
186 | |
187 | \value QtTest The failures will be reported through |
188 | QtTest's logging mechanism. |
189 | |
190 | \value Warning The failures will be reported as |
191 | warning messages in the \c{qt.modeltest} logging category. |
192 | |
193 | \value Fatal A failure will cause immediate and |
194 | abnormal program termination. The reason for the failure will be reported |
195 | using \c{qFatal()}. |
196 | */ |
197 | |
198 | /*! |
199 | Creates a model tester instance, with the given \a parent, that will test |
200 | the model \a model. |
201 | */ |
202 | QAbstractItemModelTester::QAbstractItemModelTester(QAbstractItemModel *model, QObject *parent) |
203 | : QAbstractItemModelTester(model, FailureReportingMode::QtTest, parent) |
204 | { |
205 | } |
206 | |
207 | /*! |
208 | Creates a model tester instance, with the given \a parent, that will test |
209 | the model \a model, using the specified \a mode to report test failures. |
210 | |
211 | \sa QAbstractItemModelTester::FailureReportingMode |
212 | */ |
213 | QAbstractItemModelTester::QAbstractItemModelTester(QAbstractItemModel *model, FailureReportingMode mode, QObject *parent) |
214 | : QObject(*new QAbstractItemModelTesterPrivate(model, mode), parent) |
215 | { |
216 | if (!model) |
217 | qFatal("%s: model must not be null" , Q_FUNC_INFO); |
218 | |
219 | Q_D(QAbstractItemModelTester); |
220 | |
221 | auto runAllTests = [d] { d->runAllTests(); }; |
222 | |
223 | connect(model, &QAbstractItemModel::columnsAboutToBeInserted, |
224 | this, runAllTests); |
225 | connect(model, &QAbstractItemModel::columnsAboutToBeRemoved, |
226 | this, runAllTests); |
227 | connect(model, &QAbstractItemModel::columnsInserted, |
228 | this, runAllTests); |
229 | connect(model, &QAbstractItemModel::columnsRemoved, |
230 | this, runAllTests); |
231 | connect(model, &QAbstractItemModel::dataChanged, |
232 | this, runAllTests); |
233 | connect(model, &QAbstractItemModel::headerDataChanged, |
234 | this, runAllTests); |
235 | connect(model, &QAbstractItemModel::layoutAboutToBeChanged, |
236 | this, runAllTests); |
237 | connect(model, &QAbstractItemModel::layoutChanged, |
238 | this, runAllTests); |
239 | connect(model, &QAbstractItemModel::modelReset, |
240 | this, runAllTests); |
241 | connect(model, &QAbstractItemModel::rowsAboutToBeInserted, |
242 | this, runAllTests); |
243 | connect(model, &QAbstractItemModel::rowsAboutToBeRemoved, |
244 | this, runAllTests); |
245 | connect(model, &QAbstractItemModel::rowsInserted, |
246 | this, runAllTests); |
247 | connect(model, &QAbstractItemModel::rowsRemoved, |
248 | this, runAllTests); |
249 | |
250 | // Special checks for changes |
251 | connect(model, &QAbstractItemModel::layoutAboutToBeChanged, |
252 | this, [d]{ d->layoutAboutToBeChanged(); }); |
253 | connect(model, &QAbstractItemModel::layoutChanged, |
254 | this, [d]{ d->layoutChanged(); }); |
255 | |
256 | connect(model, &QAbstractItemModel::rowsAboutToBeInserted, |
257 | this, [d](const QModelIndex &parent, int start, int end) { d->rowsAboutToBeInserted(parent, start, end); }); |
258 | connect(model, &QAbstractItemModel::rowsAboutToBeRemoved, |
259 | this, [d](const QModelIndex &parent, int start, int end) { d->rowsAboutToBeRemoved(parent, start, end); }); |
260 | connect(model, &QAbstractItemModel::rowsInserted, |
261 | this, [d](const QModelIndex &parent, int start, int end) { d->rowsInserted(parent, start, end); }); |
262 | connect(model, &QAbstractItemModel::rowsRemoved, |
263 | this, [d](const QModelIndex &parent, int start, int end) { d->rowsRemoved(parent, start, end); }); |
264 | connect(model, &QAbstractItemModel::dataChanged, |
265 | this, [d](const QModelIndex &topLeft, const QModelIndex &bottomRight) { d->dataChanged(topLeft, bottomRight); }); |
266 | connect(model, &QAbstractItemModel::headerDataChanged, |
267 | this, [d](Qt::Orientation orientation, int start, int end) { d->headerDataChanged(orientation, start, end); }); |
268 | |
269 | runAllTests(); |
270 | } |
271 | |
272 | /*! |
273 | Returns the model that this instance is testing. |
274 | */ |
275 | QAbstractItemModel *QAbstractItemModelTester::model() const |
276 | { |
277 | Q_D(const QAbstractItemModelTester); |
278 | return d->model.data(); |
279 | } |
280 | |
281 | /*! |
282 | Returns the mode that this instancing is using to report test failures. |
283 | |
284 | \sa QAbstractItemModelTester::FailureReportingMode |
285 | */ |
286 | QAbstractItemModelTester::FailureReportingMode QAbstractItemModelTester::failureReportingMode() const |
287 | { |
288 | Q_D(const QAbstractItemModelTester); |
289 | return d->failureReportingMode; |
290 | } |
291 | |
292 | bool QAbstractItemModelTester::verify(bool statement, const char *statementStr, const char *description, const char *file, int line) |
293 | { |
294 | Q_D(QAbstractItemModelTester); |
295 | return d->verify(statement, statementStr, description, file, line); |
296 | } |
297 | |
298 | QAbstractItemModelTesterPrivate::QAbstractItemModelTesterPrivate(QAbstractItemModel *model, QAbstractItemModelTester::FailureReportingMode failureReportingMode) |
299 | : model(model), |
300 | failureReportingMode(failureReportingMode), |
301 | fetchingMore(false) |
302 | { |
303 | } |
304 | |
305 | void QAbstractItemModelTesterPrivate::runAllTests() |
306 | { |
307 | if (fetchingMore) |
308 | return; |
309 | nonDestructiveBasicTest(); |
310 | rowAndColumnCount(); |
311 | hasIndex(); |
312 | index(); |
313 | parent(); |
314 | data(); |
315 | } |
316 | |
317 | /* |
318 | nonDestructiveBasicTest tries to call a number of the basic functions (not all) |
319 | to make sure the model doesn't outright segfault, testing the functions that makes sense. |
320 | */ |
321 | void QAbstractItemModelTesterPrivate::nonDestructiveBasicTest() |
322 | { |
323 | MODELTESTER_VERIFY(!model->buddy(QModelIndex()).isValid()); |
324 | model->canFetchMore(QModelIndex()); |
325 | MODELTESTER_VERIFY(model->columnCount(QModelIndex()) >= 0); |
326 | fetchingMore = true; |
327 | model->fetchMore(QModelIndex()); |
328 | fetchingMore = false; |
329 | Qt::ItemFlags flags = model->flags(QModelIndex()); |
330 | MODELTESTER_VERIFY(flags == Qt::ItemIsDropEnabled || flags == 0); |
331 | model->hasChildren(QModelIndex()); |
332 | const bool hasRow = model->hasIndex(0, 0); |
333 | QVariant cache; |
334 | if (hasRow) |
335 | model->match(model->index(0, 0), -1, cache); |
336 | model->mimeTypes(); |
337 | MODELTESTER_VERIFY(!model->parent(QModelIndex()).isValid()); |
338 | MODELTESTER_VERIFY(model->rowCount() >= 0); |
339 | model->span(QModelIndex()); |
340 | model->supportedDropActions(); |
341 | model->roleNames(); |
342 | } |
343 | |
344 | /* |
345 | Tests model's implementation of QAbstractItemModel::rowCount(), |
346 | columnCount() and hasChildren(). |
347 | |
348 | Models that are dynamically populated are not as fully tested here. |
349 | */ |
350 | void QAbstractItemModelTesterPrivate::rowAndColumnCount() |
351 | { |
352 | if (!model->hasChildren()) |
353 | return; |
354 | |
355 | QModelIndex topIndex = model->index(0, 0, QModelIndex()); |
356 | |
357 | // check top row |
358 | int rows = model->rowCount(topIndex); |
359 | MODELTESTER_VERIFY(rows >= 0); |
360 | |
361 | int columns = model->columnCount(topIndex); |
362 | MODELTESTER_VERIFY(columns >= 0); |
363 | |
364 | if (rows == 0 || columns == 0) |
365 | return; |
366 | |
367 | MODELTESTER_VERIFY(model->hasChildren(topIndex)); |
368 | |
369 | QModelIndex secondLevelIndex = model->index(0, 0, topIndex); |
370 | MODELTESTER_VERIFY(secondLevelIndex.isValid()); |
371 | |
372 | rows = model->rowCount(secondLevelIndex); |
373 | MODELTESTER_VERIFY(rows >= 0); |
374 | |
375 | columns = model->columnCount(secondLevelIndex); |
376 | MODELTESTER_VERIFY(columns >= 0); |
377 | |
378 | if (rows == 0 || columns == 0) |
379 | return; |
380 | |
381 | MODELTESTER_VERIFY(model->hasChildren(secondLevelIndex)); |
382 | |
383 | // rowCount() / columnCount() are tested more extensively in checkChildren() |
384 | } |
385 | |
386 | /* |
387 | Tests model's implementation of QAbstractItemModel::hasIndex() |
388 | */ |
389 | void QAbstractItemModelTesterPrivate::hasIndex() |
390 | { |
391 | // Make sure that invalid values returns an invalid index |
392 | MODELTESTER_VERIFY(!model->hasIndex(-2, -2)); |
393 | MODELTESTER_VERIFY(!model->hasIndex(-2, 0)); |
394 | MODELTESTER_VERIFY(!model->hasIndex(0, -2)); |
395 | |
396 | const int rows = model->rowCount(); |
397 | const int columns = model->columnCount(); |
398 | |
399 | // check out of bounds |
400 | MODELTESTER_VERIFY(!model->hasIndex(rows, columns)); |
401 | MODELTESTER_VERIFY(!model->hasIndex(rows + 1, columns + 1)); |
402 | |
403 | if (rows > 0 && columns > 0) |
404 | MODELTESTER_VERIFY(model->hasIndex(0, 0)); |
405 | |
406 | // hasIndex() is tested more extensively in checkChildren(), |
407 | // but this catches the big mistakes |
408 | } |
409 | |
410 | /* |
411 | Tests model's implementation of QAbstractItemModel::index() |
412 | */ |
413 | void QAbstractItemModelTesterPrivate::index() |
414 | { |
415 | const int rows = model->rowCount(); |
416 | const int columns = model->columnCount(); |
417 | |
418 | for (int row = 0; row < rows; ++row) { |
419 | for (int column = 0; column < columns; ++column) { |
420 | // Make sure that the same index is *always* returned |
421 | QModelIndex a = model->index(row, column); |
422 | QModelIndex b = model->index(row, column); |
423 | MODELTESTER_VERIFY(a.isValid()); |
424 | MODELTESTER_VERIFY(b.isValid()); |
425 | MODELTESTER_COMPARE(a, b); |
426 | } |
427 | } |
428 | |
429 | // index() is tested more extensively in checkChildren(), |
430 | // but this catches the big mistakes |
431 | } |
432 | |
433 | /* |
434 | Tests model's implementation of QAbstractItemModel::parent() |
435 | */ |
436 | void QAbstractItemModelTesterPrivate::parent() |
437 | { |
438 | // Make sure the model won't crash and will return an invalid QModelIndex |
439 | // when asked for the parent of an invalid index. |
440 | MODELTESTER_VERIFY(!model->parent(QModelIndex()).isValid()); |
441 | |
442 | if (model->rowCount() == 0 || model->columnCount() == 0) |
443 | return; |
444 | |
445 | // Column 0 | Column 1 | |
446 | // QModelIndex() | | |
447 | // \- topIndex | topIndex1 | |
448 | // \- childIndex | childIndex1 | |
449 | |
450 | // Common error test #1, make sure that a top level index has a parent |
451 | // that is a invalid QModelIndex. |
452 | QModelIndex topIndex = model->index(0, 0, QModelIndex()); |
453 | MODELTESTER_VERIFY(topIndex.isValid()); |
454 | MODELTESTER_VERIFY(!model->parent(topIndex).isValid()); |
455 | |
456 | // Common error test #2, make sure that a second level index has a parent |
457 | // that is the first level index. |
458 | if (model->rowCount(topIndex) > 0) { |
459 | QModelIndex childIndex = model->index(0, 0, topIndex); |
460 | MODELTESTER_VERIFY(childIndex.isValid()); |
461 | MODELTESTER_COMPARE(model->parent(childIndex), topIndex); |
462 | } |
463 | |
464 | // Common error test #3, the second column should NOT have the same children |
465 | // as the first column in a row. |
466 | // Usually the second column shouldn't have children. |
467 | if (model->hasIndex(0, 1)) { |
468 | QModelIndex topIndex1 = model->index(0, 1, QModelIndex()); |
469 | MODELTESTER_VERIFY(topIndex1.isValid()); |
470 | if (model->rowCount(topIndex) > 0 && model->rowCount(topIndex1) > 0) { |
471 | QModelIndex childIndex = model->index(0, 0, topIndex); |
472 | MODELTESTER_VERIFY(childIndex.isValid()); |
473 | QModelIndex childIndex1 = model->index(0, 0, topIndex1); |
474 | MODELTESTER_VERIFY(childIndex1.isValid()); |
475 | MODELTESTER_VERIFY(childIndex != childIndex1); |
476 | } |
477 | } |
478 | |
479 | // Full test, walk n levels deep through the model making sure that all |
480 | // parent's children correctly specify their parent. |
481 | checkChildren(QModelIndex()); |
482 | } |
483 | |
484 | /* |
485 | Called from the parent() test. |
486 | |
487 | A model that returns an index of parent X should also return X when asking |
488 | for the parent of the index. |
489 | |
490 | This recursive function does pretty extensive testing on the whole model in an |
491 | effort to catch edge cases. |
492 | |
493 | This function assumes that rowCount(), columnCount() and index() already work. |
494 | If they have a bug it will point it out, but the above tests should have already |
495 | found the basic bugs because it is easier to figure out the problem in |
496 | those tests then this one. |
497 | */ |
498 | void QAbstractItemModelTesterPrivate::checkChildren(const QModelIndex &parent, int currentDepth) |
499 | { |
500 | // First just try walking back up the tree. |
501 | QModelIndex p = parent; |
502 | while (p.isValid()) |
503 | p = p.parent(); |
504 | |
505 | // For models that are dynamically populated |
506 | if (model->canFetchMore(parent)) { |
507 | fetchingMore = true; |
508 | model->fetchMore(parent); |
509 | fetchingMore = false; |
510 | } |
511 | |
512 | const int rows = model->rowCount(parent); |
513 | const int columns = model->columnCount(parent); |
514 | |
515 | if (rows > 0) |
516 | MODELTESTER_VERIFY(model->hasChildren(parent)); |
517 | |
518 | // Some further testing against rows(), columns(), and hasChildren() |
519 | MODELTESTER_VERIFY(rows >= 0); |
520 | MODELTESTER_VERIFY(columns >= 0); |
521 | if (rows > 0 && columns > 0) |
522 | MODELTESTER_VERIFY(model->hasChildren(parent)); |
523 | |
524 | const QModelIndex topLeftChild = model->index(0, 0, parent); |
525 | |
526 | MODELTESTER_VERIFY(!model->hasIndex(rows, 0, parent)); |
527 | MODELTESTER_VERIFY(!model->hasIndex(rows + 1, 0, parent)); |
528 | |
529 | for (int r = 0; r < rows; ++r) { |
530 | MODELTESTER_VERIFY(!model->hasIndex(r, columns, parent)); |
531 | MODELTESTER_VERIFY(!model->hasIndex(r, columns + 1, parent)); |
532 | for (int c = 0; c < columns; ++c) { |
533 | MODELTESTER_VERIFY(model->hasIndex(r, c, parent)); |
534 | QModelIndex index = model->index(r, c, parent); |
535 | // rowCount() and columnCount() said that it existed... |
536 | if (!index.isValid()) |
537 | qCWarning(lcModelTest) << "Got invalid index at row=" << r << "col=" << c << "parent=" << parent; |
538 | MODELTESTER_VERIFY(index.isValid()); |
539 | |
540 | // index() should always return the same index when called twice in a row |
541 | QModelIndex modifiedIndex = model->index(r, c, parent); |
542 | MODELTESTER_COMPARE(index, modifiedIndex); |
543 | |
544 | { |
545 | const QModelIndex sibling = model->sibling(r, c, topLeftChild); |
546 | MODELTESTER_COMPARE(index, sibling); |
547 | } |
548 | { |
549 | const QModelIndex sibling = topLeftChild.sibling(r, c); |
550 | MODELTESTER_COMPARE(index, sibling); |
551 | } |
552 | |
553 | // Some basic checking on the index that is returned |
554 | MODELTESTER_COMPARE(index.model(), model); |
555 | MODELTESTER_COMPARE(index.row(), r); |
556 | MODELTESTER_COMPARE(index.column(), c); |
557 | |
558 | // If the next test fails here is some somewhat useful debug you play with. |
559 | if (model->parent(index) != parent) { |
560 | qCWarning(lcModelTest) << "Inconsistent parent() implementation detected:" ; |
561 | qCWarning(lcModelTest) << " index=" << index << "exp. parent=" << parent << "act. parent=" << model->parent(index); |
562 | qCWarning(lcModelTest) << " row=" << r << "col=" << c << "depth=" << currentDepth; |
563 | qCWarning(lcModelTest) << " data for child" << model->data(index).toString(); |
564 | qCWarning(lcModelTest) << " data for parent" << model->data(parent).toString(); |
565 | } |
566 | |
567 | // Check that we can get back our real parent. |
568 | MODELTESTER_COMPARE(model->parent(index), parent); |
569 | |
570 | QPersistentModelIndex persistentIndex = index; |
571 | |
572 | // recursively go down the children |
573 | if (model->hasChildren(index) && currentDepth < 10) |
574 | checkChildren(index, ++currentDepth); |
575 | |
576 | // make sure that after testing the children that the index doesn't change. |
577 | QModelIndex newerIndex = model->index(r, c, parent); |
578 | MODELTESTER_COMPARE(persistentIndex, newerIndex); |
579 | } |
580 | } |
581 | } |
582 | |
583 | /* |
584 | Tests model's implementation of QAbstractItemModel::data() |
585 | */ |
586 | void QAbstractItemModelTesterPrivate::data() |
587 | { |
588 | if (model->rowCount() == 0 || model->columnCount() == 0) |
589 | return; |
590 | |
591 | MODELTESTER_VERIFY(model->index(0, 0).isValid()); |
592 | |
593 | // General Purpose roles that should return a QString |
594 | QVariant variant; |
595 | variant = model->data(model->index(0, 0), Qt::DisplayRole); |
596 | if (variant.isValid()) |
597 | MODELTESTER_VERIFY(variant.canConvert<QString>()); |
598 | variant = model->data(model->index(0, 0), Qt::ToolTipRole); |
599 | if (variant.isValid()) |
600 | MODELTESTER_VERIFY(variant.canConvert<QString>()); |
601 | variant = model->data(model->index(0, 0), Qt::StatusTipRole); |
602 | if (variant.isValid()) |
603 | MODELTESTER_VERIFY(variant.canConvert<QString>()); |
604 | variant = model->data(model->index(0, 0), Qt::WhatsThisRole); |
605 | if (variant.isValid()) |
606 | MODELTESTER_VERIFY(variant.canConvert<QString>()); |
607 | |
608 | // General Purpose roles that should return a QSize |
609 | variant = model->data(model->index(0, 0), Qt::SizeHintRole); |
610 | if (variant.isValid()) |
611 | MODELTESTER_VERIFY(variant.canConvert<QSize>()); |
612 | |
613 | // Check that the alignment is one we know about |
614 | QVariant textAlignmentVariant = model->data(model->index(0, 0), Qt::TextAlignmentRole); |
615 | if (textAlignmentVariant.isValid()) { |
616 | Qt::Alignment alignment = qvariant_cast<Qt::Alignment>(textAlignmentVariant); |
617 | MODELTESTER_COMPARE(alignment, (alignment & (Qt::AlignHorizontal_Mask | Qt::AlignVertical_Mask))); |
618 | } |
619 | |
620 | // Check that the "check state" is one we know about. |
621 | QVariant checkStateVariant = model->data(model->index(0, 0), Qt::CheckStateRole); |
622 | if (checkStateVariant.isValid()) { |
623 | int state = checkStateVariant.toInt(); |
624 | MODELTESTER_VERIFY(state == Qt::Unchecked |
625 | || state == Qt::PartiallyChecked |
626 | || state == Qt::Checked); |
627 | } |
628 | |
629 | Q_Q(QAbstractItemModelTester); |
630 | |
631 | if (!QTestPrivate::testDataGuiRoles(q)) |
632 | return; |
633 | } |
634 | |
635 | /* |
636 | Store what is about to be inserted to make sure it actually happens |
637 | |
638 | \sa rowsInserted() |
639 | */ |
640 | void QAbstractItemModelTesterPrivate::rowsAboutToBeInserted(const QModelIndex &parent, int start, int end) |
641 | { |
642 | qCDebug(lcModelTest) << "rowsAboutToBeInserted" |
643 | << "start=" << start << "end=" << end << "parent=" << parent |
644 | << "parent data=" << model->data(parent).toString() |
645 | << "current count of parent=" << model->rowCount(parent) |
646 | << "last before insertion=" << model->index(start - 1, 0, parent) << model->data(model->index(start - 1, 0, parent)); |
647 | |
648 | Changing c; |
649 | c.parent = parent; |
650 | c.oldSize = model->rowCount(parent); |
651 | c.last = (start - 1 >= 0) ? model->index(start - 1, 0, parent).data() : QVariant(); |
652 | c.next = (start < c.oldSize) ? model->index(start, 0, parent).data() : QVariant(); |
653 | insert.push(c); |
654 | } |
655 | |
656 | /* |
657 | Confirm that what was said was going to happen actually did |
658 | |
659 | \sa rowsAboutToBeInserted() |
660 | */ |
661 | void QAbstractItemModelTesterPrivate::rowsInserted(const QModelIndex &parent, int start, int end) |
662 | { |
663 | qCDebug(lcModelTest) << "rowsInserted" |
664 | << "start=" << start << "end=" << end << "parent=" << parent |
665 | << "parent data=" << model->data(parent).toString() |
666 | << "current count of parent=" << model->rowCount(parent); |
667 | |
668 | for (int i = start; i <= end; ++i) { |
669 | qCDebug(lcModelTest) << " itemWasInserted:" << i |
670 | << model->index(i, 0, parent).data(); |
671 | } |
672 | |
673 | |
674 | Changing c = insert.pop(); |
675 | MODELTESTER_COMPARE(parent, c.parent); |
676 | |
677 | MODELTESTER_COMPARE(model->rowCount(parent), c.oldSize + (end - start + 1)); |
678 | if (start - 1 >= 0) |
679 | MODELTESTER_COMPARE(model->data(model->index(start - 1, 0, c.parent)), c.last); |
680 | |
681 | if (end + 1 < model->rowCount(c.parent)) { |
682 | if (c.next != model->data(model->index(end + 1, 0, c.parent))) { |
683 | qDebug() << start << end; |
684 | for (int i = 0; i < model->rowCount(); ++i) |
685 | qDebug() << model->index(i, 0).data().toString(); |
686 | qDebug() << c.next << model->data(model->index(end + 1, 0, c.parent)); |
687 | } |
688 | |
689 | MODELTESTER_COMPARE(model->data(model->index(end + 1, 0, c.parent)), c.next); |
690 | } |
691 | } |
692 | |
693 | void QAbstractItemModelTesterPrivate::layoutAboutToBeChanged() |
694 | { |
695 | for (int i = 0; i < qBound(0, model->rowCount(), 100); ++i) |
696 | changing.append(QPersistentModelIndex(model->index(i, 0))); |
697 | } |
698 | |
699 | void QAbstractItemModelTesterPrivate::layoutChanged() |
700 | { |
701 | for (int i = 0; i < changing.count(); ++i) { |
702 | QPersistentModelIndex p = changing[i]; |
703 | MODELTESTER_COMPARE(model->index(p.row(), p.column(), p.parent()), QModelIndex(p)); |
704 | } |
705 | changing.clear(); |
706 | } |
707 | |
708 | /* |
709 | Store what is about to be inserted to make sure it actually happens |
710 | |
711 | \sa rowsRemoved() |
712 | */ |
713 | void QAbstractItemModelTesterPrivate::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) |
714 | { |
715 | qCDebug(lcModelTest) << "rowsAboutToBeRemoved" |
716 | << "start=" << start << "end=" << end << "parent=" << parent |
717 | << "parent data=" << model->data(parent).toString() |
718 | << "current count of parent=" << model->rowCount(parent) |
719 | << "last before removal=" << model->index(start - 1, 0, parent) << model->data(model->index(start - 1, 0, parent)); |
720 | |
721 | Changing c; |
722 | c.parent = parent; |
723 | c.oldSize = model->rowCount(parent); |
724 | if (start > 0 && model->columnCount(parent) > 0) { |
725 | const QModelIndex startIndex = model->index(start - 1, 0, parent); |
726 | MODELTESTER_VERIFY(startIndex.isValid()); |
727 | c.last = model->data(startIndex); |
728 | } |
729 | if (end < c.oldSize - 1 && model->columnCount(parent) > 0) { |
730 | const QModelIndex endIndex = model->index(end + 1, 0, parent); |
731 | MODELTESTER_VERIFY(endIndex.isValid()); |
732 | c.next = model->data(endIndex); |
733 | } |
734 | |
735 | remove.push(c); |
736 | } |
737 | |
738 | /* |
739 | Confirm that what was said was going to happen actually did |
740 | |
741 | \sa rowsAboutToBeRemoved() |
742 | */ |
743 | void QAbstractItemModelTesterPrivate::rowsRemoved(const QModelIndex &parent, int start, int end) |
744 | { |
745 | qCDebug(lcModelTest) << "rowsRemoved" |
746 | << "start=" << start << "end=" << end << "parent=" << parent |
747 | << "parent data=" << model->data(parent).toString() |
748 | << "current count of parent=" << model->rowCount(parent); |
749 | |
750 | Changing c = remove.pop(); |
751 | MODELTESTER_COMPARE(parent, c.parent); |
752 | MODELTESTER_COMPARE(model->rowCount(parent), c.oldSize - (end - start + 1)); |
753 | if (start > 0) |
754 | MODELTESTER_COMPARE(model->data(model->index(start - 1, 0, c.parent)), c.last); |
755 | if (end < c.oldSize - 1) |
756 | MODELTESTER_COMPARE(model->data(model->index(start, 0, c.parent)), c.next); |
757 | } |
758 | |
759 | void QAbstractItemModelTesterPrivate::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) |
760 | { |
761 | MODELTESTER_VERIFY(topLeft.isValid()); |
762 | MODELTESTER_VERIFY(bottomRight.isValid()); |
763 | QModelIndex commonParent = bottomRight.parent(); |
764 | MODELTESTER_COMPARE(topLeft.parent(), commonParent); |
765 | MODELTESTER_VERIFY(topLeft.row() <= bottomRight.row()); |
766 | MODELTESTER_VERIFY(topLeft.column() <= bottomRight.column()); |
767 | int rowCount = model->rowCount(commonParent); |
768 | int columnCount = model->columnCount(commonParent); |
769 | MODELTESTER_VERIFY(bottomRight.row() < rowCount); |
770 | MODELTESTER_VERIFY(bottomRight.column() < columnCount); |
771 | } |
772 | |
773 | void QAbstractItemModelTesterPrivate::(Qt::Orientation orientation, int start, int end) |
774 | { |
775 | MODELTESTER_VERIFY(start >= 0); |
776 | MODELTESTER_VERIFY(end >= 0); |
777 | MODELTESTER_VERIFY(start <= end); |
778 | int itemCount = orientation == Qt::Vertical ? model->rowCount() : model->columnCount(); |
779 | MODELTESTER_VERIFY(start < itemCount); |
780 | MODELTESTER_VERIFY(end < itemCount); |
781 | } |
782 | |
783 | bool QAbstractItemModelTesterPrivate::verify(bool statement, |
784 | const char *statementStr, const char *description, |
785 | const char *file, int line) |
786 | { |
787 | static const char formatString[] = "FAIL! %s (%s) returned FALSE (%s:%d)" ; |
788 | |
789 | switch (failureReportingMode) { |
790 | case QAbstractItemModelTester::FailureReportingMode::QtTest: |
791 | return QTest::qVerify(statement, statementStr, description, file, line); |
792 | break; |
793 | |
794 | case QAbstractItemModelTester::FailureReportingMode::Warning: |
795 | if (!statement) |
796 | qCWarning(lcModelTest, formatString, statementStr, description, file, line); |
797 | break; |
798 | |
799 | case QAbstractItemModelTester::FailureReportingMode::Fatal: |
800 | if (!statement) |
801 | qFatal(formatString, statementStr, description, file, line); |
802 | break; |
803 | } |
804 | |
805 | return statement; |
806 | } |
807 | |
808 | |
809 | template<typename T1, typename T2> |
810 | bool QAbstractItemModelTesterPrivate::compare(const T1 &t1, const T2 &t2, |
811 | const char *actual, const char *expected, |
812 | const char *file, int line) |
813 | { |
814 | const bool result = static_cast<bool>(t1 == t2); |
815 | |
816 | static const char formatString[] = "FAIL! Compared values are not the same:\n Actual (%s) %s\n Expected (%s) %s\n (%s:%d)" ; |
817 | |
818 | switch (failureReportingMode) { |
819 | case QAbstractItemModelTester::FailureReportingMode::QtTest: |
820 | return QTest::qCompare(t1, t2, actual, expected, file, line); |
821 | break; |
822 | |
823 | case QAbstractItemModelTester::FailureReportingMode::Warning: |
824 | if (!result) { |
825 | auto t1string = QTest::toString(t1); |
826 | auto t2string = QTest::toString(t2); |
827 | qCWarning(lcModelTest, formatString, actual, t1string ? t1string : "(nullptr)" , |
828 | expected, t2string ? t2string : "(nullptr)" , |
829 | file, line); |
830 | delete [] t1string; |
831 | delete [] t2string; |
832 | } |
833 | break; |
834 | |
835 | case QAbstractItemModelTester::FailureReportingMode::Fatal: |
836 | if (!result) { |
837 | auto t1string = QTest::toString(t1); |
838 | auto t2string = QTest::toString(t2); |
839 | qFatal(formatString, actual, t1string ? t1string : "(nullptr)" , |
840 | expected, t2string ? t2string : "(nullptr)" , |
841 | file, line); |
842 | delete [] t1string; |
843 | delete [] t2string; |
844 | } |
845 | break; |
846 | } |
847 | |
848 | return result; |
849 | } |
850 | |
851 | |
852 | QT_END_NAMESPACE |
853 | |