File indexing completed on 2024-04-14 14:24:04

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"