Warning, file /frameworks/kio/autotests/fileundomanagertest.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
0001 /* 0002 This file is part of KDE 0003 SPDX-FileCopyrightText: 2006, 2008 David Faure <faure@kde.org> 0004 0005 SPDX-License-Identifier: LGPL-2.0-or-later 0006 */ 0007 0008 #include "fileundomanagertest.h" 0009 0010 #include "../src/utils_p.h" 0011 0012 #include "mockcoredelegateextensions.h" 0013 #include <kio/batchrenamejob.h> 0014 #include <kio/copyjob.h> 0015 #include <kio/deletejob.h> 0016 #include <kio/fileundomanager.h> 0017 #include <kio/mkdirjob.h> 0018 #include <kio/mkpathjob.h> 0019 #include <kio/paste.h> 0020 #include <kio/pastejob.h> 0021 #include <kio/restorejob.h> 0022 #include <kioglobal_p.h> 0023 #include <kprotocolinfo.h> 0024 0025 #include <QApplication> 0026 #include <QClipboard> 0027 #include <QDateTime> 0028 #include <QDebug> 0029 #include <QDir> 0030 #include <QFileInfo> 0031 #include <QMimeData> 0032 #include <QSignalSpy> 0033 #include <QTest> 0034 #include <qplatformdefs.h> 0035 0036 #include <KConfig> 0037 #include <KConfigGroup> 0038 #include <KUrlMimeData> 0039 0040 #include <cerrno> 0041 #include <time.h> 0042 #ifdef Q_OS_WIN 0043 #include <sys/utime.h> 0044 #else 0045 #include <sys/time.h> 0046 #include <utime.h> 0047 #endif 0048 0049 QTEST_MAIN(FileUndoManagerTest) 0050 0051 using namespace KIO; 0052 0053 static QString homeTmpDir() 0054 { 0055 return QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QDir::separator(); 0056 } 0057 static QString destDir() 0058 { 0059 return homeTmpDir() + "destdir/"; 0060 } 0061 0062 static QString srcFile() 0063 { 0064 return homeTmpDir() + "testfile"; 0065 } 0066 static QString destFile() 0067 { 0068 return destDir() + "testfile"; 0069 } 0070 0071 #ifndef Q_OS_WIN 0072 static QString srcLink() 0073 { 0074 return homeTmpDir() + "symlink"; 0075 } 0076 static QString destLink() 0077 { 0078 return destDir() + "symlink"; 0079 } 0080 #endif 0081 0082 static QString srcSubDir() 0083 { 0084 return homeTmpDir() + "subdir"; 0085 } 0086 static QString destSubDir() 0087 { 0088 return destDir() + "subdir"; 0089 } 0090 0091 static QList<QUrl> sourceList() 0092 { 0093 QList<QUrl> lst; 0094 lst << QUrl::fromLocalFile(srcFile()); 0095 #ifndef Q_OS_WIN 0096 lst << QUrl::fromLocalFile(srcLink()); 0097 #endif 0098 return lst; 0099 } 0100 0101 static void createTestFile(const QString &path, const char *contents) 0102 { 0103 QFile f(path); 0104 if (!f.open(QIODevice::WriteOnly)) { 0105 qFatal("Couldn't create %s", qPrintable(path)); 0106 } 0107 f.write(QByteArray(contents)); 0108 f.close(); 0109 } 0110 0111 static void createTestSymlink(const QString &path) 0112 { 0113 // Create symlink if it doesn't exist yet 0114 QT_STATBUF buf; 0115 if (QT_LSTAT(QFile::encodeName(path).constData(), &buf) != 0) { 0116 bool ok = KIOPrivate::createSymlink(QStringLiteral("/IDontExist"), path); // broken symlink 0117 if (!ok) { 0118 qFatal("couldn't create symlink: %s", strerror(errno)); 0119 } 0120 QVERIFY(QT_LSTAT(QFile::encodeName(path).constData(), &buf) == 0); 0121 QVERIFY(Utils::isLinkMask(buf.st_mode)); 0122 } else { 0123 QVERIFY(Utils::isLinkMask(buf.st_mode)); 0124 } 0125 qDebug("symlink %s created", qPrintable(path)); 0126 QVERIFY(QFileInfo(path).isSymLink()); 0127 } 0128 0129 static void checkTestDirectory(const QString &path) 0130 { 0131 QVERIFY(QFileInfo(path).isDir()); 0132 QVERIFY(QFileInfo(path + "/fileindir").isFile()); 0133 #ifndef Q_OS_WIN 0134 QVERIFY(QFileInfo(path + "/testlink").isSymLink()); 0135 #endif 0136 QVERIFY(QFileInfo(path + "/dirindir").isDir()); 0137 QVERIFY(QFileInfo(path + "/dirindir/nested").isFile()); 0138 } 0139 0140 static void createTestDirectory(const QString &path) 0141 { 0142 QDir dir; 0143 bool ok = dir.mkpath(path); 0144 if (!ok) { 0145 qFatal("couldn't create %s", qPrintable(path)); 0146 } 0147 createTestFile(path + "/fileindir", "File in dir"); 0148 #ifndef Q_OS_WIN 0149 createTestSymlink(path + "/testlink"); 0150 #endif 0151 ok = dir.mkdir(path + "/dirindir"); 0152 if (!ok) { 0153 qFatal("couldn't create %s", qPrintable(path)); 0154 } 0155 createTestFile(path + "/dirindir/nested", "Nested"); 0156 checkTestDirectory(path); 0157 } 0158 0159 class TestUiInterface : public FileUndoManager::UiInterface 0160 { 0161 public: 0162 TestUiInterface() 0163 : FileUndoManager::UiInterface() 0164 , m_mockAskUserInterface(new MockAskUserInterface) 0165 { 0166 setShowProgressInfo(false); 0167 } 0168 void jobError(KIO::Job *job) override 0169 { 0170 m_errorCode = job->error(); 0171 qWarning() << job->errorString(); 0172 } 0173 bool copiedFileWasModified(const QUrl &src, const QUrl &dest, const QDateTime &srcTime, const QDateTime &destTime) override 0174 { 0175 Q_UNUSED(src); 0176 m_dest = dest; 0177 Q_UNUSED(srcTime); 0178 Q_UNUSED(destTime); 0179 return true; 0180 } 0181 bool confirmDeletion(const QList<QUrl> &) override 0182 { 0183 Q_ASSERT(false); // no longer called 0184 return false; 0185 } 0186 void setNextReplyToConfirmDeletion(bool b) 0187 { 0188 m_nextReplyToConfirmDeletion = b; 0189 } 0190 QUrl dest() const 0191 { 0192 return m_dest; 0193 } 0194 int errorCode() const 0195 { 0196 return m_errorCode; 0197 } 0198 void clear() 0199 { 0200 m_dest = QUrl(); 0201 m_errorCode = 0; 0202 m_mockAskUserInterface->clear(); 0203 } 0204 MockAskUserInterface *askUserMockInterface() const 0205 { 0206 return m_mockAskUserInterface.get(); 0207 } 0208 void virtual_hook(int id, void *data) override 0209 { 0210 if (id == HookGetAskUserActionInterface) { 0211 AskUserActionInterface **p = static_cast<AskUserActionInterface **>(data); 0212 *p = m_mockAskUserInterface.get(); 0213 m_mockAskUserInterface->m_deleteResult = m_nextReplyToConfirmDeletion; 0214 } 0215 } 0216 0217 private: 0218 QUrl m_dest; 0219 std::unique_ptr<MockAskUserInterface> m_mockAskUserInterface; 0220 int m_errorCode = 0; 0221 bool m_nextReplyToConfirmDeletion = true; 0222 }; 0223 0224 void FileUndoManagerTest::initTestCase() 0225 { 0226 qDebug("initTestCase"); 0227 0228 QStandardPaths::setTestModeEnabled(true); 0229 0230 // Get kio_trash to share our environment so that it writes trashrc to the right kdehome 0231 qputenv("KIOSLAVE_ENABLE_TESTMODE", "1"); 0232 0233 // Start with a clean base dir 0234 cleanupTestCase(); 0235 0236 if (!QFile::exists(homeTmpDir())) { 0237 bool ok = QDir().mkpath(homeTmpDir()); 0238 if (!ok) { 0239 qFatal("Couldn't create %s", qPrintable(homeTmpDir())); 0240 } 0241 } 0242 0243 createTestFile(srcFile(), "Hello world"); 0244 #ifndef Q_OS_WIN 0245 createTestSymlink(srcLink()); 0246 #endif 0247 createTestDirectory(srcSubDir()); 0248 0249 QDir().mkpath(destDir()); 0250 QVERIFY(QFileInfo(destDir()).isDir()); 0251 0252 QVERIFY(!FileUndoManager::self()->isUndoAvailable()); 0253 m_uiInterface = new TestUiInterface; // owned by FileUndoManager 0254 FileUndoManager::self()->setUiInterface(m_uiInterface); 0255 } 0256 0257 void FileUndoManagerTest::cleanupTestCase() 0258 { 0259 KIO::Job *job = KIO::del(QUrl::fromLocalFile(homeTmpDir()), KIO::HideProgressInfo); 0260 job->exec(); 0261 } 0262 0263 void FileUndoManagerTest::doUndo() 0264 { 0265 QEventLoop eventLoop; 0266 connect(FileUndoManager::self(), &FileUndoManager::undoJobFinished, &eventLoop, &QEventLoop::quit); 0267 FileUndoManager::self()->undo(); 0268 eventLoop.exec(QEventLoop::ExcludeUserInputEvents); // wait for undo job to finish 0269 } 0270 0271 void FileUndoManagerTest::testCopyFiles() 0272 { 0273 // Initially inspired from JobTest::copyFileToSamePartition() 0274 const QString destdir = destDir(); 0275 QList<QUrl> lst = sourceList(); 0276 const QUrl d = QUrl::fromLocalFile(destdir); 0277 KIO::CopyJob *job = KIO::copy(lst, d, KIO::HideProgressInfo); 0278 job->setUiDelegate(nullptr); 0279 FileUndoManager::self()->recordCopyJob(job); 0280 0281 QSignalSpy spyUndoAvailable(FileUndoManager::self(), qOverload<bool>(&FileUndoManager::undoAvailable)); 0282 QVERIFY(spyUndoAvailable.isValid()); 0283 QSignalSpy spyTextChanged(FileUndoManager::self(), &FileUndoManager::undoTextChanged); 0284 QVERIFY(spyTextChanged.isValid()); 0285 0286 QVERIFY2(job->exec(), qPrintable(job->errorString())); 0287 0288 QVERIFY(QFile::exists(destFile())); 0289 #ifndef Q_OS_WIN 0290 // Don't use QFile::exists, it's a broken symlink... 0291 QVERIFY(QFileInfo(destLink()).isSymLink()); 0292 #endif 0293 0294 // might have to wait for dbus signal here... but this is currently disabled. 0295 // QTest::qWait( 20 ); 0296 QVERIFY(FileUndoManager::self()->isUndoAvailable()); 0297 QCOMPARE(spyUndoAvailable.count(), 1); 0298 QCOMPARE(spyTextChanged.count(), 1); 0299 m_uiInterface->clear(); 0300 0301 m_uiInterface->setNextReplyToConfirmDeletion(false); // act like the user didn't confirm 0302 FileUndoManager::self()->undo(); 0303 auto *lastMock = m_uiInterface->askUserMockInterface(); 0304 QCOMPARE(lastMock->m_askUserDeleteCalled, 1); 0305 QVERIFY(QFile::exists(destFile())); // nothing happened yet 0306 0307 // OK, now do it 0308 m_uiInterface->clear(); 0309 m_uiInterface->setNextReplyToConfirmDeletion(true); 0310 doUndo(); 0311 0312 QVERIFY(!FileUndoManager::self()->isUndoAvailable()); 0313 QVERIFY(spyUndoAvailable.count() >= 2); // it's in fact 3, due to lock/unlock emitting it as well 0314 QCOMPARE(spyTextChanged.count(), 2); 0315 QCOMPARE(m_uiInterface->askUserMockInterface()->m_askUserDeleteCalled, 1); 0316 0317 // Check that undo worked 0318 QVERIFY(!QFile::exists(destFile())); 0319 #ifndef Q_OS_WIN 0320 QVERIFY(!QFile::exists(destLink())); 0321 QVERIFY(!QFileInfo(destLink()).isSymLink()); 0322 #endif 0323 } 0324 0325 void FileUndoManagerTest::testMoveFiles() 0326 { 0327 const QString destdir = destDir(); 0328 QList<QUrl> lst = sourceList(); 0329 const QUrl d = QUrl::fromLocalFile(destdir); 0330 KIO::CopyJob *job = KIO::move(lst, d, KIO::HideProgressInfo); 0331 job->setUiDelegate(nullptr); 0332 FileUndoManager::self()->recordCopyJob(job); 0333 0334 QVERIFY2(job->exec(), qPrintable(job->errorString())); 0335 0336 QVERIFY(!QFile::exists(srcFile())); // the source moved 0337 QVERIFY(QFile::exists(destFile())); 0338 #ifndef Q_OS_WIN 0339 QVERIFY(!QFileInfo(srcLink()).isSymLink()); 0340 // Don't use QFile::exists, it's a broken symlink... 0341 QVERIFY(QFileInfo(destLink()).isSymLink()); 0342 #endif 0343 0344 doUndo(); 0345 0346 QVERIFY(QFile::exists(srcFile())); // the source is back 0347 QVERIFY(!QFile::exists(destFile())); 0348 #ifndef Q_OS_WIN 0349 QVERIFY(QFileInfo(srcLink()).isSymLink()); 0350 QVERIFY(!QFileInfo(destLink()).isSymLink()); 0351 #endif 0352 } 0353 0354 void FileUndoManagerTest::testCopyDirectory() 0355 { 0356 const QString destdir = destDir(); 0357 QList<QUrl> lst; 0358 lst << QUrl::fromLocalFile(srcSubDir()); 0359 const QUrl d = QUrl::fromLocalFile(destdir); 0360 KIO::CopyJob *job = KIO::copy(lst, d, KIO::HideProgressInfo); 0361 job->setUiDelegate(nullptr); 0362 FileUndoManager::self()->recordCopyJob(job); 0363 0364 QVERIFY2(job->exec(), qPrintable(job->errorString())); 0365 0366 checkTestDirectory(srcSubDir()); // src untouched 0367 checkTestDirectory(destSubDir()); 0368 0369 doUndo(); 0370 0371 checkTestDirectory(srcSubDir()); 0372 QVERIFY(!QFile::exists(destSubDir())); 0373 } 0374 0375 void FileUndoManagerTest::testCopyEmptyDirectory() 0376 { 0377 const QString src = srcSubDir() + "/.emptydir"; 0378 const QString destEmptyDir = destDir() + "/.emptydir"; 0379 QDir().mkpath(src); 0380 KIO::CopyJob *job = KIO::copy({QUrl::fromLocalFile(src)}, QUrl::fromLocalFile(destEmptyDir), KIO::HideProgressInfo); 0381 job->setUiDelegate(nullptr); 0382 FileUndoManager::self()->recordCopyJob(job); 0383 0384 QVERIFY2(job->exec(), qPrintable(job->errorString())); 0385 0386 QVERIFY(QFileInfo(src).isDir()); // untouched 0387 QVERIFY(QFileInfo(destEmptyDir).isDir()); 0388 0389 doUndo(); 0390 0391 QVERIFY(QFileInfo(src).isDir()); // untouched 0392 QVERIFY(!QFile::exists(destEmptyDir)); 0393 } 0394 0395 void FileUndoManagerTest::testMoveDirectory() 0396 { 0397 const QString destdir = destDir(); 0398 QList<QUrl> lst; 0399 lst << QUrl::fromLocalFile(srcSubDir()); 0400 const QUrl d = QUrl::fromLocalFile(destdir); 0401 KIO::CopyJob *job = KIO::move(lst, d, KIO::HideProgressInfo); 0402 job->setUiDelegate(nullptr); 0403 FileUndoManager::self()->recordCopyJob(job); 0404 0405 QVERIFY2(job->exec(), qPrintable(job->errorString())); 0406 0407 QVERIFY(!QFile::exists(srcSubDir())); 0408 checkTestDirectory(destSubDir()); 0409 0410 doUndo(); 0411 0412 checkTestDirectory(srcSubDir()); 0413 QVERIFY(!QFile::exists(destSubDir())); 0414 } 0415 0416 void FileUndoManagerTest::testRenameFile() 0417 { 0418 const QUrl oldUrl = QUrl::fromLocalFile(srcFile()); 0419 const QUrl newUrl = QUrl::fromLocalFile(srcFile() + ".new"); 0420 QList<QUrl> lst; 0421 lst.append(oldUrl); 0422 QSignalSpy spyUndoAvailable(FileUndoManager::self(), qOverload<bool>(&FileUndoManager::undoAvailable)); 0423 QVERIFY(spyUndoAvailable.isValid()); 0424 KIO::Job *job = KIO::moveAs(oldUrl, newUrl, KIO::HideProgressInfo); 0425 job->setUiDelegate(nullptr); 0426 FileUndoManager::self()->recordJob(FileUndoManager::Rename, lst, newUrl, job); 0427 0428 QVERIFY2(job->exec(), qPrintable(job->errorString())); 0429 0430 QVERIFY(!QFile::exists(srcFile())); 0431 QVERIFY(QFileInfo(newUrl.toLocalFile()).isFile()); 0432 QCOMPARE(spyUndoAvailable.count(), 1); 0433 0434 doUndo(); 0435 0436 QVERIFY(QFile::exists(srcFile())); 0437 QVERIFY(!QFileInfo(newUrl.toLocalFile()).isFile()); 0438 } 0439 0440 void FileUndoManagerTest::testRenameDir() 0441 { 0442 const QUrl oldUrl = QUrl::fromLocalFile(srcSubDir()); 0443 const QUrl newUrl = QUrl::fromLocalFile(srcSubDir() + ".new"); 0444 QList<QUrl> lst; 0445 lst.append(oldUrl); 0446 KIO::Job *job = KIO::moveAs(oldUrl, newUrl, KIO::HideProgressInfo); 0447 job->setUiDelegate(nullptr); 0448 FileUndoManager::self()->recordJob(FileUndoManager::Rename, lst, newUrl, job); 0449 0450 QVERIFY2(job->exec(), qPrintable(job->errorString())); 0451 0452 QVERIFY(!QFile::exists(srcSubDir())); 0453 QVERIFY(QFileInfo(newUrl.toLocalFile()).isDir()); 0454 0455 doUndo(); 0456 0457 QVERIFY(QFile::exists(srcSubDir())); 0458 QVERIFY(!QFileInfo(newUrl.toLocalFile()).isDir()); 0459 } 0460 0461 void FileUndoManagerTest::testCreateSymlink() 0462 { 0463 #ifdef Q_OS_WIN 0464 QSKIP("Test skipped on Windows for lack of proper symlink support"); 0465 #endif 0466 const QUrl link = QUrl::fromLocalFile(homeTmpDir() + "newlink"); 0467 const QString path = link.toLocalFile(); 0468 QVERIFY(!QFile::exists(path)); 0469 0470 const QUrl target = QUrl::fromLocalFile(homeTmpDir() + "linktarget"); 0471 const QString targetPath = target.toLocalFile(); 0472 createTestFile(targetPath, "Link's Target"); 0473 QVERIFY(QFile::exists(targetPath)); 0474 0475 KIO::CopyJob *job = KIO::link(target, link, KIO::HideProgressInfo); 0476 job->setUiDelegate(nullptr); 0477 FileUndoManager::self()->recordCopyJob(job); 0478 QVERIFY2(job->exec(), qPrintable(job->errorString())); 0479 QVERIFY(QFile::exists(path)); 0480 QVERIFY(QFileInfo(path).isSymLink()); 0481 0482 // For undoing symlinks no confirmation is required. We delete it straight away. 0483 doUndo(); 0484 0485 QVERIFY(!QFile::exists(path)); 0486 } 0487 0488 void FileUndoManagerTest::testCreateDir() 0489 { 0490 const QUrl url = QUrl::fromLocalFile(srcSubDir() + ".mkdir"); 0491 const QString path = url.toLocalFile(); 0492 QVERIFY(!QFile::exists(path)); 0493 0494 KIO::SimpleJob *job = KIO::mkdir(url); 0495 job->setUiDelegate(nullptr); 0496 FileUndoManager::self()->recordJob(FileUndoManager::Mkdir, QList<QUrl>(), url, job); 0497 QVERIFY2(job->exec(), qPrintable(job->errorString())); 0498 QVERIFY(QFile::exists(path)); 0499 QVERIFY(QFileInfo(path).isDir()); 0500 0501 m_uiInterface->clear(); 0502 m_uiInterface->setNextReplyToConfirmDeletion(false); // act like the user didn't confirm 0503 FileUndoManager::self()->undo(); 0504 QCOMPARE(m_uiInterface->askUserMockInterface()->m_askUserDeleteCalled, 1); 0505 QVERIFY(QFile::exists(path)); // nothing happened yet 0506 0507 // OK, now do it 0508 m_uiInterface->clear(); 0509 m_uiInterface->setNextReplyToConfirmDeletion(true); 0510 doUndo(); 0511 0512 QVERIFY(!QFile::exists(path)); 0513 } 0514 0515 void FileUndoManagerTest::testMkpath() 0516 { 0517 const QString parent = srcSubDir() + "mkpath"; 0518 const QString path = parent + "/subdir"; 0519 QVERIFY(!QFile::exists(path)); 0520 const QUrl url = QUrl::fromLocalFile(path); 0521 0522 KIO::Job *job = KIO::mkpath(url, QUrl(), KIO::HideProgressInfo); 0523 job->setUiDelegate(nullptr); 0524 FileUndoManager::self()->recordJob(FileUndoManager::Mkpath, QList<QUrl>(), url, job); 0525 QVERIFY(job->exec()); 0526 QVERIFY(QFileInfo(path).isDir()); 0527 0528 m_uiInterface->clear(); 0529 m_uiInterface->setNextReplyToConfirmDeletion(true); 0530 doUndo(); 0531 0532 QVERIFY(!FileUndoManager::self()->isUndoAvailable()); 0533 QCOMPARE(m_uiInterface->askUserMockInterface()->m_askUserDeleteCalled, 1); 0534 0535 QVERIFY(!QFile::exists(path)); 0536 } 0537 0538 void FileUndoManagerTest::testTrashFiles() 0539 { 0540 if (!KProtocolInfo::isKnownProtocol(QStringLiteral("trash"))) { 0541 QSKIP("kio_trash not installed"); 0542 } 0543 0544 // Trash it all at once: the file, the symlink, the subdir. 0545 QList<QUrl> lst = sourceList(); 0546 lst.append(QUrl::fromLocalFile(srcSubDir())); 0547 KIO::Job *job = KIO::trash(lst, KIO::HideProgressInfo); 0548 job->setUiDelegate(nullptr); 0549 FileUndoManager::self()->recordJob(FileUndoManager::Trash, lst, QUrl(QStringLiteral("trash:/")), job); 0550 0551 QVERIFY2(job->exec(), qPrintable(job->errorString())); 0552 0553 // Check that things got removed 0554 QVERIFY(!QFile::exists(srcFile())); 0555 #ifndef Q_OS_WIN 0556 QVERIFY(!QFileInfo(srcLink()).isSymLink()); 0557 #endif 0558 QVERIFY(!QFile::exists(srcSubDir())); 0559 0560 // check trash? 0561 // Let's just check that it's not empty. kio_trash has its own unit tests anyway. 0562 KConfig cfg(QStringLiteral("trashrc"), KConfig::SimpleConfig); 0563 QVERIFY(cfg.hasGroup("Status")); 0564 QCOMPARE(cfg.group("Status").readEntry("Empty", true), false); 0565 0566 doUndo(); 0567 0568 QVERIFY(QFile::exists(srcFile())); 0569 #ifndef Q_OS_WIN 0570 QVERIFY(QFileInfo(srcLink()).isSymLink()); 0571 #endif 0572 QVERIFY(QFile::exists(srcSubDir())); 0573 0574 // We can't check that the trash is empty; other partitions might have their own trash 0575 } 0576 0577 void FileUndoManagerTest::testRestoreTrashedFiles() 0578 { 0579 if (!KProtocolInfo::isKnownProtocol(QStringLiteral("trash"))) { 0580 QSKIP("kio_trash not installed"); 0581 } 0582 0583 // Trash it all at once: the file, the symlink, the subdir. 0584 const QFile::Permissions origPerms = QFileInfo(srcFile()).permissions(); 0585 QList<QUrl> lst = sourceList(); 0586 lst.append(QUrl::fromLocalFile(srcSubDir())); 0587 KIO::Job *job = KIO::trash(lst, KIO::HideProgressInfo); 0588 job->setUiDelegate(nullptr); 0589 QVERIFY(job->exec()); 0590 0591 const QMap<QString, QString> metaData = job->metaData(); 0592 QList<QUrl> trashUrls; 0593 for (const QUrl &src : std::as_const(lst)) { 0594 QMap<QString, QString>::ConstIterator it = metaData.find("trashURL-" + src.path()); 0595 QVERIFY(it != metaData.constEnd()); 0596 trashUrls.append(QUrl(it.value())); 0597 } 0598 0599 // Restore from trash 0600 KIO::RestoreJob *restoreJob = KIO::restoreFromTrash(trashUrls, KIO::HideProgressInfo); 0601 restoreJob->setUiDelegate(nullptr); 0602 QVERIFY(restoreJob->exec()); 0603 0604 QVERIFY(QFile::exists(srcFile())); 0605 QCOMPARE(QFileInfo(srcFile()).permissions(), origPerms); 0606 #ifndef Q_OS_WIN 0607 QVERIFY(QFileInfo(srcLink()).isSymLink()); 0608 #endif 0609 QVERIFY(QFile::exists(srcSubDir())); 0610 0611 // TODO support for RestoreJob in FileUndoManager !!! 0612 } 0613 0614 static void setTimeStamp(const QString &path) 0615 { 0616 #ifdef Q_OS_UNIX 0617 // Put timestamp in the past so that we can check that the 0618 // copy actually preserves it. 0619 struct timeval tp; 0620 gettimeofday(&tp, nullptr); 0621 struct utimbuf utbuf; 0622 utbuf.actime = tp.tv_sec + 30; // 30 seconds in the future 0623 utbuf.modtime = tp.tv_sec + 60; // 60 second in the future 0624 utime(QFile::encodeName(path).constData(), &utbuf); 0625 qDebug("Time changed for %s", qPrintable(path)); 0626 #endif 0627 } 0628 0629 void FileUndoManagerTest::testModifyFileBeforeUndo() 0630 { 0631 // based on testCopyDirectory (so that we check that it works for files in subdirs too) 0632 const QString destdir = destDir(); 0633 const QList<QUrl> lst{QUrl::fromLocalFile(srcSubDir())}; 0634 const QUrl dest = QUrl::fromLocalFile(destdir); 0635 KIO::CopyJob *job = KIO::copy(lst, dest, KIO::HideProgressInfo); 0636 job->setUiDelegate(nullptr); 0637 FileUndoManager::self()->recordCopyJob(job); 0638 0639 QVERIFY2(job->exec(), qPrintable(job->errorString())); 0640 0641 checkTestDirectory(srcSubDir()); // src untouched 0642 checkTestDirectory(destSubDir()); 0643 const QString destFile = destSubDir() + "/fileindir"; 0644 setTimeStamp(destFile); // simulate a modification of the file 0645 0646 doUndo(); 0647 0648 // Check that TestUiInterface::copiedFileWasModified got called 0649 QCOMPARE(m_uiInterface->dest().toLocalFile(), destFile); 0650 0651 checkTestDirectory(srcSubDir()); 0652 QVERIFY(!QFile::exists(destSubDir())); 0653 } 0654 0655 void FileUndoManagerTest::testPasteClipboardUndo() 0656 { 0657 const QList<QUrl> urls(sourceList()); 0658 QMimeData *mimeData = new QMimeData(); 0659 mimeData->setUrls(urls); 0660 KIO::setClipboardDataCut(mimeData, true); 0661 QClipboard *clipboard = QApplication::clipboard(); 0662 clipboard->setMimeData(mimeData); 0663 0664 // Paste the contents of the clipboard and check its status 0665 QUrl destDirUrl = QUrl::fromLocalFile(destDir()); 0666 KIO::Job *job = KIO::paste(mimeData, destDirUrl, KIO::HideProgressInfo); 0667 QVERIFY(job); 0668 QVERIFY(job->exec()); 0669 0670 // Check if the clipboard was updated after paste operation 0671 QList<QUrl> urls2; 0672 for (const QUrl &url : urls) { 0673 QUrl dUrl = destDirUrl.adjusted(QUrl::StripTrailingSlash); 0674 dUrl.setPath(dUrl.path() + '/' + url.fileName()); 0675 urls2 << dUrl; 0676 } 0677 QList<QUrl> clipboardUrls = KUrlMimeData::urlsFromMimeData(clipboard->mimeData()); 0678 QCOMPARE(clipboardUrls, urls2); 0679 0680 // Check if the clipboard was updated after undo operation 0681 doUndo(); 0682 clipboardUrls = KUrlMimeData::urlsFromMimeData(clipboard->mimeData()); 0683 QCOMPARE(clipboardUrls, urls); 0684 } 0685 0686 void FileUndoManagerTest::testBatchRename() 0687 { 0688 auto createUrl = [](const QString &path) -> QUrl { 0689 return QUrl::fromLocalFile(homeTmpDir() + path); 0690 }; 0691 0692 QList<QUrl> srcList; 0693 srcList << createUrl("textfile.txt") << createUrl("mediafile.mkv") << createUrl("sourcefile.cpp"); 0694 0695 createTestFile(srcList.at(0).path(), "foo"); 0696 createTestFile(srcList.at(1).path(), "foo"); 0697 createTestFile(srcList.at(2).path(), "foo"); 0698 0699 KIO::Job *job = KIO::batchRename(srcList, QLatin1String("newfile###"), 1, QLatin1Char('#'), KIO::HideProgressInfo); 0700 job->setUiDelegate(nullptr); 0701 FileUndoManager::self()->recordJob(FileUndoManager::BatchRename, srcList, QUrl(), job); 0702 QVERIFY2(job->exec(), qPrintable(job->errorString())); 0703 0704 QVERIFY(QFile::exists(createUrl("newfile001.txt").path())); 0705 QVERIFY(QFile::exists(createUrl("newfile002.mkv").path())); 0706 QVERIFY(QFile::exists(createUrl("newfile003.cpp").path())); 0707 QVERIFY(!QFile::exists(srcList.at(0).path())); 0708 QVERIFY(!QFile::exists(srcList.at(1).path())); 0709 QVERIFY(!QFile::exists(srcList.at(2).path())); 0710 0711 doUndo(); 0712 0713 QVERIFY(!QFile::exists(createUrl("newfile###.txt").path())); 0714 QVERIFY(!QFile::exists(createUrl("newfile###.mkv").path())); 0715 QVERIFY(!QFile::exists(createUrl("newfile###.cpp").path())); 0716 QVERIFY(QFile::exists(srcList.at(0).path())); 0717 QVERIFY(QFile::exists(srcList.at(1).path())); 0718 QVERIFY(QFile::exists(srcList.at(2).path())); 0719 } 0720 0721 void FileUndoManagerTest::testUndoCopyOfDeletedFile() 0722 { 0723 const QUrl source = QUrl::fromLocalFile(homeTmpDir() + QLatin1String("source.txt")); 0724 const QUrl dest = QUrl::fromLocalFile(homeTmpDir() + QLatin1String("copy.txt")); 0725 0726 createTestFile(source.toLocalFile(), "foo"); 0727 QVERIFY(QFileInfo::exists(source.toLocalFile())); 0728 0729 { 0730 auto copyJob = KIO::copy(source, dest, KIO::HideProgressInfo); 0731 copyJob->setUiDelegate(nullptr); 0732 FileUndoManager::self()->recordCopyJob(copyJob); 0733 QVERIFY2(copyJob->exec(), qPrintable(copyJob->errorString())); 0734 QVERIFY(QFileInfo::exists(dest.toLocalFile())); 0735 } 0736 0737 { 0738 auto deleteJob = KIO::del(dest, KIO::HideProgressInfo); 0739 deleteJob->setUiDelegate(nullptr); 0740 QVERIFY2(deleteJob->exec(), qPrintable(deleteJob->errorString())); 0741 QVERIFY(!QFileInfo::exists(dest.toLocalFile())); 0742 } 0743 0744 QVERIFY(FileUndoManager::self()->isUndoAvailable()); 0745 QSignalSpy spyUndoAvailable(FileUndoManager::self(), qOverload<bool>(&FileUndoManager::undoAvailable)); 0746 QVERIFY(spyUndoAvailable.isValid()); 0747 doUndo(); 0748 QVERIFY(spyUndoAvailable.count() >= 2); // it's in fact 3, due to lock/unlock emitting it as well 0749 QVERIFY(!spyUndoAvailable.at(0).at(0).toBool()); 0750 QVERIFY(!FileUndoManager::self()->isUndoAvailable()); 0751 } 0752 0753 void FileUndoManagerTest::testErrorDuringMoveUndo() 0754 { 0755 const QString destdir = destDir(); 0756 QList<QUrl> lst{QUrl::fromLocalFile(srcFile())}; 0757 KIO::CopyJob *job = KIO::move(lst, QUrl::fromLocalFile(destdir), KIO::HideProgressInfo); 0758 job->setUiDelegate(nullptr); 0759 FileUndoManager::self()->recordCopyJob(job); 0760 0761 QVERIFY2(job->exec(), qPrintable(job->errorString())); 0762 0763 QVERIFY(!QFile::exists(srcFile())); // the source moved 0764 QVERIFY(QFile::exists(destFile())); 0765 createTestFile(srcFile(), "I'm back"); 0766 0767 doUndo(); 0768 0769 QCOMPARE(m_uiInterface->errorCode(), KIO::ERR_FILE_ALREADY_EXIST); 0770 QVERIFY(QFile::exists(destFile())); // still there 0771 } 0772 0773 void FileUndoManagerTest::testNoUndoForSkipAll() 0774 { 0775 auto *undoManager = FileUndoManager::self(); 0776 0777 QTemporaryDir tempDir; 0778 const QString tempPath = tempDir.path(); 0779 0780 const QString destPath = tempPath + "/dest_dir"; 0781 QVERIFY(QDir(tempPath).mkdir("dest_dir")); 0782 const QUrl destUrl = QUrl::fromLocalFile(destPath); 0783 0784 const QList<QUrl> lst{QUrl::fromLocalFile(tempPath + "/file_a"), QUrl::fromLocalFile(tempPath + "/file_b")}; 0785 for (const auto &url : lst) { 0786 createTestFile(url.toLocalFile(), "foo"); 0787 } 0788 0789 auto createJob = [&]() { 0790 return KIO::copy(lst, destUrl, KIO::HideProgressInfo); 0791 }; 0792 0793 KIO::CopyJob *job = createJob(); 0794 job->setUiDelegate(nullptr); 0795 undoManager->recordCopyJob(job); 0796 0797 QSignalSpy spyUndoAvailable(undoManager, qOverload<bool>(&FileUndoManager::undoAvailable)); 0798 QVERIFY(spyUndoAvailable.isValid()); 0799 QSignalSpy spyTextChanged(undoManager, &FileUndoManager::undoTextChanged); 0800 QVERIFY(spyTextChanged.isValid()); 0801 0802 QVERIFY2(job->exec(), qPrintable(job->errorString())); 0803 0804 // Src files still exist 0805 for (const auto &url : lst) { 0806 QVERIFY(QFile::exists(url.toLocalFile())); 0807 } 0808 0809 // Files copied to dest 0810 for (const auto &url : lst) { 0811 QVERIFY(QFile::exists(destPath + '/' + url.fileName())); 0812 } 0813 0814 // An undo command was recorded 0815 QCOMPARE(spyUndoAvailable.count(), 1); 0816 QCOMPARE(spyTextChanged.count(), 1); 0817 0818 KIO::CopyJob *repeatCopy = createJob(); 0819 // Copying the same files again to the same dest, and setting skip all 0820 repeatCopy->setAutoSkip(true); 0821 undoManager->recordCopyJob(repeatCopy); 0822 0823 QVERIFY2(repeatCopy->exec(), qPrintable(repeatCopy->errorString())); 0824 0825 // No new undo command was added since the job didn't actually copy anything 0826 QCOMPARE(spyUndoAvailable.count(), 1); 0827 QCOMPARE(spyTextChanged.count(), 1); 0828 } 0829 0830 // TODO: add test (and fix bug) for DND of remote urls / "Link here" (creates .desktop files) // Undo (doesn't do anything) 0831 // TODO: add test for interrupting a moving operation and then using Undo - bug:91579