diff options
Diffstat (limited to 'src/tests')
| -rw-r--r-- | src/tests/CMakeLists.txt | 10 | ||||
| -rw-r--r-- | src/tests/kfileitemmodelbenchmark.cpp | 328 | ||||
| -rw-r--r-- | src/tests/kfileitemmodeltest.cpp | 93 |
3 files changed, 400 insertions, 31 deletions
diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index 6575eecde..543261eac 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -48,6 +48,16 @@ set(kfileitemmodeltest_SRCS kde4_add_unit_test(kfileitemmodeltest TEST ${kfileitemmodeltest_SRCS}) target_link_libraries(kfileitemmodeltest dolphinprivate ${KDE4_KIO_LIBS} ${QT_QTTEST_LIBRARY}) +# KFileItemModelBenchmark +set(kfileitemmodelbenchmark_SRCS + kfileitemmodelbenchmark.cpp + testdir.cpp + ../kitemviews/kfileitemmodel.cpp + ../kitemviews/kitemmodelbase.cpp +) +kde4_add_executable(kfileitemmodelbenchmark TEST ${kfileitemmodelbenchmark_SRCS}) +target_link_libraries(kfileitemmodelbenchmark dolphinprivate ${KDE4_KIO_LIBS} ${QT_QTTEST_LIBRARY}) + # KItemListKeyboardSearchManagerTest set(kitemlistkeyboardsearchmanagertest_SRCS kitemlistkeyboardsearchmanagertest.cpp diff --git a/src/tests/kfileitemmodelbenchmark.cpp b/src/tests/kfileitemmodelbenchmark.cpp new file mode 100644 index 000000000..b1e777c06 --- /dev/null +++ b/src/tests/kfileitemmodelbenchmark.cpp @@ -0,0 +1,328 @@ +/*************************************************************************** + * Copyright (C) 2011 by Peter Penz <[email protected]> * + * Copyright (C) 2013 by Frank Reininghaus <[email protected]> * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * + ***************************************************************************/ + +#include <qtest_kde.h> + +#include "kitemviews/kfileitemmodel.h" +#include "kitemviews/private/kfileitemmodelsortalgorithm.h" + +#include "testdir.h" + +#include <KRandomSequence> + +void myMessageOutput(QtMsgType type, const char* msg) +{ + switch (type) { + case QtDebugMsg: + break; + case QtWarningMsg: + break; + case QtCriticalMsg: + fprintf(stderr, "Critical: %s\n", msg); + break; + case QtFatalMsg: + fprintf(stderr, "Fatal: %s\n", msg); + abort(); + default: + break; + } +} + +namespace { + const int DefaultTimeout = 5000; +}; + +Q_DECLARE_METATYPE(KFileItemList) +Q_DECLARE_METATYPE(KItemRangeList) + +class KFileItemModelBenchmark : public QObject +{ + Q_OBJECT + +public: + KFileItemModelBenchmark(); + +private slots: + void insertAndRemoveManyItems_data(); + void insertAndRemoveManyItems(); + void insertManyChildItems(); + +private: + static KFileItemList createFileItemList(const QStringList& fileNames, const QString& urlPrefix = QLatin1String("file:///")); +}; + +KFileItemModelBenchmark::KFileItemModelBenchmark() +{ +} + +void KFileItemModelBenchmark::insertAndRemoveManyItems_data() +{ + QTest::addColumn<KFileItemList>("initialItems"); + QTest::addColumn<KFileItemList>("newItems"); + QTest::addColumn<KFileItemList>("removedItems"); + QTest::addColumn<KFileItemList>("expectedFinalItems"); + QTest::addColumn<KItemRangeList>("expectedItemsInserted"); + QTest::addColumn<KItemRangeList>("expectedItemsRemoved"); + + QList<int> sizes; + sizes << 1000 << 4000 << 16000 << 64000 << 256000; + //sizes << 50000 << 100000 << 150000 << 200000 << 250000; + + foreach (int n, sizes) { + QStringList allStrings; + for (int i = 0; i < n; ++i) { + allStrings << QString::number(i); + } + + // We want to keep the sorting overhead in the benchmark low. + // Therefore, we do not use natural sorting. However, this + // means that our list is currently not sorted. + allStrings.sort(); + + KFileItemList all = createFileItemList(allStrings); + + KFileItemList firstHalf, secondHalf, even, odd; + for (int i = 0; i < n; ++i) { + if (i < n / 2) { + firstHalf << all.at(i); + } else { + secondHalf << all.at(i); + } + + if (i % 2 == 0) { + even << all.at(i); + } else { + odd << all.at(i); + } + } + + KItemRangeList itemRangeListFirstHalf; + itemRangeListFirstHalf << KItemRange(0, firstHalf.count()); + + KItemRangeList itemRangeListSecondHalf; + itemRangeListSecondHalf << KItemRange(firstHalf.count(), secondHalf.count()); + + KItemRangeList itemRangeListOddInserted, itemRangeListOddRemoved; + for (int i = 0; i < odd.count(); ++i) { + // Note that the index in the KItemRange is the index of + // the model *before* the items have been inserted. + itemRangeListOddInserted << KItemRange(i + 1, 1); + itemRangeListOddRemoved << KItemRange(2 * i + 1, 1); + } + + const int bufferSize = 128; + char buffer[bufferSize]; + + snprintf(buffer, bufferSize, "all--n=%i", n); + QTest::newRow(buffer) << all << KFileItemList() << KFileItemList() << all << KItemRangeList() << KItemRangeList(); + + snprintf(buffer, bufferSize, "1st half + 2nd half--n=%i", n); + QTest::newRow(buffer) << firstHalf << secondHalf << KFileItemList() << all << itemRangeListSecondHalf << KItemRangeList(); + + snprintf(buffer, bufferSize, "2nd half + 1st half--n=%i", n); + QTest::newRow(buffer) << secondHalf << firstHalf << KFileItemList() << all << itemRangeListFirstHalf << KItemRangeList(); + + snprintf(buffer, bufferSize, "even + odd--n=%i", n); + QTest::newRow(buffer) << even << odd << KFileItemList() << all << itemRangeListOddInserted << KItemRangeList(); + + snprintf(buffer, bufferSize, "all - 2nd half--n=%i", n); + QTest::newRow(buffer) << all << KFileItemList() << secondHalf << firstHalf << KItemRangeList() << itemRangeListSecondHalf; + + snprintf(buffer, bufferSize, "all - 1st half--n=%i", n); + QTest::newRow(buffer) << all << KFileItemList() << firstHalf << secondHalf << KItemRangeList() << itemRangeListFirstHalf; + + snprintf(buffer, bufferSize, "all - odd--n=%i", n); + QTest::newRow(buffer) << all << KFileItemList() << odd << even << KItemRangeList() << itemRangeListOddRemoved; + } +} + +void KFileItemModelBenchmark::insertAndRemoveManyItems() +{ + QFETCH(KFileItemList, initialItems); + QFETCH(KFileItemList, newItems); + QFETCH(KFileItemList, removedItems); + QFETCH(KFileItemList, expectedFinalItems); + QFETCH(KItemRangeList, expectedItemsInserted); + QFETCH(KItemRangeList, expectedItemsRemoved); + + KFileItemModel model; + + // Avoid overhead caused by natural sorting + // and determining the isDir/isLink roles. + model.m_naturalSorting = false; + model.setRoles(QSet<QByteArray>() << "text"); + + QSignalSpy spyItemsInserted(&model, SIGNAL(itemsInserted(KItemRangeList))); + QSignalSpy spyItemsRemoved(&model, SIGNAL(itemsRemoved(KItemRangeList))); + + QBENCHMARK { + model.slotClear(); + model.slotNewItems(initialItems); + model.slotCompleted(); + QCOMPARE(model.count(), initialItems.count()); + + if (!newItems.isEmpty()) { + model.slotNewItems(newItems); + model.slotCompleted(); + } + QCOMPARE(model.count(), initialItems.count() + newItems.count()); + + if (!removedItems.isEmpty()) { + model.removeItems(removedItems); + } + QCOMPARE(model.count(), initialItems.count() + newItems.count() - removedItems.count()); + } + + QVERIFY(model.isConsistent()); + + for (int i = 0; i < model.count(); ++i) { + QCOMPARE(model.fileItem(i), expectedFinalItems.at(i)); + } + + if (!expectedItemsInserted.empty()) { + QVERIFY(!spyItemsInserted.empty()); + const KItemRangeList actualItemsInserted = spyItemsInserted.last().first().value<KItemRangeList>(); + QCOMPARE(actualItemsInserted, expectedItemsInserted); + } + + if (!expectedItemsRemoved.empty()) { + QVERIFY(!spyItemsRemoved.empty()); + const KItemRangeList actualItemsRemoved = spyItemsRemoved.last().first().value<KItemRangeList>(); + QCOMPARE(actualItemsRemoved, expectedItemsRemoved); + } +} + +void KFileItemModelBenchmark::insertManyChildItems() +{ + qInstallMsgHandler(myMessageOutput); + + KFileItemModel model; + + // Avoid overhead caused by natural sorting. + model.m_naturalSorting = false; + + QSet<QByteArray> modelRoles = model.roles(); + modelRoles << "isExpanded" << "isExpandable" << "expandedParentsCount"; + model.setRoles(modelRoles); + model.setSortDirectoriesFirst(false); + + // Create a test folder with a 3-level tree structure of folders. + TestDir testFolder; + int numberOfFolders = 0; + + QStringList subFolderNames; + subFolderNames << "a/" << "b/" << "c/" << "d/"; + + foreach (const QString& s1, subFolderNames) { + ++numberOfFolders; + foreach (const QString& s2, subFolderNames) { + ++numberOfFolders; + foreach (const QString& s3, subFolderNames) { + testFolder.createDir("level-1-" + s1 + "level-2-" + s2 + "level-3-" + s3); + ++numberOfFolders; + } + } + } + + // Open the folder in the model and expand all subfolders. + model.loadDirectory(testFolder.url()); + QVERIFY(QTest::kWaitForSignal(&model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); + + int index = 0; + while (index < model.count()) { + if (model.isExpandable(index)) { + model.setExpanded(index, true); + + if (!model.data(index).value("text").toString().startsWith("level-3")) { + // New subfolders will appear unless we are on the final level already. + QVERIFY(QTest::kWaitForSignal(&model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); + } + + QVERIFY(model.isExpanded(index)); + } + ++index; + } + + QCOMPARE(model.count(), numberOfFolders); + + // Create a list of many file items, which will be added to each of the + // "level 1", "level 2", and "level 3" folders. + const int filesPerDirectory = 500; + QStringList allStrings; + for (int i = 0; i < filesPerDirectory; ++i) { + allStrings << QString::number(i); + } + allStrings.sort(); + + KFileItemList newItems; + + // Also keep track of all expected items, including the existing + // folders, to verify the final state of the model. + KFileItemList allExpectedItems; + + for (int i = 0; i < model.count(); ++i) { + const KFileItem folderItem = model.fileItem(i); + allExpectedItems << folderItem; + + const KUrl folderUrl = folderItem.url(); + KFileItemList itemsInFolder = createFileItemList(allStrings, folderUrl.url(KUrl::AddTrailingSlash)); + + newItems.append(itemsInFolder); + allExpectedItems.append(itemsInFolder); + } + + // Bring the items into random order. + KRandomSequence randomSequence(0); + randomSequence.randomize(newItems); + + // Measure how long it takes to insert and then remove all files. + QBENCHMARK { + model.slotNewItems(newItems); + model.slotCompleted(); + + QCOMPARE(model.count(), allExpectedItems.count()); + QVERIFY(model.isConsistent()); + for (int i = 0; i < model.count(); ++i) { + QCOMPARE(model.fileItem(i), allExpectedItems.at(i)); + } + + model.slotItemsDeleted(newItems); + QCOMPARE(model.count(), numberOfFolders); + QVERIFY(model.isConsistent()); + } +} + +KFileItemList KFileItemModelBenchmark::createFileItemList(const QStringList& fileNames, const QString& prefix) +{ + // Suppress 'file does not exist anymore' messages from KFileItemPrivate::init(). + qInstallMsgHandler(myMessageOutput); + + KFileItemList result; + foreach (const QString& name, fileNames) { + const KUrl url(prefix + name); + const KFileItem item(url, QString(), KFileItem::Unknown); + result << item; + } + return result; +} + +QTEST_KDEMAIN(KFileItemModelBenchmark, NoGUI) + +#include "kfileitemmodelbenchmark.moc" diff --git a/src/tests/kfileitemmodeltest.cpp b/src/tests/kfileitemmodeltest.cpp index 719459c6f..c9f8a3468 100644 --- a/src/tests/kfileitemmodeltest.cpp +++ b/src/tests/kfileitemmodeltest.cpp @@ -21,6 +21,8 @@ #include <qtest_kde.h> #include <KDirLister> +#include <kio/job.h> + #include "kitemviews/kfileitemmodel.h" #include "kitemviews/private/kfileitemmodeldirlister.h" #include "testdir.h" @@ -71,6 +73,7 @@ private slots: void testItemRangeConsistencyWhenInsertingItems(); void testExpandItems(); void testExpandParentItems(); + void testMakeExpandedItemHidden(); void testSorting(); void testIndexForKeyboardSearch(); void testNameFilter(); @@ -78,7 +81,6 @@ private slots: void testRemoveHiddenItems(); private: - bool isModelConsistent() const; QStringList itemsInModel() const; private: @@ -153,7 +155,7 @@ void KFileItemModelTest::testNewItems() QCOMPARE(m_model->count(), 3); - QVERIFY(isModelConsistent()); + QVERIFY(m_model->isConsistent()); } void KFileItemModelTest::testRemoveItems() @@ -163,13 +165,13 @@ void KFileItemModelTest::testRemoveItems() m_model->loadDirectory(m_testDir->url()); QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); QCOMPARE(m_model->count(), 2); - QVERIFY(isModelConsistent()); + QVERIFY(m_model->isConsistent()); m_testDir->removeFile("a.txt"); m_model->m_dirLister->updateDirectory(m_testDir->url()); QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsRemoved(KItemRangeList)), DefaultTimeout)); QCOMPARE(m_model->count(), 1); - QVERIFY(isModelConsistent()); + QVERIFY(m_model->isConsistent()); } void KFileItemModelTest::testDirLoadingCompleted() @@ -212,7 +214,7 @@ void KFileItemModelTest::testDirLoadingCompleted() QCOMPARE(itemsRemovedSpy.count(), 2); QCOMPARE(m_model->count(), 4); - QVERIFY(isModelConsistent()); + QVERIFY(m_model->isConsistent()); } void KFileItemModelTest::testSetData() @@ -233,7 +235,7 @@ void KFileItemModelTest::testSetData() values = m_model->data(0); QCOMPARE(values.value("customRole1").toString(), QString("Test1")); QCOMPARE(values.value("customRole2").toString(), QString("Test2")); - QVERIFY(isModelConsistent()); + QVERIFY(m_model->isConsistent()); } void KFileItemModelTest::testSetDataWithModifiedSortRole_data() @@ -314,7 +316,7 @@ void KFileItemModelTest::testSetDataWithModifiedSortRole() QCOMPARE(m_model->data(0).value("rating").toInt(), ratingIndex0); QCOMPARE(m_model->data(1).value("rating").toInt(), ratingIndex1); QCOMPARE(m_model->data(2).value("rating").toInt(), ratingIndex2); - QVERIFY(isModelConsistent()); + QVERIFY(m_model->isConsistent()); } void KFileItemModelTest::testModelConsistencyWhenInsertingItems() @@ -353,7 +355,7 @@ void KFileItemModelTest::testModelConsistencyWhenInsertingItems() QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); } - QVERIFY(isModelConsistent()); + QVERIFY(m_model->isConsistent()); } QCOMPARE(m_model->count(), 201); @@ -487,6 +489,7 @@ void KFileItemModelTest::testExpandItems() QCOMPARE(spyRemoved.count(), 1); itemRangeList = spyRemoved.takeFirst().at(0).value<KItemRangeList>(); QCOMPARE(itemRangeList, KItemRangeList() << KItemRange(1, 4)); // 4 items removed + QVERIFY(m_model->isConsistent()); // Clear the model, reload the folder and try to restore the expanded folders. m_model->clear(); @@ -503,6 +506,7 @@ void KFileItemModelTest::testExpandItems() QVERIFY(m_model->isExpanded(3)); QVERIFY(!m_model->isExpanded(4)); QCOMPARE(m_model->expandedDirectories(), allFolders); + QVERIFY(m_model->isConsistent()); // Move to a sub folder, then call restoreExpandedFolders() *before* going back. // This is how DolphinView restores the expanded folders when navigating in history. @@ -565,6 +569,56 @@ void KFileItemModelTest::testExpandParentItems() QVERIFY(m_model->isExpanded(2)); QVERIFY(m_model->isExpanded(3)); QVERIFY(!m_model->isExpanded(4)); + QVERIFY(m_model->isConsistent()); +} + +/** + * Renaming an expanded folder by prepending its name with a dot makes it + * hidden. Verify that this does not cause an inconsistent model state and + * a crash later on, see https://bugs.kde.org/show_bug.cgi?id=311947 + */ +void KFileItemModelTest::testMakeExpandedItemHidden() +{ + QSet<QByteArray> modelRoles = m_model->roles(); + modelRoles << "isExpanded" << "isExpandable" << "expandedParentsCount"; + m_model->setRoles(modelRoles); + + QStringList files; + m_testDir->createFile("1a/2a/3a"); + m_testDir->createFile("1a/2a/3b"); + m_testDir->createFile("1a/2b"); + m_testDir->createFile("1b"); + + m_model->loadDirectory(m_testDir->url()); + QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); + + // So far, the model contains only "1a/" and "1b". + QCOMPARE(m_model->count(), 2); + m_model->setExpanded(0, true); + QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); + + // Now "1a/2a" and "1a/2b" have appeared. + QCOMPARE(m_model->count(), 4); + m_model->setExpanded(1, true); + QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); + QCOMPARE(m_model->count(), 6); + + // Rename "1a/2" and make it hidden. + const QString oldPath = m_model->fileItem(0).url().path() + "/2a"; + const QString newPath = m_model->fileItem(0).url().path() + "/.2a"; + + KIO::SimpleJob* job = KIO::rename(oldPath, newPath, KIO::HideProgressInfo); + bool ok = job->exec(); + QVERIFY(ok); + QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsRemoved(KItemRangeList)), DefaultTimeout)); + + // "1a/2" and its subfolders have disappeared now. + QVERIFY(m_model->isConsistent()); + QCOMPARE(m_model->count(), 3); + + m_model->setExpanded(0, false); + QCOMPARE(m_model->count(), 2); + } void KFileItemModelTest::testSorting() @@ -849,29 +903,6 @@ void KFileItemModelTest::testRemoveHiddenItems() m_model->setShowHiddenFiles(false); } -bool KFileItemModelTest::isModelConsistent() const -{ - if (m_model->m_items.count() != m_model->m_itemData.count()) { - return false; - } - - for (int i = 0; i < m_model->count(); ++i) { - const KFileItem item = m_model->fileItem(i); - if (item.isNull()) { - qWarning() << "Item" << i << "is null"; - return false; - } - - const int itemIndex = m_model->index(item); - if (itemIndex != i) { - qWarning() << "Item" << i << "has a wrong index:" << itemIndex; - return false; - } - } - - return true; -} - QStringList KFileItemModelTest::itemsInModel() const { QStringList items; |
