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