File indexing completed on 2025-01-19 12:48:38
0001 /* 0002 SPDX-FileCopyrightText: 2004 David Faure <faure@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.0-only 0005 */ 0006 0007 #include <KUser> 0008 #include <QApplication> 0009 #include <QDebug> 0010 #include <QDir> 0011 #include <QFile> 0012 #include <QTemporaryDir> 0013 #include <QTest> 0014 #include <QThread> 0015 #include <kurlcompletion.h> 0016 #include <qplatformdefs.h> 0017 0018 class KUrlCompletionTest : public QObject 0019 { 0020 Q_OBJECT 0021 private Q_SLOTS: 0022 void test(); 0023 0024 public: 0025 KUrlCompletionTest() 0026 { 0027 #ifdef NO_WAIT // kurlcompletiontest-nowait sets this, to test what happens on slower systems (or systems with many dirs or users) 0028 qputenv("KURLCOMPLETION_WAIT", "1"); // 1ms, too short for a full listing of /usr/bin, but at least give a chance for a few items in the result 0029 #endif 0030 } 0031 ~KUrlCompletionTest() override 0032 { 0033 teardown(); 0034 } 0035 void runAllTests(); 0036 void setup(); 0037 void teardown(); 0038 void testLocalRelativePath(); 0039 void testLocalAbsolutePath(); 0040 void testLocalURL(); 0041 void testEmptyCwd(); 0042 void testBug346920(); 0043 void testInvalidProtocol(); 0044 void testUser(); 0045 void testCancel(); 0046 0047 // remember to register new test methods in runAllTests 0048 0049 private: 0050 void waitForCompletion(KUrlCompletion *completion); 0051 KUrlCompletion *m_completion; 0052 KUrlCompletion *m_completionWithMimeFilter; 0053 QTemporaryDir *m_tempDir; 0054 QUrl m_dirURL; 0055 QString m_dir; 0056 KUrlCompletion *m_completionEmptyCwd; 0057 }; 0058 0059 void KUrlCompletionTest::setup() 0060 { 0061 qDebug(); 0062 m_completion = new KUrlCompletion; 0063 m_completionWithMimeFilter = new KUrlCompletion; 0064 m_completionWithMimeFilter->setMimeTypeFilters({QStringLiteral("text/x-c++src")}); 0065 m_tempDir = new QTemporaryDir; 0066 m_dir = m_tempDir->path(); 0067 m_dir += QLatin1String("/Dir With#Spaces/"); 0068 QDir().mkdir(m_dir); 0069 qDebug() << "m_dir=" << m_dir; 0070 m_completion->setDir(QUrl::fromLocalFile(m_dir)); 0071 m_completionWithMimeFilter->setDir(m_completion->dir()); 0072 m_dirURL = QUrl::fromLocalFile(m_dir); 0073 0074 QFile f1(m_dir + QStringLiteral("/file1")); 0075 bool ok = f1.open(QIODevice::WriteOnly); 0076 QVERIFY(ok); 0077 f1.close(); 0078 0079 QFile f2(m_dir + QStringLiteral("/file#a")); 0080 ok = f2.open(QIODevice::WriteOnly); 0081 QVERIFY(ok); 0082 f2.close(); 0083 0084 QFile f3(m_dir + QStringLiteral("/file.")); 0085 ok = f3.open(QIODevice::WriteOnly); 0086 QVERIFY(ok); 0087 f3.close(); 0088 0089 QFile f4(m_dir + QStringLiteral("/source.cpp")); 0090 ok = f4.open(QIODevice::WriteOnly); 0091 QVERIFY(ok); 0092 f4.close(); 0093 0094 QFile f5(m_dir + QStringLiteral("/source.php")); 0095 ok = f5.open(QIODevice::WriteOnly); 0096 QVERIFY(ok); 0097 f5.close(); 0098 0099 QDir().mkdir(m_dir + QStringLiteral("/file_subdir")); 0100 QDir().mkdir(m_dir + QStringLiteral("/.1_hidden_file_subdir")); 0101 QDir().mkdir(m_dir + QStringLiteral("/.2_hidden_file_subdir")); 0102 0103 m_completionEmptyCwd = new KUrlCompletion; 0104 m_completionEmptyCwd->setDir(QUrl()); 0105 } 0106 0107 void KUrlCompletionTest::teardown() 0108 { 0109 delete m_completion; 0110 m_completion = nullptr; 0111 delete m_completionWithMimeFilter; 0112 m_completionWithMimeFilter = nullptr; 0113 delete m_tempDir; 0114 m_tempDir = nullptr; 0115 delete m_completionEmptyCwd; 0116 m_completionEmptyCwd = nullptr; 0117 } 0118 0119 void KUrlCompletionTest::waitForCompletion(KUrlCompletion *completion) 0120 { 0121 while (completion->isRunning()) { 0122 qDebug() << "waiting for thread..."; 0123 QTest::qWait(5); 0124 } 0125 // The thread emitted a signal, process it. 0126 qApp->sendPostedEvents(nullptr, QEvent::MetaCall); 0127 } 0128 0129 void KUrlCompletionTest::testLocalRelativePath() 0130 { 0131 qDebug(); 0132 // Completion from relative path, with two matches 0133 m_completion->makeCompletion(QStringLiteral("f")); 0134 waitForCompletion(m_completion); 0135 QStringList comp1all = m_completion->allMatches(); 0136 qDebug() << comp1all; 0137 QCOMPARE(comp1all.count(), 4); 0138 QVERIFY(comp1all.contains(QLatin1String("file1"))); 0139 QVERIFY(comp1all.contains(QLatin1String("file#a"))); 0140 QVERIFY(comp1all.contains(QLatin1String("file."))); 0141 QVERIFY(comp1all.contains(QLatin1String("file_subdir/"))); 0142 QString comp1 = m_completion->replacedPath(QStringLiteral("file1")); // like KUrlRequester does 0143 QCOMPARE(comp1, QStringLiteral("file1")); 0144 0145 // Completion from relative path 0146 qDebug() << "\nnow completing on 'file#'"; 0147 m_completion->makeCompletion(QStringLiteral("file#")); 0148 QVERIFY(!m_completion->isRunning()); // last listing reused 0149 QStringList compall = m_completion->allMatches(); 0150 qDebug() << compall; 0151 QCOMPARE(compall.count(), 1); 0152 QCOMPARE(compall.first(), QStringLiteral("file#a")); 0153 QString comp2 = m_completion->replacedPath(compall.first()); // like KUrlRequester does 0154 QCOMPARE(comp2, QStringLiteral("file#a")); 0155 0156 // Completion with empty string 0157 qDebug() << "\nnow completing on ''"; 0158 m_completion->makeCompletion(QLatin1String("")); 0159 waitForCompletion(m_completion); 0160 QStringList compEmpty = m_completion->allMatches(); 0161 QCOMPARE(compEmpty.count(), 0); 0162 0163 m_completion->makeCompletion(QStringLiteral(".")); 0164 waitForCompletion(m_completion); 0165 const auto compAllHidden = m_completion->allMatches(); 0166 QCOMPARE(compAllHidden.count(), 2); 0167 QVERIFY(compAllHidden.contains(QLatin1String(".1_hidden_file_subdir/"))); 0168 QVERIFY(compAllHidden.contains(QLatin1String(".2_hidden_file_subdir/"))); 0169 0170 // Completion with '.2', should find only hidden folders starting with '2' 0171 m_completion->makeCompletion(QStringLiteral(".2")); 0172 waitForCompletion(m_completion); 0173 const auto compHiddenStartingWith2 = m_completion->allMatches(); 0174 QCOMPARE(compHiddenStartingWith2.count(), 1); 0175 QVERIFY(compHiddenStartingWith2.contains(QLatin1String(".2_hidden_file_subdir/"))); 0176 0177 // Completion with 'file.', should only find one file 0178 m_completion->makeCompletion(QStringLiteral("file.")); 0179 waitForCompletion(m_completion); 0180 const auto compFileEndingWithDot = m_completion->allMatches(); 0181 QCOMPARE(compFileEndingWithDot.count(), 1); 0182 QVERIFY(compFileEndingWithDot.contains(QLatin1String("file."))); 0183 0184 // Completion with 'source' should only find the C++ file 0185 m_completionWithMimeFilter->makeCompletion(QStringLiteral("source")); 0186 waitForCompletion(m_completionWithMimeFilter); 0187 const auto compSourceFile = m_completionWithMimeFilter->allMatches(); 0188 QCOMPARE(compSourceFile.count(), 1); 0189 QVERIFY(compSourceFile.contains(QLatin1String("source.cpp"))); 0190 0191 // But it should also be able to find folders 0192 m_completionWithMimeFilter->makeCompletion(QStringLiteral("file_subdir")); 0193 waitForCompletion(m_completionWithMimeFilter); 0194 const auto compMimeFolder = m_completionWithMimeFilter->allMatches(); 0195 QCOMPARE(compMimeFolder.count(), 1); 0196 QVERIFY(compMimeFolder.contains(QLatin1String("file_subdir/"))); 0197 } 0198 0199 void KUrlCompletionTest::testLocalAbsolutePath() 0200 { 0201 // Completion from absolute path 0202 qDebug() << m_dir + "file#"; 0203 m_completion->makeCompletion(m_dir + "file#"); 0204 waitForCompletion(m_completion); 0205 QStringList compall = m_completion->allMatches(); 0206 qDebug() << compall; 0207 QCOMPARE(compall.count(), 1); 0208 QString comp = compall.first(); 0209 QCOMPARE(comp, QString(m_dir + "file#a")); 0210 comp = m_completion->replacedPath(comp); // like KUrlRequester does 0211 QCOMPARE(comp, QString(m_dir + "file#a")); 0212 0213 // Completion with '.', should find all hidden folders 0214 m_completion->makeCompletion(m_dir + QLatin1Char('.')); 0215 waitForCompletion(m_completion); 0216 const auto compAllHidden = m_completion->allMatches(); 0217 QCOMPARE(compAllHidden.count(), 2); 0218 QVERIFY(compAllHidden.contains(m_dir + ".1_hidden_file_subdir/")); 0219 QVERIFY(compAllHidden.contains(m_dir + ".2_hidden_file_subdir/")); 0220 0221 // Completion with '.2', should find only hidden folders starting with '2' 0222 m_completion->makeCompletion(m_dir + ".2"); 0223 waitForCompletion(m_completion); 0224 const auto compHiddenStartingWith2 = m_completion->allMatches(); 0225 QCOMPARE(compHiddenStartingWith2.count(), 1); 0226 QVERIFY(compHiddenStartingWith2.contains(m_dir + ".2_hidden_file_subdir/")); 0227 0228 // Completion with 'file.', should only find one file 0229 m_completion->makeCompletion(m_dir + "file."); 0230 waitForCompletion(m_completion); 0231 const auto compFileEndingWithDot = m_completion->allMatches(); 0232 QCOMPARE(compFileEndingWithDot.count(), 1); 0233 QVERIFY(compFileEndingWithDot.contains(m_dir + "file.")); 0234 0235 // Completion with 'source' should only find the C++ file 0236 m_completionWithMimeFilter->makeCompletion(m_dir + "source"); 0237 waitForCompletion(m_completionWithMimeFilter); 0238 const auto compSourceFile = m_completionWithMimeFilter->allMatches(); 0239 QCOMPARE(compSourceFile.count(), 1); 0240 QVERIFY(compSourceFile.contains(m_dir + "source.cpp")); 0241 0242 // But it should also be able to find folders 0243 m_completionWithMimeFilter->makeCompletion(m_dir + "file_subdir"); 0244 waitForCompletion(m_completionWithMimeFilter); 0245 const auto compMimeFolder = m_completionWithMimeFilter->allMatches(); 0246 QCOMPARE(compMimeFolder.count(), 1); 0247 QVERIFY(compMimeFolder.contains(m_dir + "file_subdir/")); 0248 } 0249 0250 void KUrlCompletionTest::testLocalURL() 0251 { 0252 // Completion from URL 0253 qDebug(); 0254 QUrl url = QUrl::fromLocalFile(m_dirURL.toLocalFile() + "file"); 0255 m_completion->makeCompletion(url.toString()); 0256 waitForCompletion(m_completion); 0257 QStringList comp1all = m_completion->allMatches(); 0258 qDebug() << comp1all; 0259 QCOMPARE(comp1all.count(), 4); 0260 qDebug() << "Looking for" << m_dirURL.toString() + "file1"; 0261 QVERIFY(comp1all.contains(m_dirURL.toString() + "file1")); 0262 qDebug() << "Looking for" << m_dirURL.toString() + "file."; 0263 QVERIFY(comp1all.contains(m_dirURL.toString() + "file.")); 0264 QVERIFY(comp1all.contains(m_dirURL.toString() + "file_subdir/")); 0265 QString filehash = m_dirURL.toString() + "file%23a"; 0266 qDebug() << "Looking for" << filehash; 0267 QVERIFY(comp1all.contains(filehash)); 0268 QString filehashPath = m_completion->replacedPath(filehash); // note that it returns a path!! 0269 qDebug() << filehashPath; 0270 QCOMPARE(filehashPath, QString(m_dirURL.toLocalFile() + "file#a")); 0271 0272 // Completion from URL with no match 0273 url = QUrl::fromLocalFile(m_dirURL.toLocalFile() + "foobar"); 0274 qDebug() << "makeCompletion(" << url << ")"; 0275 QString comp2 = m_completion->makeCompletion(url.toString()); 0276 QVERIFY(comp2.isEmpty()); 0277 waitForCompletion(m_completion); 0278 QVERIFY(m_completion->allMatches().isEmpty()); 0279 0280 // Completion from URL with a ref -> no match 0281 url = QUrl::fromLocalFile(m_dirURL.toLocalFile() + 'f'); 0282 url.setFragment(QStringLiteral("ref")); 0283 qDebug() << "makeCompletion(" << url << ")"; 0284 m_completion->makeCompletion(url.toString()); 0285 waitForCompletion(m_completion); 0286 QVERIFY(m_completion->allMatches().isEmpty()); 0287 0288 // Completion with '.', should find all hidden folders 0289 qDebug() << "makeCompletion(" << (m_dirURL.toString() + QLatin1Char('.')) << ")"; 0290 m_completion->makeCompletion(m_dirURL.toString() + QLatin1Char('.')); 0291 waitForCompletion(m_completion); 0292 const auto compAllHidden = m_completion->allMatches(); 0293 QCOMPARE(compAllHidden.count(), 2); 0294 QVERIFY(compAllHidden.contains(m_dirURL.toString() + ".1_hidden_file_subdir/")); 0295 QVERIFY(compAllHidden.contains(m_dirURL.toString() + ".2_hidden_file_subdir/")); 0296 0297 // Completion with '.2', should find only hidden folders starting with '2' 0298 url = QUrl::fromLocalFile(m_dirURL.toLocalFile() + ".2"); 0299 qDebug() << "makeCompletion(" << url << ")"; 0300 m_completion->makeCompletion(url.toString()); 0301 waitForCompletion(m_completion); 0302 const auto compHiddenStartingWith2 = m_completion->allMatches(); 0303 QCOMPARE(compHiddenStartingWith2.count(), 1); 0304 QVERIFY(compHiddenStartingWith2.contains(m_dirURL.toString() + QStringLiteral(".2_hidden_file_subdir/"))); 0305 0306 // Completion with 'file.', should only find one file 0307 url = QUrl::fromLocalFile(m_dirURL.toLocalFile() + QStringLiteral("file.")); 0308 qDebug() << "makeCompletion(" << url << ")"; 0309 m_completion->makeCompletion(url.toString()); 0310 waitForCompletion(m_completion); 0311 const auto compFileEndingWithDot = m_completion->allMatches(); 0312 QCOMPARE(compFileEndingWithDot.count(), 1); 0313 QVERIFY(compFileEndingWithDot.contains(m_dirURL.toString() + QStringLiteral("file."))); 0314 0315 // Completion with 'source' should only find the C++ file 0316 m_completionWithMimeFilter->makeCompletion(m_dirURL.toString() + QStringLiteral("source")); 0317 waitForCompletion(m_completionWithMimeFilter); 0318 const auto compSourceFile = m_completionWithMimeFilter->allMatches(); 0319 QCOMPARE(compSourceFile.count(), 1); 0320 QVERIFY(compSourceFile.contains(m_dirURL.toString() + QStringLiteral("source.cpp"))); 0321 0322 // But it should also be able to find folders 0323 m_completionWithMimeFilter->makeCompletion(m_dirURL.toString() + QStringLiteral("file_subdir")); 0324 waitForCompletion(m_completionWithMimeFilter); 0325 const auto compMimeFolder = m_completionWithMimeFilter->allMatches(); 0326 QCOMPARE(compMimeFolder.count(), 1); 0327 QVERIFY(compMimeFolder.contains(m_dirURL.toString() + QStringLiteral("file_subdir/"))); 0328 } 0329 0330 void KUrlCompletionTest::testEmptyCwd() 0331 { 0332 // Completion with empty string (with a KUrlCompletion whose cwd is "") 0333 qDebug() << "\nnow completing on '' with empty cwd"; 0334 m_completionEmptyCwd->makeCompletion(QLatin1String("")); 0335 waitForCompletion(m_completionEmptyCwd); 0336 QStringList compEmpty = m_completionEmptyCwd->allMatches(); 0337 QCOMPARE(compEmpty.count(), 0); 0338 } 0339 0340 void KUrlCompletionTest::testBug346920() 0341 { 0342 m_completionEmptyCwd->makeCompletion(QStringLiteral("~/.")); 0343 waitForCompletion(m_completionEmptyCwd); 0344 m_completionEmptyCwd->allMatches(); 0345 // just don't crash 0346 } 0347 0348 void KUrlCompletionTest::testInvalidProtocol() 0349 { 0350 m_completion->makeCompletion(QStringLiteral(":/")); 0351 waitForCompletion(m_completion); 0352 m_completion->allMatches(); 0353 // just don't crash 0354 } 0355 0356 void KUrlCompletionTest::testUser() 0357 { 0358 m_completionEmptyCwd->makeCompletion(QStringLiteral("~")); 0359 waitForCompletion(m_completionEmptyCwd); 0360 const auto matches = m_completionEmptyCwd->allMatches(); 0361 const QStringList allUsers = KUser::allUserNames(); 0362 if (!allUsers.isEmpty()) { 0363 Q_ASSERT(!matches.isEmpty()); 0364 } 0365 for (const auto &user : allUsers) { 0366 QVERIFY2(matches.contains(QLatin1Char('~') + user), qPrintable(matches.join(QLatin1Char(' ')))); 0367 } 0368 0369 // Check that the same query doesn't re-list 0370 m_completionEmptyCwd->makeCompletion(QStringLiteral("~")); 0371 QVERIFY(!m_completionEmptyCwd->isRunning()); 0372 QCOMPARE(m_completionEmptyCwd->allMatches(), matches); 0373 } 0374 0375 // Test cancelling a running thread 0376 // In a normal run (./kurlcompletiontest) and a reasonable amount of files, we have few chances of making this happen 0377 // But in a "nowait" run (./kurlcompletiontest-nowait), this will cancel the thread before it even starts listing the dir. 0378 void KUrlCompletionTest::testCancel() 0379 { 0380 KUrlCompletion comp; 0381 comp.setDir(QUrl::fromLocalFile("/usr/bin")); 0382 comp.makeCompletion(QStringLiteral("g")); 0383 const QStringList matchesG = comp.allMatches(); 0384 // We get many matches in a normal run, and usually 0 matches when testing "no wait" (thread is sleeping) -> this is where this method can test cancelling 0385 // qDebug() << "got" << matchesG.count() << "matches"; 0386 bool done = !comp.isRunning(); 0387 0388 // Doing the same search again, should hopefully not restart everything from scratch 0389 comp.makeCompletion(QStringLiteral("g")); 0390 const QStringList matchesG2 = comp.allMatches(); 0391 QVERIFY(matchesG2.count() >= matchesG.count()); 0392 if (done) { 0393 QVERIFY(!comp.isRunning()); // it had no reason to restart 0394 } 0395 done = !comp.isRunning(); 0396 0397 // Search for something else, should reuse dir listing but not mix up results 0398 comp.makeCompletion(QStringLiteral("a")); 0399 if (done) { 0400 QVERIFY(!comp.isRunning()); // it had no reason to restart 0401 } 0402 const QStringList matchesA = comp.allMatches(); 0403 // qDebug() << "got" << matchesA.count() << "matches"; 0404 for (const QString &match : matchesA) { 0405 QVERIFY2(!match.startsWith(QLatin1Char('g')), qPrintable(match)); 0406 } 0407 waitForCompletion(&comp); 0408 const QStringList matchesB = comp.allMatches(); 0409 for (const QString &match : matchesB) { 0410 QVERIFY2(!match.startsWith(QLatin1Char('g')), qPrintable(match)); 0411 } 0412 } 0413 0414 void KUrlCompletionTest::test() 0415 { 0416 runAllTests(); 0417 // Try again, with another QTemporaryDir (to check that the caching doesn't give us wrong results) 0418 runAllTests(); 0419 } 0420 0421 void KUrlCompletionTest::runAllTests() 0422 { 0423 setup(); 0424 testLocalRelativePath(); 0425 testLocalAbsolutePath(); 0426 testLocalURL(); 0427 testEmptyCwd(); 0428 testBug346920(); 0429 testInvalidProtocol(); 0430 testUser(); 0431 testCancel(); 0432 teardown(); 0433 } 0434 0435 QTEST_MAIN(KUrlCompletionTest) 0436 0437 #include "kurlcompletiontest.moc"