File indexing completed on 2024-04-21 03:53:39
0001 /* 0002 This file is part of the KDE project 0003 SPDX-FileCopyrightText: 2006 Kevin Ottens <ervin@kde.org> 0004 0005 SPDX-License-Identifier: LGPL-2.0-only 0006 */ 0007 0008 #include "kjobtest.h" 0009 0010 #include <QList> 0011 #include <QMetaEnum> 0012 #include <QSignalSpy> 0013 #include <QTest> 0014 #include <QTimer> 0015 0016 #include <string> 0017 0018 QTEST_MAIN(KJobTest) 0019 0020 KJobTest::KJobTest() 0021 : loop(this) 0022 { 0023 } 0024 0025 void KJobTest::testEmitResult_data() 0026 { 0027 QTest::addColumn<int>("errorCode"); 0028 QTest::addColumn<QString>("errorText"); 0029 0030 QTest::newRow("no error") << int(KJob::NoError) << QString(); 0031 QTest::newRow("error no text") << 2 << QString(); 0032 QTest::newRow("error with text") << 6 << "oops! an error? naaah, really?"; 0033 } 0034 0035 void KJobTest::testEmitResult() 0036 { 0037 TestJob *job = new TestJob; 0038 0039 connect(job, &KJob::result, this, [this](KJob *job) { 0040 slotResult(job); 0041 loop.quit(); 0042 }); 0043 0044 QFETCH(int, errorCode); 0045 QFETCH(QString, errorText); 0046 0047 job->setError(errorCode); 0048 job->setErrorText(errorText); 0049 0050 QSignalSpy destroyed_spy(job, &QObject::destroyed); 0051 job->start(); 0052 QVERIFY(!job->isFinished()); 0053 loop.exec(); 0054 QVERIFY(job->isFinished()); 0055 0056 QCOMPARE(m_lastError, errorCode); 0057 QCOMPARE(m_lastErrorText, errorText); 0058 0059 // Verify that the job is not deleted immediately... 0060 QCOMPARE(destroyed_spy.size(), 0); 0061 QTimer::singleShot(0, &loop, &QEventLoop::quit); 0062 // ... but when we enter the event loop again. 0063 loop.exec(); 0064 QCOMPARE(destroyed_spy.size(), 1); 0065 } 0066 0067 void KJobTest::testProgressTracking() 0068 { 0069 TestJob *testJob = new TestJob; 0070 KJob *job = testJob; 0071 0072 qRegisterMetaType<KJob *>("KJob*"); 0073 qRegisterMetaType<qulonglong>("qulonglong"); 0074 0075 QSignalSpy processedChanged_spy(job, &KJob::processedAmountChanged); 0076 QSignalSpy totalChanged_spy(job, &KJob::totalAmountChanged); 0077 QSignalSpy percentChanged_spy(job, &KJob::percentChanged); 0078 0079 /* Process a first item. Corresponding signal should be emitted. 0080 * Total size didn't change. 0081 * Since the total size is unknown, no percent signal is emitted. 0082 */ 0083 testJob->setProcessedSize(1); 0084 0085 QCOMPARE(processedChanged_spy.size(), 1); 0086 QCOMPARE(processedChanged_spy.at(0).at(0).value<KJob *>(), static_cast<KJob *>(job)); 0087 QCOMPARE(processedChanged_spy.at(0).at(2).value<qulonglong>(), qulonglong(1)); 0088 QCOMPARE(totalChanged_spy.size(), 0); 0089 QCOMPARE(percentChanged_spy.size(), 0); 0090 0091 /* Now, we know the total size. It's signaled. 0092 * The new percentage is signaled too. 0093 */ 0094 testJob->setTotalSize(10); 0095 0096 QCOMPARE(processedChanged_spy.size(), 1); 0097 QCOMPARE(totalChanged_spy.size(), 1); 0098 QCOMPARE(totalChanged_spy.at(0).at(0).value<KJob *>(), job); 0099 QCOMPARE(totalChanged_spy.at(0).at(2).value<qulonglong>(), qulonglong(10)); 0100 QCOMPARE(percentChanged_spy.size(), 1); 0101 QCOMPARE(percentChanged_spy.at(0).at(0).value<KJob *>(), job); 0102 QCOMPARE(percentChanged_spy.at(0).at(1).value<unsigned long>(), static_cast<unsigned long>(10)); 0103 0104 /* We announce a new percentage by hand. 0105 * Total, and processed didn't change, so no signal is emitted for them. 0106 */ 0107 testJob->setPercent(15); 0108 0109 QCOMPARE(processedChanged_spy.size(), 1); 0110 QCOMPARE(totalChanged_spy.size(), 1); 0111 QCOMPARE(percentChanged_spy.size(), 2); 0112 QCOMPARE(percentChanged_spy.at(1).at(0).value<KJob *>(), job); 0113 QCOMPARE(percentChanged_spy.at(1).at(1).value<unsigned long>(), static_cast<unsigned long>(15)); 0114 0115 /* We make some progress. 0116 * Processed size and percent are signaled. 0117 */ 0118 testJob->setProcessedSize(3); 0119 0120 QCOMPARE(processedChanged_spy.size(), 2); 0121 QCOMPARE(processedChanged_spy.at(1).at(0).value<KJob *>(), job); 0122 QCOMPARE(processedChanged_spy.at(1).at(2).value<qulonglong>(), qulonglong(3)); 0123 QCOMPARE(totalChanged_spy.size(), 1); 0124 QCOMPARE(percentChanged_spy.size(), 3); 0125 QCOMPARE(percentChanged_spy.at(2).at(0).value<KJob *>(), job); 0126 QCOMPARE(percentChanged_spy.at(2).at(1).value<unsigned long>(), static_cast<unsigned long>(30)); 0127 0128 /* We set a new total size, but equals to the previous one. 0129 * No signal is emitted. 0130 */ 0131 testJob->setTotalSize(10); 0132 QCOMPARE(processedChanged_spy.size(), 2); 0133 QCOMPARE(totalChanged_spy.size(), 1); 0134 QCOMPARE(percentChanged_spy.size(), 3); 0135 0136 /* We 'lost' the previous work done. 0137 * Signals both percentage and new processed size. 0138 */ 0139 testJob->setProcessedSize(0); 0140 0141 QCOMPARE(processedChanged_spy.size(), 3); 0142 QCOMPARE(processedChanged_spy.at(2).at(0).value<KJob *>(), job); 0143 QCOMPARE(processedChanged_spy.at(2).at(2).value<qulonglong>(), qulonglong(0)); 0144 QCOMPARE(totalChanged_spy.size(), 1); 0145 QCOMPARE(percentChanged_spy.size(), 4); 0146 QCOMPARE(percentChanged_spy.at(3).at(0).value<KJob *>(), job); 0147 QCOMPARE(percentChanged_spy.at(3).at(1).value<unsigned long>(), static_cast<unsigned long>(0)); 0148 0149 /* We process more than the total size!? 0150 * Signals both percentage and new processed size. 0151 * Percentage is 150% 0152 * 0153 * Might sounds weird, but verify that this case is handled gracefully. 0154 */ 0155 testJob->setProcessedSize(15); 0156 0157 QCOMPARE(processedChanged_spy.size(), 4); 0158 QCOMPARE(processedChanged_spy.at(3).at(0).value<KJob *>(), job); 0159 QCOMPARE(processedChanged_spy.at(3).at(2).value<qulonglong>(), qulonglong(15)); 0160 QCOMPARE(totalChanged_spy.size(), 1); 0161 QCOMPARE(percentChanged_spy.size(), 5); 0162 QCOMPARE(percentChanged_spy.at(4).at(0).value<KJob *>(), job); 0163 QCOMPARE(percentChanged_spy.at(4).at(1).value<unsigned long>(), static_cast<unsigned long>(150)); 0164 0165 processedChanged_spy.clear(); 0166 totalChanged_spy.clear(); 0167 percentChanged_spy.clear(); 0168 0169 /** 0170 * Try again with Files as the progress unit 0171 */ 0172 testJob->setProgressUnit(KJob::Files); 0173 testJob->setProcessedSize(16); 0174 QCOMPARE(percentChanged_spy.size(), 0); 0175 0176 testJob->setTotalFiles(5); 0177 QCOMPARE(percentChanged_spy.size(), 1); 0178 QCOMPARE(percentChanged_spy.at(0).at(1).value<unsigned long>(), static_cast<unsigned long>(0)); 0179 0180 testJob->setProcessedFiles(2); 0181 QCOMPARE(percentChanged_spy.size(), 2); 0182 QCOMPARE(percentChanged_spy.at(1).at(1).value<unsigned long>(), static_cast<unsigned long>(40)); 0183 0184 delete job; 0185 } 0186 0187 void KJobTest::testExec_data() 0188 { 0189 QTest::addColumn<int>("errorCode"); 0190 QTest::addColumn<QString>("errorText"); 0191 0192 QTest::newRow("no error") << int(KJob::NoError) << QString(); 0193 QTest::newRow("error no text") << 2 << QString(); 0194 QTest::newRow("error with text") << 6 << "oops! an error? naaah, really?"; 0195 } 0196 0197 void KJobTest::testExec() 0198 { 0199 TestJob *job = new TestJob; 0200 0201 QFETCH(int, errorCode); 0202 QFETCH(QString, errorText); 0203 0204 job->setError(errorCode); 0205 job->setErrorText(errorText); 0206 0207 int resultEmitted = 0; 0208 // Prove to Kai Uwe that one can connect a job to a lambdas, despite the "private" signal 0209 connect(job, &KJob::result, this, [&resultEmitted](KJob *) { 0210 ++resultEmitted; 0211 }); 0212 0213 QSignalSpy destroyed_spy(job, &QObject::destroyed); 0214 0215 QVERIFY(!job->isFinished()); 0216 bool status = job->exec(); 0217 QVERIFY(job->isFinished()); 0218 0219 QCOMPARE(resultEmitted, 1); 0220 QCOMPARE(status, (errorCode == KJob::NoError)); 0221 QCOMPARE(job->error(), errorCode); 0222 QCOMPARE(job->errorText(), errorText); 0223 0224 // Verify that the job is not deleted immediately... 0225 QCOMPARE(destroyed_spy.size(), 0); 0226 QTimer::singleShot(0, &loop, &QEventLoop::quit); 0227 // ... but when we enter the event loop again. 0228 loop.exec(); 0229 QCOMPARE(destroyed_spy.size(), 1); 0230 } 0231 0232 void KJobTest::testKill_data() 0233 { 0234 QTest::addColumn<int>("killVerbosity"); 0235 QTest::addColumn<int>("errorCode"); 0236 QTest::addColumn<QString>("errorText"); 0237 QTest::addColumn<int>("resultEmitCount"); 0238 QTest::addColumn<int>("finishedEmitCount"); 0239 0240 QTest::newRow("killed with result") << int(KJob::EmitResult) << int(KJob::KilledJobError) << QString() << 1 << 1; 0241 QTest::newRow("killed quietly") << int(KJob::Quietly) << int(KJob::KilledJobError) << QString() << 0 << 1; 0242 } 0243 0244 void KJobTest::testKill() 0245 { 0246 auto *const job = setupErrorResultFinished(); 0247 QSignalSpy destroyed_spy(job, &QObject::destroyed); 0248 0249 QFETCH(int, killVerbosity); 0250 QFETCH(int, errorCode); 0251 QFETCH(QString, errorText); 0252 QFETCH(int, resultEmitCount); 0253 QFETCH(int, finishedEmitCount); 0254 0255 QVERIFY(!job->isFinished()); 0256 job->kill(static_cast<KJob::KillVerbosity>(killVerbosity)); 0257 QVERIFY(job->isFinished()); 0258 loop.processEvents(QEventLoop::AllEvents, 2000); 0259 0260 QCOMPARE(m_lastError, errorCode); 0261 QCOMPARE(m_lastErrorText, errorText); 0262 0263 QCOMPARE(job->error(), errorCode); 0264 QCOMPARE(job->errorText(), errorText); 0265 0266 QCOMPARE(m_resultCount, resultEmitCount); 0267 QCOMPARE(m_finishedCount, finishedEmitCount); 0268 0269 // Verify that the job is not deleted immediately... 0270 QCOMPARE(destroyed_spy.size(), 0); 0271 QTimer::singleShot(0, &loop, &QEventLoop::quit); 0272 // ... but when we enter the event loop again. 0273 loop.exec(); 0274 QCOMPARE(destroyed_spy.size(), 1); 0275 0276 QVERIFY(m_jobFinishCount.size() == (finishedEmitCount - resultEmitCount)); 0277 m_jobFinishCount.clear(); 0278 } 0279 0280 void KJobTest::testDestroy() 0281 { 0282 auto *const job = setupErrorResultFinished(); 0283 QVERIFY(!job->isFinished()); 0284 delete job; 0285 QCOMPARE(m_lastError, static_cast<int>(KJob::NoError)); 0286 QCOMPARE(m_lastErrorText, QString{}); 0287 QCOMPARE(m_resultCount, 0); 0288 QCOMPARE(m_finishedCount, 1); 0289 0290 QVERIFY(m_jobFinishCount.size() == 1); 0291 m_jobFinishCount.clear(); 0292 } 0293 0294 void KJobTest::testEmitAtMostOnce_data() 0295 { 0296 QTest::addColumn<bool>("autoDelete"); 0297 QTest::addColumn<QList<Action>>("actions"); 0298 0299 const auto actionName = [](Action action) { 0300 return QMetaEnum::fromType<Action>().valueToKey(static_cast<int>(action)); 0301 }; 0302 0303 for (bool autoDelete : {true, false}) { 0304 for (Action a : {Action::Start, Action::KillQuietly, Action::KillVerbosely}) { 0305 for (Action b : {Action::Start, Action::KillQuietly, Action::KillVerbosely}) { 0306 const auto dataTag = std::string{actionName(a)} + '-' + actionName(b) + (autoDelete ? "-autoDelete" : ""); 0307 QTest::newRow(dataTag.c_str()) << autoDelete << QList<Action>{a, b}; 0308 } 0309 } 0310 } 0311 } 0312 0313 void KJobTest::testEmitAtMostOnce() 0314 { 0315 auto *const job = setupErrorResultFinished(); 0316 QSignalSpy destroyed_spy(job, &QObject::destroyed); 0317 0318 QFETCH(bool, autoDelete); 0319 job->setAutoDelete(autoDelete); 0320 0321 QFETCH(QList<Action>, actions); 0322 for (auto action : actions) { 0323 switch (action) { 0324 case Action::Start: 0325 job->start(); // in effect calls QTimer::singleShot(0, ... emitResult) 0326 break; 0327 case Action::KillQuietly: 0328 QTimer::singleShot(0, job, [=] { 0329 job->kill(KJob::Quietly); 0330 }); 0331 break; 0332 case Action::KillVerbosely: 0333 QTimer::singleShot(0, job, [=] { 0334 job->kill(KJob::EmitResult); 0335 }); 0336 break; 0337 } 0338 } 0339 0340 QVERIFY(!job->isFinished()); 0341 loop.processEvents(QEventLoop::AllEvents, 2000); 0342 QCOMPARE(destroyed_spy.size(), autoDelete); 0343 if (!autoDelete) { 0344 QVERIFY(job->isFinished()); 0345 } 0346 0347 QVERIFY(!actions.empty()); 0348 // The first action alone should determine the job's error and result. 0349 const auto firstAction = actions.front(); 0350 0351 const int errorCode = firstAction == Action::Start ? KJob::NoError : KJob::KilledJobError; 0352 QCOMPARE(m_lastError, errorCode); 0353 QCOMPARE(m_lastErrorText, QString{}); 0354 if (!autoDelete) { 0355 QCOMPARE(job->error(), m_lastError); 0356 QCOMPARE(job->errorText(), m_lastErrorText); 0357 } 0358 0359 QCOMPARE(m_resultCount, firstAction == Action::KillQuietly ? 0 : 1); 0360 QCOMPARE(m_finishedCount, 1); 0361 0362 if (!autoDelete) { 0363 delete job; 0364 } 0365 } 0366 0367 void KJobTest::testDelegateUsage() 0368 { 0369 TestJob *job1 = new TestJob; 0370 TestJob *job2 = new TestJob; 0371 TestJobUiDelegate *delegate = new TestJobUiDelegate; 0372 QPointer<TestJobUiDelegate> guard(delegate); 0373 0374 QVERIFY(job1->uiDelegate() == nullptr); 0375 job1->setUiDelegate(delegate); 0376 QVERIFY(job1->uiDelegate() == delegate); 0377 0378 QVERIFY(job2->uiDelegate() == nullptr); 0379 job2->setUiDelegate(delegate); 0380 QVERIFY(job2->uiDelegate() == nullptr); 0381 0382 delete job1; 0383 delete job2; 0384 QVERIFY(guard.isNull()); // deleted by job1 0385 } 0386 0387 void KJobTest::testNestedExec() 0388 { 0389 m_innerJob = nullptr; 0390 QTimer::singleShot(100, this, &KJobTest::slotStartInnerJob); 0391 m_outerJob = new WaitJob(); 0392 m_outerJob->exec(); 0393 } 0394 0395 void KJobTest::slotStartInnerJob() 0396 { 0397 QTimer::singleShot(100, this, &KJobTest::slotFinishOuterJob); 0398 m_innerJob = new WaitJob(); 0399 m_innerJob->exec(); 0400 } 0401 0402 void KJobTest::slotFinishOuterJob() 0403 { 0404 QTimer::singleShot(100, this, &KJobTest::slotFinishInnerJob); 0405 m_outerJob->makeItFinish(); 0406 } 0407 0408 void KJobTest::slotFinishInnerJob() 0409 { 0410 m_innerJob->makeItFinish(); 0411 } 0412 0413 void KJobTest::slotResult(KJob *job) 0414 { 0415 const auto testJob = qobject_cast<const TestJob *>(job); 0416 QVERIFY(testJob); 0417 QVERIFY(testJob->isFinished()); 0418 0419 // Ensure the job has already emitted finished() if we are tracking from 0420 // setupErrorResultFinished 0421 if (m_jobFinishCount.contains(job)) { 0422 QVERIFY(m_jobFinishCount.value(job) == 1); 0423 QVERIFY(m_jobFinishCount.remove(job) == 1 /* num items removed */); 0424 } 0425 0426 if (job->error()) { 0427 m_lastError = job->error(); 0428 m_lastErrorText = job->errorText(); 0429 } else { 0430 m_lastError = KJob::NoError; 0431 m_lastErrorText.clear(); 0432 } 0433 0434 m_resultCount++; 0435 } 0436 0437 void KJobTest::slotFinished(KJob *job) 0438 { 0439 QVERIFY2(m_jobFinishCount.value(job) == 0, "Ensure we have not double-emitted KJob::finished()"); 0440 m_jobFinishCount[job]++; 0441 0442 if (job->error()) { 0443 m_lastError = job->error(); 0444 m_lastErrorText = job->errorText(); 0445 } else { 0446 m_lastError = KJob::NoError; 0447 m_lastErrorText.clear(); 0448 } 0449 0450 m_finishedCount++; 0451 } 0452 0453 TestJob *KJobTest::setupErrorResultFinished() 0454 { 0455 m_lastError = KJob::UserDefinedError; 0456 m_lastErrorText.clear(); 0457 m_resultCount = 0; 0458 m_finishedCount = 0; 0459 0460 auto *job = new TestJob; 0461 m_jobFinishCount[job] = 0; 0462 connect(job, &KJob::result, this, &KJobTest::slotResult); 0463 connect(job, &KJob::finished, this, &KJobTest::slotFinished); 0464 return job; 0465 } 0466 0467 TestJob::TestJob() 0468 : KJob() 0469 { 0470 } 0471 0472 TestJob::~TestJob() 0473 { 0474 } 0475 0476 void TestJob::start() 0477 { 0478 QTimer::singleShot(0, this, [this] { 0479 emitResult(); 0480 }); 0481 } 0482 0483 bool TestJob::doKill() 0484 { 0485 return true; 0486 } 0487 0488 void TestJob::setError(int errorCode) 0489 { 0490 KJob::setError(errorCode); 0491 } 0492 0493 void TestJob::setErrorText(const QString &errorText) 0494 { 0495 KJob::setErrorText(errorText); 0496 } 0497 0498 void TestJob::setProcessedSize(qulonglong size) 0499 { 0500 KJob::setProcessedAmount(KJob::Bytes, size); 0501 } 0502 0503 void TestJob::setTotalSize(qulonglong size) 0504 { 0505 KJob::setTotalAmount(KJob::Bytes, size); 0506 } 0507 0508 void TestJob::setProcessedFiles(qulonglong files) 0509 { 0510 KJob::setProcessedAmount(KJob::Files, files); 0511 } 0512 0513 void TestJob::setTotalFiles(qulonglong files) 0514 { 0515 KJob::setTotalAmount(KJob::Files, files); 0516 } 0517 0518 void TestJob::setPercent(unsigned long percentage) 0519 { 0520 KJob::setPercent(percentage); 0521 } 0522 0523 void WaitJob::start() 0524 { 0525 } 0526 0527 void WaitJob::makeItFinish() 0528 { 0529 emitResult(); 0530 } 0531 0532 void TestJobUiDelegate::connectJob(KJob *job) 0533 { 0534 QVERIFY(job->uiDelegate() != nullptr); 0535 } 0536 0537 #include "moc_kjobtest.cpp"