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