xref: /llvm-project/clang-tools-extra/clangd/unittests/HeadersTests.cpp (revision 1f90797f6a9d91d61e0f66b465b0467e4c66d0e0)
1 //===-- HeadersTests.cpp - Include headers unit tests -----------*- C++ -*-===//
2 //
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
6 //
7 //===----------------------------------------------------------------------===//
8 
9 #include "Headers.h"
10 
11 #include "Compiler.h"
12 #include "Matchers.h"
13 #include "TestFS.h"
14 #include "TestTU.h"
15 #include "clang/Basic/TokenKinds.h"
16 #include "clang/Frontend/CompilerInstance.h"
17 #include "clang/Frontend/CompilerInvocation.h"
18 #include "clang/Frontend/FrontendActions.h"
19 #include "clang/Tooling/Inclusions/HeaderIncludes.h"
20 #include "llvm/ADT/StringRef.h"
21 #include "llvm/Support/Error.h"
22 #include "llvm/Support/FormatVariadic.h"
23 #include "llvm/Support/Path.h"
24 #include "llvm/Testing/Support/Error.h"
25 #include "gmock/gmock.h"
26 #include "gtest/gtest.h"
27 #include <optional>
28 
29 namespace clang {
30 namespace clangd {
31 namespace {
32 
33 using ::testing::AllOf;
34 using ::testing::Contains;
35 using ::testing::ElementsAre;
36 using ::testing::IsEmpty;
37 using ::testing::Not;
38 using ::testing::UnorderedElementsAre;
39 
40 class HeadersTest : public ::testing::Test {
41 public:
42   HeadersTest() {
43     CDB.ExtraClangFlags = {SearchDirArg.c_str()};
44     FS.Files[MainFile] = "";
45     // Make sure directory sub/ exists.
46     FS.Files[testPath("sub/EMPTY")] = "";
47   }
48 
49 private:
50   std::unique_ptr<CompilerInstance> setupClang() {
51     auto Cmd = CDB.getCompileCommand(MainFile);
52     assert(static_cast<bool>(Cmd));
53 
54     ParseInputs PI;
55     PI.CompileCommand = *Cmd;
56     PI.TFS = &FS;
57     auto CI = buildCompilerInvocation(PI, IgnoreDiags);
58     EXPECT_TRUE(static_cast<bool>(CI));
59     // The diagnostic options must be set before creating a CompilerInstance.
60     CI->getDiagnosticOpts().IgnoreWarnings = true;
61     auto VFS = PI.TFS->view(Cmd->Directory);
62     auto Clang = prepareCompilerInstance(
63         std::move(CI), /*Preamble=*/nullptr,
64         llvm::MemoryBuffer::getMemBuffer(FS.Files[MainFile], MainFile),
65         std::move(VFS), IgnoreDiags);
66 
67     EXPECT_FALSE(Clang->getFrontendOpts().Inputs.empty());
68     return Clang;
69   }
70 
71 protected:
72   IncludeStructure::HeaderID getID(StringRef Filename,
73                                    IncludeStructure &Includes) {
74     auto &SM = Clang->getSourceManager();
75     auto Entry = SM.getFileManager().getFileRef(Filename);
76     EXPECT_THAT_EXPECTED(Entry, llvm::Succeeded());
77     return Includes.getOrCreateID(*Entry);
78   }
79 
80   IncludeStructure collectIncludes() {
81     Clang = setupClang();
82     PreprocessOnlyAction Action;
83     EXPECT_TRUE(
84         Action.BeginSourceFile(*Clang, Clang->getFrontendOpts().Inputs[0]));
85     IncludeStructure Includes;
86     Includes.collect(*Clang);
87     EXPECT_FALSE(Action.Execute());
88     Action.EndSourceFile();
89     return Includes;
90   }
91 
92   // Calculates the include path, or returns "" on error or header should not be
93   // inserted.
94   std::string calculate(PathRef Original, PathRef Preferred = "",
95                         const std::vector<Inclusion> &Inclusions = {}) {
96     Clang = setupClang();
97     PreprocessOnlyAction Action;
98     EXPECT_TRUE(
99         Action.BeginSourceFile(*Clang, Clang->getFrontendOpts().Inputs[0]));
100 
101     if (Preferred.empty())
102       Preferred = Original;
103     auto ToHeaderFile = [](llvm::StringRef Header) {
104       return HeaderFile{std::string(Header),
105                         /*Verbatim=*/!llvm::sys::path::is_absolute(Header)};
106     };
107 
108     IncludeInserter Inserter(MainFile, /*Code=*/"", format::getLLVMStyle(),
109                              CDB.getCompileCommand(MainFile)->Directory,
110                              &Clang->getPreprocessor().getHeaderSearchInfo(),
111                              QuotedHeaders, AngledHeaders);
112     for (const auto &Inc : Inclusions)
113       Inserter.addExisting(Inc);
114     auto Inserted = ToHeaderFile(Preferred);
115     if (!Inserter.shouldInsertInclude(Original, Inserted))
116       return "";
117     auto Path = Inserter.calculateIncludePath(Inserted, MainFile);
118     Action.EndSourceFile();
119     return Path.value_or("");
120   }
121 
122   std::optional<TextEdit> insert(llvm::StringRef VerbatimHeader,
123                                  tooling::IncludeDirective Directive) {
124     Clang = setupClang();
125     PreprocessOnlyAction Action;
126     EXPECT_TRUE(
127         Action.BeginSourceFile(*Clang, Clang->getFrontendOpts().Inputs[0]));
128 
129     IncludeInserter Inserter(MainFile, /*Code=*/"", format::getLLVMStyle(),
130                              CDB.getCompileCommand(MainFile)->Directory,
131                              &Clang->getPreprocessor().getHeaderSearchInfo(),
132                              QuotedHeaders, AngledHeaders);
133     auto Edit = Inserter.insert(VerbatimHeader, Directive);
134     Action.EndSourceFile();
135     return Edit;
136   }
137 
138   MockFS FS;
139   MockCompilationDatabase CDB;
140   std::string MainFile = testPath("main.cpp");
141   std::string Subdir = testPath("sub");
142   std::string SearchDirArg = (llvm::Twine("-I") + Subdir).str();
143   IgnoringDiagConsumer IgnoreDiags;
144   std::vector<std::function<bool(llvm::StringRef)>> QuotedHeaders;
145   std::vector<std::function<bool(llvm::StringRef)>> AngledHeaders;
146   std::unique_ptr<CompilerInstance> Clang;
147 };
148 
149 MATCHER_P(written, Name, "") { return arg.Written == Name; }
150 MATCHER_P(resolved, Name, "") { return arg.Resolved == Name; }
151 MATCHER_P(includeLine, N, "") { return arg.HashLine == N; }
152 MATCHER_P(directive, D, "") { return arg.Directive == D; }
153 
154 MATCHER_P2(Distance, File, D, "") {
155   if (arg.getFirst() != File)
156     *result_listener << "file =" << static_cast<unsigned>(arg.getFirst());
157   if (arg.getSecond() != D)
158     *result_listener << "distance =" << arg.getSecond();
159   return arg.getFirst() == File && arg.getSecond() == D;
160 }
161 
162 TEST_F(HeadersTest, CollectRewrittenAndResolved) {
163   FS.Files[MainFile] = R"cpp(
164 #include "sub/bar.h" // not shortest
165 )cpp";
166   std::string BarHeader = testPath("sub/bar.h");
167   FS.Files[BarHeader] = "";
168 
169   auto Includes = collectIncludes();
170   EXPECT_THAT(Includes.MainFileIncludes,
171               UnorderedElementsAre(
172                   AllOf(written("\"sub/bar.h\""), resolved(BarHeader))));
173   EXPECT_THAT(Includes.includeDepth(getID(MainFile, Includes)),
174               UnorderedElementsAre(Distance(getID(MainFile, Includes), 0u),
175                                    Distance(getID(BarHeader, Includes), 1u)));
176 }
177 
178 TEST_F(HeadersTest, OnlyCollectInclusionsInMain) {
179   std::string BazHeader = testPath("sub/baz.h");
180   FS.Files[BazHeader] = "";
181   std::string BarHeader = testPath("sub/bar.h");
182   FS.Files[BarHeader] = R"cpp(
183 #include "baz.h"
184 )cpp";
185   FS.Files[MainFile] = R"cpp(
186 #include "bar.h"
187 )cpp";
188   auto Includes = collectIncludes();
189   EXPECT_THAT(
190       Includes.MainFileIncludes,
191       UnorderedElementsAre(AllOf(written("\"bar.h\""), resolved(BarHeader))));
192   EXPECT_THAT(Includes.includeDepth(getID(MainFile, Includes)),
193               UnorderedElementsAre(Distance(getID(MainFile, Includes), 0u),
194                                    Distance(getID(BarHeader, Includes), 1u),
195                                    Distance(getID(BazHeader, Includes), 2u)));
196   // includeDepth() also works for non-main files.
197   EXPECT_THAT(Includes.includeDepth(getID(BarHeader, Includes)),
198               UnorderedElementsAre(Distance(getID(BarHeader, Includes), 0u),
199                                    Distance(getID(BazHeader, Includes), 1u)));
200 }
201 
202 TEST_F(HeadersTest, CacheBySpellingIsBuiltForMainInclusions) {
203   std::string FooHeader = testPath("foo.h");
204   FS.Files[FooHeader] = R"cpp(
205   void foo();
206 )cpp";
207   std::string BarHeader = testPath("bar.h");
208   FS.Files[BarHeader] = R"cpp(
209   void bar();
210 )cpp";
211   std::string BazHeader = testPath("baz.h");
212   FS.Files[BazHeader] = R"cpp(
213   void baz();
214 )cpp";
215   FS.Files[MainFile] = R"cpp(
216 #include "foo.h"
217 #include "bar.h"
218 #include "baz.h"
219 )cpp";
220   auto Includes = collectIncludes();
221   EXPECT_THAT(Includes.MainFileIncludes,
222               UnorderedElementsAre(written("\"foo.h\""), written("\"bar.h\""),
223                                    written("\"baz.h\"")));
224   EXPECT_THAT(Includes.mainFileIncludesWithSpelling("\"foo.h\""),
225               UnorderedElementsAre(&Includes.MainFileIncludes[0]));
226   EXPECT_THAT(Includes.mainFileIncludesWithSpelling("\"bar.h\""),
227               UnorderedElementsAre(&Includes.MainFileIncludes[1]));
228   EXPECT_THAT(Includes.mainFileIncludesWithSpelling("\"baz.h\""),
229               UnorderedElementsAre(&Includes.MainFileIncludes[2]));
230 }
231 
232 TEST_F(HeadersTest, PreambleIncludesPresentOnce) {
233   // We use TestTU here, to ensure we use the preamble replay logic.
234   // We're testing that the logic doesn't crash, and doesn't result in duplicate
235   // includes. (We'd test more directly, but it's pretty well encapsulated!)
236   auto TU = TestTU::withCode(R"cpp(
237     #include "a.h"
238 
239     #include "a.h"
240     void foo();
241     #include "a.h"
242   )cpp");
243   TU.HeaderFilename = "a.h"; // suppress "not found".
244   EXPECT_THAT(TU.build().getIncludeStructure().MainFileIncludes,
245               ElementsAre(includeLine(1), includeLine(3), includeLine(5)));
246 }
247 
248 TEST_F(HeadersTest, UnResolvedInclusion) {
249   FS.Files[MainFile] = R"cpp(
250 #include "foo.h"
251 )cpp";
252 
253   EXPECT_THAT(collectIncludes().MainFileIncludes,
254               UnorderedElementsAre(AllOf(written("\"foo.h\""), resolved(""))));
255   EXPECT_THAT(collectIncludes().IncludeChildren, IsEmpty());
256 }
257 
258 TEST_F(HeadersTest, IncludedFilesGraph) {
259   FS.Files[MainFile] = R"cpp(
260 #include "bar.h"
261 #include "foo.h"
262 )cpp";
263   std::string BarHeader = testPath("bar.h");
264   FS.Files[BarHeader] = "";
265   std::string FooHeader = testPath("foo.h");
266   FS.Files[FooHeader] = R"cpp(
267 #include "bar.h"
268 #include "baz.h"
269 )cpp";
270   std::string BazHeader = testPath("baz.h");
271   FS.Files[BazHeader] = "";
272 
273   auto Includes = collectIncludes();
274   llvm::DenseMap<IncludeStructure::HeaderID,
275                  SmallVector<IncludeStructure::HeaderID>>
276       Expected = {{getID(MainFile, Includes),
277                    {getID(BarHeader, Includes), getID(FooHeader, Includes)}},
278                   {getID(FooHeader, Includes),
279                    {getID(BarHeader, Includes), getID(BazHeader, Includes)}}};
280   EXPECT_EQ(Includes.IncludeChildren, Expected);
281 }
282 
283 TEST_F(HeadersTest, IncludeDirective) {
284   FS.Files[MainFile] = R"cpp(
285 #include "foo.h"
286 #import "foo.h"
287 #include_next "foo.h"
288 )cpp";
289 
290   // ms-compatibility changes meaning of #import, make sure it is turned off.
291   CDB.ExtraClangFlags.push_back("-fno-ms-compatibility");
292   EXPECT_THAT(collectIncludes().MainFileIncludes,
293               UnorderedElementsAre(directive(tok::pp_include),
294                                    directive(tok::pp_import),
295                                    directive(tok::pp_include_next)));
296 }
297 
298 TEST_F(HeadersTest, SearchPath) {
299   FS.Files["foo/bar.h"] = "x";
300   FS.Files["foo/bar/baz.h"] = "y";
301   CDB.ExtraClangFlags.push_back("-Ifoo/bar");
302   CDB.ExtraClangFlags.push_back("-Ifoo/bar/..");
303   EXPECT_THAT(collectIncludes().SearchPathsCanonical,
304               ElementsAre(Subdir, testPath("foo/bar"), testPath("foo")));
305 }
306 
307 TEST_F(HeadersTest, InsertInclude) {
308   std::string Path = testPath("sub/bar.h");
309   FS.Files[Path] = "";
310   EXPECT_EQ(calculate(Path), "\"bar.h\"");
311 
312   AngledHeaders.push_back([](auto Path) { return true; });
313   EXPECT_EQ(calculate(Path), "<bar.h>");
314 }
315 
316 TEST_F(HeadersTest, DoNotInsertIfInSameFile) {
317   MainFile = testPath("main.h");
318   EXPECT_EQ(calculate(MainFile), "");
319 }
320 
321 TEST_F(HeadersTest, DoNotInsertOffIncludePath) {
322   MainFile = testPath("sub/main.cpp");
323   EXPECT_EQ(calculate(testPath("sub2/main.cpp")), "");
324 }
325 
326 TEST_F(HeadersTest, ShortenIncludesInSearchPath) {
327   std::string BarHeader = testPath("sub/bar.h");
328   EXPECT_EQ(calculate(BarHeader), "\"bar.h\"");
329 
330   SearchDirArg = (llvm::Twine("-I") + Subdir + "/..").str();
331   CDB.ExtraClangFlags = {SearchDirArg.c_str()};
332   BarHeader = testPath("sub/bar.h");
333   EXPECT_EQ(calculate(BarHeader), "\"sub/bar.h\"");
334 }
335 
336 TEST_F(HeadersTest, ShortenIncludesInSearchPathBracketed) {
337   AngledHeaders.push_back([](auto Path) { return true; });
338   std::string BarHeader = testPath("sub/bar.h");
339   EXPECT_EQ(calculate(BarHeader), "<bar.h>");
340 
341   SearchDirArg = (llvm::Twine("-I") + Subdir + "/..").str();
342   CDB.ExtraClangFlags = {SearchDirArg.c_str()};
343   BarHeader = testPath("sub/bar.h");
344   EXPECT_EQ(calculate(BarHeader), "<sub/bar.h>");
345 }
346 
347 TEST_F(HeadersTest, ShortenedIncludeNotInSearchPath) {
348   std::string BarHeader =
349       llvm::sys::path::convert_to_slash(testPath("sub-2/bar.h"));
350   EXPECT_EQ(calculate(BarHeader, ""), "\"sub-2/bar.h\"");
351 }
352 
353 TEST_F(HeadersTest, PreferredHeader) {
354   std::string BarHeader = testPath("sub/bar.h");
355   EXPECT_EQ(calculate(BarHeader, "<bar>"), "<bar>");
356 
357   std::string BazHeader = testPath("sub/baz.h");
358   EXPECT_EQ(calculate(BarHeader, BazHeader), "\"baz.h\"");
359 
360   AngledHeaders.push_back([](auto Path) { return true; });
361   std::string BiffHeader = testPath("sub/biff.h");
362   EXPECT_EQ(calculate(BarHeader, BiffHeader), "<biff.h>");
363 }
364 
365 TEST_F(HeadersTest, DontInsertDuplicatePreferred) {
366   Inclusion Inc;
367   Inc.Written = "\"bar.h\"";
368   Inc.Resolved = "";
369   EXPECT_EQ(calculate(testPath("sub/bar.h"), "\"bar.h\"", {Inc}), "");
370   EXPECT_EQ(calculate("\"x.h\"", "\"bar.h\"", {Inc}), "");
371 }
372 
373 TEST_F(HeadersTest, DontInsertDuplicateResolved) {
374   Inclusion Inc;
375   Inc.Written = "fake-bar.h";
376   Inc.Resolved = testPath("sub/bar.h");
377   EXPECT_EQ(calculate(Inc.Resolved, "", {Inc}), "");
378   // Do not insert preferred.
379   EXPECT_EQ(calculate(Inc.Resolved, "\"BAR.h\"", {Inc}), "");
380 }
381 
382 TEST_F(HeadersTest, PreferInserted) {
383   auto Edit = insert("<y>", tooling::IncludeDirective::Include);
384   ASSERT_TRUE(Edit);
385   EXPECT_EQ(Edit->newText, "#include <y>\n");
386 
387   Edit = insert("\"header.h\"", tooling::IncludeDirective::Import);
388   ASSERT_TRUE(Edit);
389   EXPECT_EQ(Edit->newText, "#import \"header.h\"\n");
390 }
391 
392 TEST(Headers, NoHeaderSearchInfo) {
393   std::string MainFile = testPath("main.cpp");
394   IncludeInserter Inserter(MainFile, /*Code=*/"", format::getLLVMStyle(),
395                            /*BuildDir=*/"", /*HeaderSearchInfo=*/nullptr,
396                            /*QuotedHeaders=*/{}, /*AngledHeaders=*/{});
397 
398   auto HeaderPath = testPath("sub/bar.h");
399   auto Inserting = HeaderFile{HeaderPath, /*Verbatim=*/false};
400   auto Verbatim = HeaderFile{"<x>", /*Verbatim=*/true};
401 
402   EXPECT_EQ(Inserter.calculateIncludePath(Inserting, MainFile),
403             std::string("\"sub/bar.h\""));
404   EXPECT_EQ(Inserter.shouldInsertInclude(HeaderPath, Inserting), false);
405 
406   EXPECT_EQ(Inserter.calculateIncludePath(Verbatim, MainFile),
407             std::string("<x>"));
408   EXPECT_EQ(Inserter.shouldInsertInclude(HeaderPath, Verbatim), true);
409 
410   EXPECT_EQ(Inserter.calculateIncludePath(Inserting, "sub2/main2.cpp"),
411             std::nullopt);
412 }
413 
414 TEST_F(HeadersTest, PresumedLocations) {
415   std::string HeaderFile = "__preamble_patch__.h";
416 
417   // Line map inclusion back to main file.
418   std::string HeaderContents =
419       llvm::formatv("#line 0 \"{0}\"", llvm::sys::path::filename(MainFile));
420   HeaderContents += R"cpp(
421 #line 3
422 #include <a.h>)cpp";
423   FS.Files[HeaderFile] = HeaderContents;
424 
425   // Including through non-builtin file has no effects.
426   FS.Files[MainFile] = "#include \"__preamble_patch__.h\"\n\n";
427   EXPECT_THAT(collectIncludes().MainFileIncludes,
428               Not(Contains(written("<a.h>"))));
429 
430   // Now include through built-in file.
431   CDB.ExtraClangFlags = {"-include", testPath(HeaderFile)};
432   EXPECT_THAT(collectIncludes().MainFileIncludes,
433               Contains(AllOf(includeLine(2), written("<a.h>"))));
434 }
435 
436 } // namespace
437 } // namespace clangd
438 } // namespace clang
439