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"