109 changes: 109 additions & 0 deletions clang-tools-extra/clangd/unittests/ParsedASTTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@
#include "Annotations.h"
#include "Compiler.h"
#include "Diagnostics.h"
#include "Headers.h"
#include "ParsedAST.h"
#include "Preamble.h"
#include "SourceCode.h"
#include "TestFS.h"
#include "TestTU.h"
Expand All @@ -28,6 +30,7 @@
#include "clang/Lex/PPCallbacks.h"
#include "clang/Lex/Token.h"
#include "clang/Tooling/Syntax/Tokens.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/ScopedPrinter.h"
#include "gmock/gmock-matchers.h"
Expand Down Expand Up @@ -82,6 +85,13 @@ MATCHER_P(RangeIs, R, "") {
return arg.beginOffset() == R.Begin && arg.endOffset() == R.End;
}

MATCHER(EqInc, "") {
Inclusion Actual = testing::get<0>(arg);
Inclusion Expected = testing::get<1>(arg);
return std::tie(Actual.HashLine, Actual.Written) ==
std::tie(Expected.HashLine, Expected.Written);
}

TEST(ParsedASTTest, TopLevelDecls) {
TestTU TU;
TU.HeaderCode = R"(
Expand Down Expand Up @@ -431,6 +441,105 @@ TEST(ParsedASTTest, ReplayPreambleForTidyCheckers) {
}
}

TEST(ParsedASTTest, PatchesAdditionalIncludes) {
llvm::StringLiteral ModifiedContents = R"cpp(
#include "baz.h"
#include "foo.h"
#include "sub/aux.h"
void bar() {
foo();
baz();
aux();
})cpp";
// Build expected ast with symbols coming from headers.
TestTU TU;
TU.Filename = "foo.cpp";
TU.AdditionalFiles["foo.h"] = "void foo();";
TU.AdditionalFiles["sub/baz.h"] = "void baz();";
TU.AdditionalFiles["sub/aux.h"] = "void aux();";
TU.ExtraArgs = {"-I" + testPath("sub")};
TU.Code = ModifiedContents.str();
auto ExpectedAST = TU.build();

// Build preamble with no includes.
TU.Code = "";
StoreDiags Diags;
auto Inputs = TU.inputs();
auto CI = buildCompilerInvocation(Inputs, Diags);
auto EmptyPreamble =
buildPreamble(testPath("foo.cpp"), *CI, Inputs, true, nullptr);
ASSERT_TRUE(EmptyPreamble);
EXPECT_THAT(EmptyPreamble->Includes.MainFileIncludes, testing::IsEmpty());

// Now build an AST using empty preamble and ensure patched includes worked.
TU.Code = ModifiedContents.str();
Inputs = TU.inputs();
auto PatchedAST = ParsedAST::build(testPath("foo.cpp"), Inputs, std::move(CI),
{}, EmptyPreamble);
ASSERT_TRUE(PatchedAST);
ASSERT_TRUE(PatchedAST->getDiagnostics().empty());

// Ensure source location information is correct, including resolved paths.
EXPECT_THAT(PatchedAST->getIncludeStructure().MainFileIncludes,
testing::Pointwise(
EqInc(), ExpectedAST.getIncludeStructure().MainFileIncludes));
auto StringMapToVector = [](const llvm::StringMap<unsigned> SM) {
std::vector<std::pair<std::string, unsigned>> Res;
for (const auto &E : SM)
Res.push_back({E.first().str(), E.second});
llvm::sort(Res);
return Res;
};
// Ensure file proximity signals are correct.
EXPECT_EQ(StringMapToVector(PatchedAST->getIncludeStructure().includeDepth(
testPath("foo.cpp"))),
StringMapToVector(ExpectedAST.getIncludeStructure().includeDepth(
testPath("foo.cpp"))));
}

TEST(ParsedASTTest, PatchesDeletedIncludes) {
TestTU TU;
TU.Filename = "foo.cpp";
TU.Code = "";
auto ExpectedAST = TU.build();

// Build preamble with no includes.
TU.Code = R"cpp(#include <foo.h>)cpp";
StoreDiags Diags;
auto Inputs = TU.inputs();
auto CI = buildCompilerInvocation(Inputs, Diags);
auto BaselinePreamble =
buildPreamble(testPath("foo.cpp"), *CI, Inputs, true, nullptr);
ASSERT_TRUE(BaselinePreamble);
EXPECT_THAT(BaselinePreamble->Includes.MainFileIncludes,
ElementsAre(testing::Field(&Inclusion::Written, "<foo.h>")));

// Now build an AST using additional includes and check that locations are
// correctly parsed.
TU.Code = "";
Inputs = TU.inputs();
auto PatchedAST = ParsedAST::build(testPath("foo.cpp"), Inputs, std::move(CI),
{}, BaselinePreamble);
ASSERT_TRUE(PatchedAST);

// Ensure source location information is correct.
EXPECT_THAT(PatchedAST->getIncludeStructure().MainFileIncludes,
testing::Pointwise(
EqInc(), ExpectedAST.getIncludeStructure().MainFileIncludes));
auto StringMapToVector = [](const llvm::StringMap<unsigned> SM) {
std::vector<std::pair<std::string, unsigned>> Res;
for (const auto &E : SM)
Res.push_back({E.first().str(), E.second});
llvm::sort(Res);
return Res;
};
// Ensure file proximity signals are correct.
EXPECT_EQ(StringMapToVector(PatchedAST->getIncludeStructure().includeDepth(
testPath("foo.cpp"))),
StringMapToVector(ExpectedAST.getIncludeStructure().includeDepth(
testPath("foo.cpp"))));
}

} // namespace
} // namespace clangd
} // namespace clang
76 changes: 47 additions & 29 deletions clang-tools-extra/clangd/unittests/PreambleTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

#include "Annotations.h"
#include "Compiler.h"
#include "Headers.h"
#include "Preamble.h"
#include "TestFS.h"
#include "TestTU.h"
Expand All @@ -21,6 +22,7 @@
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include <clang/Frontend/FrontendActions.h>
#include <memory>
#include <string>
#include <vector>

Expand All @@ -30,51 +32,40 @@ namespace clang {
namespace clangd {
namespace {

MATCHER_P2(Distance, File, D, "") {
return arg.first() == File && arg.second == D;
}

// Builds a preamble for BaselineContents, patches it for ModifiedContents and
// returns the includes in the patch.
IncludeStructure
collectPatchedIncludes(llvm::StringRef ModifiedContents,
llvm::StringRef BaselineContents,
llvm::StringRef MainFileName = "main.cpp") {
std::string MainFile = testPath(MainFileName);
ParseInputs PI;
PI.FS = new llvm::vfs::InMemoryFileSystem;
MockCompilationDatabase CDB;
auto TU = TestTU::withCode(BaselineContents);
TU.Filename = MainFileName.str();
// ms-compatibility changes meaning of #import, make sure it is turned off.
CDB.ExtraClangFlags.push_back("-fno-ms-compatibility");
PI.CompileCommand = CDB.getCompileCommand(MainFile).getValue();
// Create invocation
IgnoreDiagnostics Diags;
auto CI = buildCompilerInvocation(PI, Diags);
assert(CI && "failed to create compiler invocation");
// Build baseline preamble.
PI.Contents = BaselineContents.str();
PI.Version = "baseline preamble";
auto BaselinePreamble = buildPreamble(MainFile, *CI, PI, true, nullptr);
assert(BaselinePreamble && "failed to build baseline preamble");
TU.ExtraArgs = {"-fno-ms-compatibility"};
auto BaselinePreamble = TU.preamble();
// Create the patch.
PI.Contents = ModifiedContents.str();
PI.Version = "modified contents";
auto PP = PreamblePatch::create(MainFile, PI, *BaselinePreamble);
TU = TestTU::withCode(ModifiedContents);
auto PI = TU.inputs();
auto PP = PreamblePatch::create(testPath(TU.Filename), PI, *BaselinePreamble);
// Collect patch contents.
IgnoreDiagnostics Diags;
auto CI = buildCompilerInvocation(PI, Diags);
PP.apply(*CI);
llvm::StringRef PatchContents;
for (const auto &Rempaped : CI->getPreprocessorOpts().RemappedFileBuffers) {
if (Rempaped.first == testPath("__preamble_patch__.h")) {
PatchContents = Rempaped.second->getBuffer();
break;
}
}
// Run preprocessor over the modified contents with patched Invocation to and
// BaselinePreamble to collect includes in the patch. We trim the input to
// only preamble section to not collect includes in the mainfile.
// Run preprocessor over the modified contents with patched Invocation. We
// provide a preamble and trim contents to ensure only the implicit header
// introduced by the patch is parsed and nothing else.
// We don't run PP directly over the patch cotents to test production
// behaviour.
auto Bounds = Lexer::ComputePreamble(ModifiedContents, *CI->getLangOpts());
auto Clang =
prepareCompilerInstance(std::move(CI), &BaselinePreamble->Preamble,
llvm::MemoryBuffer::getMemBufferCopy(
ModifiedContents.slice(0, Bounds.Size).str()),
PI.FS, Diags);
Clang->getPreprocessorOpts().ImplicitPCHInclude.clear();
PreprocessOnlyAction Action;
if (!Action.BeginSourceFile(*Clang, Clang->getFrontendOpts().Inputs[0])) {
ADD_FAILURE() << "failed begin source file";
Expand Down Expand Up @@ -163,6 +154,33 @@ TEST(PreamblePatchTest, MainFileIsEscaped) {
EXPECT_THAT(Includes, ElementsAre(AllOf(Field(&Inclusion::Written, "<a.h>"),
Field(&Inclusion::HashLine, 0))));
}

TEST(PreamblePatchTest, PatchesPreambleIncludes) {
IgnoreDiagnostics Diags;
auto TU = TestTU::withCode(R"cpp(
#include "a.h"
#include "c.h"
)cpp");
TU.AdditionalFiles["a.h"] = "#include \"b.h\"";
TU.AdditionalFiles["b.h"] = "";
TU.AdditionalFiles["c.h"] = "";
auto PI = TU.inputs();
auto BaselinePreamble = buildPreamble(
TU.Filename, *buildCompilerInvocation(PI, Diags), PI, true, nullptr);
// We drop c.h from modified and add a new header. Since the latter is patched
// we should only get a.h in preamble includes.
TU.Code = R"cpp(
#include "a.h"
#include "b.h"
)cpp";
auto PP = PreamblePatch::create(testPath(TU.Filename), TU.inputs(),
*BaselinePreamble);
// Only a.h should exists in the preamble, as c.h has been dropped and b.h was
// newly introduced.
EXPECT_THAT(PP.preambleIncludes(),
ElementsAre(AllOf(Field(&Inclusion::Written, "\"a.h\""),
Field(&Inclusion::Resolved, testPath("a.h")))));
}
} // namespace
} // namespace clangd
} // namespace clang
16 changes: 13 additions & 3 deletions clang-tools-extra/clangd/unittests/TestTU.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,14 +66,24 @@ ParseInputs TestTU::inputs() const {
return Inputs;
}

std::shared_ptr<const PreambleData> TestTU::preamble() const {
auto Inputs = inputs();
IgnoreDiagnostics Diags;
auto CI = buildCompilerInvocation(Inputs, Diags);
assert(CI && "Failed to build compilation invocation.");
return clang::clangd::buildPreamble(testPath(Filename), *CI, Inputs,
/*StoreInMemory=*/true,
/*PreambleCallback=*/nullptr);
}

ParsedAST TestTU::build() const {
auto Inputs = inputs();
StoreDiags Diags;
auto CI = buildCompilerInvocation(Inputs, Diags);
assert(CI && "Failed to build compilation invocation.");
auto Preamble =
buildPreamble(testPath(Filename), *CI, Inputs,
/*StoreInMemory=*/true, /*PreambleCallback=*/nullptr);
auto Preamble = clang::clangd::buildPreamble(testPath(Filename), *CI, Inputs,
/*StoreInMemory=*/true,
/*PreambleCallback=*/nullptr);
auto AST = ParsedAST::build(testPath(Filename), Inputs, std::move(CI),
Diags.take(), Preamble);
if (!AST.hasValue()) {
Expand Down
1 change: 1 addition & 0 deletions clang-tools-extra/clangd/unittests/TestTU.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ struct TestTU {
// By default, build() will report Error diagnostics as GTest errors.
// Suppress this behavior by adding an 'error-ok' comment to the code.
ParsedAST build() const;
std::shared_ptr<const PreambleData> preamble() const;
ParseInputs inputs() const;
SymbolSlab headerSymbols() const;
RefSlab headerRefs() const;
Expand Down