File indexing completed on 2025-01-12 12:29:26
0001 /* 0002 This file is part of the KDE project 0003 SPDX-FileCopyrightText: 2004-2006 David Faure <faure@kde.org> 0004 0005 SPDX-License-Identifier: LGPL-2.0-or-later 0006 */ 0007 0008 #include "jobtest.h" 0009 #include "mockcoredelegateextensions.h" 0010 0011 #include "kio/job.h" 0012 #include "kiotesthelper.h" // createTestFile etc. 0013 #include <kio/chmodjob.h> 0014 #include <kio/copyjob.h> 0015 #include <kio/deletejob.h> 0016 #include <kio/directorysizejob.h> 0017 #include <kio/statjob.h> 0018 #include <kmountpoint.h> 0019 #include <kprotocolinfo.h> 0020 0021 #include <KJobUiDelegate> 0022 #include <KLocalizedString> 0023 0024 #include <QBuffer> 0025 #include <QDebug> 0026 #include <QDir> 0027 #include <QElapsedTimer> 0028 #include <QEventLoop> 0029 #include <QFileInfo> 0030 #include <QHash> 0031 #include <QPointer> 0032 #include <QProcess> 0033 #include <QSignalSpy> 0034 #include <QTemporaryFile> 0035 #include <QTest> 0036 #include <QTimer> 0037 #include <QUrl> 0038 #include <QVariant> 0039 0040 #ifndef Q_OS_WIN 0041 #include <unistd.h> // for readlink 0042 #endif 0043 0044 QTEST_MAIN(JobTest) 0045 0046 // The code comes partly from kdebase/kioslave/trash/testtrash.cpp 0047 0048 static QString otherTmpDir() 0049 { 0050 #ifdef Q_OS_WIN 0051 return QDir::tempPath() + "/jobtest/"; 0052 #else 0053 // This one needs to be on another partition, but we can't guarantee that it is 0054 // On CI, it typically isn't... 0055 return QStringLiteral("/tmp/jobtest/"); 0056 #endif 0057 } 0058 0059 static bool otherTmpDirIsOnSamePartition() // true on CI because it's a LXC container 0060 { 0061 KMountPoint::Ptr srcMountPoint = KMountPoint::currentMountPoints().findByPath(homeTmpDir()); 0062 KMountPoint::Ptr destMountPoint = KMountPoint::currentMountPoints().findByPath(otherTmpDir()); 0063 Q_ASSERT(srcMountPoint); 0064 Q_ASSERT(destMountPoint); 0065 return srcMountPoint->mountedFrom() == destMountPoint->mountedFrom(); 0066 } 0067 0068 void JobTest::initTestCase() 0069 { 0070 QStandardPaths::setTestModeEnabled(true); 0071 QCoreApplication::instance()->setApplicationName("kio/jobtest"); // testing for #357499 0072 0073 // to make sure io is not too fast 0074 qputenv("KIOSLAVE_FILE_ENABLE_TESTMODE", "1"); 0075 0076 s_referenceTimeStamp = QDateTime::currentDateTime().addSecs(-30); // 30 seconds ago 0077 0078 // Start with a clean base dir 0079 cleanupTestCase(); 0080 homeTmpDir(); // create it 0081 if (!QFile::exists(otherTmpDir())) { 0082 bool ok = QDir().mkdir(otherTmpDir()); 0083 if (!ok) { 0084 qFatal("couldn't create %s", qPrintable(otherTmpDir())); 0085 } 0086 } 0087 0088 /***** 0089 * Set platform xattr related commands. 0090 * Linux commands: setfattr, getfattr 0091 * BSD commands: setextattr, getextattr 0092 * MacOS commands: xattr -w, xattr -p 0093 ****/ 0094 m_getXattrCmd = QStandardPaths::findExecutable("getfattr"); 0095 if (m_getXattrCmd.endsWith("getfattr")) { 0096 m_setXattrCmd = QStandardPaths::findExecutable("setfattr"); 0097 m_setXattrFormatArgs = [](const QString &attrName, const QString &value, const QString &fileName) { 0098 return QStringList{QLatin1String("-n"), attrName, QLatin1String("-v"), value, fileName}; 0099 }; 0100 } else { 0101 // On BSD there is lsextattr to list all xattrs and getextattr to get a value 0102 // for specific xattr. For test purposes lsextattr is more suitable to be used 0103 // as m_getXattrCmd, so search for it instead of getextattr. 0104 m_getXattrCmd = QStandardPaths::findExecutable("lsextattr"); 0105 if (m_getXattrCmd.endsWith("lsextattr")) { 0106 m_setXattrCmd = QStandardPaths::findExecutable("setextattr"); 0107 m_setXattrFormatArgs = [](const QString &attrName, const QString &value, const QString &fileName) { 0108 return QStringList{QLatin1String("user"), attrName, value, fileName}; 0109 }; 0110 } else { 0111 m_getXattrCmd = QStandardPaths::findExecutable("xattr"); 0112 m_setXattrFormatArgs = [](const QString &attrName, const QString &value, const QString &fileName) { 0113 return QStringList{QLatin1String("-w"), attrName, value, fileName}; 0114 }; 0115 if (!m_getXattrCmd.endsWith("xattr")) { 0116 qWarning() << "Neither getfattr, getextattr nor xattr was found."; 0117 } 0118 } 0119 } 0120 0121 qRegisterMetaType<KJob *>("KJob*"); 0122 qRegisterMetaType<KIO::Job *>("KIO::Job*"); 0123 qRegisterMetaType<QDateTime>("QDateTime"); 0124 } 0125 0126 void JobTest::cleanupTestCase() 0127 { 0128 QDir(homeTmpDir()).removeRecursively(); 0129 QDir(otherTmpDir()).removeRecursively(); 0130 } 0131 0132 struct ScopedCleaner { 0133 using Func = std::function<void()>; 0134 explicit ScopedCleaner(Func f) 0135 : m_f(std::move(f)) 0136 { 0137 } 0138 ~ScopedCleaner() 0139 { 0140 m_f(); 0141 } 0142 0143 private: 0144 const Func m_f; 0145 }; 0146 0147 void JobTest::enterLoop() 0148 { 0149 QEventLoop eventLoop; 0150 connect(this, &JobTest::exitLoop, &eventLoop, &QEventLoop::quit); 0151 eventLoop.exec(QEventLoop::ExcludeUserInputEvents); 0152 } 0153 0154 void JobTest::storedGet() 0155 { 0156 // qDebug(); 0157 const QString filePath = homeTmpDir() + "fileFromHome"; 0158 createTestFile(filePath); 0159 QUrl u = QUrl::fromLocalFile(filePath); 0160 m_result = -1; 0161 0162 KIO::StoredTransferJob *job = KIO::storedGet(u, KIO::NoReload, KIO::HideProgressInfo); 0163 0164 QSignalSpy spyPercent(job, &KJob::percentChanged); 0165 QVERIFY(spyPercent.isValid()); 0166 job->setUiDelegate(nullptr); 0167 connect(job, &KJob::result, this, &JobTest::slotGetResult); 0168 enterLoop(); 0169 QCOMPARE(m_result, 0); // no error 0170 QCOMPARE(m_data, QByteArray("Hello\0world", 11)); 0171 QCOMPARE(m_data.size(), 11); 0172 QVERIFY(!spyPercent.isEmpty()); 0173 } 0174 0175 void JobTest::slotGetResult(KJob *job) 0176 { 0177 m_result = job->error(); 0178 m_data = static_cast<KIO::StoredTransferJob *>(job)->data(); 0179 Q_EMIT exitLoop(); 0180 } 0181 0182 void JobTest::put() 0183 { 0184 const QString filePath = homeTmpDir() + "fileFromHome"; 0185 QUrl u = QUrl::fromLocalFile(filePath); 0186 KIO::TransferJob *job = KIO::put(u, 0600, KIO::Overwrite | KIO::HideProgressInfo); 0187 quint64 secsSinceEpoch = QDateTime::currentSecsSinceEpoch(); // Use second granularity, supported on all filesystems 0188 QDateTime mtime = QDateTime::fromSecsSinceEpoch(secsSinceEpoch - 30); // 30 seconds ago 0189 job->setModificationTime(mtime); 0190 job->setUiDelegate(nullptr); 0191 connect(job, &KJob::result, this, &JobTest::slotResult); 0192 connect(job, &KIO::TransferJob::dataReq, this, &JobTest::slotDataReq); 0193 m_result = -1; 0194 m_dataReqCount = 0; 0195 enterLoop(); 0196 QVERIFY(m_result == 0); // no error 0197 0198 QFileInfo fileInfo(filePath); 0199 QVERIFY(fileInfo.exists()); 0200 QCOMPARE(fileInfo.size(), 30LL); // "This is a test for KIO::put()\n" 0201 QCOMPARE((int)fileInfo.permissions(), (int)(QFile::ReadOwner | QFile::WriteOwner | QFile::ReadUser | QFile::WriteUser)); 0202 QCOMPARE(fileInfo.lastModified(), mtime); 0203 } 0204 0205 void JobTest::slotDataReq(KIO::Job *, QByteArray &data) 0206 { 0207 // Really not the way you'd write a slotDataReq usually :) 0208 switch (m_dataReqCount++) { 0209 case 0: 0210 data = "This is a test for "; 0211 break; 0212 case 1: 0213 data = "KIO::put()\n"; 0214 break; 0215 case 2: 0216 data = QByteArray(); 0217 break; 0218 } 0219 } 0220 0221 void JobTest::slotResult(KJob *job) 0222 { 0223 m_result = job->error(); 0224 Q_EMIT exitLoop(); 0225 } 0226 0227 void JobTest::storedPut() 0228 { 0229 const QString filePath = homeTmpDir() + "fileFromHome"; 0230 QUrl u = QUrl::fromLocalFile(filePath); 0231 QByteArray putData = "This is the put data"; 0232 KIO::TransferJob *job = KIO::storedPut(putData, u, 0600, KIO::Overwrite | KIO::HideProgressInfo); 0233 0234 QSignalSpy spyPercent(job, &KJob::percentChanged); 0235 QVERIFY(spyPercent.isValid()); 0236 quint64 secsSinceEpoch = QDateTime::currentSecsSinceEpoch(); // Use second granularity, supported on all filesystems 0237 QDateTime mtime = QDateTime::fromSecsSinceEpoch(secsSinceEpoch - 30); // 30 seconds ago 0238 job->setModificationTime(mtime); 0239 job->setUiDelegate(nullptr); 0240 QVERIFY2(job->exec(), qPrintable(job->errorString())); 0241 QFileInfo fileInfo(filePath); 0242 QVERIFY(fileInfo.exists()); 0243 QCOMPARE(fileInfo.size(), (long long)putData.size()); 0244 QCOMPARE((int)fileInfo.permissions(), (int)(QFile::ReadOwner | QFile::WriteOwner | QFile::ReadUser | QFile::WriteUser)); 0245 QCOMPARE(fileInfo.lastModified(), mtime); 0246 QVERIFY(!spyPercent.isEmpty()); 0247 } 0248 0249 void JobTest::storedPutIODevice() 0250 { 0251 const QString filePath = homeTmpDir() + "fileFromHome"; 0252 QBuffer putData; 0253 putData.setData("This is the put data"); 0254 QVERIFY(putData.open(QIODevice::ReadOnly)); 0255 KIO::TransferJob *job = KIO::storedPut(&putData, QUrl::fromLocalFile(filePath), 0600, KIO::Overwrite | KIO::HideProgressInfo); 0256 0257 QSignalSpy spyPercent(job, &KJob::percentChanged); 0258 QVERIFY(spyPercent.isValid()); 0259 quint64 secsSinceEpoch = QDateTime::currentSecsSinceEpoch(); // Use second granularity, supported on all filesystems 0260 QDateTime mtime = QDateTime::fromSecsSinceEpoch(secsSinceEpoch - 30); // 30 seconds ago 0261 job->setModificationTime(mtime); 0262 job->setUiDelegate(nullptr); 0263 QVERIFY2(job->exec(), qPrintable(job->errorString())); 0264 QFileInfo fileInfo(filePath); 0265 QVERIFY(fileInfo.exists()); 0266 QCOMPARE(fileInfo.size(), (long long)putData.size()); 0267 QCOMPARE((int)fileInfo.permissions(), (int)(QFile::ReadOwner | QFile::WriteOwner | QFile::ReadUser | QFile::WriteUser)); 0268 QCOMPARE(fileInfo.lastModified(), mtime); 0269 QVERIFY(!spyPercent.isEmpty()); 0270 } 0271 0272 void JobTest::storedPutIODeviceFile() 0273 { 0274 // Given a source file and a destination file 0275 const QString src = homeTmpDir() + "fileFromHome"; 0276 createTestFile(src); 0277 QVERIFY(QFile::exists(src)); 0278 QFile srcFile(src); 0279 QVERIFY(srcFile.open(QIODevice::ReadOnly)); 0280 const QString dest = homeTmpDir() + "fileFromHome_copied"; 0281 QFile::remove(dest); 0282 const QUrl destUrl = QUrl::fromLocalFile(dest); 0283 0284 // When using storedPut with the QFile as argument 0285 KIO::StoredTransferJob *job = KIO::storedPut(&srcFile, destUrl, 0600, KIO::Overwrite | KIO::HideProgressInfo); 0286 0287 // Then the copy should succeed and the dest file exist 0288 QVERIFY2(job->exec(), qPrintable(job->errorString())); 0289 QVERIFY(QFile::exists(dest)); 0290 QCOMPARE(QFileInfo(src).size(), QFileInfo(dest).size()); 0291 QFile::remove(dest); 0292 } 0293 0294 void JobTest::storedPutIODeviceTempFile() 0295 { 0296 // Create a temp file in the current dir. 0297 QTemporaryFile tempFile(QStringLiteral("jobtest-tmp")); 0298 QVERIFY(tempFile.open()); 0299 0300 // Write something into the file. 0301 QTextStream stream(&tempFile); 0302 stream << QStringLiteral("This is the put data"); 0303 stream.flush(); 0304 QVERIFY(QFileInfo(tempFile).size() > 0); 0305 0306 const QString dest = homeTmpDir() + QLatin1String("tmpfile-dest"); 0307 const QUrl destUrl = QUrl::fromLocalFile(dest); 0308 0309 // QTemporaryFiles are open in ReadWrite mode, 0310 // so we don't need to close and reopen, 0311 // but we need to rewind to the beginning. 0312 tempFile.seek(0); 0313 auto job = KIO::storedPut(&tempFile, destUrl, -1); 0314 0315 QVERIFY2(job->exec(), qPrintable(job->errorString())); 0316 QVERIFY(QFileInfo::exists(dest)); 0317 QCOMPARE(QFileInfo(dest).size(), QFileInfo(tempFile).size()); 0318 QVERIFY(QFile::remove(dest)); 0319 } 0320 0321 void JobTest::storedPutIODeviceFastDevice() 0322 { 0323 const QString filePath = homeTmpDir() + "fileFromHome"; 0324 const QUrl u = QUrl::fromLocalFile(filePath); 0325 const QByteArray putDataContents = "This is the put data"; 0326 QBuffer putDataBuffer; 0327 QVERIFY(putDataBuffer.open(QIODevice::ReadWrite)); 0328 0329 KIO::StoredTransferJob *job = KIO::storedPut(&putDataBuffer, u, 0600, KIO::Overwrite | KIO::HideProgressInfo); 0330 0331 QSignalSpy spyPercent(job, &KJob::percentChanged); 0332 QVERIFY(spyPercent.isValid()); 0333 quint64 secsSinceEpoch = QDateTime::currentSecsSinceEpoch(); // Use second granularity, supported on all filesystems 0334 QDateTime mtime = QDateTime::fromSecsSinceEpoch(secsSinceEpoch - 30); // 30 seconds ago 0335 job->setModificationTime(mtime); 0336 job->setTotalSize(putDataContents.size()); 0337 job->setUiDelegate(nullptr); 0338 job->setAsyncDataEnabled(true); 0339 0340 // Emit the readChannelFinished even before the job has had time to start 0341 const auto pos = putDataBuffer.pos(); 0342 int size = putDataBuffer.write(putDataContents); 0343 putDataBuffer.seek(pos); 0344 Q_EMIT putDataBuffer.readChannelFinished(); 0345 0346 QVERIFY2(job->exec(), qPrintable(job->errorString())); 0347 QCOMPARE(size, putDataContents.size()); 0348 QCOMPARE(putDataBuffer.bytesAvailable(), 0); 0349 0350 QFileInfo fileInfo(filePath); 0351 QVERIFY(fileInfo.exists()); 0352 QCOMPARE(fileInfo.size(), (long long)putDataContents.size()); 0353 QCOMPARE((int)fileInfo.permissions(), (int)(QFile::ReadOwner | QFile::WriteOwner | QFile::ReadUser | QFile::WriteUser)); 0354 QCOMPARE(fileInfo.lastModified(), mtime); 0355 QVERIFY(!spyPercent.isEmpty()); 0356 } 0357 0358 void JobTest::storedPutIODeviceSlowDevice() 0359 { 0360 const QString filePath = homeTmpDir() + "fileFromHome"; 0361 const QUrl u = QUrl::fromLocalFile(filePath); 0362 const QByteArray putDataContents = "This is the put data"; 0363 QBuffer putDataBuffer; 0364 QVERIFY(putDataBuffer.open(QIODevice::ReadWrite)); 0365 0366 KIO::StoredTransferJob *job = KIO::storedPut(&putDataBuffer, u, 0600, KIO::Overwrite | KIO::HideProgressInfo); 0367 0368 QSignalSpy spyPercent(job, &KJob::percentChanged); 0369 QVERIFY(spyPercent.isValid()); 0370 quint64 secsSinceEpoch = QDateTime::currentSecsSinceEpoch(); // Use second granularity, supported on all filesystems 0371 QDateTime mtime = QDateTime::fromSecsSinceEpoch(secsSinceEpoch - 30); // 30 seconds ago 0372 job->setModificationTime(mtime); 0373 job->setTotalSize(putDataContents.size()); 0374 job->setUiDelegate(nullptr); 0375 job->setAsyncDataEnabled(true); 0376 0377 int size = 0; 0378 const auto writeOnce = [&putDataBuffer, &size, putDataContents]() { 0379 const auto pos = putDataBuffer.pos(); 0380 size += putDataBuffer.write(putDataContents); 0381 putDataBuffer.seek(pos); 0382 // qDebug() << "written" << size; 0383 }; 0384 0385 QTimer::singleShot(200, this, writeOnce); 0386 QTimer::singleShot(400, this, writeOnce); 0387 // Simulate the transfer is done 0388 QTimer::singleShot(450, this, [&putDataBuffer]() { 0389 Q_EMIT putDataBuffer.readChannelFinished(); 0390 }); 0391 0392 QVERIFY2(job->exec(), qPrintable(job->errorString())); 0393 QCOMPARE(size, putDataContents.size() * 2); 0394 QCOMPARE(putDataBuffer.bytesAvailable(), 0); 0395 0396 QFileInfo fileInfo(filePath); 0397 QVERIFY(fileInfo.exists()); 0398 QCOMPARE(fileInfo.size(), (long long)putDataContents.size() * 2); 0399 QCOMPARE((int)fileInfo.permissions(), (int)(QFile::ReadOwner | QFile::WriteOwner | QFile::ReadUser | QFile::WriteUser)); 0400 QCOMPARE(fileInfo.lastModified(), mtime); 0401 QVERIFY(!spyPercent.isEmpty()); 0402 } 0403 0404 void JobTest::storedPutIODeviceSlowDeviceBigChunk() 0405 { 0406 const QString filePath = homeTmpDir() + "fileFromHome"; 0407 const QUrl u = QUrl::fromLocalFile(filePath); 0408 const QByteArray putDataContents(300000, 'K'); // Make sure the 300000 is bigger than MAX_READ_BUF_SIZE 0409 QBuffer putDataBuffer; 0410 QVERIFY(putDataBuffer.open(QIODevice::ReadWrite)); 0411 0412 KIO::StoredTransferJob *job = KIO::storedPut(&putDataBuffer, u, 0600, KIO::Overwrite | KIO::HideProgressInfo); 0413 0414 QSignalSpy spyPercent(job, &KJob::percentChanged); 0415 QVERIFY(spyPercent.isValid()); 0416 quint64 secsSinceEpoch = QDateTime::currentSecsSinceEpoch(); // Use second granularity, supported on all filesystems 0417 QDateTime mtime = QDateTime::fromSecsSinceEpoch(secsSinceEpoch - 30); // 30 seconds ago 0418 job->setModificationTime(mtime); 0419 job->setTotalSize(putDataContents.size()); 0420 job->setUiDelegate(nullptr); 0421 job->setAsyncDataEnabled(true); 0422 0423 int size = 0; 0424 const auto writeOnce = [&putDataBuffer, &size, putDataContents]() { 0425 const auto pos = putDataBuffer.pos(); 0426 size += putDataBuffer.write(putDataContents); 0427 putDataBuffer.seek(pos); 0428 // qDebug() << "written" << size; 0429 }; 0430 0431 QTimer::singleShot(200, this, writeOnce); 0432 // Simulate the transfer is done 0433 QTimer::singleShot(450, this, [&putDataBuffer]() { 0434 Q_EMIT putDataBuffer.readChannelFinished(); 0435 }); 0436 0437 QVERIFY2(job->exec(), qPrintable(job->errorString())); 0438 QCOMPARE(size, putDataContents.size()); 0439 QCOMPARE(putDataBuffer.bytesAvailable(), 0); 0440 0441 QFileInfo fileInfo(filePath); 0442 QVERIFY(fileInfo.exists()); 0443 QCOMPARE(fileInfo.size(), (long long)putDataContents.size()); 0444 QCOMPARE((int)fileInfo.permissions(), (int)(QFile::ReadOwner | QFile::WriteOwner | QFile::ReadUser | QFile::WriteUser)); 0445 QCOMPARE(fileInfo.lastModified(), mtime); 0446 QVERIFY(!spyPercent.isEmpty()); 0447 } 0448 0449 void JobTest::asyncStoredPutReadyReadAfterFinish() 0450 { 0451 const QString filePath = homeTmpDir() + "fileFromHome"; 0452 const QUrl u = QUrl::fromLocalFile(filePath); 0453 0454 QBuffer putDataBuffer; 0455 QVERIFY(putDataBuffer.open(QIODevice::ReadWrite)); 0456 0457 KIO::StoredTransferJob *job = KIO::storedPut(&putDataBuffer, u, 0600, KIO::Overwrite | KIO::HideProgressInfo); 0458 job->setAsyncDataEnabled(true); 0459 0460 bool jobFinished = false; 0461 0462 connect(job, &KJob::finished, this, [&jobFinished, &putDataBuffer] { 0463 putDataBuffer.readyRead(); 0464 jobFinished = true; 0465 }); 0466 0467 QTimer::singleShot(200, this, [job]() { 0468 job->kill(); 0469 }); 0470 0471 QTRY_VERIFY(jobFinished); 0472 } 0473 0474 static QHash<QString, QString> getSampleXattrs() 0475 { 0476 QHash<QString, QString> attrs; 0477 attrs["user.name with space"] = "value with spaces"; 0478 attrs["user.baloo.rating"] = "1"; 0479 attrs["user.fnewLine"] = "line1\\nline2"; 0480 attrs["user.flistNull"] = "item1\\0item2"; 0481 attrs["user.fattr.with.a.lot.of.namespaces"] = "true"; 0482 attrs["user.fempty"] = ""; 0483 return attrs; 0484 } 0485 0486 bool JobTest::checkXattrFsSupport(const QString &dir) 0487 { 0488 const QString writeTest = dir + "/fsXattrTestFile"; 0489 createTestFile(writeTest); 0490 bool ret = setXattr(writeTest); 0491 QFile::remove(writeTest); 0492 return ret; 0493 } 0494 0495 bool JobTest::setXattr(const QString &dest) 0496 { 0497 QProcess xattrWriter; 0498 xattrWriter.setProcessChannelMode(QProcess::MergedChannels); 0499 0500 QHash<QString, QString> attrs = getSampleXattrs(); 0501 QHashIterator<QString, QString> i(attrs); 0502 while (i.hasNext()) { 0503 i.next(); 0504 QStringList arguments = m_setXattrFormatArgs(i.key(), i.value(), dest); 0505 xattrWriter.start(m_setXattrCmd, arguments); 0506 xattrWriter.waitForStarted(); 0507 xattrWriter.waitForFinished(-1); 0508 if (xattrWriter.exitStatus() != QProcess::NormalExit) { 0509 return false; 0510 } 0511 QList<QByteArray> resultdest = xattrWriter.readAllStandardOutput().split('\n'); 0512 if (!resultdest[0].isEmpty()) { 0513 QWARN("Error writing user xattr. Xattr copy tests will be disabled."); 0514 qDebug() << resultdest; 0515 return false; 0516 } 0517 } 0518 0519 return true; 0520 } 0521 0522 QList<QByteArray> JobTest::readXattr(const QString &src) 0523 { 0524 QProcess xattrReader; 0525 xattrReader.setProcessChannelMode(QProcess::MergedChannels); 0526 0527 QStringList arguments; 0528 char outputSeparator = '\n'; 0529 // Linux 0530 if (m_getXattrCmd.endsWith("getfattr")) { 0531 arguments = QStringList{"-d", src}; 0532 } 0533 // BSD 0534 else if (m_getXattrCmd.endsWith("lsextattr")) { 0535 arguments = QStringList{"-q", "user", src}; 0536 outputSeparator = '\t'; 0537 } 0538 // MacOS 0539 else { 0540 arguments = QStringList{"-l", src}; 0541 } 0542 0543 xattrReader.start(m_getXattrCmd, arguments); 0544 xattrReader.waitForFinished(); 0545 QList<QByteArray> result = xattrReader.readAllStandardOutput().split(outputSeparator); 0546 if (m_getXattrCmd.endsWith("getfattr")) { 0547 if (result.size() > 1) { 0548 // Line 1 is the file name 0549 result.removeAt(1); 0550 } 0551 } else if (m_getXattrCmd.endsWith("lsextattr")) { 0552 // cut off trailing \n 0553 result.last().chop(1); 0554 // lsextattr does not sort its output 0555 std::sort(result.begin(), result.end()); 0556 } 0557 0558 return result; 0559 } 0560 0561 void JobTest::compareXattr(const QString &src, const QString &dest) 0562 { 0563 auto srcAttrs = readXattr(src); 0564 auto dstAttrs = readXattr(dest); 0565 QCOMPARE(dstAttrs, srcAttrs); 0566 } 0567 0568 void JobTest::copyLocalFile(const QString &src, const QString &dest) 0569 { 0570 const QUrl u = QUrl::fromLocalFile(src); 0571 const QUrl d = QUrl::fromLocalFile(dest); 0572 0573 const int perms = 0666; 0574 // copy the file with file_copy 0575 KIO::Job *job = KIO::file_copy(u, d, perms, KIO::HideProgressInfo); 0576 job->setUiDelegate(nullptr); 0577 QVERIFY2(job->exec(), qPrintable(job->errorString())); 0578 QVERIFY(QFile::exists(dest)); 0579 QVERIFY(QFile::exists(src)); // still there 0580 QCOMPARE(int(QFileInfo(dest).permissions()), int(0x6666)); 0581 compareXattr(src, dest); 0582 0583 { 0584 // check that the timestamp is the same (#24443) 0585 // Note: this only works because of copy() in kio_file. 0586 // The datapump solution ignores mtime, the app has to call FileCopyJob::setModificationTime() 0587 QFileInfo srcInfo(src); 0588 QFileInfo destInfo(dest); 0589 #ifdef Q_OS_WIN 0590 // win32 time may differs in msec part 0591 QCOMPARE(srcInfo.lastModified().toString("dd.MM.yyyy hh:mm"), destInfo.lastModified().toString("dd.MM.yyyy hh:mm")); 0592 #else 0593 QCOMPARE(srcInfo.lastModified(), destInfo.lastModified()); 0594 #endif 0595 } 0596 0597 // cleanup and retry with KIO::copy() 0598 QFile::remove(dest); 0599 auto *copyjob = KIO::copy(u, d, KIO::HideProgressInfo); 0600 QSignalSpy spyCopyingDone(copyjob, &KIO::CopyJob::copyingDone); 0601 copyjob->setUiDelegate(nullptr); 0602 copyjob->setUiDelegateExtension(nullptr); 0603 QVERIFY2(copyjob->exec(), qPrintable(copyjob->errorString())); 0604 QVERIFY(QFile::exists(dest)); 0605 QVERIFY(QFile::exists(src)); // still there 0606 compareXattr(src, dest); 0607 { 0608 // check that the timestamp is the same (#24443) 0609 QFileInfo srcInfo(src); 0610 QFileInfo destInfo(dest); 0611 #ifdef Q_OS_WIN 0612 // win32 time may differs in msec part 0613 QCOMPARE(srcInfo.lastModified().toString("dd.MM.yyyy hh:mm"), destInfo.lastModified().toString("dd.MM.yyyy hh:mm")); 0614 #else 0615 QCOMPARE(srcInfo.lastModified(), destInfo.lastModified()); 0616 #endif 0617 } 0618 QCOMPARE(spyCopyingDone.count(), 1); 0619 0620 QCOMPARE(copyjob->totalAmount(KJob::Files), 1); 0621 QCOMPARE(copyjob->totalAmount(KJob::Directories), 0); 0622 QCOMPARE(copyjob->processedAmount(KJob::Files), 1); 0623 QCOMPARE(copyjob->processedAmount(KJob::Directories), 0); 0624 QCOMPARE(copyjob->percent(), 100); 0625 0626 // cleanup and retry with KIO::copyAs() 0627 QFile::remove(dest); 0628 job = KIO::copyAs(u, d, KIO::HideProgressInfo); 0629 job->setUiDelegate(nullptr); 0630 job->setUiDelegateExtension(nullptr); 0631 QVERIFY2(job->exec(), qPrintable(job->errorString())); 0632 QVERIFY(QFile::exists(dest)); 0633 QVERIFY(QFile::exists(src)); // still there 0634 compareXattr(src, dest); 0635 0636 // Do it again, with Overwrite. 0637 job = KIO::copyAs(u, d, KIO::Overwrite | KIO::HideProgressInfo); 0638 job->setUiDelegate(nullptr); 0639 job->setUiDelegateExtension(nullptr); 0640 QVERIFY2(job->exec(), qPrintable(job->errorString())); 0641 QVERIFY(QFile::exists(dest)); 0642 QVERIFY(QFile::exists(src)); // still there 0643 compareXattr(src, dest); 0644 0645 // Do it again, without Overwrite (should fail). 0646 job = KIO::copyAs(u, d, KIO::HideProgressInfo); 0647 job->setUiDelegate(nullptr); 0648 job->setUiDelegateExtension(nullptr); 0649 QVERIFY(!job->exec()); 0650 0651 // Clean up 0652 QFile::remove(src); 0653 QFile::remove(dest); 0654 } 0655 0656 void JobTest::copyLocalDirectory(const QString &src, const QString &_dest, int flags) 0657 { 0658 QVERIFY(QFileInfo(src).isDir()); 0659 QVERIFY(QFileInfo(src + "/testfile").isFile()); 0660 QUrl u = QUrl::fromLocalFile(src); 0661 QString dest(_dest); 0662 QUrl d = QUrl::fromLocalFile(dest); 0663 if (flags & AlreadyExists) { 0664 QVERIFY(QFile::exists(dest)); 0665 } else { 0666 QVERIFY(!QFile::exists(dest)); 0667 } 0668 0669 KIO::Job *job = KIO::copy(u, d, KIO::HideProgressInfo); 0670 job->setUiDelegate(nullptr); 0671 job->setUiDelegateExtension(nullptr); 0672 QVERIFY2(job->exec(), qPrintable(job->errorString())); 0673 QVERIFY(QFile::exists(dest)); 0674 QVERIFY(QFileInfo(dest).isDir()); 0675 QVERIFY(QFileInfo(dest + "/testfile").isFile()); 0676 QVERIFY(QFile::exists(src)); // still there 0677 0678 if (flags & AlreadyExists) { 0679 dest += '/' + u.fileName(); 0680 // qDebug() << "Expecting dest=" << dest; 0681 } 0682 0683 // CopyJob::setNextDirAttribute isn't implemented for Windows currently. 0684 #ifndef Q_OS_WIN 0685 { 0686 // Check that the timestamp is the same (#24443) 0687 QFileInfo srcInfo(src); 0688 QFileInfo destInfo(dest); 0689 QCOMPARE(srcInfo.lastModified(), destInfo.lastModified()); 0690 } 0691 #endif 0692 0693 QCOMPARE(job->totalAmount(KJob::Files), 2); // testfile and testlink 0694 QCOMPARE(job->totalAmount(KJob::Directories), 1); 0695 QCOMPARE(job->processedAmount(KJob::Files), 2); 0696 QCOMPARE(job->processedAmount(KJob::Directories), 1); 0697 QCOMPARE(job->percent(), 100); 0698 0699 // Do it again, with Overwrite. 0700 // Use copyAs, we don't want a subdir inside d. 0701 job = KIO::copyAs(u, d, KIO::HideProgressInfo | KIO::Overwrite); 0702 job->setUiDelegate(nullptr); 0703 job->setUiDelegateExtension(nullptr); 0704 QVERIFY2(job->exec(), qPrintable(job->errorString())); 0705 0706 QCOMPARE(job->totalAmount(KJob::Files), 2); // testfile and testlink 0707 QCOMPARE(job->totalAmount(KJob::Directories), 1); 0708 QCOMPARE(job->processedAmount(KJob::Files), 2); 0709 QCOMPARE(job->processedAmount(KJob::Directories), 1); 0710 QCOMPARE(job->percent(), 100); 0711 0712 // Do it again, without Overwrite (should fail). 0713 job = KIO::copyAs(u, d, KIO::HideProgressInfo); 0714 job->setUiDelegate(nullptr); 0715 job->setUiDelegateExtension(nullptr); 0716 QVERIFY(!job->exec()); 0717 } 0718 0719 #ifndef Q_OS_WIN 0720 static QString linkTarget(const QString &path) 0721 { 0722 // Use readlink on Unix because symLinkTarget turns relative targets into absolute (#352927) 0723 char linkTargetBuffer[4096]; 0724 const int n = readlink(QFile::encodeName(path).constData(), linkTargetBuffer, sizeof(linkTargetBuffer) - 1); 0725 if (n != -1) { 0726 linkTargetBuffer[n] = 0; 0727 } 0728 return QFile::decodeName(linkTargetBuffer); 0729 } 0730 0731 static void copyLocalSymlink(const QString &src, const QString &dest, const QString &expectedLinkTarget) 0732 { 0733 QT_STATBUF buf; 0734 QVERIFY(QT_LSTAT(QFile::encodeName(src).constData(), &buf) == 0); 0735 QUrl u = QUrl::fromLocalFile(src); 0736 QUrl d = QUrl::fromLocalFile(dest); 0737 0738 // copy the symlink 0739 KIO::Job *job = KIO::copy(u, d, KIO::HideProgressInfo); 0740 job->setUiDelegate(nullptr); 0741 job->setUiDelegateExtension(nullptr); 0742 QVERIFY2(job->exec(), qPrintable(QString::number(job->error()))); 0743 QVERIFY(QT_LSTAT(QFile::encodeName(dest).constData(), &buf) == 0); // dest exists 0744 QCOMPARE(linkTarget(dest), expectedLinkTarget); 0745 0746 // cleanup 0747 QFile::remove(dest); 0748 } 0749 #endif 0750 0751 void JobTest::copyFileToSamePartition() 0752 { 0753 const QString homeDir = homeTmpDir(); 0754 const QString filePath = homeDir + "fileFromHome"; 0755 const QString dest = homeDir + "fileFromHome_copied"; 0756 createTestFile(filePath); 0757 if (checkXattrFsSupport(homeDir)) { 0758 setXattr(filePath); 0759 } 0760 copyLocalFile(filePath, dest); 0761 } 0762 0763 void JobTest::copyDirectoryToSamePartition() 0764 { 0765 // qDebug(); 0766 const QString src = homeTmpDir() + "dirFromHome"; 0767 const QString dest = homeTmpDir() + "dirFromHome_copied"; 0768 createTestDirectory(src); 0769 copyLocalDirectory(src, dest); 0770 } 0771 0772 void JobTest::copyDirectoryToExistingDirectory() 0773 { 0774 // qDebug(); 0775 // just the same as copyDirectoryToSamePartition, but this time dest exists. 0776 // So we get a subdir, "dirFromHome_copy/dirFromHome" 0777 const QString src = homeTmpDir() + "dirFromHome"; 0778 const QString dest = homeTmpDir() + "dirFromHome_copied"; 0779 createTestDirectory(src); 0780 createTestDirectory(dest); 0781 copyLocalDirectory(src, dest, AlreadyExists); 0782 } 0783 0784 void JobTest::copyDirectoryToExistingSymlinkedDirectory() 0785 { 0786 // qDebug(); 0787 // just the same as copyDirectoryToSamePartition, but this time dest is a symlink. 0788 // So we get a file in the symlink dir, "dirFromHome_symlink/dirFromHome" and 0789 // "dirFromHome_symOrigin/dirFromHome" 0790 const QString src = homeTmpDir() + "dirFromHome"; 0791 const QString origSymlink = homeTmpDir() + "dirFromHome_symOrigin"; 0792 const QString targetSymlink = homeTmpDir() + "dirFromHome_symlink"; 0793 createTestDirectory(src); 0794 createTestDirectory(origSymlink); 0795 0796 bool ok = KIOPrivate::createSymlink(origSymlink, targetSymlink); 0797 if (!ok) { 0798 qFatal("couldn't create symlink: %s", strerror(errno)); 0799 } 0800 QVERIFY(QFileInfo(targetSymlink).isSymLink()); 0801 QVERIFY(QFileInfo(targetSymlink).isDir()); 0802 0803 KIO::Job *job = KIO::copy(QUrl::fromLocalFile(src), QUrl::fromLocalFile(targetSymlink), KIO::HideProgressInfo); 0804 job->setUiDelegate(nullptr); 0805 job->setUiDelegateExtension(nullptr); 0806 QVERIFY2(job->exec(), qPrintable(job->errorString())); 0807 QVERIFY(QFile::exists(src)); // still there 0808 0809 // file is visible in both places due to symlink 0810 QVERIFY(QFileInfo(origSymlink + "/dirFromHome").isDir()); 0811 ; 0812 QVERIFY(QFileInfo(targetSymlink + "/dirFromHome").isDir()); 0813 QVERIFY(QDir(origSymlink).removeRecursively()); 0814 QVERIFY(QFile::remove(targetSymlink)); 0815 } 0816 0817 void JobTest::copyFileToOtherPartition() 0818 { 0819 // qDebug(); 0820 const QString homeDir = homeTmpDir(); 0821 const QString otherHomeDir = otherTmpDir(); 0822 const QString filePath = homeDir + "fileFromHome"; 0823 const QString dest = otherHomeDir + "fileFromHome_copied"; 0824 bool canRead = checkXattrFsSupport(homeDir); 0825 bool canWrite = checkXattrFsSupport(otherHomeDir); 0826 createTestFile(filePath); 0827 if (canRead && canWrite) { 0828 setXattr(filePath); 0829 } 0830 copyLocalFile(filePath, dest); 0831 } 0832 0833 // Same partition doesn't matter as much as copying to the same filesystem type 0834 // to be sure it supports the same permissions 0835 void JobTest::testCopyFilePermissionsToSamePartition() 0836 { 0837 #if defined(Q_OS_UNIX) 0838 const QString homeDir = homeTmpDir(); 0839 const QString src = homeDir + "fileFromHome"; 0840 const QUrl u = QUrl::fromLocalFile(src); 0841 createTestFile(src); 0842 0843 const QByteArray src_c = QFile::encodeName(src).constData(); 0844 QT_STATBUF src_buff; 0845 QCOMPARE(QT_LSTAT(src_c.constData(), &src_buff), 0); // Exists 0846 // Default system permissions for newly created files, umask et al. 0847 const mode_t systemDefaultPerms = src_buff.st_mode; 0848 0849 const QString dest = homeDir + "fileFromHome_copied"; 0850 const QUrl d = QUrl::fromLocalFile(dest); 0851 0852 const QByteArray dest_c = QFile::encodeName(dest).constData(); 0853 QT_STATBUF dest_buff; 0854 0855 // Copy the file, with -1 permissions (i.e. don't touch dest permissions) 0856 auto copyStat = [&](const mode_t perms) { 0857 KIO::Job *job = KIO::file_copy(u, d, perms, KIO::HideProgressInfo); 0858 job->setUiDelegate(nullptr); 0859 QVERIFY2(job->exec(), qPrintable(job->errorString())); 0860 QCOMPARE(QT_LSTAT(dest_c.constData(), &dest_buff), 0); 0861 }; 0862 0863 copyStat(-1); 0864 // dest should have system default permissions 0865 QCOMPARE(dest_buff.st_mode, systemDefaultPerms); 0866 0867 QVERIFY(QFile::remove(dest)); 0868 // Change src permissions to non-default 0869 QCOMPARE(::chmod(src_c.constData(), S_IRUSR), 0); 0870 // Copy the file again, permissions -1 (i.e. don't touch dest permissions) 0871 copyStat(-1); 0872 // dest should have system default permissions, not src's ones 0873 QCOMPARE(dest_buff.st_mode, systemDefaultPerms); 0874 0875 QVERIFY(QFile::remove(dest)); 0876 // Copy the file again, explicitly setting the permissions to the src ones 0877 copyStat(src_buff.st_mode); 0878 // dest should have same permissions as src 0879 QCOMPARE(dest_buff.st_mode, dest_buff.st_mode); 0880 0881 QVERIFY(QFile::remove(dest)); 0882 // Copy the file again, explicitly setting some other permissions 0883 copyStat(S_IWUSR); 0884 // dest should have S_IWUSR 0885 QCOMPARE(dest_buff.st_mode & 0777, S_IWUSR); 0886 0887 // Clean up, the weird permissions used above mess with the next 0888 // unit tests 0889 QVERIFY(QFile::remove(dest)); 0890 QVERIFY(QFile::remove(src)); 0891 #endif 0892 } 0893 0894 void JobTest::copyDirectoryToOtherPartition() 0895 { 0896 // qDebug(); 0897 const QString src = homeTmpDir() + "dirFromHome"; 0898 const QString dest = otherTmpDir() + "dirFromHome_copied"; 0899 createTestDirectory(src); 0900 copyLocalDirectory(src, dest); 0901 } 0902 0903 void JobTest::copyRelativeSymlinkToSamePartition() // #352927 0904 { 0905 #ifdef Q_OS_WIN 0906 QSKIP("Skipping symlink test on Windows"); 0907 #else 0908 const QString filePath = homeTmpDir() + "testlink"; 0909 const QString dest = homeTmpDir() + "testlink_copied"; 0910 createTestSymlink(filePath, "relative"); 0911 copyLocalSymlink(filePath, dest, QStringLiteral("relative")); 0912 QFile::remove(filePath); 0913 #endif 0914 } 0915 0916 void JobTest::copyAbsoluteSymlinkToOtherPartition() 0917 { 0918 #ifdef Q_OS_WIN 0919 QSKIP("Skipping symlink test on Windows"); 0920 #else 0921 const QString filePath = homeTmpDir() + "testlink"; 0922 const QString dest = otherTmpDir() + "testlink_copied"; 0923 createTestSymlink(filePath, QFile::encodeName(homeTmpDir())); 0924 copyLocalSymlink(filePath, dest, homeTmpDir()); 0925 QFile::remove(filePath); 0926 #endif 0927 } 0928 0929 void JobTest::copyFolderWithUnaccessibleSubfolder() 0930 { 0931 #ifdef Q_OS_WIN 0932 QSKIP("Skipping unaccessible folder test on Windows, cannot remove all permissions from a folder"); 0933 #endif 0934 QTemporaryDir dir(homeTmpDir() + "UnaccessibleSubfolderTest"); 0935 QVERIFY(dir.isValid()); 0936 const QString src_dir = dir.path() + "srcHome"; 0937 const QString dst_dir = dir.path() + "dstHome"; 0938 0939 QDir().remove(src_dir); 0940 QDir().remove(dst_dir); 0941 0942 createTestDirectory(src_dir); 0943 createTestDirectory(src_dir + "/folder1"); 0944 QString inaccessible = src_dir + "/folder1/inaccessible"; 0945 0946 createTestDirectory(inaccessible); 0947 0948 QFile(inaccessible).setPermissions(QFile::Permissions()); // Make it inaccessible 0949 // Copying should throw some warnings, as it cannot access some folders 0950 0951 ScopedCleaner cleaner([&] { 0952 QFile(inaccessible).setPermissions(QFile::Permissions(QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner)); 0953 0954 qDebug() << "Cleaning up" << src_dir << "and" << dst_dir; 0955 KIO::DeleteJob *deljob1 = KIO::del(QUrl::fromLocalFile(src_dir), KIO::HideProgressInfo); 0956 deljob1->setUiDelegate(nullptr); // no skip dialog, thanks 0957 const bool job1OK = deljob1->exec(); 0958 QVERIFY(job1OK); 0959 0960 KIO::DeleteJob *deljob2 = KIO::del(QUrl::fromLocalFile(dst_dir), KIO::HideProgressInfo); 0961 deljob2->setUiDelegate(nullptr); // no skip dialog, thanks 0962 const bool job2OK = deljob2->exec(); 0963 QVERIFY(job2OK); 0964 0965 qDebug() << "Result:" << job1OK << job2OK; 0966 }); 0967 0968 KIO::CopyJob *job = KIO::copy(QUrl::fromLocalFile(src_dir), QUrl::fromLocalFile(dst_dir), KIO::HideProgressInfo); 0969 0970 QSignalSpy spy(job, &KJob::warning); 0971 job->setUiDelegate(nullptr); // no skip dialog, thanks 0972 QVERIFY2(job->exec(), qPrintable(job->errorString())); 0973 0974 QCOMPARE(job->totalAmount(KJob::Files), 4); // testfile, testlink, folder1/testlink, folder1/testfile 0975 QCOMPARE(job->totalAmount(KJob::Directories), 3); // srcHome, srcHome/folder1, srcHome/folder1/inaccessible 0976 QCOMPARE(job->processedAmount(KJob::Files), 4); 0977 QCOMPARE(job->processedAmount(KJob::Directories), 3); 0978 QCOMPARE(job->percent(), 100); 0979 0980 QCOMPARE(spy.count(), 1); // one warning should be emitted by the copy job 0981 } 0982 0983 void JobTest::copyDataUrl() 0984 { 0985 // GIVEN 0986 const QString dst_dir = homeTmpDir(); 0987 QVERIFY(!QFileInfo::exists(dst_dir + "/data")); 0988 // WHEN 0989 KIO::CopyJob *job = KIO::copy(QUrl("data:,Hello%2C%20World!"), QUrl::fromLocalFile(dst_dir), KIO::HideProgressInfo); 0990 QVERIFY2(job->exec(), qPrintable(job->errorString())); 0991 // THEN 0992 const QFileInfo fileInfo(dst_dir + "/data"); 0993 QVERIFY(fileInfo.isFile()); 0994 QCOMPARE(fileInfo.size(), 13); 0995 QFile::remove(dst_dir + "/data"); 0996 } 0997 0998 void JobTest::suspendFileCopy() 0999 { 1000 const QString filePath = homeTmpDir() + "fileFromHome"; 1001 const QString dest = homeTmpDir() + "fileFromHome_copied"; 1002 createTestFile(filePath); 1003 1004 const QUrl u = QUrl::fromLocalFile(filePath); 1005 const QUrl d = QUrl::fromLocalFile(dest); 1006 KIO::Job *job = KIO::file_copy(u, d, KIO::HideProgressInfo); 1007 QSignalSpy spyResult(job, &KJob::result); 1008 job->setUiDelegate(nullptr); 1009 job->setUiDelegateExtension(nullptr); 1010 QVERIFY(job->suspend()); 1011 QVERIFY(!spyResult.wait(300)); 1012 QVERIFY(job->resume()); 1013 QVERIFY2(job->exec(), qPrintable(job->errorString())); 1014 QVERIFY(QFile::exists(dest)); 1015 QFile::remove(dest); 1016 } 1017 1018 void JobTest::suspendCopy() 1019 { 1020 const QString filePath = homeTmpDir() + "fileFromHome"; 1021 const QString dest = homeTmpDir() + "fileFromHome_copied"; 1022 createTestFile(filePath); 1023 1024 const QUrl u = QUrl::fromLocalFile(filePath); 1025 const QUrl d = QUrl::fromLocalFile(dest); 1026 KIO::Job *job = KIO::copy(u, d, KIO::HideProgressInfo); 1027 QSignalSpy spyResult(job, &KJob::result); 1028 job->setUiDelegate(nullptr); 1029 job->setUiDelegateExtension(nullptr); 1030 QVERIFY(job->suspend()); 1031 QVERIFY(!spyResult.wait(300)); 1032 QVERIFY(job->resume()); 1033 QVERIFY2(job->exec(), qPrintable(job->errorString())); 1034 QVERIFY(QFile::exists(dest)); 1035 QFile::remove(dest); 1036 } 1037 1038 void JobTest::moveLocalFile(const QString &src, const QString &dest) 1039 { 1040 QVERIFY(QFile::exists(src)); 1041 QUrl u = QUrl::fromLocalFile(src); 1042 QUrl d = QUrl::fromLocalFile(dest); 1043 1044 // move the file with file_move 1045 KIO::Job *job = KIO::file_move(u, d, 0666, KIO::HideProgressInfo); 1046 job->setUiDelegate(nullptr); 1047 QVERIFY2(job->exec(), qPrintable(job->errorString())); 1048 QVERIFY(QFile::exists(dest)); 1049 QVERIFY(!QFile::exists(src)); // not there anymore 1050 QCOMPARE(int(QFileInfo(dest).permissions()), int(0x6666)); 1051 1052 // move it back with KIO::move() 1053 job = KIO::move(d, u, KIO::HideProgressInfo); 1054 job->setUiDelegate(nullptr); 1055 job->setUiDelegateExtension(nullptr); 1056 QVERIFY2(job->exec(), qPrintable(job->errorString())); 1057 QVERIFY(!QFile::exists(dest)); 1058 QVERIFY(QFile::exists(src)); // it's back 1059 } 1060 1061 static void moveLocalSymlink(const QString &src, const QString &dest) 1062 { 1063 QT_STATBUF buf; 1064 QVERIFY(QT_LSTAT(QFile::encodeName(src).constData(), &buf) == 0); 1065 QUrl u = QUrl::fromLocalFile(src); 1066 QUrl d = QUrl::fromLocalFile(dest); 1067 1068 // move the symlink with move, NOT with file_move 1069 KIO::Job *job = KIO::move(u, d, KIO::HideProgressInfo); 1070 job->setUiDelegate(nullptr); 1071 job->setUiDelegateExtension(nullptr); 1072 QVERIFY2(job->exec(), qPrintable(job->errorString())); 1073 QVERIFY(QT_LSTAT(QFile::encodeName(dest).constData(), &buf) == 0); 1074 QVERIFY(!QFile::exists(src)); // not there anymore 1075 1076 // move it back with KIO::move() 1077 job = KIO::move(d, u, KIO::HideProgressInfo); 1078 job->setUiDelegate(nullptr); 1079 job->setUiDelegateExtension(nullptr); 1080 QVERIFY2(job->exec(), qPrintable(job->errorString())); 1081 QVERIFY(QT_LSTAT(QFile::encodeName(dest).constData(), &buf) != 0); // doesn't exist anymore 1082 QVERIFY(QT_LSTAT(QFile::encodeName(src).constData(), &buf) == 0); // it's back 1083 } 1084 1085 void JobTest::moveLocalDirectory(const QString &src, const QString &dest) 1086 { 1087 qDebug() << src << " " << dest; 1088 QVERIFY(QFile::exists(src)); 1089 QVERIFY(QFileInfo(src).isDir()); 1090 QVERIFY(QFileInfo(src + "/testfile").isFile()); 1091 #ifndef Q_OS_WIN 1092 QVERIFY(QFileInfo(src + "/testlink").isSymLink()); 1093 #endif 1094 QUrl u = QUrl::fromLocalFile(src); 1095 QUrl d = QUrl::fromLocalFile(dest); 1096 1097 KIO::Job *job = KIO::move(u, d, KIO::HideProgressInfo); 1098 job->setUiDelegate(nullptr); 1099 job->setUiDelegateExtension(nullptr); 1100 QVERIFY2(job->exec(), qPrintable(job->errorString())); 1101 QVERIFY(QFile::exists(dest)); 1102 QVERIFY(QFileInfo(dest).isDir()); 1103 QVERIFY(QFileInfo(dest + "/testfile").isFile()); 1104 QVERIFY(!QFile::exists(src)); // not there anymore 1105 #ifndef Q_OS_WIN 1106 QVERIFY(QFileInfo(dest + "/testlink").isSymLink()); 1107 #endif 1108 } 1109 1110 void JobTest::moveFileToSamePartition() 1111 { 1112 qDebug(); 1113 const QString filePath = homeTmpDir() + "fileFromHome"; 1114 const QString dest = homeTmpDir() + "fileFromHome_moved"; 1115 createTestFile(filePath); 1116 moveLocalFile(filePath, dest); 1117 } 1118 1119 void JobTest::moveDirectoryToSamePartition() 1120 { 1121 qDebug(); 1122 const QString src = homeTmpDir() + "dirFromHome"; 1123 const QString dest = homeTmpDir() + "dirFromHome_moved"; 1124 createTestDirectory(src); 1125 moveLocalDirectory(src, dest); 1126 } 1127 1128 void JobTest::moveDirectoryIntoItself() 1129 { 1130 qDebug(); 1131 const QString src = homeTmpDir() + "dirFromHome"; 1132 const QString dest = src + "/foo"; 1133 createTestDirectory(src); 1134 QVERIFY(QFile::exists(src)); 1135 QUrl u = QUrl::fromLocalFile(src); 1136 QUrl d = QUrl::fromLocalFile(dest); 1137 KIO::CopyJob *job = KIO::move(u, d); 1138 QVERIFY(!job->exec()); 1139 QCOMPARE(job->error(), (int)KIO::ERR_CANNOT_MOVE_INTO_ITSELF); 1140 QCOMPARE(job->errorString(), i18n("A folder cannot be moved into itself")); 1141 QDir(dest).removeRecursively(); 1142 } 1143 1144 void JobTest::moveFileToOtherPartition() 1145 { 1146 qDebug(); 1147 const QString filePath = homeTmpDir() + "fileFromHome"; 1148 const QString dest = otherTmpDir() + "fileFromHome_moved"; 1149 createTestFile(filePath); 1150 moveLocalFile(filePath, dest); 1151 } 1152 1153 void JobTest::moveSymlinkToOtherPartition() 1154 { 1155 #ifndef Q_OS_WIN 1156 qDebug(); 1157 const QString filePath = homeTmpDir() + "testlink"; 1158 const QString dest = otherTmpDir() + "testlink_moved"; 1159 createTestSymlink(filePath); 1160 moveLocalSymlink(filePath, dest); 1161 #endif 1162 } 1163 1164 void JobTest::moveDirectoryToOtherPartition() 1165 { 1166 qDebug(); 1167 #ifndef Q_OS_WIN 1168 const QString src = homeTmpDir() + "dirFromHome"; 1169 const QString dest = otherTmpDir() + "dirFromHome_moved"; 1170 createTestDirectory(src); 1171 moveLocalDirectory(src, dest); 1172 #endif 1173 } 1174 1175 struct CleanupInaccessibleSubdir { 1176 explicit CleanupInaccessibleSubdir(const QString &subdir) 1177 : subdir(subdir) 1178 { 1179 } 1180 ~CleanupInaccessibleSubdir() 1181 { 1182 QVERIFY(QFile(subdir).setPermissions(QFile::Permissions(QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner))); 1183 QVERIFY(QDir(subdir).removeRecursively()); 1184 } 1185 1186 private: 1187 const QString subdir; 1188 }; 1189 1190 void JobTest::moveFileNoPermissions() 1191 { 1192 #ifdef Q_OS_WIN 1193 QSKIP("Skipping unaccessible folder test on Windows, cannot remove all permissions from a folder"); 1194 #endif 1195 // Given a file that cannot be moved (subdir has no permissions) 1196 const QString subdir = homeTmpDir() + "subdir"; 1197 QVERIFY(QDir().mkpath(subdir)); 1198 const QString src = subdir + "/thefile"; 1199 createTestFile(src); 1200 QVERIFY(QFile(subdir).setPermissions(QFile::Permissions())); // Make it inaccessible 1201 CleanupInaccessibleSubdir c(subdir); 1202 1203 // When trying to move it 1204 const QString dest = homeTmpDir() + "dest"; 1205 KIO::CopyJob *job = KIO::move(QUrl::fromLocalFile(src), QUrl::fromLocalFile(dest), KIO::HideProgressInfo); 1206 job->setUiDelegate(nullptr); 1207 job->setUiDelegateExtension(nullptr); // no skip dialog, thanks 1208 1209 // The job should fail with "access denied" 1210 QVERIFY(!job->exec()); 1211 QCOMPARE(job->error(), (int)KIO::ERR_ACCESS_DENIED); 1212 // Note that, just like mv(1), KIO's behavior depends on whether 1213 // a direct rename(2) was used, or a full copy+del. In the first case 1214 // there is no destination file created, but in the second case the 1215 // destination file remains. 1216 // In this test it's the same partition, so no dest created. 1217 QVERIFY(!QFile::exists(dest)); 1218 1219 QCOMPARE(job->totalAmount(KJob::Files), 1); 1220 QCOMPARE(job->totalAmount(KJob::Directories), 0); 1221 QCOMPARE(job->processedAmount(KJob::Files), 0); 1222 QCOMPARE(job->processedAmount(KJob::Directories), 0); 1223 QCOMPARE(job->percent(), 0); 1224 } 1225 1226 void JobTest::moveDirectoryNoPermissions() 1227 { 1228 #ifdef Q_OS_WIN 1229 QSKIP("Skipping unaccessible folder test on Windows, cannot remove all permissions from a folder"); 1230 #endif 1231 // Given a dir that cannot be moved (parent dir has no permissions) 1232 const QString subdir = homeTmpDir() + "subdir"; 1233 const QString src = subdir + "/thedir"; 1234 QVERIFY(QDir().mkpath(src)); 1235 QVERIFY(QFileInfo(src).isDir()); 1236 QVERIFY(QFile(subdir).setPermissions(QFile::Permissions())); // Make it inaccessible 1237 CleanupInaccessibleSubdir c(subdir); 1238 1239 // When trying to move it 1240 const QString dest = homeTmpDir() + "mdnp"; 1241 KIO::CopyJob *job = KIO::move(QUrl::fromLocalFile(src), QUrl::fromLocalFile(dest), KIO::HideProgressInfo); 1242 job->setUiDelegate(nullptr); 1243 job->setUiDelegateExtension(nullptr); // no skip dialog, thanks 1244 1245 // The job should fail with "access denied" 1246 QVERIFY(!job->exec()); 1247 QCOMPARE(job->error(), (int)KIO::ERR_ACCESS_DENIED); 1248 1249 QVERIFY(!QFile::exists(dest)); 1250 1251 QCOMPARE(job->totalAmount(KJob::Files), 1); 1252 QCOMPARE(job->totalAmount(KJob::Directories), 0); 1253 QCOMPARE(job->processedAmount(KJob::Files), 0); 1254 QCOMPARE(job->processedAmount(KJob::Directories), 0); 1255 QCOMPARE(job->percent(), 0); 1256 } 1257 1258 void JobTest::moveDirectoryToReadonlyFilesystem_data() 1259 { 1260 QTest::addColumn<QList<QUrl>>("sources"); 1261 QTest::addColumn<int>("expectedErrorCode"); 1262 1263 const QString srcFileHomePath = homeTmpDir() + "srcFileHome"; 1264 const QUrl srcFileHome = QUrl::fromLocalFile(srcFileHomePath); 1265 createTestFile(srcFileHomePath); 1266 1267 const QString srcFileOtherPath = otherTmpDir() + "srcFileOther"; 1268 const QUrl srcFileOther = QUrl::fromLocalFile(srcFileOtherPath); 1269 createTestFile(srcFileOtherPath); 1270 1271 const QString srcDirHomePath = homeTmpDir() + "srcDirHome"; 1272 const QUrl srcDirHome = QUrl::fromLocalFile(srcDirHomePath); 1273 createTestDirectory(srcDirHomePath); 1274 1275 const QString srcDirHome2Path = homeTmpDir() + "srcDirHome2"; 1276 const QUrl srcDirHome2 = QUrl::fromLocalFile(srcDirHome2Path); 1277 createTestDirectory(srcDirHome2Path); 1278 1279 const QString srcDirOtherPath = otherTmpDir() + "srcDirOther"; 1280 const QUrl srcDirOther = QUrl::fromLocalFile(srcDirOtherPath); 1281 createTestDirectory(srcDirOtherPath); 1282 1283 QTest::newRow("file_same_partition") << QList<QUrl>{srcFileHome} << int(KIO::ERR_WRITE_ACCESS_DENIED); 1284 QTest::newRow("file_other_partition") << QList<QUrl>{srcFileOther} << int(KIO::ERR_WRITE_ACCESS_DENIED); 1285 QTest::newRow("one_dir_same_partition") << QList<QUrl>{srcDirHome} << int(KIO::ERR_WRITE_ACCESS_DENIED); 1286 QTest::newRow("one_dir_other_partition") << QList<QUrl>{srcDirOther} << int(KIO::ERR_WRITE_ACCESS_DENIED); 1287 QTest::newRow("dirs_same_partition") << QList<QUrl>{srcDirHome, srcDirHome2} << int(KIO::ERR_WRITE_ACCESS_DENIED); 1288 QTest::newRow("dirs_both_partitions") << QList<QUrl>{srcDirOther, srcDirHome} << int(KIO::ERR_WRITE_ACCESS_DENIED); 1289 } 1290 1291 void JobTest::moveDirectoryToReadonlyFilesystem() 1292 { 1293 QFETCH(QList<QUrl>, sources); 1294 QFETCH(int, expectedErrorCode); 1295 1296 const QString dst_dir = homeTmpDir() + "readonlyDest"; 1297 const QUrl dst = QUrl::fromLocalFile(dst_dir); 1298 QVERIFY2(QDir().mkdir(dst_dir), qPrintable(dst_dir)); 1299 QFile(dst_dir).setPermissions(QFile::Permissions(QFile::ReadOwner | QFile::ExeOwner)); // Make it readonly, moving should throw some errors 1300 1301 ScopedCleaner cleaner([&] { 1302 QVERIFY(QFile(dst_dir).setPermissions(QFile::Permissions(QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner))); 1303 QVERIFY(QDir(dst_dir).removeRecursively()); 1304 }); 1305 1306 KIO::CopyJob *job = KIO::move(sources, dst, KIO::HideProgressInfo | KIO::NoPrivilegeExecution); 1307 job->setUiDelegate(nullptr); 1308 QVERIFY(!job->exec()); 1309 QCOMPARE(job->error(), expectedErrorCode); 1310 for (const QUrl &srcUrl : std::as_const(sources)) { 1311 QVERIFY(QFileInfo::exists(srcUrl.toLocalFile())); // no moving happened 1312 } 1313 1314 KIO::CopyJob *job2 = KIO::move(sources, dst, KIO::HideProgressInfo); 1315 job2->setUiDelegate(nullptr); 1316 QVERIFY(!job2->exec()); 1317 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 1318 QCOMPARE(job2->error(), expectedErrorCode); 1319 } 1320 for (const QUrl &srcUrl : std::as_const(sources)) { 1321 QVERIFY(QFileInfo::exists(srcUrl.toLocalFile())); // no moving happened 1322 } 1323 } 1324 1325 static QByteArray expectedListRecursiveOutput() 1326 { 1327 return QByteArray( 1328 ".,..," 1329 "dirFromHome,dirFromHome/testfile," 1330 "dirFromHome/testlink," // exists on Windows too, see createTestDirectory 1331 "dirFromHome_copied," 1332 "dirFromHome_copied/dirFromHome,dirFromHome_copied/dirFromHome/testfile," 1333 "dirFromHome_copied/dirFromHome/testlink," 1334 "dirFromHome_copied/testfile," 1335 "dirFromHome_copied/testlink," 1336 #ifndef Q_OS_WIN 1337 "dirFromHome_link," 1338 #endif 1339 "fileFromHome"); 1340 } 1341 1342 void JobTest::listRecursive() 1343 { 1344 // Note: many other tests must have been run before since we rely on the files they created 1345 1346 const QString src = homeTmpDir(); 1347 #ifndef Q_OS_WIN 1348 // Add a symlink to a dir, to make sure we don't recurse into those 1349 bool symlinkOk = symlink("dirFromHome", QFile::encodeName(src + "/dirFromHome_link").constData()) == 0; 1350 QVERIFY(symlinkOk); 1351 #endif 1352 m_names.clear(); 1353 KIO::ListJob *job = KIO::listRecursive(QUrl::fromLocalFile(src), KIO::HideProgressInfo); 1354 job->setUiDelegate(nullptr); 1355 connect(job, &KIO::ListJob::entries, this, &JobTest::slotEntries); 1356 QVERIFY2(job->exec(), qPrintable(job->errorString())); 1357 m_names.sort(); 1358 const QByteArray ref_names = expectedListRecursiveOutput(); 1359 const QString joinedNames = m_names.join(QLatin1Char(',')); 1360 if (joinedNames.toLatin1() != ref_names) { 1361 qDebug("%s", qPrintable(joinedNames)); 1362 qDebug("%s", ref_names.data()); 1363 } 1364 QCOMPARE(joinedNames.toLatin1(), ref_names); 1365 } 1366 1367 void JobTest::multipleListRecursive() 1368 { 1369 // Note: listRecursive() must have been run first 1370 const QString src = homeTmpDir(); 1371 m_names.clear(); 1372 QVector<KIO::ListJob *> jobs; 1373 for (int i = 0; i < 100; ++i) { 1374 KIO::ListJob *job = KIO::listRecursive(QUrl::fromLocalFile(src), KIO::HideProgressInfo); 1375 job->setUiDelegate(nullptr); 1376 if (i == 6) { 1377 connect(job, &KIO::ListJob::entries, this, &JobTest::slotEntries); 1378 } 1379 connect(job, &KJob::result, this, [&jobs, job]() { 1380 jobs.removeOne(job); 1381 }); 1382 jobs.push_back(job); 1383 } 1384 QTRY_VERIFY(jobs.isEmpty()); 1385 1386 m_names.sort(); 1387 const QByteArray ref_names = expectedListRecursiveOutput(); 1388 const QString joinedNames = m_names.join(QLatin1Char(',')); 1389 if (joinedNames.toLatin1() != ref_names) { 1390 qDebug("%s", qPrintable(joinedNames)); 1391 qDebug("%s", ref_names.data()); 1392 } 1393 QCOMPARE(joinedNames.toLatin1(), ref_names); 1394 } 1395 1396 void JobTest::listFile() 1397 { 1398 const QString filePath = homeTmpDir() + "fileFromHome"; 1399 createTestFile(filePath); 1400 KIO::ListJob *job = KIO::listDir(QUrl::fromLocalFile(filePath), KIO::HideProgressInfo); 1401 job->setUiDelegate(nullptr); 1402 QVERIFY(!job->exec()); 1403 QCOMPARE(job->error(), static_cast<int>(KIO::ERR_IS_FILE)); 1404 1405 // And list something that doesn't exist 1406 const QString path = homeTmpDir() + "fileFromHomeDoesNotExist"; 1407 job = KIO::listDir(QUrl::fromLocalFile(path), KIO::HideProgressInfo); 1408 job->setUiDelegate(nullptr); 1409 QVERIFY(!job->exec()); 1410 QCOMPARE(job->error(), static_cast<int>(KIO::ERR_DOES_NOT_EXIST)); 1411 } 1412 1413 void JobTest::killJob() 1414 { 1415 const QString src = homeTmpDir(); 1416 KIO::ListJob *job = KIO::listDir(QUrl::fromLocalFile(src), KIO::HideProgressInfo); 1417 QVERIFY(job->isAutoDelete()); 1418 QPointer<KIO::ListJob> ptr(job); 1419 job->setUiDelegate(nullptr); 1420 qApp->processEvents(); // let the job start, it's no fun otherwise 1421 job->kill(); 1422 qApp->sendPostedEvents(nullptr, QEvent::DeferredDelete); // process the deferred delete of the job 1423 QVERIFY(ptr.isNull()); 1424 } 1425 1426 void JobTest::killJobBeforeStart() 1427 { 1428 const QString src = homeTmpDir(); 1429 KIO::Job *job = KIO::stat(QUrl::fromLocalFile(src), KIO::HideProgressInfo); 1430 QVERIFY(job->isAutoDelete()); 1431 QPointer<KIO::Job> ptr(job); 1432 job->setUiDelegate(nullptr); 1433 job->kill(); 1434 qApp->sendPostedEvents(nullptr, QEvent::DeferredDelete); // process the deferred delete of the job 1435 QVERIFY(ptr.isNull()); 1436 qApp->processEvents(); // does KIO scheduler crash here? nope. 1437 } 1438 1439 void JobTest::deleteJobBeforeStart() // #163171 1440 { 1441 const QString src = homeTmpDir(); 1442 KIO::Job *job = KIO::stat(QUrl::fromLocalFile(src), KIO::HideProgressInfo); 1443 QVERIFY(job->isAutoDelete()); 1444 job->setUiDelegate(nullptr); 1445 delete job; 1446 qApp->processEvents(); // does KIO scheduler crash here? 1447 } 1448 1449 void JobTest::directorySize() 1450 { 1451 // Note: many other tests must have been run before since we rely on the files they created 1452 1453 const QString src = homeTmpDir(); 1454 1455 KIO::DirectorySizeJob *job = KIO::directorySize(QUrl::fromLocalFile(src)); 1456 job->setUiDelegate(nullptr); 1457 QVERIFY2(job->exec(), qPrintable(job->errorString())); 1458 qDebug() << "totalSize: " << job->totalSize(); 1459 qDebug() << "totalFiles: " << job->totalFiles(); 1460 qDebug() << "totalSubdirs: " << job->totalSubdirs(); 1461 #ifdef Q_OS_WIN 1462 QCOMPARE(job->totalFiles(), 5ULL); // see expected result in listRecursive() above 1463 QCOMPARE(job->totalSubdirs(), 3ULL); // see expected result in listRecursive() above 1464 QVERIFY(job->totalSize() > 54); 1465 #else 1466 QCOMPARE(job->totalFiles(), 7ULL); // see expected result in listRecursive() above 1467 QCOMPARE(job->totalSubdirs(), 4ULL); // see expected result in listRecursive() above 1468 QVERIFY2(job->totalSize() >= 60, 1469 qPrintable(QString("totalSize was %1").arg(job->totalSize()))); // size of subdir entries is filesystem dependent. E.g. this is 16428 with ext4 but 1470 // only 272 with xfs, and 63 on FreeBSD 1471 #endif 1472 1473 qApp->sendPostedEvents(nullptr, QEvent::DeferredDelete); 1474 } 1475 1476 void JobTest::directorySizeError() 1477 { 1478 KIO::DirectorySizeJob *job = KIO::directorySize(QUrl::fromLocalFile(QStringLiteral("/I/Dont/Exist"))); 1479 job->setUiDelegate(nullptr); 1480 QVERIFY(!job->exec()); 1481 qApp->sendPostedEvents(nullptr, QEvent::DeferredDelete); 1482 } 1483 1484 void JobTest::slotEntries(KIO::Job *, const KIO::UDSEntryList &lst) 1485 { 1486 for (KIO::UDSEntryList::ConstIterator it = lst.begin(); it != lst.end(); ++it) { 1487 QString displayName = (*it).stringValue(KIO::UDSEntry::UDS_NAME); 1488 // QUrl url = (*it).stringValue( KIO::UDSEntry::UDS_URL ); 1489 m_names.append(displayName); 1490 } 1491 } 1492 1493 void JobTest::calculateRemainingSeconds() 1494 { 1495 unsigned int seconds = KIO::calculateRemainingSeconds(2 * 86400 - 60, 0, 1); 1496 QCOMPARE(seconds, static_cast<unsigned int>(2 * 86400 - 60)); 1497 QString text = KIO::convertSeconds(seconds); 1498 QCOMPARE(text, i18n("1 day 23:59:00")); 1499 1500 seconds = KIO::calculateRemainingSeconds(520, 20, 10); 1501 QCOMPARE(seconds, static_cast<unsigned int>(50)); 1502 text = KIO::convertSeconds(seconds); 1503 QCOMPARE(text, i18n("00:00:50")); 1504 } 1505 1506 void JobTest::getInvalidUrl() 1507 { 1508 QUrl url(QStringLiteral("http://strange<hostname>/")); 1509 QVERIFY(!url.isValid()); 1510 1511 KIO::SimpleJob *job = KIO::get(url, KIO::NoReload, KIO::HideProgressInfo); 1512 QVERIFY(job != nullptr); 1513 job->setUiDelegate(nullptr); 1514 1515 QVERIFY(!job->exec()); // it should fail :) 1516 } 1517 1518 void JobTest::slotMimetype(KIO::Job *job, const QString &type) 1519 { 1520 QVERIFY(job != nullptr); 1521 m_mimetype = type; 1522 } 1523 1524 void JobTest::deleteFile() 1525 { 1526 const QString dest = otherTmpDir() + "fileFromHome_copied"; 1527 createTestFile(dest); 1528 KIO::Job *job = KIO::del(QUrl::fromLocalFile(dest), KIO::HideProgressInfo); 1529 job->setUiDelegate(nullptr); 1530 QVERIFY2(job->exec(), qPrintable(job->errorString())); 1531 QVERIFY(!QFile::exists(dest)); 1532 } 1533 1534 void JobTest::deleteDirectory() 1535 { 1536 const QString dest = otherTmpDir() + "dirFromHome_copied"; 1537 if (!QFile::exists(dest)) { 1538 createTestDirectory(dest); 1539 } 1540 // Let's put a few things in there to see if the recursive deletion works correctly 1541 // A hidden file: 1542 createTestFile(dest + "/.hidden"); 1543 #ifndef Q_OS_WIN 1544 // A broken symlink: 1545 createTestSymlink(dest + "/broken_symlink"); 1546 // A symlink to a dir: 1547 const auto srcFileName = QFile::encodeName(QFileInfo(QFINDTESTDATA("jobtest.cpp")).absolutePath()).constData(); 1548 const auto symLinkFileName = QFile::encodeName(dest + "/symlink_to_dir").constData(); 1549 if (symlink(srcFileName, symLinkFileName) != 0) { 1550 qFatal("couldn't create symlink: %s", strerror(errno)); 1551 } 1552 #endif 1553 1554 KIO::Job *job = KIO::del(QUrl::fromLocalFile(dest), KIO::HideProgressInfo); 1555 job->setUiDelegate(nullptr); 1556 QVERIFY2(job->exec(), qPrintable(job->errorString())); 1557 QVERIFY(!QFile::exists(dest)); 1558 } 1559 1560 void JobTest::deleteSymlink(bool using_fast_path) 1561 { 1562 extern KIOCORE_EXPORT bool kio_resolve_local_urls; 1563 kio_resolve_local_urls = !using_fast_path; 1564 1565 #ifndef Q_OS_WIN 1566 const QString src = homeTmpDir() + "dirFromHome"; 1567 createTestDirectory(src); 1568 QVERIFY(QFile::exists(src)); 1569 const QString dest = homeTmpDir() + "/dirFromHome_link"; 1570 if (!QFile::exists(dest)) { 1571 // Add a symlink to a dir, to make sure we don't recurse into those 1572 bool symlinkOk = symlink(QFile::encodeName(src).constData(), QFile::encodeName(dest).constData()) == 0; 1573 QVERIFY(symlinkOk); 1574 QVERIFY(QFile::exists(dest)); 1575 } 1576 KIO::Job *job = KIO::del(QUrl::fromLocalFile(dest), KIO::HideProgressInfo); 1577 job->setUiDelegate(nullptr); 1578 QVERIFY2(job->exec(), qPrintable(job->errorString())); 1579 QVERIFY(!QFile::exists(dest)); 1580 QVERIFY(QFile::exists(src)); 1581 #endif 1582 1583 kio_resolve_local_urls = true; 1584 } 1585 1586 void JobTest::deleteSymlink() 1587 { 1588 #ifndef Q_OS_WIN 1589 deleteSymlink(true); 1590 deleteSymlink(false); 1591 #endif 1592 } 1593 1594 void JobTest::deleteManyDirs(bool using_fast_path) 1595 { 1596 extern KIOCORE_EXPORT bool kio_resolve_local_urls; 1597 kio_resolve_local_urls = !using_fast_path; 1598 1599 const int numDirs = 50; 1600 QList<QUrl> dirs; 1601 for (int i = 0; i < numDirs; ++i) { 1602 const QString dir = homeTmpDir() + "dir" + QString::number(i); 1603 createTestDirectory(dir); 1604 dirs << QUrl::fromLocalFile(dir); 1605 } 1606 QElapsedTimer dt; 1607 dt.start(); 1608 KIO::Job *job = KIO::del(dirs, KIO::HideProgressInfo); 1609 job->setUiDelegate(nullptr); 1610 QVERIFY2(job->exec(), qPrintable(job->errorString())); 1611 for (const QUrl &dir : std::as_const(dirs)) { 1612 QVERIFY(!QFile::exists(dir.toLocalFile())); 1613 } 1614 1615 qDebug() << "Deleted" << numDirs << "dirs in" << dt.elapsed() << "milliseconds"; 1616 kio_resolve_local_urls = true; 1617 } 1618 1619 void JobTest::deleteManyDirs() 1620 { 1621 deleteManyDirs(true); 1622 deleteManyDirs(false); 1623 } 1624 1625 static QList<QUrl> createManyFiles(const QString &baseDir, int numFiles) 1626 { 1627 QList<QUrl> ret; 1628 ret.reserve(numFiles); 1629 for (int i = 0; i < numFiles; ++i) { 1630 // create empty file 1631 const QString file = baseDir + QString::number(i); 1632 QFile f(file); 1633 bool ok = f.open(QIODevice::WriteOnly); 1634 if (ok) { 1635 f.write("Hello"); 1636 ret.append(QUrl::fromLocalFile(file)); 1637 } 1638 } 1639 return ret; 1640 } 1641 1642 void JobTest::deleteManyFilesIndependently() 1643 { 1644 QElapsedTimer dt; 1645 dt.start(); 1646 const int numFiles = 100; // Use 1000 for performance testing 1647 const QString baseDir = homeTmpDir(); 1648 const QList<QUrl> urls = createManyFiles(baseDir, numFiles); 1649 QCOMPARE(urls.count(), numFiles); 1650 for (int i = 0; i < numFiles; ++i) { 1651 // delete each file independently. lots of jobs. this stress-tests kio scheduling. 1652 const QUrl url = urls.at(i); 1653 const QString file = url.toLocalFile(); 1654 QVERIFY(QFile::exists(file)); 1655 // qDebug() << file; 1656 KIO::Job *job = KIO::del(url, KIO::HideProgressInfo); 1657 job->setUiDelegate(nullptr); 1658 QVERIFY2(job->exec(), qPrintable(job->errorString())); 1659 QVERIFY(!QFile::exists(file)); 1660 } 1661 qDebug() << "Deleted" << numFiles << "files in" << dt.elapsed() << "milliseconds"; 1662 } 1663 1664 void JobTest::deleteManyFilesTogether(bool using_fast_path) 1665 { 1666 extern KIOCORE_EXPORT bool kio_resolve_local_urls; 1667 kio_resolve_local_urls = !using_fast_path; 1668 1669 QElapsedTimer dt; 1670 dt.start(); 1671 const int numFiles = 100; // Use 1000 for performance testing 1672 const QString baseDir = homeTmpDir(); 1673 const QList<QUrl> urls = createManyFiles(baseDir, numFiles); 1674 QCOMPARE(urls.count(), numFiles); 1675 1676 // qDebug() << file; 1677 KIO::Job *job = KIO::del(urls, KIO::HideProgressInfo); 1678 job->setUiDelegate(nullptr); 1679 QVERIFY2(job->exec(), qPrintable(job->errorString())); 1680 qDebug() << "Deleted" << numFiles << "files in" << dt.elapsed() << "milliseconds"; 1681 1682 kio_resolve_local_urls = true; 1683 } 1684 1685 void JobTest::deleteManyFilesTogether() 1686 { 1687 deleteManyFilesTogether(true); 1688 deleteManyFilesTogether(false); 1689 } 1690 1691 void JobTest::rmdirEmpty() 1692 { 1693 const QString dir = homeTmpDir() + "dir"; 1694 QDir().mkdir(dir); 1695 QVERIFY(QFile::exists(dir)); 1696 KIO::Job *job = KIO::rmdir(QUrl::fromLocalFile(dir)); 1697 QVERIFY2(job->exec(), qPrintable(job->errorString())); 1698 QVERIFY(!QFile::exists(dir)); 1699 } 1700 1701 void JobTest::rmdirNotEmpty() 1702 { 1703 const QString dir = homeTmpDir() + "dir"; 1704 createTestDirectory(dir); 1705 createTestDirectory(dir + "/subdir"); 1706 KIO::Job *job = KIO::rmdir(QUrl::fromLocalFile(dir)); 1707 QVERIFY(!job->exec()); 1708 QVERIFY(QFile::exists(dir)); 1709 } 1710 1711 void JobTest::stat() 1712 { 1713 #if 1 1714 const QString filePath = homeTmpDir() + "fileFromHome"; 1715 createTestFile(filePath); 1716 const QUrl url(QUrl::fromLocalFile(filePath)); 1717 KIO::StatJob *job = KIO::stat(url, KIO::HideProgressInfo); 1718 QVERIFY(job); 1719 QVERIFY2(job->exec(), qPrintable(job->errorString())); 1720 // TODO set setSide 1721 const KIO::UDSEntry &entry = job->statResult(); 1722 1723 // we only get filename, access, type, size, uid, gid, btime, mtime, atime 1724 QVERIFY(entry.contains(KIO::UDSEntry::UDS_NAME)); 1725 QVERIFY(entry.contains(KIO::UDSEntry::UDS_ACCESS)); 1726 QVERIFY(entry.contains(KIO::UDSEntry::UDS_SIZE)); 1727 QVERIFY(entry.contains(KIO::UDSEntry::UDS_FILE_TYPE)); 1728 QVERIFY(entry.contains(KIO::UDSEntry::UDS_USER)); 1729 QVERIFY(entry.contains(KIO::UDSEntry::UDS_GROUP)); 1730 // QVERIFY(entry.contains(KIO::UDSEntry::UDS_CREATION_TIME)); // only true if st_birthtime or statx is used 1731 QVERIFY(entry.contains(KIO::UDSEntry::UDS_MODIFICATION_TIME)); 1732 QVERIFY(entry.contains(KIO::UDSEntry::UDS_ACCESS_TIME)); 1733 QCOMPARE(entry.count(), 8 + (entry.contains(KIO::UDSEntry::UDS_CREATION_TIME) ? 1 : 0)); 1734 1735 QVERIFY(!entry.isDir()); 1736 QVERIFY(!entry.isLink()); 1737 QCOMPARE(entry.stringValue(KIO::UDSEntry::UDS_NAME), QStringLiteral("fileFromHome")); 1738 1739 // Compare what we get via kio_file and what we get when KFileItem stat()s directly 1740 const KFileItem kioItem(entry, url); 1741 const KFileItem fileItem(url); 1742 QCOMPARE(kioItem.name(), fileItem.name()); 1743 QCOMPARE(kioItem.url(), fileItem.url()); 1744 QCOMPARE(kioItem.size(), fileItem.size()); 1745 QCOMPARE(kioItem.user(), fileItem.user()); 1746 QCOMPARE(kioItem.group(), fileItem.group()); 1747 QCOMPARE(kioItem.mimetype(), fileItem.mimetype()); 1748 QCOMPARE(kioItem.permissions(), fileItem.permissions()); 1749 QCOMPARE(kioItem.time(KFileItem::ModificationTime), fileItem.time(KFileItem::ModificationTime)); 1750 QCOMPARE(kioItem.time(KFileItem::AccessTime), fileItem.time(KFileItem::AccessTime)); 1751 1752 #else 1753 // Testing stat over HTTP 1754 KIO::StatJob *job = KIO::stat(QUrl("http://www.kde.org"), KIO::HideProgressInfo); 1755 QVERIFY(job); 1756 QVERIFY2(job->exec(), qPrintable(job->errorString())); 1757 // TODO set setSide, setDetails 1758 const KIO::UDSEntry &entry = job->statResult(); 1759 QVERIFY(!entry.isDir()); 1760 QVERIFY(!entry.isLink()); 1761 QCOMPARE(entry.stringValue(KIO::UDSEntry::UDS_NAME), QString()); 1762 #endif 1763 } 1764 1765 void JobTest::statDetailsBasic() 1766 { 1767 const QString filePath = homeTmpDir() + "fileFromHome"; 1768 createTestFile(filePath); 1769 const QUrl url(QUrl::fromLocalFile(filePath)); 1770 KIO::StatJob *job = KIO::statDetails(url, KIO::StatJob::StatSide::SourceSide, KIO::StatBasic, KIO::HideProgressInfo); 1771 QVERIFY(job); 1772 QVERIFY2(job->exec(), qPrintable(job->errorString())); 1773 // TODO set setSide 1774 const KIO::UDSEntry &entry = job->statResult(); 1775 1776 // we only get filename, access, type, size, (no linkdest) 1777 QVERIFY(entry.contains(KIO::UDSEntry::UDS_NAME)); 1778 QVERIFY(entry.contains(KIO::UDSEntry::UDS_ACCESS)); 1779 QVERIFY(entry.contains(KIO::UDSEntry::UDS_SIZE)); 1780 QVERIFY(entry.contains(KIO::UDSEntry::UDS_FILE_TYPE)); 1781 QCOMPARE(entry.count(), 4); 1782 1783 QVERIFY(!entry.isDir()); 1784 QVERIFY(!entry.isLink()); 1785 QVERIFY(entry.numberValue(KIO::UDSEntry::UDS_ACCESS) > 0); 1786 QCOMPARE(entry.stringValue(KIO::UDSEntry::UDS_NAME), QStringLiteral("fileFromHome")); 1787 1788 // Compare what we get via kio_file and what we get when KFileItem stat()s directly 1789 // for the requested fields 1790 const KFileItem kioItem(entry, url); 1791 const KFileItem fileItem(url); 1792 QCOMPARE(kioItem.name(), fileItem.name()); 1793 QCOMPARE(kioItem.url(), fileItem.url()); 1794 QCOMPARE(kioItem.size(), fileItem.size()); 1795 QCOMPARE(kioItem.user(), ""); 1796 QCOMPARE(kioItem.group(), ""); 1797 QCOMPARE(kioItem.mimetype(), "application/octet-stream"); 1798 QCOMPARE(kioItem.permissions(), 438); 1799 QCOMPARE(kioItem.time(KFileItem::ModificationTime), QDateTime()); 1800 QCOMPARE(kioItem.time(KFileItem::AccessTime), QDateTime()); 1801 } 1802 1803 void JobTest::statDetailsBasicSetDetails() 1804 { 1805 const QString filePath = homeTmpDir() + "fileFromHome"; 1806 createTestFile(filePath); 1807 const QUrl url(QUrl::fromLocalFile(filePath)); 1808 KIO::StatJob *job = KIO::stat(url); 1809 job->setDetails(KIO::StatBasic); 1810 QVERIFY(job); 1811 QVERIFY2(job->exec(), qPrintable(job->errorString())); 1812 // TODO set setSide 1813 const KIO::UDSEntry &entry = job->statResult(); 1814 1815 // we only get filename, access, type, size, (no linkdest) 1816 QVERIFY(entry.contains(KIO::UDSEntry::UDS_NAME)); 1817 QVERIFY(entry.contains(KIO::UDSEntry::UDS_ACCESS)); 1818 QVERIFY(entry.contains(KIO::UDSEntry::UDS_SIZE)); 1819 QVERIFY(entry.contains(KIO::UDSEntry::UDS_FILE_TYPE)); 1820 QCOMPARE(entry.count(), 4); 1821 1822 QVERIFY(!entry.isDir()); 1823 QVERIFY(!entry.isLink()); 1824 QVERIFY(entry.numberValue(KIO::UDSEntry::UDS_ACCESS) > 0); 1825 QCOMPARE(entry.stringValue(KIO::UDSEntry::UDS_NAME), QStringLiteral("fileFromHome")); 1826 1827 // Compare what we get via kio_file and what we get when KFileItem stat()s directly 1828 // for the requested fields 1829 const KFileItem kioItem(entry, url); 1830 const KFileItem fileItem(url); 1831 QCOMPARE(kioItem.name(), fileItem.name()); 1832 QCOMPARE(kioItem.url(), fileItem.url()); 1833 QCOMPARE(kioItem.size(), fileItem.size()); 1834 QCOMPARE(kioItem.user(), ""); 1835 QCOMPARE(kioItem.group(), ""); 1836 QCOMPARE(kioItem.mimetype(), "application/octet-stream"); 1837 QCOMPARE(kioItem.permissions(), 438); 1838 QCOMPARE(kioItem.time(KFileItem::ModificationTime), QDateTime()); 1839 QCOMPARE(kioItem.time(KFileItem::AccessTime), QDateTime()); 1840 } 1841 1842 void JobTest::statWithInode() 1843 { 1844 const QString filePath = homeTmpDir() + "fileFromHome"; 1845 createTestFile(filePath); 1846 const QUrl url(QUrl::fromLocalFile(filePath)); 1847 KIO::StatJob *job = KIO::statDetails(url, KIO::StatJob::SourceSide, KIO::StatInode); 1848 QVERIFY(job); 1849 QVERIFY2(job->exec(), qPrintable(job->errorString())); 1850 1851 const KIO::UDSEntry entry = job->statResult(); 1852 QVERIFY(entry.contains(KIO::UDSEntry::UDS_DEVICE_ID)); 1853 QVERIFY(entry.contains(KIO::UDSEntry::UDS_INODE)); 1854 QCOMPARE(entry.count(), 2); 1855 1856 const QString path = otherTmpDir() + "otherFile"; 1857 createTestFile(path); 1858 const QUrl otherUrl(QUrl::fromLocalFile(path)); 1859 KIO::StatJob *otherJob = KIO::statDetails(otherUrl, KIO::StatJob::SourceSide, KIO::StatInode); 1860 QVERIFY(otherJob); 1861 QVERIFY2(otherJob->exec(), qPrintable(otherJob->errorString())); 1862 1863 const KIO::UDSEntry otherEntry = otherJob->statResult(); 1864 QVERIFY(otherEntry.contains(KIO::UDSEntry::UDS_DEVICE_ID)); 1865 QVERIFY(otherEntry.contains(KIO::UDSEntry::UDS_INODE)); 1866 QCOMPARE(otherEntry.count(), 2); 1867 1868 const int device = entry.numberValue(KIO::UDSEntry::UDS_DEVICE_ID); 1869 const int otherDevice = otherEntry.numberValue(KIO::UDSEntry::UDS_DEVICE_ID); 1870 1871 // this test doesn't make sense on the CI as it's an LXC container with one partition 1872 if (otherTmpDirIsOnSamePartition()) { 1873 // On the CI where the two tmp dirs are on the only partition available 1874 // in the LXC container, the device ID's would be identical 1875 QCOMPARE(device, otherDevice); 1876 } else { 1877 QVERIFY(device != otherDevice); 1878 } 1879 } 1880 1881 #ifndef Q_OS_WIN 1882 void JobTest::statSymlink() 1883 { 1884 const QString filePath = homeTmpDir() + "fileFromHome"; 1885 createTestFile(filePath); 1886 const QString symlink = otherTmpDir() + "link"; 1887 QVERIFY(QFile(filePath).link(symlink)); 1888 QVERIFY(QFile::exists(symlink)); 1889 setTimeStamp(symlink, QDateTime::currentDateTime().addSecs(-20)); // differentiate link time and source file time 1890 1891 const QUrl url(QUrl::fromLocalFile(symlink)); 1892 KIO::StatJob *job = KIO::statDetails(url, 1893 KIO::StatJob::StatSide::SourceSide, 1894 KIO::StatBasic | KIO::StatResolveSymlink | KIO::StatUser | KIO::StatTime, 1895 KIO::HideProgressInfo); 1896 QVERIFY(job); 1897 QVERIFY2(job->exec(), qPrintable(job->errorString())); 1898 // TODO set setSide, setDetails 1899 const KIO::UDSEntry &entry = job->statResult(); 1900 1901 // we only get filename, access, type, size, linkdest, uid, gid, btime, mtime, atime 1902 QVERIFY(entry.contains(KIO::UDSEntry::UDS_NAME)); 1903 QVERIFY(entry.contains(KIO::UDSEntry::UDS_ACCESS)); 1904 QVERIFY(entry.contains(KIO::UDSEntry::UDS_SIZE)); 1905 QVERIFY(entry.contains(KIO::UDSEntry::UDS_FILE_TYPE)); 1906 QVERIFY(entry.contains(KIO::UDSEntry::UDS_LINK_DEST)); 1907 QVERIFY(entry.contains(KIO::UDSEntry::UDS_USER)); 1908 QVERIFY(entry.contains(KIO::UDSEntry::UDS_GROUP)); 1909 // QVERIFY(entry.contains(KIO::UDSEntry::UDS_CREATION_TIME)); // only true if st_birthtime or statx is used 1910 QVERIFY(entry.contains(KIO::UDSEntry::UDS_MODIFICATION_TIME)); 1911 QVERIFY(entry.contains(KIO::UDSEntry::UDS_ACCESS_TIME)); 1912 QCOMPARE(entry.count(), 9 + (entry.contains(KIO::UDSEntry::UDS_CREATION_TIME) ? 1 : 0)); 1913 1914 QVERIFY(!entry.isDir()); 1915 QVERIFY(entry.isLink()); 1916 QVERIFY(entry.numberValue(KIO::UDSEntry::UDS_ACCESS) > 0); 1917 QCOMPARE(entry.stringValue(KIO::UDSEntry::UDS_NAME), QStringLiteral("link")); 1918 1919 // Compare what we get via kio_file and what we get when KFileItem stat()s directly 1920 const KFileItem kioItem(entry, url); 1921 const KFileItem fileItem(url); 1922 QCOMPARE(kioItem.name(), fileItem.name()); 1923 QCOMPARE(kioItem.url(), fileItem.url()); 1924 QVERIFY(kioItem.isLink()); 1925 QVERIFY(fileItem.isLink()); 1926 QCOMPARE(kioItem.linkDest(), fileItem.linkDest()); 1927 QCOMPARE(kioItem.size(), fileItem.size()); 1928 QCOMPARE(kioItem.user(), fileItem.user()); 1929 QCOMPARE(kioItem.group(), fileItem.group()); 1930 QCOMPARE(kioItem.mimetype(), fileItem.mimetype()); 1931 QCOMPARE(kioItem.permissions(), fileItem.permissions()); 1932 QCOMPARE(kioItem.time(KFileItem::ModificationTime), fileItem.time(KFileItem::ModificationTime)); 1933 QCOMPARE(kioItem.time(KFileItem::AccessTime), fileItem.time(KFileItem::AccessTime)); 1934 } 1935 1936 /* Check that the underlying system, and Qt, support 1937 * millisecond timestamp resolution. 1938 */ 1939 void JobTest::statTimeResolution() 1940 { 1941 const QString filePath = homeTmpDir() + "statFile"; 1942 const QDateTime early70sDate = QDateTime::fromMSecsSinceEpoch(107780520123L); 1943 const time_t early70sTime = 107780520; // Seconds for January 6 1973, 12:02 1944 1945 createTestFile(filePath); 1946 1947 QFile dest_file(filePath); 1948 QVERIFY(dest_file.open(QIODevice::ReadOnly)); 1949 #if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) 1950 // with nano secs precision 1951 struct timespec ut[2]; 1952 ut[0].tv_sec = early70sTime; 1953 ut[0].tv_nsec = 123000000L; // 123 ms 1954 ut[1] = ut[0]; 1955 // need to do this with the dest file still opened, or this fails 1956 QCOMPARE(::futimens(dest_file.handle(), ut), 0); 1957 #else 1958 struct timeval ut[2]; 1959 ut[0].tv_sec = early70sTime; 1960 ut[0].tv_usec = 123000; 1961 ut[1] = ut[0]; 1962 QCOMPARE(::futimes(dest_file.handle(), ut), 0); 1963 #endif 1964 dest_file.close(); 1965 1966 // Check that the modification time is set with millisecond precision 1967 dest_file.setFileName(filePath); 1968 QDateTime d = dest_file.fileTime(QFileDevice::FileModificationTime); 1969 QCOMPARE(d, early70sDate); 1970 QCOMPARE(d.time().msec(), 123); 1971 1972 #if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) 1973 QT_STATBUF buff_dest; 1974 QCOMPARE(QT_STAT(filePath.toLocal8Bit().data(), &buff_dest), 0); 1975 QCOMPARE(buff_dest.st_mtim.tv_sec, early70sTime); 1976 QCOMPARE(buff_dest.st_mtim.tv_nsec, 123000000L); 1977 #endif 1978 1979 QCOMPARE(QFileInfo(filePath).lastModified(), early70sDate); 1980 } 1981 #endif 1982 1983 void JobTest::mostLocalUrl() 1984 { 1985 const QString filePath = homeTmpDir() + "fileFromHome"; 1986 createTestFile(filePath); 1987 KIO::StatJob *job = KIO::mostLocalUrl(QUrl::fromLocalFile(filePath), KIO::HideProgressInfo); 1988 QVERIFY(job); 1989 QVERIFY2(job->exec(), qPrintable(job->errorString())); 1990 QCOMPARE(job->mostLocalUrl().toLocalFile(), filePath); 1991 } 1992 1993 void JobTest::mostLocalUrlHttp() 1994 { 1995 // the url is returned as-is, as an http url can't have a mostLocalUrl 1996 const QUrl url("http://www.google.com"); 1997 KIO::StatJob *httpStat = KIO::mostLocalUrl(url, KIO::HideProgressInfo); 1998 QVERIFY(httpStat); 1999 QVERIFY2(httpStat->exec(), qPrintable(httpStat->errorString())); 2000 QCOMPARE(httpStat->mostLocalUrl(), url); 2001 } 2002 2003 void JobTest::chmodFile() 2004 { 2005 const QString filePath = homeTmpDir() + "fileForChmod"; 2006 createTestFile(filePath); 2007 KFileItem item(QUrl::fromLocalFile(filePath)); 2008 const mode_t origPerm = item.permissions(); 2009 mode_t newPerm = origPerm ^ S_IWGRP; 2010 QVERIFY(newPerm != origPerm); 2011 KFileItemList items; 2012 items << item; 2013 KIO::Job *job = KIO::chmod(items, newPerm, S_IWGRP /*TODO: QFile::WriteGroup*/, QString(), QString(), false, KIO::HideProgressInfo); 2014 job->setUiDelegate(nullptr); 2015 QVERIFY2(job->exec(), qPrintable(job->errorString())); 2016 2017 KFileItem newItem(QUrl::fromLocalFile(filePath)); 2018 QCOMPARE(QString::number(newItem.permissions(), 8), QString::number(newPerm, 8)); 2019 QFile::remove(filePath); 2020 } 2021 2022 #ifdef Q_OS_UNIX 2023 void JobTest::chmodSticky() 2024 { 2025 const QString dirPath = homeTmpDir() + "dirForChmodSticky"; 2026 QDir().mkpath(dirPath); 2027 KFileItem item(QUrl::fromLocalFile(dirPath)); 2028 const mode_t origPerm = item.permissions(); 2029 mode_t newPerm = origPerm ^ S_ISVTX; 2030 QVERIFY(newPerm != origPerm); 2031 KFileItemList items({item}); 2032 KIO::Job *job = KIO::chmod(items, newPerm, S_ISVTX, QString(), QString(), false, KIO::HideProgressInfo); 2033 job->setUiDelegate(nullptr); 2034 QVERIFY2(job->exec(), qPrintable(job->errorString())); 2035 2036 KFileItem newItem(QUrl::fromLocalFile(dirPath)); 2037 QCOMPARE(QString::number(newItem.permissions(), 8), QString::number(newPerm, 8)); 2038 QVERIFY(QDir().rmdir(dirPath)); 2039 } 2040 #endif 2041 2042 void JobTest::chmodFileError() 2043 { 2044 // chown(root) should fail 2045 const QString filePath = homeTmpDir() + "fileForChmod"; 2046 createTestFile(filePath); 2047 KFileItem item(QUrl::fromLocalFile(filePath)); 2048 const mode_t origPerm = item.permissions(); 2049 mode_t newPerm = origPerm ^ S_IWGRP; 2050 QVERIFY(newPerm != origPerm); 2051 KFileItemList items; 2052 items << item; 2053 KIO::Job *job = KIO::chmod(items, newPerm, S_IWGRP /*TODO: QFile::WriteGroup*/, QStringLiteral("root"), QString(), false, KIO::HideProgressInfo); 2054 // Simulate the user pressing "Skip" in the dialog. 2055 job->setUiDelegate(new KJobUiDelegate); 2056 auto *askUser = new MockAskUserInterface(job->uiDelegate()); 2057 2058 QVERIFY2(job->exec(), qPrintable(job->errorString())); 2059 2060 QCOMPARE(askUser->m_askUserSkipCalled, 1); 2061 KFileItem newItem(QUrl::fromLocalFile(filePath)); 2062 // We skipped, so the chmod didn't happen. 2063 QCOMPARE(QString::number(newItem.permissions(), 8), QString::number(origPerm, 8)); 2064 QFile::remove(filePath); 2065 } 2066 2067 void JobTest::mimeType() 2068 { 2069 #if 1 2070 const QString filePath = homeTmpDir() + "fileFromHome"; 2071 createTestFile(filePath); 2072 KIO::MimetypeJob *job = KIO::mimetype(QUrl::fromLocalFile(filePath), KIO::HideProgressInfo); 2073 QVERIFY(job); 2074 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 78) 2075 QSignalSpy spyMimeType(job, qOverload<KIO::Job *, const QString &>(&KIO::MimetypeJob::mimetype)); 2076 #endif 2077 QSignalSpy spyMimeTypeFound(job, &KIO::TransferJob::mimeTypeFound); 2078 QVERIFY2(job->exec(), qPrintable(job->errorString())); 2079 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 78) 2080 QCOMPARE(spyMimeType.count(), 1); 2081 QCOMPARE(spyMimeType[0][0], QVariant::fromValue(static_cast<KIO::Job *>(job))); 2082 QCOMPARE(spyMimeType[0][1].toString(), QStringLiteral("application/octet-stream")); 2083 #endif 2084 QCOMPARE(spyMimeTypeFound.count(), 1); 2085 QCOMPARE(spyMimeTypeFound[0][0], QVariant::fromValue(static_cast<KIO::Job *>(job))); 2086 QCOMPARE(spyMimeTypeFound[0][1].toString(), QStringLiteral("application/octet-stream")); 2087 #else 2088 // Testing mimetype over HTTP 2089 KIO::MimetypeJob *job = KIO::mimetype(QUrl("http://www.kde.org"), KIO::HideProgressInfo); 2090 QVERIFY(job); 2091 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 78) 2092 QSignalSpy spyMimeType(job, qOverload<KIO::Job *, const QString &>(&KIO::MimetypeJob::mimetype)); 2093 #endif 2094 QSignalSpy spyMimeTypeFound(job, &KIO::TransferJob::mimeTypeFound); 2095 QVERIFY2(job->exec(), qPrintable(job->errorString())); 2096 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 78) 2097 QCOMPARE(spyMimeType.count(), 1); 2098 QCOMPARE(spyMimeType[0][0], QVariant::fromValue(static_cast<KIO::Job *>(job))); 2099 QCOMPARE(spyMimeType[0][1].toString(), QString("text/html")); 2100 #endif 2101 QCOMPARE(spyMimeTypeFound.count(), 1); 2102 QCOMPARE(spyMimeTypeFound[0][0], QVariant::fromValue(static_cast<KIO::Job *>(job))); 2103 QCOMPARE(spyMimeTypeFound[0][1].toString(), QString("text/html")); 2104 #endif 2105 } 2106 2107 void JobTest::mimeTypeError() 2108 { 2109 // KIO::mimetype() on a file that doesn't exist 2110 const QString filePath = homeTmpDir() + "doesNotExist"; 2111 KIO::MimetypeJob *job = KIO::mimetype(QUrl::fromLocalFile(filePath), KIO::HideProgressInfo); 2112 QVERIFY(job); 2113 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 78) 2114 QSignalSpy spyMimeType(job, qOverload<KIO::Job *, const QString &>(&KIO::MimetypeJob::mimetype)); 2115 #endif 2116 QSignalSpy spyMimeTypeFound(job, &KIO::TransferJob::mimeTypeFound); 2117 QSignalSpy spyResult(job, &KJob::result); 2118 QVERIFY(!job->exec()); 2119 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 78) 2120 QCOMPARE(spyMimeType.count(), 0); 2121 #endif 2122 QCOMPARE(spyMimeTypeFound.count(), 0); 2123 QCOMPARE(spyResult.count(), 1); 2124 } 2125 2126 void JobTest::moveFileDestAlreadyExists_data() 2127 { 2128 QTest::addColumn<bool>("autoSkip"); 2129 2130 QTest::newRow("autoSkip") << true; 2131 QTest::newRow("manualSkip") << false; 2132 } 2133 2134 void JobTest::moveFileDestAlreadyExists() // #157601 2135 { 2136 QFETCH(bool, autoSkip); 2137 2138 const QString file1 = homeTmpDir() + "fileFromHome"; 2139 createTestFile(file1); 2140 const QString file2 = homeTmpDir() + "fileFromHome2"; 2141 createTestFile(file2); 2142 const QString file3 = homeTmpDir() + "anotherFile"; 2143 createTestFile(file3); 2144 const QString existingDest = otherTmpDir() + "fileFromHome"; 2145 createTestFile(existingDest); 2146 const QString existingDest2 = otherTmpDir() + "fileFromHome2"; 2147 createTestFile(existingDest2); 2148 2149 ScopedCleaner cleaner([] { 2150 QFile::remove(otherTmpDir() + "anotherFile"); 2151 }); 2152 2153 const QList<QUrl> urls{QUrl::fromLocalFile(file1), QUrl::fromLocalFile(file2), QUrl::fromLocalFile(file3)}; 2154 KIO::CopyJob *job = KIO::move(urls, QUrl::fromLocalFile(otherTmpDir()), KIO::HideProgressInfo); 2155 MockAskUserInterface *askUserHandler = nullptr; 2156 if (autoSkip) { 2157 job->setUiDelegate(nullptr); 2158 job->setAutoSkip(true); 2159 } else { 2160 // Simulate the user pressing "Skip" in the dialog. 2161 job->setUiDelegate(new KJobUiDelegate); 2162 askUserHandler = new MockAskUserInterface(job->uiDelegate()); 2163 askUserHandler->m_renameResult = KIO::Result_Skip; 2164 } 2165 QVERIFY2(job->exec(), qPrintable(job->errorString())); 2166 2167 if (askUserHandler) { 2168 QCOMPARE(askUserHandler->m_askUserRenameCalled, 2); 2169 QCOMPARE(askUserHandler->m_askUserSkipCalled, 0); 2170 } 2171 QVERIFY(QFile::exists(file1)); // it was skipped 2172 QVERIFY(QFile::exists(file2)); // it was skipped 2173 QVERIFY(!QFile::exists(file3)); // it was moved 2174 2175 QCOMPARE(job->totalAmount(KJob::Files), 3); 2176 QCOMPARE(job->totalAmount(KJob::Directories), 0); 2177 QCOMPARE(job->processedAmount(KJob::Files), 1); 2178 QCOMPARE(job->processedAmount(KJob::Directories), 0); 2179 QCOMPARE(job->percent(), 100); 2180 } 2181 2182 void JobTest::copyFileDestAlreadyExists_data() 2183 { 2184 QTest::addColumn<bool>("autoSkip"); 2185 2186 QTest::newRow("autoSkip") << true; 2187 QTest::newRow("manualSkip") << false; 2188 } 2189 2190 static void simulatePressingSkip(KJob *job) 2191 { 2192 // Simulate the user pressing "Skip" in the dialog. 2193 job->setUiDelegate(new KJobUiDelegate); 2194 auto *askUserHandler = new MockAskUserInterface(job->uiDelegate()); 2195 askUserHandler->m_skipResult = KIO::Result_Skip; 2196 } 2197 2198 void JobTest::copyFileDestAlreadyExists() // to test skipping when copying 2199 { 2200 QFETCH(bool, autoSkip); 2201 const QString file1 = homeTmpDir() + "fileFromHome"; 2202 createTestFile(file1); 2203 const QString file2 = homeTmpDir() + "anotherFile"; 2204 createTestFile(file2); 2205 const QString existingDest = otherTmpDir() + "fileFromHome"; 2206 createTestFile(existingDest); 2207 2208 ScopedCleaner cleaner([] { 2209 QFile::remove(otherTmpDir() + "anotherFile"); 2210 }); 2211 2212 const QList<QUrl> urls{QUrl::fromLocalFile(file1), QUrl::fromLocalFile(file2)}; 2213 KIO::CopyJob *job = KIO::copy(urls, QUrl::fromLocalFile(otherTmpDir()), KIO::HideProgressInfo); 2214 if (autoSkip) { 2215 job->setUiDelegate(nullptr); 2216 job->setAutoSkip(true); 2217 } else { 2218 simulatePressingSkip(job); 2219 } 2220 QVERIFY2(job->exec(), qPrintable(job->errorString())); 2221 QVERIFY(QFile::exists(otherTmpDir() + "anotherFile")); 2222 2223 QCOMPARE(job->totalAmount(KJob::Files), 2); // file1, file2 2224 QCOMPARE(job->totalAmount(KJob::Directories), 0); 2225 QCOMPARE(job->processedAmount(KJob::Files), 1); 2226 QCOMPARE(job->processedAmount(KJob::Directories), 0); 2227 QCOMPARE(job->percent(), 100); 2228 } 2229 2230 void JobTest::moveDestAlreadyExistsAutoRename_data() 2231 { 2232 QTest::addColumn<bool>("samePartition"); 2233 QTest::addColumn<bool>("moveDirs"); 2234 2235 QTest::newRow("files_same_partition") << true << false; 2236 QTest::newRow("files_other_partition") << false << false; 2237 QTest::newRow("dirs_same_partition") << true << true; 2238 QTest::newRow("dirs_other_partition") << false << true; 2239 } 2240 2241 void JobTest::moveDestAlreadyExistsAutoRename() 2242 { 2243 QFETCH(bool, samePartition); 2244 QFETCH(bool, moveDirs); 2245 2246 QString dir; 2247 if (samePartition) { 2248 dir = homeTmpDir() + "dir/"; 2249 QVERIFY(QDir(dir).exists() || QDir().mkdir(dir)); 2250 } else { 2251 dir = otherTmpDir(); 2252 } 2253 moveDestAlreadyExistsAutoRename(dir, moveDirs); 2254 2255 if (samePartition) { 2256 // cleanup 2257 KIO::Job *job = KIO::del(QUrl::fromLocalFile(dir), KIO::HideProgressInfo); 2258 QVERIFY2(job->exec(), qPrintable(job->errorString())); 2259 QVERIFY(!QFile::exists(dir)); 2260 } 2261 } 2262 2263 void JobTest::moveDestAlreadyExistsAutoRename(const QString &destDir, bool moveDirs) // #256650 2264 { 2265 const QString prefix = moveDirs ? QStringLiteral("dir ") : QStringLiteral("file "); 2266 2267 const QString file1 = homeTmpDir() + prefix + "(1)"; 2268 const QString file2 = homeTmpDir() + prefix + "(2)"; 2269 const QString existingDest1 = destDir + prefix + "(1)"; 2270 const QString existingDest2 = destDir + prefix + "(2)"; 2271 const QStringList sources = QStringList{file1, file2, existingDest1, existingDest2}; 2272 for (const QString &source : sources) { 2273 if (moveDirs) { 2274 QVERIFY(QDir().mkdir(source)); 2275 createTestFile(source + "/innerfile"); 2276 createTestFile(source + "/innerfile2"); 2277 } else { 2278 createTestFile(source); 2279 } 2280 } 2281 const QString file3 = destDir + prefix + "(3)"; 2282 const QString file4 = destDir + prefix + "(4)"; 2283 2284 ScopedCleaner cleaner([&]() { 2285 if (moveDirs) { 2286 QDir().rmdir(file1); 2287 QDir().rmdir(file2); 2288 QDir().rmdir(file3); 2289 QDir().rmdir(file4); 2290 } else { 2291 QFile::remove(file1); 2292 QFile::remove(file2); 2293 QFile::remove(file3); 2294 QFile::remove(file4); 2295 } 2296 }); 2297 2298 const QList<QUrl> urls = {QUrl::fromLocalFile(file1), QUrl::fromLocalFile(file2)}; 2299 KIO::CopyJob *job = KIO::move(urls, QUrl::fromLocalFile(destDir), KIO::HideProgressInfo); 2300 job->setUiDelegate(nullptr); 2301 job->setUiDelegateExtension(nullptr); 2302 job->setAutoRename(true); 2303 2304 QSignalSpy spyRenamed(job, &KIO::CopyJob::renamed); 2305 2306 // qDebug() << QDir(destDir).entryList(); 2307 2308 QVERIFY2(job->exec(), qPrintable(job->errorString())); 2309 2310 // qDebug() << QDir(destDir).entryList(); 2311 QVERIFY(!QFile::exists(file1)); // it was moved 2312 QVERIFY(!QFile::exists(file2)); // it was moved 2313 2314 QVERIFY(QFile::exists(existingDest1)); 2315 QVERIFY(QFile::exists(existingDest2)); 2316 QVERIFY(QFile::exists(file3)); 2317 QVERIFY(QFile::exists(file4)); 2318 2319 QVERIFY(!spyRenamed.isEmpty()); 2320 2321 auto list = spyRenamed.takeFirst(); 2322 QCOMPARE(list.at(1).toUrl(), QUrl::fromLocalFile(destDir + prefix + "(1)")); 2323 QCOMPARE(list.at(2).toUrl(), QUrl::fromLocalFile(file3)); 2324 2325 bool samePartition = false; 2326 // Normally we'd see renamed(1, 3) and renamed(2, 4) 2327 // But across partitions, direct rename fails, and we end up with a task list of 2328 // 1->3, 2->3 since renaming 1 to 3 didn't happen yet. 2329 // so renamed(2, 3) is emitted, as if the user had chosen that. 2330 // And when that fails, we then get (3, 4) 2331 if (spyRenamed.count() == 1) { 2332 // It was indeed on the same partition 2333 samePartition = true; 2334 list = spyRenamed.takeFirst(); 2335 QCOMPARE(list.at(1).toUrl(), QUrl::fromLocalFile(destDir + prefix + "(2)")); 2336 QCOMPARE(list.at(2).toUrl(), QUrl::fromLocalFile(file4)); 2337 } else { 2338 // Remove all renamed signals about innerfiles 2339 spyRenamed.erase(std::remove_if(spyRenamed.begin(), 2340 spyRenamed.end(), 2341 [](const QList<QVariant> &spy) { 2342 return spy.at(1).toUrl().path().contains("innerfile"); 2343 }), 2344 spyRenamed.end()); 2345 2346 list = spyRenamed.takeFirst(); 2347 QCOMPARE(list.at(1).toUrl(), QUrl::fromLocalFile(destDir + prefix + "(2)")); 2348 QCOMPARE(list.at(2).toUrl(), QUrl::fromLocalFile(file3)); 2349 2350 list = spyRenamed.takeFirst(); 2351 QCOMPARE(list.at(1).toUrl(), QUrl::fromLocalFile(file3)); 2352 QCOMPARE(list.at(2).toUrl(), QUrl::fromLocalFile(file4)); 2353 } 2354 2355 if (samePartition) { 2356 QCOMPARE(job->totalAmount(KJob::Files), 2); // direct-renamed, so counted as files 2357 QCOMPARE(job->totalAmount(KJob::Directories), 0); 2358 QCOMPARE(job->processedAmount(KJob::Files), 2); 2359 QCOMPARE(job->processedAmount(KJob::Directories), 0); 2360 } else { 2361 if (moveDirs) { 2362 QCOMPARE(job->totalAmount(KJob::Directories), 2); 2363 QCOMPARE(job->totalAmount(KJob::Files), 4); // innerfiles 2364 QCOMPARE(job->processedAmount(KJob::Directories), 2); 2365 QCOMPARE(job->processedAmount(KJob::Files), 4); 2366 } else { 2367 QCOMPARE(job->totalAmount(KJob::Files), 2); 2368 QCOMPARE(job->totalAmount(KJob::Directories), 0); 2369 QCOMPARE(job->processedAmount(KJob::Files), 2); 2370 QCOMPARE(job->processedAmount(KJob::Directories), 0); 2371 } 2372 } 2373 2374 QCOMPARE(job->percent(), 100); 2375 } 2376 2377 void JobTest::copyDirectoryAlreadyExistsSkip() 2378 { 2379 // when copying a directory (which contains at least one file) to some location, and then 2380 // copying the same dir to the same location again, and clicking "Skip" there should be no 2381 // segmentation fault, bug 408350 2382 2383 const QString src = homeTmpDir() + "a"; 2384 createTestDirectory(src); 2385 const QString dest = homeTmpDir() + "dest"; 2386 createTestDirectory(dest); 2387 2388 QUrl u = QUrl::fromLocalFile(src); 2389 QUrl d = QUrl::fromLocalFile(dest); 2390 2391 KIO::Job *job = KIO::copy(u, d, KIO::HideProgressInfo); 2392 job->setUiDelegate(nullptr); 2393 QVERIFY2(job->exec(), qPrintable(job->errorString())); 2394 QVERIFY(QFile::exists(dest + QStringLiteral("/a/testfile"))); 2395 2396 job = KIO::copy(u, d, KIO::HideProgressInfo); 2397 2398 simulatePressingSkip(job); 2399 2400 QVERIFY2(job->exec(), qPrintable(job->errorString())); 2401 QVERIFY(QFile::exists(dest + QStringLiteral("/a/testfile"))); 2402 2403 QDir(src).removeRecursively(); 2404 QDir(dest).removeRecursively(); 2405 2406 QCOMPARE(job->totalAmount(KJob::Files), 2); // testfile, testlink 2407 QCOMPARE(job->totalAmount(KJob::Directories), 1); 2408 QCOMPARE(job->processedAmount(KJob::Files), 0); 2409 QCOMPARE(job->processedAmount(KJob::Directories), 1); 2410 QCOMPARE(job->percent(), 0); 2411 } 2412 2413 void JobTest::copyFileAlreadyExistsRename() 2414 { 2415 const QString sourceFile = homeTmpDir() + "file"; 2416 const QString dest = homeTmpDir() + "dest/"; 2417 const QString alreadyExisting = dest + "file"; 2418 const QString renamedFile = dest + "file-renamed"; 2419 2420 createTestFile(sourceFile); 2421 createTestFile(alreadyExisting); 2422 QVERIFY(QFile::exists(sourceFile)); 2423 QVERIFY(QFile::exists(alreadyExisting)); 2424 2425 createTestDirectory(dest); 2426 2427 ScopedCleaner cleaner([&] { 2428 QVERIFY(QFile(sourceFile).remove()); 2429 QVERIFY(QDir(dest).removeRecursively()); 2430 }); 2431 2432 QUrl s = QUrl::fromLocalFile(sourceFile); 2433 QUrl d = QUrl::fromLocalFile(dest); 2434 2435 KIO::CopyJob *job = KIO::copy(s, d, KIO::HideProgressInfo); 2436 // Simulate the user pressing "Rename" in the dialog and choosing another destination. 2437 job->setUiDelegate(new KJobUiDelegate); 2438 auto *askUserHandler = new MockAskUserInterface(job->uiDelegate()); 2439 askUserHandler->m_renameResult = KIO::Result_Rename; 2440 askUserHandler->m_newDestUrl = QUrl::fromLocalFile(renamedFile); 2441 2442 QSignalSpy spyRenamed(job, &KIO::CopyJob::renamed); 2443 2444 QVERIFY2(job->exec(), qPrintable(job->errorString())); 2445 QVERIFY(QFile::exists(renamedFile)); 2446 2447 QCOMPARE(spyRenamed.count(), 1); 2448 auto list = spyRenamed.takeFirst(); 2449 QCOMPARE(list.at(1).toUrl(), QUrl::fromLocalFile(alreadyExisting)); 2450 QCOMPARE(list.at(2).toUrl(), QUrl::fromLocalFile(renamedFile)); 2451 } 2452 2453 void JobTest::safeOverwrite_data() 2454 { 2455 QTest::addColumn<bool>("destFileExists"); 2456 2457 QTest::newRow("dest_file_exists") << true; 2458 QTest::newRow("dest_file_does_not_exist") << false; 2459 } 2460 2461 void JobTest::safeOverwrite() 2462 { 2463 #ifdef Q_OS_WIN 2464 QSKIP("Test skipped on Windows"); 2465 #endif 2466 2467 QFETCH(bool, destFileExists); 2468 const QString srcDir = homeTmpDir() + "overwrite"; 2469 const QString srcFile = srcDir + "/testfile"; 2470 const QString destDir = otherTmpDir() + "overwrite_other"; 2471 const QString destFile = destDir + "/testfile"; 2472 const QString destPartFile = destFile + ".part"; 2473 2474 createTestDirectory(srcDir); 2475 createTestDirectory(destDir); 2476 2477 ScopedCleaner cleaner([&] { 2478 QDir(srcDir).removeRecursively(); 2479 QDir(destDir).removeRecursively(); 2480 }); 2481 2482 const int srcSize = 1000000; // ~1MB 2483 QVERIFY(QFile::resize(srcFile, srcSize)); 2484 if (!destFileExists) { 2485 QVERIFY(QFile::remove(destFile)); 2486 } else { 2487 QVERIFY(QFile::exists(destFile)); 2488 } 2489 QVERIFY(!QFile::exists(destPartFile)); 2490 2491 if (otherTmpDirIsOnSamePartition()) { 2492 QSKIP(qPrintable(QStringLiteral("This test requires %1 and %2 to be on different partitions").arg(srcDir, destDir))); 2493 } 2494 2495 KIO::FileCopyJob *job = KIO::file_move(QUrl::fromLocalFile(srcFile), QUrl::fromLocalFile(destFile), -1, KIO::HideProgressInfo | KIO::Overwrite); 2496 job->setUiDelegate(nullptr); 2497 QSignalSpy spyTotalSize(job, &KIO::FileCopyJob::totalSize); 2498 connect(job, &KIO::FileCopyJob::processedSize, this, [&](KJob *job, qulonglong size) { 2499 Q_UNUSED(job); 2500 if (size > 0 && size < srcSize) { 2501 // To avoid overwriting dest, we want the KIO worker to use dest.part 2502 QCOMPARE(QFileInfo::exists(destPartFile), destFileExists); 2503 } 2504 }); 2505 QVERIFY2(job->exec(), qPrintable(job->errorString())); 2506 QVERIFY(QFile::exists(destFile)); 2507 QVERIFY(!QFile::exists(srcFile)); 2508 QVERIFY(!QFile::exists(destPartFile)); 2509 QCOMPARE(spyTotalSize.count(), 1); 2510 } 2511 2512 void JobTest::overwriteOlderFiles_data() 2513 { 2514 QTest::addColumn<bool>("destFileOlder"); 2515 QTest::addColumn<bool>("moving"); 2516 2517 QTest::newRow("dest_file_older_copying") << true << false; 2518 QTest::newRow("dest_file_older_moving") << true << true; 2519 QTest::newRow("dest_file_younger_copying") << false << false; 2520 QTest::newRow("dest_file_younger_moving") << false << true; 2521 } 2522 2523 void JobTest::overwriteOlderFiles() 2524 { 2525 QFETCH(bool, destFileOlder); 2526 QFETCH(bool, moving); 2527 const QString srcDir = homeTmpDir() + "overwrite"; 2528 const QString srcFile = srcDir + "/testfile"; 2529 const QString srcFile2 = srcDir + "/testfile2"; 2530 const QString srcFile3 = srcDir + "/testfile3"; 2531 const QString destDir = otherTmpDir() + "overwrite_other"; 2532 const QString destFile = destDir + "/testfile"; 2533 const QString destFile2 = destDir + "/testfile2"; 2534 const QString destFile3 = destDir + "/testfile3"; 2535 const QString destPartFile = destFile + ".part"; 2536 2537 createTestDirectory(srcDir); 2538 createTestDirectory(destDir); 2539 createTestFile(srcFile2); 2540 createTestFile(srcFile3); 2541 createTestFile(destFile2); 2542 createTestFile(destFile3); 2543 QVERIFY(!QFile::exists(destPartFile)); 2544 2545 const int srcSize = 1000; // ~1KB 2546 QVERIFY(QFile::resize(srcFile, srcSize)); 2547 QVERIFY(QFile::resize(srcFile2, srcSize)); 2548 QVERIFY(QFile::resize(srcFile3, srcSize)); 2549 if (destFileOlder) { 2550 setTimeStamp(destFile, QFile(srcFile).fileTime(QFileDevice::FileModificationTime).addSecs(-2)); 2551 setTimeStamp(destFile2, QFile(srcFile2).fileTime(QFileDevice::FileModificationTime).addSecs(-2)); 2552 2553 QVERIFY(QFile(destFile).fileTime(QFileDevice::FileModificationTime) <= QFile(srcFile).fileTime(QFileDevice::FileModificationTime)); 2554 QVERIFY(QFile(destFile2).fileTime(QFileDevice::FileModificationTime) <= QFile(srcFile2).fileTime(QFileDevice::FileModificationTime)); 2555 } else { 2556 setTimeStamp(destFile, QFile(srcFile).fileTime(QFileDevice::FileModificationTime).addSecs(2)); 2557 setTimeStamp(destFile2, QFile(srcFile2).fileTime(QFileDevice::FileModificationTime).addSecs(2)); 2558 2559 QVERIFY(QFile(destFile).fileTime(QFileDevice::FileModificationTime) >= QFile(srcFile).fileTime(QFileDevice::FileModificationTime)); 2560 QVERIFY(QFile(destFile2).fileTime(QFileDevice::FileModificationTime) >= QFile(srcFile2).fileTime(QFileDevice::FileModificationTime)); 2561 } 2562 // to have an always skipped file 2563 setTimeStamp(destFile3, QFile(srcFile3).fileTime(QFileDevice::FileModificationTime).addSecs(2)); 2564 2565 KIO::CopyJob *job; 2566 if (moving) { 2567 job = KIO::move({QUrl::fromLocalFile(srcFile), QUrl::fromLocalFile(srcFile2), QUrl::fromLocalFile(srcFile3)}, 2568 QUrl::fromLocalFile(destDir), 2569 KIO::HideProgressInfo); 2570 } else { 2571 job = KIO::copy({QUrl::fromLocalFile(srcFile), QUrl::fromLocalFile(srcFile2), QUrl::fromLocalFile(srcFile3)}, 2572 QUrl::fromLocalFile(destDir), 2573 KIO::HideProgressInfo); 2574 } 2575 2576 job->setUiDelegate(new KJobUiDelegate); 2577 auto *askUserHandler = new MockAskUserInterface(job->uiDelegate()); 2578 askUserHandler->m_renameResult = KIO::Result_OverwriteWhenOlder; 2579 2580 QVERIFY2(job->exec(), qPrintable(job->errorString())); 2581 QCOMPARE(askUserHandler->m_askUserRenameCalled, 1); 2582 QVERIFY(!QFile::exists(destPartFile)); 2583 // QCOMPARE(spyTotalSize.count(), 1); 2584 2585 // skipped file whose dest is always newer 2586 QVERIFY(QFile::exists(srcFile3)); // it was skipped 2587 QCOMPARE(QFile(destFile3).size(), 11); 2588 2589 if (destFileOlder) { 2590 // files were overwritten 2591 QCOMPARE(QFile(destFile).size(), 1000); 2592 QCOMPARE(QFile(destFile2).size(), 1000); 2593 2594 // files were overwritten 2595 QCOMPARE(job->processedAmount(KJob::Files), 2); 2596 QCOMPARE(job->processedAmount(KJob::Directories), 0); 2597 2598 if (moving) { 2599 QVERIFY(!QFile::exists(srcFile)); // it was moved 2600 QVERIFY(!QFile::exists(srcFile2)); // it was moved 2601 } else { 2602 QVERIFY(QFile::exists(srcFile)); // it was copied 2603 QVERIFY(QFile::exists(srcFile2)); // it was copied 2604 2605 QCOMPARE(QFile(destFile).fileTime(QFileDevice::FileModificationTime), QFile(srcFile).fileTime(QFileDevice::FileModificationTime)); 2606 QCOMPARE(QFile(destFile2).fileTime(QFileDevice::FileModificationTime), QFile(srcFile2).fileTime(QFileDevice::FileModificationTime)); 2607 } 2608 } else { 2609 // files were skipped 2610 QCOMPARE(job->processedAmount(KJob::Files), 0); 2611 QCOMPARE(job->processedAmount(KJob::Directories), 0); 2612 2613 QCOMPARE(QFile(destFile).size(), 11); 2614 QCOMPARE(QFile(destFile2).size(), 11); 2615 2616 QVERIFY(QFile::exists(srcFile)); 2617 QVERIFY(QFile::exists(srcFile2)); 2618 } 2619 2620 QDir(srcDir).removeRecursively(); 2621 QDir(destDir).removeRecursively(); 2622 } 2623 2624 void JobTest::moveAndOverwrite() 2625 { 2626 const QString sourceFile = homeTmpDir() + "fileFromHome"; 2627 createTestFile(sourceFile); 2628 QString existingDest = otherTmpDir() + "fileFromHome"; 2629 createTestFile(existingDest); 2630 2631 KIO::FileCopyJob *job = KIO::file_move(QUrl::fromLocalFile(sourceFile), QUrl::fromLocalFile(existingDest), -1, KIO::HideProgressInfo | KIO::Overwrite); 2632 job->setUiDelegate(nullptr); 2633 QVERIFY2(job->exec(), qPrintable(job->errorString())); 2634 QVERIFY(!QFile::exists(sourceFile)); // it was moved 2635 2636 #ifndef Q_OS_WIN 2637 // Now same thing when the target is a symlink to the source 2638 createTestFile(sourceFile); 2639 createTestSymlink(existingDest, QFile::encodeName(sourceFile)); 2640 QVERIFY(QFile::exists(existingDest)); 2641 job = KIO::file_move(QUrl::fromLocalFile(sourceFile), QUrl::fromLocalFile(existingDest), -1, KIO::HideProgressInfo | KIO::Overwrite); 2642 job->setUiDelegate(nullptr); 2643 QVERIFY2(job->exec(), qPrintable(job->errorString())); 2644 QVERIFY(!QFile::exists(sourceFile)); // it was moved 2645 2646 // Now same thing when the target is a symlink to another file 2647 createTestFile(sourceFile); 2648 createTestFile(sourceFile + QLatin1Char('2')); 2649 createTestSymlink(existingDest, QFile::encodeName(sourceFile + QLatin1Char('2'))); 2650 QVERIFY(QFile::exists(existingDest)); 2651 job = KIO::file_move(QUrl::fromLocalFile(sourceFile), QUrl::fromLocalFile(existingDest), -1, KIO::HideProgressInfo | KIO::Overwrite); 2652 job->setUiDelegate(nullptr); 2653 QVERIFY2(job->exec(), qPrintable(job->errorString())); 2654 QVERIFY(!QFile::exists(sourceFile)); // it was moved 2655 2656 // Now same thing when the target is a _broken_ symlink 2657 createTestFile(sourceFile); 2658 createTestSymlink(existingDest); 2659 QVERIFY(!QFile::exists(existingDest)); // it exists, but it's broken... 2660 job = KIO::file_move(QUrl::fromLocalFile(sourceFile), QUrl::fromLocalFile(existingDest), -1, KIO::HideProgressInfo | KIO::Overwrite); 2661 job->setUiDelegate(nullptr); 2662 QVERIFY2(job->exec(), qPrintable(job->errorString())); 2663 QVERIFY(!QFile::exists(sourceFile)); // it was moved 2664 #endif 2665 } 2666 2667 void JobTest::moveOverSymlinkToSelf() // #169547 2668 { 2669 #ifndef Q_OS_WIN 2670 const QString sourceFile = homeTmpDir() + "fileFromHome"; 2671 createTestFile(sourceFile); 2672 const QString existingDest = homeTmpDir() + "testlink"; 2673 createTestSymlink(existingDest, QFile::encodeName(sourceFile)); 2674 QVERIFY(QFile::exists(existingDest)); 2675 2676 KIO::CopyJob *job = KIO::move(QUrl::fromLocalFile(sourceFile), QUrl::fromLocalFile(existingDest), KIO::HideProgressInfo); 2677 job->setUiDelegate(nullptr); 2678 QVERIFY(!job->exec()); 2679 QCOMPARE(job->error(), (int)KIO::ERR_FILE_ALREADY_EXIST); // and not ERR_IDENTICAL_FILES! 2680 QVERIFY(QFile::exists(sourceFile)); // it not moved 2681 #endif 2682 } 2683 2684 void JobTest::createSymlink() 2685 { 2686 #ifdef Q_OS_WIN 2687 QSKIP("Test skipped on Windows"); 2688 #endif 2689 const QString sourceFile = homeTmpDir() + "fileFromHome"; 2690 createTestFile(sourceFile); 2691 const QString destDir = homeTmpDir() + "dest"; 2692 QVERIFY(QDir().mkpath(destDir)); 2693 2694 ScopedCleaner cleaner([&] { 2695 QDir(destDir).removeRecursively(); 2696 }); 2697 2698 // With KIO::link (high-level) 2699 KIO::CopyJob *job = KIO::link(QUrl::fromLocalFile(sourceFile), QUrl::fromLocalFile(destDir), KIO::HideProgressInfo); 2700 QVERIFY2(job->exec(), qPrintable(job->errorString())); 2701 QVERIFY(QFileInfo::exists(sourceFile)); 2702 const QString dest = destDir + "/fileFromHome"; 2703 QVERIFY(QFileInfo(dest).isSymLink()); 2704 QCOMPARE(QFileInfo(dest).symLinkTarget(), sourceFile); 2705 QFile::remove(dest); 2706 2707 // With KIO::symlink (low-level) 2708 const QString linkPath = destDir + "/link"; 2709 KIO::Job *symlinkJob = KIO::symlink(sourceFile, QUrl::fromLocalFile(linkPath), KIO::HideProgressInfo); 2710 QVERIFY2(symlinkJob->exec(), qPrintable(symlinkJob->errorString())); 2711 QVERIFY(QFileInfo::exists(sourceFile)); 2712 QVERIFY(QFileInfo(linkPath).isSymLink()); 2713 QCOMPARE(QFileInfo(linkPath).symLinkTarget(), sourceFile); 2714 } 2715 2716 void JobTest::createSymlinkTargetDirDoesntExist() 2717 { 2718 #ifdef Q_OS_WIN 2719 QSKIP("Test skipped on Windows"); 2720 #endif 2721 const QString sourceFile = homeTmpDir() + "fileFromHome"; 2722 createTestFile(sourceFile); 2723 const QString destDir = homeTmpDir() + "dest/does/not/exist"; 2724 2725 KIO::CopyJob *job = KIO::link(QUrl::fromLocalFile(sourceFile), QUrl::fromLocalFile(destDir), KIO::HideProgressInfo); 2726 QVERIFY(!job->exec()); 2727 QCOMPARE(job->error(), static_cast<int>(KIO::ERR_CANNOT_SYMLINK)); 2728 } 2729 2730 void JobTest::createSymlinkAsShouldSucceed() 2731 { 2732 #ifdef Q_OS_WIN 2733 QSKIP("Test skipped on Windows"); 2734 #endif 2735 const QString sourceFile = homeTmpDir() + "fileFromHome"; 2736 createTestFile(sourceFile); 2737 const QString dest = homeTmpDir() + "testlink"; 2738 QFile::remove(dest); // just in case 2739 2740 ScopedCleaner cleaner([&] { 2741 QVERIFY(QFile::remove(dest)); 2742 }); 2743 2744 KIO::CopyJob *job = KIO::linkAs(QUrl::fromLocalFile(sourceFile), QUrl::fromLocalFile(dest), KIO::HideProgressInfo); 2745 QVERIFY2(job->exec(), qPrintable(job->errorString())); 2746 QVERIFY(QFileInfo::exists(sourceFile)); 2747 QVERIFY(QFileInfo(dest).isSymLink()); 2748 } 2749 2750 void JobTest::createSymlinkAsShouldFailDirectoryExists() 2751 { 2752 #ifdef Q_OS_WIN 2753 QSKIP("Test skipped on Windows"); 2754 #endif 2755 const QString sourceFile = homeTmpDir() + "fileFromHome"; 2756 createTestFile(sourceFile); 2757 const QString dest = homeTmpDir() + "dest"; 2758 QVERIFY(QDir().mkpath(dest)); // dest exists as a directory 2759 2760 ScopedCleaner cleaner([&] { 2761 QVERIFY(QDir().rmdir(dest)); 2762 }); 2763 2764 // With KIO::link (high-level) 2765 KIO::CopyJob *job = KIO::linkAs(QUrl::fromLocalFile(sourceFile), QUrl::fromLocalFile(dest), KIO::HideProgressInfo); 2766 QVERIFY(!job->exec()); 2767 QCOMPARE(job->error(), (int)KIO::ERR_DIR_ALREADY_EXIST); 2768 QVERIFY(QFileInfo::exists(sourceFile)); 2769 QVERIFY(!QFileInfo::exists(dest + "/fileFromHome")); 2770 2771 // With KIO::symlink (low-level) 2772 KIO::Job *symlinkJob = KIO::symlink(sourceFile, QUrl::fromLocalFile(dest), KIO::HideProgressInfo); 2773 QVERIFY(!symlinkJob->exec()); 2774 QCOMPARE(symlinkJob->error(), (int)KIO::ERR_DIR_ALREADY_EXIST); 2775 QVERIFY(QFileInfo::exists(sourceFile)); 2776 } 2777 2778 void JobTest::createSymlinkAsShouldFailFileExists() 2779 { 2780 #ifdef Q_OS_WIN 2781 QSKIP("Test skipped on Windows"); 2782 #endif 2783 const QString sourceFile = homeTmpDir() + "fileFromHome"; 2784 createTestFile(sourceFile); 2785 const QString dest = homeTmpDir() + "testlink"; 2786 QFile::remove(dest); // just in case 2787 2788 ScopedCleaner cleaner([&] { 2789 QVERIFY(QFile::remove(sourceFile)); 2790 QVERIFY(QFile::remove(dest)); 2791 }); 2792 2793 // First time works 2794 KIO::CopyJob *job = KIO::linkAs(QUrl::fromLocalFile(sourceFile), QUrl::fromLocalFile(dest), KIO::HideProgressInfo); 2795 QVERIFY2(job->exec(), qPrintable(job->errorString())); 2796 QVERIFY(QFileInfo(dest).isSymLink()); 2797 2798 // Second time fails (already exists) 2799 job = KIO::linkAs(QUrl::fromLocalFile(sourceFile), QUrl::fromLocalFile(dest), KIO::HideProgressInfo); 2800 QVERIFY(!job->exec()); 2801 QCOMPARE(job->error(), (int)KIO::ERR_FILE_ALREADY_EXIST); 2802 2803 // KIO::symlink fails too 2804 KIO::Job *symlinkJob = KIO::symlink(sourceFile, QUrl::fromLocalFile(dest), KIO::HideProgressInfo); 2805 QVERIFY(!symlinkJob->exec()); 2806 QCOMPARE(symlinkJob->error(), (int)KIO::ERR_FILE_ALREADY_EXIST); 2807 } 2808 2809 void JobTest::createSymlinkWithOverwriteShouldWork() 2810 { 2811 #ifdef Q_OS_WIN 2812 QSKIP("Test skipped on Windows"); 2813 #endif 2814 const QString sourceFile = homeTmpDir() + "fileFromHome"; 2815 createTestFile(sourceFile); 2816 const QString dest = homeTmpDir() + "testlink"; 2817 QFile::remove(dest); // just in case 2818 2819 ScopedCleaner cleaner([&] { 2820 QVERIFY(QFile::remove(sourceFile)); 2821 QVERIFY(QFile::remove(dest)); 2822 }); 2823 2824 // First time works 2825 KIO::CopyJob *job = KIO::linkAs(QUrl::fromLocalFile(sourceFile), QUrl::fromLocalFile(dest), KIO::HideProgressInfo); 2826 QVERIFY2(job->exec(), qPrintable(job->errorString())); 2827 QVERIFY(QFileInfo(dest).isSymLink()); 2828 2829 // Changing the link target, with overwrite, works 2830 job = KIO::linkAs(QUrl::fromLocalFile(sourceFile + QLatin1Char('2')), QUrl::fromLocalFile(dest), KIO::Overwrite | KIO::HideProgressInfo); 2831 QVERIFY2(job->exec(), qPrintable(job->errorString())); 2832 QVERIFY(QFileInfo(dest).isSymLink()); 2833 QCOMPARE(QFileInfo(dest).symLinkTarget(), QString(sourceFile + QLatin1Char('2'))); 2834 2835 // Changing the link target using KIO::symlink, with overwrite, works 2836 KIO::Job *symlinkJob = KIO::symlink(sourceFile + QLatin1Char('3'), QUrl::fromLocalFile(dest), KIO::Overwrite | KIO::HideProgressInfo); 2837 QVERIFY2(symlinkJob->exec(), qPrintable(symlinkJob->errorString())); 2838 QVERIFY(QFileInfo(dest).isSymLink()); 2839 QCOMPARE(QFileInfo(dest).symLinkTarget(), QString(sourceFile + QLatin1Char('3'))); 2840 } 2841 2842 void JobTest::createBrokenSymlink() 2843 { 2844 #ifdef Q_OS_WIN 2845 QSKIP("Test skipped on Windows"); 2846 #endif 2847 const QString sourceFile = "/does/not/exist"; 2848 const QString dest = homeTmpDir() + "testlink"; 2849 QFile::remove(dest); // just in case 2850 KIO::CopyJob *job = KIO::linkAs(QUrl::fromLocalFile(sourceFile), QUrl::fromLocalFile(dest), KIO::HideProgressInfo); 2851 QVERIFY2(job->exec(), qPrintable(job->errorString())); 2852 QVERIFY(QFileInfo(dest).isSymLink()); 2853 2854 // Second time fails (already exists) 2855 job = KIO::linkAs(QUrl::fromLocalFile(sourceFile), QUrl::fromLocalFile(dest), KIO::HideProgressInfo); 2856 QVERIFY(!job->exec()); 2857 QCOMPARE(job->error(), (int)KIO::ERR_FILE_ALREADY_EXIST); 2858 QVERIFY(QFile::remove(dest)); 2859 } 2860 2861 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 84) 2862 void JobTest::multiGet() 2863 { 2864 const int numFiles = 10; 2865 const QString baseDir = homeTmpDir(); 2866 const QList<QUrl> urls = createManyFiles(baseDir, numFiles); 2867 QCOMPARE(urls.count(), numFiles); 2868 2869 // qDebug() << file; 2870 KIO::MultiGetJob *job = KIO::multi_get(0, urls.at(0), KIO::MetaData()); // TODO: missing KIO::HideProgressInfo 2871 2872 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 79) 2873 QSignalSpy spyData(job, &KIO::MultiGetJob::data); 2874 #else 2875 QSignalSpy spyData(job, &KIO::MultiGetJob::dataReceived); 2876 #endif 2877 2878 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 78) 2879 QSignalSpy spyMimeType(job, &KIO::MultiGetJob::mimetype); 2880 #endif 2881 QSignalSpy spyMimeTypeFound(job, &KIO::MultiGetJob::mimeTypeFound); 2882 2883 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 79) 2884 QSignalSpy spyResultId(job, qOverload<long>(&KIO::MultiGetJob::result)); 2885 #else 2886 QSignalSpy spyResultId(job, &KIO::MultiGetJob::fileTransferred); 2887 #endif 2888 2889 QSignalSpy spyResult(job, &KJob::result); 2890 job->setUiDelegate(nullptr); 2891 2892 for (int i = 1; i < numFiles; ++i) { 2893 const QUrl url = urls.at(i); 2894 job->get(i, url, KIO::MetaData()); 2895 } 2896 // connect(job, &KIO::MultiGetJob::result, [=] (long id) { qDebug() << "ID I got" << id;}); 2897 // connect(job, &KJob::result, [this](KJob* ) {qDebug() << "END";}); 2898 2899 QVERIFY2(job->exec(), qPrintable(job->errorString())); 2900 2901 QCOMPARE(spyResult.count(), 1); 2902 QCOMPARE(spyResultId.count(), numFiles); 2903 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 78) 2904 QCOMPARE(spyMimeType.count(), numFiles); 2905 #endif 2906 QCOMPARE(spyMimeTypeFound.count(), numFiles); 2907 QCOMPARE(spyData.count(), numFiles * 2); 2908 for (int i = 0; i < numFiles; ++i) { 2909 QCOMPARE(spyResultId.at(i).at(0).toInt(), i); 2910 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 78) 2911 QCOMPARE(spyMimeType.at(i).at(0).toInt(), i); 2912 QCOMPARE(spyMimeType.at(i).at(1).toString(), QStringLiteral("text/plain")); 2913 #endif 2914 QCOMPARE(spyMimeTypeFound.at(i).at(0).toInt(), i); 2915 QCOMPARE(spyMimeTypeFound.at(i).at(1).toString(), QStringLiteral("text/plain")); 2916 QCOMPARE(spyData.at(i * 2).at(0).toInt(), i); 2917 QCOMPARE(QString(spyData.at(i * 2).at(1).toByteArray()), QStringLiteral("Hello")); 2918 QCOMPARE(spyData.at(i * 2 + 1).at(0).toInt(), i); 2919 QCOMPARE(QString(spyData.at(i * 2 + 1).at(1).toByteArray()), QLatin1String("")); 2920 } 2921 } 2922 #endif // Deprecated since 5.84 2923 2924 void JobTest::cancelCopyAndCleanDest_data() 2925 { 2926 QTest::addColumn<bool>("suspend"); 2927 QTest::addColumn<bool>("overwrite"); 2928 2929 QTest::newRow("suspend_no_overwrite") << true << false; 2930 QTest::newRow("no_suspend_no_overwrite") << false << false; 2931 2932 #ifndef Q_OS_WIN 2933 QTest::newRow("suspend_with_overwrite") << true << true; 2934 QTest::newRow("no_suspend_with_overwrite") << false << true; 2935 #endif 2936 } 2937 2938 void JobTest::cancelCopyAndCleanDest() 2939 { 2940 QFETCH(bool, suspend); 2941 QFETCH(bool, overwrite); 2942 2943 const QString baseDir = homeTmpDir(); 2944 const QString srcTemplate = baseDir + QStringLiteral("testfile_XXXXXX"); 2945 const QString destFile = baseDir + QStringLiteral("testfile_copy_slow_") + QString::fromLatin1(QTest::currentDataTag()); 2946 2947 QTemporaryFile f(srcTemplate); 2948 if (!f.open()) { 2949 qFatal("Couldn't open %s", qPrintable(f.fileName())); 2950 } 2951 2952 const int sz = 4000000; //~4MB 2953 f.seek(sz - 1); 2954 f.write("0"); 2955 f.close(); 2956 QCOMPARE(f.size(), sz); 2957 2958 if (overwrite) { 2959 createTestFile(destFile); 2960 } 2961 const QString destToCheck = (overwrite) ? destFile + QStringLiteral(".part") : destFile; 2962 2963 KIO::JobFlag m_overwriteFlag = overwrite ? KIO::Overwrite : KIO::DefaultFlags; 2964 KIO::FileCopyJob *copyJob = KIO::file_copy(QUrl::fromLocalFile(f.fileName()), QUrl::fromLocalFile(destFile), -1, KIO::HideProgressInfo | m_overwriteFlag); 2965 copyJob->setUiDelegate(nullptr); 2966 QSignalSpy spyProcessedSize(copyJob, &KIO::Job::processedSize); 2967 QSignalSpy spyFinished(copyJob, &KIO::Job::finished); 2968 connect(copyJob, &KIO::Job::processedSize, this, [destFile, suspend, destToCheck](KJob *job, qulonglong processedSize) { 2969 if (processedSize > 0) { 2970 QVERIFY2(QFile::exists(destToCheck), qPrintable(destToCheck)); 2971 qDebug() << "processedSize=" << processedSize << "file size" << QFileInfo(destToCheck).size(); 2972 if (suspend) { 2973 job->suspend(); 2974 } 2975 QVERIFY(job->kill()); 2976 } 2977 }); 2978 2979 QVERIFY(!copyJob->exec()); 2980 QCOMPARE(spyProcessedSize.count(), 1); 2981 QCOMPARE(spyFinished.count(), 1); 2982 QCOMPARE(copyJob->error(), KIO::ERR_USER_CANCELED); 2983 2984 // the destination file actual deletion happens after finished() is emitted 2985 // we need to give some time to the KIO worker to finish the file cleaning 2986 QTRY_VERIFY2(!QFile::exists(destToCheck), qPrintable(destToCheck)); 2987 } 2988 2989 #include "moc_jobtest.cpp"