cf12defd28
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2937 lines
107 KiB
C++
2937 lines
107 KiB
C++
/*
|
|
This file is part of the KDE project
|
|
SPDX-FileCopyrightText: 2004-2006 David Faure <faure@kde.org>
|
|
|
|
SPDX-License-Identifier: LGPL-2.0-or-later
|
|
*/
|
|
|
|
#include "jobtest.h"
|
|
#include "mockcoredelegateextensions.h"
|
|
|
|
#include "kio/job.h"
|
|
#include "kiotesthelper.h" // createTestFile etc.
|
|
#include <kio/chmodjob.h>
|
|
#include <kio/copyjob.h>
|
|
#include <kio/deletejob.h>
|
|
#include <kio/directorysizejob.h>
|
|
#include <kio/filecopyjob.h>
|
|
#include <kio/listjob.h>
|
|
#include <kio/mimetypejob.h>
|
|
#include <kio/statjob.h>
|
|
#include <kio/storedtransferjob.h>
|
|
#include <kmountpoint.h>
|
|
#include <kprotocolinfo.h>
|
|
|
|
#include <KJobUiDelegate>
|
|
#include <KLocalizedString>
|
|
|
|
#include <QBuffer>
|
|
#include <QDebug>
|
|
#include <QDir>
|
|
#include <QElapsedTimer>
|
|
#include <QEventLoop>
|
|
#include <QFileInfo>
|
|
#include <QHash>
|
|
#include <QPointer>
|
|
#include <QProcess>
|
|
#include <QSignalSpy>
|
|
#include <QTemporaryFile>
|
|
#include <QTest>
|
|
#include <QTimer>
|
|
#include <QUrl>
|
|
#include <QVariant>
|
|
|
|
#ifndef Q_OS_WIN
|
|
#include <unistd.h> // for readlink
|
|
#endif
|
|
|
|
QTEST_MAIN(JobTest)
|
|
|
|
// The code comes partly from kdebase/kioslave/trash/testtrash.cpp
|
|
|
|
static QString otherTmpDir()
|
|
{
|
|
#ifdef Q_OS_WIN
|
|
return QDir::tempPath() + "/jobtest/";
|
|
#else
|
|
// This one needs to be on another partition, but we can't guarantee that it is
|
|
// On CI, it typically isn't...
|
|
return QStringLiteral("/tmp/jobtest/");
|
|
#endif
|
|
}
|
|
|
|
static bool otherTmpDirIsOnSamePartition() // true on CI because it's a LXC container
|
|
{
|
|
KMountPoint::Ptr srcMountPoint = KMountPoint::currentMountPoints().findByPath(homeTmpDir());
|
|
KMountPoint::Ptr destMountPoint = KMountPoint::currentMountPoints().findByPath(otherTmpDir());
|
|
Q_ASSERT(srcMountPoint);
|
|
Q_ASSERT(destMountPoint);
|
|
return srcMountPoint->mountedFrom() == destMountPoint->mountedFrom();
|
|
}
|
|
|
|
void JobTest::initTestCase()
|
|
{
|
|
QStandardPaths::setTestModeEnabled(true);
|
|
QCoreApplication::instance()->setApplicationName("kio/jobtest"); // testing for #357499
|
|
|
|
// to make sure io is not too fast
|
|
qputenv("KIOWORKER_FILE_ENABLE_TESTMODE", "1");
|
|
|
|
s_referenceTimeStamp = QDateTime::currentDateTime().addSecs(-30); // 30 seconds ago
|
|
|
|
// Start with a clean base dir
|
|
cleanupTestCase();
|
|
homeTmpDir(); // create it
|
|
if (!QFile::exists(otherTmpDir())) {
|
|
bool ok = QDir().mkdir(otherTmpDir());
|
|
if (!ok) {
|
|
qFatal("couldn't create %s", qPrintable(otherTmpDir()));
|
|
}
|
|
}
|
|
|
|
/*****
|
|
* Set platform xattr related commands.
|
|
* Linux commands: setfattr, getfattr
|
|
* BSD commands: setextattr, getextattr
|
|
* MacOS commands: xattr -w, xattr -p
|
|
****/
|
|
m_getXattrCmd = QStandardPaths::findExecutable("getfattr");
|
|
if (m_getXattrCmd.endsWith("getfattr")) {
|
|
m_setXattrCmd = QStandardPaths::findExecutable("setfattr");
|
|
m_setXattrFormatArgs = [](const QString &attrName, const QString &value, const QString &fileName) {
|
|
return QStringList{QLatin1String("-n"), attrName, QLatin1String("-v"), value, fileName};
|
|
};
|
|
} else {
|
|
// On BSD there is lsextattr to list all xattrs and getextattr to get a value
|
|
// for specific xattr. For test purposes lsextattr is more suitable to be used
|
|
// as m_getXattrCmd, so search for it instead of getextattr.
|
|
m_getXattrCmd = QStandardPaths::findExecutable("lsextattr");
|
|
if (m_getXattrCmd.endsWith("lsextattr")) {
|
|
m_setXattrCmd = QStandardPaths::findExecutable("setextattr");
|
|
m_setXattrFormatArgs = [](const QString &attrName, const QString &value, const QString &fileName) {
|
|
return QStringList{QLatin1String("user"), attrName, value, fileName};
|
|
};
|
|
} else {
|
|
m_getXattrCmd = QStandardPaths::findExecutable("xattr");
|
|
m_setXattrFormatArgs = [](const QString &attrName, const QString &value, const QString &fileName) {
|
|
return QStringList{QLatin1String("-w"), attrName, value, fileName};
|
|
};
|
|
if (!m_getXattrCmd.endsWith("xattr")) {
|
|
qWarning() << "Neither getfattr, getextattr nor xattr was found.";
|
|
}
|
|
}
|
|
}
|
|
|
|
qRegisterMetaType<KJob *>("KJob*");
|
|
qRegisterMetaType<KIO::Job *>("KIO::Job*");
|
|
qRegisterMetaType<QDateTime>("QDateTime");
|
|
}
|
|
|
|
void JobTest::cleanupTestCase()
|
|
{
|
|
QDir(homeTmpDir()).removeRecursively();
|
|
QDir(otherTmpDir()).removeRecursively();
|
|
}
|
|
|
|
struct ScopedCleaner {
|
|
using Func = std::function<void()>;
|
|
explicit ScopedCleaner(Func f)
|
|
: m_f(std::move(f))
|
|
{
|
|
}
|
|
~ScopedCleaner()
|
|
{
|
|
m_f();
|
|
}
|
|
|
|
private:
|
|
const Func m_f;
|
|
};
|
|
|
|
void JobTest::enterLoop()
|
|
{
|
|
QEventLoop eventLoop;
|
|
connect(this, &JobTest::exitLoop, &eventLoop, &QEventLoop::quit);
|
|
eventLoop.exec(QEventLoop::ExcludeUserInputEvents);
|
|
}
|
|
|
|
void JobTest::storedGet()
|
|
{
|
|
// qDebug();
|
|
const QString filePath = homeTmpDir() + "fileFromHome";
|
|
createTestFile(filePath);
|
|
QUrl u = QUrl::fromLocalFile(filePath);
|
|
m_result = -1;
|
|
|
|
KIO::StoredTransferJob *job = KIO::storedGet(u, KIO::NoReload, KIO::HideProgressInfo);
|
|
|
|
QSignalSpy spyPercent(job, &KJob::percentChanged);
|
|
QVERIFY(spyPercent.isValid());
|
|
job->setUiDelegate(nullptr);
|
|
connect(job, &KJob::result, this, &JobTest::slotGetResult);
|
|
enterLoop();
|
|
QCOMPARE(m_result, 0); // no error
|
|
QCOMPARE(m_data, QByteArray("Hello\0world", 11));
|
|
QCOMPARE(m_data.size(), 11);
|
|
QVERIFY(!spyPercent.isEmpty());
|
|
}
|
|
|
|
void JobTest::slotGetResult(KJob *job)
|
|
{
|
|
m_result = job->error();
|
|
m_data = static_cast<KIO::StoredTransferJob *>(job)->data();
|
|
Q_EMIT exitLoop();
|
|
}
|
|
|
|
void JobTest::put()
|
|
{
|
|
const QString filePath = homeTmpDir() + "fileFromHome";
|
|
QUrl u = QUrl::fromLocalFile(filePath);
|
|
KIO::TransferJob *job = KIO::put(u, 0600, KIO::Overwrite | KIO::HideProgressInfo);
|
|
quint64 secsSinceEpoch = QDateTime::currentSecsSinceEpoch(); // Use second granularity, supported on all filesystems
|
|
QDateTime mtime = QDateTime::fromSecsSinceEpoch(secsSinceEpoch - 30); // 30 seconds ago
|
|
job->setModificationTime(mtime);
|
|
job->setUiDelegate(nullptr);
|
|
connect(job, &KJob::result, this, &JobTest::slotResult);
|
|
connect(job, &KIO::TransferJob::dataReq, this, &JobTest::slotDataReq);
|
|
m_result = -1;
|
|
m_dataReqCount = 0;
|
|
enterLoop();
|
|
QVERIFY(m_result == 0); // no error
|
|
|
|
QFileInfo fileInfo(filePath);
|
|
QVERIFY(fileInfo.exists());
|
|
QCOMPARE(fileInfo.size(), 30LL); // "This is a test for KIO::put()\n"
|
|
QCOMPARE(fileInfo.permissions(), QFile::ReadOwner | QFile::WriteOwner | QFile::ReadUser | QFile::WriteUser);
|
|
QCOMPARE(fileInfo.lastModified(), mtime);
|
|
}
|
|
|
|
void JobTest::putPermissionKept()
|
|
{
|
|
const QString filePath = homeTmpDir() + "fileFromHome";
|
|
QVERIFY2(::chmod(filePath.toUtf8(), 0644) == 0, strerror(errno));
|
|
|
|
QUrl u = QUrl::fromLocalFile(filePath);
|
|
KIO::TransferJob *job = KIO::put(u, -1, KIO::Overwrite | KIO::HideProgressInfo);
|
|
quint64 secsSinceEpoch = QDateTime::currentSecsSinceEpoch(); // Use second granularity, supported on all filesystems
|
|
QDateTime mtime = QDateTime::fromSecsSinceEpoch(secsSinceEpoch - 30); // 30 seconds ago
|
|
job->setModificationTime(mtime);
|
|
job->setUiDelegate(nullptr);
|
|
connect(job, &KJob::result, this, &JobTest::slotResult);
|
|
connect(job, &KIO::TransferJob::dataReq, this, &JobTest::slotDataReq);
|
|
m_result = -1;
|
|
m_dataReqCount = 0;
|
|
enterLoop();
|
|
QVERIFY(m_result == 0); // no error
|
|
|
|
QFileInfo fileInfo(filePath);
|
|
QVERIFY(fileInfo.exists());
|
|
QCOMPARE(fileInfo.size(), 30LL); // "This is a test for KIO::put()\n"
|
|
QCOMPARE(fileInfo.permissions(), QFile::ReadOwner | QFile::WriteOwner | QFile::ReadUser | QFile::WriteUser | QFile::ReadGroup | QFile::ReadOther /* 644 */);
|
|
QCOMPARE(fileInfo.lastModified(), mtime);
|
|
}
|
|
|
|
void JobTest::slotDataReq(KIO::Job *, QByteArray &data)
|
|
{
|
|
// Really not the way you'd write a slotDataReq usually :)
|
|
switch (m_dataReqCount++) {
|
|
case 0:
|
|
data = "This is a test for ";
|
|
break;
|
|
case 1:
|
|
data = "KIO::put()\n";
|
|
break;
|
|
case 2:
|
|
data = QByteArray();
|
|
break;
|
|
}
|
|
}
|
|
|
|
void JobTest::slotResult(KJob *job)
|
|
{
|
|
m_result = job->error();
|
|
Q_EMIT exitLoop();
|
|
}
|
|
|
|
void JobTest::storedPut()
|
|
{
|
|
const QString filePath = homeTmpDir() + "fileFromHome";
|
|
QUrl u = QUrl::fromLocalFile(filePath);
|
|
QByteArray putData = "This is the put data";
|
|
KIO::TransferJob *job = KIO::storedPut(putData, u, 0600, KIO::Overwrite | KIO::HideProgressInfo);
|
|
|
|
QSignalSpy spyPercent(job, &KJob::percentChanged);
|
|
QVERIFY(spyPercent.isValid());
|
|
quint64 secsSinceEpoch = QDateTime::currentSecsSinceEpoch(); // Use second granularity, supported on all filesystems
|
|
QDateTime mtime = QDateTime::fromSecsSinceEpoch(secsSinceEpoch - 30); // 30 seconds ago
|
|
job->setModificationTime(mtime);
|
|
job->setUiDelegate(nullptr);
|
|
QVERIFY2(job->exec(), qPrintable(job->errorString()));
|
|
QFileInfo fileInfo(filePath);
|
|
QVERIFY(fileInfo.exists());
|
|
QCOMPARE(fileInfo.size(), (long long)putData.size());
|
|
QCOMPARE((int)fileInfo.permissions(), (int)(QFile::ReadOwner | QFile::WriteOwner | QFile::ReadUser | QFile::WriteUser));
|
|
QCOMPARE(fileInfo.lastModified(), mtime);
|
|
QVERIFY(!spyPercent.isEmpty());
|
|
}
|
|
|
|
void JobTest::storedPutIODevice()
|
|
{
|
|
const QString filePath = homeTmpDir() + "fileFromHome";
|
|
QBuffer putData;
|
|
putData.setData("This is the put data");
|
|
QVERIFY(putData.open(QIODevice::ReadOnly));
|
|
KIO::TransferJob *job = KIO::storedPut(&putData, QUrl::fromLocalFile(filePath), 0600, KIO::Overwrite | KIO::HideProgressInfo);
|
|
|
|
QSignalSpy spyPercent(job, &KJob::percentChanged);
|
|
QVERIFY(spyPercent.isValid());
|
|
quint64 secsSinceEpoch = QDateTime::currentSecsSinceEpoch(); // Use second granularity, supported on all filesystems
|
|
QDateTime mtime = QDateTime::fromSecsSinceEpoch(secsSinceEpoch - 30); // 30 seconds ago
|
|
job->setModificationTime(mtime);
|
|
job->setUiDelegate(nullptr);
|
|
QVERIFY2(job->exec(), qPrintable(job->errorString()));
|
|
QFileInfo fileInfo(filePath);
|
|
QVERIFY(fileInfo.exists());
|
|
QCOMPARE(fileInfo.size(), (long long)putData.size());
|
|
QCOMPARE((int)fileInfo.permissions(), (int)(QFile::ReadOwner | QFile::WriteOwner | QFile::ReadUser | QFile::WriteUser));
|
|
QCOMPARE(fileInfo.lastModified(), mtime);
|
|
QVERIFY(!spyPercent.isEmpty());
|
|
}
|
|
|
|
void JobTest::storedPutIODeviceFile()
|
|
{
|
|
// Given a source file and a destination file
|
|
const QString src = homeTmpDir() + "fileFromHome";
|
|
createTestFile(src);
|
|
QVERIFY(QFile::exists(src));
|
|
QFile srcFile(src);
|
|
QVERIFY(srcFile.open(QIODevice::ReadOnly));
|
|
const QString dest = homeTmpDir() + "fileFromHome_copied";
|
|
QFile::remove(dest);
|
|
const QUrl destUrl = QUrl::fromLocalFile(dest);
|
|
|
|
// When using storedPut with the QFile as argument
|
|
KIO::StoredTransferJob *job = KIO::storedPut(&srcFile, destUrl, 0600, KIO::Overwrite | KIO::HideProgressInfo);
|
|
|
|
// Then the copy should succeed and the dest file exist
|
|
QVERIFY2(job->exec(), qPrintable(job->errorString()));
|
|
QVERIFY(QFile::exists(dest));
|
|
QCOMPARE(QFileInfo(src).size(), QFileInfo(dest).size());
|
|
QFile::remove(dest);
|
|
}
|
|
|
|
void JobTest::storedPutIODeviceTempFile()
|
|
{
|
|
// Create a temp file in the current dir.
|
|
QTemporaryFile tempFile(QStringLiteral("jobtest-tmp"));
|
|
QVERIFY(tempFile.open());
|
|
|
|
// Write something into the file.
|
|
QTextStream stream(&tempFile);
|
|
stream << QStringLiteral("This is the put data");
|
|
stream.flush();
|
|
QVERIFY(QFileInfo(tempFile).size() > 0);
|
|
|
|
const QString dest = homeTmpDir() + QLatin1String("tmpfile-dest");
|
|
const QUrl destUrl = QUrl::fromLocalFile(dest);
|
|
|
|
// QTemporaryFiles are open in ReadWrite mode,
|
|
// so we don't need to close and reopen,
|
|
// but we need to rewind to the beginning.
|
|
tempFile.seek(0);
|
|
auto job = KIO::storedPut(&tempFile, destUrl, -1);
|
|
|
|
QVERIFY2(job->exec(), qPrintable(job->errorString()));
|
|
QVERIFY(QFileInfo::exists(dest));
|
|
QCOMPARE(QFileInfo(dest).size(), QFileInfo(tempFile).size());
|
|
QVERIFY(QFile::remove(dest));
|
|
}
|
|
|
|
void JobTest::storedPutIODeviceFastDevice()
|
|
{
|
|
const QString filePath = homeTmpDir() + "fileFromHome";
|
|
const QUrl u = QUrl::fromLocalFile(filePath);
|
|
const QByteArray putDataContents = "This is the put data";
|
|
QBuffer putDataBuffer;
|
|
QVERIFY(putDataBuffer.open(QIODevice::ReadWrite));
|
|
|
|
KIO::StoredTransferJob *job = KIO::storedPut(&putDataBuffer, u, 0600, KIO::Overwrite | KIO::HideProgressInfo);
|
|
|
|
QSignalSpy spyPercent(job, &KJob::percentChanged);
|
|
QVERIFY(spyPercent.isValid());
|
|
quint64 secsSinceEpoch = QDateTime::currentSecsSinceEpoch(); // Use second granularity, supported on all filesystems
|
|
QDateTime mtime = QDateTime::fromSecsSinceEpoch(secsSinceEpoch - 30); // 30 seconds ago
|
|
job->setModificationTime(mtime);
|
|
job->setTotalSize(putDataContents.size());
|
|
job->setUiDelegate(nullptr);
|
|
job->setAsyncDataEnabled(true);
|
|
|
|
// Emit the readChannelFinished even before the job has had time to start
|
|
const auto pos = putDataBuffer.pos();
|
|
int size = putDataBuffer.write(putDataContents);
|
|
putDataBuffer.seek(pos);
|
|
Q_EMIT putDataBuffer.readChannelFinished();
|
|
|
|
QVERIFY2(job->exec(), qPrintable(job->errorString()));
|
|
QCOMPARE(size, putDataContents.size());
|
|
QCOMPARE(putDataBuffer.bytesAvailable(), 0);
|
|
|
|
QFileInfo fileInfo(filePath);
|
|
QVERIFY(fileInfo.exists());
|
|
QCOMPARE(fileInfo.size(), (long long)putDataContents.size());
|
|
QCOMPARE((int)fileInfo.permissions(), (int)(QFile::ReadOwner | QFile::WriteOwner | QFile::ReadUser | QFile::WriteUser));
|
|
QCOMPARE(fileInfo.lastModified(), mtime);
|
|
QVERIFY(!spyPercent.isEmpty());
|
|
}
|
|
|
|
void JobTest::storedPutIODeviceSlowDevice()
|
|
{
|
|
const QString filePath = homeTmpDir() + "fileFromHome";
|
|
const QUrl u = QUrl::fromLocalFile(filePath);
|
|
const QByteArray putDataContents = "This is the put data";
|
|
QBuffer putDataBuffer;
|
|
QVERIFY(putDataBuffer.open(QIODevice::ReadWrite));
|
|
|
|
KIO::StoredTransferJob *job = KIO::storedPut(&putDataBuffer, u, 0600, KIO::Overwrite | KIO::HideProgressInfo);
|
|
|
|
QSignalSpy spyPercent(job, &KJob::percentChanged);
|
|
QVERIFY(spyPercent.isValid());
|
|
quint64 secsSinceEpoch = QDateTime::currentSecsSinceEpoch(); // Use second granularity, supported on all filesystems
|
|
QDateTime mtime = QDateTime::fromSecsSinceEpoch(secsSinceEpoch - 30); // 30 seconds ago
|
|
job->setModificationTime(mtime);
|
|
job->setTotalSize(putDataContents.size());
|
|
job->setUiDelegate(nullptr);
|
|
job->setAsyncDataEnabled(true);
|
|
|
|
int size = 0;
|
|
const auto writeOnce = [&putDataBuffer, &size, putDataContents]() {
|
|
const auto pos = putDataBuffer.pos();
|
|
size += putDataBuffer.write(putDataContents);
|
|
putDataBuffer.seek(pos);
|
|
// qDebug() << "written" << size;
|
|
};
|
|
|
|
QTimer::singleShot(200, this, writeOnce);
|
|
QTimer::singleShot(400, this, writeOnce);
|
|
// Simulate the transfer is done
|
|
QTimer::singleShot(450, this, [&putDataBuffer]() {
|
|
Q_EMIT putDataBuffer.readChannelFinished();
|
|
});
|
|
|
|
QVERIFY2(job->exec(), qPrintable(job->errorString()));
|
|
QCOMPARE(size, putDataContents.size() * 2);
|
|
QCOMPARE(putDataBuffer.bytesAvailable(), 0);
|
|
|
|
QFileInfo fileInfo(filePath);
|
|
QVERIFY(fileInfo.exists());
|
|
QCOMPARE(fileInfo.size(), (long long)putDataContents.size() * 2);
|
|
QCOMPARE((int)fileInfo.permissions(), (int)(QFile::ReadOwner | QFile::WriteOwner | QFile::ReadUser | QFile::WriteUser));
|
|
QCOMPARE(fileInfo.lastModified(), mtime);
|
|
QVERIFY(!spyPercent.isEmpty());
|
|
}
|
|
|
|
void JobTest::storedPutIODeviceSlowDeviceBigChunk()
|
|
{
|
|
const QString filePath = homeTmpDir() + "fileFromHome";
|
|
const QUrl u = QUrl::fromLocalFile(filePath);
|
|
const QByteArray putDataContents(300000, 'K'); // Make sure the 300000 is bigger than MAX_READ_BUF_SIZE
|
|
QBuffer putDataBuffer;
|
|
QVERIFY(putDataBuffer.open(QIODevice::ReadWrite));
|
|
|
|
KIO::StoredTransferJob *job = KIO::storedPut(&putDataBuffer, u, 0600, KIO::Overwrite | KIO::HideProgressInfo);
|
|
|
|
QSignalSpy spyPercent(job, &KJob::percentChanged);
|
|
QVERIFY(spyPercent.isValid());
|
|
quint64 secsSinceEpoch = QDateTime::currentSecsSinceEpoch(); // Use second granularity, supported on all filesystems
|
|
QDateTime mtime = QDateTime::fromSecsSinceEpoch(secsSinceEpoch - 30); // 30 seconds ago
|
|
job->setModificationTime(mtime);
|
|
job->setTotalSize(putDataContents.size());
|
|
job->setUiDelegate(nullptr);
|
|
job->setAsyncDataEnabled(true);
|
|
|
|
int size = 0;
|
|
const auto writeOnce = [&putDataBuffer, &size, putDataContents]() {
|
|
const auto pos = putDataBuffer.pos();
|
|
size += putDataBuffer.write(putDataContents);
|
|
putDataBuffer.seek(pos);
|
|
// qDebug() << "written" << size;
|
|
};
|
|
|
|
QTimer::singleShot(200, this, writeOnce);
|
|
// Simulate the transfer is done
|
|
QTimer::singleShot(450, this, [&putDataBuffer]() {
|
|
Q_EMIT putDataBuffer.readChannelFinished();
|
|
});
|
|
|
|
QVERIFY2(job->exec(), qPrintable(job->errorString()));
|
|
QCOMPARE(size, putDataContents.size());
|
|
QCOMPARE(putDataBuffer.bytesAvailable(), 0);
|
|
|
|
QFileInfo fileInfo(filePath);
|
|
QVERIFY(fileInfo.exists());
|
|
QCOMPARE(fileInfo.size(), (long long)putDataContents.size());
|
|
QCOMPARE((int)fileInfo.permissions(), (int)(QFile::ReadOwner | QFile::WriteOwner | QFile::ReadUser | QFile::WriteUser));
|
|
QCOMPARE(fileInfo.lastModified(), mtime);
|
|
QVERIFY(!spyPercent.isEmpty());
|
|
}
|
|
|
|
void JobTest::asyncStoredPutReadyReadAfterFinish()
|
|
{
|
|
const QString filePath = homeTmpDir() + "fileFromHome";
|
|
const QUrl u = QUrl::fromLocalFile(filePath);
|
|
|
|
QBuffer putDataBuffer;
|
|
QVERIFY(putDataBuffer.open(QIODevice::ReadWrite));
|
|
|
|
KIO::StoredTransferJob *job = KIO::storedPut(&putDataBuffer, u, 0600, KIO::Overwrite | KIO::HideProgressInfo);
|
|
job->setAsyncDataEnabled(true);
|
|
|
|
bool jobFinished = false;
|
|
|
|
connect(job, &KJob::finished, this, [&jobFinished, &putDataBuffer] {
|
|
putDataBuffer.readyRead();
|
|
jobFinished = true;
|
|
});
|
|
|
|
QTimer::singleShot(200, this, [job]() {
|
|
job->kill();
|
|
});
|
|
|
|
QTRY_VERIFY(jobFinished);
|
|
}
|
|
|
|
static QHash<QString, QString> getSampleXattrs()
|
|
{
|
|
QHash<QString, QString> attrs;
|
|
attrs["user.name with space"] = "value with spaces";
|
|
attrs["user.baloo.rating"] = "1";
|
|
attrs["user.fnewLine"] = "line1\\nline2";
|
|
attrs["user.flistNull"] = "item1\\0item2";
|
|
attrs["user.fattr.with.a.lot.of.namespaces"] = "true";
|
|
attrs["user.fempty"] = "";
|
|
return attrs;
|
|
}
|
|
|
|
bool JobTest::checkXattrFsSupport(const QString &dir)
|
|
{
|
|
const QString writeTest = dir + "/fsXattrTestFile";
|
|
createTestFile(writeTest);
|
|
bool ret = setXattr(writeTest);
|
|
QFile::remove(writeTest);
|
|
return ret;
|
|
}
|
|
|
|
bool JobTest::setXattr(const QString &dest)
|
|
{
|
|
QProcess xattrWriter;
|
|
xattrWriter.setProcessChannelMode(QProcess::MergedChannels);
|
|
|
|
QHash<QString, QString> attrs = getSampleXattrs();
|
|
QHashIterator<QString, QString> i(attrs);
|
|
while (i.hasNext()) {
|
|
i.next();
|
|
QStringList arguments = m_setXattrFormatArgs(i.key(), i.value(), dest);
|
|
xattrWriter.start(m_setXattrCmd, arguments);
|
|
xattrWriter.waitForStarted();
|
|
xattrWriter.waitForFinished(-1);
|
|
if (xattrWriter.exitStatus() != QProcess::NormalExit) {
|
|
return false;
|
|
}
|
|
QList<QByteArray> resultdest = xattrWriter.readAllStandardOutput().split('\n');
|
|
if (!resultdest[0].isEmpty()) {
|
|
qWarning() << "Error writing user xattr. Xattr copy tests will be disabled.";
|
|
qDebug() << resultdest;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
QList<QByteArray> JobTest::readXattr(const QString &src)
|
|
{
|
|
QProcess xattrReader;
|
|
xattrReader.setProcessChannelMode(QProcess::MergedChannels);
|
|
|
|
QStringList arguments;
|
|
char outputSeparator = '\n';
|
|
// Linux
|
|
if (m_getXattrCmd.endsWith("getfattr")) {
|
|
arguments = QStringList{"-d", src};
|
|
}
|
|
// BSD
|
|
else if (m_getXattrCmd.endsWith("lsextattr")) {
|
|
arguments = QStringList{"-q", "user", src};
|
|
outputSeparator = '\t';
|
|
}
|
|
// MacOS
|
|
else {
|
|
arguments = QStringList{"-l", src};
|
|
}
|
|
|
|
xattrReader.start(m_getXattrCmd, arguments);
|
|
xattrReader.waitForFinished();
|
|
QList<QByteArray> result = xattrReader.readAllStandardOutput().split(outputSeparator);
|
|
if (m_getXattrCmd.endsWith("getfattr")) {
|
|
if (result.size() > 1) {
|
|
// Line 1 is the file name
|
|
result.removeAt(1);
|
|
}
|
|
} else if (m_getXattrCmd.endsWith("lsextattr")) {
|
|
// cut off trailing \n
|
|
result.last().chop(1);
|
|
// lsextattr does not sort its output
|
|
std::sort(result.begin(), result.end());
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void JobTest::compareXattr(const QString &src, const QString &dest)
|
|
{
|
|
auto srcAttrs = readXattr(src);
|
|
auto dstAttrs = readXattr(dest);
|
|
QCOMPARE(dstAttrs, srcAttrs);
|
|
}
|
|
|
|
void JobTest::copyLocalFile(const QString &src, const QString &dest)
|
|
{
|
|
const QUrl u = QUrl::fromLocalFile(src);
|
|
const QUrl d = QUrl::fromLocalFile(dest);
|
|
|
|
const int perms = 0666;
|
|
// copy the file with file_copy
|
|
KIO::Job *job = KIO::file_copy(u, d, perms, KIO::HideProgressInfo);
|
|
job->setUiDelegate(nullptr);
|
|
QVERIFY2(job->exec(), qPrintable(job->errorString()));
|
|
QVERIFY(QFile::exists(dest));
|
|
QVERIFY(QFile::exists(src)); // still there
|
|
QCOMPARE(int(QFileInfo(dest).permissions()), int(0x6666));
|
|
compareXattr(src, dest);
|
|
|
|
{
|
|
// check that the timestamp is the same (#24443)
|
|
// Note: this only works because of copy() in kio_file.
|
|
// The datapump solution ignores mtime, the app has to call FileCopyJob::setModificationTime()
|
|
QFileInfo srcInfo(src);
|
|
QFileInfo destInfo(dest);
|
|
#ifdef Q_OS_WIN
|
|
// win32 time may differs in msec part
|
|
QCOMPARE(srcInfo.lastModified().toString("dd.MM.yyyy hh:mm"), destInfo.lastModified().toString("dd.MM.yyyy hh:mm"));
|
|
#else
|
|
QCOMPARE(srcInfo.lastModified(), destInfo.lastModified());
|
|
#endif
|
|
}
|
|
|
|
// cleanup and retry with KIO::copy()
|
|
QFile::remove(dest);
|
|
auto *copyjob = KIO::copy(u, d, KIO::HideProgressInfo);
|
|
QSignalSpy spyCopyingDone(copyjob, &KIO::CopyJob::copyingDone);
|
|
copyjob->setUiDelegate(nullptr);
|
|
copyjob->setUiDelegateExtension(nullptr);
|
|
QVERIFY2(copyjob->exec(), qPrintable(copyjob->errorString()));
|
|
QVERIFY(QFile::exists(dest));
|
|
QVERIFY(QFile::exists(src)); // still there
|
|
compareXattr(src, dest);
|
|
{
|
|
// check that the timestamp is the same (#24443)
|
|
QFileInfo srcInfo(src);
|
|
QFileInfo destInfo(dest);
|
|
#ifdef Q_OS_WIN
|
|
// win32 time may differs in msec part
|
|
QCOMPARE(srcInfo.lastModified().toString("dd.MM.yyyy hh:mm"), destInfo.lastModified().toString("dd.MM.yyyy hh:mm"));
|
|
#else
|
|
QCOMPARE(srcInfo.lastModified(), destInfo.lastModified());
|
|
#endif
|
|
}
|
|
QCOMPARE(spyCopyingDone.count(), 1);
|
|
|
|
QCOMPARE(copyjob->totalAmount(KJob::Files), 1);
|
|
QCOMPARE(copyjob->totalAmount(KJob::Directories), 0);
|
|
QCOMPARE(copyjob->processedAmount(KJob::Files), 1);
|
|
QCOMPARE(copyjob->processedAmount(KJob::Directories), 0);
|
|
QCOMPARE(copyjob->percent(), 100);
|
|
|
|
// cleanup and retry with KIO::copyAs()
|
|
QFile::remove(dest);
|
|
job = KIO::copyAs(u, d, KIO::HideProgressInfo);
|
|
job->setUiDelegate(nullptr);
|
|
job->setUiDelegateExtension(nullptr);
|
|
QVERIFY2(job->exec(), qPrintable(job->errorString()));
|
|
QVERIFY(QFile::exists(dest));
|
|
QVERIFY(QFile::exists(src)); // still there
|
|
compareXattr(src, dest);
|
|
|
|
// Do it again, with Overwrite.
|
|
job = KIO::copyAs(u, d, KIO::Overwrite | KIO::HideProgressInfo);
|
|
job->setUiDelegate(nullptr);
|
|
job->setUiDelegateExtension(nullptr);
|
|
QVERIFY2(job->exec(), qPrintable(job->errorString()));
|
|
QVERIFY(QFile::exists(dest));
|
|
QVERIFY(QFile::exists(src)); // still there
|
|
compareXattr(src, dest);
|
|
|
|
// Do it again, without Overwrite (should fail).
|
|
job = KIO::copyAs(u, d, KIO::HideProgressInfo);
|
|
job->setUiDelegate(nullptr);
|
|
job->setUiDelegateExtension(nullptr);
|
|
QVERIFY(!job->exec());
|
|
|
|
// Clean up
|
|
QFile::remove(src);
|
|
QFile::remove(dest);
|
|
}
|
|
|
|
void JobTest::copyLocalDirectory(const QString &src, const QString &_dest, int flags)
|
|
{
|
|
QVERIFY(QFileInfo(src).isDir());
|
|
QVERIFY(QFileInfo(src + "/testfile").isFile());
|
|
QUrl u = QUrl::fromLocalFile(src);
|
|
QString dest(_dest);
|
|
QUrl d = QUrl::fromLocalFile(dest);
|
|
if (flags & AlreadyExists) {
|
|
QVERIFY(QFile::exists(dest));
|
|
} else {
|
|
QVERIFY(!QFile::exists(dest));
|
|
}
|
|
|
|
KIO::Job *job = KIO::copy(u, d, KIO::HideProgressInfo);
|
|
job->setUiDelegate(nullptr);
|
|
job->setUiDelegateExtension(nullptr);
|
|
QVERIFY2(job->exec(), qPrintable(job->errorString()));
|
|
QVERIFY(QFile::exists(dest));
|
|
QVERIFY(QFileInfo(dest).isDir());
|
|
QVERIFY(QFileInfo(dest + "/testfile").isFile());
|
|
QVERIFY(QFile::exists(src)); // still there
|
|
|
|
if (flags & AlreadyExists) {
|
|
dest += '/' + u.fileName();
|
|
// qDebug() << "Expecting dest=" << dest;
|
|
}
|
|
|
|
// CopyJob::setNextDirAttribute isn't implemented for Windows currently.
|
|
#ifndef Q_OS_WIN
|
|
{
|
|
// Check that the timestamp is the same (#24443)
|
|
QFileInfo srcInfo(src);
|
|
QFileInfo destInfo(dest);
|
|
QCOMPARE(srcInfo.lastModified(), destInfo.lastModified());
|
|
}
|
|
#endif
|
|
|
|
QCOMPARE(job->totalAmount(KJob::Files), 2); // testfile and testlink
|
|
QCOMPARE(job->totalAmount(KJob::Directories), 1);
|
|
QCOMPARE(job->processedAmount(KJob::Files), 2);
|
|
QCOMPARE(job->processedAmount(KJob::Directories), 1);
|
|
QCOMPARE(job->percent(), 100);
|
|
|
|
// Do it again, with Overwrite.
|
|
// Use copyAs, we don't want a subdir inside d.
|
|
job = KIO::copyAs(u, d, KIO::HideProgressInfo | KIO::Overwrite);
|
|
job->setUiDelegate(nullptr);
|
|
job->setUiDelegateExtension(nullptr);
|
|
QVERIFY2(job->exec(), qPrintable(job->errorString()));
|
|
|
|
QCOMPARE(job->totalAmount(KJob::Files), 2); // testfile and testlink
|
|
QCOMPARE(job->totalAmount(KJob::Directories), 1);
|
|
QCOMPARE(job->processedAmount(KJob::Files), 2);
|
|
QCOMPARE(job->processedAmount(KJob::Directories), 1);
|
|
QCOMPARE(job->percent(), 100);
|
|
|
|
// Do it again, without Overwrite (should fail).
|
|
job = KIO::copyAs(u, d, KIO::HideProgressInfo);
|
|
job->setUiDelegate(nullptr);
|
|
job->setUiDelegateExtension(nullptr);
|
|
QVERIFY(!job->exec());
|
|
}
|
|
|
|
#ifndef Q_OS_WIN
|
|
static QString linkTarget(const QString &path)
|
|
{
|
|
// Use readlink on Unix because symLinkTarget turns relative targets into absolute (#352927)
|
|
char linkTargetBuffer[4096];
|
|
const int n = readlink(QFile::encodeName(path).constData(), linkTargetBuffer, sizeof(linkTargetBuffer) - 1);
|
|
if (n != -1) {
|
|
linkTargetBuffer[n] = 0;
|
|
}
|
|
return QFile::decodeName(linkTargetBuffer);
|
|
}
|
|
|
|
static void copyLocalSymlink(const QString &src, const QString &dest, const QString &expectedLinkTarget)
|
|
{
|
|
QT_STATBUF buf;
|
|
QVERIFY(QT_LSTAT(QFile::encodeName(src).constData(), &buf) == 0);
|
|
QUrl u = QUrl::fromLocalFile(src);
|
|
QUrl d = QUrl::fromLocalFile(dest);
|
|
|
|
// copy the symlink
|
|
KIO::Job *job = KIO::copy(u, d, KIO::HideProgressInfo);
|
|
job->setUiDelegate(nullptr);
|
|
job->setUiDelegateExtension(nullptr);
|
|
QVERIFY2(job->exec(), qPrintable(QString::number(job->error())));
|
|
QVERIFY(QT_LSTAT(QFile::encodeName(dest).constData(), &buf) == 0); // dest exists
|
|
QCOMPARE(linkTarget(dest), expectedLinkTarget);
|
|
|
|
// cleanup
|
|
QFile::remove(dest);
|
|
}
|
|
#endif
|
|
|
|
void JobTest::copyFileToSamePartition()
|
|
{
|
|
const QString homeDir = homeTmpDir();
|
|
const QString filePath = homeDir + "fileFromHome";
|
|
const QString dest = homeDir + "fileFromHome_copied";
|
|
createTestFile(filePath);
|
|
if (checkXattrFsSupport(homeDir)) {
|
|
setXattr(filePath);
|
|
}
|
|
copyLocalFile(filePath, dest);
|
|
}
|
|
|
|
void JobTest::copyDirectoryToSamePartition()
|
|
{
|
|
// qDebug();
|
|
const QString src = homeTmpDir() + "dirFromHome";
|
|
const QString dest = homeTmpDir() + "dirFromHome_copied";
|
|
createTestDirectory(src);
|
|
copyLocalDirectory(src, dest);
|
|
}
|
|
|
|
void JobTest::copyDirectoryToExistingDirectory()
|
|
{
|
|
// qDebug();
|
|
// just the same as copyDirectoryToSamePartition, but this time dest exists.
|
|
// So we get a subdir, "dirFromHome_copy/dirFromHome"
|
|
const QString src = homeTmpDir() + "dirFromHome";
|
|
const QString dest = homeTmpDir() + "dirFromHome_copied";
|
|
createTestDirectory(src);
|
|
createTestDirectory(dest);
|
|
copyLocalDirectory(src, dest, AlreadyExists);
|
|
}
|
|
|
|
void JobTest::copyDirectoryToExistingSymlinkedDirectory()
|
|
{
|
|
// qDebug();
|
|
// just the same as copyDirectoryToSamePartition, but this time dest is a symlink.
|
|
// So we get a file in the symlink dir, "dirFromHome_symlink/dirFromHome" and
|
|
// "dirFromHome_symOrigin/dirFromHome"
|
|
const QString src = homeTmpDir() + "dirFromHome";
|
|
const QString origSymlink = homeTmpDir() + "dirFromHome_symOrigin";
|
|
const QString targetSymlink = homeTmpDir() + "dirFromHome_symlink";
|
|
createTestDirectory(src);
|
|
createTestDirectory(origSymlink);
|
|
|
|
bool ok = KIOPrivate::createSymlink(origSymlink, targetSymlink);
|
|
if (!ok) {
|
|
qFatal("couldn't create symlink: %s", strerror(errno));
|
|
}
|
|
QVERIFY(QFileInfo(targetSymlink).isSymLink());
|
|
QVERIFY(QFileInfo(targetSymlink).isDir());
|
|
|
|
KIO::Job *job = KIO::copy(QUrl::fromLocalFile(src), QUrl::fromLocalFile(targetSymlink), KIO::HideProgressInfo);
|
|
job->setUiDelegate(nullptr);
|
|
job->setUiDelegateExtension(nullptr);
|
|
QVERIFY2(job->exec(), qPrintable(job->errorString()));
|
|
QVERIFY(QFile::exists(src)); // still there
|
|
|
|
// file is visible in both places due to symlink
|
|
QVERIFY(QFileInfo(origSymlink + "/dirFromHome").isDir());
|
|
;
|
|
QVERIFY(QFileInfo(targetSymlink + "/dirFromHome").isDir());
|
|
QVERIFY(QDir(origSymlink).removeRecursively());
|
|
QVERIFY(QFile::remove(targetSymlink));
|
|
}
|
|
|
|
void JobTest::copyFileToOtherPartition()
|
|
{
|
|
// qDebug();
|
|
const QString homeDir = homeTmpDir();
|
|
const QString otherHomeDir = otherTmpDir();
|
|
const QString filePath = homeDir + "fileFromHome";
|
|
const QString dest = otherHomeDir + "fileFromHome_copied";
|
|
bool canRead = checkXattrFsSupport(homeDir);
|
|
bool canWrite = checkXattrFsSupport(otherHomeDir);
|
|
createTestFile(filePath);
|
|
if (canRead && canWrite) {
|
|
setXattr(filePath);
|
|
}
|
|
copyLocalFile(filePath, dest);
|
|
}
|
|
|
|
// Same partition doesn't matter as much as copying to the same filesystem type
|
|
// to be sure it supports the same permissions
|
|
void JobTest::testCopyFilePermissionsToSamePartition()
|
|
{
|
|
#if defined(Q_OS_UNIX)
|
|
const QString homeDir = homeTmpDir();
|
|
const QString src = homeDir + "fileFromHome";
|
|
const QUrl u = QUrl::fromLocalFile(src);
|
|
createTestFile(src);
|
|
|
|
const QByteArray src_c = QFile::encodeName(src).constData();
|
|
QT_STATBUF src_buff;
|
|
QCOMPARE(QT_LSTAT(src_c.constData(), &src_buff), 0); // Exists
|
|
// Default system permissions for newly created files, umask et al.
|
|
const mode_t systemDefaultPerms = src_buff.st_mode;
|
|
|
|
const QString dest = homeDir + "fileFromHome_copied";
|
|
const QUrl d = QUrl::fromLocalFile(dest);
|
|
|
|
const QByteArray dest_c = QFile::encodeName(dest).constData();
|
|
QT_STATBUF dest_buff;
|
|
|
|
// Copy the file, with -1 permissions (i.e. don't touch dest permissions)
|
|
auto copyStat = [&](const mode_t perms) {
|
|
KIO::Job *job = KIO::file_copy(u, d, perms, KIO::HideProgressInfo);
|
|
job->setUiDelegate(nullptr);
|
|
QVERIFY2(job->exec(), qPrintable(job->errorString()));
|
|
QCOMPARE(QT_LSTAT(dest_c.constData(), &dest_buff), 0);
|
|
};
|
|
|
|
copyStat(-1);
|
|
// dest should have system default permissions
|
|
QCOMPARE(dest_buff.st_mode, systemDefaultPerms);
|
|
|
|
QVERIFY(QFile::remove(dest));
|
|
// Change src permissions to non-default
|
|
QCOMPARE(::chmod(src_c.constData(), S_IRUSR), 0);
|
|
// Copy the file again, permissions -1 (i.e. don't touch dest permissions)
|
|
copyStat(-1);
|
|
// dest should have system default permissions, not src's ones
|
|
QCOMPARE(dest_buff.st_mode, systemDefaultPerms);
|
|
|
|
QVERIFY(QFile::remove(dest));
|
|
// Copy the file again, explicitly setting the permissions to the src ones
|
|
copyStat(src_buff.st_mode);
|
|
// dest should have same permissions as src
|
|
QCOMPARE(dest_buff.st_mode, dest_buff.st_mode);
|
|
|
|
QVERIFY(QFile::remove(dest));
|
|
// Copy the file again, explicitly setting some other permissions
|
|
copyStat(S_IWUSR);
|
|
// dest should have S_IWUSR
|
|
QCOMPARE(dest_buff.st_mode & 0777, S_IWUSR);
|
|
|
|
// Clean up, the weird permissions used above mess with the next
|
|
// unit tests
|
|
QVERIFY(QFile::remove(dest));
|
|
QVERIFY(QFile::remove(src));
|
|
#endif
|
|
}
|
|
|
|
void JobTest::copyDirectoryToOtherPartition()
|
|
{
|
|
// qDebug();
|
|
const QString src = homeTmpDir() + "dirFromHome";
|
|
const QString dest = otherTmpDir() + "dirFromHome_copied";
|
|
createTestDirectory(src);
|
|
copyLocalDirectory(src, dest);
|
|
}
|
|
|
|
void JobTest::copyRelativeSymlinkToSamePartition() // #352927
|
|
{
|
|
#ifdef Q_OS_WIN
|
|
QSKIP("Skipping symlink test on Windows");
|
|
#else
|
|
const QString filePath = homeTmpDir() + "testlink";
|
|
const QString dest = homeTmpDir() + "testlink_copied";
|
|
createTestSymlink(filePath, "relative");
|
|
copyLocalSymlink(filePath, dest, QStringLiteral("relative"));
|
|
QFile::remove(filePath);
|
|
#endif
|
|
}
|
|
|
|
void JobTest::copyAbsoluteSymlinkToOtherPartition()
|
|
{
|
|
#ifdef Q_OS_WIN
|
|
QSKIP("Skipping symlink test on Windows");
|
|
#else
|
|
const QString filePath = homeTmpDir() + "testlink";
|
|
const QString dest = otherTmpDir() + "testlink_copied";
|
|
createTestSymlink(filePath, QFile::encodeName(homeTmpDir()));
|
|
copyLocalSymlink(filePath, dest, homeTmpDir());
|
|
QFile::remove(filePath);
|
|
#endif
|
|
}
|
|
|
|
void JobTest::copyFolderWithUnaccessibleSubfolder()
|
|
{
|
|
#ifdef Q_OS_WIN
|
|
QSKIP("Skipping unaccessible folder test on Windows, cannot remove all permissions from a folder");
|
|
#endif
|
|
QTemporaryDir dir(homeTmpDir() + "UnaccessibleSubfolderTest");
|
|
QVERIFY(dir.isValid());
|
|
const QString src_dir = dir.path() + "srcHome";
|
|
const QString dst_dir = dir.path() + "dstHome";
|
|
|
|
QDir().remove(src_dir);
|
|
QDir().remove(dst_dir);
|
|
|
|
createTestDirectory(src_dir);
|
|
createTestDirectory(src_dir + "/folder1");
|
|
QString inaccessible = src_dir + "/folder1/inaccessible";
|
|
|
|
createTestDirectory(inaccessible);
|
|
|
|
QFile(inaccessible).setPermissions(QFile::Permissions()); // Make it inaccessible
|
|
// Copying should throw some warnings, as it cannot access some folders
|
|
|
|
ScopedCleaner cleaner([&] {
|
|
QFile(inaccessible).setPermissions(QFile::Permissions(QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner));
|
|
|
|
qDebug() << "Cleaning up" << src_dir << "and" << dst_dir;
|
|
KIO::DeleteJob *deljob1 = KIO::del(QUrl::fromLocalFile(src_dir), KIO::HideProgressInfo);
|
|
deljob1->setUiDelegate(nullptr); // no skip dialog, thanks
|
|
const bool job1OK = deljob1->exec();
|
|
QVERIFY(job1OK);
|
|
|
|
KIO::DeleteJob *deljob2 = KIO::del(QUrl::fromLocalFile(dst_dir), KIO::HideProgressInfo);
|
|
deljob2->setUiDelegate(nullptr); // no skip dialog, thanks
|
|
const bool job2OK = deljob2->exec();
|
|
QVERIFY(job2OK);
|
|
|
|
qDebug() << "Result:" << job1OK << job2OK;
|
|
});
|
|
|
|
KIO::CopyJob *job = KIO::copy(QUrl::fromLocalFile(src_dir), QUrl::fromLocalFile(dst_dir), KIO::HideProgressInfo);
|
|
|
|
QSignalSpy spy(job, &KJob::warning);
|
|
job->setUiDelegate(nullptr); // no skip dialog, thanks
|
|
QVERIFY2(job->exec(), qPrintable(job->errorString()));
|
|
|
|
QCOMPARE(job->totalAmount(KJob::Files), 4); // testfile, testlink, folder1/testlink, folder1/testfile
|
|
QCOMPARE(job->totalAmount(KJob::Directories), 3); // srcHome, srcHome/folder1, srcHome/folder1/inaccessible
|
|
QCOMPARE(job->processedAmount(KJob::Files), 4);
|
|
QCOMPARE(job->processedAmount(KJob::Directories), 3);
|
|
QCOMPARE(job->percent(), 100);
|
|
|
|
QCOMPARE(spy.count(), 1); // one warning should be emitted by the copy job
|
|
}
|
|
|
|
void JobTest::copyDataUrl()
|
|
{
|
|
// GIVEN
|
|
const QString dst_dir = homeTmpDir();
|
|
QVERIFY(!QFileInfo::exists(dst_dir + "/data"));
|
|
// WHEN
|
|
KIO::CopyJob *job = KIO::copy(QUrl("data:,Hello%2C%20World!"), QUrl::fromLocalFile(dst_dir), KIO::HideProgressInfo);
|
|
QVERIFY2(job->exec(), qPrintable(job->errorString()));
|
|
// THEN
|
|
const QFileInfo fileInfo(dst_dir + "/data");
|
|
QVERIFY(fileInfo.isFile());
|
|
QCOMPARE(fileInfo.size(), 13);
|
|
QFile::remove(dst_dir + "/data");
|
|
}
|
|
|
|
void JobTest::suspendFileCopy()
|
|
{
|
|
const QString filePath = homeTmpDir() + "fileFromHome";
|
|
const QString dest = homeTmpDir() + "fileFromHome_copied";
|
|
createTestFile(filePath);
|
|
|
|
const QUrl u = QUrl::fromLocalFile(filePath);
|
|
const QUrl d = QUrl::fromLocalFile(dest);
|
|
KIO::Job *job = KIO::file_copy(u, d, KIO::HideProgressInfo);
|
|
QSignalSpy spyResult(job, &KJob::result);
|
|
job->setUiDelegate(nullptr);
|
|
job->setUiDelegateExtension(nullptr);
|
|
QVERIFY(job->suspend());
|
|
QVERIFY(!spyResult.wait(300));
|
|
QVERIFY(job->resume());
|
|
QVERIFY2(job->exec(), qPrintable(job->errorString()));
|
|
QVERIFY(QFile::exists(dest));
|
|
QFile::remove(dest);
|
|
}
|
|
|
|
void JobTest::suspendCopy()
|
|
{
|
|
const QString filePath = homeTmpDir() + "fileFromHome";
|
|
const QString dest = homeTmpDir() + "fileFromHome_copied";
|
|
createTestFile(filePath);
|
|
|
|
const QUrl u = QUrl::fromLocalFile(filePath);
|
|
const QUrl d = QUrl::fromLocalFile(dest);
|
|
KIO::Job *job = KIO::copy(u, d, KIO::HideProgressInfo);
|
|
QSignalSpy spyResult(job, &KJob::result);
|
|
job->setUiDelegate(nullptr);
|
|
job->setUiDelegateExtension(nullptr);
|
|
QVERIFY(job->suspend());
|
|
QVERIFY(!spyResult.wait(300));
|
|
QVERIFY(job->resume());
|
|
QVERIFY2(job->exec(), qPrintable(job->errorString()));
|
|
QVERIFY(QFile::exists(dest));
|
|
QFile::remove(dest);
|
|
}
|
|
|
|
void JobTest::moveLocalFile(const QString &src, const QString &dest)
|
|
{
|
|
QVERIFY(QFile::exists(src));
|
|
QUrl u = QUrl::fromLocalFile(src);
|
|
QUrl d = QUrl::fromLocalFile(dest);
|
|
|
|
// move the file with file_move
|
|
KIO::Job *job = KIO::file_move(u, d, 0666, KIO::HideProgressInfo);
|
|
job->setUiDelegate(nullptr);
|
|
QVERIFY2(job->exec(), qPrintable(job->errorString()));
|
|
QVERIFY(QFile::exists(dest));
|
|
QVERIFY(!QFile::exists(src)); // not there anymore
|
|
QCOMPARE(int(QFileInfo(dest).permissions()), int(0x6666));
|
|
|
|
// move it back with KIO::move()
|
|
job = KIO::move(d, u, KIO::HideProgressInfo);
|
|
job->setUiDelegate(nullptr);
|
|
job->setUiDelegateExtension(nullptr);
|
|
QVERIFY2(job->exec(), qPrintable(job->errorString()));
|
|
QVERIFY(!QFile::exists(dest));
|
|
QVERIFY(QFile::exists(src)); // it's back
|
|
}
|
|
|
|
static void moveLocalSymlink(const QString &src, const QString &dest)
|
|
{
|
|
QT_STATBUF buf;
|
|
QVERIFY(QT_LSTAT(QFile::encodeName(src).constData(), &buf) == 0);
|
|
QUrl u = QUrl::fromLocalFile(src);
|
|
QUrl d = QUrl::fromLocalFile(dest);
|
|
|
|
// move the symlink with move, NOT with file_move
|
|
KIO::Job *job = KIO::move(u, d, KIO::HideProgressInfo);
|
|
job->setUiDelegate(nullptr);
|
|
job->setUiDelegateExtension(nullptr);
|
|
QVERIFY2(job->exec(), qPrintable(job->errorString()));
|
|
QVERIFY(QT_LSTAT(QFile::encodeName(dest).constData(), &buf) == 0);
|
|
QVERIFY(!QFile::exists(src)); // not there anymore
|
|
|
|
// move it back with KIO::move()
|
|
job = KIO::move(d, u, KIO::HideProgressInfo);
|
|
job->setUiDelegate(nullptr);
|
|
job->setUiDelegateExtension(nullptr);
|
|
QVERIFY2(job->exec(), qPrintable(job->errorString()));
|
|
QVERIFY(QT_LSTAT(QFile::encodeName(dest).constData(), &buf) != 0); // doesn't exist anymore
|
|
QVERIFY(QT_LSTAT(QFile::encodeName(src).constData(), &buf) == 0); // it's back
|
|
}
|
|
|
|
void JobTest::moveLocalDirectory(const QString &src, const QString &dest)
|
|
{
|
|
qDebug() << src << " " << dest;
|
|
QVERIFY(QFile::exists(src));
|
|
QVERIFY(QFileInfo(src).isDir());
|
|
QVERIFY(QFileInfo(src + "/testfile").isFile());
|
|
#ifndef Q_OS_WIN
|
|
QVERIFY(QFileInfo(src + "/testlink").isSymLink());
|
|
#endif
|
|
QUrl u = QUrl::fromLocalFile(src);
|
|
QUrl d = QUrl::fromLocalFile(dest);
|
|
|
|
KIO::Job *job = KIO::move(u, d, KIO::HideProgressInfo);
|
|
job->setUiDelegate(nullptr);
|
|
job->setUiDelegateExtension(nullptr);
|
|
QVERIFY2(job->exec(), qPrintable(job->errorString()));
|
|
QVERIFY(QFile::exists(dest));
|
|
QVERIFY(QFileInfo(dest).isDir());
|
|
QVERIFY(QFileInfo(dest + "/testfile").isFile());
|
|
QVERIFY(!QFile::exists(src)); // not there anymore
|
|
#ifndef Q_OS_WIN
|
|
QVERIFY(QFileInfo(dest + "/testlink").isSymLink());
|
|
#endif
|
|
}
|
|
|
|
void JobTest::moveFileToSamePartition()
|
|
{
|
|
qDebug();
|
|
const QString filePath = homeTmpDir() + "fileFromHome";
|
|
const QString dest = homeTmpDir() + "fileFromHome_moved";
|
|
createTestFile(filePath);
|
|
moveLocalFile(filePath, dest);
|
|
}
|
|
|
|
void JobTest::moveDirectoryToSamePartition()
|
|
{
|
|
qDebug();
|
|
const QString src = homeTmpDir() + "dirFromHome";
|
|
const QString dest = homeTmpDir() + "dirFromHome_moved";
|
|
createTestDirectory(src);
|
|
moveLocalDirectory(src, dest);
|
|
}
|
|
|
|
void JobTest::moveDirectoryIntoItself()
|
|
{
|
|
qDebug();
|
|
const QString src = homeTmpDir() + "dirFromHome";
|
|
const QString dest = src + "/foo";
|
|
createTestDirectory(src);
|
|
QVERIFY(QFile::exists(src));
|
|
QUrl u = QUrl::fromLocalFile(src);
|
|
QUrl d = QUrl::fromLocalFile(dest);
|
|
KIO::CopyJob *job = KIO::move(u, d);
|
|
QVERIFY(!job->exec());
|
|
QCOMPARE(job->error(), (int)KIO::ERR_CANNOT_MOVE_INTO_ITSELF);
|
|
QCOMPARE(job->errorString(), i18n("A folder cannot be moved into itself"));
|
|
QDir(dest).removeRecursively();
|
|
}
|
|
|
|
void JobTest::moveFileToOtherPartition()
|
|
{
|
|
qDebug();
|
|
const QString filePath = homeTmpDir() + "fileFromHome";
|
|
const QString dest = otherTmpDir() + "fileFromHome_moved";
|
|
createTestFile(filePath);
|
|
moveLocalFile(filePath, dest);
|
|
}
|
|
|
|
void JobTest::moveSymlinkToOtherPartition()
|
|
{
|
|
#ifndef Q_OS_WIN
|
|
qDebug();
|
|
const QString filePath = homeTmpDir() + "testlink";
|
|
const QString dest = otherTmpDir() + "testlink_moved";
|
|
createTestSymlink(filePath);
|
|
moveLocalSymlink(filePath, dest);
|
|
#endif
|
|
}
|
|
|
|
void JobTest::moveDirectoryToOtherPartition()
|
|
{
|
|
qDebug();
|
|
#ifndef Q_OS_WIN
|
|
const QString src = homeTmpDir() + "dirFromHome";
|
|
const QString dest = otherTmpDir() + "dirFromHome_moved";
|
|
createTestDirectory(src);
|
|
moveLocalDirectory(src, dest);
|
|
#endif
|
|
}
|
|
|
|
struct CleanupInaccessibleSubdir {
|
|
explicit CleanupInaccessibleSubdir(const QString &subdir)
|
|
: subdir(subdir)
|
|
{
|
|
}
|
|
~CleanupInaccessibleSubdir()
|
|
{
|
|
QVERIFY(QFile(subdir).setPermissions(QFile::Permissions(QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner)));
|
|
QVERIFY(QDir(subdir).removeRecursively());
|
|
}
|
|
|
|
private:
|
|
const QString subdir;
|
|
};
|
|
|
|
void JobTest::moveFileNoPermissions()
|
|
{
|
|
#ifdef Q_OS_WIN
|
|
QSKIP("Skipping unaccessible folder test on Windows, cannot remove all permissions from a folder");
|
|
#endif
|
|
// Given a file that cannot be moved (subdir has no permissions)
|
|
const QString subdir = homeTmpDir() + "subdir";
|
|
QVERIFY(QDir().mkpath(subdir));
|
|
const QString src = subdir + "/thefile";
|
|
createTestFile(src);
|
|
QVERIFY(QFile(subdir).setPermissions(QFile::Permissions())); // Make it inaccessible
|
|
CleanupInaccessibleSubdir c(subdir);
|
|
|
|
// When trying to move it
|
|
const QString dest = homeTmpDir() + "dest";
|
|
KIO::CopyJob *job = KIO::move(QUrl::fromLocalFile(src), QUrl::fromLocalFile(dest), KIO::HideProgressInfo);
|
|
job->setUiDelegate(nullptr);
|
|
job->setUiDelegateExtension(nullptr); // no skip dialog, thanks
|
|
|
|
// The job should fail with "access denied"
|
|
QVERIFY(!job->exec());
|
|
QCOMPARE(job->error(), (int)KIO::ERR_ACCESS_DENIED);
|
|
// Note that, just like mv(1), KIO's behavior depends on whether
|
|
// a direct rename(2) was used, or a full copy+del. In the first case
|
|
// there is no destination file created, but in the second case the
|
|
// destination file remains.
|
|
// In this test it's the same partition, so no dest created.
|
|
QVERIFY(!QFile::exists(dest));
|
|
|
|
QCOMPARE(job->totalAmount(KJob::Files), 1);
|
|
QCOMPARE(job->totalAmount(KJob::Directories), 0);
|
|
QCOMPARE(job->processedAmount(KJob::Files), 0);
|
|
QCOMPARE(job->processedAmount(KJob::Directories), 0);
|
|
QCOMPARE(job->percent(), 0);
|
|
}
|
|
|
|
void JobTest::moveDirectoryNoPermissions()
|
|
{
|
|
#ifdef Q_OS_WIN
|
|
QSKIP("Skipping unaccessible folder test on Windows, cannot remove all permissions from a folder");
|
|
#endif
|
|
// Given a dir that cannot be moved (parent dir has no permissions)
|
|
const QString subdir = homeTmpDir() + "subdir";
|
|
const QString src = subdir + "/thedir";
|
|
QVERIFY(QDir().mkpath(src));
|
|
QVERIFY(QFileInfo(src).isDir());
|
|
QVERIFY(QFile(subdir).setPermissions(QFile::Permissions())); // Make it inaccessible
|
|
CleanupInaccessibleSubdir c(subdir);
|
|
|
|
// When trying to move it
|
|
const QString dest = homeTmpDir() + "mdnp";
|
|
KIO::CopyJob *job = KIO::move(QUrl::fromLocalFile(src), QUrl::fromLocalFile(dest), KIO::HideProgressInfo);
|
|
job->setUiDelegate(nullptr);
|
|
job->setUiDelegateExtension(nullptr); // no skip dialog, thanks
|
|
|
|
// The job should fail with "access denied"
|
|
QVERIFY(!job->exec());
|
|
QCOMPARE(job->error(), (int)KIO::ERR_ACCESS_DENIED);
|
|
|
|
QVERIFY(!QFile::exists(dest));
|
|
|
|
QCOMPARE(job->totalAmount(KJob::Files), 1);
|
|
QCOMPARE(job->totalAmount(KJob::Directories), 0);
|
|
QCOMPARE(job->processedAmount(KJob::Files), 0);
|
|
QCOMPARE(job->processedAmount(KJob::Directories), 0);
|
|
QCOMPARE(job->percent(), 0);
|
|
}
|
|
|
|
void JobTest::moveDirectoryToReadonlyFilesystem_data()
|
|
{
|
|
QTest::addColumn<QList<QUrl>>("sources");
|
|
QTest::addColumn<int>("expectedErrorCode");
|
|
|
|
const QString srcFileHomePath = homeTmpDir() + "srcFileHome";
|
|
const QUrl srcFileHome = QUrl::fromLocalFile(srcFileHomePath);
|
|
createTestFile(srcFileHomePath);
|
|
|
|
const QString srcFileOtherPath = otherTmpDir() + "srcFileOther";
|
|
const QUrl srcFileOther = QUrl::fromLocalFile(srcFileOtherPath);
|
|
createTestFile(srcFileOtherPath);
|
|
|
|
const QString srcDirHomePath = homeTmpDir() + "srcDirHome";
|
|
const QUrl srcDirHome = QUrl::fromLocalFile(srcDirHomePath);
|
|
createTestDirectory(srcDirHomePath);
|
|
|
|
const QString srcDirHome2Path = homeTmpDir() + "srcDirHome2";
|
|
const QUrl srcDirHome2 = QUrl::fromLocalFile(srcDirHome2Path);
|
|
createTestDirectory(srcDirHome2Path);
|
|
|
|
const QString srcDirOtherPath = otherTmpDir() + "srcDirOther";
|
|
const QUrl srcDirOther = QUrl::fromLocalFile(srcDirOtherPath);
|
|
createTestDirectory(srcDirOtherPath);
|
|
|
|
QTest::newRow("file_same_partition") << QList<QUrl>{srcFileHome} << int(KIO::ERR_WRITE_ACCESS_DENIED);
|
|
QTest::newRow("file_other_partition") << QList<QUrl>{srcFileOther} << int(KIO::ERR_WRITE_ACCESS_DENIED);
|
|
QTest::newRow("one_dir_same_partition") << QList<QUrl>{srcDirHome} << int(KIO::ERR_WRITE_ACCESS_DENIED);
|
|
QTest::newRow("one_dir_other_partition") << QList<QUrl>{srcDirOther} << int(KIO::ERR_WRITE_ACCESS_DENIED);
|
|
QTest::newRow("dirs_same_partition") << QList<QUrl>{srcDirHome, srcDirHome2} << int(KIO::ERR_WRITE_ACCESS_DENIED);
|
|
QTest::newRow("dirs_both_partitions") << QList<QUrl>{srcDirOther, srcDirHome} << int(KIO::ERR_WRITE_ACCESS_DENIED);
|
|
}
|
|
|
|
void JobTest::moveDirectoryToReadonlyFilesystem()
|
|
{
|
|
QFETCH(QList<QUrl>, sources);
|
|
QFETCH(int, expectedErrorCode);
|
|
|
|
const QString dst_dir = homeTmpDir() + "readonlyDest";
|
|
const QUrl dst = QUrl::fromLocalFile(dst_dir);
|
|
QVERIFY2(QDir().mkdir(dst_dir), qPrintable(dst_dir));
|
|
QFile(dst_dir).setPermissions(QFile::Permissions(QFile::ReadOwner | QFile::ExeOwner)); // Make it readonly, moving should throw some errors
|
|
|
|
ScopedCleaner cleaner([&] {
|
|
QVERIFY(QFile(dst_dir).setPermissions(QFile::Permissions(QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner)));
|
|
QVERIFY(QDir(dst_dir).removeRecursively());
|
|
});
|
|
|
|
KIO::CopyJob *job = KIO::move(sources, dst, KIO::HideProgressInfo | KIO::NoPrivilegeExecution);
|
|
job->setUiDelegate(nullptr);
|
|
QVERIFY(!job->exec());
|
|
QCOMPARE(job->error(), expectedErrorCode);
|
|
for (const QUrl &srcUrl : std::as_const(sources)) {
|
|
QVERIFY(QFileInfo::exists(srcUrl.toLocalFile())); // no moving happened
|
|
}
|
|
|
|
KIO::CopyJob *job2 = KIO::move(sources, dst, KIO::HideProgressInfo);
|
|
job2->setUiDelegate(nullptr);
|
|
QVERIFY(!job2->exec());
|
|
if (job2->error() != KIO::ERR_CANNOT_MKDIR) { // This can happen when moving between partitions, but on CI it's the same partition so allow both
|
|
QCOMPARE(job2->error(), expectedErrorCode);
|
|
}
|
|
for (const QUrl &srcUrl : std::as_const(sources)) {
|
|
QVERIFY(QFileInfo::exists(srcUrl.toLocalFile())); // no moving happened
|
|
}
|
|
}
|
|
|
|
static QByteArray expectedListRecursiveOutput()
|
|
{
|
|
return QByteArray(
|
|
".,..,"
|
|
"dirFromHome,dirFromHome/testfile,"
|
|
"dirFromHome/testlink," // exists on Windows too, see createTestDirectory
|
|
"dirFromHome_copied,"
|
|
"dirFromHome_copied/dirFromHome,dirFromHome_copied/dirFromHome/testfile,"
|
|
"dirFromHome_copied/dirFromHome/testlink,"
|
|
"dirFromHome_copied/testfile,"
|
|
"dirFromHome_copied/testlink,"
|
|
#ifndef Q_OS_WIN
|
|
"dirFromHome_link,"
|
|
#endif
|
|
"fileFromHome");
|
|
}
|
|
|
|
void JobTest::listRecursive()
|
|
{
|
|
// Note: many other tests must have been run before since we rely on the files they created
|
|
|
|
const QString src = homeTmpDir();
|
|
#ifndef Q_OS_WIN
|
|
// Add a symlink to a dir, to make sure we don't recurse into those
|
|
bool symlinkOk = symlink("dirFromHome", QFile::encodeName(src + "/dirFromHome_link").constData()) == 0;
|
|
QVERIFY(symlinkOk);
|
|
#endif
|
|
m_names.clear();
|
|
KIO::ListJob *job = KIO::listRecursive(QUrl::fromLocalFile(src), KIO::HideProgressInfo);
|
|
job->setUiDelegate(nullptr);
|
|
connect(job, &KIO::ListJob::entries, this, &JobTest::slotEntries);
|
|
QVERIFY2(job->exec(), qPrintable(job->errorString()));
|
|
m_names.sort();
|
|
const QByteArray ref_names = expectedListRecursiveOutput();
|
|
const QString joinedNames = m_names.join(QLatin1Char(','));
|
|
if (joinedNames.toLatin1() != ref_names) {
|
|
qDebug("%s", qPrintable(joinedNames));
|
|
qDebug("%s", ref_names.data());
|
|
}
|
|
QCOMPARE(joinedNames.toLatin1(), ref_names);
|
|
}
|
|
|
|
void JobTest::multipleListRecursive()
|
|
{
|
|
// Note: listRecursive() must have been run first
|
|
const QString src = homeTmpDir();
|
|
m_names.clear();
|
|
QList<KIO::ListJob *> jobs;
|
|
for (int i = 0; i < 100; ++i) {
|
|
KIO::ListJob *job = KIO::listRecursive(QUrl::fromLocalFile(src), KIO::HideProgressInfo);
|
|
job->setUiDelegate(nullptr);
|
|
if (i == 6) {
|
|
connect(job, &KIO::ListJob::entries, this, &JobTest::slotEntries);
|
|
}
|
|
connect(job, &KJob::result, this, [&jobs, job]() {
|
|
jobs.removeOne(job);
|
|
});
|
|
jobs.push_back(job);
|
|
}
|
|
QTRY_VERIFY(jobs.isEmpty());
|
|
|
|
m_names.sort();
|
|
const QByteArray ref_names = expectedListRecursiveOutput();
|
|
const QString joinedNames = m_names.join(QLatin1Char(','));
|
|
if (joinedNames.toLatin1() != ref_names) {
|
|
qDebug("%s", qPrintable(joinedNames));
|
|
qDebug("%s", ref_names.data());
|
|
}
|
|
QCOMPARE(joinedNames.toLatin1(), ref_names);
|
|
}
|
|
|
|
void JobTest::listFile()
|
|
{
|
|
const QString filePath = homeTmpDir() + "fileFromHome";
|
|
createTestFile(filePath);
|
|
KIO::ListJob *job = KIO::listDir(QUrl::fromLocalFile(filePath), KIO::HideProgressInfo);
|
|
job->setUiDelegate(nullptr);
|
|
QVERIFY(!job->exec());
|
|
QCOMPARE(job->error(), static_cast<int>(KIO::ERR_IS_FILE));
|
|
|
|
// And list something that doesn't exist
|
|
const QString path = homeTmpDir() + "fileFromHomeDoesNotExist";
|
|
job = KIO::listDir(QUrl::fromLocalFile(path), KIO::HideProgressInfo);
|
|
job->setUiDelegate(nullptr);
|
|
QVERIFY(!job->exec());
|
|
QCOMPARE(job->error(), static_cast<int>(KIO::ERR_DOES_NOT_EXIST));
|
|
}
|
|
|
|
void JobTest::killJob()
|
|
{
|
|
const QString src = homeTmpDir();
|
|
KIO::ListJob *job = KIO::listDir(QUrl::fromLocalFile(src), KIO::HideProgressInfo);
|
|
QVERIFY(job->isAutoDelete());
|
|
QPointer<KIO::ListJob> ptr(job);
|
|
job->setUiDelegate(nullptr);
|
|
qApp->processEvents(); // let the job start, it's no fun otherwise
|
|
job->kill();
|
|
qApp->sendPostedEvents(nullptr, QEvent::DeferredDelete); // process the deferred delete of the job
|
|
QVERIFY(ptr.isNull());
|
|
}
|
|
|
|
void JobTest::killJobBeforeStart()
|
|
{
|
|
const QString src = homeTmpDir();
|
|
KIO::Job *job = KIO::stat(QUrl::fromLocalFile(src), KIO::HideProgressInfo);
|
|
QVERIFY(job->isAutoDelete());
|
|
QPointer<KIO::Job> ptr(job);
|
|
job->setUiDelegate(nullptr);
|
|
job->kill();
|
|
qApp->sendPostedEvents(nullptr, QEvent::DeferredDelete); // process the deferred delete of the job
|
|
QVERIFY(ptr.isNull());
|
|
qApp->processEvents(); // does KIO scheduler crash here? nope.
|
|
}
|
|
|
|
void JobTest::deleteJobBeforeStart() // #163171
|
|
{
|
|
const QString src = homeTmpDir();
|
|
KIO::Job *job = KIO::stat(QUrl::fromLocalFile(src), KIO::HideProgressInfo);
|
|
QVERIFY(job->isAutoDelete());
|
|
job->setUiDelegate(nullptr);
|
|
delete job;
|
|
qApp->processEvents(); // does KIO scheduler crash here?
|
|
}
|
|
|
|
void JobTest::directorySize()
|
|
{
|
|
// Note: many other tests must have been run before since we rely on the files they created
|
|
|
|
const QString src = homeTmpDir();
|
|
|
|
KIO::DirectorySizeJob *job = KIO::directorySize(QUrl::fromLocalFile(src));
|
|
job->setUiDelegate(nullptr);
|
|
QVERIFY2(job->exec(), qPrintable(job->errorString()));
|
|
qDebug() << "totalSize: " << job->totalSize();
|
|
qDebug() << "totalFiles: " << job->totalFiles();
|
|
qDebug() << "totalSubdirs: " << job->totalSubdirs();
|
|
#ifdef Q_OS_WIN
|
|
QCOMPARE(job->totalFiles(), 5ULL); // see expected result in listRecursive() above
|
|
QCOMPARE(job->totalSubdirs(), 3ULL); // see expected result in listRecursive() above
|
|
QVERIFY(job->totalSize() > 54);
|
|
#else
|
|
QCOMPARE(job->totalFiles(), 7ULL); // see expected result in listRecursive() above
|
|
QCOMPARE(job->totalSubdirs(), 4ULL); // see expected result in listRecursive() above
|
|
QVERIFY2(job->totalSize() >= 60,
|
|
qPrintable(QString("totalSize was %1").arg(job->totalSize()))); // size of subdir entries is filesystem dependent. E.g. this is 16428 with ext4 but
|
|
// only 272 with xfs, and 63 on FreeBSD
|
|
#endif
|
|
|
|
qApp->sendPostedEvents(nullptr, QEvent::DeferredDelete);
|
|
}
|
|
|
|
void JobTest::directorySizeError()
|
|
{
|
|
KIO::DirectorySizeJob *job = KIO::directorySize(QUrl::fromLocalFile(QStringLiteral("/I/Dont/Exist")));
|
|
job->setUiDelegate(nullptr);
|
|
QVERIFY(!job->exec());
|
|
qApp->sendPostedEvents(nullptr, QEvent::DeferredDelete);
|
|
}
|
|
|
|
void JobTest::slotEntries(KIO::Job *, const KIO::UDSEntryList &lst)
|
|
{
|
|
for (KIO::UDSEntryList::ConstIterator it = lst.begin(); it != lst.end(); ++it) {
|
|
QString displayName = (*it).stringValue(KIO::UDSEntry::UDS_NAME);
|
|
// QUrl url = (*it).stringValue( KIO::UDSEntry::UDS_URL );
|
|
m_names.append(displayName);
|
|
}
|
|
}
|
|
|
|
void JobTest::calculateRemainingSeconds()
|
|
{
|
|
unsigned int seconds = KIO::calculateRemainingSeconds(2 * 86400 - 60, 0, 1);
|
|
QCOMPARE(seconds, static_cast<unsigned int>(2 * 86400 - 60));
|
|
QString text = KIO::convertSeconds(seconds);
|
|
QCOMPARE(text, i18n("1 day 23:59:00"));
|
|
|
|
seconds = KIO::calculateRemainingSeconds(520, 20, 10);
|
|
QCOMPARE(seconds, static_cast<unsigned int>(50));
|
|
text = KIO::convertSeconds(seconds);
|
|
QCOMPARE(text, i18n("00:00:50"));
|
|
}
|
|
|
|
void JobTest::getInvalidUrl()
|
|
{
|
|
QUrl url(QStringLiteral("http://strange<hostname>/"));
|
|
QVERIFY(!url.isValid());
|
|
|
|
KIO::SimpleJob *job = KIO::get(url, KIO::NoReload, KIO::HideProgressInfo);
|
|
QVERIFY(job != nullptr);
|
|
job->setUiDelegate(nullptr);
|
|
|
|
QVERIFY(!job->exec()); // it should fail :)
|
|
}
|
|
|
|
void JobTest::slotMimetype(KIO::Job *job, const QString &type)
|
|
{
|
|
QVERIFY(job != nullptr);
|
|
m_mimetype = type;
|
|
}
|
|
|
|
void JobTest::deleteFile()
|
|
{
|
|
const QString dest = otherTmpDir() + "fileFromHome_copied";
|
|
createTestFile(dest);
|
|
KIO::Job *job = KIO::del(QUrl::fromLocalFile(dest), KIO::HideProgressInfo);
|
|
job->setUiDelegate(nullptr);
|
|
QVERIFY2(job->exec(), qPrintable(job->errorString()));
|
|
QVERIFY(!QFile::exists(dest));
|
|
}
|
|
|
|
void JobTest::deleteDirectory()
|
|
{
|
|
const QString dest = otherTmpDir() + "dirFromHome_copied";
|
|
if (!QFile::exists(dest)) {
|
|
createTestDirectory(dest);
|
|
}
|
|
// Let's put a few things in there to see if the recursive deletion works correctly
|
|
// A hidden file:
|
|
createTestFile(dest + "/.hidden");
|
|
#ifndef Q_OS_WIN
|
|
// A broken symlink:
|
|
createTestSymlink(dest + "/broken_symlink");
|
|
// A symlink to a dir:
|
|
const auto srcFileName = QFile::encodeName(QFileInfo(QFINDTESTDATA("jobtest.cpp")).absolutePath()).constData();
|
|
const auto symLinkFileName = QFile::encodeName(dest + "/symlink_to_dir").constData();
|
|
if (symlink(srcFileName, symLinkFileName) != 0) {
|
|
qFatal("couldn't create symlink: %s", strerror(errno));
|
|
}
|
|
#endif
|
|
|
|
KIO::Job *job = KIO::del(QUrl::fromLocalFile(dest), KIO::HideProgressInfo);
|
|
job->setUiDelegate(nullptr);
|
|
QVERIFY2(job->exec(), qPrintable(job->errorString()));
|
|
QVERIFY(!QFile::exists(dest));
|
|
}
|
|
|
|
void JobTest::deleteSymlink(bool using_fast_path)
|
|
{
|
|
extern KIOCORE_EXPORT bool kio_resolve_local_urls;
|
|
kio_resolve_local_urls = !using_fast_path;
|
|
|
|
#ifndef Q_OS_WIN
|
|
const QString src = homeTmpDir() + "dirFromHome";
|
|
createTestDirectory(src);
|
|
QVERIFY(QFile::exists(src));
|
|
const QString dest = homeTmpDir() + "/dirFromHome_link";
|
|
if (!QFile::exists(dest)) {
|
|
// Add a symlink to a dir, to make sure we don't recurse into those
|
|
bool symlinkOk = symlink(QFile::encodeName(src).constData(), QFile::encodeName(dest).constData()) == 0;
|
|
QVERIFY(symlinkOk);
|
|
QVERIFY(QFile::exists(dest));
|
|
}
|
|
KIO::Job *job = KIO::del(QUrl::fromLocalFile(dest), KIO::HideProgressInfo);
|
|
job->setUiDelegate(nullptr);
|
|
QVERIFY2(job->exec(), qPrintable(job->errorString()));
|
|
QVERIFY(!QFile::exists(dest));
|
|
QVERIFY(QFile::exists(src));
|
|
#endif
|
|
|
|
kio_resolve_local_urls = true;
|
|
}
|
|
|
|
void JobTest::deleteSymlink()
|
|
{
|
|
#ifndef Q_OS_WIN
|
|
deleteSymlink(true);
|
|
deleteSymlink(false);
|
|
#endif
|
|
}
|
|
|
|
void JobTest::deleteManyDirs(bool using_fast_path)
|
|
{
|
|
extern KIOCORE_EXPORT bool kio_resolve_local_urls;
|
|
kio_resolve_local_urls = !using_fast_path;
|
|
|
|
const int numDirs = 50;
|
|
QList<QUrl> dirs;
|
|
for (int i = 0; i < numDirs; ++i) {
|
|
const QString dir = homeTmpDir() + "dir" + QString::number(i);
|
|
createTestDirectory(dir);
|
|
dirs << QUrl::fromLocalFile(dir);
|
|
}
|
|
QElapsedTimer dt;
|
|
dt.start();
|
|
KIO::Job *job = KIO::del(dirs, KIO::HideProgressInfo);
|
|
job->setUiDelegate(nullptr);
|
|
QVERIFY2(job->exec(), qPrintable(job->errorString()));
|
|
for (const QUrl &dir : std::as_const(dirs)) {
|
|
QVERIFY(!QFile::exists(dir.toLocalFile()));
|
|
}
|
|
|
|
qDebug() << "Deleted" << numDirs << "dirs in" << dt.elapsed() << "milliseconds";
|
|
kio_resolve_local_urls = true;
|
|
}
|
|
|
|
void JobTest::deleteManyDirs()
|
|
{
|
|
deleteManyDirs(true);
|
|
deleteManyDirs(false);
|
|
}
|
|
|
|
static QList<QUrl> createManyFiles(const QString &baseDir, int numFiles)
|
|
{
|
|
QList<QUrl> ret;
|
|
ret.reserve(numFiles);
|
|
for (int i = 0; i < numFiles; ++i) {
|
|
// create empty file
|
|
const QString file = baseDir + QString::number(i);
|
|
QFile f(file);
|
|
bool ok = f.open(QIODevice::WriteOnly);
|
|
if (ok) {
|
|
f.write("Hello");
|
|
ret.append(QUrl::fromLocalFile(file));
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
void JobTest::deleteManyFilesIndependently()
|
|
{
|
|
QElapsedTimer dt;
|
|
dt.start();
|
|
const int numFiles = 100; // Use 1000 for performance testing
|
|
const QString baseDir = homeTmpDir();
|
|
const QList<QUrl> urls = createManyFiles(baseDir, numFiles);
|
|
QCOMPARE(urls.count(), numFiles);
|
|
for (int i = 0; i < numFiles; ++i) {
|
|
// delete each file independently. lots of jobs. this stress-tests kio scheduling.
|
|
const QUrl url = urls.at(i);
|
|
const QString file = url.toLocalFile();
|
|
QVERIFY(QFile::exists(file));
|
|
// qDebug() << file;
|
|
KIO::Job *job = KIO::del(url, KIO::HideProgressInfo);
|
|
job->setUiDelegate(nullptr);
|
|
QVERIFY2(job->exec(), qPrintable(job->errorString()));
|
|
QVERIFY(!QFile::exists(file));
|
|
}
|
|
qDebug() << "Deleted" << numFiles << "files in" << dt.elapsed() << "milliseconds";
|
|
}
|
|
|
|
void JobTest::deleteManyFilesTogether(bool using_fast_path)
|
|
{
|
|
extern KIOCORE_EXPORT bool kio_resolve_local_urls;
|
|
kio_resolve_local_urls = !using_fast_path;
|
|
|
|
QElapsedTimer dt;
|
|
dt.start();
|
|
const int numFiles = 100; // Use 1000 for performance testing
|
|
const QString baseDir = homeTmpDir();
|
|
const QList<QUrl> urls = createManyFiles(baseDir, numFiles);
|
|
QCOMPARE(urls.count(), numFiles);
|
|
|
|
// qDebug() << file;
|
|
KIO::Job *job = KIO::del(urls, KIO::HideProgressInfo);
|
|
job->setUiDelegate(nullptr);
|
|
QVERIFY2(job->exec(), qPrintable(job->errorString()));
|
|
qDebug() << "Deleted" << numFiles << "files in" << dt.elapsed() << "milliseconds";
|
|
|
|
kio_resolve_local_urls = true;
|
|
}
|
|
|
|
void JobTest::deleteManyFilesTogether()
|
|
{
|
|
deleteManyFilesTogether(true);
|
|
deleteManyFilesTogether(false);
|
|
}
|
|
|
|
void JobTest::rmdirEmpty()
|
|
{
|
|
const QString dir = homeTmpDir() + "dir";
|
|
QDir().mkdir(dir);
|
|
QVERIFY(QFile::exists(dir));
|
|
KIO::Job *job = KIO::rmdir(QUrl::fromLocalFile(dir));
|
|
QVERIFY2(job->exec(), qPrintable(job->errorString()));
|
|
QVERIFY(!QFile::exists(dir));
|
|
}
|
|
|
|
void JobTest::rmdirNotEmpty()
|
|
{
|
|
const QString dir = homeTmpDir() + "dir";
|
|
createTestDirectory(dir);
|
|
createTestDirectory(dir + "/subdir");
|
|
KIO::Job *job = KIO::rmdir(QUrl::fromLocalFile(dir));
|
|
QVERIFY(!job->exec());
|
|
QVERIFY(QFile::exists(dir));
|
|
}
|
|
|
|
void JobTest::stat()
|
|
{
|
|
#if 1
|
|
const QString filePath = homeTmpDir() + "fileFromHome";
|
|
createTestFile(filePath);
|
|
const QUrl url(QUrl::fromLocalFile(filePath));
|
|
KIO::StatJob *job = KIO::stat(url, KIO::HideProgressInfo);
|
|
QVERIFY(job);
|
|
QVERIFY2(job->exec(), qPrintable(job->errorString()));
|
|
// TODO set setSide
|
|
const KIO::UDSEntry &entry = job->statResult();
|
|
|
|
// we only get filename, access, type, size, uid, gid, btime, mtime, atime
|
|
QVERIFY(entry.contains(KIO::UDSEntry::UDS_NAME));
|
|
QVERIFY(entry.contains(KIO::UDSEntry::UDS_ACCESS));
|
|
QVERIFY(entry.contains(KIO::UDSEntry::UDS_SIZE));
|
|
QVERIFY(entry.contains(KIO::UDSEntry::UDS_FILE_TYPE));
|
|
QVERIFY(entry.contains(KIO::UDSEntry::UDS_LOCAL_USER_ID));
|
|
QVERIFY(entry.contains(KIO::UDSEntry::UDS_LOCAL_GROUP_ID));
|
|
QVERIFY(!entry.contains(KIO::UDSEntry::UDS_USER));
|
|
QVERIFY(!entry.contains(KIO::UDSEntry::UDS_GROUP));
|
|
// QVERIFY(entry.contains(KIO::UDSEntry::UDS_CREATION_TIME)); // only true if st_birthtime or statx is used
|
|
QVERIFY(entry.contains(KIO::UDSEntry::UDS_MODIFICATION_TIME));
|
|
QVERIFY(entry.contains(KIO::UDSEntry::UDS_ACCESS_TIME));
|
|
QCOMPARE(entry.count(), 8 + (entry.contains(KIO::UDSEntry::UDS_CREATION_TIME) ? 1 : 0));
|
|
|
|
QVERIFY(!entry.isDir());
|
|
QVERIFY(!entry.isLink());
|
|
QCOMPARE(entry.stringValue(KIO::UDSEntry::UDS_NAME), QStringLiteral("fileFromHome"));
|
|
|
|
// Compare what we get via kio_file and what we get when KFileItem stat()s directly
|
|
const KFileItem kioItem(entry, url);
|
|
const KFileItem fileItem(url);
|
|
QCOMPARE(kioItem.name(), fileItem.name());
|
|
QCOMPARE(kioItem.url(), fileItem.url());
|
|
QCOMPARE(kioItem.size(), fileItem.size());
|
|
QCOMPARE(kioItem.user(), fileItem.user());
|
|
QCOMPARE(kioItem.group(), fileItem.group());
|
|
QCOMPARE(kioItem.mimetype(), fileItem.mimetype());
|
|
QCOMPARE(kioItem.permissions(), fileItem.permissions());
|
|
QCOMPARE(kioItem.time(KFileItem::ModificationTime), fileItem.time(KFileItem::ModificationTime));
|
|
QCOMPARE(kioItem.time(KFileItem::AccessTime), fileItem.time(KFileItem::AccessTime));
|
|
|
|
#else
|
|
// Testing stat over HTTP
|
|
KIO::StatJob *job = KIO::stat(QUrl("http://www.kde.org"), KIO::HideProgressInfo);
|
|
QVERIFY(job);
|
|
QVERIFY2(job->exec(), qPrintable(job->errorString()));
|
|
// TODO set setSide, setDetails
|
|
const KIO::UDSEntry &entry = job->statResult();
|
|
QVERIFY(!entry.isDir());
|
|
QVERIFY(!entry.isLink());
|
|
QCOMPARE(entry.stringValue(KIO::UDSEntry::UDS_NAME), QString());
|
|
#endif
|
|
}
|
|
|
|
void JobTest::statDetailsBasic()
|
|
{
|
|
const QString filePath = homeTmpDir() + "fileFromHome";
|
|
createTestFile(filePath);
|
|
const QUrl url(QUrl::fromLocalFile(filePath));
|
|
KIO::StatJob *job = KIO::stat(url, KIO::StatJob::StatSide::SourceSide, KIO::StatBasic, KIO::HideProgressInfo);
|
|
QVERIFY(job);
|
|
QVERIFY2(job->exec(), qPrintable(job->errorString()));
|
|
// TODO set setSide
|
|
const KIO::UDSEntry &entry = job->statResult();
|
|
|
|
// we only get filename, access, type, size, (no linkdest)
|
|
QVERIFY(entry.contains(KIO::UDSEntry::UDS_NAME));
|
|
QVERIFY(entry.contains(KIO::UDSEntry::UDS_ACCESS));
|
|
QVERIFY(entry.contains(KIO::UDSEntry::UDS_SIZE));
|
|
QVERIFY(entry.contains(KIO::UDSEntry::UDS_FILE_TYPE));
|
|
QCOMPARE(entry.count(), 4);
|
|
|
|
QVERIFY(!entry.isDir());
|
|
QVERIFY(!entry.isLink());
|
|
QVERIFY(entry.numberValue(KIO::UDSEntry::UDS_ACCESS) > 0);
|
|
QCOMPARE(entry.stringValue(KIO::UDSEntry::UDS_NAME), QStringLiteral("fileFromHome"));
|
|
|
|
// Compare what we get via kio_file and what we get when KFileItem stat()s directly
|
|
// for the requested fields
|
|
const KFileItem kioItem(entry, url);
|
|
const KFileItem fileItem(url);
|
|
QCOMPARE(kioItem.name(), fileItem.name());
|
|
QCOMPARE(kioItem.url(), fileItem.url());
|
|
QCOMPARE(kioItem.size(), fileItem.size());
|
|
QCOMPARE(kioItem.user(), "");
|
|
QCOMPARE(kioItem.group(), "");
|
|
QCOMPARE(kioItem.mimetype(), "application/octet-stream");
|
|
QCOMPARE(kioItem.permissions(), 438);
|
|
QCOMPARE(kioItem.time(KFileItem::ModificationTime), QDateTime());
|
|
QCOMPARE(kioItem.time(KFileItem::AccessTime), QDateTime());
|
|
}
|
|
|
|
void JobTest::statDetailsBasicSetDetails()
|
|
{
|
|
const QString filePath = homeTmpDir() + "fileFromHome";
|
|
createTestFile(filePath);
|
|
const QUrl url(QUrl::fromLocalFile(filePath));
|
|
KIO::StatJob *job = KIO::stat(url);
|
|
job->setDetails(KIO::StatBasic);
|
|
QVERIFY(job);
|
|
QVERIFY2(job->exec(), qPrintable(job->errorString()));
|
|
// TODO set setSide
|
|
const KIO::UDSEntry &entry = job->statResult();
|
|
|
|
// we only get filename, access, type, size, (no linkdest)
|
|
QVERIFY(entry.contains(KIO::UDSEntry::UDS_NAME));
|
|
QVERIFY(entry.contains(KIO::UDSEntry::UDS_ACCESS));
|
|
QVERIFY(entry.contains(KIO::UDSEntry::UDS_SIZE));
|
|
QVERIFY(entry.contains(KIO::UDSEntry::UDS_FILE_TYPE));
|
|
QCOMPARE(entry.count(), 4);
|
|
|
|
QVERIFY(!entry.isDir());
|
|
QVERIFY(!entry.isLink());
|
|
QVERIFY(entry.numberValue(KIO::UDSEntry::UDS_ACCESS) > 0);
|
|
QCOMPARE(entry.stringValue(KIO::UDSEntry::UDS_NAME), QStringLiteral("fileFromHome"));
|
|
|
|
// Compare what we get via kio_file and what we get when KFileItem stat()s directly
|
|
// for the requested fields
|
|
const KFileItem kioItem(entry, url);
|
|
const KFileItem fileItem(url);
|
|
QCOMPARE(kioItem.name(), fileItem.name());
|
|
QCOMPARE(kioItem.url(), fileItem.url());
|
|
QCOMPARE(kioItem.size(), fileItem.size());
|
|
QCOMPARE(kioItem.user(), "");
|
|
QCOMPARE(kioItem.group(), "");
|
|
QCOMPARE(kioItem.mimetype(), "application/octet-stream");
|
|
QCOMPARE(kioItem.permissions(), 438);
|
|
QCOMPARE(kioItem.time(KFileItem::ModificationTime), QDateTime());
|
|
QCOMPARE(kioItem.time(KFileItem::AccessTime), QDateTime());
|
|
}
|
|
|
|
void JobTest::statWithInode()
|
|
{
|
|
const QString filePath = homeTmpDir() + "fileFromHome";
|
|
createTestFile(filePath);
|
|
const QUrl url(QUrl::fromLocalFile(filePath));
|
|
KIO::StatJob *job = KIO::stat(url, KIO::StatJob::SourceSide, KIO::StatInode);
|
|
QVERIFY(job);
|
|
QVERIFY2(job->exec(), qPrintable(job->errorString()));
|
|
|
|
const KIO::UDSEntry entry = job->statResult();
|
|
QVERIFY(entry.contains(KIO::UDSEntry::UDS_DEVICE_ID));
|
|
QVERIFY(entry.contains(KIO::UDSEntry::UDS_INODE));
|
|
QCOMPARE(entry.count(), 2);
|
|
|
|
const QString path = otherTmpDir() + "otherFile";
|
|
createTestFile(path);
|
|
const QUrl otherUrl(QUrl::fromLocalFile(path));
|
|
KIO::StatJob *otherJob = KIO::stat(otherUrl, KIO::StatJob::SourceSide, KIO::StatInode);
|
|
QVERIFY(otherJob);
|
|
QVERIFY2(otherJob->exec(), qPrintable(otherJob->errorString()));
|
|
|
|
const KIO::UDSEntry otherEntry = otherJob->statResult();
|
|
QVERIFY(otherEntry.contains(KIO::UDSEntry::UDS_DEVICE_ID));
|
|
QVERIFY(otherEntry.contains(KIO::UDSEntry::UDS_INODE));
|
|
QCOMPARE(otherEntry.count(), 2);
|
|
|
|
const int device = entry.numberValue(KIO::UDSEntry::UDS_DEVICE_ID);
|
|
const int otherDevice = otherEntry.numberValue(KIO::UDSEntry::UDS_DEVICE_ID);
|
|
|
|
// this test doesn't make sense on the CI as it's an LXC container with one partition
|
|
if (otherTmpDirIsOnSamePartition()) {
|
|
// On the CI where the two tmp dirs are on the only partition available
|
|
// in the LXC container, the device ID's would be identical
|
|
QCOMPARE(device, otherDevice);
|
|
} else {
|
|
QVERIFY(device != otherDevice);
|
|
}
|
|
}
|
|
|
|
#ifndef Q_OS_WIN
|
|
void JobTest::statSymlink()
|
|
{
|
|
const QString filePath = homeTmpDir() + "fileFromHome";
|
|
createTestFile(filePath);
|
|
const QString symlink = otherTmpDir() + "link";
|
|
QVERIFY(QFile(filePath).link(symlink));
|
|
QVERIFY(QFile::exists(symlink));
|
|
setTimeStamp(symlink, QDateTime::currentDateTime().addSecs(-20)); // differentiate link time and source file time
|
|
|
|
const QUrl url(QUrl::fromLocalFile(symlink));
|
|
KIO::StatJob *job =
|
|
KIO::stat(url, KIO::StatJob::StatSide::SourceSide, KIO::StatBasic | KIO::StatResolveSymlink | KIO::StatUser | KIO::StatTime, KIO::HideProgressInfo);
|
|
QVERIFY(job);
|
|
QVERIFY2(job->exec(), qPrintable(job->errorString()));
|
|
// TODO set setSide, setDetails
|
|
const KIO::UDSEntry &entry = job->statResult();
|
|
|
|
// we only get filename, access, type, size, linkdest, uid, gid, btime, mtime, atime
|
|
QVERIFY(entry.contains(KIO::UDSEntry::UDS_NAME));
|
|
QVERIFY(entry.contains(KIO::UDSEntry::UDS_ACCESS));
|
|
QVERIFY(entry.contains(KIO::UDSEntry::UDS_SIZE));
|
|
QVERIFY(entry.contains(KIO::UDSEntry::UDS_FILE_TYPE));
|
|
QVERIFY(entry.contains(KIO::UDSEntry::UDS_LINK_DEST));
|
|
QVERIFY(entry.contains(KIO::UDSEntry::UDS_LOCAL_USER_ID));
|
|
QVERIFY(entry.contains(KIO::UDSEntry::UDS_LOCAL_GROUP_ID));
|
|
QVERIFY(!entry.contains(KIO::UDSEntry::UDS_USER));
|
|
QVERIFY(!entry.contains(KIO::UDSEntry::UDS_GROUP));
|
|
// QVERIFY(entry.contains(KIO::UDSEntry::UDS_CREATION_TIME)); // only true if st_birthtime or statx is used
|
|
QVERIFY(entry.contains(KIO::UDSEntry::UDS_MODIFICATION_TIME));
|
|
QVERIFY(entry.contains(KIO::UDSEntry::UDS_ACCESS_TIME));
|
|
QCOMPARE(entry.count(), 9 + (entry.contains(KIO::UDSEntry::UDS_CREATION_TIME) ? 1 : 0));
|
|
|
|
QVERIFY(!entry.isDir());
|
|
QVERIFY(entry.isLink());
|
|
QVERIFY(entry.numberValue(KIO::UDSEntry::UDS_ACCESS) > 0);
|
|
QCOMPARE(entry.stringValue(KIO::UDSEntry::UDS_NAME), QStringLiteral("link"));
|
|
|
|
// Compare what we get via kio_file and what we get when KFileItem stat()s directly
|
|
const KFileItem kioItem(entry, url);
|
|
const KFileItem fileItem(url);
|
|
QCOMPARE(kioItem.name(), fileItem.name());
|
|
QCOMPARE(kioItem.url(), fileItem.url());
|
|
QVERIFY(kioItem.isLink());
|
|
QVERIFY(fileItem.isLink());
|
|
QCOMPARE(kioItem.linkDest(), fileItem.linkDest());
|
|
QCOMPARE(kioItem.size(), fileItem.size());
|
|
QCOMPARE(kioItem.user(), fileItem.user());
|
|
QCOMPARE(kioItem.group(), fileItem.group());
|
|
QCOMPARE(kioItem.mimetype(), fileItem.mimetype());
|
|
QCOMPARE(kioItem.permissions(), fileItem.permissions());
|
|
QCOMPARE(kioItem.time(KFileItem::ModificationTime), fileItem.time(KFileItem::ModificationTime));
|
|
QCOMPARE(kioItem.time(KFileItem::AccessTime), fileItem.time(KFileItem::AccessTime));
|
|
}
|
|
|
|
/* Check that the underlying system, and Qt, support
|
|
* millisecond timestamp resolution.
|
|
*/
|
|
void JobTest::statTimeResolution()
|
|
{
|
|
const QString filePath = homeTmpDir() + "statFile";
|
|
const QDateTime early70sDate = QDateTime::fromMSecsSinceEpoch(107780520123L);
|
|
const time_t early70sTime = 107780520; // Seconds for January 6 1973, 12:02
|
|
|
|
createTestFile(filePath);
|
|
|
|
QFile dest_file(filePath);
|
|
QVERIFY(dest_file.open(QIODevice::ReadOnly));
|
|
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_HAIKU)
|
|
// with nano secs precision
|
|
struct timespec ut[2];
|
|
ut[0].tv_sec = early70sTime;
|
|
ut[0].tv_nsec = 123000000L; // 123 ms
|
|
ut[1] = ut[0];
|
|
// need to do this with the dest file still opened, or this fails
|
|
QCOMPARE(::futimens(dest_file.handle(), ut), 0);
|
|
#else
|
|
struct timeval ut[2];
|
|
ut[0].tv_sec = early70sTime;
|
|
ut[0].tv_usec = 123000;
|
|
ut[1] = ut[0];
|
|
QCOMPARE(::futimes(dest_file.handle(), ut), 0);
|
|
#endif
|
|
dest_file.close();
|
|
|
|
// Check that the modification time is set with millisecond precision
|
|
dest_file.setFileName(filePath);
|
|
QDateTime d = dest_file.fileTime(QFileDevice::FileModificationTime);
|
|
QCOMPARE(d, early70sDate);
|
|
QCOMPARE(d.time().msec(), 123);
|
|
|
|
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
|
|
QT_STATBUF buff_dest;
|
|
QCOMPARE(QT_STAT(filePath.toLocal8Bit().data(), &buff_dest), 0);
|
|
QCOMPARE(buff_dest.st_mtim.tv_sec, early70sTime);
|
|
QCOMPARE(buff_dest.st_mtim.tv_nsec, 123000000L);
|
|
#endif
|
|
|
|
QCOMPARE(QFileInfo(filePath).lastModified(), early70sDate);
|
|
}
|
|
#endif
|
|
|
|
void JobTest::mostLocalUrl()
|
|
{
|
|
const QString filePath = homeTmpDir() + "fileFromHome";
|
|
createTestFile(filePath);
|
|
KIO::StatJob *job = KIO::mostLocalUrl(QUrl::fromLocalFile(filePath), KIO::HideProgressInfo);
|
|
QVERIFY(job);
|
|
QVERIFY2(job->exec(), qPrintable(job->errorString()));
|
|
QCOMPARE(job->mostLocalUrl().toLocalFile(), filePath);
|
|
}
|
|
|
|
void JobTest::mostLocalUrlHttp()
|
|
{
|
|
// the url is returned as-is, as an http url can't have a mostLocalUrl
|
|
const QUrl url("http://www.google.com");
|
|
KIO::StatJob *httpStat = KIO::mostLocalUrl(url, KIO::HideProgressInfo);
|
|
QVERIFY(httpStat);
|
|
QVERIFY2(httpStat->exec(), qPrintable(httpStat->errorString()));
|
|
QCOMPARE(httpStat->mostLocalUrl(), url);
|
|
}
|
|
|
|
void JobTest::chmodFile()
|
|
{
|
|
const QString filePath = homeTmpDir() + "fileForChmod";
|
|
createTestFile(filePath);
|
|
KFileItem item(QUrl::fromLocalFile(filePath));
|
|
const mode_t origPerm = item.permissions();
|
|
mode_t newPerm = origPerm ^ S_IWGRP;
|
|
QVERIFY(newPerm != origPerm);
|
|
KFileItemList items;
|
|
items << item;
|
|
KIO::Job *job = KIO::chmod(items, newPerm, S_IWGRP /*TODO: QFile::WriteGroup*/, QString(), QString(), false, KIO::HideProgressInfo);
|
|
job->setUiDelegate(nullptr);
|
|
QVERIFY2(job->exec(), qPrintable(job->errorString()));
|
|
|
|
KFileItem newItem(QUrl::fromLocalFile(filePath));
|
|
QCOMPARE(QString::number(newItem.permissions(), 8), QString::number(newPerm, 8));
|
|
QFile::remove(filePath);
|
|
}
|
|
|
|
#ifdef Q_OS_UNIX
|
|
void JobTest::chmodSticky()
|
|
{
|
|
const QString dirPath = homeTmpDir() + "dirForChmodSticky";
|
|
QDir().mkpath(dirPath);
|
|
KFileItem item(QUrl::fromLocalFile(dirPath));
|
|
const mode_t origPerm = item.permissions();
|
|
mode_t newPerm = origPerm ^ S_ISVTX;
|
|
QVERIFY(newPerm != origPerm);
|
|
KFileItemList items({item});
|
|
KIO::Job *job = KIO::chmod(items, newPerm, S_ISVTX, QString(), QString(), false, KIO::HideProgressInfo);
|
|
job->setUiDelegate(nullptr);
|
|
QVERIFY2(job->exec(), qPrintable(job->errorString()));
|
|
|
|
KFileItem newItem(QUrl::fromLocalFile(dirPath));
|
|
QCOMPARE(QString::number(newItem.permissions(), 8), QString::number(newPerm, 8));
|
|
QVERIFY(QDir().rmdir(dirPath));
|
|
}
|
|
#endif
|
|
|
|
void JobTest::chmodFileError()
|
|
{
|
|
// chown(root) should fail
|
|
const QString filePath = homeTmpDir() + "fileForChmod";
|
|
createTestFile(filePath);
|
|
KFileItem item(QUrl::fromLocalFile(filePath));
|
|
const mode_t origPerm = item.permissions();
|
|
mode_t newPerm = origPerm ^ S_IWGRP;
|
|
QVERIFY(newPerm != origPerm);
|
|
KFileItemList items;
|
|
items << item;
|
|
KIO::Job *job = KIO::chmod(items, newPerm, S_IWGRP /*TODO: QFile::WriteGroup*/, QStringLiteral("root"), QString(), false, KIO::HideProgressInfo);
|
|
// Simulate the user pressing "Skip" in the dialog.
|
|
job->setUiDelegate(new KJobUiDelegate);
|
|
auto *askUser = new MockAskUserInterface(job->uiDelegate());
|
|
|
|
QVERIFY2(job->exec(), qPrintable(job->errorString()));
|
|
|
|
QCOMPARE(askUser->m_askUserSkipCalled, 1);
|
|
KFileItem newItem(QUrl::fromLocalFile(filePath));
|
|
// We skipped, so the chmod didn't happen.
|
|
QCOMPARE(QString::number(newItem.permissions(), 8), QString::number(origPerm, 8));
|
|
QFile::remove(filePath);
|
|
}
|
|
|
|
void JobTest::mimeType()
|
|
{
|
|
#if 1
|
|
const QString filePath = homeTmpDir() + "fileFromHome";
|
|
createTestFile(filePath);
|
|
KIO::MimetypeJob *job = KIO::mimetype(QUrl::fromLocalFile(filePath), KIO::HideProgressInfo);
|
|
QVERIFY(job);
|
|
QSignalSpy spyMimeTypeFound(job, &KIO::TransferJob::mimeTypeFound);
|
|
QVERIFY2(job->exec(), qPrintable(job->errorString()));
|
|
|
|
QCOMPARE(spyMimeTypeFound.count(), 1);
|
|
QCOMPARE(spyMimeTypeFound[0][0], QVariant::fromValue(static_cast<KIO::Job *>(job)));
|
|
QCOMPARE(spyMimeTypeFound[0][1].toString(), QStringLiteral("application/octet-stream"));
|
|
#else
|
|
// Testing mimetype over HTTP
|
|
KIO::MimetypeJob *job = KIO::mimetype(QUrl("http://www.kde.org"), KIO::HideProgressInfo);
|
|
QVERIFY(job);
|
|
QSignalSpy spyMimeTypeFound(job, &KIO::TransferJob::mimeTypeFound);
|
|
QVERIFY2(job->exec(), qPrintable(job->errorString()));
|
|
QCOMPARE(spyMimeTypeFound.count(), 1);
|
|
QCOMPARE(spyMimeTypeFound[0][0], QVariant::fromValue(static_cast<KIO::Job *>(job)));
|
|
QCOMPARE(spyMimeTypeFound[0][1].toString(), QString("text/html"));
|
|
#endif
|
|
}
|
|
|
|
void JobTest::mimeTypeError()
|
|
{
|
|
// KIO::mimetype() on a file that doesn't exist
|
|
const QString filePath = homeTmpDir() + "doesNotExist";
|
|
KIO::MimetypeJob *job = KIO::mimetype(QUrl::fromLocalFile(filePath), KIO::HideProgressInfo);
|
|
QVERIFY(job);
|
|
QSignalSpy spyMimeTypeFound(job, &KIO::TransferJob::mimeTypeFound);
|
|
QSignalSpy spyResult(job, &KJob::result);
|
|
QVERIFY(!job->exec());
|
|
QCOMPARE(spyMimeTypeFound.count(), 0);
|
|
QCOMPARE(spyResult.count(), 1);
|
|
}
|
|
|
|
void JobTest::moveFileDestAlreadyExists_data()
|
|
{
|
|
QTest::addColumn<bool>("autoSkip");
|
|
|
|
QTest::newRow("autoSkip") << true;
|
|
QTest::newRow("manualSkip") << false;
|
|
}
|
|
|
|
void JobTest::moveFileDestAlreadyExists() // #157601
|
|
{
|
|
QFETCH(bool, autoSkip);
|
|
|
|
const QString file1 = homeTmpDir() + "fileFromHome";
|
|
createTestFile(file1);
|
|
const QString file2 = homeTmpDir() + "fileFromHome2";
|
|
createTestFile(file2);
|
|
const QString file3 = homeTmpDir() + "anotherFile";
|
|
createTestFile(file3);
|
|
const QString existingDest = otherTmpDir() + "fileFromHome";
|
|
createTestFile(existingDest);
|
|
const QString existingDest2 = otherTmpDir() + "fileFromHome2";
|
|
createTestFile(existingDest2);
|
|
|
|
ScopedCleaner cleaner([] {
|
|
QFile::remove(otherTmpDir() + "anotherFile");
|
|
});
|
|
|
|
const QList<QUrl> urls{QUrl::fromLocalFile(file1), QUrl::fromLocalFile(file2), QUrl::fromLocalFile(file3)};
|
|
KIO::CopyJob *job = KIO::move(urls, QUrl::fromLocalFile(otherTmpDir()), KIO::HideProgressInfo);
|
|
MockAskUserInterface *askUserHandler = nullptr;
|
|
if (autoSkip) {
|
|
job->setUiDelegate(nullptr);
|
|
job->setAutoSkip(true);
|
|
} else {
|
|
// Simulate the user pressing "Skip" in the dialog.
|
|
job->setUiDelegate(new KJobUiDelegate);
|
|
askUserHandler = new MockAskUserInterface(job->uiDelegate());
|
|
askUserHandler->m_renameResult = KIO::Result_Skip;
|
|
}
|
|
QVERIFY2(job->exec(), qPrintable(job->errorString()));
|
|
|
|
if (askUserHandler) {
|
|
QCOMPARE(askUserHandler->m_askUserRenameCalled, 2);
|
|
QCOMPARE(askUserHandler->m_askUserSkipCalled, 0);
|
|
}
|
|
QVERIFY(QFile::exists(file1)); // it was skipped
|
|
QVERIFY(QFile::exists(file2)); // it was skipped
|
|
QVERIFY(!QFile::exists(file3)); // it was moved
|
|
|
|
QCOMPARE(job->totalAmount(KJob::Files), 3);
|
|
QCOMPARE(job->totalAmount(KJob::Directories), 0);
|
|
QCOMPARE(job->processedAmount(KJob::Files), 1);
|
|
QCOMPARE(job->processedAmount(KJob::Directories), 0);
|
|
QCOMPARE(job->percent(), 100);
|
|
}
|
|
|
|
void JobTest::copyFileDestAlreadyExists_data()
|
|
{
|
|
QTest::addColumn<bool>("autoSkip");
|
|
|
|
QTest::newRow("autoSkip") << true;
|
|
QTest::newRow("manualSkip") << false;
|
|
}
|
|
|
|
static void simulatePressingSkip(KJob *job)
|
|
{
|
|
// Simulate the user pressing "Skip" in the dialog.
|
|
job->setUiDelegate(new KJobUiDelegate);
|
|
auto *askUserHandler = new MockAskUserInterface(job->uiDelegate());
|
|
askUserHandler->m_skipResult = KIO::Result_Skip;
|
|
}
|
|
|
|
void JobTest::copyFileDestAlreadyExists() // to test skipping when copying
|
|
{
|
|
QFETCH(bool, autoSkip);
|
|
const QString file1 = homeTmpDir() + "fileFromHome";
|
|
createTestFile(file1);
|
|
const QString file2 = homeTmpDir() + "anotherFile";
|
|
createTestFile(file2);
|
|
const QString existingDest = otherTmpDir() + "fileFromHome";
|
|
createTestFile(existingDest);
|
|
|
|
ScopedCleaner cleaner([] {
|
|
QFile::remove(otherTmpDir() + "anotherFile");
|
|
});
|
|
|
|
const QList<QUrl> urls{QUrl::fromLocalFile(file1), QUrl::fromLocalFile(file2)};
|
|
KIO::CopyJob *job = KIO::copy(urls, QUrl::fromLocalFile(otherTmpDir()), KIO::HideProgressInfo);
|
|
if (autoSkip) {
|
|
job->setUiDelegate(nullptr);
|
|
job->setAutoSkip(true);
|
|
} else {
|
|
simulatePressingSkip(job);
|
|
}
|
|
QVERIFY2(job->exec(), qPrintable(job->errorString()));
|
|
QVERIFY(QFile::exists(otherTmpDir() + "anotherFile"));
|
|
|
|
QCOMPARE(job->totalAmount(KJob::Files), 2); // file1, file2
|
|
QCOMPARE(job->totalAmount(KJob::Directories), 0);
|
|
QCOMPARE(job->processedAmount(KJob::Files), 1);
|
|
QCOMPARE(job->processedAmount(KJob::Directories), 0);
|
|
QCOMPARE(job->percent(), 100);
|
|
}
|
|
|
|
void JobTest::moveDestAlreadyExistsAutoRename_data()
|
|
{
|
|
QTest::addColumn<bool>("samePartition");
|
|
QTest::addColumn<bool>("moveDirs");
|
|
|
|
QTest::newRow("files_same_partition") << true << false;
|
|
QTest::newRow("files_other_partition") << false << false;
|
|
QTest::newRow("dirs_same_partition") << true << true;
|
|
QTest::newRow("dirs_other_partition") << false << true;
|
|
}
|
|
|
|
void JobTest::moveDestAlreadyExistsAutoRename()
|
|
{
|
|
QFETCH(bool, samePartition);
|
|
QFETCH(bool, moveDirs);
|
|
|
|
QString dir;
|
|
if (samePartition) {
|
|
dir = homeTmpDir() + "dir/";
|
|
QVERIFY(QDir(dir).exists() || QDir().mkdir(dir));
|
|
} else {
|
|
dir = otherTmpDir();
|
|
}
|
|
moveDestAlreadyExistsAutoRename(dir, moveDirs);
|
|
|
|
if (samePartition) {
|
|
// cleanup
|
|
KIO::Job *job = KIO::del(QUrl::fromLocalFile(dir), KIO::HideProgressInfo);
|
|
QVERIFY2(job->exec(), qPrintable(job->errorString()));
|
|
QVERIFY(!QFile::exists(dir));
|
|
}
|
|
}
|
|
|
|
void JobTest::moveDestAlreadyExistsAutoRename(const QString &destDir, bool moveDirs) // #256650
|
|
{
|
|
const QString prefix = moveDirs ? QStringLiteral("dir ") : QStringLiteral("file ");
|
|
|
|
const QString file1 = homeTmpDir() + prefix + "(1)";
|
|
const QString file2 = homeTmpDir() + prefix + "(2)";
|
|
const QString existingDest1 = destDir + prefix + "(1)";
|
|
const QString existingDest2 = destDir + prefix + "(2)";
|
|
const QStringList sources = QStringList{file1, file2, existingDest1, existingDest2};
|
|
for (const QString &source : sources) {
|
|
if (moveDirs) {
|
|
QVERIFY(QDir().mkdir(source));
|
|
createTestFile(source + "/innerfile");
|
|
createTestFile(source + "/innerfile2");
|
|
} else {
|
|
createTestFile(source);
|
|
}
|
|
}
|
|
const QString file3 = destDir + prefix + "(3)";
|
|
const QString file4 = destDir + prefix + "(4)";
|
|
|
|
ScopedCleaner cleaner([&]() {
|
|
if (moveDirs) {
|
|
QDir().rmdir(file1);
|
|
QDir().rmdir(file2);
|
|
QDir().rmdir(file3);
|
|
QDir().rmdir(file4);
|
|
} else {
|
|
QFile::remove(file1);
|
|
QFile::remove(file2);
|
|
QFile::remove(file3);
|
|
QFile::remove(file4);
|
|
}
|
|
});
|
|
|
|
const QList<QUrl> urls = {QUrl::fromLocalFile(file1), QUrl::fromLocalFile(file2)};
|
|
KIO::CopyJob *job = KIO::move(urls, QUrl::fromLocalFile(destDir), KIO::HideProgressInfo);
|
|
job->setUiDelegate(nullptr);
|
|
job->setUiDelegateExtension(nullptr);
|
|
job->setAutoRename(true);
|
|
|
|
QSignalSpy spyRenamed(job, &KIO::CopyJob::renamed);
|
|
|
|
// qDebug() << QDir(destDir).entryList();
|
|
|
|
QVERIFY2(job->exec(), qPrintable(job->errorString()));
|
|
|
|
// qDebug() << QDir(destDir).entryList();
|
|
QVERIFY(!QFile::exists(file1)); // it was moved
|
|
QVERIFY(!QFile::exists(file2)); // it was moved
|
|
|
|
QVERIFY(QFile::exists(existingDest1));
|
|
QVERIFY(QFile::exists(existingDest2));
|
|
QVERIFY(QFile::exists(file3));
|
|
QVERIFY(QFile::exists(file4));
|
|
|
|
QVERIFY(!spyRenamed.isEmpty());
|
|
|
|
auto list = spyRenamed.takeFirst();
|
|
QCOMPARE(list.at(1).toUrl(), QUrl::fromLocalFile(destDir + prefix + "(1)"));
|
|
QCOMPARE(list.at(2).toUrl(), QUrl::fromLocalFile(file3));
|
|
|
|
bool samePartition = false;
|
|
// Normally we'd see renamed(1, 3) and renamed(2, 4)
|
|
// But across partitions, direct rename fails, and we end up with a task list of
|
|
// 1->3, 2->3 since renaming 1 to 3 didn't happen yet.
|
|
// so renamed(2, 3) is emitted, as if the user had chosen that.
|
|
// And when that fails, we then get (3, 4)
|
|
if (spyRenamed.count() == 1) {
|
|
// It was indeed on the same partition
|
|
samePartition = true;
|
|
list = spyRenamed.takeFirst();
|
|
QCOMPARE(list.at(1).toUrl(), QUrl::fromLocalFile(destDir + prefix + "(2)"));
|
|
QCOMPARE(list.at(2).toUrl(), QUrl::fromLocalFile(file4));
|
|
} else {
|
|
// Remove all renamed signals about innerfiles
|
|
spyRenamed.erase(std::remove_if(spyRenamed.begin(),
|
|
spyRenamed.end(),
|
|
[](const QList<QVariant> &spy) {
|
|
return spy.at(1).toUrl().path().contains("innerfile");
|
|
}),
|
|
spyRenamed.end());
|
|
|
|
list = spyRenamed.takeFirst();
|
|
QCOMPARE(list.at(1).toUrl(), QUrl::fromLocalFile(destDir + prefix + "(2)"));
|
|
QCOMPARE(list.at(2).toUrl(), QUrl::fromLocalFile(file3));
|
|
|
|
list = spyRenamed.takeFirst();
|
|
QCOMPARE(list.at(1).toUrl(), QUrl::fromLocalFile(file3));
|
|
QCOMPARE(list.at(2).toUrl(), QUrl::fromLocalFile(file4));
|
|
}
|
|
|
|
if (samePartition) {
|
|
QCOMPARE(job->totalAmount(KJob::Files), 2); // direct-renamed, so counted as files
|
|
QCOMPARE(job->totalAmount(KJob::Directories), 0);
|
|
QCOMPARE(job->processedAmount(KJob::Files), 2);
|
|
QCOMPARE(job->processedAmount(KJob::Directories), 0);
|
|
} else {
|
|
if (moveDirs) {
|
|
QCOMPARE(job->totalAmount(KJob::Directories), 2);
|
|
QCOMPARE(job->totalAmount(KJob::Files), 4); // innerfiles
|
|
QCOMPARE(job->processedAmount(KJob::Directories), 2);
|
|
QCOMPARE(job->processedAmount(KJob::Files), 4);
|
|
} else {
|
|
QCOMPARE(job->totalAmount(KJob::Files), 2);
|
|
QCOMPARE(job->totalAmount(KJob::Directories), 0);
|
|
QCOMPARE(job->processedAmount(KJob::Files), 2);
|
|
QCOMPARE(job->processedAmount(KJob::Directories), 0);
|
|
}
|
|
}
|
|
|
|
QCOMPARE(job->percent(), 100);
|
|
}
|
|
|
|
void JobTest::copyDirectoryAlreadyExistsSkip()
|
|
{
|
|
// when copying a directory (which contains at least one file) to some location, and then
|
|
// copying the same dir to the same location again, and clicking "Skip" there should be no
|
|
// segmentation fault, bug 408350
|
|
|
|
const QString src = homeTmpDir() + "a";
|
|
createTestDirectory(src);
|
|
const QString dest = homeTmpDir() + "dest";
|
|
createTestDirectory(dest);
|
|
|
|
QUrl u = QUrl::fromLocalFile(src);
|
|
QUrl d = QUrl::fromLocalFile(dest);
|
|
|
|
KIO::Job *job = KIO::copy(u, d, KIO::HideProgressInfo);
|
|
job->setUiDelegate(nullptr);
|
|
QVERIFY2(job->exec(), qPrintable(job->errorString()));
|
|
QVERIFY(QFile::exists(dest + QStringLiteral("/a/testfile")));
|
|
|
|
job = KIO::copy(u, d, KIO::HideProgressInfo);
|
|
|
|
simulatePressingSkip(job);
|
|
|
|
QVERIFY2(job->exec(), qPrintable(job->errorString()));
|
|
QVERIFY(QFile::exists(dest + QStringLiteral("/a/testfile")));
|
|
|
|
QDir(src).removeRecursively();
|
|
QDir(dest).removeRecursively();
|
|
|
|
QCOMPARE(job->totalAmount(KJob::Files), 2); // testfile, testlink
|
|
QCOMPARE(job->totalAmount(KJob::Directories), 1);
|
|
QCOMPARE(job->processedAmount(KJob::Files), 0);
|
|
QCOMPARE(job->processedAmount(KJob::Directories), 1);
|
|
QCOMPARE(job->percent(), 0);
|
|
}
|
|
|
|
void JobTest::copyFileAlreadyExistsRename()
|
|
{
|
|
const QString sourceFile = homeTmpDir() + "file";
|
|
const QString dest = homeTmpDir() + "dest/";
|
|
const QString alreadyExisting = dest + "file";
|
|
const QString renamedFile = dest + "file-renamed";
|
|
|
|
createTestFile(sourceFile);
|
|
createTestFile(alreadyExisting);
|
|
QVERIFY(QFile::exists(sourceFile));
|
|
QVERIFY(QFile::exists(alreadyExisting));
|
|
|
|
createTestDirectory(dest);
|
|
|
|
ScopedCleaner cleaner([&] {
|
|
QVERIFY(QFile(sourceFile).remove());
|
|
QVERIFY(QDir(dest).removeRecursively());
|
|
});
|
|
|
|
QUrl s = QUrl::fromLocalFile(sourceFile);
|
|
QUrl d = QUrl::fromLocalFile(dest);
|
|
|
|
KIO::CopyJob *job = KIO::copy(s, d, KIO::HideProgressInfo);
|
|
// Simulate the user pressing "Rename" in the dialog and choosing another destination.
|
|
job->setUiDelegate(new KJobUiDelegate);
|
|
auto *askUserHandler = new MockAskUserInterface(job->uiDelegate());
|
|
askUserHandler->m_renameResult = KIO::Result_Rename;
|
|
askUserHandler->m_newDestUrl = QUrl::fromLocalFile(renamedFile);
|
|
|
|
QSignalSpy spyRenamed(job, &KIO::CopyJob::renamed);
|
|
|
|
QVERIFY2(job->exec(), qPrintable(job->errorString()));
|
|
QVERIFY(QFile::exists(renamedFile));
|
|
|
|
QCOMPARE(spyRenamed.count(), 1);
|
|
auto list = spyRenamed.takeFirst();
|
|
QCOMPARE(list.at(1).toUrl(), QUrl::fromLocalFile(alreadyExisting));
|
|
QCOMPARE(list.at(2).toUrl(), QUrl::fromLocalFile(renamedFile));
|
|
}
|
|
|
|
void JobTest::safeOverwrite_data()
|
|
{
|
|
QTest::addColumn<bool>("destFileExists");
|
|
|
|
QTest::newRow("dest_file_exists") << true;
|
|
QTest::newRow("dest_file_does_not_exist") << false;
|
|
}
|
|
|
|
void JobTest::safeOverwrite()
|
|
{
|
|
#ifdef Q_OS_WIN
|
|
QSKIP("Test skipped on Windows");
|
|
#endif
|
|
|
|
QFETCH(bool, destFileExists);
|
|
const QString srcDir = homeTmpDir() + "overwrite";
|
|
const QString srcFile = srcDir + "/testfile";
|
|
const QString destDir = otherTmpDir() + "overwrite_other";
|
|
const QString destFile = destDir + "/testfile";
|
|
const QString destPartFile = destFile + ".part";
|
|
|
|
createTestDirectory(srcDir);
|
|
createTestDirectory(destDir);
|
|
|
|
ScopedCleaner cleaner([&] {
|
|
QDir(srcDir).removeRecursively();
|
|
QDir(destDir).removeRecursively();
|
|
});
|
|
|
|
const int srcSize = 1000000; // ~1MB
|
|
QVERIFY(QFile::resize(srcFile, srcSize));
|
|
if (!destFileExists) {
|
|
QVERIFY(QFile::remove(destFile));
|
|
} else {
|
|
QVERIFY(QFile::exists(destFile));
|
|
}
|
|
QVERIFY(!QFile::exists(destPartFile));
|
|
|
|
if (otherTmpDirIsOnSamePartition()) {
|
|
QSKIP(qPrintable(QStringLiteral("This test requires %1 and %2 to be on different partitions").arg(srcDir, destDir)));
|
|
}
|
|
|
|
KIO::FileCopyJob *job = KIO::file_move(QUrl::fromLocalFile(srcFile), QUrl::fromLocalFile(destFile), -1, KIO::HideProgressInfo | KIO::Overwrite);
|
|
job->setUiDelegate(nullptr);
|
|
QSignalSpy spyTotalSize(job, &KIO::FileCopyJob::totalSize);
|
|
connect(job, &KIO::FileCopyJob::processedSize, this, [&](KJob *job, qulonglong size) {
|
|
Q_UNUSED(job);
|
|
if (size > 0 && size < srcSize) {
|
|
// To avoid overwriting dest, we want the KIO worker to use dest.part
|
|
QCOMPARE(QFileInfo::exists(destPartFile), destFileExists);
|
|
}
|
|
});
|
|
QVERIFY2(job->exec(), qPrintable(job->errorString()));
|
|
QVERIFY(QFile::exists(destFile));
|
|
QVERIFY(!QFile::exists(srcFile));
|
|
QVERIFY(!QFile::exists(destPartFile));
|
|
QCOMPARE(spyTotalSize.count(), 1);
|
|
}
|
|
|
|
void JobTest::overwriteOlderFiles_data()
|
|
{
|
|
QTest::addColumn<bool>("destFileOlder");
|
|
QTest::addColumn<bool>("moving");
|
|
|
|
QTest::newRow("dest_file_older_copying") << true << false;
|
|
QTest::newRow("dest_file_older_moving") << true << true;
|
|
QTest::newRow("dest_file_younger_copying") << false << false;
|
|
QTest::newRow("dest_file_younger_moving") << false << true;
|
|
}
|
|
|
|
void JobTest::overwriteOlderFiles()
|
|
{
|
|
QFETCH(bool, destFileOlder);
|
|
QFETCH(bool, moving);
|
|
const QString srcDir = homeTmpDir() + "overwrite";
|
|
const QString srcFile = srcDir + "/testfile";
|
|
const QString srcFile2 = srcDir + "/testfile2";
|
|
const QString srcFile3 = srcDir + "/testfile3";
|
|
const QString destDir = otherTmpDir() + "overwrite_other";
|
|
const QString destFile = destDir + "/testfile";
|
|
const QString destFile2 = destDir + "/testfile2";
|
|
const QString destFile3 = destDir + "/testfile3";
|
|
const QString destPartFile = destFile + ".part";
|
|
|
|
createTestDirectory(srcDir);
|
|
createTestDirectory(destDir);
|
|
createTestFile(srcFile2);
|
|
createTestFile(srcFile3);
|
|
createTestFile(destFile2);
|
|
createTestFile(destFile3);
|
|
QVERIFY(!QFile::exists(destPartFile));
|
|
|
|
const int srcSize = 1000; // ~1KB
|
|
QVERIFY(QFile::resize(srcFile, srcSize));
|
|
QVERIFY(QFile::resize(srcFile2, srcSize));
|
|
QVERIFY(QFile::resize(srcFile3, srcSize));
|
|
if (destFileOlder) {
|
|
setTimeStamp(destFile, QFile(srcFile).fileTime(QFileDevice::FileModificationTime).addSecs(-2));
|
|
setTimeStamp(destFile2, QFile(srcFile2).fileTime(QFileDevice::FileModificationTime).addSecs(-2));
|
|
|
|
QVERIFY(QFile(destFile).fileTime(QFileDevice::FileModificationTime) <= QFile(srcFile).fileTime(QFileDevice::FileModificationTime));
|
|
QVERIFY(QFile(destFile2).fileTime(QFileDevice::FileModificationTime) <= QFile(srcFile2).fileTime(QFileDevice::FileModificationTime));
|
|
} else {
|
|
setTimeStamp(destFile, QFile(srcFile).fileTime(QFileDevice::FileModificationTime).addSecs(2));
|
|
setTimeStamp(destFile2, QFile(srcFile2).fileTime(QFileDevice::FileModificationTime).addSecs(2));
|
|
|
|
QVERIFY(QFile(destFile).fileTime(QFileDevice::FileModificationTime) >= QFile(srcFile).fileTime(QFileDevice::FileModificationTime));
|
|
QVERIFY(QFile(destFile2).fileTime(QFileDevice::FileModificationTime) >= QFile(srcFile2).fileTime(QFileDevice::FileModificationTime));
|
|
}
|
|
// to have an always skipped file
|
|
setTimeStamp(destFile3, QFile(srcFile3).fileTime(QFileDevice::FileModificationTime).addSecs(2));
|
|
|
|
KIO::CopyJob *job;
|
|
if (moving) {
|
|
job = KIO::move({QUrl::fromLocalFile(srcFile), QUrl::fromLocalFile(srcFile2), QUrl::fromLocalFile(srcFile3)},
|
|
QUrl::fromLocalFile(destDir),
|
|
KIO::HideProgressInfo);
|
|
} else {
|
|
job = KIO::copy({QUrl::fromLocalFile(srcFile), QUrl::fromLocalFile(srcFile2), QUrl::fromLocalFile(srcFile3)},
|
|
QUrl::fromLocalFile(destDir),
|
|
KIO::HideProgressInfo);
|
|
}
|
|
|
|
job->setUiDelegate(new KJobUiDelegate);
|
|
auto *askUserHandler = new MockAskUserInterface(job->uiDelegate());
|
|
askUserHandler->m_renameResult = KIO::Result_OverwriteWhenOlder;
|
|
|
|
QVERIFY2(job->exec(), qPrintable(job->errorString()));
|
|
QCOMPARE(askUserHandler->m_askUserRenameCalled, 1);
|
|
QVERIFY(!QFile::exists(destPartFile));
|
|
// QCOMPARE(spyTotalSize.count(), 1);
|
|
|
|
// skipped file whose dest is always newer
|
|
QVERIFY(QFile::exists(srcFile3)); // it was skipped
|
|
QCOMPARE(QFile(destFile3).size(), 11);
|
|
|
|
if (destFileOlder) {
|
|
// files were overwritten
|
|
QCOMPARE(QFile(destFile).size(), 1000);
|
|
QCOMPARE(QFile(destFile2).size(), 1000);
|
|
|
|
// files were overwritten
|
|
QCOMPARE(job->processedAmount(KJob::Files), 2);
|
|
QCOMPARE(job->processedAmount(KJob::Directories), 0);
|
|
|
|
if (moving) {
|
|
QVERIFY(!QFile::exists(srcFile)); // it was moved
|
|
QVERIFY(!QFile::exists(srcFile2)); // it was moved
|
|
} else {
|
|
QVERIFY(QFile::exists(srcFile)); // it was copied
|
|
QVERIFY(QFile::exists(srcFile2)); // it was copied
|
|
|
|
QCOMPARE(QFile(destFile).fileTime(QFileDevice::FileModificationTime), QFile(srcFile).fileTime(QFileDevice::FileModificationTime));
|
|
QCOMPARE(QFile(destFile2).fileTime(QFileDevice::FileModificationTime), QFile(srcFile2).fileTime(QFileDevice::FileModificationTime));
|
|
}
|
|
} else {
|
|
// files were skipped
|
|
QCOMPARE(job->processedAmount(KJob::Files), 0);
|
|
QCOMPARE(job->processedAmount(KJob::Directories), 0);
|
|
|
|
QCOMPARE(QFile(destFile).size(), 11);
|
|
QCOMPARE(QFile(destFile2).size(), 11);
|
|
|
|
QVERIFY(QFile::exists(srcFile));
|
|
QVERIFY(QFile::exists(srcFile2));
|
|
}
|
|
|
|
QDir(srcDir).removeRecursively();
|
|
QDir(destDir).removeRecursively();
|
|
}
|
|
|
|
void JobTest::moveAndOverwrite()
|
|
{
|
|
const QString sourceFile = homeTmpDir() + "fileFromHome";
|
|
createTestFile(sourceFile);
|
|
QString existingDest = otherTmpDir() + "fileFromHome";
|
|
createTestFile(existingDest);
|
|
|
|
KIO::FileCopyJob *job = KIO::file_move(QUrl::fromLocalFile(sourceFile), QUrl::fromLocalFile(existingDest), -1, KIO::HideProgressInfo | KIO::Overwrite);
|
|
job->setUiDelegate(nullptr);
|
|
QVERIFY2(job->exec(), qPrintable(job->errorString()));
|
|
QVERIFY(!QFile::exists(sourceFile)); // it was moved
|
|
|
|
#ifndef Q_OS_WIN
|
|
// Now same thing when the target is a symlink to the source
|
|
createTestFile(sourceFile);
|
|
createTestSymlink(existingDest, QFile::encodeName(sourceFile));
|
|
QVERIFY(QFile::exists(existingDest));
|
|
job = KIO::file_move(QUrl::fromLocalFile(sourceFile), QUrl::fromLocalFile(existingDest), -1, KIO::HideProgressInfo | KIO::Overwrite);
|
|
job->setUiDelegate(nullptr);
|
|
QVERIFY2(job->exec(), qPrintable(job->errorString()));
|
|
QVERIFY(!QFile::exists(sourceFile)); // it was moved
|
|
|
|
// Now same thing when the target is a symlink to another file
|
|
createTestFile(sourceFile);
|
|
createTestFile(sourceFile + QLatin1Char('2'));
|
|
createTestSymlink(existingDest, QFile::encodeName(sourceFile + QLatin1Char('2')));
|
|
QVERIFY(QFile::exists(existingDest));
|
|
job = KIO::file_move(QUrl::fromLocalFile(sourceFile), QUrl::fromLocalFile(existingDest), -1, KIO::HideProgressInfo | KIO::Overwrite);
|
|
job->setUiDelegate(nullptr);
|
|
QVERIFY2(job->exec(), qPrintable(job->errorString()));
|
|
QVERIFY(!QFile::exists(sourceFile)); // it was moved
|
|
|
|
// Now same thing when the target is a _broken_ symlink
|
|
createTestFile(sourceFile);
|
|
createTestSymlink(existingDest);
|
|
QVERIFY(!QFile::exists(existingDest)); // it exists, but it's broken...
|
|
job = KIO::file_move(QUrl::fromLocalFile(sourceFile), QUrl::fromLocalFile(existingDest), -1, KIO::HideProgressInfo | KIO::Overwrite);
|
|
job->setUiDelegate(nullptr);
|
|
QVERIFY2(job->exec(), qPrintable(job->errorString()));
|
|
QVERIFY(!QFile::exists(sourceFile)); // it was moved
|
|
#endif
|
|
}
|
|
|
|
void JobTest::moveOverSymlinkToSelf() // #169547
|
|
{
|
|
#ifndef Q_OS_WIN
|
|
const QString sourceFile = homeTmpDir() + "fileFromHome";
|
|
createTestFile(sourceFile);
|
|
const QString existingDest = homeTmpDir() + "testlink";
|
|
createTestSymlink(existingDest, QFile::encodeName(sourceFile));
|
|
QVERIFY(QFile::exists(existingDest));
|
|
|
|
KIO::CopyJob *job = KIO::move(QUrl::fromLocalFile(sourceFile), QUrl::fromLocalFile(existingDest), KIO::HideProgressInfo);
|
|
job->setUiDelegate(nullptr);
|
|
QVERIFY(!job->exec());
|
|
QCOMPARE(job->error(), (int)KIO::ERR_FILE_ALREADY_EXIST); // and not ERR_IDENTICAL_FILES!
|
|
QVERIFY(QFile::exists(sourceFile)); // it not moved
|
|
#endif
|
|
}
|
|
|
|
void JobTest::createSymlink()
|
|
{
|
|
#ifdef Q_OS_WIN
|
|
QSKIP("Test skipped on Windows");
|
|
#endif
|
|
const QString sourceFile = homeTmpDir() + "fileFromHome";
|
|
createTestFile(sourceFile);
|
|
const QString destDir = homeTmpDir() + "dest";
|
|
QVERIFY(QDir().mkpath(destDir));
|
|
|
|
ScopedCleaner cleaner([&] {
|
|
QDir(destDir).removeRecursively();
|
|
});
|
|
|
|
// With KIO::link (high-level)
|
|
KIO::CopyJob *job = KIO::link(QUrl::fromLocalFile(sourceFile), QUrl::fromLocalFile(destDir), KIO::HideProgressInfo);
|
|
QVERIFY2(job->exec(), qPrintable(job->errorString()));
|
|
QVERIFY(QFileInfo::exists(sourceFile));
|
|
const QString dest = destDir + "/fileFromHome";
|
|
QVERIFY(QFileInfo(dest).isSymLink());
|
|
QCOMPARE(QFileInfo(dest).symLinkTarget(), sourceFile);
|
|
QFile::remove(dest);
|
|
|
|
// With KIO::symlink (low-level)
|
|
const QString linkPath = destDir + "/link";
|
|
KIO::Job *symlinkJob = KIO::symlink(sourceFile, QUrl::fromLocalFile(linkPath), KIO::HideProgressInfo);
|
|
QVERIFY2(symlinkJob->exec(), qPrintable(symlinkJob->errorString()));
|
|
QVERIFY(QFileInfo::exists(sourceFile));
|
|
QVERIFY(QFileInfo(linkPath).isSymLink());
|
|
QCOMPARE(QFileInfo(linkPath).symLinkTarget(), sourceFile);
|
|
}
|
|
|
|
void JobTest::createSymlinkTargetDirDoesntExist()
|
|
{
|
|
#ifdef Q_OS_WIN
|
|
QSKIP("Test skipped on Windows");
|
|
#endif
|
|
const QString sourceFile = homeTmpDir() + "fileFromHome";
|
|
createTestFile(sourceFile);
|
|
const QString destDir = homeTmpDir() + "dest/does/not/exist";
|
|
|
|
KIO::CopyJob *job = KIO::link(QUrl::fromLocalFile(sourceFile), QUrl::fromLocalFile(destDir), KIO::HideProgressInfo);
|
|
QVERIFY(!job->exec());
|
|
QCOMPARE(job->error(), static_cast<int>(KIO::ERR_CANNOT_SYMLINK));
|
|
}
|
|
|
|
void JobTest::createSymlinkAsShouldSucceed()
|
|
{
|
|
#ifdef Q_OS_WIN
|
|
QSKIP("Test skipped on Windows");
|
|
#endif
|
|
const QString sourceFile = homeTmpDir() + "fileFromHome";
|
|
createTestFile(sourceFile);
|
|
const QString dest = homeTmpDir() + "testlink";
|
|
QFile::remove(dest); // just in case
|
|
|
|
ScopedCleaner cleaner([&] {
|
|
QVERIFY(QFile::remove(dest));
|
|
});
|
|
|
|
KIO::CopyJob *job = KIO::linkAs(QUrl::fromLocalFile(sourceFile), QUrl::fromLocalFile(dest), KIO::HideProgressInfo);
|
|
QVERIFY2(job->exec(), qPrintable(job->errorString()));
|
|
QVERIFY(QFileInfo::exists(sourceFile));
|
|
QVERIFY(QFileInfo(dest).isSymLink());
|
|
}
|
|
|
|
void JobTest::createSymlinkAsShouldFailDirectoryExists()
|
|
{
|
|
#ifdef Q_OS_WIN
|
|
QSKIP("Test skipped on Windows");
|
|
#endif
|
|
const QString sourceFile = homeTmpDir() + "fileFromHome";
|
|
createTestFile(sourceFile);
|
|
const QString dest = homeTmpDir() + "dest";
|
|
QVERIFY(QDir().mkpath(dest)); // dest exists as a directory
|
|
|
|
ScopedCleaner cleaner([&] {
|
|
QVERIFY(QDir().rmdir(dest));
|
|
});
|
|
|
|
// With KIO::link (high-level)
|
|
KIO::CopyJob *job = KIO::linkAs(QUrl::fromLocalFile(sourceFile), QUrl::fromLocalFile(dest), KIO::HideProgressInfo);
|
|
QVERIFY(!job->exec());
|
|
QCOMPARE(job->error(), (int)KIO::ERR_DIR_ALREADY_EXIST);
|
|
QVERIFY(QFileInfo::exists(sourceFile));
|
|
QVERIFY(!QFileInfo::exists(dest + "/fileFromHome"));
|
|
|
|
// With KIO::symlink (low-level)
|
|
KIO::Job *symlinkJob = KIO::symlink(sourceFile, QUrl::fromLocalFile(dest), KIO::HideProgressInfo);
|
|
QVERIFY(!symlinkJob->exec());
|
|
QCOMPARE(symlinkJob->error(), (int)KIO::ERR_DIR_ALREADY_EXIST);
|
|
QVERIFY(QFileInfo::exists(sourceFile));
|
|
}
|
|
|
|
void JobTest::createSymlinkAsShouldFailFileExists()
|
|
{
|
|
#ifdef Q_OS_WIN
|
|
QSKIP("Test skipped on Windows");
|
|
#endif
|
|
const QString sourceFile = homeTmpDir() + "fileFromHome";
|
|
createTestFile(sourceFile);
|
|
const QString dest = homeTmpDir() + "testlink";
|
|
QFile::remove(dest); // just in case
|
|
|
|
ScopedCleaner cleaner([&] {
|
|
QVERIFY(QFile::remove(sourceFile));
|
|
QVERIFY(QFile::remove(dest));
|
|
});
|
|
|
|
// First time works
|
|
KIO::CopyJob *job = KIO::linkAs(QUrl::fromLocalFile(sourceFile), QUrl::fromLocalFile(dest), KIO::HideProgressInfo);
|
|
QVERIFY2(job->exec(), qPrintable(job->errorString()));
|
|
QVERIFY(QFileInfo(dest).isSymLink());
|
|
|
|
// Second time fails (already exists)
|
|
job = KIO::linkAs(QUrl::fromLocalFile(sourceFile), QUrl::fromLocalFile(dest), KIO::HideProgressInfo);
|
|
QVERIFY(!job->exec());
|
|
QCOMPARE(job->error(), (int)KIO::ERR_FILE_ALREADY_EXIST);
|
|
|
|
// KIO::symlink fails too
|
|
KIO::Job *symlinkJob = KIO::symlink(sourceFile, QUrl::fromLocalFile(dest), KIO::HideProgressInfo);
|
|
QVERIFY(!symlinkJob->exec());
|
|
QCOMPARE(symlinkJob->error(), (int)KIO::ERR_FILE_ALREADY_EXIST);
|
|
}
|
|
|
|
void JobTest::createSymlinkWithOverwriteShouldWork()
|
|
{
|
|
#ifdef Q_OS_WIN
|
|
QSKIP("Test skipped on Windows");
|
|
#endif
|
|
const QString sourceFile = homeTmpDir() + "fileFromHome";
|
|
createTestFile(sourceFile);
|
|
const QString dest = homeTmpDir() + "testlink";
|
|
QFile::remove(dest); // just in case
|
|
|
|
ScopedCleaner cleaner([&] {
|
|
QVERIFY(QFile::remove(sourceFile));
|
|
QVERIFY(QFile::remove(dest));
|
|
});
|
|
|
|
// First time works
|
|
KIO::CopyJob *job = KIO::linkAs(QUrl::fromLocalFile(sourceFile), QUrl::fromLocalFile(dest), KIO::HideProgressInfo);
|
|
QVERIFY2(job->exec(), qPrintable(job->errorString()));
|
|
QVERIFY(QFileInfo(dest).isSymLink());
|
|
|
|
// Changing the link target, with overwrite, works
|
|
job = KIO::linkAs(QUrl::fromLocalFile(sourceFile + QLatin1Char('2')), QUrl::fromLocalFile(dest), KIO::Overwrite | KIO::HideProgressInfo);
|
|
QVERIFY2(job->exec(), qPrintable(job->errorString()));
|
|
QVERIFY(QFileInfo(dest).isSymLink());
|
|
QCOMPARE(QFileInfo(dest).symLinkTarget(), QString(sourceFile + QLatin1Char('2')));
|
|
|
|
// Changing the link target using KIO::symlink, with overwrite, works
|
|
KIO::Job *symlinkJob = KIO::symlink(sourceFile + QLatin1Char('3'), QUrl::fromLocalFile(dest), KIO::Overwrite | KIO::HideProgressInfo);
|
|
QVERIFY2(symlinkJob->exec(), qPrintable(symlinkJob->errorString()));
|
|
QVERIFY(QFileInfo(dest).isSymLink());
|
|
QCOMPARE(QFileInfo(dest).symLinkTarget(), QString(sourceFile + QLatin1Char('3')));
|
|
}
|
|
|
|
void JobTest::createBrokenSymlink()
|
|
{
|
|
#ifdef Q_OS_WIN
|
|
QSKIP("Test skipped on Windows");
|
|
#endif
|
|
const QString sourceFile = "/does/not/exist";
|
|
const QString dest = homeTmpDir() + "testlink";
|
|
QFile::remove(dest); // just in case
|
|
KIO::CopyJob *job = KIO::linkAs(QUrl::fromLocalFile(sourceFile), QUrl::fromLocalFile(dest), KIO::HideProgressInfo);
|
|
QVERIFY2(job->exec(), qPrintable(job->errorString()));
|
|
QVERIFY(QFileInfo(dest).isSymLink());
|
|
|
|
// Second time fails (already exists)
|
|
job = KIO::linkAs(QUrl::fromLocalFile(sourceFile), QUrl::fromLocalFile(dest), KIO::HideProgressInfo);
|
|
QVERIFY(!job->exec());
|
|
QCOMPARE(job->error(), (int)KIO::ERR_FILE_ALREADY_EXIST);
|
|
QVERIFY(QFile::remove(dest));
|
|
}
|
|
|
|
void JobTest::cancelCopyAndCleanDest_data()
|
|
{
|
|
QTest::addColumn<bool>("suspend");
|
|
QTest::addColumn<bool>("overwrite");
|
|
|
|
QTest::newRow("suspend_no_overwrite") << true << false;
|
|
QTest::newRow("no_suspend_no_overwrite") << false << false;
|
|
|
|
#ifndef Q_OS_WIN
|
|
QTest::newRow("suspend_with_overwrite") << true << true;
|
|
QTest::newRow("no_suspend_with_overwrite") << false << true;
|
|
#endif
|
|
}
|
|
|
|
void JobTest::cancelCopyAndCleanDest()
|
|
{
|
|
QFETCH(bool, suspend);
|
|
QFETCH(bool, overwrite);
|
|
|
|
const QString baseDir = homeTmpDir();
|
|
const QString srcTemplate = baseDir + QStringLiteral("testfile_XXXXXX");
|
|
const QString destFile = baseDir + QStringLiteral("testfile_copy_slow_") + QString::fromLatin1(QTest::currentDataTag());
|
|
|
|
QTemporaryFile f(srcTemplate);
|
|
if (!f.open()) {
|
|
qFatal("Couldn't open %s", qPrintable(f.fileName()));
|
|
}
|
|
|
|
const int sz = 4000000; //~4MB
|
|
f.seek(sz - 1);
|
|
f.write("0");
|
|
f.close();
|
|
QCOMPARE(f.size(), sz);
|
|
|
|
if (overwrite) {
|
|
createTestFile(destFile);
|
|
}
|
|
const QString destToCheck = (overwrite) ? destFile + QStringLiteral(".part") : destFile;
|
|
|
|
KIO::JobFlag m_overwriteFlag = overwrite ? KIO::Overwrite : KIO::DefaultFlags;
|
|
KIO::FileCopyJob *copyJob = KIO::file_copy(QUrl::fromLocalFile(f.fileName()), QUrl::fromLocalFile(destFile), -1, KIO::HideProgressInfo | m_overwriteFlag);
|
|
copyJob->setUiDelegate(nullptr);
|
|
QSignalSpy spyProcessedSize(copyJob, &KIO::Job::processedSize);
|
|
QSignalSpy spyFinished(copyJob, &KIO::Job::finished);
|
|
connect(copyJob, &KIO::Job::processedSize, this, [destFile, suspend, destToCheck](KJob *job, qulonglong processedSize) {
|
|
if (processedSize > 0) {
|
|
QVERIFY2(QFile::exists(destToCheck), qPrintable(destToCheck));
|
|
qDebug() << "processedSize=" << processedSize << "file size" << QFileInfo(destToCheck).size();
|
|
if (suspend) {
|
|
job->suspend();
|
|
}
|
|
QVERIFY(job->kill());
|
|
}
|
|
});
|
|
|
|
QVERIFY(!copyJob->exec());
|
|
QCOMPARE(spyProcessedSize.count(), 1);
|
|
QCOMPARE(spyFinished.count(), 1);
|
|
QCOMPARE(copyJob->error(), KIO::ERR_USER_CANCELED);
|
|
|
|
// the destination file actual deletion happens after finished() is emitted
|
|
// we need to give some time to the KIO worker to finish the file cleaning
|
|
QTRY_VERIFY2(!QFile::exists(destToCheck), qPrintable(destToCheck));
|
|
}
|
|
|
|
#include "moc_jobtest.cpp"
|