clang-tools  14.0.0git
CompileCommandsTests.cpp
Go to the documentation of this file.
1 //===-- CompileCommandsTests.cpp ------------------------------------------===//
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 "CompileCommands.h"
10 #include "Config.h"
11 #include "TestFS.h"
12 #include "support/Context.h"
13 
14 #include "clang/Tooling/ArgumentsAdjusters.h"
15 #include "llvm/ADT/ArrayRef.h"
16 #include "llvm/ADT/STLExtras.h"
17 #include "llvm/ADT/ScopeExit.h"
18 #include "llvm/ADT/StringExtras.h"
19 #include "llvm/ADT/StringRef.h"
20 #include "llvm/Support/FileSystem.h"
21 #include "llvm/Support/Path.h"
22 #include "llvm/Support/Process.h"
23 
24 #include "gmock/gmock.h"
25 #include "gtest/gtest.h"
26 
27 namespace clang {
28 namespace clangd {
29 namespace {
30 
31 using ::testing::_;
32 using ::testing::Contains;
33 using ::testing::ElementsAre;
34 using ::testing::HasSubstr;
35 using ::testing::Not;
36 
37 // Sadly, CommandMangler::detect(), which contains much of the logic, is
38 // a bunch of untested integration glue. We test the string manipulation here
39 // assuming its results are correct.
40 
41 // Make use of all features and assert the exact command we get out.
42 // Other tests just verify presence/absence of certain args.
43 TEST(CommandMangler, Everything) {
44  auto Mangler = CommandMangler::forTests();
45  Mangler.ClangPath = testPath("fake/clang");
46  Mangler.ResourceDir = testPath("fake/resources");
47  Mangler.Sysroot = testPath("fake/sysroot");
48  std::vector<std::string> Cmd = {"clang++", "--", "foo.cc", "bar.cc"};
49  Mangler.adjust(Cmd, "foo.cc");
50  EXPECT_THAT(Cmd, ElementsAre(testPath("fake/clang++"),
51  "-resource-dir=" + testPath("fake/resources"),
52  "-isysroot", testPath("fake/sysroot"), "--",
53  "foo.cc"));
54 }
55 
56 TEST(CommandMangler, ResourceDir) {
57  auto Mangler = CommandMangler::forTests();
58  Mangler.ResourceDir = testPath("fake/resources");
59  std::vector<std::string> Cmd = {"clang++", "foo.cc"};
60  Mangler.adjust(Cmd, "foo.cc");
61  EXPECT_THAT(Cmd, Contains("-resource-dir=" + testPath("fake/resources")));
62 }
63 
64 TEST(CommandMangler, Sysroot) {
65  auto Mangler = CommandMangler::forTests();
66  Mangler.Sysroot = testPath("fake/sysroot");
67 
68  std::vector<std::string> Cmd = {"clang++", "foo.cc"};
69  Mangler.adjust(Cmd, "foo.cc");
70  EXPECT_THAT(llvm::join(Cmd, " "),
71  HasSubstr("-isysroot " + testPath("fake/sysroot")));
72 }
73 
74 TEST(CommandMangler, ClangPath) {
75  auto Mangler = CommandMangler::forTests();
76  Mangler.ClangPath = testPath("fake/clang");
77 
78  std::vector<std::string> Cmd = {"clang++", "foo.cc"};
79  Mangler.adjust(Cmd, "foo.cc");
80  EXPECT_EQ(testPath("fake/clang++"), Cmd.front());
81 
82  Cmd = {"unknown-binary", "foo.cc"};
83  Mangler.adjust(Cmd, "foo.cc");
84  EXPECT_EQ(testPath("fake/unknown-binary"), Cmd.front());
85 
86  Cmd = {testPath("path/clang++"), "foo.cc"};
87  Mangler.adjust(Cmd, "foo.cc");
88  EXPECT_EQ(testPath("path/clang++"), Cmd.front());
89 
90  Cmd = {"foo/unknown-binary", "foo.cc"};
91  Mangler.adjust(Cmd, "foo.cc");
92  EXPECT_EQ("foo/unknown-binary", Cmd.front());
93 }
94 
95 // Only run the PATH/symlink resolving test on unix, we need to fiddle
96 // with permissions and environment variables...
97 #ifdef LLVM_ON_UNIX
98 MATCHER(Ok, "") {
99  if (arg) {
100  *result_listener << arg.message();
101  return false;
102  }
103  return true;
104 }
105 
106 TEST(CommandMangler, ClangPathResolve) {
107  // Set up filesystem:
108  // /temp/
109  // bin/
110  // foo -> temp/lib/bar
111  // lib/
112  // bar
113  llvm::SmallString<256> TempDir;
114  ASSERT_THAT(llvm::sys::fs::createUniqueDirectory("ClangPathResolve", TempDir),
115  Ok());
116  // /var/tmp is a symlink on Mac. Resolve it so we're asserting the right path.
117  ASSERT_THAT(llvm::sys::fs::real_path(TempDir.str(), TempDir), Ok());
118  auto CleanDir = llvm::make_scope_exit(
119  [&] { llvm::sys::fs::remove_directories(TempDir); });
120  ASSERT_THAT(llvm::sys::fs::create_directory(TempDir + "/bin"), Ok());
121  ASSERT_THAT(llvm::sys::fs::create_directory(TempDir + "/lib"), Ok());
122  int FD;
123  ASSERT_THAT(llvm::sys::fs::openFileForWrite(TempDir + "/lib/bar", FD), Ok());
124  ASSERT_THAT(llvm::sys::Process::SafelyCloseFileDescriptor(FD), Ok());
125  ::chmod((TempDir + "/lib/bar").str().c_str(), 0755); // executable
126  ASSERT_THAT(
127  llvm::sys::fs::create_link(TempDir + "/lib/bar", TempDir + "/bin/foo"),
128  Ok());
129 
130  // Test the case where the driver is an absolute path to a symlink.
131  auto Mangler = CommandMangler::forTests();
132  Mangler.ClangPath = testPath("fake/clang");
133  std::vector<std::string> Cmd = {(TempDir + "/bin/foo").str(), "foo.cc"};
134  Mangler.adjust(Cmd, "foo.cc");
135  // Directory based on resolved symlink, basename preserved.
136  EXPECT_EQ((TempDir + "/lib/foo").str(), Cmd.front());
137 
138  // Set PATH to point to temp/bin so we can find 'foo' on it.
139  ASSERT_TRUE(::getenv("PATH"));
140  auto RestorePath =
141  llvm::make_scope_exit([OldPath = std::string(::getenv("PATH"))] {
142  ::setenv("PATH", OldPath.c_str(), 1);
143  });
144  ::setenv("PATH", (TempDir + "/bin").str().c_str(), /*overwrite=*/1);
145 
146  // Test the case where the driver is a $PATH-relative path to a symlink.
147  Mangler = CommandMangler::forTests();
148  Mangler.ClangPath = testPath("fake/clang");
149  // Driver found on PATH.
150  Cmd = {"foo", "foo.cc"};
151  Mangler.adjust(Cmd, "foo.cc");
152  // Found the symlink and resolved the path as above.
153  EXPECT_EQ((TempDir + "/lib/foo").str(), Cmd.front());
154 
155  // Symlink not resolved with -no-canonical-prefixes.
156  Cmd = {"foo", "-no-canonical-prefixes", "foo.cc"};
157  Mangler.adjust(Cmd, "foo.cc");
158  EXPECT_EQ((TempDir + "/bin/foo").str(), Cmd.front());
159 }
160 #endif
161 
162 TEST(CommandMangler, ConfigEdits) {
163  auto Mangler = CommandMangler::forTests();
164  std::vector<std::string> Cmd = {"clang++", "foo.cc"};
165  {
166  Config Cfg;
167  Cfg.CompileFlags.Edits.push_back([](std::vector<std::string> &Argv) {
168  for (auto &Arg : Argv)
169  for (char &C : Arg)
170  C = llvm::toUpper(C);
171  });
172  Cfg.CompileFlags.Edits.push_back([](std::vector<std::string> &Argv) {
173  Argv = tooling::getInsertArgumentAdjuster("--hello")(Argv, "");
174  });
175  WithContextValue WithConfig(Config::Key, std::move(Cfg));
176  Mangler.adjust(Cmd, "foo.cc");
177  }
178  // Edits are applied in given order and before other mangling and they always
179  // go before filename.
180  EXPECT_THAT(Cmd, ElementsAre(_, "--hello", "--", "FOO.CC"));
181 }
182 
183 static std::string strip(llvm::StringRef Arg, llvm::StringRef Argv) {
184  llvm::SmallVector<llvm::StringRef> Parts;
185  llvm::SplitString(Argv, Parts);
186  std::vector<std::string> Args = {Parts.begin(), Parts.end()};
187  ArgStripper S;
188  S.strip(Arg);
189  S.process(Args);
190  return printArgv(Args);
191 }
192 
193 TEST(ArgStripperTest, Spellings) {
194  // May use alternate prefixes.
195  EXPECT_EQ(strip("-pedantic", "clang -pedantic foo.cc"), "clang foo.cc");
196  EXPECT_EQ(strip("-pedantic", "clang --pedantic foo.cc"), "clang foo.cc");
197  EXPECT_EQ(strip("--pedantic", "clang -pedantic foo.cc"), "clang foo.cc");
198  EXPECT_EQ(strip("--pedantic", "clang --pedantic foo.cc"), "clang foo.cc");
199  // May use alternate names.
200  EXPECT_EQ(strip("-x", "clang -x c++ foo.cc"), "clang foo.cc");
201  EXPECT_EQ(strip("-x", "clang --language=c++ foo.cc"), "clang foo.cc");
202  EXPECT_EQ(strip("--language=", "clang -x c++ foo.cc"), "clang foo.cc");
203  EXPECT_EQ(strip("--language=", "clang --language=c++ foo.cc"),
204  "clang foo.cc");
205 }
206 
207 TEST(ArgStripperTest, UnknownFlag) {
208  EXPECT_EQ(strip("-xyzzy", "clang -xyzzy foo.cc"), "clang foo.cc");
209  EXPECT_EQ(strip("-xyz*", "clang -xyzzy foo.cc"), "clang foo.cc");
210  EXPECT_EQ(strip("-xyzzy", "clang -Xclang -xyzzy foo.cc"), "clang foo.cc");
211 }
212 
213 TEST(ArgStripperTest, Xclang) {
214  // Flags may be -Xclang escaped.
215  EXPECT_EQ(strip("-ast-dump", "clang -Xclang -ast-dump foo.cc"),
216  "clang foo.cc");
217  // Args may be -Xclang escaped.
218  EXPECT_EQ(strip("-add-plugin", "clang -Xclang -add-plugin -Xclang z foo.cc"),
219  "clang foo.cc");
220 }
221 
222 TEST(ArgStripperTest, ClangCL) {
223  // /I is a synonym for -I in clang-cl mode only.
224  // Not stripped by default.
225  EXPECT_EQ(strip("-I", "clang -I /usr/inc /Interesting/file.cc"),
226  "clang /Interesting/file.cc");
227  // Stripped when invoked as clang-cl.
228  EXPECT_EQ(strip("-I", "clang-cl -I /usr/inc /Interesting/file.cc"),
229  "clang-cl");
230  // Stripped when invoked as CL.EXE
231  EXPECT_EQ(strip("-I", "CL.EXE -I /usr/inc /Interesting/file.cc"), "CL.EXE");
232  // Stripped when passed --driver-mode=cl.
233  EXPECT_EQ(strip("-I", "cc -I /usr/inc /Interesting/file.cc --driver-mode=cl"),
234  "cc --driver-mode=cl");
235 }
236 
237 TEST(ArgStripperTest, ArgStyles) {
238  // Flag
239  EXPECT_EQ(strip("-Qn", "clang -Qn foo.cc"), "clang foo.cc");
240  EXPECT_EQ(strip("-Qn", "clang -QnZ foo.cc"), "clang -QnZ foo.cc");
241  // Joined
242  EXPECT_EQ(strip("-std=", "clang -std= foo.cc"), "clang foo.cc");
243  EXPECT_EQ(strip("-std=", "clang -std=c++11 foo.cc"), "clang foo.cc");
244  // Separate
245  EXPECT_EQ(strip("-mllvm", "clang -mllvm X foo.cc"), "clang foo.cc");
246  EXPECT_EQ(strip("-mllvm", "clang -mllvmX foo.cc"), "clang -mllvmX foo.cc");
247  // RemainingArgsJoined
248  EXPECT_EQ(strip("/link", "clang-cl /link b c d foo.cc"), "clang-cl");
249  EXPECT_EQ(strip("/link", "clang-cl /linka b c d foo.cc"), "clang-cl");
250  // CommaJoined
251  EXPECT_EQ(strip("-Wl,", "clang -Wl,x,y foo.cc"), "clang foo.cc");
252  EXPECT_EQ(strip("-Wl,", "clang -Wl, foo.cc"), "clang foo.cc");
253  // MultiArg
254  EXPECT_EQ(strip("-segaddr", "clang -segaddr a b foo.cc"), "clang foo.cc");
255  EXPECT_EQ(strip("-segaddr", "clang -segaddra b foo.cc"),
256  "clang -segaddra b foo.cc");
257  // JoinedOrSeparate
258  EXPECT_EQ(strip("-G", "clang -GX foo.cc"), "clang foo.cc");
259  EXPECT_EQ(strip("-G", "clang -G X foo.cc"), "clang foo.cc");
260  // JoinedAndSeparate
261  EXPECT_EQ(strip("-plugin-arg-", "clang -cc1 -plugin-arg-X Y foo.cc"),
262  "clang -cc1 foo.cc");
263  EXPECT_EQ(strip("-plugin-arg-", "clang -cc1 -plugin-arg- Y foo.cc"),
264  "clang -cc1 foo.cc");
265 }
266 
267 TEST(ArgStripperTest, EndOfList) {
268  // When we hit the end-of-args prematurely, we don't crash.
269  // We consume the incomplete args if we've matched the target option.
270  EXPECT_EQ(strip("-I", "clang -Xclang"), "clang -Xclang");
271  EXPECT_EQ(strip("-I", "clang -Xclang -I"), "clang");
272  EXPECT_EQ(strip("-I", "clang -I -Xclang"), "clang");
273  EXPECT_EQ(strip("-I", "clang -I"), "clang");
274 }
275 
276 TEST(ArgStripperTest, Multiple) {
277  ArgStripper S;
278  S.strip("-o");
279  S.strip("-c");
280  std::vector<std::string> Args = {"clang", "-o", "foo.o", "foo.cc", "-c"};
281  S.process(Args);
282  EXPECT_THAT(Args, ElementsAre("clang", "foo.cc"));
283 }
284 
285 TEST(ArgStripperTest, Warning) {
286  {
287  // -W is a flag name
288  ArgStripper S;
289  S.strip("-W");
290  std::vector<std::string> Args = {"clang", "-Wfoo", "-Wno-bar", "-Werror",
291  "foo.cc"};
292  S.process(Args);
293  EXPECT_THAT(Args, ElementsAre("clang", "foo.cc"));
294  }
295  {
296  // -Wfoo is not a flag name, matched literally.
297  ArgStripper S;
298  S.strip("-Wunused");
299  std::vector<std::string> Args = {"clang", "-Wunused", "-Wno-unused",
300  "foo.cc"};
301  S.process(Args);
302  EXPECT_THAT(Args, ElementsAre("clang", "-Wno-unused", "foo.cc"));
303  }
304 }
305 
306 TEST(ArgStripperTest, Define) {
307  {
308  // -D is a flag name
309  ArgStripper S;
310  S.strip("-D");
311  std::vector<std::string> Args = {"clang", "-Dfoo", "-Dbar=baz", "foo.cc"};
312  S.process(Args);
313  EXPECT_THAT(Args, ElementsAre("clang", "foo.cc"));
314  }
315  {
316  // -Dbar is not: matched literally
317  ArgStripper S;
318  S.strip("-Dbar");
319  std::vector<std::string> Args = {"clang", "-Dfoo", "-Dbar=baz", "foo.cc"};
320  S.process(Args);
321  EXPECT_THAT(Args, ElementsAre("clang", "-Dfoo", "-Dbar=baz", "foo.cc"));
322  S.strip("-Dfoo");
323  S.process(Args);
324  EXPECT_THAT(Args, ElementsAre("clang", "-Dbar=baz", "foo.cc"));
325  S.strip("-Dbar=*");
326  S.process(Args);
327  EXPECT_THAT(Args, ElementsAre("clang", "foo.cc"));
328  }
329 }
330 
331 TEST(ArgStripperTest, OrderDependent) {
332  ArgStripper S;
333  // If -include is stripped first, we see -pch as its arg and foo.pch remains.
334  // To get this case right, we must process -include-pch first.
335  S.strip("-include");
336  S.strip("-include-pch");
337  std::vector<std::string> Args = {"clang", "-include-pch", "foo.pch",
338  "foo.cc"};
339  S.process(Args);
340  EXPECT_THAT(Args, ElementsAre("clang", "foo.cc"));
341 }
342 
343 TEST(PrintArgvTest, All) {
344  std::vector<llvm::StringRef> Args = {
345  "one", "two", "thr ee", "f\"o\"ur", "fi\\ve", "$"
346  };
347  const char *Expected = R"(one two "thr ee" "f\"o\"ur" "fi\\ve" $)";
348  EXPECT_EQ(Expected, printArgv(Args));
349 }
350 
351 TEST(CommandMangler, InputsAfterDashDash) {
352  const auto Mangler = CommandMangler::forTests();
353  {
354  std::vector<std::string> Args = {"clang", "/Users/foo.cc"};
355  Mangler.adjust(Args, "/Users/foo.cc");
356  EXPECT_THAT(llvm::makeArrayRef(Args).take_back(2),
357  ElementsAre("--", "/Users/foo.cc"));
358  EXPECT_THAT(llvm::makeArrayRef(Args).drop_back(2),
359  Not(Contains("/Users/foo.cc")));
360  }
361  // In CL mode /U triggers an undef operation, hence `/Users/foo.cc` shouldn't
362  // be interpreted as a file.
363  {
364  std::vector<std::string> Args = {"clang", "--driver-mode=cl", "bar.cc",
365  "/Users/foo.cc"};
366  Mangler.adjust(Args, "bar.cc");
367  EXPECT_THAT(llvm::makeArrayRef(Args).take_back(2),
368  ElementsAre("--", "bar.cc"));
369  EXPECT_THAT(llvm::makeArrayRef(Args).drop_back(2), Not(Contains("bar.cc")));
370  }
371  // All inputs but the main file is dropped.
372  {
373  std::vector<std::string> Args = {"clang", "foo.cc", "bar.cc"};
374  Mangler.adjust(Args, "baz.cc");
375  EXPECT_THAT(llvm::makeArrayRef(Args).take_back(2),
376  ElementsAre("--", "baz.cc"));
377  EXPECT_THAT(
378  llvm::makeArrayRef(Args).drop_back(2),
379  testing::AllOf(Not(Contains("foo.cc")), Not(Contains("bar.cc"))));
380  }
381 }
382 
383 TEST(CommandMangler, StripsMultipleArch) {
384  const auto Mangler = CommandMangler::forTests();
385  std::vector<std::string> Args = {"clang", "-arch", "foo",
386  "-arch", "bar", "/Users/foo.cc"};
387  Mangler.adjust(Args, "/Users/foo.cc");
388  EXPECT_EQ(
389  llvm::count_if(Args, [](llvm::StringRef Arg) { return Arg == "-arch"; }),
390  0);
391 
392  // Single arch option is preserved.
393  Args = {"clang", "-arch", "foo", "/Users/foo.cc"};
394  Mangler.adjust(Args, "/Users/foo.cc");
395  EXPECT_EQ(
396  llvm::count_if(Args, [](llvm::StringRef Arg) { return Arg == "-arch"; }),
397  1);
398 }
399 
400 TEST(CommandMangler, EmptyArgs) {
401  const auto Mangler = CommandMangler::forTests();
402  std::vector<std::string> Args = {};
403  // Make sure we don't crash.
404  Mangler.adjust(Args, "foo.cc");
405 }
406 } // namespace
407 } // namespace clangd
408 } // namespace clang
clang::clangd::TEST
TEST(BackgroundQueueTest, Priority)
Definition: BackgroundIndexTests.cpp:751
clang::clangd::testPath
std::string testPath(PathRef File, llvm::sys::path::Style Style)
Definition: TestFS.cpp:82
clang::clangd::printArgv
std::string printArgv(llvm::ArrayRef< llvm::StringRef > Args)
Definition: CompileCommands.cpp:582
Expected
std::vector< const char * > Expected
Definition: PrintASTTests.cpp:27
clang::tidy::cppcoreguidelines::join
static std::string join(ArrayRef< SpecialMemberFunctionsCheck::SpecialMemberFunctionKind > SMFS, llvm::StringRef AndOr)
Definition: SpecialMemberFunctionsCheck.cpp:78
CompileCommands.h
Args
llvm::json::Object Args
Definition: Trace.cpp:139
TestFS.h
Config
static cl::opt< std::string > Config("config", cl::desc(R"( Specifies a configuration in YAML/JSON format: -config="{Checks:' *', CheckOptions:[{key:x, value:y}]}" When the value is empty, clang-tidy will attempt to find a file named .clang-tidy for each source file in its parent directories. )"), cl::init(""), cl::cat(ClangTidyCategory))
Config.h
C
const Criteria C
Definition: FunctionCognitiveComplexityCheck.cpp:93
clang
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Definition: ApplyReplacements.h:27
clang::clangd::Config::Key
static clangd::Key< Config > Key
Context key which can be used to set the current Config.
Definition: Config.h:47
clang::clangd::CommandMangler::forTests
static CommandMangler forTests()
Definition: CompileCommands.cpp:196
clang::clangd::MATCHER
MATCHER(Declared, "")
Definition: BackgroundIndexTests.cpp:33
Context.h