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