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(msg: "%s: model must not be null" , Q_FUNC_INFO); |
217 | |
218 | Q_D(QAbstractItemModelTester); |
219 | |
220 | auto runAllTests = [d] { d->runAllTests(); }; |
221 | |
222 | connect(sender: model, signal: &QAbstractItemModel::columnsAboutToBeInserted, |
223 | context: this, slot: runAllTests); |
224 | connect(sender: model, signal: &QAbstractItemModel::columnsAboutToBeRemoved, |
225 | context: this, slot: runAllTests); |
226 | connect(sender: model, signal: &QAbstractItemModel::columnsInserted, |
227 | context: this, slot: runAllTests); |
228 | connect(sender: model, signal: &QAbstractItemModel::columnsRemoved, |
229 | context: this, slot: runAllTests); |
230 | connect(sender: model, signal: &QAbstractItemModel::dataChanged, |
231 | context: this, slot: runAllTests); |
232 | connect(sender: model, signal: &QAbstractItemModel::headerDataChanged, |
233 | context: this, slot: runAllTests); |
234 | connect(sender: model, signal: &QAbstractItemModel::layoutAboutToBeChanged, |
235 | context: this, slot: runAllTests); |
236 | connect(sender: model, signal: &QAbstractItemModel::layoutChanged, |
237 | context: this, slot: runAllTests); |
238 | connect(sender: model, signal: &QAbstractItemModel::modelReset, |
239 | context: this, slot: runAllTests); |
240 | connect(sender: model, signal: &QAbstractItemModel::rowsAboutToBeInserted, |
241 | context: this, slot: runAllTests); |
242 | connect(sender: model, signal: &QAbstractItemModel::rowsAboutToBeRemoved, |
243 | context: this, slot: runAllTests); |
244 | connect(sender: model, signal: &QAbstractItemModel::rowsInserted, |
245 | context: this, slot: runAllTests); |
246 | connect(sender: model, signal: &QAbstractItemModel::rowsRemoved, |
247 | context: this, slot: runAllTests); |
248 | |
249 | // Special checks for changes |
250 | connect(sender: model, signal: &QAbstractItemModel::layoutAboutToBeChanged, |
251 | context: this, slot: [d]{ d->layoutAboutToBeChanged(); }); |
252 | connect(sender: model, signal: &QAbstractItemModel::layoutChanged, |
253 | context: this, slot: [d]{ d->layoutChanged(); }); |
254 | |
255 | connect(sender: model, signal: &QAbstractItemModel::rowsAboutToBeInserted, |
256 | context: this, slot: [d](const QModelIndex &parent, int start, int end) { d->rowsAboutToBeInserted(parent, start, end); }); |
257 | connect(sender: model, signal: &QAbstractItemModel::rowsAboutToBeRemoved, |
258 | context: this, slot: [d](const QModelIndex &parent, int start, int end) { d->rowsAboutToBeRemoved(parent, start, end); }); |
259 | connect(sender: model, signal: &QAbstractItemModel::rowsInserted, |
260 | context: this, slot: [d](const QModelIndex &parent, int start, int end) { d->rowsInserted(parent, start, end); }); |
261 | connect(sender: model, signal: &QAbstractItemModel::rowsRemoved, |
262 | context: this, slot: [d](const QModelIndex &parent, int start, int end) { d->rowsRemoved(parent, start, end); }); |
263 | connect(sender: model, signal: &QAbstractItemModel::dataChanged, |
264 | context: this, slot: [d](const QModelIndex &topLeft, const QModelIndex &bottomRight) { d->dataChanged(topLeft, bottomRight); }); |
265 | connect(sender: model, signal: &QAbstractItemModel::headerDataChanged, |
266 | context: this, slot: [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(parent: QModelIndex()); |
324 | MODELTESTER_VERIFY(model->columnCount(QModelIndex()) >= 0); |
325 | fetchingMore = true; |
326 | model->fetchMore(parent: QModelIndex()); |
327 | fetchingMore = false; |
328 | Qt::ItemFlags flags = model->flags(index: QModelIndex()); |
329 | MODELTESTER_VERIFY(flags == Qt::ItemIsDropEnabled || flags == 0); |
330 | model->hasChildren(parent: QModelIndex()); |
331 | const bool hasRow = model->hasIndex(row: 0, column: 0); |
332 | QVariant cache; |
333 | if (hasRow) |
334 | model->match(start: model->index(row: 0, column: 0), role: -1, value: cache); |
335 | model->mimeTypes(); |
336 | MODELTESTER_VERIFY(!model->parent(QModelIndex()).isValid()); |
337 | MODELTESTER_VERIFY(model->rowCount() >= 0); |
338 | model->span(index: 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(row: 0, column: 0, parent: QModelIndex()); |
355 | |
356 | // check top row |
357 | int rows = model->rowCount(parent: topIndex); |
358 | MODELTESTER_VERIFY(rows >= 0); |
359 | |
360 | int columns = model->columnCount(parent: 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(row: 0, column: 0, parent: topIndex); |
369 | MODELTESTER_VERIFY(secondLevelIndex.isValid()); |
370 | |
371 | rows = model->rowCount(parent: secondLevelIndex); |
372 | MODELTESTER_VERIFY(rows >= 0); |
373 | |
374 | columns = model->columnCount(parent: 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->rowCount() == 0 || model->columnCount() == 0) |
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(row: 0, column: 0, parent: QModelIndex()); |
452 | MODELTESTER_VERIFY(topIndex.isValid()); |
453 | MODELTESTER_VERIFY(!model->parent(topIndex).isValid()); |
454 | |
455 | // Common error test #2, make sure that a second level index has a parent |
456 | // that is the first level index. |
457 | if (model->rowCount(parent: topIndex) > 0) { |
458 | QModelIndex childIndex = model->index(row: 0, column: 0, parent: topIndex); |
459 | MODELTESTER_VERIFY(childIndex.isValid()); |
460 | MODELTESTER_COMPARE(model->parent(childIndex), topIndex); |
461 | } |
462 | |
463 | // Common error test #3, the second column should NOT have the same children |
464 | // as the first column in a row. |
465 | // Usually the second column shouldn't have children. |
466 | if (model->hasIndex(row: 0, column: 1)) { |
467 | QModelIndex topIndex1 = model->index(row: 0, column: 1, parent: QModelIndex()); |
468 | MODELTESTER_VERIFY(topIndex1.isValid()); |
469 | if (model->rowCount(parent: topIndex) > 0 && model->rowCount(parent: topIndex1) > 0) { |
470 | QModelIndex childIndex = model->index(row: 0, column: 0, parent: topIndex); |
471 | MODELTESTER_VERIFY(childIndex.isValid()); |
472 | QModelIndex childIndex1 = model->index(row: 0, column: 0, parent: topIndex1); |
473 | MODELTESTER_VERIFY(childIndex1.isValid()); |
474 | MODELTESTER_VERIFY(childIndex != childIndex1); |
475 | } |
476 | } |
477 | |
478 | // Full test, walk n levels deep through the model making sure that all |
479 | // parent's children correctly specify their parent. |
480 | checkChildren(parent: QModelIndex()); |
481 | } |
482 | |
483 | /* |
484 | Called from the parent() test. |
485 | |
486 | A model that returns an index of parent X should also return X when asking |
487 | for the parent of the index. |
488 | |
489 | This recursive function does pretty extensive testing on the whole model in an |
490 | effort to catch edge cases. |
491 | |
492 | This function assumes that rowCount(), columnCount() and index() already work. |
493 | If they have a bug it will point it out, but the above tests should have already |
494 | found the basic bugs because it is easier to figure out the problem in |
495 | those tests then this one. |
496 | */ |
497 | void QAbstractItemModelTesterPrivate::checkChildren(const QModelIndex &parent, int currentDepth) |
498 | { |
499 | // First just try walking back up the tree. |
500 | QModelIndex p = parent; |
501 | while (p.isValid()) |
502 | p = p.parent(); |
503 | |
504 | // For models that are dynamically populated |
505 | if (model->canFetchMore(parent)) { |
506 | fetchingMore = true; |
507 | model->fetchMore(parent); |
508 | fetchingMore = false; |
509 | } |
510 | |
511 | const int rows = model->rowCount(parent); |
512 | const int columns = model->columnCount(parent); |
513 | |
514 | if (rows > 0) |
515 | MODELTESTER_VERIFY(model->hasChildren(parent)); |
516 | |
517 | // Some further testing against rows(), columns(), and hasChildren() |
518 | MODELTESTER_VERIFY(rows >= 0); |
519 | MODELTESTER_VERIFY(columns >= 0); |
520 | if (rows > 0 && columns > 0) |
521 | MODELTESTER_VERIFY(model->hasChildren(parent)); |
522 | |
523 | const QModelIndex topLeftChild = model->index(row: 0, column: 0, parent); |
524 | |
525 | MODELTESTER_VERIFY(!model->hasIndex(rows, 0, parent)); |
526 | MODELTESTER_VERIFY(!model->hasIndex(rows + 1, 0, parent)); |
527 | |
528 | for (int r = 0; r < rows; ++r) { |
529 | MODELTESTER_VERIFY(!model->hasIndex(r, columns, parent)); |
530 | MODELTESTER_VERIFY(!model->hasIndex(r, columns + 1, parent)); |
531 | for (int c = 0; c < columns; ++c) { |
532 | MODELTESTER_VERIFY(model->hasIndex(r, c, parent)); |
533 | QModelIndex index = model->index(row: r, column: c, parent); |
534 | // rowCount() and columnCount() said that it existed... |
535 | if (!index.isValid()) |
536 | qCWarning(lcModelTest) << "Got invalid index at row=" << r << "col=" << c << "parent=" << parent; |
537 | MODELTESTER_VERIFY(index.isValid()); |
538 | |
539 | // index() should always return the same index when called twice in a row |
540 | QModelIndex modifiedIndex = model->index(row: r, column: c, parent); |
541 | MODELTESTER_COMPARE(index, modifiedIndex); |
542 | |
543 | { |
544 | const QModelIndex sibling = model->sibling(row: r, column: c, idx: topLeftChild); |
545 | MODELTESTER_COMPARE(index, sibling); |
546 | } |
547 | { |
548 | const QModelIndex sibling = topLeftChild.sibling(arow: r, acolumn: c); |
549 | MODELTESTER_COMPARE(index, sibling); |
550 | } |
551 | |
552 | // Some basic checking on the index that is returned |
553 | MODELTESTER_COMPARE(index.model(), model); |
554 | MODELTESTER_COMPARE(index.row(), r); |
555 | MODELTESTER_COMPARE(index.column(), c); |
556 | |
557 | // If the next test fails here is some somewhat useful debug you play with. |
558 | if (model->parent(child: index) != parent) { |
559 | qCWarning(lcModelTest) << "Inconsistent parent() implementation detected:" ; |
560 | qCWarning(lcModelTest) << " index=" << index << "exp. parent=" << parent << "act. parent=" << model->parent(child: index); |
561 | qCWarning(lcModelTest) << " row=" << r << "col=" << c << "depth=" << currentDepth; |
562 | qCWarning(lcModelTest) << " data for child" << model->data(index).toString(); |
563 | qCWarning(lcModelTest) << " data for parent" << model->data(index: parent).toString(); |
564 | } |
565 | |
566 | // Check that we can get back our real parent. |
567 | MODELTESTER_COMPARE(model->parent(index), parent); |
568 | |
569 | QPersistentModelIndex persistentIndex = index; |
570 | |
571 | // recursively go down the children |
572 | if (model->hasChildren(parent: index) && currentDepth < 10) |
573 | checkChildren(parent: index, currentDepth: currentDepth + 1); |
574 | |
575 | // make sure that after testing the children that the index doesn't change. |
576 | QModelIndex newerIndex = model->index(row: r, column: c, parent); |
577 | MODELTESTER_COMPARE(persistentIndex, newerIndex); |
578 | } |
579 | } |
580 | } |
581 | |
582 | /* |
583 | Tests model's implementation of QAbstractItemModel::data() |
584 | */ |
585 | void QAbstractItemModelTesterPrivate::data() |
586 | { |
587 | if (model->rowCount() == 0 || model->columnCount() == 0) |
588 | return; |
589 | |
590 | MODELTESTER_VERIFY(model->index(0, 0).isValid()); |
591 | |
592 | // General Purpose roles that should return a QString |
593 | QVariant variant; |
594 | variant = model->data(index: model->index(row: 0, column: 0), role: Qt::DisplayRole); |
595 | if (variant.isValid()) |
596 | MODELTESTER_VERIFY(variant.canConvert<QString>()); |
597 | variant = model->data(index: model->index(row: 0, column: 0), role: Qt::ToolTipRole); |
598 | if (variant.isValid()) |
599 | MODELTESTER_VERIFY(variant.canConvert<QString>()); |
600 | variant = model->data(index: model->index(row: 0, column: 0), role: Qt::StatusTipRole); |
601 | if (variant.isValid()) |
602 | MODELTESTER_VERIFY(variant.canConvert<QString>()); |
603 | variant = model->data(index: model->index(row: 0, column: 0), role: Qt::WhatsThisRole); |
604 | if (variant.isValid()) |
605 | MODELTESTER_VERIFY(variant.canConvert<QString>()); |
606 | |
607 | // General Purpose roles that should return a QSize |
608 | variant = model->data(index: model->index(row: 0, column: 0), role: Qt::SizeHintRole); |
609 | if (variant.isValid()) |
610 | MODELTESTER_VERIFY(variant.canConvert<QSize>()); |
611 | |
612 | // Check that the alignment is one we know about |
613 | QVariant textAlignmentVariant = model->data(index: model->index(row: 0, column: 0), role: Qt::TextAlignmentRole); |
614 | if (textAlignmentVariant.isValid()) { |
615 | Qt::Alignment alignment = qvariant_cast<Qt::Alignment>(v: textAlignmentVariant); |
616 | MODELTESTER_COMPARE(alignment, (alignment & (Qt::AlignHorizontal_Mask | Qt::AlignVertical_Mask))); |
617 | } |
618 | |
619 | // Check that the "check state" is one we know about. |
620 | QVariant checkStateVariant = model->data(index: model->index(row: 0, column: 0), role: Qt::CheckStateRole); |
621 | if (checkStateVariant.isValid()) { |
622 | int state = checkStateVariant.toInt(); |
623 | MODELTESTER_VERIFY(state == Qt::Unchecked |
624 | || state == Qt::PartiallyChecked |
625 | || state == Qt::Checked); |
626 | } |
627 | |
628 | Q_Q(QAbstractItemModelTester); |
629 | |
630 | if (!QTestPrivate::testDataGuiRoles(tester: q)) |
631 | return; |
632 | } |
633 | |
634 | /* |
635 | Store what is about to be inserted to make sure it actually happens |
636 | |
637 | \sa rowsInserted() |
638 | */ |
639 | void QAbstractItemModelTesterPrivate::rowsAboutToBeInserted(const QModelIndex &parent, int start, int end) |
640 | { |
641 | qCDebug(lcModelTest) << "rowsAboutToBeInserted" |
642 | << "start=" << start << "end=" << end << "parent=" << parent |
643 | << "parent data=" << model->data(index: parent).toString() |
644 | << "current count of parent=" << model->rowCount(parent) |
645 | << "last before insertion=" << model->index(row: start - 1, column: 0, parent) << model->data(index: model->index(row: start - 1, column: 0, parent)); |
646 | |
647 | Changing c; |
648 | c.parent = parent; |
649 | c.oldSize = model->rowCount(parent); |
650 | c.last = (start - 1 >= 0) ? model->index(row: start - 1, column: 0, parent).data() : QVariant(); |
651 | c.next = (start < c.oldSize) ? model->index(row: start, column: 0, parent).data() : QVariant(); |
652 | insert.push(t: c); |
653 | } |
654 | |
655 | /* |
656 | Confirm that what was said was going to happen actually did |
657 | |
658 | \sa rowsAboutToBeInserted() |
659 | */ |
660 | void QAbstractItemModelTesterPrivate::rowsInserted(const QModelIndex &parent, int start, int end) |
661 | { |
662 | qCDebug(lcModelTest) << "rowsInserted" |
663 | << "start=" << start << "end=" << end << "parent=" << parent |
664 | << "parent data=" << model->data(index: parent).toString() |
665 | << "current count of parent=" << model->rowCount(parent); |
666 | |
667 | for (int i = start; i <= end; ++i) { |
668 | qCDebug(lcModelTest) << " itemWasInserted:" << i |
669 | << model->index(row: i, column: 0, parent).data(); |
670 | } |
671 | |
672 | |
673 | Changing c = insert.pop(); |
674 | MODELTESTER_COMPARE(parent, c.parent); |
675 | |
676 | MODELTESTER_COMPARE(model->rowCount(parent), c.oldSize + (end - start + 1)); |
677 | if (start - 1 >= 0) |
678 | MODELTESTER_COMPARE(model->data(model->index(start - 1, 0, c.parent)), c.last); |
679 | |
680 | if (end + 1 < model->rowCount(parent: c.parent)) { |
681 | if (c.next != model->data(index: model->index(row: end + 1, column: 0, parent: c.parent))) { |
682 | qDebug() << start << end; |
683 | for (int i = 0; i < model->rowCount(); ++i) |
684 | qDebug() << model->index(row: i, column: 0).data().toString(); |
685 | qDebug() << c.next << model->data(index: model->index(row: end + 1, column: 0, parent: c.parent)); |
686 | } |
687 | |
688 | MODELTESTER_COMPARE(model->data(model->index(end + 1, 0, c.parent)), c.next); |
689 | } |
690 | } |
691 | |
692 | void QAbstractItemModelTesterPrivate::layoutAboutToBeChanged() |
693 | { |
694 | for (int i = 0; i < qBound(min: 0, val: model->rowCount(), max: 100); ++i) |
695 | changing.append(t: QPersistentModelIndex(model->index(row: i, column: 0))); |
696 | } |
697 | |
698 | void QAbstractItemModelTesterPrivate::layoutChanged() |
699 | { |
700 | for (int i = 0; i < changing.count(); ++i) { |
701 | QPersistentModelIndex p = changing[i]; |
702 | MODELTESTER_COMPARE(model->index(p.row(), p.column(), p.parent()), QModelIndex(p)); |
703 | } |
704 | changing.clear(); |
705 | } |
706 | |
707 | /* |
708 | Store what is about to be inserted to make sure it actually happens |
709 | |
710 | \sa rowsRemoved() |
711 | */ |
712 | void QAbstractItemModelTesterPrivate::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) |
713 | { |
714 | qCDebug(lcModelTest) << "rowsAboutToBeRemoved" |
715 | << "start=" << start << "end=" << end << "parent=" << parent |
716 | << "parent data=" << model->data(index: parent).toString() |
717 | << "current count of parent=" << model->rowCount(parent) |
718 | << "last before removal=" << model->index(row: start - 1, column: 0, parent) << model->data(index: model->index(row: start - 1, column: 0, parent)); |
719 | |
720 | Changing c; |
721 | c.parent = parent; |
722 | c.oldSize = model->rowCount(parent); |
723 | if (start > 0 && model->columnCount(parent) > 0) { |
724 | const QModelIndex startIndex = model->index(row: start - 1, column: 0, parent); |
725 | MODELTESTER_VERIFY(startIndex.isValid()); |
726 | c.last = model->data(index: startIndex); |
727 | } |
728 | if (end < c.oldSize - 1 && model->columnCount(parent) > 0) { |
729 | const QModelIndex endIndex = model->index(row: end + 1, column: 0, parent); |
730 | MODELTESTER_VERIFY(endIndex.isValid()); |
731 | c.next = model->data(index: endIndex); |
732 | } |
733 | |
734 | remove.push(t: c); |
735 | } |
736 | |
737 | /* |
738 | Confirm that what was said was going to happen actually did |
739 | |
740 | \sa rowsAboutToBeRemoved() |
741 | */ |
742 | void QAbstractItemModelTesterPrivate::rowsRemoved(const QModelIndex &parent, int start, int end) |
743 | { |
744 | qCDebug(lcModelTest) << "rowsRemoved" |
745 | << "start=" << start << "end=" << end << "parent=" << parent |
746 | << "parent data=" << model->data(index: parent).toString() |
747 | << "current count of parent=" << model->rowCount(parent); |
748 | |
749 | Changing c = remove.pop(); |
750 | MODELTESTER_COMPARE(parent, c.parent); |
751 | MODELTESTER_COMPARE(model->rowCount(parent), c.oldSize - (end - start + 1)); |
752 | if (start > 0) |
753 | MODELTESTER_COMPARE(model->data(model->index(start - 1, 0, c.parent)), c.last); |
754 | if (end < c.oldSize - 1) |
755 | MODELTESTER_COMPARE(model->data(model->index(start, 0, c.parent)), c.next); |
756 | } |
757 | |
758 | void QAbstractItemModelTesterPrivate::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) |
759 | { |
760 | MODELTESTER_VERIFY(topLeft.isValid()); |
761 | MODELTESTER_VERIFY(bottomRight.isValid()); |
762 | QModelIndex commonParent = bottomRight.parent(); |
763 | MODELTESTER_COMPARE(topLeft.parent(), commonParent); |
764 | MODELTESTER_VERIFY(topLeft.row() <= bottomRight.row()); |
765 | MODELTESTER_VERIFY(topLeft.column() <= bottomRight.column()); |
766 | int rowCount = model->rowCount(parent: commonParent); |
767 | int columnCount = model->columnCount(parent: commonParent); |
768 | MODELTESTER_VERIFY(bottomRight.row() < rowCount); |
769 | MODELTESTER_VERIFY(bottomRight.column() < columnCount); |
770 | } |
771 | |
772 | void QAbstractItemModelTesterPrivate::(Qt::Orientation orientation, int start, int end) |
773 | { |
774 | MODELTESTER_VERIFY(start >= 0); |
775 | MODELTESTER_VERIFY(end >= 0); |
776 | MODELTESTER_VERIFY(start <= end); |
777 | int itemCount = orientation == Qt::Vertical ? model->rowCount() : model->columnCount(); |
778 | MODELTESTER_VERIFY(start < itemCount); |
779 | MODELTESTER_VERIFY(end < itemCount); |
780 | } |
781 | |
782 | bool QAbstractItemModelTesterPrivate::verify(bool statement, |
783 | const char *statementStr, const char *description, |
784 | const char *file, int line) |
785 | { |
786 | static const char formatString[] = "FAIL! %s (%s) returned FALSE (%s:%d)" ; |
787 | |
788 | switch (failureReportingMode) { |
789 | case QAbstractItemModelTester::FailureReportingMode::QtTest: |
790 | return QTest::qVerify(statement, statementStr, description, file, line); |
791 | break; |
792 | |
793 | case QAbstractItemModelTester::FailureReportingMode::Warning: |
794 | if (!statement) |
795 | qCWarning(lcModelTest, formatString, statementStr, description, file, line); |
796 | break; |
797 | |
798 | case QAbstractItemModelTester::FailureReportingMode::Fatal: |
799 | if (!statement) |
800 | qFatal(msg: formatString, statementStr, description, file, line); |
801 | break; |
802 | } |
803 | |
804 | return statement; |
805 | } |
806 | |
807 | |
808 | template<typename T1, typename T2> |
809 | bool QAbstractItemModelTesterPrivate::compare(const T1 &t1, const T2 &t2, |
810 | const char *actual, const char *expected, |
811 | const char *file, int line) |
812 | { |
813 | const bool result = static_cast<bool>(t1 == t2); |
814 | |
815 | static const char formatString[] = "FAIL! Compared values are not the same:\n Actual (%s) %s\n Expected (%s) %s\n (%s:%d)" ; |
816 | |
817 | switch (failureReportingMode) { |
818 | case QAbstractItemModelTester::FailureReportingMode::QtTest: |
819 | return QTest::qCompare(t1, t2, actual, expected, file, line); |
820 | break; |
821 | |
822 | case QAbstractItemModelTester::FailureReportingMode::Warning: |
823 | if (!result) { |
824 | auto t1string = QTest::toString(t1); |
825 | auto t2string = QTest::toString(t2); |
826 | qCWarning(lcModelTest, formatString, actual, t1string, expected, t2string, file, line); |
827 | delete [] t1string; |
828 | delete [] t2string; |
829 | } |
830 | break; |
831 | |
832 | case QAbstractItemModelTester::FailureReportingMode::Fatal: |
833 | if (!result) { |
834 | auto t1string = QTest::toString(t1); |
835 | auto t2string = QTest::toString(t2); |
836 | qFatal(msg: formatString, actual, t1string, expected, t2string, file, line); |
837 | delete [] t1string; |
838 | delete [] t2string; |
839 | } |
840 | break; |
841 | } |
842 | |
843 | return result; |
844 | } |
845 | |
846 | |
847 | QT_END_NAMESPACE |
848 | |