File indexing completed on 2024-05-19 05:42:05

0001 // ct_lvtclp_testutil.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_testutil.h>
0021 
0022 #include <ct_lvtclp_logicaldepscanner.h>
0023 #include <ct_lvtclp_logicalpostprocessutil.h>
0024 
0025 #include <ct_lvtmdb_componentobject.h>
0026 #include <ct_lvtmdb_fieldobject.h>
0027 #include <ct_lvtmdb_fileobject.h>
0028 #include <ct_lvtmdb_methodobject.h>
0029 #include <ct_lvtmdb_namespaceobject.h>
0030 #include <ct_lvtmdb_objectstore.h>
0031 #include <ct_lvtmdb_packageobject.h>
0032 #include <ct_lvtmdb_typeobject.h>
0033 
0034 #include <clang/Tooling/Tooling.h>
0035 
0036 #include <QDebug>
0037 
0038 #include <algorithm>
0039 #include <cassert>
0040 #include <catch2-local-includes.h>
0041 #include <fstream>
0042 #include <initializer_list>
0043 #include <vector>
0044 
0045 namespace {
0046 
0047 template<class C>
0048 bool qnameVectorMatches(const std::vector<C *>& ptrs, const std::vector<std::string>& expectedUnsorted)
0049 {
0050     if (ptrs.size() != expectedUnsorted.size()) {
0051         qDebug() << "qnameVectorMatches FAILED. Sizes didn't match: Expected size " << expectedUnsorted.size()
0052                  << ", obtained " << ptrs.size();
0053 
0054         qDebug() << "qname mismatch. Expect:\n";
0055         for (const auto& src_include : std::as_const(expectedUnsorted)) {
0056             qDebug() << "\t" << src_include.c_str();
0057         }
0058 
0059         assert(false);
0060         return false;
0061     }
0062 
0063     std::vector<std::string> qnames;
0064     qnames.reserve(ptrs.size());
0065 
0066     std::transform(ptrs.begin(), ptrs.end(), std::back_inserter(qnames), [](const auto& ptr) {
0067         auto lock = ptr->readOnlyLock();
0068         return ptr->qualifiedName();
0069     });
0070 
0071     // sort so we don't flag things being in different orders
0072     std::sort(qnames.begin(), qnames.end());
0073     std::vector<std::string> expected(expectedUnsorted.begin(), expectedUnsorted.end());
0074     std::sort(expected.begin(), expected.end());
0075 
0076     bool res = std::equal(expected.begin(), expected.end(), qnames.begin());
0077     if (!res) {
0078         qDebug() << "Vector matches? " << res;
0079 
0080         qDebug() << "qname mismatch. Expect:\n";
0081         for (const auto& src_include : std::as_const(expected)) {
0082             qDebug() << "\t" << src_include.c_str();
0083         }
0084         qDebug() << "Obtained:\n";
0085         for (const auto& src_include : std::as_const(qnames)) {
0086             qDebug() << "\t" << src_include.c_str();
0087         }
0088     }
0089     return res;
0090 }
0091 
0092 template<class C>
0093 bool ptrMatches(C *ptr, const std::string& expected)
0094 {
0095     if (ptr) {
0096         bool matches = true;
0097         ptr->withROLock([&] {
0098             if (ptr->qualifiedName() != expected) {
0099                 qDebug() << "ptrMatches FAILED. Expected '" << expected.c_str() << "', obtained '"
0100                          << ptr->qualifiedName().c_str();
0101                 matches = false;
0102             }
0103         });
0104         return matches;
0105     }
0106 
0107     // ptr is null
0108     return expected.empty();
0109 }
0110 
0111 } // namespace
0112 
0113 namespace Codethink::lvtclp {
0114 
0115 bool Test_Util::runOnCode(lvtmdb::ObjectStore& mdb, const std::string& source, const std::string& fileName = "file.cpp")
0116 {
0117     auto callback = [](const std::string&) {};
0118     auto messageCallback = [](const std::string&, long) {};
0119 
0120     auto prefix = std::filesystem::current_path();
0121     LogicalDepActionFactory actionFactory(mdb, prefix, {}, {}, callback, messageCallback, false);
0122 
0123     auto frontendAction = actionFactory.create();
0124 
0125     const std::vector<std::string> args{
0126         "-std=c++17", // allow nested namespaces
0127     };
0128 
0129     bool res = clang::tooling::runToolOnCodeWithArgs(std::move(frontendAction), source, args, fileName);
0130     if (!res) {
0131         return res;
0132     }
0133 
0134     return LogicalPostProcessUtil::postprocess(mdb, false);
0135 }
0136 
0137 bool Test_Util::isAExists(const std::string& derivedClassQualifiedName,
0138                           const std::string& baseClassQualifiedName,
0139                           lvtmdb::ObjectStore& session)
0140 {
0141     lvtmdb::TypeObject *base = nullptr;
0142     lvtmdb::TypeObject *derived = nullptr;
0143 
0144     session.withROLock([&] {
0145         base = session.getType(baseClassQualifiedName);
0146         derived = session.getType(derivedClassQualifiedName);
0147     });
0148 
0149     if (!base) {
0150         return false;
0151     }
0152     if (!derived) {
0153         return false;
0154     }
0155 
0156     // check forward relation
0157     std::vector<lvtmdb::TypeObject *> derivedClasses;
0158     base->withROLock([&] {
0159         derivedClasses = base->subclasses();
0160     });
0161 
0162     auto it = std::find_if(derivedClasses.begin(), derivedClasses.end(), [&derived](lvtmdb::TypeObject *const hier) {
0163         return hier == derived;
0164     });
0165 
0166     if (it == derivedClasses.end()) {
0167         return false;
0168     }
0169 
0170     // check reverse relation
0171     std::vector<lvtmdb::TypeObject *> baseClasses;
0172     derived->withROLock([&] {
0173         baseClasses = derived->superclasses();
0174     });
0175 
0176     it = std::find_if(baseClasses.begin(), baseClasses.end(), [&base](const lvtmdb::TypeObject *hier) {
0177         return hier == base;
0178     });
0179 
0180     return it != baseClasses.end();
0181 }
0182 
0183 bool Test_Util::usesInTheImplementationExists(const std::string& sourceQualifiedName,
0184                                               const std::string& targetQualifiedName,
0185                                               lvtmdb::ObjectStore& session)
0186 {
0187     lvtmdb::TypeObject *source = nullptr;
0188     lvtmdb::TypeObject *target = nullptr;
0189 
0190     session.withROLock([&] {
0191         source = session.getType(sourceQualifiedName);
0192         target = session.getType(targetQualifiedName);
0193     });
0194 
0195     if (!source) {
0196         return false;
0197     }
0198     if (!target) {
0199         return false;
0200     }
0201 
0202     // check forward relation
0203     std::vector<lvtmdb::TypeObject *> rels;
0204     source->withROLock([&] {
0205         rels = source->usesInTheImplementation();
0206     });
0207 
0208     auto it = std::find_if(rels.begin(), rels.end(), [&target](const auto& rel) {
0209         return rel == target;
0210     });
0211 
0212     if (it == rels.end()) {
0213         return false;
0214     }
0215 
0216     // check reverse relation
0217     std::vector<lvtmdb::TypeObject *> revRels;
0218     target->withROLock([&] {
0219         revRels = target->revUsesInTheImplementation();
0220     });
0221 
0222     it = std::find_if(revRels.begin(), revRels.end(), [&source](const auto& revRel) {
0223         return revRel == source;
0224     });
0225 
0226     return it != revRels.end();
0227 }
0228 
0229 bool Test_Util::usesInTheInterfaceExists(const std::string& sourceQualifiedName,
0230                                          const std::string& targetQualifiedName,
0231                                          lvtmdb::ObjectStore& session)
0232 {
0233     lvtmdb::TypeObject *source = nullptr;
0234     lvtmdb::TypeObject *target = nullptr;
0235 
0236     session.withROLock([&] {
0237         source = session.getType(sourceQualifiedName);
0238         target = session.getType(targetQualifiedName);
0239     });
0240 
0241     if (!source) {
0242         return false;
0243     }
0244 
0245     if (!target) {
0246         return false;
0247     }
0248 
0249     // check forward relation
0250     std::vector<lvtmdb::TypeObject *> rels;
0251     source->withROLock([&] {
0252         rels = source->usesInTheInterface();
0253     });
0254 
0255     auto it = std::find_if(rels.begin(), rels.end(), [&target](const auto& rel) {
0256         return rel == target;
0257     });
0258 
0259     if (it == rels.end()) {
0260         return false;
0261     }
0262 
0263     // check reverse relation
0264     std::vector<lvtmdb::TypeObject *> revRels;
0265     target->withROLock([&] {
0266         revRels = target->revUsesInTheInterface();
0267     });
0268 
0269     it = std::find_if(revRels.begin(), revRels.end(), [&source](const auto& revRel) {
0270         return revRel == source;
0271     });
0272 
0273     return it != revRels.end();
0274 }
0275 
0276 bool Test_Util::classDefinedInFile(const std::string& classQualifiedName,
0277                                    const std::string& fileQualifiedName,
0278                                    lvtmdb::ObjectStore& session)
0279 {
0280     lvtmdb::TypeObject *klass = nullptr;
0281     lvtmdb::FileObject *file = nullptr;
0282 
0283     session.withROLock([&] {
0284         klass = session.getType(classQualifiedName);
0285         file = session.getFile(fileQualifiedName);
0286     });
0287 
0288     assert(klass);
0289     assert(file);
0290 
0291     bool ret = false;
0292     klass->withROLock([&] {
0293         const std::vector<lvtmdb::FileObject *>& files = klass->files();
0294         const auto it = std::find(files.begin(), files.end(), file);
0295         ret = it != files.end();
0296     });
0297 
0298     return ret;
0299 }
0300 
0301 bool Test_Util::createFile(const std::filesystem::path& path, const std::string& contents)
0302 {
0303     if (std::filesystem::exists(path)) {
0304         if (!std::filesystem::remove(path)) {
0305             qDebug() << "Failed to remove path " << path.c_str();
0306             return false;
0307         }
0308     }
0309 
0310     std::filesystem::path parent = path.parent_path();
0311     if (!std::filesystem::exists(parent)) {
0312         std::filesystem::create_directories(parent);
0313     }
0314 
0315     std::ofstream output(path);
0316     if (output.fail()) {
0317         qDebug() << "Failed to open " << path.c_str();
0318         return false;
0319     }
0320 
0321     output << contents;
0322 
0323     return true;
0324 }
0325 
0326 StaticCompilationDatabase::StaticCompilationDatabase(std::initializer_list<clang::tooling::CompileCommand> commands,
0327                                                      std::filesystem::path directory):
0328     d_compileCommands(commands), d_directory(std::move(directory))
0329 {
0330 }
0331 
0332 StaticCompilationDatabase::StaticCompilationDatabase(std::initializer_list<std::pair<std::string, std::string>> paths,
0333                                                      const std::string& command,
0334                                                      const std::vector<std::string>& arguments,
0335                                                      std::filesystem::path directory):
0336     d_directory(std::move(directory))
0337 {
0338     d_compileCommands.reserve(paths.size());
0339 
0340     for (const auto& [inFile, outFile] : paths) {
0341         auto normalisePath = [&](const auto& path) {
0342             std::filesystem::path p(path);
0343             std::filesystem::path full = d_directory / p;
0344             return std::filesystem::weakly_canonical(full).string();
0345         };
0346 
0347         std::string normInFile = normalisePath(inFile);
0348         std::string normOutFile = normalisePath(outFile);
0349 
0350         std::vector<std::string> cmdLine;
0351         cmdLine.reserve(arguments.size() + 4);
0352 
0353         cmdLine.push_back(command);
0354         std::copy(arguments.begin(), arguments.end(), std::back_inserter(cmdLine));
0355         cmdLine.emplace_back(normInFile);
0356         cmdLine.emplace_back("-o");
0357         cmdLine.emplace_back(normOutFile);
0358 
0359         d_compileCommands.emplace_back(d_directory.string(), normInFile, std::move(cmdLine), normOutFile);
0360     }
0361 }
0362 
0363 std::vector<clang::tooling::CompileCommand>
0364 StaticCompilationDatabase::getCompileCommands(llvm::StringRef FilePath) const
0365 {
0366     for (auto const& cmd : d_compileCommands) {
0367         if (cmd.Filename == FilePath) {
0368             return {cmd};
0369         }
0370     }
0371     return {};
0372 }
0373 
0374 std::vector<std::string> StaticCompilationDatabase::getAllFiles() const
0375 {
0376     std::vector<std::string> out;
0377     out.reserve(d_compileCommands.size());
0378 
0379     std::transform(d_compileCommands.begin(),
0380                    d_compileCommands.end(),
0381                    std::back_inserter(out),
0382                    [](const clang::tooling::CompileCommand& cmd) {
0383                        return cmd.Filename;
0384                    });
0385 
0386     return out;
0387 }
0388 
0389 std::vector<clang::tooling::CompileCommand> StaticCompilationDatabase::getAllCompileCommands() const
0390 {
0391     return d_compileCommands;
0392 }
0393 
0394 bool StaticCompilationDatabase::containsFile(const std::string& file) const
0395 {
0396     const std::filesystem::path filePath = std::filesystem::weakly_canonical(d_directory / file);
0397 
0398     // this is only test code. It can be slow.
0399     std::vector<std::string> files = getAllFiles();
0400 
0401     // The compile commands only contain references to the .cpp file, so compare
0402     // filenames without the file extension so we can still find headers. Lakosian
0403     // rules don't allow headers without sources or sources without headers
0404     std::filesystem::path component(filePath);
0405     component.replace_extension();
0406 
0407     const auto it = std::find_if(files.begin(), files.end(), [&component](const std::string& str) {
0408         std::filesystem::path iterPath(str);
0409         iterPath.replace_extension();
0410 
0411         return iterPath == component;
0412     });
0413     return it != files.end();
0414 }
0415 
0416 bool StaticCompilationDatabase::containsPackage(const std::string& str) const
0417 {
0418     const std::filesystem::path pkgPath = std::filesystem::weakly_canonical(d_directory / str);
0419 
0420     // this is only test code. It can be slow.
0421     std::vector<std::string> files = getAllFiles();
0422 
0423     const auto it = std::find_if(files.begin(), files.end(), [&pkgPath](const std::string& file) {
0424         std::filesystem::path path(file);
0425 
0426         // remember paths look like
0427         // groups/grp/grppkg/grppkg_component.cpp
0428         std::filesystem::path pkg = path.parent_path();
0429         std::filesystem::path pkggrp = pkg.parent_path();
0430 
0431         return pkg == pkgPath || pkggrp == pkgPath;
0432     });
0433     return it != files.end();
0434 }
0435 
0436 void ModelUtil::checkSourceFiles(lvtmdb::ObjectStore& store,
0437                                  const std::initializer_list<ModelUtil::SourceFileModel>& files,
0438                                  bool printToConsole)
0439 {
0440     auto storeLock = store.readOnlyLock();
0441     if (printToConsole) {
0442         qDebug() << "Store Files:";
0443         for (auto *innerFile : store.getAllFiles()) {
0444             auto lock = innerFile->readOnlyLock();
0445             qDebug() << "\t" << innerFile->qualifiedName().c_str();
0446         }
0447 
0448         qDebug() << "And Expected files: \n";
0449         for (auto const& expected : files) {
0450             INFO("Processing " << expected.path);
0451             qDebug() << "\t" << expected.path.c_str();
0452         }
0453     }
0454 
0455     for (auto const& expected : files) {
0456         lvtmdb::FileObject *file = store.getFile(expected.path);
0457         REQUIRE(file != nullptr);
0458         file->withROLock([&] {
0459             REQUIRE(file->isHeader() == expected.isHeader);
0460             REQUIRE(ptrMatches(file->package(), expected.pkg));
0461             REQUIRE(ptrMatches(file->component(), expected.component));
0462             REQUIRE(qnameVectorMatches(file->namespaces(), expected.namespaces));
0463             REQUIRE(qnameVectorMatches(file->types(), expected.classes));
0464         });
0465         // REQUIRE(qnameVectorMatchesRelations(file->includeSources(), expected.includes));
0466     }
0467 }
0468 
0469 void ModelUtil::checkComponents(lvtmdb::ObjectStore& store,
0470                                 const std::initializer_list<ModelUtil::ComponentModel>& components)
0471 {
0472     auto storeLock = store.readOnlyLock();
0473     for (auto const& expected : components) {
0474         lvtmdb::ComponentObject *comp = store.getComponent(expected.qualifiedName);
0475         REQUIRE(comp != nullptr);
0476         comp->withROLock([&] {
0477             REQUIRE(comp->name() == expected.name);
0478             REQUIRE(qnameVectorMatches(comp->files(), expected.files));
0479         });
0480         // REQUIRE(qnameVectorMatchesRelations(comp->relationSources(), expected.deps));
0481     }
0482 }
0483 
0484 void ModelUtil::checkPackages(lvtmdb::ObjectStore& store, const std::initializer_list<ModelUtil::PackageModel>& pkgs)
0485 {
0486     auto lock = store.readOnlyLock();
0487     for (auto const& expected : pkgs) {
0488         INFO("Checking expected package '" << expected.name << "'...");
0489         lvtmdb::PackageObject *pkg = store.getPackage(expected.name);
0490         REQUIRE(pkg != nullptr);
0491         pkg->withROLock([&] {
0492             REQUIRE(ptrMatches(pkg->parent(), expected.parent));
0493             REQUIRE(qnameVectorMatches(pkg->types(), expected.udts));
0494             REQUIRE(qnameVectorMatches(pkg->components(), expected.components));
0495         });
0496         // REQUIRE(qnameVectorMatchesRelations(pkg->dependencies(), expected.dependencies));
0497     }
0498 }
0499 
0500 void ModelUtil::checkUDTs(lvtmdb::ObjectStore& store, const std::initializer_list<ModelUtil::UDTModel>& udts)
0501 {
0502     auto lock = store.readOnlyLock();
0503 
0504     for (auto const& expected : udts) {
0505         lvtmdb::TypeObject *udt = store.getType(expected.qualifiedName);
0506         REQUIRE(udt != nullptr);
0507         udt->withROLock([&] {
0508             REQUIRE(ptrMatches(udt->parentNamespace(), expected.nmspc));
0509             REQUIRE(ptrMatches(udt->package(), expected.pkg));
0510             // REQUIRE(qnameVectorMatchesRelations(udt->usesInTheImplementation(), expected.usesInImpl));
0511             // REQUIRE(qnameVectorMatchesRelations(udt->usesInTheInterface(), expected.usesInInter));
0512             // REQUIRE(qnameVectorMatchesRevRelations(udt->parents(), expected.isA));
0513             REQUIRE(qnameVectorMatches(udt->methods(), expected.methods));
0514             REQUIRE(qnameVectorMatches(udt->fields(), expected.fields));
0515         });
0516     }
0517 }
0518 
0519 } // namespace Codethink::lvtclp