diff --git a/autotests/kdirmodeltest.cpp b/autotests/kdirmodeltest.cpp index a3e5fdaf..1123be74 100644 --- a/autotests/kdirmodeltest.cpp +++ b/autotests/kdirmodeltest.cpp @@ -1,1518 +1,1519 @@ /* This file is part of the KDE project Copyright 2006 - 2007 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kdirmodeltest.h" #include #include #include #include #include #include //TODO #include "../../kdeui/tests/proxymodeltestsuite/modelspy.h" #include #ifdef Q_OS_UNIX #include #endif #include #include #include #include #include "kiotesthelper.h" #include QTEST_MAIN(KDirModelTest) #ifndef USE_QTESTEVENTLOOP #define exitLoop quit #endif #define checkedConnect(a,b,c,d) QVERIFY(QObject::connect(a,b,c,d)) #ifndef Q_OS_WIN #define SPECIALCHARS " special chars%:.pdf" #else #define SPECIALCHARS " special chars%.pdf" #endif Q_DECLARE_METATYPE(KFileItemList) void KDirModelTest::initTestCase() { qputenv("LC_ALL", "en_US.UTF-8"); // To avoid a runtime dependency on klauncher qputenv("KDE_FORK_SLAVES", "yes"); qRegisterMetaType("KFileItemList"); m_dirModelForExpand = nullptr; m_dirModel = nullptr; s_referenceTimeStamp = QDateTime::currentDateTime().addSecs(-30); // 30 seconds ago m_tempDir = nullptr; m_topLevelFileNames << QStringLiteral("toplevelfile_1") << QStringLiteral("toplevelfile_2") << QStringLiteral("toplevelfile_3") << SPECIALCHARS ; recreateTestData(); fillModel(false); } void KDirModelTest::recreateTestData() { if (m_tempDir) { qDebug() << "Deleting old tempdir" << m_tempDir->path(); delete m_tempDir; qApp->processEvents(); // process inotify events so they don't pollute us later on } m_tempDir = new QTemporaryDir; qDebug() << "new tmp dir:" << m_tempDir->path(); // Create test data: /* * PATH/toplevelfile_1 * PATH/toplevelfile_2 * PATH/toplevelfile_3 * PATH/special chars%:.pdf * PATH/.hiddenfile * PATH/.hiddenfile2 * PATH/subdir * PATH/subdir/testfile * PATH/subdir/testsymlink * PATH/subdir/subsubdir * PATH/subdir/subsubdir/testfile */ const QString path = m_tempDir->path() + '/'; foreach (const QString &f, m_topLevelFileNames) { createTestFile(path + f); } createTestFile(path + ".hiddenfile"); createTestFile(path + ".hiddenfile2"); createTestDirectory(path + "subdir"); createTestDirectory(path + "subdir/subsubdir", NoSymlink); m_dirIndex = QModelIndex(); m_fileIndex = QModelIndex(); m_secondFileIndex = QModelIndex(); } void KDirModelTest::cleanupTestCase() { delete m_tempDir; m_tempDir = nullptr; delete m_dirModel; m_dirModel = nullptr; } void KDirModelTest::fillModel(bool reload, bool expectAllIndexes) { if (!m_dirModel) { m_dirModel = new KDirModel; } m_dirModel->dirLister()->setAutoErrorHandlingEnabled(false, nullptr); const QString path = m_tempDir->path() + '/'; KDirLister *dirLister = m_dirModel->dirLister(); qDebug() << "Calling openUrl"; dirLister->openUrl(QUrl::fromLocalFile(path), reload ? KDirLister::Reload : KDirLister::NoFlags); checkedConnect(dirLister, SIGNAL(completed()), this, SLOT(slotListingCompleted())); qDebug() << "enterLoop, waiting for completed()"; enterLoop(); if (expectAllIndexes) { collectKnownIndexes(); } disconnect(dirLister, SIGNAL(completed()), this, SLOT(slotListingCompleted())); } // Called after test function void KDirModelTest::cleanup() { if (m_dirModel) { disconnect(m_dirModel, nullptr, &m_eventLoop, nullptr); disconnect(m_dirModel->dirLister(), nullptr, this, nullptr); m_dirModel->dirLister()->setNameFilter(QString()); m_dirModel->dirLister()->setMimeFilter(QStringList()); m_dirModel->dirLister()->emitChanges(); } } void KDirModelTest::collectKnownIndexes() { m_dirIndex = QModelIndex(); m_fileIndex = QModelIndex(); m_secondFileIndex = QModelIndex(); // Create the indexes once and for all // The trouble is that the order of listing is undefined, one can get 1/2/3/subdir or subdir/3/2/1 for instance. for (int row = 0; row < m_topLevelFileNames.count() + 1 /*subdir*/; ++row) { QModelIndex idx = m_dirModel->index(row, 0, QModelIndex()); QVERIFY(idx.isValid()); KFileItem item = m_dirModel->itemForIndex(idx); qDebug() << item.url() << "isDir=" << item.isDir(); QString fileName = item.url().fileName(); if (item.isDir()) { m_dirIndex = idx; } else if (fileName == QLatin1String("toplevelfile_1")) { m_fileIndex = idx; } else if (fileName == QLatin1String("toplevelfile_2")) { m_secondFileIndex = idx; } else if (fileName.startsWith(QLatin1String(" special"))) { m_specialFileIndex = idx; } } QVERIFY(m_dirIndex.isValid()); QVERIFY(m_fileIndex.isValid()); QVERIFY(m_secondFileIndex.isValid()); QVERIFY(m_specialFileIndex.isValid()); // Now list subdir/ QVERIFY(m_dirModel->canFetchMore(m_dirIndex)); m_dirModel->fetchMore(m_dirIndex); qDebug() << "Listing subdir/"; enterLoop(); // Index of a file inside a directory (subdir/testfile) QModelIndex subdirIndex; m_fileInDirIndex = QModelIndex(); for (int row = 0; row < 3; ++row) { QModelIndex idx = m_dirModel->index(row, 0, m_dirIndex); if (m_dirModel->itemForIndex(idx).isDir()) { subdirIndex = idx; } else if (m_dirModel->itemForIndex(idx).name() == QLatin1String("testfile")) { m_fileInDirIndex = idx; } } // List subdir/subsubdir QVERIFY(m_dirModel->canFetchMore(subdirIndex)); qDebug() << "Listing subdir/subsubdir"; m_dirModel->fetchMore(subdirIndex); enterLoop(); // Index of ... well, subdir/subsubdir/testfile m_fileInSubdirIndex = m_dirModel->index(0, 0, subdirIndex); } void KDirModelTest::enterLoop() { #ifdef USE_QTESTEVENTLOOP m_eventLoop.enterLoop(10 /*seconds max*/); QVERIFY(!m_eventLoop.timeout()); #else m_eventLoop.exec(); #endif } void KDirModelTest::slotListingCompleted() { qDebug(); #ifdef USE_QTESTEVENTLOOP m_eventLoop.exitLoop(); #else m_eventLoop.quit(); #endif } void KDirModelTest::testRowCount() { const int topLevelRowCount = m_dirModel->rowCount(); QCOMPARE(topLevelRowCount, m_topLevelFileNames.count() + 1 /*subdir*/); const int subdirRowCount = m_dirModel->rowCount(m_dirIndex); QCOMPARE(subdirRowCount, 3); QVERIFY(m_fileIndex.isValid()); const int fileRowCount = m_dirModel->rowCount(m_fileIndex); // #176555 QCOMPARE(fileRowCount, 0); } void KDirModelTest::testIndex() { QVERIFY(m_dirModel->hasChildren()); // Index of the first file QVERIFY(m_fileIndex.isValid()); QCOMPARE(m_fileIndex.model(), static_cast(m_dirModel)); //QCOMPARE(m_fileIndex.row(), 0); QCOMPARE(m_fileIndex.column(), 0); QVERIFY(!m_fileIndex.parent().isValid()); QVERIFY(!m_dirModel->hasChildren(m_fileIndex)); // Index of a directory QVERIFY(m_dirIndex.isValid()); QCOMPARE(m_dirIndex.model(), static_cast(m_dirModel)); //QCOMPARE(m_dirIndex.row(), 3); // ordering isn't guaranteed QCOMPARE(m_dirIndex.column(), 0); QVERIFY(!m_dirIndex.parent().isValid()); QVERIFY(m_dirModel->hasChildren(m_dirIndex)); // Index of a file inside a directory (subdir/testfile) QVERIFY(m_fileInDirIndex.isValid()); QCOMPARE(m_fileInDirIndex.model(), static_cast(m_dirModel)); //QCOMPARE(m_fileInDirIndex.row(), 0); // ordering isn't guaranteed QCOMPARE(m_fileInDirIndex.column(), 0); QVERIFY(m_fileInDirIndex.parent() == m_dirIndex); QVERIFY(!m_dirModel->hasChildren(m_fileInDirIndex)); // Index of subdir/subsubdir/testfile QVERIFY(m_fileInSubdirIndex.isValid()); QCOMPARE(m_fileInSubdirIndex.model(), static_cast(m_dirModel)); QCOMPARE(m_fileInSubdirIndex.row(), 0); // we can check it because it's the only file there QCOMPARE(m_fileInSubdirIndex.column(), 0); QVERIFY(m_fileInSubdirIndex.parent().parent() == m_dirIndex); QVERIFY(!m_dirModel->hasChildren(m_fileInSubdirIndex)); // Test sibling() by going from subdir/testfile to subdir/subsubdir const QModelIndex subsubdirIndex = m_fileInSubdirIndex.parent(); QVERIFY(subsubdirIndex.isValid()); QModelIndex sibling1 = m_dirModel->sibling(subsubdirIndex.row(), 0, m_fileInDirIndex); QVERIFY(sibling1.isValid()); QVERIFY(sibling1 == subsubdirIndex); // Invalid sibling call QVERIFY(!m_dirModel->sibling(1, 0, m_fileInSubdirIndex).isValid()); // Test index() with a valid parent (dir). QModelIndex index2 = m_dirModel->index(m_fileInSubdirIndex.row(), m_fileInSubdirIndex.column(), subsubdirIndex); QVERIFY(index2.isValid()); QVERIFY(index2 == m_fileInSubdirIndex); // Test index() with a non-parent (file). QModelIndex index3 = m_dirModel->index(m_fileInSubdirIndex.row(), m_fileInSubdirIndex.column(), m_fileIndex); QVERIFY(!index3.isValid()); } void KDirModelTest::testNames() { QString fileName = m_dirModel->data(m_fileIndex, Qt::DisplayRole).toString(); QCOMPARE(fileName, QString("toplevelfile_1")); QString specialFileName = m_dirModel->data(m_specialFileIndex, Qt::DisplayRole).toString(); QCOMPARE(specialFileName, QString(SPECIALCHARS)); QString dirName = m_dirModel->data(m_dirIndex, Qt::DisplayRole).toString(); QCOMPARE(dirName, QString("subdir")); QString fileInDirName = m_dirModel->data(m_fileInDirIndex, Qt::DisplayRole).toString(); QCOMPARE(fileInDirName, QString("testfile")); QString fileInSubdirName = m_dirModel->data(m_fileInSubdirIndex, Qt::DisplayRole).toString(); QCOMPARE(fileInSubdirName, QString("testfile")); } void KDirModelTest::testItemForIndex() { // root item KFileItem rootItem = m_dirModel->itemForIndex(QModelIndex()); QVERIFY(!rootItem.isNull()); QCOMPARE(rootItem.name(), QString(".")); KFileItem fileItem = m_dirModel->itemForIndex(m_fileIndex); QVERIFY(!fileItem.isNull()); QCOMPARE(fileItem.name(), QString("toplevelfile_1")); QVERIFY(!fileItem.isDir()); QCOMPARE(fileItem.url().toLocalFile(), QString(m_tempDir->path() + "/toplevelfile_1")); KFileItem dirItem = m_dirModel->itemForIndex(m_dirIndex); QVERIFY(!dirItem.isNull()); QCOMPARE(dirItem.name(), QString("subdir")); QVERIFY(dirItem.isDir()); QCOMPARE(dirItem.url().toLocalFile(), QString(m_tempDir->path() + "/subdir")); KFileItem fileInDirItem = m_dirModel->itemForIndex(m_fileInDirIndex); QVERIFY(!fileInDirItem.isNull()); QCOMPARE(fileInDirItem.name(), QString("testfile")); QVERIFY(!fileInDirItem.isDir()); QCOMPARE(fileInDirItem.url().toLocalFile(), QString(m_tempDir->path() + "/subdir/testfile")); KFileItem fileInSubdirItem = m_dirModel->itemForIndex(m_fileInSubdirIndex); QVERIFY(!fileInSubdirItem.isNull()); QCOMPARE(fileInSubdirItem.name(), QString("testfile")); QVERIFY(!fileInSubdirItem.isDir()); QCOMPARE(fileInSubdirItem.url().toLocalFile(), QString(m_tempDir->path() + "/subdir/subsubdir/testfile")); } void KDirModelTest::testIndexForItem() { KFileItem rootItem = m_dirModel->itemForIndex(QModelIndex()); QModelIndex rootIndex = m_dirModel->indexForItem(rootItem); QVERIFY(!rootIndex.isValid()); KFileItem fileItem = m_dirModel->itemForIndex(m_fileIndex); QModelIndex fileIndex = m_dirModel->indexForItem(fileItem); QCOMPARE(fileIndex, m_fileIndex); KFileItem dirItem = m_dirModel->itemForIndex(m_dirIndex); QModelIndex dirIndex = m_dirModel->indexForItem(dirItem); QCOMPARE(dirIndex, m_dirIndex); KFileItem fileInDirItem = m_dirModel->itemForIndex(m_fileInDirIndex); QModelIndex fileInDirIndex = m_dirModel->indexForItem(fileInDirItem); QCOMPARE(fileInDirIndex, m_fileInDirIndex); KFileItem fileInSubdirItem = m_dirModel->itemForIndex(m_fileInSubdirIndex); QModelIndex fileInSubdirIndex = m_dirModel->indexForItem(fileInSubdirItem); QCOMPARE(fileInSubdirIndex, m_fileInSubdirIndex); } void KDirModelTest::testData() { // First file QModelIndex idx1col0 = m_dirModel->index(m_fileIndex.row(), 0, QModelIndex()); QCOMPARE(idx1col0.data().toString(), QString("toplevelfile_1")); QModelIndex idx1col1 = m_dirModel->index(m_fileIndex.row(), 1, QModelIndex()); QString size1 = m_dirModel->data(idx1col1, Qt::DisplayRole).toString(); QCOMPARE(size1, QString("11 B")); KFileItem item = m_dirModel->data(m_fileIndex, KDirModel::FileItemRole).value(); KFileItem fileItem = m_dirModel->itemForIndex(m_fileIndex); QCOMPARE(item, fileItem); QCOMPARE(m_dirModel->data(m_fileIndex, KDirModel::ChildCountRole).toInt(), (int)KDirModel::ChildCountUnknown); // Second file QModelIndex idx2col0 = m_dirModel->index(m_secondFileIndex.row(), 0, QModelIndex()); QString display2 = m_dirModel->data(idx2col0, Qt::DisplayRole).toString(); QCOMPARE(display2, QString("toplevelfile_2")); // Subdir: check child count QCOMPARE(m_dirModel->data(m_dirIndex, KDirModel::ChildCountRole).toInt(), 3); // Subsubdir: check child count QCOMPARE(m_dirModel->data(m_fileInSubdirIndex.parent(), KDirModel::ChildCountRole).toInt(), 1); } void KDirModelTest::testReload() { fillModel(true); testItemForIndex(); } // We want more info than just "the values differ", if they do. #define COMPARE_INDEXES(a, b) \ QCOMPARE(a.row(), b.row()); \ QCOMPARE(a.column(), b.column()); \ QCOMPARE(a.model(), b.model()); \ QCOMPARE(a.parent().isValid(), b.parent().isValid()); \ QCOMPARE(a, b); void KDirModelTest::testModifyFile() { const QString file = m_tempDir->path() + "/toplevelfile_2"; const QUrl url = QUrl::fromLocalFile(file); #if 1 QSignalSpy spyDataChanged(m_dirModel, SIGNAL(dataChanged(QModelIndex,QModelIndex))); #else ModelSpy modelSpy(m_dirModel); #endif checkedConnect(m_dirModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), &m_eventLoop, SLOT(exitLoop())); // "Touch" the file setTimeStamp(file, s_referenceTimeStamp.addSecs(20)); // In stat mode, kdirwatch doesn't notice file changes; we need to trigger it // by creating a file. //createTestFile(m_tempDir->path() + "/toplevelfile_5"); KDirWatch::self()->setDirty(m_tempDir->path()); // Wait for KDirWatch to notify the change (especially when using Stat) enterLoop(); // If we come here, then dataChanged() was emitted - all good. #if 0 QCOMPARE(modelSpy.count(), 1); const QVariantList dataChanged = modelSpy.first(); #else const QVariantList dataChanged = spyDataChanged[0]; #endif QModelIndex receivedIndex = dataChanged[0].value(); COMPARE_INDEXES(receivedIndex, m_secondFileIndex); receivedIndex = dataChanged[1].value(); QCOMPARE(receivedIndex.row(), m_secondFileIndex.row()); // only compare row; column is count-1 disconnect(m_dirModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), &m_eventLoop, SLOT(exitLoop())); } void KDirModelTest::testRenameFile() { const QUrl url = QUrl::fromLocalFile(m_tempDir->path() + "/toplevelfile_2"); const QUrl newUrl = QUrl::fromLocalFile(m_tempDir->path() + "/toplevelfile_2_renamed"); QSignalSpy spyDataChanged(m_dirModel, SIGNAL(dataChanged(QModelIndex,QModelIndex))); checkedConnect(m_dirModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), &m_eventLoop, SLOT(exitLoop())); KIO::SimpleJob *job = KIO::rename(url, newUrl, KIO::HideProgressInfo); QVERIFY(job->exec()); // Wait for the DBUS signal from KDirNotify, it's the one the triggers dataChanged enterLoop(); // If we come here, then dataChanged() was emitted - all good. QCOMPARE(spyDataChanged.count(), 1); COMPARE_INDEXES(spyDataChanged[0][0].value(), m_secondFileIndex); QModelIndex receivedIndex = spyDataChanged[0][1].value(); QCOMPARE(receivedIndex.row(), m_secondFileIndex.row()); // only compare row; column is count-1 // check renaming happened QCOMPARE(m_dirModel->itemForIndex(m_secondFileIndex).url().toString(), newUrl.toString()); // check that KDirLister::cachedItemForUrl won't give a bad name if copying that item (#195385) KFileItem cachedItem = KDirLister::cachedItemForUrl(newUrl); QVERIFY(!cachedItem.isNull()); QCOMPARE(cachedItem.name(), QString("toplevelfile_2_renamed")); QCOMPARE(cachedItem.entry().stringValue(KIO::UDSEntry::UDS_NAME), QString("toplevelfile_2_renamed")); // Put things back to normal job = KIO::rename(newUrl, url, KIO::HideProgressInfo); QVERIFY(job->exec()); // Wait for the DBUS signal from KDirNotify, it's the one the triggers dataChanged enterLoop(); QCOMPARE(m_dirModel->itemForIndex(m_secondFileIndex).url().toString(), url.toString()); disconnect(m_dirModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), &m_eventLoop, SLOT(exitLoop())); } void KDirModelTest::testMoveDirectory() { testMoveDirectory(QStringLiteral("subdir")); } void KDirModelTest::testMoveDirectory(const QString &dir /*just a dir name, no slash*/) { const QString path = m_tempDir->path() + '/'; const QString srcdir = path + dir; QVERIFY(QDir(srcdir).exists()); QTemporaryDir destDir; const QString dest = destDir.path() + '/'; QVERIFY(QDir(dest).exists()); checkedConnect(m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), &m_eventLoop, SLOT(exitLoop())); // Move qDebug() << "Moving" << srcdir << "to" << dest; KIO::CopyJob *job = KIO::move(QUrl::fromLocalFile(srcdir), QUrl::fromLocalFile(dest), KIO::HideProgressInfo); job->setUiDelegate(nullptr); job->setUiDelegateExtension(nullptr); QVERIFY(job->exec()); // wait for kdirnotify enterLoop(); disconnect(m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), &m_eventLoop, SLOT(exitLoop())); QVERIFY(!m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir")).isValid()); QVERIFY(!m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir_renamed")).isValid()); checkedConnect(m_dirModel, SIGNAL(rowsInserted(QModelIndex,int,int)), &m_eventLoop, SLOT(exitLoop())); // Move back qDebug() << "Moving" << dest + dir << "back to" << srcdir; job = KIO::move(QUrl::fromLocalFile(dest + dir), QUrl::fromLocalFile(srcdir), KIO::HideProgressInfo); job->setUiDelegate(nullptr); job->setUiDelegateExtension(nullptr); QVERIFY(job->exec()); enterLoop(); QVERIFY(QDir(srcdir).exists()); disconnect(m_dirModel, SIGNAL(rowsInserted(QModelIndex,int,int)), &m_eventLoop, SLOT(exitLoop())); // m_dirIndex is invalid after the above... fillModel(true); } void KDirModelTest::testRenameDirectory() // #172945, #174703, (and #180156) { const QString path = m_tempDir->path() + '/'; const QUrl url = QUrl::fromLocalFile(path + "subdir"); const QUrl newUrl = QUrl::fromLocalFile(path + "subdir_renamed"); // For #180156 we need a second kdirmodel, viewing the subdir being renamed. // I'm abusing m_dirModelForExpand for that purpose. delete m_dirModelForExpand; m_dirModelForExpand = new KDirModel; KDirLister *dirListerForExpand = m_dirModelForExpand->dirLister(); checkedConnect(dirListerForExpand, SIGNAL(completed()), this, SLOT(slotListingCompleted())); dirListerForExpand->openUrl(url); // async enterLoop(); // Now do the renaming QSignalSpy spyDataChanged(m_dirModel, SIGNAL(dataChanged(QModelIndex,QModelIndex))); checkedConnect(m_dirModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), &m_eventLoop, SLOT(exitLoop())); KIO::SimpleJob *job = KIO::rename(url, newUrl, KIO::HideProgressInfo); QVERIFY(job->exec()); // Wait for the DBUS signal from KDirNotify, it's the one the triggers dataChanged enterLoop(); // If we come here, then dataChanged() was emitted - all good. //QCOMPARE(spyDataChanged.count(), 1); // it was in fact emitted 5 times... //COMPARE_INDEXES(spyDataChanged[0][0].value(), m_dirIndex); //QModelIndex receivedIndex = spyDataChanged[0][1].value(); //QCOMPARE(receivedIndex.row(), m_dirIndex.row()); // only compare row; column is count-1 // check renaming happened QCOMPARE(m_dirModel->itemForIndex(m_dirIndex).url().toString(), newUrl.toString()); qDebug() << newUrl << "indexForUrl=" << m_dirModel->indexForUrl(newUrl) << "m_dirIndex=" << m_dirIndex; QCOMPARE(m_dirModel->indexForUrl(newUrl), m_dirIndex); QVERIFY(m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir_renamed")).isValid()); QVERIFY(m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir_renamed/testfile")).isValid()); QVERIFY(m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir_renamed/subsubdir")).isValid()); QVERIFY(m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir_renamed/subsubdir/testfile")).isValid()); // Check the other kdirmodel got redirected QCOMPARE(dirListerForExpand->url().toLocalFile(), QString(path + "subdir_renamed")); qDebug() << "calling testMoveDirectory(subdir_renamed)"; // Test moving the renamed directory; if something inside KDirModel // wasn't properly updated by the renaming, this would detect it and crash (#180673) testMoveDirectory(QStringLiteral("subdir_renamed")); // Put things back to normal job = KIO::rename(newUrl, url, KIO::HideProgressInfo); QVERIFY(job->exec()); // Wait for the DBUS signal from KDirNotify, it's the one the triggers dataChanged enterLoop(); QCOMPARE(m_dirModel->itemForIndex(m_dirIndex).url().toString(), url.toString()); disconnect(m_dirModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), &m_eventLoop, SLOT(exitLoop())); QCOMPARE(m_dirModel->itemForIndex(m_dirIndex).url().toString(), url.toString()); QCOMPARE(m_dirModel->indexForUrl(url), m_dirIndex); QVERIFY(m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir")).isValid()); QVERIFY(m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir/testfile")).isValid()); QVERIFY(m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir/subsubdir")).isValid()); QVERIFY(m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir/subsubdir/testfile")).isValid()); QVERIFY(!m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir_renamed")).isValid()); QVERIFY(!m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir_renamed/testfile")).isValid()); QVERIFY(!m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir_renamed/subsubdir")).isValid()); QVERIFY(!m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir_renamed/subsubdir/testfile")).isValid()); // TODO INVESTIGATE // QCOMPARE(dirListerForExpand->url().toLocalFile(), path+"subdir"); delete m_dirModelForExpand; m_dirModelForExpand = nullptr; } void KDirModelTest::testRenameDirectoryInCache() // #188807 { // Ensure the stuff is in cache. fillModel(true); const QString path = m_tempDir->path() + '/'; QVERIFY(!m_dirModel->dirLister()->findByUrl(QUrl::fromLocalFile(path)).isNull()); // No more dirmodel nor dirlister. delete m_dirModel; m_dirModel = nullptr; // Now let's rename a directory that is in KCoreDirListerCache const QUrl url = QUrl::fromLocalFile(path); QUrl newUrl = url.adjusted(QUrl::StripTrailingSlash); newUrl.setPath(newUrl.path() + "_renamed"); qDebug() << newUrl; KIO::SimpleJob *job = KIO::rename(url, newUrl, KIO::HideProgressInfo); QVERIFY(job->exec()); // Put things back to normal job = KIO::rename(newUrl, url, KIO::HideProgressInfo); QVERIFY(job->exec()); // KDirNotify emits FileRenamed for each rename() above, which in turn // re-lists the directory. We need to wait for both signals to be emitted // otherwise the dirlister will not be in the state we expect. QTest::qWait(200); fillModel(true); QVERIFY(m_dirIndex.isValid()); KFileItem rootItem = m_dirModel->dirLister()->findByUrl(QUrl::fromLocalFile(path)); QVERIFY(!rootItem.isNull()); } void KDirModelTest::testChmodDirectory() // #53397 { QSignalSpy spyDataChanged(m_dirModel, SIGNAL(dataChanged(QModelIndex,QModelIndex))); checkedConnect(m_dirModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), &m_eventLoop, SLOT(exitLoop())); const QString path = m_tempDir->path() + '/'; KFileItem rootItem = m_dirModel->itemForIndex(QModelIndex()); const mode_t origPerm = rootItem.permissions(); mode_t newPerm = origPerm ^ S_IWGRP; //const QFile::Permissions origPerm = rootItem.filePermissions(); //QVERIFY(origPerm & QFile::ReadOwner); //const QFile::Permissions newPerm = origPerm ^ QFile::WriteGroup; QVERIFY(newPerm != origPerm); KFileItemList items; items << rootItem; KIO::Job *job = KIO::chmod(items, newPerm, S_IWGRP /*TODO: QFile::WriteGroup*/, QString(), QString(), false, KIO::HideProgressInfo); job->setUiDelegate(nullptr); QVERIFY(job->exec()); // ChmodJob doesn't talk to KDirNotify, kpropertiesdialog does. // [this allows to group notifications after all the changes one can make in the dialog] org::kde::KDirNotify::emitFilesChanged(QList() << QUrl::fromLocalFile(path)); // Wait for the DBUS signal from KDirNotify, it's the one the triggers rowsRemoved enterLoop(); // If we come here, then dataChanged() was emitted - all good. QCOMPARE(spyDataChanged.count(), 1); QModelIndex receivedIndex = spyDataChanged[0][0].value(); qDebug() << receivedIndex; QVERIFY(!receivedIndex.isValid()); const KFileItem newRootItem = m_dirModel->itemForIndex(QModelIndex()); QVERIFY(!newRootItem.isNull()); QCOMPARE(QString::number(newRootItem.permissions(), 16), QString::number(newPerm, 16)); disconnect(m_dirModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), &m_eventLoop, SLOT(exitLoop())); } enum { NoFlag = 0, NewDir = 1, // whether to re-create a new QTemporaryDir completely, to avoid cached fileitems ListFinalDir = 2, // whether to list the target dir at the same time, like k3b, for #193364 Recreate = 4, CacheSubdir = 8, // put subdir in the cache before expandToUrl // flags, next item is 16! }; void KDirModelTest::testExpandToUrl_data() { QTest::addColumn("flags"); // see enum above QTest::addColumn("expandToPath"); // relative path QTest::addColumn("expectedExpandSignals"); QTest::newRow("the root, nothing to do") << int(NoFlag) << QString() << QStringList(); QTest::newRow(".") << int(NoFlag) << "." << (QStringList()); QTest::newRow("subdir") << int(NoFlag) << "subdir" << (QStringList() << QStringLiteral("subdir")); QTest::newRow("subdir/.") << int(NoFlag) << "subdir/." << (QStringList() << QStringLiteral("subdir")); const QString subsubdir = QStringLiteral("subdir/subsubdir"); // Must list root, emit expand for subdir, list subdir, emit expand for subsubdir. QTest::newRow("subdir/subsubdir") << int(NoFlag) << subsubdir << (QStringList() << QStringLiteral("subdir") << subsubdir); // Must list root, emit expand for subdir, list subdir, emit expand for subsubdir, list subsubdir. const QString subsubdirfile = subsubdir + "/testfile"; QTest::newRow("subdir/subsubdir/testfile sync") << int(NoFlag) << subsubdirfile << (QStringList() << QStringLiteral("subdir") << subsubdir << subsubdirfile); #ifndef Q_OS_WIN // Expand a symlink to a directory (#219547) const QString dirlink = m_tempDir->path() + "/dirlink"; createTestSymlink(dirlink, "/"); QTest::newRow("dirlink") << int(NoFlag) << "dirlink/tmp" << (QStringList() << QStringLiteral("dirlink") << QStringLiteral("dirlink/tmp")); #endif // Do a cold-cache test too, but nowadays it doesn't change anything anymore, // apart from testing different code paths inside KDirLister. QTest::newRow("subdir/subsubdir/testfile with reload") << int(NewDir) << subsubdirfile << (QStringList() << QStringLiteral("subdir") << subsubdir << subsubdirfile); QTest::newRow("hold dest dir") // #193364 << int(NewDir | ListFinalDir) << subsubdirfile << (QStringList() << QStringLiteral("subdir") << subsubdir << subsubdirfile); // Put subdir in cache too (#175035) QTest::newRow("hold subdir and dest dir") << int(NewDir | CacheSubdir | ListFinalDir | Recreate) << subsubdirfile << (QStringList() << QStringLiteral("subdir") << subsubdir << subsubdirfile); // Make sure the last test has the Recreate option set, for the subsequent test methods. } void KDirModelTest::testExpandToUrl() { QFETCH(int, flags); QFETCH(QString, expandToPath); // relative QFETCH(QStringList, expectedExpandSignals); if (flags & NewDir) { recreateTestData(); // WARNING! m_dirIndex, m_fileIndex, m_secondFileIndex etc. are not valid anymore after this point! } const QString path = m_tempDir->path() + '/'; if (flags & CacheSubdir) { // This way, the listDir for subdir will find items in cache, and will schedule a CachedItemsJob m_dirModel->dirLister()->openUrl(QUrl::fromLocalFile(path + "subdir")); QSignalSpy completedSpy(m_dirModel->dirLister(), SIGNAL(completed())); QVERIFY(completedSpy.wait(2000)); } if (flags & ListFinalDir) { // This way, the last listDir will find items in cache, and will schedule a CachedItemsJob m_dirModel->dirLister()->openUrl(QUrl::fromLocalFile(path + "subdir/subsubdir")); QSignalSpy completedSpy(m_dirModel->dirLister(), SIGNAL(completed())); QVERIFY(completedSpy.wait(2000)); } if (!m_dirModelForExpand || (flags & NewDir)) { delete m_dirModelForExpand; m_dirModelForExpand = new KDirModel; checkedConnect(m_dirModelForExpand, SIGNAL(expand(QModelIndex)), this, SLOT(slotExpand(QModelIndex))); checkedConnect(m_dirModelForExpand, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(slotRowsInserted(QModelIndex,int,int))); KDirLister *dirListerForExpand = m_dirModelForExpand->dirLister(); dirListerForExpand->openUrl(QUrl::fromLocalFile(path), KDirLister::NoFlags); // async } m_rowsInsertedEmitted = false; m_expectedExpandSignals = expectedExpandSignals; m_nextExpectedExpandSignals = 0; QSignalSpy spyExpand(m_dirModelForExpand, SIGNAL(expand(QModelIndex))); m_urlToExpandTo = QUrl::fromLocalFile(path + expandToPath); // If KDirModel doesn't know this URL yet, then we want to see rowsInserted signals // being emitted, so that the slots can get the index to that url then. m_expectRowsInserted = !expandToPath.isEmpty() && !m_dirModelForExpand->indexForUrl(m_urlToExpandTo).isValid(); m_dirModelForExpand->expandToUrl(m_urlToExpandTo); if (expectedExpandSignals.isEmpty()) { QTest::qWait(20); // to make sure we process queued connection calls, otherwise spyExpand.count() is always 0 even if there's a bug... QCOMPARE(spyExpand.count(), 0); } else { if (spyExpand.count() < expectedExpandSignals.count()) { enterLoop(); QCOMPARE(spyExpand.count(), expectedExpandSignals.count()); } if (m_expectRowsInserted) { QVERIFY(m_rowsInsertedEmitted); } } // Now it should exist if (!expandToPath.isEmpty() && expandToPath != QLatin1String(".")) { qDebug() << "Do I know" << m_urlToExpandTo << "?"; QVERIFY(m_dirModelForExpand->indexForUrl(m_urlToExpandTo).isValid()); } if (flags & ListFinalDir) { testUpdateParentAfterExpand(); } if (flags & Recreate) { // Clean up, for the next tests recreateTestData(); fillModel(false); } } void KDirModelTest::slotExpand(const QModelIndex &index) { QVERIFY(index.isValid()); const QString path = m_tempDir->path() + '/'; KFileItem item = m_dirModelForExpand->itemForIndex(index); QVERIFY(!item.isNull()); qDebug() << item.url().toLocalFile(); QCOMPARE(item.url().toLocalFile(), QString(path + m_expectedExpandSignals[m_nextExpectedExpandSignals++])); // if rowsInserted wasn't emitted yet, then any proxy model would be unable to do anything with index at this point if (item.url() == m_urlToExpandTo) { QVERIFY(m_dirModelForExpand->indexForUrl(m_urlToExpandTo).isValid()); if (m_expectRowsInserted) { QVERIFY(m_rowsInsertedEmitted); } } if (m_nextExpectedExpandSignals == m_expectedExpandSignals.count()) { m_eventLoop.exitLoop(); // done } } void KDirModelTest::slotRowsInserted(const QModelIndex &, int, int) { m_rowsInsertedEmitted = true; } // This code is called by testExpandToUrl void KDirModelTest::testUpdateParentAfterExpand() // #193364 { const QString path = m_tempDir->path() + '/'; const QString file = path + "subdir/aNewFile"; qDebug() << "Creating" << file; QVERIFY(!QFile::exists(file)); createTestFile(file); QSignalSpy spyRowsInserted(m_dirModelForExpand, SIGNAL(rowsInserted(QModelIndex,int,int))); QVERIFY(spyRowsInserted.wait(1000)); } void KDirModelTest::testFilter() { QVERIFY(m_dirIndex.isValid()); const int oldTopLevelRowCount = m_dirModel->rowCount(); const int oldSubdirRowCount = m_dirModel->rowCount(m_dirIndex); QSignalSpy spyItemsFilteredByMime(m_dirModel->dirLister(), SIGNAL(itemsFilteredByMime(KFileItemList))); QSignalSpy spyItemsDeleted(m_dirModel->dirLister(), SIGNAL(itemsDeleted(KFileItemList))); QSignalSpy spyRowsRemoved(m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int))); m_dirModel->dirLister()->setNameFilter(QStringLiteral("toplevel*")); QCOMPARE(m_dirModel->rowCount(), oldTopLevelRowCount); // no change yet QCOMPARE(m_dirModel->rowCount(m_dirIndex), oldSubdirRowCount); // no change yet m_dirModel->dirLister()->emitChanges(); QCOMPARE(m_dirModel->rowCount(), 4); // 3 toplevel* files, one subdir QCOMPARE(m_dirModel->rowCount(m_dirIndex), 1); // the files get filtered out, the subdir remains // In the subdir, we can get rowsRemoved signals like (1,2) or (0,0)+(2,2), // depending on the order of the files in the model. // So QCOMPARE(spyRowsRemoved.count(), 3) is fragile, we rather need // to sum up the removed rows per parent directory. QMap rowsRemovedPerDir; for (int i = 0; i < spyRowsRemoved.count(); ++i) { const QVariantList args = spyRowsRemoved[i]; const QModelIndex parentIdx = args[0].value(); QString dirName; if (parentIdx.isValid()) { const KFileItem item = m_dirModel->itemForIndex(parentIdx); dirName = item.name(); } else { dirName = QStringLiteral("root"); } rowsRemovedPerDir[dirName] += args[2].toInt() - args[1].toInt() + 1; //qDebug() << parentIdx << args[1].toInt() << args[2].toInt(); } QCOMPARE(rowsRemovedPerDir.count(), 3); // once for every dir QCOMPARE(rowsRemovedPerDir.value("root"), 1); // one from toplevel ('special chars') QCOMPARE(rowsRemovedPerDir.value("subdir"), 2); // two from subdir QCOMPARE(rowsRemovedPerDir.value("subsubdir"), 1); // one from subsubdir QCOMPARE(spyItemsDeleted.count(), 3); // once for every dir QCOMPARE(spyItemsDeleted[0][0].value().count(), 1); // one from toplevel ('special chars') QCOMPARE(spyItemsDeleted[1][0].value().count(), 2); // two from subdir QCOMPARE(spyItemsDeleted[2][0].value().count(), 1); // one from subsubdir QCOMPARE(spyItemsFilteredByMime.count(), 0); spyItemsDeleted.clear(); spyItemsFilteredByMime.clear(); // Reset the filter qDebug() << "reset to no filter"; m_dirModel->dirLister()->setNameFilter(QString()); m_dirModel->dirLister()->emitChanges(); QCOMPARE(m_dirModel->rowCount(), oldTopLevelRowCount); QCOMPARE(m_dirModel->rowCount(m_dirIndex), oldSubdirRowCount); QCOMPARE(spyItemsDeleted.count(), 0); QCOMPARE(spyItemsFilteredByMime.count(), 0); // The order of things changed because of filtering. // Fill again, so that m_fileIndex etc. are correct again. fillModel(true); } void KDirModelTest::testMimeFilter() { QVERIFY(m_dirIndex.isValid()); const int oldTopLevelRowCount = m_dirModel->rowCount(); const int oldSubdirRowCount = m_dirModel->rowCount(m_dirIndex); QSignalSpy spyItemsFilteredByMime(m_dirModel->dirLister(), SIGNAL(itemsFilteredByMime(KFileItemList))); QSignalSpy spyItemsDeleted(m_dirModel->dirLister(), SIGNAL(itemsDeleted(KFileItemList))); QSignalSpy spyRowsRemoved(m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int))); m_dirModel->dirLister()->setMimeFilter(QStringList() << QStringLiteral("application/pdf")); QCOMPARE(m_dirModel->rowCount(), oldTopLevelRowCount); // no change yet QCOMPARE(m_dirModel->rowCount(m_dirIndex), oldSubdirRowCount); // no change yet m_dirModel->dirLister()->emitChanges(); QCOMPARE(m_dirModel->rowCount(), 1); // 1 pdf files, no subdir anymore QVERIFY(spyRowsRemoved.count() >= 1); // depends on contiguity... QVERIFY(spyItemsDeleted.count() >= 1); // once for every dir // Maybe it would make sense to have those items in itemsFilteredByMime, // but well, for the only existing use of that signal (mime filter plugin), // it's not really necessary, the plugin has seen those files before anyway. // The signal is mostly useful for the case of listing a dir with a mime filter set. //QCOMPARE(spyItemsFilteredByMime.count(), 1); //QCOMPARE(spyItemsFilteredByMime[0][0].value().count(), 4); spyItemsDeleted.clear(); spyItemsFilteredByMime.clear(); // Reset the filter qDebug() << "reset to no filter"; m_dirModel->dirLister()->setMimeFilter(QStringList()); m_dirModel->dirLister()->emitChanges(); QCOMPARE(m_dirModel->rowCount(), oldTopLevelRowCount); QCOMPARE(spyItemsDeleted.count(), 0); QCOMPARE(spyItemsFilteredByMime.count(), 0); // The order of things changed because of filtering. // Fill again, so that m_fileIndex etc. are correct again. fillModel(true); } void KDirModelTest::testShowHiddenFiles() // #174788 { KDirLister *dirLister = m_dirModel->dirLister(); QSignalSpy spyRowsRemoved(m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int))); QSignalSpy spyNewItems(dirLister, SIGNAL(newItems(KFileItemList))); QSignalSpy spyRowsInserted(m_dirModel, SIGNAL(rowsInserted(QModelIndex,int,int))); dirLister->setShowingDotFiles(true); dirLister->emitChanges(); const int numberOfDotFiles = 2; QCOMPARE(spyNewItems.count(), 1); QCOMPARE(spyNewItems[0][0].value().count(), numberOfDotFiles); QCOMPARE(spyRowsInserted.count(), 1); QCOMPARE(spyRowsRemoved.count(), 0); spyNewItems.clear(); spyRowsInserted.clear(); dirLister->setShowingDotFiles(false); dirLister->emitChanges(); QCOMPARE(spyNewItems.count(), 0); QCOMPARE(spyRowsInserted.count(), 0); QCOMPARE(spyRowsRemoved.count(), 1); } void KDirModelTest::testMultipleSlashes() { const QString path = m_tempDir->path() + '/'; QModelIndex index = m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir//testfile")); QVERIFY(index.isValid()); index = m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir//subsubdir//")); QVERIFY(index.isValid()); index = m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir///subsubdir////testfile")); QVERIFY(index.isValid()); } void KDirModelTest::testUrlWithRef() // #171117 { const QString path = m_tempDir->path() + '/'; KDirLister *dirLister = m_dirModel->dirLister(); QUrl url = QUrl::fromLocalFile(path); url.setFragment(QStringLiteral("ref")); QVERIFY(url.url().endsWith("#ref")); dirLister->openUrl(url, KDirLister::NoFlags); checkedConnect(dirLister, SIGNAL(completed()), this, SLOT(slotListingCompleted())); enterLoop(); QCOMPARE(dirLister->url().toString(), url.toString(QUrl::StripTrailingSlash)); collectKnownIndexes(); disconnect(dirLister, SIGNAL(completed()), this, SLOT(slotListingCompleted())); } //void KDirModelTest::testFontUrlWithHost() // #160057 --> moved to kio_fonts (kfontinst/kio/autotests) void KDirModelTest::testRemoteUrlWithHost() // #178416 { if (!KProtocolInfo::isKnownProtocol(QStringLiteral("remote"))) { QSKIP("kio_remote not installed"); } QUrl url(QStringLiteral("remote://foo")); KDirLister *dirLister = m_dirModel->dirLister(); dirLister->openUrl(url, KDirLister::NoFlags); checkedConnect(dirLister, SIGNAL(completed()), this, SLOT(slotListingCompleted())); enterLoop(); QCOMPARE(dirLister->url().toString(), QString("remote:")); } void KDirModelTest::testZipFile() // # 171721 { const QString path = QFileInfo(QFINDTESTDATA("wronglocalsizes.zip")).absolutePath(); KDirLister *dirLister = m_dirModel->dirLister(); dirLister->openUrl(QUrl::fromLocalFile(path), KDirLister::NoFlags); checkedConnect(dirLister, SIGNAL(completed()), this, SLOT(slotListingCompleted())); enterLoop(); disconnect(dirLister, SIGNAL(completed()), this, SLOT(slotListingCompleted())); QUrl zipUrl(QUrl::fromLocalFile(path)); zipUrl.setPath(zipUrl.path() + "/wronglocalsizes.zip"); // just a zip file lying here for other reasons QVERIFY(QFile::exists(zipUrl.toLocalFile())); zipUrl.setScheme(QStringLiteral("zip")); QModelIndex index = m_dirModel->indexForUrl(zipUrl); QVERIFY(!index.isValid()); // protocol mismatch, can't find it! zipUrl.setScheme(QStringLiteral("file")); index = m_dirModel->indexForUrl(zipUrl); QVERIFY(index.isValid()); } void KDirModelTest::testSmb() { const QUrl smbUrl(QStringLiteral("smb:/")); // TODO: feed a KDirModel without using a KDirLister. // Calling the slots directly. // This requires that KDirModel does not ask the KDirLister for its rootItem anymore, // but that KDirLister emits the root item whenever it changes. if (!KProtocolInfo::isKnownProtocol(QStringLiteral("smb"))) { QSKIP("kio_smb not installed"); } KDirLister *dirLister = m_dirModel->dirLister(); dirLister->openUrl(smbUrl, KDirLister::NoFlags); checkedConnect(dirLister, SIGNAL(completed()), this, SLOT(slotListingCompleted())); checkedConnect(dirLister, SIGNAL(canceled()), this, SLOT(slotListingCompleted())); QSignalSpy spyCanceled(dirLister, SIGNAL(canceled())); enterLoop(); // wait for completed signal if (spyCanceled.count() > 0) { QSKIP("smb:/ returns an error, probably no network available"); } QModelIndex index = m_dirModel->index(0, 0); if (index.isValid()) { QVERIFY(m_dirModel->canFetchMore(index)); m_dirModel->fetchMore(index); enterLoop(); // wait for completed signal disconnect(dirLister, SIGNAL(completed()), this, SLOT(slotListingCompleted())); } } class MyDirLister : public KDirLister { public: void emitItemsDeleted(const KFileItemList &items) { emit itemsDeleted(items); } }; void KDirModelTest::testBug196695() { KFileItem rootItem(QUrl::fromLocalFile(m_tempDir->path()), QString(), KFileItem::Unknown); KFileItem childItem(QUrl::fromLocalFile(QString(m_tempDir->path() + "/toplevelfile_1")), QString(), KFileItem::Unknown); KFileItemList list; // Important: the root item must not be first in the list to trigger bug 196695 list << childItem << rootItem; MyDirLister *dirLister = static_cast(m_dirModel->dirLister()); dirLister->emitItemsDeleted(list); fillModel(true); } void KDirModelTest::testMimeData() { QModelIndex index0 = m_dirModel->index(0, 0); QVERIFY(index0.isValid()); QModelIndex index1 = m_dirModel->index(1, 0); QVERIFY(index1.isValid()); QList indexes; indexes << index0 << index1; QMimeData *mimeData = m_dirModel->mimeData(indexes); QVERIFY(mimeData); QVERIFY(mimeData->hasUrls()); const QList urls = mimeData->urls(); QCOMPARE(urls.count(), indexes.count()); + delete mimeData; } void KDirModelTest::testDotHiddenFile_data() { QTest::addColumn("fileContents"); QTest::addColumn("expectedListing"); QStringList allItems; allItems << QStringLiteral("toplevelfile_1") << QStringLiteral("toplevelfile_2") << QStringLiteral("toplevelfile_3") << SPECIALCHARS << QStringLiteral("subdir"); QTest::newRow("empty_file") << QStringList() << allItems; QTest::newRow("simple_name") << (QStringList() << QStringLiteral("toplevelfile_1")) << QStringList(allItems.mid(1)); QStringList allButSpecialChars = allItems; allButSpecialChars.removeAt(3); QTest::newRow("special_chars") << (QStringList() << SPECIALCHARS) << allButSpecialChars; QStringList allButSubdir = allItems; allButSubdir.removeAt(4); QTest::newRow("subdir") << (QStringList() << QStringLiteral("subdir")) << allButSubdir; QTest::newRow("many_lines") << (QStringList() << QStringLiteral("subdir") << QStringLiteral("toplevelfile_1") << QStringLiteral("toplevelfile_3") << QStringLiteral("toplevelfile_2")) << (QStringList() << SPECIALCHARS); } void KDirModelTest::testDotHiddenFile() { QFETCH(QStringList, fileContents); QFETCH(QStringList, expectedListing); const QString path = m_tempDir->path() + '/'; const QString dotHiddenFile = path + ".hidden"; QTest::qWait(1000); // mtime-based cache, so we need to wait for 1 second QFile dh(dotHiddenFile); QVERIFY(dh.open(QIODevice::WriteOnly)); dh.write(fileContents.join('\n').toUtf8()); dh.close(); // Do it twice: once to read from the file and once to use the cache for (int i = 0; i < 2; ++i) { fillModel(true, false); QStringList files; for (int row = 0; row < m_dirModel->rowCount(); ++row) { files.append(m_dirModel->index(row, KDirModel::Name).data().toString()); } files.sort(); expectedListing.sort(); QCOMPARE(files, expectedListing); } dh.remove(); } void KDirModelTest::testDeleteFile() { fillModel(true); QVERIFY(m_fileIndex.isValid()); const int oldTopLevelRowCount = m_dirModel->rowCount(); const QString path = m_tempDir->path() + '/'; const QString file = path + "toplevelfile_1"; const QUrl url = QUrl::fromLocalFile(file); QSignalSpy spyRowsRemoved(m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int))); checkedConnect(m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), &m_eventLoop, SLOT(exitLoop())); KIO::DeleteJob *job = KIO::del(url, KIO::HideProgressInfo); QVERIFY(job->exec()); // Wait for the DBUS signal from KDirNotify, it's the one the triggers rowsRemoved enterLoop(); // If we come here, then rowsRemoved() was emitted - all good. const int topLevelRowCount = m_dirModel->rowCount(); QCOMPARE(topLevelRowCount, oldTopLevelRowCount - 1); // one less than before QCOMPARE(spyRowsRemoved.count(), 1); QCOMPARE(spyRowsRemoved[0][1].toInt(), m_fileIndex.row()); QCOMPARE(spyRowsRemoved[0][2].toInt(), m_fileIndex.row()); disconnect(m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), &m_eventLoop, SLOT(exitLoop())); QModelIndex fileIndex = m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "toplevelfile_1")); QVERIFY(!fileIndex.isValid()); // Recreate the file, for consistency in the next tests // So the second part of this test is a "testCreateFile" createTestFile(file); // Tricky problem - KDirLister::openUrl will emit items from cache // and then schedule an update; so just calling fillModel would // not wait enough, it would abort due to not finding toplevelfile_1 // in the items from cache. This progressive-emitting behavior is fine // for GUIs but not for unit tests ;-) fillModel(true, false); fillModel(false); } void KDirModelTest::testDeleteFileWhileListing() // doesn't really test that yet, the kdirwatch deleted signal comes too late { const int oldTopLevelRowCount = m_dirModel->rowCount(); const QString path = m_tempDir->path() + '/'; const QString file = path + "toplevelfile_1"; const QUrl url = QUrl::fromLocalFile(file); KDirLister *dirLister = m_dirModel->dirLister(); QSignalSpy spyCompleted(dirLister, SIGNAL(completed())); checkedConnect(dirLister, SIGNAL(completed()), this, SLOT(slotListingCompleted())); dirLister->openUrl(QUrl::fromLocalFile(path), KDirLister::NoFlags); if (!spyCompleted.isEmpty()) { QSKIP("listing completed too early"); } QSignalSpy spyRowsRemoved(m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int))); KIO::DeleteJob *job = KIO::del(url, KIO::HideProgressInfo); QVERIFY(job->exec()); if (spyCompleted.isEmpty()) { enterLoop(); } QVERIFY(spyRowsRemoved.wait(1000)); const int topLevelRowCount = m_dirModel->rowCount(); QCOMPARE(topLevelRowCount, oldTopLevelRowCount - 1); // one less than before QCOMPARE(spyRowsRemoved.count(), 1); QCOMPARE(spyRowsRemoved[0][1].toInt(), m_fileIndex.row()); QCOMPARE(spyRowsRemoved[0][2].toInt(), m_fileIndex.row()); QModelIndex fileIndex = m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "toplevelfile_1")); QVERIFY(!fileIndex.isValid()); qDebug() << "Test done, recreating file"; // Recreate the file, for consistency in the next tests // So the second part of this test is a "testCreateFile" createTestFile(file); fillModel(true, false); // see testDeleteFile fillModel(false); } void KDirModelTest::testOverwriteFileWithDir() // #151851 c4 { fillModel(false); const QString path = m_tempDir->path() + '/'; const QString dir = path + "subdir"; const QString file = path + "toplevelfile_1"; const int oldTopLevelRowCount = m_dirModel->rowCount(); bool removalWithinTopLevel = false; bool dataChangedAtFirstLevel = false; auto rrc = connect(m_dirModel, &KDirModel::rowsRemoved, this, [&removalWithinTopLevel](const QModelIndex &index) { if (!index.isValid()) { // yes, that's what we have been waiting for removalWithinTopLevel = true; } }); auto dcc = connect(m_dirModel, &KDirModel::dataChanged, this, [&dataChangedAtFirstLevel](const QModelIndex &index) { if (index.isValid() && !index.parent().isValid()) { // a change of a node whose parent is root, yay, that's it dataChangedAtFirstLevel = true; } }); checkedConnect(m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), &m_eventLoop, SLOT(exitLoop())); KIO::Job *job = KIO::move(QUrl::fromLocalFile(dir), QUrl::fromLocalFile(file), KIO::HideProgressInfo); job->setUiDelegate(nullptr); PredefinedAnswerJobUiDelegate extension; extension.m_renameResult = KIO::R_OVERWRITE; job->setUiDelegateExtension(&extension); QVERIFY(job->exec()); QCOMPARE(extension.m_askFileRenameCalled, 1); // Wait for a removal within the top level (that's for the old file going away), and also // for a dataChanged which notifies us that a file has become a directory int retries = 0; while ((!removalWithinTopLevel || !dataChangedAtFirstLevel) && retries < 100) { QTest::qWait(10); ++retries; } QVERIFY(removalWithinTopLevel); QVERIFY(dataChangedAtFirstLevel); m_dirModel->disconnect(rrc); m_dirModel->disconnect(dcc); // If we come here, then rowsRemoved() was emitted - all good. const int topLevelRowCount = m_dirModel->rowCount(); QCOMPARE(topLevelRowCount, oldTopLevelRowCount - 1); // one less than before QVERIFY(!m_dirModel->indexForUrl(QUrl::fromLocalFile(dir)).isValid()); QModelIndex newIndex = m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "toplevelfile_1")); QVERIFY(newIndex.isValid()); KFileItem newItem = m_dirModel->itemForIndex(newIndex); QVERIFY(newItem.isDir()); // yes, the file is a dir now ;-) qDebug() << "========= Test done, recreating test data ========="; recreateTestData(); fillModel(false); } void KDirModelTest::testDeleteFiles() { const int oldTopLevelRowCount = m_dirModel->rowCount(); const QString file = m_tempDir->path() + "/toplevelfile_"; QList urls; urls << QUrl::fromLocalFile(file + '1') << QUrl::fromLocalFile(file + '2') << QUrl::fromLocalFile(file + '3'); QSignalSpy spyRowsRemoved(m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int))); KIO::DeleteJob *job = KIO::del(urls, KIO::HideProgressInfo); QVERIFY(job->exec()); int numRowsRemoved = 0; while (numRowsRemoved < 3) { QTest::qWait(20); numRowsRemoved = 0; for (int sigNum = 0; sigNum < spyRowsRemoved.count(); ++sigNum) { numRowsRemoved += spyRowsRemoved[sigNum][2].toInt() - spyRowsRemoved[sigNum][1].toInt() + 1; } qDebug() << "numRowsRemoved=" << numRowsRemoved; } const int topLevelRowCount = m_dirModel->rowCount(); QCOMPARE(topLevelRowCount, oldTopLevelRowCount - 3); // three less than before qDebug() << "Recreating test data"; recreateTestData(); qDebug() << "Re-filling model"; fillModel(false); } // A renaming that looks more like a deletion to the model void KDirModelTest::testRenameFileToHidden() // #174721 { const QUrl url = QUrl::fromLocalFile(m_tempDir->path() + "/toplevelfile_2"); const QUrl newUrl = QUrl::fromLocalFile(m_tempDir->path() + "/.toplevelfile_2"); QSignalSpy spyDataChanged(m_dirModel, SIGNAL(dataChanged(QModelIndex,QModelIndex))); QSignalSpy spyRowsRemoved(m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int))); QSignalSpy spyRowsInserted(m_dirModel, SIGNAL(rowsInserted(QModelIndex,int,int))); checkedConnect(m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), &m_eventLoop, SLOT(exitLoop())); KIO::SimpleJob *job = KIO::rename(url, newUrl, KIO::HideProgressInfo); QVERIFY(job->exec()); // Wait for the DBUS signal from KDirNotify, it's the one the triggers KDirLister enterLoop(); // If we come here, then rowsRemoved() was emitted - all good. QCOMPARE(spyDataChanged.count(), 0); QCOMPARE(spyRowsRemoved.count(), 1); QCOMPARE(spyRowsInserted.count(), 0); COMPARE_INDEXES(spyRowsRemoved[0][0].value(), QModelIndex()); // parent is invalid const int row = spyRowsRemoved[0][1].toInt(); QCOMPARE(row, m_secondFileIndex.row()); // only compare row disconnect(m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), &m_eventLoop, SLOT(exitLoop())); spyRowsRemoved.clear(); // Put things back to normal, should make the file reappear checkedConnect(m_dirModel, SIGNAL(rowsInserted(QModelIndex,int,int)), &m_eventLoop, SLOT(exitLoop())); job = KIO::rename(newUrl, url, KIO::HideProgressInfo); QVERIFY(job->exec()); // Wait for the DBUS signal from KDirNotify, it's the one the triggers KDirLister enterLoop(); QCOMPARE(spyDataChanged.count(), 0); QCOMPARE(spyRowsRemoved.count(), 0); QCOMPARE(spyRowsInserted.count(), 1); int newRow = spyRowsInserted[0][1].toInt(); m_secondFileIndex = m_dirModel->index(newRow, 0); QVERIFY(m_secondFileIndex.isValid()); QCOMPARE(m_dirModel->itemForIndex(m_secondFileIndex).url().toString(), url.toString()); } void KDirModelTest::testDeleteDirectory() { const QString path = m_tempDir->path() + '/'; const QUrl url = QUrl::fromLocalFile(path + "subdir/subsubdir"); QSignalSpy spyRowsRemoved(m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int))); checkedConnect(m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), &m_eventLoop, SLOT(exitLoop())); QSignalSpy spyDirWatchDeleted(KDirWatch::self(), SIGNAL(deleted(QString))); KIO::DeleteJob *job = KIO::del(url, KIO::HideProgressInfo); QVERIFY(job->exec()); // Wait for the DBUS signal from KDirNotify, it's the one the triggers rowsRemoved enterLoop(); // If we come here, then rowsRemoved() was emitted - all good. QCOMPARE(spyRowsRemoved.count(), 1); disconnect(m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), &m_eventLoop, SLOT(exitLoop())); QModelIndex deletedDirIndex = m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir/subsubdir")); QVERIFY(!deletedDirIndex.isValid()); QModelIndex dirIndex = m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir")); QVERIFY(dirIndex.isValid()); // TODO!!! Bug in KDirWatch? ### // QCOMPARE(spyDirWatchDeleted.count(), 1); } void KDirModelTest::testDeleteCurrentDirectory() { const int oldTopLevelRowCount = m_dirModel->rowCount(); const QString path = m_tempDir->path() + '/'; const QUrl url = QUrl::fromLocalFile(path); QSignalSpy spyRowsRemoved(m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int))); checkedConnect(m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), &m_eventLoop, SLOT(exitLoop())); KDirWatch::self()->statistics(); KIO::DeleteJob *job = KIO::del(url, KIO::HideProgressInfo); QVERIFY(job->exec()); // Wait for the DBUS signal from KDirNotify, it's the one the triggers rowsRemoved enterLoop(); // If we come here, then rowsRemoved() was emitted - all good. const int topLevelRowCount = m_dirModel->rowCount(); QCOMPARE(topLevelRowCount, 0); // empty // We can get rowsRemoved for subdirs first, since kdirwatch notices that. QVERIFY(spyRowsRemoved.count() >= 1); // Look for the signal(s) that had QModelIndex() as parent. int i; int numDeleted = 0; for (i = 0; i < spyRowsRemoved.count(); ++i) { const int from = spyRowsRemoved[i][1].toInt(); const int to = spyRowsRemoved[i][2].toInt(); qDebug() << spyRowsRemoved[i][0].value() << from << to; if (!spyRowsRemoved[i][0].value().isValid()) { numDeleted += (to - from) + 1; } } QCOMPARE(numDeleted, oldTopLevelRowCount); disconnect(m_dirModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), &m_eventLoop, SLOT(exitLoop())); QModelIndex fileIndex = m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "toplevelfile_1")); QVERIFY(!fileIndex.isValid()); } void KDirModelTest::testQUrlHash() { const int count = 3000; // Prepare an array of QUrls so that url constructing isn't part of the timing QVector urls; urls.resize(count); for (int i = 0; i < count; ++i) { urls[i] = QUrl("http://www.kde.org/path/" + QString::number(i)); } QHash qurlHash; QHash kurlHash; QTime dt; dt.start(); for (int i = 0; i < count; ++i) { qurlHash.insert(urls[i], i); } //qDebug() << "inserting" << count << "urls into QHash using old qHash:" << dt.elapsed() << "msecs"; dt.start(); for (int i = 0; i < count; ++i) { kurlHash.insert(urls[i], i); } //qDebug() << "inserting" << count << "urls into QHash using new qHash:" << dt.elapsed() << "msecs"; // Nice results: for count=30000 I got 4515 (before) and 103 (after) dt.start(); for (int i = 0; i < count; ++i) { QCOMPARE(qurlHash.value(urls[i]), i); } //qDebug() << "looking up" << count << "urls into QHash using old qHash:" << dt.elapsed() << "msecs"; dt.start(); for (int i = 0; i < count; ++i) { QCOMPARE(kurlHash.value(urls[i]), i); } //qDebug() << "looking up" << count << "urls into QHash using new qHash:" << dt.elapsed() << "msecs"; // Nice results: for count=30000 I got 4296 (before) and 63 (after) }