Advance Wayland and KDE package bring-up
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -0,0 +1,828 @@
|
||||
/*
|
||||
This file is part of KDE
|
||||
SPDX-FileCopyrightText: 2006, 2008 David Faure <faure@kde.org>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include "fileundomanagertest.h"
|
||||
|
||||
#include "../src/utils_p.h"
|
||||
|
||||
#include "mockcoredelegateextensions.h"
|
||||
#include <kio/batchrenamejob.h>
|
||||
#include <kio/copyjob.h>
|
||||
#include <kio/deletejob.h>
|
||||
#include <kio/fileundomanager.h>
|
||||
#include <kio/mkdirjob.h>
|
||||
#include <kio/mkpathjob.h>
|
||||
#include <kio/paste.h>
|
||||
#include <kio/pastejob.h>
|
||||
#include <kio/restorejob.h>
|
||||
#include <kioglobal_p.h>
|
||||
#include <kprotocolinfo.h>
|
||||
|
||||
#include <QApplication>
|
||||
#include <QClipboard>
|
||||
#include <QDateTime>
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
#include <QMimeData>
|
||||
#include <QSignalSpy>
|
||||
#include <QTest>
|
||||
#include <qplatformdefs.h>
|
||||
|
||||
#include <KConfig>
|
||||
#include <KConfigGroup>
|
||||
#include <KUrlMimeData>
|
||||
|
||||
#include <cerrno>
|
||||
#include <time.h>
|
||||
#ifdef Q_OS_WIN
|
||||
#include <sys/utime.h>
|
||||
#else
|
||||
#include <sys/time.h>
|
||||
#include <utime.h>
|
||||
#endif
|
||||
|
||||
QTEST_MAIN(FileUndoManagerTest)
|
||||
|
||||
using namespace KIO;
|
||||
|
||||
static QString homeTmpDir()
|
||||
{
|
||||
return QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QDir::separator();
|
||||
}
|
||||
static QString destDir()
|
||||
{
|
||||
return homeTmpDir() + "destdir/";
|
||||
}
|
||||
|
||||
static QString srcFile()
|
||||
{
|
||||
return homeTmpDir() + "testfile";
|
||||
}
|
||||
static QString destFile()
|
||||
{
|
||||
return destDir() + "testfile";
|
||||
}
|
||||
|
||||
#ifndef Q_OS_WIN
|
||||
static QString srcLink()
|
||||
{
|
||||
return homeTmpDir() + "symlink";
|
||||
}
|
||||
static QString destLink()
|
||||
{
|
||||
return destDir() + "symlink";
|
||||
}
|
||||
#endif
|
||||
|
||||
static QString srcSubDir()
|
||||
{
|
||||
return homeTmpDir() + "subdir";
|
||||
}
|
||||
static QString destSubDir()
|
||||
{
|
||||
return destDir() + "subdir";
|
||||
}
|
||||
|
||||
static QList<QUrl> sourceList()
|
||||
{
|
||||
QList<QUrl> lst;
|
||||
lst << QUrl::fromLocalFile(srcFile());
|
||||
#ifndef Q_OS_WIN
|
||||
lst << QUrl::fromLocalFile(srcLink());
|
||||
#endif
|
||||
return lst;
|
||||
}
|
||||
|
||||
static void createTestFile(const QString &path, const char *contents)
|
||||
{
|
||||
QFile f(path);
|
||||
if (!f.open(QIODevice::WriteOnly)) {
|
||||
qFatal("Couldn't create %s", qPrintable(path));
|
||||
}
|
||||
f.write(QByteArray(contents));
|
||||
f.close();
|
||||
}
|
||||
|
||||
static void createTestSymlink(const QString &path)
|
||||
{
|
||||
// Create symlink if it doesn't exist yet
|
||||
QT_STATBUF buf;
|
||||
if (QT_LSTAT(QFile::encodeName(path).constData(), &buf) != 0) {
|
||||
bool ok = KIOPrivate::createSymlink(QStringLiteral("/IDontExist"), path); // broken symlink
|
||||
if (!ok) {
|
||||
qFatal("couldn't create symlink: %s", strerror(errno));
|
||||
}
|
||||
QVERIFY(QT_LSTAT(QFile::encodeName(path).constData(), &buf) == 0);
|
||||
QVERIFY(Utils::isLinkMask(buf.st_mode));
|
||||
} else {
|
||||
QVERIFY(Utils::isLinkMask(buf.st_mode));
|
||||
}
|
||||
qDebug("symlink %s created", qPrintable(path));
|
||||
QVERIFY(QFileInfo(path).isSymLink());
|
||||
}
|
||||
|
||||
static void checkTestDirectory(const QString &path)
|
||||
{
|
||||
QVERIFY(QFileInfo(path).isDir());
|
||||
QVERIFY(QFileInfo(path + "/fileindir").isFile());
|
||||
#ifndef Q_OS_WIN
|
||||
QVERIFY(QFileInfo(path + "/testlink").isSymLink());
|
||||
#endif
|
||||
QVERIFY(QFileInfo(path + "/dirindir").isDir());
|
||||
QVERIFY(QFileInfo(path + "/dirindir/nested").isFile());
|
||||
}
|
||||
|
||||
static void createTestDirectory(const QString &path)
|
||||
{
|
||||
QDir dir;
|
||||
bool ok = dir.mkpath(path);
|
||||
if (!ok) {
|
||||
qFatal("couldn't create %s", qPrintable(path));
|
||||
}
|
||||
createTestFile(path + "/fileindir", "File in dir");
|
||||
#ifndef Q_OS_WIN
|
||||
createTestSymlink(path + "/testlink");
|
||||
#endif
|
||||
ok = dir.mkdir(path + "/dirindir");
|
||||
if (!ok) {
|
||||
qFatal("couldn't create %s", qPrintable(path));
|
||||
}
|
||||
createTestFile(path + "/dirindir/nested", "Nested");
|
||||
checkTestDirectory(path);
|
||||
}
|
||||
|
||||
class TestUiInterface : public FileUndoManager::UiInterface
|
||||
{
|
||||
public:
|
||||
TestUiInterface()
|
||||
: FileUndoManager::UiInterface()
|
||||
, m_mockAskUserInterface(new MockAskUserInterface)
|
||||
{
|
||||
setShowProgressInfo(false);
|
||||
}
|
||||
void jobError(KIO::Job *job) override
|
||||
{
|
||||
m_errorCode = job->error();
|
||||
qWarning() << job->errorString();
|
||||
}
|
||||
bool copiedFileWasModified(const QUrl &src, const QUrl &dest, const QDateTime &srcTime, const QDateTime &destTime) override
|
||||
{
|
||||
Q_UNUSED(src);
|
||||
m_dest = dest;
|
||||
Q_UNUSED(srcTime);
|
||||
Q_UNUSED(destTime);
|
||||
return true;
|
||||
}
|
||||
void setNextReplyToConfirmDeletion(bool b)
|
||||
{
|
||||
m_nextReplyToConfirmDeletion = b;
|
||||
}
|
||||
QUrl dest() const
|
||||
{
|
||||
return m_dest;
|
||||
}
|
||||
int errorCode() const
|
||||
{
|
||||
return m_errorCode;
|
||||
}
|
||||
void clear()
|
||||
{
|
||||
m_dest = QUrl();
|
||||
m_errorCode = 0;
|
||||
m_mockAskUserInterface->clear();
|
||||
}
|
||||
MockAskUserInterface *askUserMockInterface() const
|
||||
{
|
||||
return m_mockAskUserInterface.get();
|
||||
}
|
||||
void virtual_hook(int id, void *data) override
|
||||
{
|
||||
if (id == HookGetAskUserActionInterface) {
|
||||
AskUserActionInterface **p = static_cast<AskUserActionInterface **>(data);
|
||||
*p = m_mockAskUserInterface.get();
|
||||
m_mockAskUserInterface->m_deleteResult = m_nextReplyToConfirmDeletion;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
QUrl m_dest;
|
||||
std::unique_ptr<MockAskUserInterface> m_mockAskUserInterface;
|
||||
int m_errorCode = 0;
|
||||
bool m_nextReplyToConfirmDeletion = true;
|
||||
};
|
||||
|
||||
void FileUndoManagerTest::initTestCase()
|
||||
{
|
||||
qDebug("initTestCase");
|
||||
|
||||
QStandardPaths::setTestModeEnabled(true);
|
||||
|
||||
// Get kio_trash to share our environment so that it writes trashrc to the right kdehome
|
||||
qputenv("KIOWORKER_ENABLE_TESTMODE", "1");
|
||||
|
||||
// Start with a clean base dir
|
||||
cleanupTestCase();
|
||||
|
||||
if (!QFile::exists(homeTmpDir())) {
|
||||
bool ok = QDir().mkpath(homeTmpDir());
|
||||
if (!ok) {
|
||||
qFatal("Couldn't create %s", qPrintable(homeTmpDir()));
|
||||
}
|
||||
}
|
||||
|
||||
createTestFile(srcFile(), "Hello world");
|
||||
#ifndef Q_OS_WIN
|
||||
createTestSymlink(srcLink());
|
||||
#endif
|
||||
createTestDirectory(srcSubDir());
|
||||
|
||||
QDir().mkpath(destDir());
|
||||
QVERIFY(QFileInfo(destDir()).isDir());
|
||||
|
||||
QVERIFY(!FileUndoManager::self()->isUndoAvailable());
|
||||
m_uiInterface = new TestUiInterface; // owned by FileUndoManager
|
||||
FileUndoManager::self()->setUiInterface(m_uiInterface);
|
||||
}
|
||||
|
||||
void FileUndoManagerTest::cleanupTestCase()
|
||||
{
|
||||
KIO::Job *job = KIO::del(QUrl::fromLocalFile(homeTmpDir()), KIO::HideProgressInfo);
|
||||
job->exec();
|
||||
}
|
||||
|
||||
void FileUndoManagerTest::doUndo()
|
||||
{
|
||||
QEventLoop eventLoop;
|
||||
connect(FileUndoManager::self(), &FileUndoManager::undoJobFinished, &eventLoop, &QEventLoop::quit);
|
||||
FileUndoManager::self()->undo();
|
||||
eventLoop.exec(QEventLoop::ExcludeUserInputEvents); // wait for undo job to finish
|
||||
}
|
||||
|
||||
void FileUndoManagerTest::testCopyFiles()
|
||||
{
|
||||
// Initially inspired from JobTest::copyFileToSamePartition()
|
||||
const QString destdir = destDir();
|
||||
QList<QUrl> lst = sourceList();
|
||||
const QUrl d = QUrl::fromLocalFile(destdir);
|
||||
KIO::CopyJob *job = KIO::copy(lst, d, KIO::HideProgressInfo);
|
||||
job->setUiDelegate(nullptr);
|
||||
FileUndoManager::self()->recordCopyJob(job);
|
||||
|
||||
QSignalSpy spyUndoAvailable(FileUndoManager::self(), &FileUndoManager::undoAvailable);
|
||||
QVERIFY(spyUndoAvailable.isValid());
|
||||
QSignalSpy spyTextChanged(FileUndoManager::self(), &FileUndoManager::undoTextChanged);
|
||||
QVERIFY(spyTextChanged.isValid());
|
||||
|
||||
QVERIFY2(job->exec(), qPrintable(job->errorString()));
|
||||
|
||||
QVERIFY(QFile::exists(destFile()));
|
||||
#ifndef Q_OS_WIN
|
||||
// Don't use QFile::exists, it's a broken symlink...
|
||||
QVERIFY(QFileInfo(destLink()).isSymLink());
|
||||
#endif
|
||||
|
||||
// might have to wait for dbus signal here... but this is currently disabled.
|
||||
// QTest::qWait( 20 );
|
||||
QVERIFY(FileUndoManager::self()->isUndoAvailable());
|
||||
QCOMPARE(spyUndoAvailable.count(), 1);
|
||||
QCOMPARE(spyTextChanged.count(), 1);
|
||||
m_uiInterface->clear();
|
||||
|
||||
m_uiInterface->setNextReplyToConfirmDeletion(false); // act like the user didn't confirm
|
||||
FileUndoManager::self()->undo();
|
||||
auto *lastMock = m_uiInterface->askUserMockInterface();
|
||||
QCOMPARE(lastMock->m_askUserDeleteCalled, 1);
|
||||
QVERIFY(QFile::exists(destFile())); // nothing happened yet
|
||||
|
||||
// OK, now do it
|
||||
m_uiInterface->clear();
|
||||
m_uiInterface->setNextReplyToConfirmDeletion(true);
|
||||
doUndo();
|
||||
|
||||
QVERIFY(!FileUndoManager::self()->isUndoAvailable());
|
||||
QVERIFY(spyUndoAvailable.count() >= 2); // it's in fact 3, due to lock/unlock emitting it as well
|
||||
QCOMPARE(spyTextChanged.count(), 2);
|
||||
QCOMPARE(m_uiInterface->askUserMockInterface()->m_askUserDeleteCalled, 1);
|
||||
|
||||
// Check that undo worked
|
||||
QVERIFY(!QFile::exists(destFile()));
|
||||
#ifndef Q_OS_WIN
|
||||
QVERIFY(!QFile::exists(destLink()));
|
||||
QVERIFY(!QFileInfo(destLink()).isSymLink());
|
||||
#endif
|
||||
}
|
||||
|
||||
void FileUndoManagerTest::testMoveFiles()
|
||||
{
|
||||
const QString destdir = destDir();
|
||||
QList<QUrl> lst = sourceList();
|
||||
const QUrl d = QUrl::fromLocalFile(destdir);
|
||||
KIO::CopyJob *job = KIO::move(lst, d, KIO::HideProgressInfo);
|
||||
job->setUiDelegate(nullptr);
|
||||
FileUndoManager::self()->recordCopyJob(job);
|
||||
|
||||
QVERIFY2(job->exec(), qPrintable(job->errorString()));
|
||||
|
||||
QVERIFY(!QFile::exists(srcFile())); // the source moved
|
||||
QVERIFY(QFile::exists(destFile()));
|
||||
#ifndef Q_OS_WIN
|
||||
QVERIFY(!QFileInfo(srcLink()).isSymLink());
|
||||
// Don't use QFile::exists, it's a broken symlink...
|
||||
QVERIFY(QFileInfo(destLink()).isSymLink());
|
||||
#endif
|
||||
|
||||
doUndo();
|
||||
|
||||
QVERIFY(QFile::exists(srcFile())); // the source is back
|
||||
QVERIFY(!QFile::exists(destFile()));
|
||||
#ifndef Q_OS_WIN
|
||||
QVERIFY(QFileInfo(srcLink()).isSymLink());
|
||||
QVERIFY(!QFileInfo(destLink()).isSymLink());
|
||||
#endif
|
||||
}
|
||||
|
||||
void FileUndoManagerTest::testCopyDirectory()
|
||||
{
|
||||
const QString destdir = destDir();
|
||||
QList<QUrl> lst;
|
||||
lst << QUrl::fromLocalFile(srcSubDir());
|
||||
const QUrl d = QUrl::fromLocalFile(destdir);
|
||||
KIO::CopyJob *job = KIO::copy(lst, d, KIO::HideProgressInfo);
|
||||
job->setUiDelegate(nullptr);
|
||||
FileUndoManager::self()->recordCopyJob(job);
|
||||
|
||||
QVERIFY2(job->exec(), qPrintable(job->errorString()));
|
||||
|
||||
checkTestDirectory(srcSubDir()); // src untouched
|
||||
checkTestDirectory(destSubDir());
|
||||
|
||||
doUndo();
|
||||
|
||||
checkTestDirectory(srcSubDir());
|
||||
QVERIFY(!QFile::exists(destSubDir()));
|
||||
}
|
||||
|
||||
void FileUndoManagerTest::testCopyEmptyDirectory()
|
||||
{
|
||||
const QString src = srcSubDir() + "/.emptydir";
|
||||
const QString destEmptyDir = destDir() + "/.emptydir";
|
||||
QDir().mkpath(src);
|
||||
KIO::CopyJob *job = KIO::copy({QUrl::fromLocalFile(src)}, QUrl::fromLocalFile(destEmptyDir), KIO::HideProgressInfo);
|
||||
job->setUiDelegate(nullptr);
|
||||
FileUndoManager::self()->recordCopyJob(job);
|
||||
|
||||
QVERIFY2(job->exec(), qPrintable(job->errorString()));
|
||||
|
||||
QVERIFY(QFileInfo(src).isDir()); // untouched
|
||||
QVERIFY(QFileInfo(destEmptyDir).isDir());
|
||||
|
||||
doUndo();
|
||||
|
||||
QVERIFY(QFileInfo(src).isDir()); // untouched
|
||||
QVERIFY(!QFile::exists(destEmptyDir));
|
||||
}
|
||||
|
||||
void FileUndoManagerTest::testMoveDirectory()
|
||||
{
|
||||
const QString destdir = destDir();
|
||||
QList<QUrl> lst;
|
||||
lst << QUrl::fromLocalFile(srcSubDir());
|
||||
const QUrl d = QUrl::fromLocalFile(destdir);
|
||||
KIO::CopyJob *job = KIO::move(lst, d, KIO::HideProgressInfo);
|
||||
job->setUiDelegate(nullptr);
|
||||
FileUndoManager::self()->recordCopyJob(job);
|
||||
|
||||
QVERIFY2(job->exec(), qPrintable(job->errorString()));
|
||||
|
||||
QVERIFY(!QFile::exists(srcSubDir()));
|
||||
checkTestDirectory(destSubDir());
|
||||
|
||||
doUndo();
|
||||
|
||||
checkTestDirectory(srcSubDir());
|
||||
QVERIFY(!QFile::exists(destSubDir()));
|
||||
}
|
||||
|
||||
void FileUndoManagerTest::testRenameFile()
|
||||
{
|
||||
const QUrl oldUrl = QUrl::fromLocalFile(srcFile());
|
||||
const QUrl newUrl = QUrl::fromLocalFile(srcFile() + ".new");
|
||||
QList<QUrl> lst;
|
||||
lst.append(oldUrl);
|
||||
QSignalSpy spyUndoAvailable(FileUndoManager::self(), &FileUndoManager::undoAvailable);
|
||||
QVERIFY(spyUndoAvailable.isValid());
|
||||
KIO::Job *job = KIO::moveAs(oldUrl, newUrl, KIO::HideProgressInfo);
|
||||
job->setUiDelegate(nullptr);
|
||||
FileUndoManager::self()->recordJob(FileUndoManager::Rename, lst, newUrl, job);
|
||||
|
||||
QVERIFY2(job->exec(), qPrintable(job->errorString()));
|
||||
|
||||
QVERIFY(!QFile::exists(srcFile()));
|
||||
QVERIFY(QFileInfo(newUrl.toLocalFile()).isFile());
|
||||
QCOMPARE(spyUndoAvailable.count(), 1);
|
||||
|
||||
doUndo();
|
||||
|
||||
QVERIFY(QFile::exists(srcFile()));
|
||||
QVERIFY(!QFileInfo(newUrl.toLocalFile()).isFile());
|
||||
}
|
||||
|
||||
void FileUndoManagerTest::testRenameDir()
|
||||
{
|
||||
const QUrl oldUrl = QUrl::fromLocalFile(srcSubDir());
|
||||
const QUrl newUrl = QUrl::fromLocalFile(srcSubDir() + ".new");
|
||||
QList<QUrl> lst;
|
||||
lst.append(oldUrl);
|
||||
KIO::Job *job = KIO::moveAs(oldUrl, newUrl, KIO::HideProgressInfo);
|
||||
job->setUiDelegate(nullptr);
|
||||
FileUndoManager::self()->recordJob(FileUndoManager::Rename, lst, newUrl, job);
|
||||
|
||||
QVERIFY2(job->exec(), qPrintable(job->errorString()));
|
||||
|
||||
QVERIFY(!QFile::exists(srcSubDir()));
|
||||
QVERIFY(QFileInfo(newUrl.toLocalFile()).isDir());
|
||||
|
||||
doUndo();
|
||||
|
||||
QVERIFY(QFile::exists(srcSubDir()));
|
||||
QVERIFY(!QFileInfo(newUrl.toLocalFile()).isDir());
|
||||
}
|
||||
|
||||
void FileUndoManagerTest::testCreateSymlink()
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
QSKIP("Test skipped on Windows for lack of proper symlink support");
|
||||
#endif
|
||||
const QUrl link = QUrl::fromLocalFile(homeTmpDir() + "newlink");
|
||||
const QString path = link.toLocalFile();
|
||||
QVERIFY(!QFile::exists(path));
|
||||
|
||||
const QUrl target = QUrl::fromLocalFile(homeTmpDir() + "linktarget");
|
||||
const QString targetPath = target.toLocalFile();
|
||||
createTestFile(targetPath, "Link's Target");
|
||||
QVERIFY(QFile::exists(targetPath));
|
||||
|
||||
KIO::CopyJob *job = KIO::link(target, link, KIO::HideProgressInfo);
|
||||
job->setUiDelegate(nullptr);
|
||||
FileUndoManager::self()->recordCopyJob(job);
|
||||
QVERIFY2(job->exec(), qPrintable(job->errorString()));
|
||||
QVERIFY(QFile::exists(path));
|
||||
QVERIFY(QFileInfo(path).isSymLink());
|
||||
|
||||
// For undoing symlinks no confirmation is required. We delete it straight away.
|
||||
doUndo();
|
||||
|
||||
QVERIFY(!QFile::exists(path));
|
||||
}
|
||||
|
||||
void FileUndoManagerTest::testCreateDir()
|
||||
{
|
||||
const QUrl url = QUrl::fromLocalFile(srcSubDir() + ".mkdir");
|
||||
const QString path = url.toLocalFile();
|
||||
QVERIFY(!QFile::exists(path));
|
||||
|
||||
KIO::SimpleJob *job = KIO::mkdir(url);
|
||||
job->setUiDelegate(nullptr);
|
||||
FileUndoManager::self()->recordJob(FileUndoManager::Mkdir, QList<QUrl>(), url, job);
|
||||
QVERIFY2(job->exec(), qPrintable(job->errorString()));
|
||||
QVERIFY(QFile::exists(path));
|
||||
QVERIFY(QFileInfo(path).isDir());
|
||||
|
||||
m_uiInterface->clear();
|
||||
m_uiInterface->setNextReplyToConfirmDeletion(false); // act like the user didn't confirm
|
||||
FileUndoManager::self()->undo();
|
||||
QCOMPARE(m_uiInterface->askUserMockInterface()->m_askUserDeleteCalled, 1);
|
||||
QVERIFY(QFile::exists(path)); // nothing happened yet
|
||||
|
||||
// OK, now do it
|
||||
m_uiInterface->clear();
|
||||
m_uiInterface->setNextReplyToConfirmDeletion(true);
|
||||
doUndo();
|
||||
|
||||
QVERIFY(!QFile::exists(path));
|
||||
}
|
||||
|
||||
void FileUndoManagerTest::testMkpath()
|
||||
{
|
||||
const QString parent = srcSubDir() + "mkpath";
|
||||
const QString path = parent + "/subdir";
|
||||
QVERIFY(!QFile::exists(path));
|
||||
const QUrl url = QUrl::fromLocalFile(path);
|
||||
|
||||
KIO::Job *job = KIO::mkpath(url, QUrl(), KIO::HideProgressInfo);
|
||||
job->setUiDelegate(nullptr);
|
||||
FileUndoManager::self()->recordJob(FileUndoManager::Mkpath, QList<QUrl>(), url, job);
|
||||
QVERIFY(job->exec());
|
||||
QVERIFY(QFileInfo(path).isDir());
|
||||
|
||||
m_uiInterface->clear();
|
||||
m_uiInterface->setNextReplyToConfirmDeletion(true);
|
||||
doUndo();
|
||||
|
||||
QVERIFY(!FileUndoManager::self()->isUndoAvailable());
|
||||
QCOMPARE(m_uiInterface->askUserMockInterface()->m_askUserDeleteCalled, 1);
|
||||
|
||||
QVERIFY(!QFile::exists(path));
|
||||
}
|
||||
|
||||
void FileUndoManagerTest::testTrashFiles()
|
||||
{
|
||||
if (!KProtocolInfo::isKnownProtocol(QStringLiteral("trash"))) {
|
||||
QSKIP("kio_trash not installed");
|
||||
}
|
||||
|
||||
// Trash it all at once: the file, the symlink, the subdir.
|
||||
QList<QUrl> lst = sourceList();
|
||||
lst.append(QUrl::fromLocalFile(srcSubDir()));
|
||||
KIO::Job *job = KIO::trash(lst, KIO::HideProgressInfo);
|
||||
job->setUiDelegate(nullptr);
|
||||
FileUndoManager::self()->recordJob(FileUndoManager::Trash, lst, QUrl(QStringLiteral("trash:/")), job);
|
||||
|
||||
QVERIFY2(job->exec(), qPrintable(job->errorString()));
|
||||
|
||||
// Check that things got removed
|
||||
QVERIFY(!QFile::exists(srcFile()));
|
||||
#ifndef Q_OS_WIN
|
||||
QVERIFY(!QFileInfo(srcLink()).isSymLink());
|
||||
#endif
|
||||
QVERIFY(!QFile::exists(srcSubDir()));
|
||||
|
||||
// check trash?
|
||||
// Let's just check that it's not empty. kio_trash has its own unit tests anyway.
|
||||
KConfig cfg(QStringLiteral("trashrc"), KConfig::SimpleConfig);
|
||||
QVERIFY(cfg.hasGroup(QStringLiteral("Status")));
|
||||
QCOMPARE(cfg.group(QStringLiteral("Status")).readEntry("Empty", true), false);
|
||||
|
||||
doUndo();
|
||||
|
||||
QVERIFY(QFile::exists(srcFile()));
|
||||
#ifndef Q_OS_WIN
|
||||
QVERIFY(QFileInfo(srcLink()).isSymLink());
|
||||
#endif
|
||||
QVERIFY(QFile::exists(srcSubDir()));
|
||||
|
||||
// We can't check that the trash is empty; other partitions might have their own trash
|
||||
}
|
||||
|
||||
void FileUndoManagerTest::testRestoreTrashedFiles()
|
||||
{
|
||||
if (!KProtocolInfo::isKnownProtocol(QStringLiteral("trash"))) {
|
||||
QSKIP("kio_trash not installed");
|
||||
}
|
||||
|
||||
// Trash it all at once: the file, the symlink, the subdir.
|
||||
const QFile::Permissions origPerms = QFileInfo(srcFile()).permissions();
|
||||
QList<QUrl> lst = sourceList();
|
||||
lst.append(QUrl::fromLocalFile(srcSubDir()));
|
||||
KIO::Job *job = KIO::trash(lst, KIO::HideProgressInfo);
|
||||
job->setUiDelegate(nullptr);
|
||||
QVERIFY(job->exec());
|
||||
|
||||
const QMap<QString, QString> metaData = job->metaData();
|
||||
QList<QUrl> trashUrls;
|
||||
for (const QUrl &src : std::as_const(lst)) {
|
||||
QMap<QString, QString>::ConstIterator it = metaData.find("trashURL-" + src.path());
|
||||
QVERIFY(it != metaData.constEnd());
|
||||
trashUrls.append(QUrl(it.value()));
|
||||
}
|
||||
|
||||
// Restore from trash
|
||||
KIO::RestoreJob *restoreJob = KIO::restoreFromTrash(trashUrls, KIO::HideProgressInfo);
|
||||
restoreJob->setUiDelegate(nullptr);
|
||||
QVERIFY(restoreJob->exec());
|
||||
|
||||
QVERIFY(QFile::exists(srcFile()));
|
||||
QCOMPARE(QFileInfo(srcFile()).permissions(), origPerms);
|
||||
#ifndef Q_OS_WIN
|
||||
QVERIFY(QFileInfo(srcLink()).isSymLink());
|
||||
#endif
|
||||
QVERIFY(QFile::exists(srcSubDir()));
|
||||
|
||||
// TODO support for RestoreJob in FileUndoManager !!!
|
||||
}
|
||||
|
||||
static void setTimeStamp(const QString &path)
|
||||
{
|
||||
#ifdef Q_OS_UNIX
|
||||
// Put timestamp in the past so that we can check that the
|
||||
// copy actually preserves it.
|
||||
struct timeval tp;
|
||||
gettimeofday(&tp, nullptr);
|
||||
struct utimbuf utbuf;
|
||||
utbuf.actime = tp.tv_sec + 30; // 30 seconds in the future
|
||||
utbuf.modtime = tp.tv_sec + 60; // 60 second in the future
|
||||
utime(QFile::encodeName(path).constData(), &utbuf);
|
||||
qDebug("Time changed for %s", qPrintable(path));
|
||||
#endif
|
||||
}
|
||||
|
||||
void FileUndoManagerTest::testModifyFileBeforeUndo()
|
||||
{
|
||||
// based on testCopyDirectory (so that we check that it works for files in subdirs too)
|
||||
const QString destdir = destDir();
|
||||
const QList<QUrl> lst{QUrl::fromLocalFile(srcSubDir())};
|
||||
const QUrl dest = QUrl::fromLocalFile(destdir);
|
||||
KIO::CopyJob *job = KIO::copy(lst, dest, KIO::HideProgressInfo);
|
||||
job->setUiDelegate(nullptr);
|
||||
FileUndoManager::self()->recordCopyJob(job);
|
||||
|
||||
QVERIFY2(job->exec(), qPrintable(job->errorString()));
|
||||
|
||||
checkTestDirectory(srcSubDir()); // src untouched
|
||||
checkTestDirectory(destSubDir());
|
||||
const QString destFile = destSubDir() + "/fileindir";
|
||||
setTimeStamp(destFile); // simulate a modification of the file
|
||||
|
||||
doUndo();
|
||||
|
||||
// Check that TestUiInterface::copiedFileWasModified got called
|
||||
QCOMPARE(m_uiInterface->dest().toLocalFile(), destFile);
|
||||
|
||||
checkTestDirectory(srcSubDir());
|
||||
QVERIFY(!QFile::exists(destSubDir()));
|
||||
}
|
||||
|
||||
void FileUndoManagerTest::testPasteClipboardUndo()
|
||||
{
|
||||
const QList<QUrl> urls(sourceList());
|
||||
QMimeData *mimeData = new QMimeData();
|
||||
mimeData->setUrls(urls);
|
||||
KIO::setClipboardDataCut(mimeData, true);
|
||||
QClipboard *clipboard = QApplication::clipboard();
|
||||
clipboard->setMimeData(mimeData);
|
||||
|
||||
// Paste the contents of the clipboard and check its status
|
||||
QUrl destDirUrl = QUrl::fromLocalFile(destDir());
|
||||
KIO::Job *job = KIO::paste(mimeData, destDirUrl, KIO::HideProgressInfo);
|
||||
QVERIFY(job);
|
||||
QVERIFY(job->exec());
|
||||
|
||||
// Check if the clipboard was updated after paste operation
|
||||
QList<QUrl> urls2;
|
||||
for (const QUrl &url : urls) {
|
||||
QUrl dUrl = destDirUrl.adjusted(QUrl::StripTrailingSlash);
|
||||
dUrl.setPath(dUrl.path() + '/' + url.fileName());
|
||||
urls2 << dUrl;
|
||||
}
|
||||
QList<QUrl> clipboardUrls = KUrlMimeData::urlsFromMimeData(clipboard->mimeData());
|
||||
QCOMPARE(clipboardUrls, urls2);
|
||||
|
||||
// Check if the clipboard was updated after undo operation
|
||||
doUndo();
|
||||
clipboardUrls = KUrlMimeData::urlsFromMimeData(clipboard->mimeData());
|
||||
QCOMPARE(clipboardUrls, urls);
|
||||
}
|
||||
|
||||
void FileUndoManagerTest::testBatchRename()
|
||||
{
|
||||
auto createUrl = [](const QString &path) -> QUrl {
|
||||
return QUrl::fromLocalFile(homeTmpDir() + path);
|
||||
};
|
||||
|
||||
QList<QUrl> srcList;
|
||||
srcList << createUrl("textfile.txt") << createUrl("mediafile.mkv") << createUrl("sourcefile.cpp");
|
||||
|
||||
createTestFile(srcList.at(0).path(), "foo");
|
||||
createTestFile(srcList.at(1).path(), "foo");
|
||||
createTestFile(srcList.at(2).path(), "foo");
|
||||
|
||||
KIO::Job *job = KIO::batchRename(srcList, QLatin1String("newfile###"), 1, QLatin1Char('#'), KIO::HideProgressInfo);
|
||||
job->setUiDelegate(nullptr);
|
||||
FileUndoManager::self()->recordJob(FileUndoManager::BatchRename, srcList, QUrl(), job);
|
||||
QVERIFY2(job->exec(), qPrintable(job->errorString()));
|
||||
|
||||
QVERIFY(QFile::exists(createUrl("newfile001.txt").path()));
|
||||
QVERIFY(QFile::exists(createUrl("newfile002.mkv").path()));
|
||||
QVERIFY(QFile::exists(createUrl("newfile003.cpp").path()));
|
||||
QVERIFY(!QFile::exists(srcList.at(0).path()));
|
||||
QVERIFY(!QFile::exists(srcList.at(1).path()));
|
||||
QVERIFY(!QFile::exists(srcList.at(2).path()));
|
||||
|
||||
doUndo();
|
||||
|
||||
QVERIFY(!QFile::exists(createUrl("newfile###.txt").path()));
|
||||
QVERIFY(!QFile::exists(createUrl("newfile###.mkv").path()));
|
||||
QVERIFY(!QFile::exists(createUrl("newfile###.cpp").path()));
|
||||
QVERIFY(QFile::exists(srcList.at(0).path()));
|
||||
QVERIFY(QFile::exists(srcList.at(1).path()));
|
||||
QVERIFY(QFile::exists(srcList.at(2).path()));
|
||||
}
|
||||
|
||||
void FileUndoManagerTest::testUndoCopyOfDeletedFile()
|
||||
{
|
||||
const QUrl source = QUrl::fromLocalFile(homeTmpDir() + QLatin1String("source.txt"));
|
||||
const QUrl dest = QUrl::fromLocalFile(homeTmpDir() + QLatin1String("copy.txt"));
|
||||
|
||||
createTestFile(source.toLocalFile(), "foo");
|
||||
QVERIFY(QFileInfo::exists(source.toLocalFile()));
|
||||
|
||||
{
|
||||
auto copyJob = KIO::copy(source, dest, KIO::HideProgressInfo);
|
||||
copyJob->setUiDelegate(nullptr);
|
||||
FileUndoManager::self()->recordCopyJob(copyJob);
|
||||
QVERIFY2(copyJob->exec(), qPrintable(copyJob->errorString()));
|
||||
QVERIFY(QFileInfo::exists(dest.toLocalFile()));
|
||||
}
|
||||
|
||||
{
|
||||
auto deleteJob = KIO::del(dest, KIO::HideProgressInfo);
|
||||
deleteJob->setUiDelegate(nullptr);
|
||||
QVERIFY2(deleteJob->exec(), qPrintable(deleteJob->errorString()));
|
||||
QVERIFY(!QFileInfo::exists(dest.toLocalFile()));
|
||||
}
|
||||
|
||||
QVERIFY(FileUndoManager::self()->isUndoAvailable());
|
||||
QSignalSpy spyUndoAvailable(FileUndoManager::self(), &FileUndoManager::undoAvailable);
|
||||
QVERIFY(spyUndoAvailable.isValid());
|
||||
doUndo();
|
||||
QVERIFY(spyUndoAvailable.count() >= 2); // it's in fact 3, due to lock/unlock emitting it as well
|
||||
QVERIFY(!spyUndoAvailable.at(0).at(0).toBool());
|
||||
QVERIFY(!FileUndoManager::self()->isUndoAvailable());
|
||||
}
|
||||
|
||||
void FileUndoManagerTest::testErrorDuringMoveUndo()
|
||||
{
|
||||
const QString destdir = destDir();
|
||||
QList<QUrl> lst{QUrl::fromLocalFile(srcFile())};
|
||||
KIO::CopyJob *job = KIO::move(lst, QUrl::fromLocalFile(destdir), KIO::HideProgressInfo);
|
||||
job->setUiDelegate(nullptr);
|
||||
FileUndoManager::self()->recordCopyJob(job);
|
||||
|
||||
QVERIFY2(job->exec(), qPrintable(job->errorString()));
|
||||
|
||||
QVERIFY(!QFile::exists(srcFile())); // the source moved
|
||||
QVERIFY(QFile::exists(destFile()));
|
||||
createTestFile(srcFile(), "I'm back");
|
||||
|
||||
doUndo();
|
||||
|
||||
QCOMPARE(m_uiInterface->errorCode(), KIO::ERR_FILE_ALREADY_EXIST);
|
||||
QVERIFY(QFile::exists(destFile())); // still there
|
||||
}
|
||||
|
||||
void FileUndoManagerTest::testNoUndoForSkipAll()
|
||||
{
|
||||
auto *undoManager = FileUndoManager::self();
|
||||
|
||||
QTemporaryDir tempDir;
|
||||
const QString tempPath = tempDir.path();
|
||||
|
||||
const QString destPath = tempPath + "/dest_dir";
|
||||
QVERIFY(QDir(tempPath).mkdir("dest_dir"));
|
||||
const QUrl destUrl = QUrl::fromLocalFile(destPath);
|
||||
|
||||
const QList<QUrl> lst{QUrl::fromLocalFile(tempPath + "/file_a"), QUrl::fromLocalFile(tempPath + "/file_b")};
|
||||
for (const auto &url : lst) {
|
||||
createTestFile(url.toLocalFile(), "foo");
|
||||
}
|
||||
|
||||
auto createJob = [&]() {
|
||||
return KIO::copy(lst, destUrl, KIO::HideProgressInfo);
|
||||
};
|
||||
|
||||
KIO::CopyJob *job = createJob();
|
||||
job->setUiDelegate(nullptr);
|
||||
undoManager->recordCopyJob(job);
|
||||
|
||||
QSignalSpy spyUndoAvailable(undoManager, &FileUndoManager::undoAvailable);
|
||||
QVERIFY(spyUndoAvailable.isValid());
|
||||
QSignalSpy spyTextChanged(undoManager, &FileUndoManager::undoTextChanged);
|
||||
QVERIFY(spyTextChanged.isValid());
|
||||
|
||||
QVERIFY2(job->exec(), qPrintable(job->errorString()));
|
||||
|
||||
// Src files still exist
|
||||
for (const auto &url : lst) {
|
||||
QVERIFY(QFile::exists(url.toLocalFile()));
|
||||
}
|
||||
|
||||
// Files copied to dest
|
||||
for (const auto &url : lst) {
|
||||
QVERIFY(QFile::exists(destPath + '/' + url.fileName()));
|
||||
}
|
||||
|
||||
// An undo command was recorded
|
||||
QCOMPARE(spyUndoAvailable.count(), 1);
|
||||
QCOMPARE(spyTextChanged.count(), 1);
|
||||
|
||||
KIO::CopyJob *repeatCopy = createJob();
|
||||
// Copying the same files again to the same dest, and setting skip all
|
||||
repeatCopy->setAutoSkip(true);
|
||||
undoManager->recordCopyJob(repeatCopy);
|
||||
|
||||
QVERIFY2(repeatCopy->exec(), qPrintable(repeatCopy->errorString()));
|
||||
|
||||
// No new undo command was added since the job didn't actually copy anything
|
||||
QCOMPARE(spyUndoAvailable.count(), 1);
|
||||
QCOMPARE(spyTextChanged.count(), 1);
|
||||
}
|
||||
|
||||
// TODO: add test (and fix bug) for DND of remote urls / "Link here" (creates .desktop files) // Undo (doesn't do anything)
|
||||
// TODO: add test for interrupting a moving operation and then using Undo - bug:91579
|
||||
|
||||
#include "moc_fileundomanagertest.cpp"
|
||||
Reference in New Issue
Block a user