File indexing completed on 2024-05-19 05:42:02
0001 // ct_lvtclp_filesystemscanner.t.cpp -*-C++-*- 0002 0003 /* 0004 // Copyright 2023 Codethink Ltd <codethink@codethink.co.uk> 0005 // SPDX-License-Identifier: Apache-2.0 0006 // 0007 // Licensed under the Apache License, Version 2.0 (the "License"); 0008 // you may not use this file except in compliance with the License. 0009 // You may obtain a copy of the License at 0010 // 0011 // http://www.apache.org/licenses/LICENSE-2.0 0012 // 0013 // Unless required by applicable law or agreed to in writing, software 0014 // distributed under the License is distributed on an "AS IS" BASIS, 0015 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 0016 // See the License for the specific language governing permissions and 0017 // limitations under the License. 0018 */ 0019 0020 #include <ct_lvtclp_filesystemscanner.h> 0021 0022 #include <ct_lvtclp_cpp_tool.h> 0023 #include <ct_lvtclp_testutil.h> 0024 0025 #include <ct_lvtmdb_objectstore.h> 0026 0027 #include <QtGlobal> 0028 #include <catch2-local-includes.h> 0029 #include <filesystem> 0030 #include <fstream> 0031 #include <initializer_list> 0032 0033 #include <ct_lvtmdb_componentobject.h> 0034 #include <ct_lvtmdb_fileobject.h> 0035 #include <ct_lvtmdb_packageobject.h> 0036 #include <ct_lvtmdb_repositoryobject.h> 0037 #include <ct_lvtmdb_soci_reader.h> 0038 #include <ct_lvtmdb_soci_writer.h> 0039 0040 #include <pybind11/embed.h> 0041 #include <pybind11/pybind11.h> 0042 0043 using namespace Codethink; 0044 0045 using Codethink::lvtclp::CppTool; 0046 using Codethink::lvtclp::FilesystemScanner; 0047 using Codethink::lvtclp::StaticCompilationDatabase; 0048 using Codethink::lvtclp::Test_Util; 0049 0050 const PyDefaultGilReleasedContext defaultGilContextForTesting; 0051 0052 const auto messageCallback = [](const std::string&, long) {}; 0053 0054 static void createComponent(const std::filesystem::path& dir, const std::string& name, const std::string& prefix = "") 0055 { 0056 for (const char *ext : {".h", ".cpp", ".t.cpp"}) { 0057 std::string pkgname = dir.filename().string(); 0058 std::string fileName; 0059 fileName.append(pkgname).append("_").append(name).append(ext); 0060 if (!prefix.empty()) { 0061 std::string newFileName; 0062 newFileName.append(prefix).append("_").append(fileName); 0063 fileName = std::move(newFileName); 0064 } 0065 0066 REQUIRE(Test_Util::createFile(dir / fileName)); 0067 } 0068 } 0069 0070 static void createTestEnv(const std::filesystem::path& topLevel) 0071 { 0072 if (std::filesystem::exists(topLevel)) { 0073 std::filesystem::remove_all(topLevel); 0074 } 0075 0076 REQUIRE(std::filesystem::create_directories(topLevel / "groups/one/onepkg")); 0077 REQUIRE(std::filesystem::create_directories(topLevel / "groups/two/twofoo")); 0078 REQUIRE(std::filesystem::create_directories(topLevel / "groups/two/twobar")); 0079 REQUIRE(std::filesystem::create_directories(topLevel / "unrelated")); 0080 REQUIRE(std::filesystem::create_directories(topLevel / "groups/one/doc")); 0081 REQUIRE(std::filesystem::create_directories(topLevel / "groups/pkgnme")); 0082 REQUIRE(std::filesystem::create_directories(topLevel / "standalones/s_pkgtst")); 0083 0084 createComponent(topLevel / "groups/one/onepkg", "foo"); 0085 createComponent(topLevel / "groups/one/onepkg", "bar"); 0086 createComponent(topLevel / "groups/two/twofoo", "component"); 0087 createComponent(topLevel / "groups/two/twobar", "component"); 0088 createComponent(topLevel / "standalones/s_pkgtst", "somecmp"); 0089 0090 REQUIRE(Test_Util::createFile(topLevel / "unrelated/test.cpp")); 0091 } 0092 0093 static bool compareResultList(std::vector<std::string> expected, std::vector<std::string> result) 0094 { 0095 if (expected.size() != result.size()) { 0096 return false; 0097 } 0098 0099 std::sort(expected.begin(), expected.end()); 0100 std::sort(result.begin(), result.end()); 0101 return expected == result; 0102 } 0103 0104 struct FilesystemScannerFixture { 0105 FilesystemScannerFixture(): topLevel(std::filesystem::temp_directory_path() / "lvtclp_filesystemscanner_test") 0106 { 0107 if (std::filesystem::exists(topLevel)) { 0108 REQUIRE(std::filesystem::remove_all(topLevel)); 0109 } 0110 } 0111 0112 ~FilesystemScannerFixture() 0113 { 0114 if (std::filesystem::exists(topLevel)) { 0115 REQUIRE(std::filesystem::remove_all(topLevel)); 0116 } 0117 } 0118 0119 std::filesystem::path topLevel; 0120 }; 0121 0122 TEST_CASE_METHOD(FilesystemScannerFixture, "Filesystem scanner") 0123 { 0124 createTestEnv(topLevel); 0125 0126 Codethink::lvtclp::StaticCompilationDatabase cdb( 0127 { 0128 {(topLevel / "groups/one/onepkg/onepkg_foo.cpp").string(), ""}, 0129 {(topLevel / "groups/one/onepkg/onepkg_bar.cpp").string(), ""}, 0130 {(topLevel / "groups/two/twofoo/twofoo_component.cpp").string(), ""}, 0131 {(topLevel / "groups/two/twobar/twobar_component.cpp").string(), ""}, 0132 {(topLevel / "standalones/s_pkgtst/s_pkgtst_somecmp.cpp").string(), ""}, 0133 }, 0134 "", 0135 {}, 0136 topLevel); 0137 0138 // Test we can parse okay when everything is new to the database 0139 { 0140 std::filesystem::remove("test.db"); 0141 0142 lvtmdb::ObjectStore memDb; 0143 FilesystemScanner scanner(memDb, topLevel, cdb, messageCallback, false, {}, {}); 0144 FilesystemScanner::IncrementalResult res = scanner.scanCompilationDb(); 0145 0146 lvtmdb::SociWriter writer; 0147 writer.createOrOpen("test.db"); 0148 memDb.writeToDatabase(writer); 0149 0150 // The old code tested if things are saved in the database. this new 0151 // one takes a different approach (because checking if something is ) 0152 // in the database will need direct access to soci or to a underlying 0153 // database package - but we have the SociReader, that should read 0154 // everything from a database to an ObjectStore. so, clear the original 0155 // store, and reload from disk, test if things still exist there. 0156 memDb.withRWLock([&] { 0157 memDb.clear(); 0158 }); 0159 0160 REQUIRE(memDb.packages().empty()); 0161 REQUIRE(memDb.components().empty()); 0162 0163 lvtmdb::SociReader reader; 0164 auto ret = memDb.readFromDatabase(reader, "test.db"); 0165 REQUIRE(ret.has_value()); 0166 0167 auto lock = memDb.rwLock(); 0168 (void) lock; 0169 0170 // package groups 0171 auto *one = memDb.getPackage("groups/one"); 0172 REQUIRE(one); 0173 auto *two = memDb.getPackage("groups/two"); 0174 REQUIRE(two); 0175 auto *standalonepkg = memDb.getPackage("standalones/s_pkgtst"); 0176 REQUIRE(standalonepkg); 0177 0178 // packages 0179 auto *onepkg = memDb.getPackage("groups/one/onepkg"); 0180 REQUIRE(onepkg); 0181 auto onepkg_lock = onepkg->readOnlyLock(); 0182 (void) onepkg_lock; 0183 REQUIRE(onepkg->parent()); 0184 auto parent_lock = onepkg->parent()->readOnlyLock(); 0185 (void) parent_lock; 0186 REQUIRE(onepkg->parent()->qualifiedName() == "groups/one"); 0187 0188 auto *twofoo = memDb.getPackage("groups/two/twofoo"); 0189 REQUIRE(twofoo); 0190 0191 auto two_lock = twofoo->readOnlyLock(); 0192 (void) two_lock; 0193 REQUIRE(twofoo->parent()); 0194 0195 auto two_foo_parent_lock = twofoo->parent()->readOnlyLock(); 0196 (void) two_foo_parent_lock; 0197 REQUIRE(twofoo->parent()->qualifiedName() == "groups/two"); 0198 0199 auto *twobar = memDb.getPackage("groups/two/twobar"); 0200 REQUIRE(twobar); 0201 0202 auto two_bar_lock = twobar->readOnlyLock(); 0203 (void) two_bar_lock; 0204 REQUIRE(twobar->parent()); 0205 0206 auto two_bar_parent_lock = twobar->parent()->readOnlyLock(); 0207 (void) two_bar_parent_lock; 0208 REQUIRE(twobar->parent()->qualifiedName() == "groups/two"); 0209 0210 // files 0211 auto *foo_comp = memDb.getComponent("groups/one/onepkg/onepkg_foo"); 0212 auto *bar_comp = memDb.getComponent("groups/one/onepkg/onepkg_bar"); 0213 auto *other_comp = memDb.getComponent("groups/two/twofoo/twofoo_component"); 0214 auto *twobar_comp = memDb.getComponent("groups/two/twobar/twobar_component"); 0215 auto *standalone_comp = memDb.getComponent("standalones/s_pkgtst/s_pkgtst_somecmp"); 0216 auto *null_pkg = memDb.getComponent("unrelated"); 0217 auto *null_pkg_2 = memDb.getComponent("doc"); 0218 auto *null_pkg_3 = memDb.getComponent("pkgname"); 0219 0220 REQUIRE(foo_comp); 0221 REQUIRE(bar_comp); 0222 REQUIRE(other_comp); 0223 REQUIRE(twobar_comp); 0224 REQUIRE(standalone_comp); 0225 REQUIRE_FALSE(null_pkg); 0226 REQUIRE_FALSE(null_pkg_2); 0227 REQUIRE_FALSE(null_pkg_3); 0228 0229 auto checkComp = [](lvtmdb::ComponentObject *comp, lvtmdb::PackageObject *const supposedParent) { 0230 auto lock = comp->readOnlyLock(); 0231 (void) lock; 0232 REQUIRE(comp->package() == supposedParent); 0233 }; 0234 0235 checkComp(foo_comp, onepkg); 0236 checkComp(bar_comp, onepkg); 0237 checkComp(other_comp, twofoo); 0238 checkComp(twobar_comp, twobar); 0239 checkComp(standalone_comp, standalonepkg); 0240 0241 // IncrementalResult 0242 REQUIRE(compareResultList({"groups/two/twofoo/twofoo_component.cpp", 0243 "groups/two/twofoo/twofoo_component.h", 0244 "groups/two/twobar/twobar_component.h", 0245 "groups/two/twobar/twobar_component.cpp", 0246 "groups/one/onepkg/onepkg_foo.h", 0247 "groups/one/onepkg/onepkg_bar.h", 0248 "groups/one/onepkg/onepkg_foo.cpp", 0249 "groups/one/onepkg/onepkg_bar.cpp", 0250 "standalones/s_pkgtst/s_pkgtst_somecmp.cpp", 0251 "standalones/s_pkgtst/s_pkgtst_somecmp.h"}, 0252 res.newFiles)); 0253 REQUIRE(res.modifiedFiles.empty()); 0254 REQUIRE(res.deletedFiles.empty()); 0255 0256 REQUIRE(compareResultList({"groups/one", 0257 "groups/two", 0258 "groups/one/onepkg", 0259 "groups/two/twofoo", 0260 "groups/two/twobar", 0261 "standalones/s_pkgtst"}, 0262 res.newPkgs)); 0263 REQUIRE(res.deletedPkgs.empty()); 0264 } 0265 0266 // Test nothing is added when reparsing the same files with an already 0267 // populated database 0268 { 0269 lvtmdb::ObjectStore memDb; 0270 0271 lvtmdb::SociReader reader; 0272 auto ret = memDb.readFromDatabase(reader, "test.db"); 0273 REQUIRE(ret.has_value()); 0274 0275 FilesystemScanner scanner(memDb, topLevel, cdb, messageCallback, false, {}, {}); 0276 FilesystemScanner::IncrementalResult res = scanner.scanCompilationDb(); 0277 0278 // nothing changed: 0279 REQUIRE(res.newFiles.empty()); 0280 REQUIRE(res.modifiedFiles.empty()); 0281 REQUIRE(res.deletedFiles.empty()); 0282 REQUIRE(res.newPkgs.empty()); 0283 REQUIRE(res.deletedPkgs.empty()); 0284 } 0285 0286 // Test a deleted file is picked up 0287 { 0288 const char *file = "groups/one/onepkg/onepkg_foo.cpp"; 0289 0290 createTestEnv(topLevel); 0291 0292 // delete a file 0293 REQUIRE(std::filesystem::remove(topLevel / file)); 0294 0295 lvtmdb::ObjectStore memDb; 0296 lvtmdb::SociReader reader; 0297 auto ret = memDb.readFromDatabase(reader, "test.db"); 0298 REQUIRE(ret.has_value()); 0299 0300 // scan 0301 FilesystemScanner scanner(memDb, topLevel, cdb, messageCallback, false, {}, {}); 0302 FilesystemScanner::IncrementalResult res = scanner.scanCompilationDb(); 0303 0304 { 0305 // file was deleted 0306 REQUIRE(compareResultList({file}, res.deletedFiles)); 0307 0308 // nothing else changed 0309 REQUIRE(res.newFiles.empty()); 0310 REQUIRE(res.modifiedFiles.empty()); 0311 REQUIRE(res.newPkgs.empty()); 0312 REQUIRE(res.deletedPkgs.empty()); 0313 0314 // In the next step we add the deleted file back again and see if the 0315 // scanner finds it. The scanner spots new files by comparing with the 0316 // database so we first need to delete the deleted file from the database 0317 for (auto deletedFile : res.deletedFiles) { 0318 memDb.withRWLock([&] { 0319 auto *memFile = memDb.getFile(deletedFile); 0320 std::set<intptr_t> removed; 0321 memDb.removeFile(memFile, removed); 0322 }); 0323 } 0324 } 0325 0326 // add deleted file back again 0327 REQUIRE(Test_Util::createFile(topLevel / file)); 0328 0329 // scan (testing re-using a scanner instance) 0330 res = scanner.scanCompilationDb(); 0331 0332 // file was added back 0333 REQUIRE(compareResultList({file}, res.newFiles)); 0334 0335 // nothing else changed 0336 REQUIRE(res.modifiedFiles.empty()); 0337 REQUIRE(res.deletedFiles.empty()); 0338 REQUIRE(res.newPkgs.empty()); 0339 REQUIRE(res.deletedPkgs.empty()); 0340 } 0341 0342 // Test that we detect file modification 0343 { 0344 const char *file = "groups/two/twobar/twobar_component.cpp"; 0345 0346 createTestEnv(topLevel); 0347 0348 // modify file 0349 std::ofstream ofile((topLevel / file).c_str()); 0350 ofile << "namespace Codethink { }" << std::endl; 0351 ofile.close(); 0352 0353 // scan 0354 lvtmdb::ObjectStore memDb; 0355 lvtmdb::SociReader reader; 0356 auto ret = memDb.readFromDatabase(reader, "test.db"); 0357 REQUIRE(ret.has_value()); 0358 0359 FilesystemScanner scanner(memDb, topLevel, cdb, messageCallback, false, {}, {}); 0360 FilesystemScanner::IncrementalResult res = scanner.scanCompilationDb(); 0361 0362 // file was modified 0363 REQUIRE(compareResultList({file}, res.modifiedFiles)); 0364 0365 // nothing else changed 0366 REQUIRE(res.newFiles.empty()); 0367 REQUIRE(res.deletedFiles.empty()); 0368 REQUIRE(res.newPkgs.empty()); 0369 REQUIRE(res.deletedPkgs.empty()); 0370 0371 // change file back again 0372 REQUIRE(std::filesystem::remove(topLevel / file)); 0373 REQUIRE(Test_Util::createFile(topLevel / file)); 0374 0375 // re-scan 0376 res = scanner.scanCompilationDb(); 0377 0378 // file was modified 0379 REQUIRE(compareResultList({file}, res.modifiedFiles)); 0380 0381 // nothing else changed 0382 REQUIRE(res.newFiles.empty()); 0383 REQUIRE(res.deletedFiles.empty()); 0384 REQUIRE(res.newPkgs.empty()); 0385 REQUIRE(res.deletedPkgs.empty()); 0386 } 0387 0388 // delete a package and add it back again 0389 { 0390 createTestEnv(topLevel); 0391 // scan 0392 lvtmdb::ObjectStore memDb; 0393 0394 // Scan original files. 0395 FilesystemScanner scanner(memDb, topLevel, cdb, messageCallback, false, {}, {}); 0396 FilesystemScanner::IncrementalResult res = scanner.scanCompilationDb(); 0397 0398 REQUIRE(compareResultList({"groups/two", 0399 "groups/one", 0400 "groups/one/onepkg", 0401 "groups/two/twofoo", 0402 "groups/two/twobar", 0403 "standalones/s_pkgtst"}, 0404 res.newPkgs)); 0405 0406 // delete a package 0407 REQUIRE(std::filesystem::remove_all(topLevel / "groups/one/onepkg")); 0408 0409 res = scanner.scanCompilationDb(); 0410 REQUIRE(res.newFiles.empty()); 0411 REQUIRE(res.modifiedFiles.empty()); 0412 REQUIRE(compareResultList({"groups/one/onepkg/onepkg_foo.h", 0413 "groups/one/onepkg/onepkg_bar.h", 0414 "groups/one/onepkg/onepkg_foo.cpp", 0415 "groups/one/onepkg/onepkg_bar.cpp"}, 0416 res.deletedFiles)); 0417 0418 REQUIRE(res.newPkgs.empty()); 0419 REQUIRE(compareResultList({"groups/one/onepkg", "groups/one"}, res.deletedPkgs)); 0420 0421 for (auto file : res.deletedFiles) { 0422 lvtmdb::FileObject *memFile = nullptr; 0423 memDb.withROLock([&] { 0424 memFile = memDb.getFile(file); 0425 }); 0426 std::set<intptr_t> removed; 0427 memDb.removeFile(memFile, removed); 0428 } 0429 for (auto pkg : res.deletedPkgs) { 0430 lvtmdb::PackageObject *memPkg = nullptr; 0431 0432 memDb.withROLock([&] { 0433 memPkg = memDb.getPackage(pkg); 0434 }); 0435 std::set<intptr_t> removed; 0436 memDb.removePackage(memPkg, removed); 0437 } 0438 0439 // add package back again 0440 createTestEnv(topLevel); 0441 // re-scan 0442 res = scanner.scanCompilationDb(); 0443 0444 REQUIRE(compareResultList({"groups/one/onepkg/onepkg_foo.h", 0445 "groups/one/onepkg/onepkg_bar.h", 0446 "groups/one/onepkg/onepkg_foo.cpp", 0447 "groups/one/onepkg/onepkg_bar.cpp"}, 0448 res.newFiles)); 0449 REQUIRE(res.modifiedFiles.empty()); 0450 REQUIRE(res.deletedFiles.empty()); 0451 0452 for (const auto& pkg : res.newPkgs) { 0453 std::cout << "New packages added " << pkg << "\n"; 0454 } 0455 0456 REQUIRE(compareResultList({"groups/one", "groups/one/onepkg"}, res.newPkgs)); 0457 REQUIRE(res.deletedPkgs.empty()); 0458 } 0459 0460 // delete a package group and add it back again 0461 { 0462 std::cout << "---------------------\n"; 0463 createTestEnv(topLevel); 0464 0465 // scan 0466 lvtmdb::ObjectStore memDb; 0467 FilesystemScanner scanner(memDb, topLevel, cdb, messageCallback, false, {}, {}); 0468 FilesystemScanner::IncrementalResult res = scanner.scanCompilationDb(); 0469 0470 // delete a package group 0471 REQUIRE(std::filesystem::remove_all(topLevel / "groups/two")); 0472 0473 res = scanner.scanCompilationDb(); 0474 0475 REQUIRE(res.newFiles.empty()); 0476 REQUIRE(res.modifiedFiles.empty()); 0477 REQUIRE(compareResultList({"groups/two/twofoo/twofoo_component.cpp", 0478 "groups/two/twofoo/twofoo_component.h", 0479 "groups/two/twobar/twobar_component.h", 0480 "groups/two/twobar/twobar_component.cpp"}, 0481 res.deletedFiles)); 0482 0483 REQUIRE(res.newPkgs.empty()); 0484 REQUIRE(compareResultList({"groups/two", "groups/two/twofoo", "groups/two/twobar"}, res.deletedPkgs)); 0485 0486 // delete package from the database so the next step works okay 0487 for (auto file : res.deletedFiles) { 0488 lvtmdb::FileObject *memFile = nullptr; 0489 memDb.withROLock([&] { 0490 memFile = memDb.getFile(file); 0491 }); 0492 std::set<intptr_t> removed; 0493 memDb.removeFile(memFile, removed); 0494 } 0495 for (auto pkg : res.deletedPkgs) { 0496 lvtmdb::PackageObject *memPkg = nullptr; 0497 0498 memDb.withROLock([&] { 0499 memPkg = memDb.getPackage(pkg); 0500 }); 0501 std::set<intptr_t> removed; 0502 memDb.removePackage(memPkg, removed); 0503 } 0504 0505 // add package group back again 0506 createTestEnv(topLevel); 0507 0508 // re-scan 0509 res = scanner.scanCompilationDb(); 0510 0511 REQUIRE(compareResultList({"groups/two/twofoo/twofoo_component.cpp", 0512 "groups/two/twofoo/twofoo_component.h", 0513 "groups/two/twobar/twobar_component.h", 0514 "groups/two/twobar/twobar_component.cpp"}, 0515 res.newFiles)); 0516 REQUIRE(res.modifiedFiles.empty()); 0517 REQUIRE(res.deletedFiles.empty()); 0518 0519 REQUIRE(compareResultList({"groups/two", "groups/two/twofoo", "groups/two/twobar"}, res.newPkgs)); 0520 REQUIRE(res.deletedPkgs.empty()); 0521 } 0522 } 0523 0524 TEST_CASE_METHOD(FilesystemScannerFixture, "Non Lakosian") 0525 { 0526 // create a partially lakosian hierarchy: 0527 // groups/one/onepkg/onepkg_foo.{cpp,h} 0528 // groups/one/onepkg/onepkg_bar.{cpp,h} 0529 // thirdparty/nonlakosian.cpp 0530 // include/nonlakosian.hpp 0531 REQUIRE(std::filesystem::create_directories(topLevel / "groups/one/onepkg")); 0532 REQUIRE(std::filesystem::create_directories(topLevel / "thirdparty")); 0533 REQUIRE(std::filesystem::create_directories(topLevel / "include")); 0534 0535 createComponent(topLevel / "groups/one/onepkg", "foo"); 0536 createComponent(topLevel / "groups/one/onepkg", "bar"); 0537 0538 REQUIRE(Test_Util::createFile(topLevel / "thirdparty/nonlakosian.cpp")); 0539 REQUIRE(Test_Util::createFile(topLevel / "include/nonlakosian.hpp")); 0540 0541 Codethink::lvtclp::StaticCompilationDatabase cdb( 0542 { 0543 {(topLevel / "groups/one/onepkg/onepkg_foo.cpp").string(), ""}, 0544 {(topLevel / "groups/one/onepkg/onepkg_bar.cpp").string(), ""}, 0545 {(topLevel / "thirdparty/nonlakosian.cpp").string(), ""}, 0546 }, 0547 "compiler", 0548 {"--unrelated", "-I../include", "-I/not/a/path"}, 0549 topLevel); 0550 0551 // scan 0552 lvtmdb::ObjectStore memDb; 0553 0554 FilesystemScanner scanner(memDb, topLevel, cdb, messageCallback, false, {topLevel / "thirdparty"}, {}); 0555 FilesystemScanner::IncrementalResult res = scanner.scanCompilationDb(); 0556 0557 // Dump to disk. 0558 lvtmdb::SociWriter writer; 0559 std::filesystem::remove("test.db"); 0560 writer.createOrOpen("test.db"); 0561 memDb.writeToDatabase(writer); 0562 0563 // Clear the current memory 0564 memDb.withRWLock([&] { 0565 memDb.clear(); 0566 }); 0567 0568 // Reload from disk. 0569 lvtmdb::SociReader reader; 0570 auto ret = memDb.readFromDatabase(reader, "test.db"); 0571 REQUIRE(ret.has_value()); 0572 0573 // package groups 0574 auto memDblock = memDb.readOnlyLock(); 0575 (void) memDblock; 0576 0577 auto *one = memDb.getPackage("groups/one"); 0578 REQUIRE(one); 0579 0580 auto *nonLakosian = memDb.getPackage(lvtclp::ClpUtil::NON_LAKOSIAN_GROUP_NAME); 0581 REQUIRE(nonLakosian); 0582 0583 // packages 0584 auto *onepkg = memDb.getPackage("groups/one/onepkg"); 0585 REQUIRE(onepkg); 0586 auto lock = onepkg->readOnlyLock(); 0587 (void) lock; 0588 REQUIRE(onepkg->parent()); 0589 REQUIRE(onepkg->parent() == one); 0590 0591 auto *thirdparty = memDb.getPackage("thirdparty"); 0592 REQUIRE(thirdparty); 0593 auto thirdparty_lock = thirdparty->readOnlyLock(); 0594 (void) thirdparty_lock; 0595 REQUIRE(thirdparty->parent() == nonLakosian); 0596 0597 // lakosian files 0598 for (const auto& [_1, comp] : memDb.components()) { 0599 comp->withROLock([&comp = comp] { 0600 std::cout << "Component name: " << comp->qualifiedName() << "\n"; 0601 }); 0602 } 0603 REQUIRE(memDb.getComponent("groups/one/onepkg/onepkg_foo")); 0604 REQUIRE(memDb.getComponent("groups/one/onepkg/onepkg_bar")); 0605 0606 // non-lakosian files 0607 lvtmdb::FileObject *source = memDb.getFile("thirdparty/nonlakosian.cpp"); 0608 REQUIRE(source); 0609 0610 source->withROLock([&] { 0611 REQUIRE(source->package() == thirdparty); 0612 }); 0613 0614 REQUIRE(memDb.getFile("include/nonlakosian.hpp")); 0615 0616 // currently the header would be in a package called "include" inside 0617 // "non-lakosian group". Ideally it would be in "thirdparty". Not testing 0618 // this now in case it is fixed later 0619 } 0620 0621 class FSNonLakosianFixture { 0622 public: 0623 // public data for Catch2 magic 0624 const std::filesystem::path d_topLevel; 0625 const std::filesystem::path d_submodules; 0626 const std::filesystem::path d_configurationParser; 0627 const std::filesystem::path d_main; 0628 const std::filesystem::path d_qsettings; 0629 const std::filesystem::path d_dumpQsettings; 0630 const std::filesystem::path d_lvtclp; 0631 const std::filesystem::path d_filesystemScanner; 0632 0633 FSNonLakosianFixture(): 0634 d_topLevel(std::filesystem::temp_directory_path() / "lvtclp_testfilesystemscanner"), 0635 d_submodules(d_topLevel / "submodules"), 0636 d_configurationParser(d_submodules / "configuration-parser"), 0637 d_main(d_configurationParser / "main.cpp"), 0638 d_qsettings(d_configurationParser / "qsettings"), 0639 d_dumpQsettings(d_qsettings / "dump_qsettings.cpp"), 0640 d_lvtclp(d_topLevel / "lvtclp"), 0641 d_filesystemScanner(d_lvtclp / "lvtclp_filesystemScanner.cpp") 0642 { 0643 if (std::filesystem::exists(d_topLevel)) { 0644 REQUIRE(std::filesystem::remove_all(d_topLevel)); 0645 } 0646 0647 REQUIRE(std::filesystem::create_directories(d_configurationParser)); 0648 REQUIRE(Test_Util::createFile(d_main)); 0649 0650 REQUIRE(std::filesystem::create_directories(d_qsettings)); 0651 REQUIRE(Test_Util::createFile(d_dumpQsettings)); 0652 0653 REQUIRE(std::filesystem::create_directories(d_lvtclp)); 0654 REQUIRE(Test_Util::createFile(d_filesystemScanner)); 0655 } 0656 0657 ~FSNonLakosianFixture() 0658 { 0659 REQUIRE(std::filesystem::remove_all(d_topLevel)); 0660 } 0661 }; 0662 0663 TEST_CASE_METHOD(FSNonLakosianFixture, "Non-lakosian extra levels of hierarchy") 0664 { 0665 StaticCompilationDatabase cmds( 0666 {{d_main.string(), ""}, {d_dumpQsettings.string(), ""}, {d_filesystemScanner.string(), ""}}, 0667 "placeholder-g++", 0668 {}, 0669 d_topLevel); 0670 0671 CppTool tool(d_topLevel, cmds, ":memory:"); 0672 // regression test: filsystem scanner should not crash 0673 REQUIRE(tool.runPhysical()); 0674 } 0675 0676 TEST_CASE_METHOD(FilesystemScannerFixture, "Semantic packing") 0677 { 0678 createTestEnv(topLevel); 0679 auto semRulesPath = topLevel / "semrules"; 0680 REQUIRE(std::filesystem::create_directories(semRulesPath)); 0681 0682 auto filePath = semRulesPath / "myrules.py"; 0683 if (std::filesystem::exists(filePath)) { 0684 std::filesystem::remove(filePath); 0685 } 0686 0687 auto scriptStream = std::ofstream(filePath.string()); 0688 const std::string SCRIPT_CONTENTS = 0689 "\ndef accept(path):" 0690 "\n return True" 0691 "\n" 0692 "\ndef process(path, addPkg):" 0693 "\n if 'groups' in path:" 0694 "\n addPkg('MyPkg', 'MyPkgGrp', 'MyRepo', None)" 0695 "\n return 'MyPkg'" 0696 "\n else:" 0697 "\n addPkg('MyStandalone', None, 'MyRepo', None)" 0698 "\n return 'MyStandalone'" 0699 "\n"; 0700 scriptStream << SCRIPT_CONTENTS; 0701 scriptStream.close(); 0702 0703 REQUIRE(std::ifstream{filePath.string()}.good()); 0704 REQUIRE(qputenv("SEMRULES_PATH", semRulesPath.string().c_str())); 0705 0706 Codethink::lvtclp::StaticCompilationDatabase cdb( 0707 { 0708 {(topLevel / "groups/one/onepkg/onepkg_foo.cpp").string(), ""}, 0709 {(topLevel / "groups/one/onepkg/onepkg_bar.cpp").string(), ""}, 0710 {(topLevel / "groups/two/twofoo/twofoo_component.cpp").string(), ""}, 0711 {(topLevel / "groups/two/twobar/twobar_component.cpp").string(), ""}, 0712 {(topLevel / "standalones/s_pkgtst/s_pkgtst_somecmp.cpp").string(), ""}, 0713 }, 0714 "", 0715 {}, 0716 topLevel); 0717 0718 lvtmdb::ObjectStore memDb; 0719 0720 FilesystemScanner scanner(memDb, topLevel, cdb, messageCallback, false, {}, {}); 0721 FilesystemScanner::IncrementalResult res = scanner.scanCompilationDb(); 0722 auto lock = memDb.readOnlyLock(); 0723 0724 lvtmdb::RepositoryObject *repo = memDb.getRepository("MyRepo"); 0725 0726 REQUIRE(repo); 0727 repo->withROLock([&] { 0728 REQUIRE(repo->children().size() == 3); 0729 }); 0730 0731 // lakosian files 0732 for (const auto& [_1, pkg] : memDb.packages()) { 0733 pkg->withROLock([&pkg = pkg] { 0734 std::cout << "Component name: " << pkg->qualifiedName() << "\n"; 0735 }); 0736 } 0737 0738 lvtmdb::PackageObject *myPkg = memDb.getPackage("MyPkg"); 0739 0740 myPkg->withROLock([&] { 0741 REQUIRE(myPkg->qualifiedName() == "MyPkg"); 0742 REQUIRE(myPkg->components().size() == 4); 0743 0744 for (auto *comp : myPkg->components()) { 0745 comp->withROLock([&] { 0746 auto qname = comp->qualifiedName(); 0747 INFO(qname); 0748 REQUIRE((qname == "groups/one/onepkg/onepkg_foo" || qname == "groups/one/onepkg/onepkg_bar" 0749 || qname == "groups/two/twofoo/twofoo_component" 0750 || qname == "groups/two/twobar/twobar_component" 0751 || qname == "standalones/s_pkgtst/s_pkgtst_somecmp")); 0752 }); 0753 } 0754 }); 0755 0756 lvtmdb::PackageObject *myStandalones = memDb.getPackage("MyStandalone"); 0757 myStandalones->withROLock([&] { 0758 REQUIRE(myStandalones->qualifiedName() == "MyStandalone"); 0759 REQUIRE(myStandalones->components().size() == 1); 0760 for (lvtmdb::ComponentObject *comp : myStandalones->components()) { 0761 comp->withROLock([&] { 0762 auto qname = comp->qualifiedName(); 0763 INFO(qname); 0764 REQUIRE((qname == "standalones/s_pkgtst/s_pkgtst_somecmp")); 0765 }); 0766 } 0767 }); 0768 0769 REQUIRE(qunsetenv("SEMRULES_PATH")); 0770 }