File indexing completed on 2024-05-12 04:40:57

0001 /*
0002     SPDX-FileCopyrightText: Milian Wolff <mail@milianw.de>
0003 
0004     SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005 */
0006 
0007 #include "test_quickopen.h"
0008 #include <interfaces/idocumentcontroller.h>
0009 #include <project/projectutils.h>
0010 
0011 #include <QTemporaryDir>
0012 #include <QTest>
0013 #include <QTemporaryFile>
0014 
0015 #include <type_traits>
0016 #include <utility>
0017 
0018 QTEST_MAIN(TestQuickOpen)
0019 
0020 using namespace KDevelop;
0021 
0022 static_assert(std::is_nothrow_move_assignable<ProjectFile>(), "Why would a move assignment operator throw?");
0023 static_assert(std::is_nothrow_move_constructible<ProjectFile>(), "Why would a move constructor throw?");
0024 
0025 using ItemList = QVector<DUChainItem>;
0026 using StringList = QVector<QString>;
0027 
0028 
0029 TestQuickOpen::TestQuickOpen(QObject* parent)
0030     : QuickOpenTestBase(Core::Default, parent)
0031 {
0032     QStandardPaths::setTestModeEnabled(true);
0033 }
0034 
0035 void TestQuickOpen::testProjectFileSwap()
0036 {
0037     QScopedPointer<TestProject> project(getProjectWithFiles(2));
0038     QVector<ProjectFile> projectFiles;
0039     KDevelop::forEachFile(project->projectItem(), [&projectFiles](ProjectFileItem* fileItem) {
0040         projectFiles.push_back(ProjectFile{fileItem});
0041     });
0042     QCOMPARE(projectFiles.size(), 2);
0043 
0044     const auto equivalent = [](const ProjectFile &x, const ProjectFile &y) {
0045         return !(x < y) && !(y < x);
0046     };
0047 
0048     ProjectFile a = projectFiles.at(0);
0049     ProjectFile b = projectFiles.at(1);
0050     QCOMPARE(a.projectPath, b.projectPath);
0051     QVERIFY(!equivalent(a, b));
0052 
0053     const auto aCopy = a;
0054     const auto bCopy = b;
0055     QCOMPARE(aCopy.projectPath, a.projectPath);
0056     QVERIFY(equivalent(aCopy, a));
0057     QCOMPARE(bCopy.projectPath, b.projectPath);
0058     QVERIFY(equivalent(bCopy, b));
0059 
0060     using std::swap;
0061 
0062     swap(a, b);
0063     QCOMPARE(a.projectPath, bCopy.projectPath);
0064     QVERIFY(equivalent(a, bCopy));
0065     QCOMPARE(b.projectPath, aCopy.projectPath);
0066     QVERIFY(equivalent(b, aCopy));
0067 
0068     QString anotherProjectPath = "/some/special/path/to/a-project";
0069 #ifdef Q_OS_WIN
0070     anotherProjectPath.prepend("C:");
0071 #endif
0072     a.projectPath = Path{anotherProjectPath};
0073     QCOMPARE(a.projectPath.pathOrUrl(), anotherProjectPath);
0074 
0075     swap(a, b);
0076     QCOMPARE(a.projectPath, aCopy.projectPath);
0077     QVERIFY(equivalent(a, aCopy));
0078     QCOMPARE(b.projectPath.pathOrUrl(), anotherProjectPath);
0079     QVERIFY(equivalent(b, bCopy));
0080 
0081     QVERIFY(a.projectPath != b.projectPath);
0082     QVERIFY(!equivalent(a, b));
0083 }
0084 
0085 void TestQuickOpen::testDuchainFilter()
0086 {
0087     QFETCH(ItemList, items);
0088     QFETCH(QString, filter);
0089     QFETCH(ItemList, filtered);
0090 
0091     auto toStringList = [](const ItemList& items) {
0092                             QStringList result;
0093                             for (const DUChainItem& item: items) {
0094                                 result << item.m_text;
0095                             }
0096 
0097                             return result;
0098                         };
0099 
0100     TestFilter filterItems;
0101     filterItems.setItems(items);
0102     filterItems.setFilter(filter);
0103     QCOMPARE(toStringList(filterItems.filteredItems()), toStringList(filtered));
0104 }
0105 
0106 void TestQuickOpen::testDuchainFilter_data()
0107 {
0108     QTest::addColumn<ItemList>("items");
0109     QTest::addColumn<QString>("filter");
0110     QTest::addColumn<ItemList>("filtered");
0111 
0112     auto i = [](const QString& text) {
0113                  auto item = DUChainItem();
0114                  item.m_text = text;
0115                  return item;
0116              };
0117 
0118     auto items = ItemList()
0119                  << i(QStringLiteral("KTextEditor::Cursor"))
0120                  << i(QStringLiteral("void KTextEditor::Cursor::explode()"))
0121                  << i(QStringLiteral("QVector<int> SomeNamespace::SomeClass::func(int)"));
0122 
0123     QTest::newRow("prefix") << items << "KTE" << (ItemList() << items.at(0) << items.at(1));
0124     QTest::newRow("prefix_mismatch") << items << "KTEY" << (ItemList());
0125     QTest::newRow("prefix_colon") << items << "KTE:" << (ItemList() << items.at(0) << items.at(1));
0126     QTest::newRow("prefix_colon_mismatch") << items << "KTE:Y" << (ItemList());
0127     QTest::newRow("prefix_colon_mismatch2") << items << "XKTE:" << (ItemList());
0128     QTest::newRow("prefix_two_colon") << items << "KTE::" << (ItemList() << items.at(0) << items.at(1));
0129     QTest::newRow("prefix_two_colon_mismatch") << items << "KTE::Y" << (ItemList());
0130     QTest::newRow("prefix_two_colon_mismatch2") << items << "XKTE::" << (ItemList());
0131     QTest::newRow("suffix") << items << "Curs" << (ItemList() << items.at(0) << items.at(1));
0132     QTest::newRow("suffix2") << items << "curs" << (ItemList() << items.at(0) << items.at(1));
0133     QTest::newRow("mid") << items << "SomeClass" << (ItemList() << items.at(2));
0134     QTest::newRow("mid_abbrev") << items << "SClass" << (ItemList() << items.at(2));
0135 }
0136 
0137 void TestQuickOpen::testAbbreviations()
0138 {
0139     QFETCH(StringList, items);
0140     QFETCH(QString, filter);
0141     QFETCH(StringList, filtered);
0142 
0143     PathTestFilter filterItems;
0144     filterItems.setItems(std::move(items));
0145     filterItems.setFilter(filter.split('/', Qt::SkipEmptyParts));
0146     QCOMPARE(filterItems.filteredItems(), filtered);
0147 }
0148 
0149 void TestQuickOpen::testAbbreviations_data()
0150 {
0151     QTest::addColumn<StringList>("items");
0152     QTest::addColumn<QString>("filter");
0153     QTest::addColumn<StringList>("filtered");
0154 
0155     const StringList items = {
0156         QStringLiteral("/foo/bar/caz/a.h"),
0157         QStringLiteral("/KateThing/CMakeLists.txt"),
0158         QStringLiteral("/FooBar/FooBar/Footestfoo.h") };
0159 
0160     QTest::newRow("path_segments") << items << "fbc" << StringList();
0161     QTest::newRow("path_segment_abbrev") << items << "cmli" << StringList({ items.at(1) });
0162     QTest::newRow("path_segment_old") << items << "kate/cmake" << StringList({ items.at(1) });
0163     QTest::newRow("path_segment_multi_mixed") << items << "ftfoo.h" << StringList({ items.at(2) });
0164 }
0165 
0166 void TestQuickOpen::testSorting()
0167 {
0168     QFETCH(StringList, items);
0169     QFETCH(QString, filter);
0170     QFETCH(StringList, filtered);
0171 
0172     const auto filterList = filter.split('/', Qt::SkipEmptyParts);
0173     PathTestFilter filterItems;
0174     filterItems.setItems(std::move(items));
0175     filterItems.setFilter(filterList);
0176     QEXPECT_FAIL("bar7", "empty parts are skipped", Abort);
0177     if (filterItems.filteredItems() != filtered)
0178         qWarning() << filterItems.filteredItems() << filtered;
0179     QCOMPARE(filterItems.filteredItems(), filtered);
0180 
0181     // check whether sorting is stable
0182     filterItems.setFilter(filterList);
0183     QCOMPARE(filterItems.filteredItems(), filtered);
0184 }
0185 
0186 void TestQuickOpen::testSorting_data()
0187 {
0188     QTest::addColumn<StringList>("items");
0189     QTest::addColumn<QString>("filter");
0190     QTest::addColumn<StringList>("filtered");
0191 
0192     const StringList items({
0193         QStringLiteral("/foo/a.h"),
0194         QStringLiteral("/foo/ab.h"),
0195         QStringLiteral("/foo/bc.h"),
0196         QStringLiteral("/bar/a.h")});
0197 
0198     {
0199         QTest::newRow("no-filter") << items << QString() << items;
0200     }
0201     {
0202         const StringList filtered = { QStringLiteral("/bar/a.h") };
0203         QTest::newRow("bar1") << items << QStringLiteral("bar") << filtered;
0204         QTest::newRow("bar2") << items << QStringLiteral("/bar") << filtered;
0205         QTest::newRow("bar3") << items << QStringLiteral("/bar/") << filtered;
0206         QTest::newRow("bar4") << items << QStringLiteral("bar/") << filtered;
0207         QTest::newRow("bar5") << items << QStringLiteral("ar/") << filtered;
0208         QTest::newRow("bar6") << items << QStringLiteral("r/") << filtered;
0209         QTest::newRow("bar7") << items << QStringLiteral("b/") << filtered;
0210         QTest::newRow("bar8") << items << QStringLiteral("b/a") << filtered;
0211         QTest::newRow("bar9") << items << QStringLiteral("b/a.h") << filtered;
0212         QTest::newRow("bar10") << items << QStringLiteral("b/a.") << filtered;
0213     }
0214     {
0215         const StringList filtered = { QStringLiteral("/foo/a.h"), QStringLiteral("/foo/ab.h") };
0216         QTest::newRow("foo_a1") << items << QStringLiteral("foo/a") << filtered;
0217         QTest::newRow("foo_a2") << items << QStringLiteral("/f/a") << filtered;
0218     }
0219     {
0220         // now matches ab.h too because of abbreviation matching, but should be sorted last
0221         const StringList filtered = { QStringLiteral("/foo/a.h"), QStringLiteral("/bar/a.h"), QStringLiteral("/foo/ab.h") };
0222         QTest::newRow("a_h") << items << QStringLiteral("a.h") << filtered;
0223     }
0224     {
0225         const StringList base = { QStringLiteral("/foo/a_test"), QStringLiteral("/foo/test_b_1"), QStringLiteral("/foo/test_b") };
0226         const StringList sorted = { QStringLiteral("/foo/test_b"), QStringLiteral("/foo/test_b_1") };
0227         QTest::newRow("prefer_exact") << base << QStringLiteral("test_b") << sorted;
0228     }
0229     {
0230         // from commit: 769491f06a4560a4798592ff060675ffb0d990a6
0231         const QString file = QStringLiteral("/myProject/someStrangePath/anItem.cpp");
0232         const StringList base = { QStringLiteral("/foo/a"), file };
0233         const StringList filtered = { file };
0234         QTest::newRow("strange") << base << QStringLiteral("strange/item") << filtered;
0235     }
0236     {
0237         const StringList base = { QStringLiteral("/foo/a_test"), QStringLiteral("/foo/test_b_1"),
0238                                     QStringLiteral("/foo/test_b"), QStringLiteral("/foo/test/a") };
0239         const StringList sorted = { QStringLiteral("/foo/test_b_1"), QStringLiteral("/foo/test_b"),
0240                                       QStringLiteral("/foo/a_test"), QStringLiteral("/foo/test/a") };
0241         QTest::newRow("prefer_start1") << base << QStringLiteral("test") << sorted;
0242         QTest::newRow("prefer_start2") << base << QStringLiteral("foo/test") << sorted;
0243     }
0244     {
0245         const StringList base = { QStringLiteral("/muh/kuh/asdf/foo"), QStringLiteral("/muh/kuh/foo/asdf") };
0246         const StringList reverse = { QStringLiteral("/muh/kuh/foo/asdf"), QStringLiteral("/muh/kuh/asdf/foo") };
0247         QTest::newRow("prefer_start3") << base << QStringLiteral("f") << base;
0248         QTest::newRow("prefer_start4") << base << QStringLiteral("/fo") << base;
0249         QTest::newRow("prefer_start5") << base << QStringLiteral("/foo") << base;
0250         QTest::newRow("prefer_start6") << base << QStringLiteral("a") << reverse;
0251         QTest::newRow("prefer_start7") << base << QStringLiteral("/a") << reverse;
0252         QTest::newRow("prefer_start8") << base << QStringLiteral("uh/as") << reverse;
0253         QTest::newRow("prefer_start9") << base << QStringLiteral("asdf") << reverse;
0254     }
0255     {
0256         QTest::newRow("duplicate") << StringList({ QStringLiteral("/muh/kuh/asdf/foo") }) << QStringLiteral("kuh/kuh") << StringList();
0257     }
0258     {
0259         const StringList fuzzyItems = {
0260             QStringLiteral("/foo/bar.h"),
0261             QStringLiteral("/foo/fooXbar.h"),
0262             QStringLiteral("/foo/fXoXoXbXaXr.h"),
0263             QStringLiteral("/bar/FOOxBAR.h")
0264         };
0265 
0266         QTest::newRow("fuzzy1") << fuzzyItems << QStringLiteral("br") << fuzzyItems;
0267         QTest::newRow("fuzzy2") << fuzzyItems << QStringLiteral("foo/br") << StringList({
0268             QStringLiteral("/foo/bar.h"),
0269             QStringLiteral("/foo/fooXbar.h"),
0270             QStringLiteral("/foo/fXoXoXbXaXr.h")
0271         });
0272         QTest::newRow("fuzzy3") << fuzzyItems << QStringLiteral("b/br") << StringList({
0273             QStringLiteral("/bar/FOOxBAR.h")
0274         });
0275         QTest::newRow("fuzzy4") << fuzzyItems << QStringLiteral("br/br") << StringList();
0276         QTest::newRow("fuzzy5") << fuzzyItems << QStringLiteral("foo/bar") << StringList({
0277             QStringLiteral("/foo/bar.h"),
0278             QStringLiteral("/foo/fooXbar.h"),
0279             QStringLiteral("/foo/fXoXoXbXaXr.h")
0280         });
0281         QTest::newRow("fuzzy6") << fuzzyItems << QStringLiteral("foobar") << StringList({
0282             QStringLiteral("/foo/fooXbar.h"),
0283             QStringLiteral("/foo/fXoXoXbXaXr.h"),
0284             QStringLiteral("/bar/FOOxBAR.h")
0285         });
0286     }
0287     {
0288         const StringList a = {
0289             QStringLiteral("/home/user/src/code/user/something"),
0290             QStringLiteral("/home/user/src/code/home/else"),
0291         };
0292         const StringList b = {
0293             QStringLiteral("/home/user/src/code/home/else"),
0294             QStringLiteral("/home/user/src/code/user/something"),
0295         };
0296         QTest::newRow("prefer_multimatch_a_home") << a << QStringLiteral("home") << b;
0297         QTest::newRow("prefer_multimatch_b_home") << b << QStringLiteral("home") << b;
0298         QTest::newRow("prefer_multimatch_a_user") << a << QStringLiteral("user") << a;
0299         QTest::newRow("prefer_multimatch_b_user") << b << QStringLiteral("user") << a;
0300     }
0301     {
0302         const StringList a = {
0303             QStringLiteral("/home/user/project/A/file"),
0304             QStringLiteral("/home/user/project/B/project/A/file"),
0305             QStringLiteral("/home/user/project/user/C/D/E"),
0306         };
0307         const StringList b = {
0308             QStringLiteral("/home/user/project/B/project/A/file"),
0309             QStringLiteral("/home/user/project/A/file"),
0310         };
0311         const StringList c = {
0312             QStringLiteral("/home/user/project/user/C/D/E"),
0313             QStringLiteral("/home/user/project/A/file"),
0314             QStringLiteral("/home/user/project/B/project/A/file"),
0315         };
0316         QTest::newRow("prefer_multimatch_a_project/file") << a << QStringLiteral("project/file") << b;
0317         QTest::newRow("prefer_multimatch_b_project/file") << b << QStringLiteral("project/file") << b;
0318         QTest::newRow("prefer_multimatch_a_project/le") << a << QStringLiteral("project/le") << b;
0319         QTest::newRow("prefer_multimatch_b_project/le") << b << QStringLiteral("project/le") << b;
0320         QTest::newRow("prefer_multimatch_a_project/a/file") << a << QStringLiteral("project/a/file") << b;
0321         QTest::newRow("prefer_multimatch_b_project/a/file") << b << QStringLiteral("project/a/file") << b;
0322         QTest::newRow("prefer_multimatch_a_project_user") << a << QStringLiteral("user") << c;
0323         QTest::newRow("prefer_multimatch_c_project_user") << c << QStringLiteral("user") << c;
0324     }
0325 }
0326 
0327 void TestQuickOpen::testStableSort()
0328 {
0329     const StringList items = {
0330         QStringLiteral("a/c/CMakeLists.txt"),
0331         QStringLiteral("a/d/CMakeLists.txt"),
0332         QStringLiteral("b/e/CMakeLists.txt"),
0333         QStringLiteral("b/f/CMakeLists.txt")
0334     };
0335     PathTestFilter filterItems;
0336     filterItems.setItems(items);
0337 
0338     QStringList filter = {QString()};
0339     const auto cmakeListsString = QStringLiteral("CMakeLists.txt");
0340     for (auto c : cmakeListsString) {
0341         filter[0].append(c);
0342         filterItems.setFilter(filter);
0343         QCOMPARE(filterItems.filteredItems(), items);
0344     }
0345 }
0346 
0347 void TestQuickOpen::testProjectFileFilter()
0348 {
0349     QTemporaryDir dir;
0350     auto* project = new TestProject(Path(dir.path()));
0351     auto* foo = createChild<ProjectFolderItem>(project->projectItem(), QStringLiteral("foo"));
0352     createChild<ProjectFileItem>(foo, QStringLiteral("bar"));
0353     createChild<ProjectFileItem>(foo, QStringLiteral("asdf"));
0354     createChild<ProjectFileItem>(foo, QStringLiteral("space bar"));
0355     auto* asdf = createChild<ProjectFolderItem>(project->projectItem(), QStringLiteral("asdf"));
0356     createChild<ProjectFileItem>(asdf, QStringLiteral("bar"));
0357 
0358     QTemporaryFile tmpFile;
0359     tmpFile.setFileName(dir.path() + "/aaaa");
0360     QVERIFY(tmpFile.open());
0361     auto* aaaa = new ProjectFileItem(QStringLiteral("aaaa"), project->projectItem());
0362     QCOMPARE(project->fileSet().size(), 5);
0363 
0364     ProjectFileDataProvider provider;
0365     QCOMPARE(provider.itemCount(), 0u);
0366     projectController->addProject(project);
0367 
0368     const QStringList original = QStringList()
0369                                  << QStringLiteral("aaaa") << QStringLiteral("asdf/bar") << QStringLiteral("foo/asdf") << QStringLiteral("foo/bar") << QStringLiteral("foo/space bar");
0370 
0371     // lazy load
0372     QCOMPARE(provider.itemCount(), 0u);
0373     provider.reset();
0374     QCOMPARE(items(provider), original);
0375 
0376     QCOMPARE(provider.itemPath(provider.items().first()), aaaa->path());
0377     QCOMPARE(provider.data(0)->text(), QStringLiteral("aaaa"));
0378 
0379     // don't show opened file
0380     QVERIFY(core->documentController()->openDocument(QUrl::fromLocalFile(tmpFile.fileName())));
0381     // lazy load again
0382     QCOMPARE(items(provider), original);
0383     provider.reset();
0384     QCOMPARE(items(provider), QStringList() << QStringLiteral("asdf/bar") << QStringLiteral("foo/asdf") << QStringLiteral("foo/bar") << QStringLiteral("foo/space bar"));
0385 
0386     // prefer files starting with filter
0387     provider.setFilterText(QStringLiteral("as"));
0388     qDebug() << items(provider);
0389     QCOMPARE(items(provider), QStringList() << QStringLiteral("foo/asdf") << QStringLiteral("asdf/bar"));
0390 
0391     // clear filter
0392     provider.reset();
0393     QCOMPARE(items(provider), QStringList() << QStringLiteral("asdf/bar") << QStringLiteral("foo/asdf") << QStringLiteral("foo/bar") << QStringLiteral("foo/space bar"));
0394 
0395     // update on document close, lazy load again
0396     core->documentController()->closeAllDocuments();
0397     QCOMPARE(items(provider), QStringList() << QStringLiteral("asdf/bar") << QStringLiteral("foo/asdf") << QStringLiteral("foo/bar") << QStringLiteral("foo/space bar"));
0398     provider.reset();
0399     QCOMPARE(items(provider), original);
0400 
0401     auto* blub = createChild<ProjectFileItem>(project->projectItem(), QStringLiteral("blub"));
0402     // lazy load
0403     QCOMPARE(provider.itemCount(), 5u);
0404     provider.reset();
0405     QCOMPARE(provider.itemCount(), 6u);
0406 
0407     // ensure we don't add stuff multiple times
0408     QMetaObject::invokeMethod(&provider, "fileAddedToSet",
0409                               Q_ARG(KDevelop::ProjectFileItem*, blub));
0410     QCOMPARE(provider.itemCount(), 6u);
0411     provider.reset();
0412     QCOMPARE(provider.itemCount(), 6u);
0413 
0414     // lazy load in this implementation
0415     delete blub;
0416     QCOMPARE(provider.itemCount(), 6u);
0417     provider.reset();
0418     QCOMPARE(provider.itemCount(), 5u);
0419 
0420     QCOMPARE(items(provider), original);
0421 
0422     // allow filtering by path to project
0423     provider.setFilterText(dir.path());
0424     QCOMPARE(items(provider), original);
0425 
0426     Path buildFolderItem(project->path().parent(), QStringLiteral(".build/generated.h"));
0427     new ProjectFileItem(project, buildFolderItem, project->projectItem());
0428     // lazy load
0429     QCOMPARE(items(provider), original);
0430     provider.reset();
0431     QCOMPARE(items(provider), QStringList() << QStringLiteral("aaaa") << QStringLiteral("asdf/bar") << QStringLiteral("foo/asdf")
0432                                             << QStringLiteral("foo/bar") << QStringLiteral("foo/space bar") << QStringLiteral("../.build/generated.h"));
0433 
0434     projectController->closeProject(project);
0435     provider.reset();
0436     QVERIFY(!provider.itemCount());
0437 }
0438 
0439 #include "moc_test_quickopen.cpp"