Files
RedBear-OS/local/recipes/kde/kf6-kio/source/autotests/kurlcompletiontest.cpp
T
2026-04-14 10:51:06 +01:00

438 lines
16 KiB
C++

/*
SPDX-FileCopyrightText: 2004 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#include <KUser>
#include <QApplication>
#include <QDebug>
#include <QDir>
#include <QFile>
#include <QTemporaryDir>
#include <QTest>
#include <QThread>
#include <kurlcompletion.h>
#include <qplatformdefs.h>
class KUrlCompletionTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void test();
public:
KUrlCompletionTest()
{
#ifdef NO_WAIT // kurlcompletiontest-nowait sets this, to test what happens on slower systems (or systems with many dirs or users)
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
#endif
}
~KUrlCompletionTest() override
{
teardown();
}
void runAllTests();
void setup();
void teardown();
void testLocalRelativePath();
void testLocalAbsolutePath();
void testLocalURL();
void testEmptyCwd();
void testBug346920();
void testInvalidProtocol();
void testUser();
void testCancel();
// remember to register new test methods in runAllTests
private:
void waitForCompletion(KUrlCompletion *completion);
KUrlCompletion *m_completion;
KUrlCompletion *m_completionWithMimeFilter;
QTemporaryDir *m_tempDir;
QUrl m_dirURL;
QString m_dir;
KUrlCompletion *m_completionEmptyCwd;
};
void KUrlCompletionTest::setup()
{
qDebug();
m_completion = new KUrlCompletion;
m_completionWithMimeFilter = new KUrlCompletion;
m_completionWithMimeFilter->setMimeTypeFilters({QStringLiteral("text/x-c++src")});
m_tempDir = new QTemporaryDir;
m_dir = m_tempDir->path();
m_dir += QLatin1String("/Dir With#Spaces/");
QDir().mkdir(m_dir);
qDebug() << "m_dir=" << m_dir;
m_completion->setDir(QUrl::fromLocalFile(m_dir));
m_completionWithMimeFilter->setDir(m_completion->dir());
m_dirURL = QUrl::fromLocalFile(m_dir);
QFile f1(m_dir + QStringLiteral("/file1"));
bool ok = f1.open(QIODevice::WriteOnly);
QVERIFY(ok);
f1.close();
QFile f2(m_dir + QStringLiteral("/file#a"));
ok = f2.open(QIODevice::WriteOnly);
QVERIFY(ok);
f2.close();
QFile f3(m_dir + QStringLiteral("/file."));
ok = f3.open(QIODevice::WriteOnly);
QVERIFY(ok);
f3.close();
QFile f4(m_dir + QStringLiteral("/source.cpp"));
ok = f4.open(QIODevice::WriteOnly);
QVERIFY(ok);
f4.close();
QFile f5(m_dir + QStringLiteral("/source.php"));
ok = f5.open(QIODevice::WriteOnly);
QVERIFY(ok);
f5.close();
QDir().mkdir(m_dir + QStringLiteral("/file_subdir"));
QDir().mkdir(m_dir + QStringLiteral("/.1_hidden_file_subdir"));
QDir().mkdir(m_dir + QStringLiteral("/.2_hidden_file_subdir"));
m_completionEmptyCwd = new KUrlCompletion;
m_completionEmptyCwd->setDir(QUrl());
}
void KUrlCompletionTest::teardown()
{
delete m_completion;
m_completion = nullptr;
delete m_completionWithMimeFilter;
m_completionWithMimeFilter = nullptr;
delete m_tempDir;
m_tempDir = nullptr;
delete m_completionEmptyCwd;
m_completionEmptyCwd = nullptr;
}
void KUrlCompletionTest::waitForCompletion(KUrlCompletion *completion)
{
while (completion->isRunning()) {
qDebug() << "waiting for thread...";
QTest::qWait(5);
}
// The thread emitted a signal, process it.
qApp->sendPostedEvents(nullptr, QEvent::MetaCall);
}
void KUrlCompletionTest::testLocalRelativePath()
{
qDebug();
// Completion from relative path, with two matches
m_completion->makeCompletion(QStringLiteral("f"));
waitForCompletion(m_completion);
QStringList comp1all = m_completion->allMatches();
qDebug() << comp1all;
QCOMPARE(comp1all.count(), 4);
QVERIFY(comp1all.contains(QLatin1String("file1")));
QVERIFY(comp1all.contains(QLatin1String("file#a")));
QVERIFY(comp1all.contains(QLatin1String("file.")));
QVERIFY(comp1all.contains(QLatin1String("file_subdir/")));
QString comp1 = m_completion->replacedPath(QStringLiteral("file1")); // like KUrlRequester does
QCOMPARE(comp1, QStringLiteral("file1"));
// Completion from relative path
qDebug() << "\nnow completing on 'file#'";
m_completion->makeCompletion(QStringLiteral("file#"));
QVERIFY(!m_completion->isRunning()); // last listing reused
QStringList compall = m_completion->allMatches();
qDebug() << compall;
QCOMPARE(compall.count(), 1);
QCOMPARE(compall.first(), QStringLiteral("file#a"));
QString comp2 = m_completion->replacedPath(compall.first()); // like KUrlRequester does
QCOMPARE(comp2, QStringLiteral("file#a"));
// Completion with empty string
qDebug() << "\nnow completing on ''";
m_completion->makeCompletion(QLatin1String(""));
waitForCompletion(m_completion);
QStringList compEmpty = m_completion->allMatches();
QCOMPARE(compEmpty.count(), 0);
m_completion->makeCompletion(QStringLiteral("."));
waitForCompletion(m_completion);
const auto compAllHidden = m_completion->allMatches();
QCOMPARE(compAllHidden.count(), 2);
QVERIFY(compAllHidden.contains(QLatin1String(".1_hidden_file_subdir/")));
QVERIFY(compAllHidden.contains(QLatin1String(".2_hidden_file_subdir/")));
// Completion with '.2', should find only hidden folders starting with '2'
m_completion->makeCompletion(QStringLiteral(".2"));
waitForCompletion(m_completion);
const auto compHiddenStartingWith2 = m_completion->allMatches();
QCOMPARE(compHiddenStartingWith2.count(), 1);
QVERIFY(compHiddenStartingWith2.contains(QLatin1String(".2_hidden_file_subdir/")));
// Completion with 'file.', should only find one file
m_completion->makeCompletion(QStringLiteral("file."));
waitForCompletion(m_completion);
const auto compFileEndingWithDot = m_completion->allMatches();
QCOMPARE(compFileEndingWithDot.count(), 1);
QVERIFY(compFileEndingWithDot.contains(QLatin1String("file.")));
// Completion with 'source' should only find the C++ file
m_completionWithMimeFilter->makeCompletion(QStringLiteral("source"));
waitForCompletion(m_completionWithMimeFilter);
const auto compSourceFile = m_completionWithMimeFilter->allMatches();
QCOMPARE(compSourceFile.count(), 1);
QVERIFY(compSourceFile.contains(QLatin1String("source.cpp")));
// But it should also be able to find folders
m_completionWithMimeFilter->makeCompletion(QStringLiteral("file_subdir"));
waitForCompletion(m_completionWithMimeFilter);
const auto compMimeFolder = m_completionWithMimeFilter->allMatches();
QCOMPARE(compMimeFolder.count(), 1);
QVERIFY(compMimeFolder.contains(QLatin1String("file_subdir/")));
}
void KUrlCompletionTest::testLocalAbsolutePath()
{
// Completion from absolute path
qDebug() << m_dir + "file#";
m_completion->makeCompletion(m_dir + "file#");
waitForCompletion(m_completion);
QStringList compall = m_completion->allMatches();
qDebug() << compall;
QCOMPARE(compall.count(), 1);
QString comp = compall.first();
QCOMPARE(comp, QString(m_dir + "file#a"));
comp = m_completion->replacedPath(comp); // like KUrlRequester does
QCOMPARE(comp, QString(m_dir + "file#a"));
// Completion with '.', should find all hidden folders
m_completion->makeCompletion(m_dir + QLatin1Char('.'));
waitForCompletion(m_completion);
const auto compAllHidden = m_completion->allMatches();
QCOMPARE(compAllHidden.count(), 2);
QVERIFY(compAllHidden.contains(m_dir + ".1_hidden_file_subdir/"));
QVERIFY(compAllHidden.contains(m_dir + ".2_hidden_file_subdir/"));
// Completion with '.2', should find only hidden folders starting with '2'
m_completion->makeCompletion(m_dir + ".2");
waitForCompletion(m_completion);
const auto compHiddenStartingWith2 = m_completion->allMatches();
QCOMPARE(compHiddenStartingWith2.count(), 1);
QVERIFY(compHiddenStartingWith2.contains(m_dir + ".2_hidden_file_subdir/"));
// Completion with 'file.', should only find one file
m_completion->makeCompletion(m_dir + "file.");
waitForCompletion(m_completion);
const auto compFileEndingWithDot = m_completion->allMatches();
QCOMPARE(compFileEndingWithDot.count(), 1);
QVERIFY(compFileEndingWithDot.contains(m_dir + "file."));
// Completion with 'source' should only find the C++ file
m_completionWithMimeFilter->makeCompletion(m_dir + "source");
waitForCompletion(m_completionWithMimeFilter);
const auto compSourceFile = m_completionWithMimeFilter->allMatches();
QCOMPARE(compSourceFile.count(), 1);
QVERIFY(compSourceFile.contains(m_dir + "source.cpp"));
// But it should also be able to find folders
m_completionWithMimeFilter->makeCompletion(m_dir + "file_subdir");
waitForCompletion(m_completionWithMimeFilter);
const auto compMimeFolder = m_completionWithMimeFilter->allMatches();
QCOMPARE(compMimeFolder.count(), 1);
QVERIFY(compMimeFolder.contains(m_dir + "file_subdir/"));
}
void KUrlCompletionTest::testLocalURL()
{
// Completion from URL
qDebug();
QUrl url = QUrl::fromLocalFile(m_dirURL.toLocalFile() + "file");
m_completion->makeCompletion(url.toString());
waitForCompletion(m_completion);
QStringList comp1all = m_completion->allMatches();
qDebug() << comp1all;
QCOMPARE(comp1all.count(), 4);
qDebug() << "Looking for" << m_dirURL.toString() + "file1";
QVERIFY(comp1all.contains(m_dirURL.toString() + "file1"));
qDebug() << "Looking for" << m_dirURL.toString() + "file.";
QVERIFY(comp1all.contains(m_dirURL.toString() + "file."));
QVERIFY(comp1all.contains(m_dirURL.toString() + "file_subdir/"));
QString filehash = m_dirURL.toString() + "file%23a";
qDebug() << "Looking for" << filehash;
QVERIFY(comp1all.contains(filehash));
QString filehashPath = m_completion->replacedPath(filehash); // note that it returns a path!!
qDebug() << filehashPath;
QCOMPARE(filehashPath, QString(m_dirURL.toLocalFile() + "file#a"));
// Completion from URL with no match
url = QUrl::fromLocalFile(m_dirURL.toLocalFile() + "foobar");
qDebug() << "makeCompletion(" << url << ")";
QString comp2 = m_completion->makeCompletion(url.toString());
QVERIFY(comp2.isEmpty());
waitForCompletion(m_completion);
QVERIFY(m_completion->allMatches().isEmpty());
// Completion from URL with a ref -> no match
url = QUrl::fromLocalFile(m_dirURL.toLocalFile() + 'f');
url.setFragment(QStringLiteral("ref"));
qDebug() << "makeCompletion(" << url << ")";
m_completion->makeCompletion(url.toString());
waitForCompletion(m_completion);
QVERIFY(m_completion->allMatches().isEmpty());
// Completion with '.', should find all hidden folders
qDebug() << "makeCompletion(" << (m_dirURL.toString() + QLatin1Char('.')) << ")";
m_completion->makeCompletion(m_dirURL.toString() + QLatin1Char('.'));
waitForCompletion(m_completion);
const auto compAllHidden = m_completion->allMatches();
QCOMPARE(compAllHidden.count(), 2);
QVERIFY(compAllHidden.contains(m_dirURL.toString() + ".1_hidden_file_subdir/"));
QVERIFY(compAllHidden.contains(m_dirURL.toString() + ".2_hidden_file_subdir/"));
// Completion with '.2', should find only hidden folders starting with '2'
url = QUrl::fromLocalFile(m_dirURL.toLocalFile() + ".2");
qDebug() << "makeCompletion(" << url << ")";
m_completion->makeCompletion(url.toString());
waitForCompletion(m_completion);
const auto compHiddenStartingWith2 = m_completion->allMatches();
QCOMPARE(compHiddenStartingWith2.count(), 1);
QVERIFY(compHiddenStartingWith2.contains(m_dirURL.toString() + QStringLiteral(".2_hidden_file_subdir/")));
// Completion with 'file.', should only find one file
url = QUrl::fromLocalFile(m_dirURL.toLocalFile() + QStringLiteral("file."));
qDebug() << "makeCompletion(" << url << ")";
m_completion->makeCompletion(url.toString());
waitForCompletion(m_completion);
const auto compFileEndingWithDot = m_completion->allMatches();
QCOMPARE(compFileEndingWithDot.count(), 1);
QVERIFY(compFileEndingWithDot.contains(m_dirURL.toString() + QStringLiteral("file.")));
// Completion with 'source' should only find the C++ file
m_completionWithMimeFilter->makeCompletion(m_dirURL.toString() + QStringLiteral("source"));
waitForCompletion(m_completionWithMimeFilter);
const auto compSourceFile = m_completionWithMimeFilter->allMatches();
QCOMPARE(compSourceFile.count(), 1);
QVERIFY(compSourceFile.contains(m_dirURL.toString() + QStringLiteral("source.cpp")));
// But it should also be able to find folders
m_completionWithMimeFilter->makeCompletion(m_dirURL.toString() + QStringLiteral("file_subdir"));
waitForCompletion(m_completionWithMimeFilter);
const auto compMimeFolder = m_completionWithMimeFilter->allMatches();
QCOMPARE(compMimeFolder.count(), 1);
QVERIFY(compMimeFolder.contains(m_dirURL.toString() + QStringLiteral("file_subdir/")));
}
void KUrlCompletionTest::testEmptyCwd()
{
// Completion with empty string (with a KUrlCompletion whose cwd is "")
qDebug() << "\nnow completing on '' with empty cwd";
m_completionEmptyCwd->makeCompletion(QLatin1String(""));
waitForCompletion(m_completionEmptyCwd);
QStringList compEmpty = m_completionEmptyCwd->allMatches();
QCOMPARE(compEmpty.count(), 0);
}
void KUrlCompletionTest::testBug346920()
{
m_completionEmptyCwd->makeCompletion(QStringLiteral("~/."));
waitForCompletion(m_completionEmptyCwd);
m_completionEmptyCwd->allMatches();
// just don't crash
}
void KUrlCompletionTest::testInvalidProtocol()
{
m_completion->makeCompletion(QStringLiteral(":/"));
waitForCompletion(m_completion);
m_completion->allMatches();
// just don't crash
}
void KUrlCompletionTest::testUser()
{
m_completionEmptyCwd->makeCompletion(QStringLiteral("~"));
waitForCompletion(m_completionEmptyCwd);
const auto matches = m_completionEmptyCwd->allMatches();
const QStringList allUsers = KUser::allUserNames();
if (!allUsers.isEmpty()) {
Q_ASSERT(!matches.isEmpty());
}
for (const auto &user : allUsers) {
QVERIFY2(matches.contains(QLatin1Char('~') + user), qPrintable(matches.join(QLatin1Char(' '))));
}
// Check that the same query doesn't re-list
m_completionEmptyCwd->makeCompletion(QStringLiteral("~"));
QVERIFY(!m_completionEmptyCwd->isRunning());
QCOMPARE(m_completionEmptyCwd->allMatches(), matches);
}
// Test cancelling a running thread
// In a normal run (./kurlcompletiontest) and a reasonable amount of files, we have few chances of making this happen
// But in a "nowait" run (./kurlcompletiontest-nowait), this will cancel the thread before it even starts listing the dir.
void KUrlCompletionTest::testCancel()
{
KUrlCompletion comp;
comp.setDir(QUrl::fromLocalFile("/usr/bin"));
comp.makeCompletion(QStringLiteral("g"));
const QStringList matchesG = comp.allMatches();
// 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
// qDebug() << "got" << matchesG.count() << "matches";
bool done = !comp.isRunning();
// Doing the same search again, should hopefully not restart everything from scratch
comp.makeCompletion(QStringLiteral("g"));
const QStringList matchesG2 = comp.allMatches();
QVERIFY(matchesG2.count() >= matchesG.count());
if (done) {
QVERIFY(!comp.isRunning()); // it had no reason to restart
}
done = !comp.isRunning();
// Search for something else, should reuse dir listing but not mix up results
comp.makeCompletion(QStringLiteral("a"));
if (done) {
QVERIFY(!comp.isRunning()); // it had no reason to restart
}
const QStringList matchesA = comp.allMatches();
// qDebug() << "got" << matchesA.count() << "matches";
for (const QString &match : matchesA) {
QVERIFY2(!match.startsWith(QLatin1Char('g')), qPrintable(match));
}
waitForCompletion(&comp);
const QStringList matchesB = comp.allMatches();
for (const QString &match : matchesB) {
QVERIFY2(!match.startsWith(QLatin1Char('g')), qPrintable(match));
}
}
void KUrlCompletionTest::test()
{
runAllTests();
// Try again, with another QTemporaryDir (to check that the caching doesn't give us wrong results)
runAllTests();
}
void KUrlCompletionTest::runAllTests()
{
setup();
testLocalRelativePath();
testLocalAbsolutePath();
testLocalURL();
testEmptyCwd();
testBug346920();
testInvalidProtocol();
testUser();
testCancel();
teardown();
}
QTEST_MAIN(KUrlCompletionTest)
#include "kurlcompletiontest.moc"