#ifndef _WIN32
#include "ModulesBuilder.h"
#include "ScanningProjectModules.h"
#include "Annotations.h"
#include "CodeComplete.h"
#include "Compiler.h"
#include "TestTU.h"
#include "support/ThreadsafeFS.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/raw_ostream.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
namespace clang::clangd {
namespace {
class MockDirectoryCompilationDatabase : public MockCompilationDatabase {
public:
MockDirectoryCompilationDatabase(StringRef TestDir, const ThreadsafeFS &TFS)
: MockCompilationDatabase(TestDir),
MockedCDBPtr(std::make_shared<MockClangCompilationDatabase>(*this)),
TFS(TFS) {
this->ExtraClangFlags.push_back("-std=c++20");
this->ExtraClangFlags.push_back("-c");
}
void addFile(llvm::StringRef Path, llvm::StringRef Contents);
std::unique_ptr<ProjectModules> getProjectModules(PathRef) const override {
return scanningProjectModules(MockedCDBPtr, TFS);
}
private:
class MockClangCompilationDatabase : public tooling::CompilationDatabase {
public:
MockClangCompilationDatabase(MockDirectoryCompilationDatabase &MCDB)
: MCDB(MCDB) {}
std::vector<tooling::CompileCommand>
getCompileCommands(StringRef FilePath) const override {
std::optional<tooling::CompileCommand> Cmd =
MCDB.getCompileCommand(FilePath);
EXPECT_TRUE(Cmd);
return {*Cmd};
}
std::vector<std::string> getAllFiles() const override { return Files; }
void AddFile(StringRef File) { Files.push_back(File.str()); }
private:
MockDirectoryCompilationDatabase &MCDB;
std::vector<std::string> Files;
};
std::shared_ptr<MockClangCompilationDatabase> MockedCDBPtr;
const ThreadsafeFS &TFS;
};
void MockDirectoryCompilationDatabase::addFile(llvm::StringRef Path,
llvm::StringRef Contents) {
ASSERT_FALSE(llvm::sys::path::is_absolute(Path));
SmallString<256> AbsPath(Directory);
llvm::sys::path::append(AbsPath, Path);
ASSERT_FALSE(
llvm::sys::fs::create_directories(llvm::sys::path::parent_path(AbsPath)));
std::error_code EC;
llvm::raw_fd_ostream OS(AbsPath, EC);
ASSERT_FALSE(EC);
OS << Contents;
MockedCDBPtr->AddFile(Path);
}
class PrerequisiteModulesTests : public ::testing::Test {
protected:
void SetUp() override {
ASSERT_FALSE(llvm::sys::fs::createUniqueDirectory("modules-test", TestDir));
}
void TearDown() override {
ASSERT_FALSE(llvm::sys::fs::remove_directories(TestDir));
}
public:
std::string getFullPath(llvm::StringRef Path) {
SmallString<128> Result(TestDir);
llvm::sys::path::append(Result, Path);
EXPECT_TRUE(llvm::sys::fs::exists(Result.str()));
return Result.str().str();
}
ParseInputs getInputs(llvm::StringRef FileName,
const GlobalCompilationDatabase &CDB) {
std::string FullPathName = getFullPath(FileName);
ParseInputs Inputs;
std::optional<tooling::CompileCommand> Cmd =
CDB.getCompileCommand(FullPathName);
EXPECT_TRUE(Cmd);
Inputs.CompileCommand = std::move(*Cmd);
Inputs.TFS = &FS;
if (auto Contents = FS.view(TestDir)->getBufferForFile(FullPathName))
Inputs.Contents = Contents->get()->getBuffer().str();
return Inputs;
}
SmallString<256> TestDir;
RealThreadsafeFS FS;
DiagnosticConsumer DiagConsumer;
};
TEST_F(PrerequisiteModulesTests, NonModularTest) {
MockDirectoryCompilationDatabase CDB(TestDir, FS);
CDB.addFile("foo.h", R"cpp(
inline void foo() {}
)cpp");
CDB.addFile("NonModular.cpp", R"cpp(
#include "foo.h"
void use() {
foo();
}
)cpp");
ModulesBuilder Builder(CDB);
auto NonModularInfo =
Builder.buildPrerequisiteModulesFor(getFullPath("NonModular.cpp"), FS);
EXPECT_TRUE(NonModularInfo);
HeaderSearchOptions HSOpts;
NonModularInfo->adjustHeaderSearchOptions(HSOpts);
EXPECT_TRUE(HSOpts.PrebuiltModuleFiles.empty());
auto Invocation =
buildCompilerInvocation(getInputs("NonModular.cpp", CDB), DiagConsumer);
EXPECT_TRUE(NonModularInfo->canReuse(*Invocation, FS.view(TestDir)));
}
TEST_F(PrerequisiteModulesTests, ModuleWithoutDepTest) {
MockDirectoryCompilationDatabase CDB(TestDir, FS);
CDB.addFile("foo.h", R"cpp(
inline void foo() {}
)cpp");
CDB.addFile("M.cppm", R"cpp(
module;
#include "foo.h"
export module M;
)cpp");
ModulesBuilder Builder(CDB);
auto MInfo = Builder.buildPrerequisiteModulesFor(getFullPath("M.cppm"), FS);
EXPECT_TRUE(MInfo);
HeaderSearchOptions HSOpts;
MInfo->adjustHeaderSearchOptions(HSOpts);
EXPECT_TRUE(HSOpts.PrebuiltModuleFiles.empty());
auto Invocation =
buildCompilerInvocation(getInputs("M.cppm", CDB), DiagConsumer);
EXPECT_TRUE(MInfo->canReuse(*Invocation, FS.view(TestDir)));
}
TEST_F(PrerequisiteModulesTests, ModuleWithDepTest) {
MockDirectoryCompilationDatabase CDB(TestDir, FS);
CDB.addFile("foo.h", R"cpp(
inline void foo() {}
)cpp");
CDB.addFile("M.cppm", R"cpp(
module;
#include "foo.h"
export module M;
)cpp");
CDB.addFile("N.cppm", R"cpp(
export module N;
import :Part;
import M;
)cpp");
CDB.addFile("N-part.cppm", R"cpp(
// Different module name with filename intentionally.
export module N:Part;
)cpp");
ModulesBuilder Builder(CDB);
auto NInfo = Builder.buildPrerequisiteModulesFor(getFullPath("N.cppm"), FS);
EXPECT_TRUE(NInfo);
ParseInputs NInput = getInputs("N.cppm", CDB);
std::unique_ptr<CompilerInvocation> Invocation =
buildCompilerInvocation(NInput, DiagConsumer);
EXPECT_TRUE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
{
HeaderSearchOptions HSOpts;
NInfo->adjustHeaderSearchOptions(HSOpts);
EXPECT_TRUE(HSOpts.PrebuiltModuleFiles.count("M"));
EXPECT_TRUE(HSOpts.PrebuiltModuleFiles.count("N:Part"));
}
{
HeaderSearchOptions HSOpts;
HSOpts.PrebuiltModuleFiles["M"] = "incorrect_path";
HSOpts.PrebuiltModuleFiles["N:Part"] = "incorrect_path";
NInfo->adjustHeaderSearchOptions(HSOpts);
EXPECT_TRUE(StringRef(HSOpts.PrebuiltModuleFiles["M"]).ends_with(".pcm"));
EXPECT_TRUE(
StringRef(HSOpts.PrebuiltModuleFiles["N:Part"]).ends_with(".pcm"));
}
}
TEST_F(PrerequisiteModulesTests, ReusabilityTest) {
MockDirectoryCompilationDatabase CDB(TestDir, FS);
CDB.addFile("foo.h", R"cpp(
inline void foo() {}
)cpp");
CDB.addFile("M.cppm", R"cpp(
module;
#include "foo.h"
export module M;
)cpp");
CDB.addFile("N.cppm", R"cpp(
export module N;
import :Part;
import M;
)cpp");
CDB.addFile("N-part.cppm", R"cpp(
// Different module name with filename intentionally.
export module N:Part;
)cpp");
ModulesBuilder Builder(CDB);
auto NInfo = Builder.buildPrerequisiteModulesFor(getFullPath("N.cppm"), FS);
EXPECT_TRUE(NInfo);
EXPECT_TRUE(NInfo);
ParseInputs NInput = getInputs("N.cppm", CDB);
std::unique_ptr<CompilerInvocation> Invocation =
buildCompilerInvocation(NInput, DiagConsumer);
EXPECT_TRUE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
{
CDB.addFile("L.cppm", R"cpp(
module;
#include "foo.h"
export module L;
export int ll = 43;
)cpp");
EXPECT_TRUE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
CDB.addFile("bar.h", R"cpp(
inline void bar() {}
inline void bar(int) {}
)cpp");
EXPECT_TRUE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
}
{
CDB.addFile("M.cppm", R"cpp(
module;
#include "foo.h"
export module M;
export int mm = 44;
)cpp");
EXPECT_FALSE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
NInfo = Builder.buildPrerequisiteModulesFor(getFullPath("N.cppm"), FS);
EXPECT_TRUE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
CDB.addFile("foo.h", R"cpp(
inline void foo() {}
inline void foo(int) {}
)cpp");
EXPECT_FALSE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
NInfo = Builder.buildPrerequisiteModulesFor(getFullPath("N.cppm"), FS);
EXPECT_TRUE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
}
CDB.addFile("N-part.cppm", R"cpp(
export module N:Part;
// Intentioned to make it uncompilable.
export int NPart = 4LIdjwldijaw
)cpp");
EXPECT_FALSE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
NInfo = Builder.buildPrerequisiteModulesFor(getFullPath("N.cppm"), FS);
EXPECT_TRUE(NInfo);
EXPECT_FALSE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
CDB.addFile("N-part.cppm", R"cpp(
export module N:Part;
export int NPart = 43;
)cpp");
EXPECT_TRUE(NInfo);
EXPECT_FALSE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
NInfo = Builder.buildPrerequisiteModulesFor(getFullPath("N.cppm"), FS);
EXPECT_TRUE(NInfo);
EXPECT_TRUE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
CDB.addFile("N-part.cppm", R"cpp(
export module N:Part;
export int NPart = 43;
)cpp");
EXPECT_TRUE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
CDB.addFile("N.cppm", R"cpp(
export module N;
import :Part;
import M;
export int nn = 43;
)cpp");
EXPECT_TRUE(NInfo->canReuse(*Invocation, FS.view(TestDir)));
}
TEST_F(PrerequisiteModulesTests, ParsedASTTest) {
MockDirectoryCompilationDatabase CDB(TestDir, FS);
CDB.addFile("A.cppm", R"cpp(
export module A;
export void printA();
)cpp");
CDB.addFile("Use.cpp", R"cpp(
import A;
)cpp");
ModulesBuilder Builder(CDB);
ParseInputs Use = getInputs("Use.cpp", CDB);
Use.ModulesManager = &Builder;
std::unique_ptr<CompilerInvocation> CI =
buildCompilerInvocation(Use, DiagConsumer);
EXPECT_TRUE(CI);
auto Preamble =
buildPreamble(getFullPath("Use.cpp"), *CI, Use, true,
nullptr);
EXPECT_TRUE(Preamble);
EXPECT_TRUE(Preamble->RequiredModules);
auto AST = ParsedAST::build(getFullPath("Use.cpp"), Use, std::move(CI), {},
Preamble);
EXPECT_TRUE(AST);
const NamedDecl &D = findDecl(*AST, "printA");
EXPECT_TRUE(D.isFromASTFile());
}
}
}
#endif