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 }