Long live QAbstractItemModelTester!
AKA the model tester, living in QtTestLib now. Underwent some significant refactoring from the original modeltester: in particular, it will stop testing illegal indices. [ChangeLog][QtTestLib] Added QAbstractItemModelTester, a class to help testing item models. Change-Id: I0e5efed7217330be11465ce3abb3590f3f2601a4 Reviewed-by: David Faure <david.faure@kdab.com>
This commit is contained in:
parent
2d260eada8
commit
b3e4be2d8b
@ -31,7 +31,9 @@
|
||||
|
||||
Qt Test provides classes for unit testing Qt applications and libraries.
|
||||
All public methods are in the \l QTest namespace. In addition, the
|
||||
\l QSignalSpy class provides easy introspection for Qt's signals and slots.
|
||||
\l QSignalSpy class provides easy introspection for Qt's signals and slots,
|
||||
and the \l QAbstractItemModelTester allows for non-destructive testing
|
||||
of item models.
|
||||
|
||||
\section1 Getting Started
|
||||
|
||||
|
807
src/testlib/qabstractitemmodeltester.cpp
Normal file
807
src/testlib/qabstractitemmodeltester.cpp
Normal file
@ -0,0 +1,807 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 The Qt Company Ltd.
|
||||
** Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Giuseppe D'Angelo <giuseppe.dangelo@kdab.com>
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of the test suite of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include "qabstractitemmodeltester.h"
|
||||
|
||||
#include <private/qobject_p.h>
|
||||
#include <QtCore/QPointer>
|
||||
#include <QtCore/QAbstractItemModel>
|
||||
#include <QtCore/QStack>
|
||||
#include <QtTest/QtTest>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
Q_LOGGING_CATEGORY(lcModelTest, "qt.modeltest")
|
||||
|
||||
#define MODELTESTER_VERIFY(statement) \
|
||||
do { \
|
||||
if (!verify(static_cast<bool>(statement), #statement, "", __FILE__, __LINE__)) \
|
||||
return; \
|
||||
} while (false)
|
||||
|
||||
#define MODELTESTER_COMPARE(actual, expected) \
|
||||
do { \
|
||||
if (!compare((actual), (expected), #actual, #expected, __FILE__, __LINE__)) \
|
||||
return; \
|
||||
} while (false)
|
||||
|
||||
class QAbstractItemModelTesterPrivate : public QObjectPrivate
|
||||
{
|
||||
Q_DECLARE_PUBLIC(QAbstractItemModelTester)
|
||||
public:
|
||||
QAbstractItemModelTesterPrivate(QAbstractItemModel *model, QAbstractItemModelTester::FailureReportingMode failureReportingMode);
|
||||
|
||||
void nonDestructiveBasicTest();
|
||||
void rowAndColumnCount();
|
||||
void hasIndex();
|
||||
void index();
|
||||
void parent();
|
||||
void data();
|
||||
|
||||
void runAllTests();
|
||||
void layoutAboutToBeChanged();
|
||||
void layoutChanged();
|
||||
void rowsAboutToBeInserted(const QModelIndex &parent, int start, int end);
|
||||
void rowsInserted(const QModelIndex &parent, int start, int end);
|
||||
void rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end);
|
||||
void rowsRemoved(const QModelIndex &parent, int start, int end);
|
||||
void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight);
|
||||
void headerDataChanged(Qt::Orientation orientation, int start, int end);
|
||||
|
||||
private:
|
||||
void checkChildren(const QModelIndex &parent, int currentDepth = 0);
|
||||
|
||||
bool verify(bool statement, const char *statementStr, const char *description, const char *file, int line);
|
||||
|
||||
template<typename T1, typename T2>
|
||||
bool compare(const T1 &t1, const T2 &t2,
|
||||
const char *actual, const char *expected,
|
||||
const char *file, int line);
|
||||
|
||||
QPointer<QAbstractItemModel> model;
|
||||
QAbstractItemModelTester::FailureReportingMode failureReportingMode;
|
||||
|
||||
struct Changing {
|
||||
QModelIndex parent;
|
||||
int oldSize;
|
||||
QVariant last;
|
||||
QVariant next;
|
||||
};
|
||||
QStack<Changing> insert;
|
||||
QStack<Changing> remove;
|
||||
|
||||
bool fetchingMore;
|
||||
|
||||
QList<QPersistentModelIndex> changing;
|
||||
};
|
||||
|
||||
/*!
|
||||
\class QAbstractItemModelTester
|
||||
\since 5.11
|
||||
\inmodule QtTest
|
||||
|
||||
\brief The QAbstractItemModelTester class helps testing QAbstractItemModel subclasses.
|
||||
|
||||
The QAbstractItemModelTester class is a utility class to test item models.
|
||||
|
||||
When implementing an item model (that is, a concrete QAbstractItemModel
|
||||
subclass) one must abide to a very strict set of rules that ensure
|
||||
consistency for users of the model (views, proxy models, and so on).
|
||||
|
||||
For instance, for a given index, a model's reimplementation of
|
||||
\l{QAbstractItemModel::hasChildren()}{hasChildren()} must be consistent
|
||||
with the values returned by \l{QAbstractItemModel::rowCount()}{rowCount()}
|
||||
and \l{QAbstractItemModel::columnCount()}{columnCount()}.
|
||||
|
||||
QAbstractItemModelTester helps catching the most common errors in custom
|
||||
item model classes. By performing a series of tests, it
|
||||
will try to check that the model status is consistent at all times. The
|
||||
tests will be repeated automatically every time the model is modified.
|
||||
|
||||
QAbstractItemModelTester employs non-destructive tests, which typically
|
||||
consist in reading data and metadata out of a given item model.
|
||||
QAbstractItemModelTester will also attempt illegal modifications of
|
||||
the model. In models which are properly implemented, such attempts
|
||||
should be rejected, and no data should be changed as a consequence.
|
||||
|
||||
\section1 Usage
|
||||
|
||||
Using QAbstractItemModelTester is straightforward. In a \l{Qt Test Overview}{test case}
|
||||
it is sufficient to create an instance, passing the model that
|
||||
needs to be tested to the constructor:
|
||||
|
||||
\code
|
||||
MyModel *modelToBeTested = ...;
|
||||
auto tester = new QAbstractItemModelTester(modelToBeTested);
|
||||
\endcode
|
||||
|
||||
QAbstractItemModelTester will report testing failures through the
|
||||
Qt Test logging mechanisms.
|
||||
|
||||
It is also possible to use QAbstractItemModelTester outside of a test case.
|
||||
For instance, it may be useful to test an item model used by an application
|
||||
without the need of building an explicit unit test for such a model (which
|
||||
might be challenging). In order to use QAbstractItemModelTester outside of
|
||||
a test case, pass one of the \c QAbstractItemModelTester::FailureReportingMode
|
||||
enumerators to its constructor, therefore specifying how failures should
|
||||
be logged.
|
||||
|
||||
QAbstractItemModelTester may also report additional debugging information
|
||||
as logging messages under the \c qt.modeltest logging category. Such
|
||||
debug logging is disabled by default; refer to the
|
||||
QLoggingCategory documentation to learn how to enable it.
|
||||
|
||||
\note While QAbstractItemModelTester is a valid help for development and
|
||||
testing of custom item models, it does not (and cannot) catch all possible
|
||||
problems in QAbstractItemModel subclasses. Notably, it will never perform
|
||||
meaningful destructive testing of a model, which must be therefore tested
|
||||
separately.
|
||||
|
||||
\sa {Model/View Programming}, QAbstractItemModel
|
||||
*/
|
||||
|
||||
/*!
|
||||
\enum QAbstractItemModelTester::FailureReportingMode
|
||||
|
||||
This enumeration specifies how QAbstractItemModelTester should report
|
||||
a failure when it tests a QAbstractItemModel subclass.
|
||||
|
||||
\value FailureReportingMode::QtTest The failures will be reported through
|
||||
QtTest's logging mechanism.
|
||||
|
||||
\value FailureReportingMode::Warning The failures will be reported as
|
||||
warning messages in the \c{qt.modeltest} logging category.
|
||||
|
||||
\value FailureReportingMode::Fatal A failure will cause immediate and
|
||||
abnormal program termination. The reason for the failure will be reported
|
||||
using \c{qFatal()}.
|
||||
*/
|
||||
|
||||
/*!
|
||||
Creates a model tester instance, with the given \a parent, that will test
|
||||
the model \a model.
|
||||
*/
|
||||
QAbstractItemModelTester::QAbstractItemModelTester(QAbstractItemModel *model, QObject *parent)
|
||||
: QAbstractItemModelTester(model, FailureReportingMode::QtTest, parent)
|
||||
{
|
||||
}
|
||||
|
||||
/*!
|
||||
Creates a model tester instance, with the given \a parent, that will test
|
||||
the model \a model, using the specified \a mode to report test failures.
|
||||
|
||||
\sa QAbstractItemModelTester::FailureReportingMode
|
||||
*/
|
||||
QAbstractItemModelTester::QAbstractItemModelTester(QAbstractItemModel *model, FailureReportingMode mode, QObject *parent)
|
||||
: QObject(*new QAbstractItemModelTesterPrivate(model, mode), parent)
|
||||
{
|
||||
if (!model)
|
||||
qFatal("%s: model must not be null", Q_FUNC_INFO);
|
||||
|
||||
Q_D(QAbstractItemModelTester);
|
||||
|
||||
const auto &runAllTests = [d] { d->runAllTests(); };
|
||||
|
||||
connect(model, &QAbstractItemModel::columnsAboutToBeInserted,
|
||||
this, runAllTests);
|
||||
connect(model, &QAbstractItemModel::columnsAboutToBeRemoved,
|
||||
this, runAllTests);
|
||||
connect(model, &QAbstractItemModel::columnsInserted,
|
||||
this, runAllTests);
|
||||
connect(model, &QAbstractItemModel::columnsRemoved,
|
||||
this, runAllTests);
|
||||
connect(model, &QAbstractItemModel::dataChanged,
|
||||
this, runAllTests);
|
||||
connect(model, &QAbstractItemModel::headerDataChanged,
|
||||
this, runAllTests);
|
||||
connect(model, &QAbstractItemModel::layoutAboutToBeChanged,
|
||||
this, runAllTests);
|
||||
connect(model, &QAbstractItemModel::layoutChanged,
|
||||
this, runAllTests);
|
||||
connect(model, &QAbstractItemModel::modelReset,
|
||||
this, runAllTests);
|
||||
connect(model, &QAbstractItemModel::rowsAboutToBeInserted,
|
||||
this, runAllTests);
|
||||
connect(model, &QAbstractItemModel::rowsAboutToBeRemoved,
|
||||
this, runAllTests);
|
||||
connect(model, &QAbstractItemModel::rowsInserted,
|
||||
this, runAllTests);
|
||||
connect(model, &QAbstractItemModel::rowsRemoved,
|
||||
this, runAllTests);
|
||||
|
||||
// Special checks for changes
|
||||
connect(model, &QAbstractItemModel::layoutAboutToBeChanged,
|
||||
this, [d]{ d->layoutAboutToBeChanged(); });
|
||||
connect(model, &QAbstractItemModel::layoutChanged,
|
||||
this, [d]{ d->layoutChanged(); });
|
||||
|
||||
connect(model, &QAbstractItemModel::rowsAboutToBeInserted,
|
||||
this, [d](const QModelIndex &parent, int start, int end) { d->rowsAboutToBeInserted(parent, start, end); });
|
||||
connect(model, &QAbstractItemModel::rowsAboutToBeRemoved,
|
||||
this, [d](const QModelIndex &parent, int start, int end) { d->rowsAboutToBeRemoved(parent, start, end); });
|
||||
connect(model, &QAbstractItemModel::rowsInserted,
|
||||
this, [d](const QModelIndex &parent, int start, int end) { d->rowsInserted(parent, start, end); });
|
||||
connect(model, &QAbstractItemModel::rowsRemoved,
|
||||
this, [d](const QModelIndex &parent, int start, int end) { d->rowsRemoved(parent, start, end); });
|
||||
connect(model, &QAbstractItemModel::dataChanged,
|
||||
this, [d](const QModelIndex &topLeft, const QModelIndex &bottomRight) { d->dataChanged(topLeft, bottomRight); });
|
||||
connect(model, &QAbstractItemModel::headerDataChanged,
|
||||
this, [d](Qt::Orientation orientation, int start, int end) { d->headerDataChanged(orientation, start, end); });
|
||||
|
||||
runAllTests();
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the model that this instance is testing.
|
||||
*/
|
||||
QAbstractItemModel *QAbstractItemModelTester::model() const
|
||||
{
|
||||
Q_D(const QAbstractItemModelTester);
|
||||
return d->model.data();
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the mode that this instancing is using to report test failures.
|
||||
|
||||
\sa QAbstractItemModelTester::FailureReportingMode
|
||||
*/
|
||||
QAbstractItemModelTester::FailureReportingMode QAbstractItemModelTester::failureReportingMode() const
|
||||
{
|
||||
Q_D(const QAbstractItemModelTester);
|
||||
return d->failureReportingMode;
|
||||
}
|
||||
|
||||
QAbstractItemModelTesterPrivate::QAbstractItemModelTesterPrivate(QAbstractItemModel *model, QAbstractItemModelTester::FailureReportingMode failureReportingMode)
|
||||
: model(model),
|
||||
failureReportingMode(failureReportingMode),
|
||||
fetchingMore(false)
|
||||
{
|
||||
}
|
||||
|
||||
void QAbstractItemModelTesterPrivate::runAllTests()
|
||||
{
|
||||
if (fetchingMore)
|
||||
return;
|
||||
nonDestructiveBasicTest();
|
||||
rowAndColumnCount();
|
||||
hasIndex();
|
||||
index();
|
||||
parent();
|
||||
data();
|
||||
}
|
||||
|
||||
/*!
|
||||
nonDestructiveBasicTest tries to call a number of the basic functions (not all)
|
||||
to make sure the model doesn't outright segfault, testing the functions that makes sense.
|
||||
*/
|
||||
void QAbstractItemModelTesterPrivate::nonDestructiveBasicTest()
|
||||
{
|
||||
MODELTESTER_VERIFY(!model->buddy(QModelIndex()).isValid());
|
||||
model->canFetchMore(QModelIndex());
|
||||
MODELTESTER_VERIFY(model->columnCount(QModelIndex()) >= 0);
|
||||
fetchingMore = true;
|
||||
model->fetchMore(QModelIndex());
|
||||
fetchingMore = false;
|
||||
Qt::ItemFlags flags = model->flags(QModelIndex());
|
||||
MODELTESTER_VERIFY(flags == Qt::ItemIsDropEnabled || flags == 0);
|
||||
model->hasChildren(QModelIndex());
|
||||
model->hasIndex(0, 0);
|
||||
QVariant cache;
|
||||
model->match(QModelIndex(), -1, cache);
|
||||
model->mimeTypes();
|
||||
MODELTESTER_VERIFY(!model->parent(QModelIndex()).isValid());
|
||||
MODELTESTER_VERIFY(model->rowCount() >= 0);
|
||||
model->span(QModelIndex());
|
||||
model->supportedDropActions();
|
||||
model->roleNames();
|
||||
}
|
||||
|
||||
/*!
|
||||
Tests model's implementation of QAbstractItemModel::rowCount(),
|
||||
columnCount() and hasChildren().
|
||||
|
||||
Models that are dynamically populated are not as fully tested here.
|
||||
*/
|
||||
void QAbstractItemModelTesterPrivate::rowAndColumnCount()
|
||||
{
|
||||
if (!model->hasChildren())
|
||||
return;
|
||||
|
||||
QModelIndex topIndex = model->index(0, 0, QModelIndex());
|
||||
|
||||
// check top row
|
||||
int rows = model->rowCount(topIndex);
|
||||
MODELTESTER_VERIFY(rows >= 0);
|
||||
|
||||
int columns = model->columnCount(topIndex);
|
||||
MODELTESTER_VERIFY(columns >= 0);
|
||||
|
||||
if (rows == 0 || columns == 0)
|
||||
return;
|
||||
|
||||
MODELTESTER_VERIFY(model->hasChildren(topIndex));
|
||||
|
||||
QModelIndex secondLevelIndex = model->index(0, 0, topIndex);
|
||||
MODELTESTER_VERIFY(secondLevelIndex.isValid());
|
||||
|
||||
rows = model->rowCount(secondLevelIndex);
|
||||
MODELTESTER_VERIFY(rows >= 0);
|
||||
|
||||
columns = model->columnCount(secondLevelIndex);
|
||||
MODELTESTER_VERIFY(columns >= 0);
|
||||
|
||||
if (rows == 0 || columns == 0)
|
||||
return;
|
||||
|
||||
MODELTESTER_VERIFY(model->hasChildren(secondLevelIndex));
|
||||
|
||||
// rowCount() / columnCount() are tested more extensively in checkChildren()
|
||||
}
|
||||
|
||||
/*!
|
||||
Tests model's implementation of QAbstractItemModel::hasIndex()
|
||||
*/
|
||||
void QAbstractItemModelTesterPrivate::hasIndex()
|
||||
{
|
||||
// Make sure that invalid values returns an invalid index
|
||||
MODELTESTER_VERIFY(!model->hasIndex(-2, -2));
|
||||
MODELTESTER_VERIFY(!model->hasIndex(-2, 0));
|
||||
MODELTESTER_VERIFY(!model->hasIndex(0, -2));
|
||||
|
||||
const int rows = model->rowCount();
|
||||
const int columns = model->columnCount();
|
||||
|
||||
// check out of bounds
|
||||
MODELTESTER_VERIFY(!model->hasIndex(rows, columns));
|
||||
MODELTESTER_VERIFY(!model->hasIndex(rows + 1, columns + 1));
|
||||
|
||||
if (rows > 0 && columns > 0)
|
||||
MODELTESTER_VERIFY(model->hasIndex(0, 0));
|
||||
|
||||
// hasIndex() is tested more extensively in checkChildren(),
|
||||
// but this catches the big mistakes
|
||||
}
|
||||
|
||||
/*!
|
||||
Tests model's implementation of QAbstractItemModel::index()
|
||||
*/
|
||||
void QAbstractItemModelTesterPrivate::index()
|
||||
{
|
||||
const int rows = model->rowCount();
|
||||
const int columns = model->columnCount();
|
||||
|
||||
for (int row = 0; row < rows; ++row) {
|
||||
for (int column = 0; column < columns; ++column) {
|
||||
// Make sure that the same index is *always* returned
|
||||
QModelIndex a = model->index(row, column);
|
||||
QModelIndex b = model->index(row, column);
|
||||
MODELTESTER_VERIFY(a.isValid());
|
||||
MODELTESTER_VERIFY(b.isValid());
|
||||
MODELTESTER_COMPARE(a, b);
|
||||
}
|
||||
}
|
||||
|
||||
// index() is tested more extensively in checkChildren(),
|
||||
// but this catches the big mistakes
|
||||
}
|
||||
|
||||
/*!
|
||||
Tests model's implementation of QAbstractItemModel::parent()
|
||||
*/
|
||||
void QAbstractItemModelTesterPrivate::parent()
|
||||
{
|
||||
// Make sure the model won't crash and will return an invalid QModelIndex
|
||||
// when asked for the parent of an invalid index.
|
||||
MODELTESTER_VERIFY(!model->parent(QModelIndex()).isValid());
|
||||
|
||||
if (!model->hasChildren())
|
||||
return;
|
||||
|
||||
// Column 0 | Column 1 |
|
||||
// QModelIndex() | |
|
||||
// \- topIndex | topIndex1 |
|
||||
// \- childIndex | childIndex1 |
|
||||
|
||||
// Common error test #1, make sure that a top level index has a parent
|
||||
// that is a invalid QModelIndex.
|
||||
QModelIndex topIndex = model->index(0, 0, QModelIndex());
|
||||
MODELTESTER_VERIFY(!model->parent(topIndex).isValid());
|
||||
|
||||
// Common error test #2, make sure that a second level index has a parent
|
||||
// that is the first level index.
|
||||
if (model->hasChildren(topIndex)) {
|
||||
QModelIndex childIndex = model->index(0, 0, topIndex);
|
||||
MODELTESTER_VERIFY(childIndex.isValid());
|
||||
MODELTESTER_COMPARE(model->parent(childIndex), topIndex);
|
||||
}
|
||||
|
||||
// Common error test #3, the second column should NOT have the same children
|
||||
// as the first column in a row.
|
||||
// Usually the second column shouldn't have children.
|
||||
if (model->hasIndex(0, 1)) {
|
||||
QModelIndex topIndex1 = model->index(0, 1, QModelIndex());
|
||||
MODELTESTER_VERIFY(topIndex1.isValid());
|
||||
if (model->hasChildren(topIndex) && model->hasChildren(topIndex1)) {
|
||||
QModelIndex childIndex = model->index(0, 0, topIndex);
|
||||
MODELTESTER_VERIFY(childIndex.isValid());
|
||||
QModelIndex childIndex1 = model->index(0, 0, topIndex1);
|
||||
MODELTESTER_VERIFY(childIndex1.isValid());
|
||||
MODELTESTER_VERIFY(childIndex != childIndex1);
|
||||
}
|
||||
}
|
||||
|
||||
// Full test, walk n levels deep through the model making sure that all
|
||||
// parent's children correctly specify their parent.
|
||||
checkChildren(QModelIndex());
|
||||
}
|
||||
|
||||
/*!
|
||||
Called from the parent() test.
|
||||
|
||||
A model that returns an index of parent X should also return X when asking
|
||||
for the parent of the index.
|
||||
|
||||
This recursive function does pretty extensive testing on the whole model in an
|
||||
effort to catch edge cases.
|
||||
|
||||
This function assumes that rowCount(), columnCount() and index() already work.
|
||||
If they have a bug it will point it out, but the above tests should have already
|
||||
found the basic bugs because it is easier to figure out the problem in
|
||||
those tests then this one.
|
||||
*/
|
||||
void QAbstractItemModelTesterPrivate::checkChildren(const QModelIndex &parent, int currentDepth)
|
||||
{
|
||||
// First just try walking back up the tree.
|
||||
QModelIndex p = parent;
|
||||
while (p.isValid())
|
||||
p = p.parent();
|
||||
|
||||
// For models that are dynamically populated
|
||||
if (model->canFetchMore(parent)) {
|
||||
fetchingMore = true;
|
||||
model->fetchMore(parent);
|
||||
fetchingMore = false;
|
||||
}
|
||||
|
||||
const int rows = model->rowCount(parent);
|
||||
const int columns = model->columnCount(parent);
|
||||
|
||||
if (rows > 0)
|
||||
MODELTESTER_VERIFY(model->hasChildren(parent));
|
||||
|
||||
// Some further testing against rows(), columns(), and hasChildren()
|
||||
MODELTESTER_VERIFY(rows >= 0);
|
||||
MODELTESTER_VERIFY(columns >= 0);
|
||||
if (rows > 0 && columns > 0)
|
||||
MODELTESTER_VERIFY(model->hasChildren(parent));
|
||||
|
||||
const QModelIndex topLeftChild = model->index(0, 0, parent);
|
||||
|
||||
MODELTESTER_VERIFY(!model->hasIndex(rows, 0, parent));
|
||||
MODELTESTER_VERIFY(!model->hasIndex(rows + 1, 0, parent));
|
||||
|
||||
for (int r = 0; r < rows; ++r) {
|
||||
MODELTESTER_VERIFY(!model->hasIndex(r, columns, parent));
|
||||
MODELTESTER_VERIFY(!model->hasIndex(r, columns + 1, parent));
|
||||
for (int c = 0; c < columns; ++c) {
|
||||
MODELTESTER_VERIFY(model->hasIndex(r, c, parent));
|
||||
QModelIndex index = model->index(r, c, parent);
|
||||
// rowCount() and columnCount() said that it existed...
|
||||
if (!index.isValid())
|
||||
qCWarning(lcModelTest) << "Got invalid index at row=" << r << "col=" << c << "parent=" << parent;
|
||||
MODELTESTER_VERIFY(index.isValid());
|
||||
|
||||
// index() should always return the same index when called twice in a row
|
||||
QModelIndex modifiedIndex = model->index(r, c, parent);
|
||||
MODELTESTER_COMPARE(index, modifiedIndex);
|
||||
|
||||
{
|
||||
const QModelIndex sibling = model->sibling(r, c, topLeftChild);
|
||||
MODELTESTER_COMPARE(index, sibling);
|
||||
}
|
||||
{
|
||||
const QModelIndex sibling = topLeftChild.sibling(r, c);
|
||||
MODELTESTER_COMPARE(index, sibling);
|
||||
}
|
||||
|
||||
// Some basic checking on the index that is returned
|
||||
MODELTESTER_COMPARE(index.model(), model);
|
||||
MODELTESTER_COMPARE(index.row(), r);
|
||||
MODELTESTER_COMPARE(index.column(), c);
|
||||
|
||||
// If the next test fails here is some somewhat useful debug you play with.
|
||||
if (model->parent(index) != parent) {
|
||||
qCWarning(lcModelTest) << "Inconsistent parent() implementation detected:";
|
||||
qCWarning(lcModelTest) << " index=" << index << "exp. parent=" << parent << "act. parent=" << model->parent(index);
|
||||
qCWarning(lcModelTest) << " row=" << r << "col=" << c << "depth=" << currentDepth;
|
||||
qCWarning(lcModelTest) << " data for child" << model->data(index).toString();
|
||||
qCWarning(lcModelTest) << " data for parent" << model->data(parent).toString();
|
||||
}
|
||||
|
||||
// Check that we can get back our real parent.
|
||||
MODELTESTER_COMPARE(model->parent(index), parent);
|
||||
|
||||
QPersistentModelIndex persistentIndex = index;
|
||||
|
||||
// recursively go down the children
|
||||
if (model->hasChildren(index) && currentDepth < 10)
|
||||
checkChildren(index, ++currentDepth);
|
||||
|
||||
// make sure that after testing the children that the index doesn't change.
|
||||
QModelIndex newerIndex = model->index(r, c, parent);
|
||||
MODELTESTER_COMPARE(persistentIndex, newerIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Tests model's implementation of QAbstractItemModel::data()
|
||||
*/
|
||||
void QAbstractItemModelTesterPrivate::data()
|
||||
{
|
||||
if (!model->hasChildren())
|
||||
return;
|
||||
|
||||
MODELTESTER_VERIFY(model->index(0, 0).isValid());
|
||||
|
||||
// General Purpose roles that should return a QString
|
||||
QVariant variant;
|
||||
variant = model->data(model->index(0, 0), Qt::DisplayRole);
|
||||
if (variant.isValid())
|
||||
MODELTESTER_VERIFY(variant.canConvert<QString>());
|
||||
variant = model->data(model->index(0, 0), Qt::ToolTipRole);
|
||||
if (variant.isValid())
|
||||
MODELTESTER_VERIFY(variant.canConvert<QString>());
|
||||
variant = model->data(model->index(0, 0), Qt::StatusTipRole);
|
||||
if (variant.isValid())
|
||||
MODELTESTER_VERIFY(variant.canConvert<QString>());
|
||||
variant = model->data(model->index(0, 0), Qt::WhatsThisRole);
|
||||
if (variant.isValid())
|
||||
MODELTESTER_VERIFY(variant.canConvert<QString>());
|
||||
|
||||
// General Purpose roles that should return a QSize
|
||||
variant = model->data(model->index(0, 0), Qt::SizeHintRole);
|
||||
if (variant.isValid())
|
||||
MODELTESTER_VERIFY(variant.canConvert<QSize>());
|
||||
|
||||
// Check that the alignment is one we know about
|
||||
QVariant textAlignmentVariant = model->data(model->index(0, 0), Qt::TextAlignmentRole);
|
||||
if (textAlignmentVariant.isValid()) {
|
||||
Qt::Alignment alignment = textAlignmentVariant.value<Qt::Alignment>();
|
||||
MODELTESTER_COMPARE(alignment, (alignment & (Qt::AlignHorizontal_Mask | Qt::AlignVertical_Mask)));
|
||||
}
|
||||
|
||||
// Check that the "check state" is one we know about.
|
||||
QVariant checkStateVariant = model->data(model->index(0, 0), Qt::CheckStateRole);
|
||||
if (checkStateVariant.isValid()) {
|
||||
int state = checkStateVariant.toInt();
|
||||
MODELTESTER_VERIFY(state == Qt::Unchecked
|
||||
|| state == Qt::PartiallyChecked
|
||||
|| state == Qt::Checked);
|
||||
}
|
||||
|
||||
Q_Q(QAbstractItemModelTester);
|
||||
|
||||
if (!QTestPrivate::testDataGuiRoles(q))
|
||||
return;
|
||||
}
|
||||
|
||||
/*!
|
||||
Store what is about to be inserted to make sure it actually happens
|
||||
|
||||
\sa rowsInserted()
|
||||
*/
|
||||
void QAbstractItemModelTesterPrivate::rowsAboutToBeInserted(const QModelIndex &parent, int start, int end)
|
||||
{
|
||||
qCDebug(lcModelTest) << "rowsAboutToBeInserted"
|
||||
<< "start=" << start << "end=" << end << "parent=" << parent
|
||||
<< "parent data=" << model->data(parent).toString()
|
||||
<< "current count of parent=" << model->rowCount(parent)
|
||||
<< "last before insertion=" << model->index(start - 1, 0, parent) << model->data(model->index(start - 1, 0, parent));
|
||||
|
||||
Changing c;
|
||||
c.parent = parent;
|
||||
c.oldSize = model->rowCount(parent);
|
||||
c.last = (start - 1 >= 0) ? model->index(start - 1, 0, parent).data() : QVariant();
|
||||
c.next = (start < c.oldSize) ? model->index(start, 0, parent).data() : QVariant();
|
||||
insert.push(c);
|
||||
}
|
||||
|
||||
/*!
|
||||
Confirm that what was said was going to happen actually did
|
||||
|
||||
\sa rowsAboutToBeInserted()
|
||||
*/
|
||||
void QAbstractItemModelTesterPrivate::rowsInserted(const QModelIndex &parent, int start, int end)
|
||||
{
|
||||
qCDebug(lcModelTest) << "rowsInserted"
|
||||
<< "start=" << start << "end=" << end << "parent=" << parent
|
||||
<< "parent data=" << model->data(parent).toString()
|
||||
<< "current count of parent=" << model->rowCount(parent);
|
||||
|
||||
for (int i = start; i <= end; ++i) {
|
||||
qCDebug(lcModelTest) << " itemWasInserted:" << i
|
||||
<< model->index(i, 0, parent).data();
|
||||
}
|
||||
|
||||
|
||||
Changing c = insert.pop();
|
||||
MODELTESTER_COMPARE(parent, c.parent);
|
||||
|
||||
MODELTESTER_COMPARE(model->rowCount(parent), c.oldSize + (end - start + 1));
|
||||
if (start - 1 >= 0)
|
||||
MODELTESTER_COMPARE(model->data(model->index(start - 1, 0, c.parent)), c.last);
|
||||
|
||||
if (end + 1 < model->rowCount(c.parent)) {
|
||||
if (c.next != model->data(model->index(end + 1, 0, c.parent))) {
|
||||
qDebug() << start << end;
|
||||
for (int i = 0; i < model->rowCount(); ++i)
|
||||
qDebug() << model->index(i, 0).data().toString();
|
||||
qDebug() << c.next << model->data(model->index(end + 1, 0, c.parent));
|
||||
}
|
||||
|
||||
MODELTESTER_COMPARE(model->data(model->index(end + 1, 0, c.parent)), c.next);
|
||||
}
|
||||
}
|
||||
|
||||
void QAbstractItemModelTesterPrivate::layoutAboutToBeChanged()
|
||||
{
|
||||
for (int i = 0; i < qBound(0, model->rowCount(), 100); ++i)
|
||||
changing.append(QPersistentModelIndex(model->index(i, 0)));
|
||||
}
|
||||
|
||||
void QAbstractItemModelTesterPrivate::layoutChanged()
|
||||
{
|
||||
for (int i = 0; i < changing.count(); ++i) {
|
||||
QPersistentModelIndex p = changing[i];
|
||||
MODELTESTER_COMPARE(model->index(p.row(), p.column(), p.parent()), QModelIndex(p));
|
||||
}
|
||||
changing.clear();
|
||||
}
|
||||
|
||||
/*!
|
||||
Store what is about to be inserted to make sure it actually happens
|
||||
|
||||
\sa rowsRemoved()
|
||||
*/
|
||||
void QAbstractItemModelTesterPrivate::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end)
|
||||
{
|
||||
qCDebug(lcModelTest) << "rowsAboutToBeRemoved"
|
||||
<< "start=" << start << "end=" << end << "parent=" << parent
|
||||
<< "parent data=" << model->data(parent).toString()
|
||||
<< "current count of parent=" << model->rowCount(parent)
|
||||
<< "last before removal=" << model->index(start - 1, 0, parent) << model->data(model->index(start - 1, 0, parent));
|
||||
|
||||
Changing c;
|
||||
c.parent = parent;
|
||||
c.oldSize = model->rowCount(parent);
|
||||
c.last = model->data(model->index(start - 1, 0, parent));
|
||||
c.next = model->data(model->index(end + 1, 0, parent));
|
||||
remove.push(c);
|
||||
}
|
||||
|
||||
/*!
|
||||
Confirm that what was said was going to happen actually did
|
||||
|
||||
\sa rowsAboutToBeRemoved()
|
||||
*/
|
||||
void QAbstractItemModelTesterPrivate::rowsRemoved(const QModelIndex &parent, int start, int end)
|
||||
{
|
||||
qCDebug(lcModelTest) << "rowsRemoved"
|
||||
<< "start=" << start << "end=" << end << "parent=" << parent
|
||||
<< "parent data=" << model->data(parent).toString()
|
||||
<< "current count of parent=" << model->rowCount(parent);
|
||||
|
||||
Changing c = remove.pop();
|
||||
MODELTESTER_COMPARE(parent, c.parent);
|
||||
MODELTESTER_COMPARE(model->rowCount(parent), c.oldSize - (end - start + 1));
|
||||
MODELTESTER_COMPARE(model->data(model->index(start - 1, 0, c.parent)), c.last);
|
||||
MODELTESTER_COMPARE(model->data(model->index(start, 0, c.parent)), c.next);
|
||||
}
|
||||
|
||||
void QAbstractItemModelTesterPrivate::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
|
||||
{
|
||||
MODELTESTER_VERIFY(topLeft.isValid());
|
||||
MODELTESTER_VERIFY(bottomRight.isValid());
|
||||
QModelIndex commonParent = bottomRight.parent();
|
||||
MODELTESTER_COMPARE(topLeft.parent(), commonParent);
|
||||
MODELTESTER_VERIFY(topLeft.row() <= bottomRight.row());
|
||||
MODELTESTER_VERIFY(topLeft.column() <= bottomRight.column());
|
||||
int rowCount = model->rowCount(commonParent);
|
||||
int columnCount = model->columnCount(commonParent);
|
||||
MODELTESTER_VERIFY(bottomRight.row() < rowCount);
|
||||
MODELTESTER_VERIFY(bottomRight.column() < columnCount);
|
||||
}
|
||||
|
||||
void QAbstractItemModelTesterPrivate::headerDataChanged(Qt::Orientation orientation, int start, int end)
|
||||
{
|
||||
MODELTESTER_VERIFY(start >= 0);
|
||||
MODELTESTER_VERIFY(end >= 0);
|
||||
MODELTESTER_VERIFY(start <= end);
|
||||
int itemCount = orientation == Qt::Vertical ? model->rowCount() : model->columnCount();
|
||||
MODELTESTER_VERIFY(start < itemCount);
|
||||
MODELTESTER_VERIFY(end < itemCount);
|
||||
}
|
||||
|
||||
bool QAbstractItemModelTesterPrivate::verify(bool statement,
|
||||
const char *statementStr, const char *description,
|
||||
const char *file, int line)
|
||||
{
|
||||
static const char formatString[] = "FAIL! %s (%s) returned FALSE (%s:%d)";
|
||||
|
||||
switch (failureReportingMode) {
|
||||
case QAbstractItemModelTester::FailureReportingMode::QtTest:
|
||||
return QTest::qVerify(statement, statementStr, description, file, line);
|
||||
break;
|
||||
|
||||
case QAbstractItemModelTester::FailureReportingMode::Warning:
|
||||
if (!statement)
|
||||
qCWarning(lcModelTest, formatString, statementStr, description, file, line);
|
||||
break;
|
||||
|
||||
case QAbstractItemModelTester::FailureReportingMode::Fatal:
|
||||
if (!statement)
|
||||
qFatal(formatString, statementStr, description, file, line);
|
||||
break;
|
||||
}
|
||||
|
||||
return statement;
|
||||
}
|
||||
|
||||
|
||||
template<typename T1, typename T2>
|
||||
bool QAbstractItemModelTesterPrivate::compare(const T1 &t1, const T2 &t2,
|
||||
const char *actual, const char *expected,
|
||||
const char *file, int line)
|
||||
{
|
||||
const bool result = static_cast<bool>(t1 == t2);
|
||||
|
||||
static const char formatString[] = "FAIL! Compared values are not the same:\n Actual (%s) %s\n Expected (%s) %s\n (%s:%d)";
|
||||
|
||||
switch (failureReportingMode) {
|
||||
case QAbstractItemModelTester::FailureReportingMode::QtTest:
|
||||
return QTest::qCompare(t1, t2, actual, expected, file, line);
|
||||
break;
|
||||
|
||||
case QAbstractItemModelTester::FailureReportingMode::Warning:
|
||||
if (!result)
|
||||
qCWarning(lcModelTest, formatString, actual, QTest::toString(t1), expected, QTest::toString(t2), file, line);
|
||||
break;
|
||||
|
||||
case QAbstractItemModelTester::FailureReportingMode::Fatal:
|
||||
if (!result)
|
||||
qFatal(formatString, actual, QTest::toString(t1), expected, QTest::toString(t2), file, line);
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
QT_END_NAMESPACE
|
131
src/testlib/qabstractitemmodeltester.h
Normal file
131
src/testlib/qabstractitemmodeltester.h
Normal file
@ -0,0 +1,131 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of the test suite of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef QABSTRACTITEMMODELTESTER_H
|
||||
#define QABSTRACTITEMMODELTESTER_H
|
||||
|
||||
#include <QtCore/QObject>
|
||||
#include <QtTest/qtest_global.h>
|
||||
|
||||
#ifdef QT_GUI_LIB
|
||||
#include <QtGui/QFont>
|
||||
#include <QtGui/QColor>
|
||||
#include <QtGui/QBrush>
|
||||
#include <QtGui/QPixmap>
|
||||
#include <QtGui/QImage>
|
||||
#include <QtGui/QIcon>
|
||||
#endif
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
class QAbstractItemModel;
|
||||
class QAbstractItemModelTester;
|
||||
class QAbstractItemModelTesterPrivate;
|
||||
|
||||
namespace QTestPrivate {
|
||||
inline bool testDataGuiRoles(QAbstractItemModelTester *tester);
|
||||
}
|
||||
|
||||
class Q_TESTLIB_EXPORT QAbstractItemModelTester : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DECLARE_PRIVATE(QAbstractItemModelTester)
|
||||
|
||||
public:
|
||||
enum class FailureReportingMode {
|
||||
QtTest,
|
||||
Warning,
|
||||
Fatal
|
||||
};
|
||||
|
||||
QAbstractItemModelTester(QAbstractItemModel *model, QObject *parent = nullptr);
|
||||
QAbstractItemModelTester(QAbstractItemModel *model, FailureReportingMode mode, QObject *parent = nullptr);
|
||||
|
||||
QAbstractItemModel *model() const;
|
||||
FailureReportingMode failureReportingMode() const;
|
||||
|
||||
private:
|
||||
friend inline bool QTestPrivate::testDataGuiRoles(QAbstractItemModelTester *tester);
|
||||
bool verify(bool statement, const char *statementStr, const char *description, const char *file, int line);
|
||||
};
|
||||
|
||||
namespace QTestPrivate {
|
||||
inline bool testDataGuiRoles(QAbstractItemModelTester *tester)
|
||||
{
|
||||
#ifdef QT_GUI_LIB
|
||||
|
||||
#define MODELTESTER_VERIFY(statement) \
|
||||
do { \
|
||||
if (!tester->verify(static_cast<bool>(statement), #statement, "", __FILE__, __LINE__)) \
|
||||
return false; \
|
||||
} while (false)
|
||||
|
||||
const auto model = tester->model();
|
||||
Q_ASSERT(model);
|
||||
|
||||
if (!model->hasChildren())
|
||||
return true;
|
||||
|
||||
QVariant variant;
|
||||
|
||||
variant = model->data(model->index(0, 0), Qt::DecorationRole);
|
||||
if (variant.isValid()) {
|
||||
MODELTESTER_VERIFY(variant.canConvert<QPixmap>()
|
||||
|| variant.canConvert<QImage>()
|
||||
|| variant.canConvert<QIcon>()
|
||||
|| variant.canConvert<QColor>()
|
||||
|| variant.canConvert<QBrush>());
|
||||
}
|
||||
|
||||
// General Purpose roles that should return a QFont
|
||||
variant = model->data(model->index(0, 0), Qt::FontRole);
|
||||
if (variant.isValid())
|
||||
MODELTESTER_VERIFY(variant.canConvert<QFont>());
|
||||
|
||||
// General Purpose roles that should return a QColor or a QBrush
|
||||
variant = model->data(model->index(0, 0), Qt::BackgroundColorRole);
|
||||
if (variant.isValid())
|
||||
MODELTESTER_VERIFY(variant.canConvert<QColor>() || variant.canConvert<QBrush>());
|
||||
|
||||
variant = model->data(model->index(0, 0), Qt::TextColorRole);
|
||||
if (variant.isValid())
|
||||
MODELTESTER_VERIFY(variant.canConvert<QColor>() || variant.canConvert<QBrush>());
|
||||
|
||||
#undef MODELTESTER_VERIFY
|
||||
|
||||
#else
|
||||
Q_UNUSED(tester);
|
||||
#endif // QT_GUI_LIB
|
||||
|
||||
return true;
|
||||
}
|
||||
} // namespaceQTestPrivate
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
#endif // QABSTRACTITEMMODELTESTER_H
|
@ -11,7 +11,9 @@ unix:!embedded:QMAKE_PKGCONFIG_DESCRIPTION = Qt \
|
||||
|
||||
QMAKE_DOCS = $$PWD/doc/qttestlib.qdocconf
|
||||
|
||||
HEADERS = qbenchmark.h \
|
||||
HEADERS = \
|
||||
qabstractitemmodeltester.h \
|
||||
qbenchmark.h \
|
||||
qbenchmark_p.h \
|
||||
qbenchmarkmeasurement_p.h \
|
||||
qbenchmarktimemeasurers_p.h \
|
||||
@ -40,7 +42,9 @@ HEADERS = qbenchmark.h \
|
||||
qtestblacklist_p.h \
|
||||
qtesthelpers_p.h
|
||||
|
||||
SOURCES = qtestcase.cpp \
|
||||
SOURCES = \
|
||||
qabstractitemmodeltester.cpp \
|
||||
qtestcase.cpp \
|
||||
qtestlog.cpp \
|
||||
qtesttable.cpp \
|
||||
qtestdata.cpp \
|
||||
|
Loading…
Reference in New Issue
Block a user