1 //===- unittest/Tooling/CrossTranslationUnitTest.cpp - Tooling unit tests -===//
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7 //===----------------------------------------------------------------------===//
9 #include "clang/CrossTU/CrossTranslationUnit.h"
10 #include "clang/AST/ASTConsumer.h"
11 #include "clang/AST/ParentMapContext.h"
12 #include "clang/Frontend/CompilerInstance.h"
13 #include "clang/Frontend/FrontendAction.h"
14 #include "clang/Tooling/Tooling.h"
15 #include "llvm/Support/FileSystem.h"
16 #include "llvm/Support/Path.h"
17 #include "llvm/Support/ToolOutputFile.h"
18 #include "gtest/gtest.h"
26 class CTUASTConsumer
: public clang::ASTConsumer
{
28 explicit CTUASTConsumer(clang::CompilerInstance
&CI
, bool *Success
)
29 : CTU(CI
), Success(Success
) {}
31 void HandleTranslationUnit(ASTContext
&Ctx
) override
{
32 auto FindFInTU
= [](const TranslationUnitDecl
*TU
) {
33 const FunctionDecl
*FD
= nullptr;
34 for (const Decl
*D
: TU
->decls()) {
35 FD
= dyn_cast
<FunctionDecl
>(D
);
36 if (FD
&& FD
->getName() == "f")
42 const TranslationUnitDecl
*TU
= Ctx
.getTranslationUnitDecl();
43 const FunctionDecl
*FD
= FindFInTU(TU
);
44 assert(FD
&& FD
->getName() == "f");
45 bool OrigFDHasBody
= FD
->hasBody();
47 const DynTypedNodeList ParentsBeforeImport
=
48 Ctx
.getParentMapContext().getParents
<Decl
>(*FD
);
49 ASSERT_FALSE(ParentsBeforeImport
.empty());
51 // Prepare the index file and the AST file.
53 llvm::SmallString
<256> ASTFileName
;
55 llvm::sys::fs::createTemporaryFile("f_ast", "ast", ASTFD
, ASTFileName
));
56 llvm::ToolOutputFile
ASTFile(ASTFileName
, ASTFD
);
59 llvm::SmallString
<256> IndexFileName
;
60 ASSERT_FALSE(llvm::sys::fs::createTemporaryFile("index", "txt", IndexFD
,
62 llvm::ToolOutputFile
IndexFile(IndexFileName
, IndexFD
);
63 IndexFile
.os() << "9:c:@F@f#I# " << ASTFileName
<< "\n";
64 IndexFile
.os().flush();
65 EXPECT_TRUE(llvm::sys::fs::exists(IndexFileName
));
67 StringRef SourceText
= "int f(int) { return 0; }\n";
68 // This file must exist since the saved ASTFile will reference it.
70 llvm::SmallString
<256> SourceFileName
;
71 ASSERT_FALSE(llvm::sys::fs::createTemporaryFile("input", "cpp", SourceFD
,
73 llvm::ToolOutputFile
SourceFile(SourceFileName
, SourceFD
);
74 SourceFile
.os() << SourceText
;
75 SourceFile
.os().flush();
76 EXPECT_TRUE(llvm::sys::fs::exists(SourceFileName
));
78 std::unique_ptr
<ASTUnit
> ASTWithDefinition
=
79 tooling::buildASTFromCode(SourceText
, SourceFileName
);
80 ASTWithDefinition
->Save(ASTFileName
.str());
81 EXPECT_TRUE(llvm::sys::fs::exists(ASTFileName
));
83 // Load the definition from the AST file.
84 llvm::Expected
<const FunctionDecl
*> NewFDorError
= handleExpected(
85 CTU
.getCrossTUDefinition(FD
, "", IndexFileName
, false),
86 []() { return nullptr; }, [](IndexError
&) {});
89 const FunctionDecl
*NewFD
= *NewFDorError
;
90 *Success
= NewFD
&& NewFD
->hasBody() && !OrigFDHasBody
;
94 const DynTypedNodeList ParentsAfterImport
=
95 Ctx
.getParentMapContext().getParents
<Decl
>(*FD
);
96 const DynTypedNodeList ParentsOfImported
=
97 Ctx
.getParentMapContext().getParents
<Decl
>(*NewFD
);
99 checkParentListsEq(ParentsBeforeImport
, ParentsAfterImport
));
100 EXPECT_FALSE(ParentsOfImported
.empty());
105 static bool checkParentListsEq(const DynTypedNodeList
&L1
,
106 const DynTypedNodeList
&L2
) {
107 if (L1
.size() != L2
.size())
109 for (unsigned int I
= 0; I
< L1
.size(); ++I
)
116 CrossTranslationUnitContext CTU
;
120 class CTUAction
: public clang::ASTFrontendAction
{
122 CTUAction(bool *Success
, unsigned OverrideLimit
)
123 : Success(Success
), OverrideLimit(OverrideLimit
) {}
126 std::unique_ptr
<clang::ASTConsumer
>
127 CreateASTConsumer(clang::CompilerInstance
&CI
, StringRef
) override
{
128 CI
.getAnalyzerOpts().CTUImportThreshold
= OverrideLimit
;
129 CI
.getAnalyzerOpts().CTUImportCppThreshold
= OverrideLimit
;
130 return std::make_unique
<CTUASTConsumer
>(CI
, Success
);
135 const unsigned OverrideLimit
;
140 TEST(CrossTranslationUnit
, CanLoadFunctionDefinition
) {
141 bool Success
= false;
142 EXPECT_TRUE(tooling::runToolOnCode(std::make_unique
<CTUAction
>(&Success
, 1u),
144 EXPECT_TRUE(Success
);
147 TEST(CrossTranslationUnit
, RespectsLoadThreshold
) {
148 bool Success
= false;
149 EXPECT_TRUE(tooling::runToolOnCode(std::make_unique
<CTUAction
>(&Success
, 0u),
151 EXPECT_FALSE(Success
);
154 TEST(CrossTranslationUnit
, IndexFormatCanBeParsed
) {
155 llvm::StringMap
<std::string
> Index
;
156 Index
["a"] = "/b/f1";
157 Index
["c"] = "/d/f2";
158 Index
["e"] = "/f/f3";
159 std::string IndexText
= createCrossTUIndexString(Index
);
162 llvm::SmallString
<256> IndexFileName
;
163 ASSERT_FALSE(llvm::sys::fs::createTemporaryFile("index", "txt", IndexFD
,
165 llvm::ToolOutputFile
IndexFile(IndexFileName
, IndexFD
);
166 IndexFile
.os() << IndexText
;
167 IndexFile
.os().flush();
168 EXPECT_TRUE(llvm::sys::fs::exists(IndexFileName
));
169 llvm::Expected
<llvm::StringMap
<std::string
>> IndexOrErr
=
170 parseCrossTUIndex(IndexFileName
);
171 EXPECT_TRUE((bool)IndexOrErr
);
172 llvm::StringMap
<std::string
> ParsedIndex
= IndexOrErr
.get();
173 for (const auto &E
: Index
) {
174 EXPECT_TRUE(ParsedIndex
.count(E
.getKey()));
175 EXPECT_EQ(ParsedIndex
[E
.getKey()], E
.getValue());
177 for (const auto &E
: ParsedIndex
)
178 EXPECT_TRUE(Index
.count(E
.getKey()));
181 TEST(CrossTranslationUnit
, EmptyInvocationListIsNotValid
) {
184 llvm::Expected
<InvocationListTy
> Result
= parseInvocationList(Input
);
185 EXPECT_FALSE(static_cast<bool>(Result
));
186 bool IsWrongFromatError
= false;
187 llvm::handleAllErrors(Result
.takeError(), [&](IndexError
&Err
) {
189 Err
.getCode() == index_error_code::invocation_list_wrong_format
;
191 EXPECT_TRUE(IsWrongFromatError
);
194 TEST(CrossTranslationUnit
, AmbiguousInvocationListIsDetected
) {
195 // The same source file occurs twice (for two different architecture) in
196 // this test case. The disambiguation is the responsibility of the user.
214 llvm::Expected
<InvocationListTy
> Result
= parseInvocationList(Input
);
215 EXPECT_FALSE(static_cast<bool>(Result
));
216 bool IsAmbiguousError
= false;
217 llvm::handleAllErrors(Result
.takeError(), [&](IndexError
&Err
) {
219 Err
.getCode() == index_error_code::invocation_list_ambiguous
;
221 EXPECT_TRUE(IsAmbiguousError
);
224 TEST(CrossTranslationUnit
, SingleInvocationCanBeParsed
) {
230 llvm::Expected
<InvocationListTy
> Result
= parseInvocationList(Input
);
231 EXPECT_TRUE(static_cast<bool>(Result
));
233 EXPECT_EQ(Result
->size(), 1u);
235 auto It
= Result
->find("/tmp/main.cpp");
236 EXPECT_TRUE(It
!= Result
->end());
237 EXPECT_EQ(It
->getValue()[0], "clang++");
238 EXPECT_EQ(It
->getValue()[1], "/tmp/main.cpp");
241 TEST(CrossTranslationUnit
, MultipleInvocationsCanBeParsed
) {
254 llvm::Expected
<InvocationListTy
> Result
= parseInvocationList(Input
);
255 EXPECT_TRUE(static_cast<bool>(Result
));
257 EXPECT_EQ(Result
->size(), 2u);
259 auto It
= Result
->find("/tmp/main.cpp");
260 EXPECT_TRUE(It
!= Result
->end());
261 EXPECT_EQ(It
->getKey(), "/tmp/main.cpp");
262 EXPECT_EQ(It
->getValue()[0], "clang++");
263 EXPECT_EQ(It
->getValue()[1], "/tmp/other.o");
264 EXPECT_EQ(It
->getValue()[2], "/tmp/main.cpp");
266 It
= Result
->find("/tmp/other.cpp");
267 EXPECT_TRUE(It
!= Result
->end());
268 EXPECT_EQ(It
->getValue()[0], "g++");
269 EXPECT_EQ(It
->getValue()[1], "-c");
270 EXPECT_EQ(It
->getValue()[2], "-o");
271 EXPECT_EQ(It
->getValue()[3], "/tmp/other.o");
272 EXPECT_EQ(It
->getValue()[4], "/tmp/other.cpp");
275 } // end namespace cross_tu
276 } // end namespace clang