xref: /llvm-project/clang-tools-extra/clangd/unittests/CompileCommandsTests.cpp (revision b8d6885ff67efbc3142a2b49506ed0cc2b95e054)
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/Testing/CommandLineArgs.h"
15 #include "clang/Tooling/ArgumentsAdjusters.h"
16 #include "llvm/ADT/ArrayRef.h"
17 #include "llvm/ADT/STLExtras.h"
18 #include "llvm/ADT/ScopeExit.h"
19 #include "llvm/ADT/StringExtras.h"
20 #include "llvm/ADT/StringRef.h"
21 #include "llvm/Config/llvm-config.h" // for LLVM_ON_UNIX
22 #include "llvm/Support/FileSystem.h"
23 #include "llvm/Support/Path.h"
24 #include "llvm/Support/Process.h"
25 #include "llvm/Support/TargetSelect.h"
26 
27 #include "gmock/gmock.h"
28 #include "gtest/gtest.h"
29 
30 namespace clang {
31 namespace clangd {
32 namespace {
33 
34 using ::testing::_;
35 using ::testing::Contains;
36 using ::testing::ElementsAre;
37 using ::testing::HasSubstr;
38 using ::testing::Not;
39 
40 // Sadly, CommandMangler::detect(), which contains much of the logic, is
41 // a bunch of untested integration glue. We test the string manipulation here
42 // assuming its results are correct.
43 
44 // Make use of all features and assert the exact command we get out.
45 // Other tests just verify presence/absence of certain args.
46 TEST(CommandMangler, Everything) {
47   llvm::InitializeAllTargetInfos(); // As in ClangdMain
48   std::string Target = getAnyTargetForTesting();
49   auto Mangler = CommandMangler::forTests();
50   Mangler.ClangPath = testPath("fake/clang");
51   Mangler.ResourceDir = testPath("fake/resources");
52   Mangler.Sysroot = testPath("fake/sysroot");
53   tooling::CompileCommand Cmd;
54   Cmd.CommandLine = {Target + "-clang++", "--", "foo.cc", "bar.cc"};
55   Mangler(Cmd, "foo.cc");
56   EXPECT_THAT(Cmd.CommandLine,
57               ElementsAre(testPath("fake/" + Target + "-clang++"),
58                           "--target=" + Target, "--driver-mode=g++",
59                           "-resource-dir=" + testPath("fake/resources"),
60                           "-isysroot", testPath("fake/sysroot"), "--",
61                           "foo.cc"));
62 }
63 
64 TEST(CommandMangler, FilenameMismatch) {
65   auto Mangler = CommandMangler::forTests();
66   Mangler.ClangPath = testPath("clang");
67   // Our compile flags refer to foo.cc...
68   tooling::CompileCommand Cmd;
69   Cmd.CommandLine = {"clang", "foo.cc"};
70   // but we're applying it to foo.h...
71   Mangler(Cmd, "foo.h");
72   // so transferCompileCommand should add -x c++-header to preserve semantics.
73   EXPECT_THAT(Cmd.CommandLine, ElementsAre(testPath("clang"), "-x",
74                                            "c++-header", "--", "foo.h"));
75 }
76 
77 TEST(CommandMangler, ResourceDir) {
78   auto Mangler = CommandMangler::forTests();
79   Mangler.ResourceDir = testPath("fake/resources");
80   tooling::CompileCommand Cmd;
81   Cmd.CommandLine = {"clang++", "foo.cc"};
82   Mangler(Cmd, "foo.cc");
83   EXPECT_THAT(Cmd.CommandLine,
84               Contains("-resource-dir=" + testPath("fake/resources")));
85 }
86 
87 TEST(CommandMangler, Sysroot) {
88   auto Mangler = CommandMangler::forTests();
89   Mangler.Sysroot = testPath("fake/sysroot");
90 
91   tooling::CompileCommand Cmd;
92   Cmd.CommandLine = {"clang++", "foo.cc"};
93   Mangler(Cmd, "foo.cc");
94   EXPECT_THAT(llvm::join(Cmd.CommandLine, " "),
95               HasSubstr("-isysroot " + testPath("fake/sysroot")));
96 }
97 
98 TEST(CommandMangler, ClangPath) {
99   auto Mangler = CommandMangler::forTests();
100   Mangler.ClangPath = testPath("fake/clang");
101 
102   tooling::CompileCommand Cmd;
103   Cmd.CommandLine = {"clang++", "foo.cc"};
104   Mangler(Cmd, "foo.cc");
105   EXPECT_EQ(testPath("fake/clang++"), Cmd.CommandLine.front());
106 
107   Cmd.CommandLine = {"unknown-binary", "foo.cc"};
108   Mangler(Cmd, "foo.cc");
109   EXPECT_EQ(testPath("fake/unknown-binary"), Cmd.CommandLine.front());
110 
111   Cmd.CommandLine = {testPath("path/clang++"), "foo.cc"};
112   Mangler(Cmd, "foo.cc");
113   EXPECT_EQ(testPath("path/clang++"), Cmd.CommandLine.front());
114 
115   Cmd.CommandLine = {"foo/unknown-binary", "foo.cc"};
116   Mangler(Cmd, "foo.cc");
117   EXPECT_EQ("foo/unknown-binary", Cmd.CommandLine.front());
118 }
119 
120 // Only run the PATH/symlink resolving test on unix, we need to fiddle
121 // with permissions and environment variables...
122 #ifdef LLVM_ON_UNIX
123 MATCHER(ok, "") {
124   if (arg) {
125     *result_listener << arg.message();
126     return false;
127   }
128   return true;
129 }
130 
131 TEST(CommandMangler, ClangPathResolve) {
132   // Set up filesystem:
133   //   /temp/
134   //     bin/
135   //       foo -> temp/lib/bar
136   //     lib/
137   //       bar
138   llvm::SmallString<256> TempDir;
139   ASSERT_THAT(llvm::sys::fs::createUniqueDirectory("ClangPathResolve", TempDir),
140               ok());
141   // /var/tmp is a symlink on Mac. Resolve it so we're asserting the right path.
142   ASSERT_THAT(llvm::sys::fs::real_path(TempDir.str(), TempDir), ok());
143   auto CleanDir = llvm::make_scope_exit(
144       [&] { llvm::sys::fs::remove_directories(TempDir); });
145   ASSERT_THAT(llvm::sys::fs::create_directory(TempDir + "/bin"), ok());
146   ASSERT_THAT(llvm::sys::fs::create_directory(TempDir + "/lib"), ok());
147   int FD;
148   ASSERT_THAT(llvm::sys::fs::openFileForWrite(TempDir + "/lib/bar", FD), ok());
149   ASSERT_THAT(llvm::sys::Process::SafelyCloseFileDescriptor(FD), ok());
150   ::chmod((TempDir + "/lib/bar").str().c_str(), 0755); // executable
151   ASSERT_THAT(
152       llvm::sys::fs::create_link(TempDir + "/lib/bar", TempDir + "/bin/foo"),
153       ok());
154 
155   // Test the case where the driver is an absolute path to a symlink.
156   auto Mangler = CommandMangler::forTests();
157   Mangler.ClangPath = testPath("fake/clang");
158   tooling::CompileCommand Cmd;
159   Cmd.CommandLine = {(TempDir + "/bin/foo").str(), "foo.cc"};
160   Mangler(Cmd, "foo.cc");
161   // Directory based on resolved symlink, basename preserved.
162   EXPECT_EQ((TempDir + "/lib/foo").str(), Cmd.CommandLine.front());
163 
164   // Set PATH to point to temp/bin so we can find 'foo' on it.
165   ASSERT_TRUE(::getenv("PATH"));
166   auto RestorePath =
167       llvm::make_scope_exit([OldPath = std::string(::getenv("PATH"))] {
168         ::setenv("PATH", OldPath.c_str(), 1);
169       });
170   ::setenv("PATH", (TempDir + "/bin").str().c_str(), /*overwrite=*/1);
171 
172   // Test the case where the driver is a $PATH-relative path to a symlink.
173   Mangler = CommandMangler::forTests();
174   Mangler.ClangPath = testPath("fake/clang");
175   // Driver found on PATH.
176   Cmd.CommandLine = {"foo", "foo.cc"};
177   Mangler(Cmd, "foo.cc");
178   // Found the symlink and resolved the path as above.
179   EXPECT_EQ((TempDir + "/lib/foo").str(), Cmd.CommandLine.front());
180 
181   // Symlink not resolved with -no-canonical-prefixes.
182   Cmd.CommandLine = {"foo", "-no-canonical-prefixes", "foo.cc"};
183   Mangler(Cmd, "foo.cc");
184   EXPECT_EQ((TempDir + "/bin/foo").str(), Cmd.CommandLine.front());
185 }
186 #endif
187 
188 TEST(CommandMangler, ConfigEdits) {
189   auto Mangler = CommandMangler::forTests();
190   tooling::CompileCommand Cmd;
191   Cmd.CommandLine = {"clang++", "foo.cc"};
192   {
193     Config Cfg;
194     Cfg.CompileFlags.Edits.push_back([](std::vector<std::string> &Argv) {
195       for (auto &Arg : Argv)
196         for (char &C : Arg)
197           C = llvm::toUpper(C);
198     });
199     Cfg.CompileFlags.Edits.push_back([](std::vector<std::string> &Argv) {
200       Argv = tooling::getInsertArgumentAdjuster("--hello")(Argv, "");
201     });
202     WithContextValue WithConfig(Config::Key, std::move(Cfg));
203     Mangler(Cmd, "foo.cc");
204   }
205   // Edits are applied in given order and before other mangling and they always
206   // go before filename. `--driver-mode=g++` here is in lower case because
207   // options inserted by addTargetAndModeForProgramName are not editable,
208   // see discussion in https://reviews.llvm.org/D138546
209   EXPECT_THAT(Cmd.CommandLine,
210               ElementsAre(_, "--driver-mode=g++", "--hello", "--", "FOO.CC"));
211 }
212 
213 static std::string strip(llvm::StringRef Arg, llvm::StringRef Argv) {
214   llvm::SmallVector<llvm::StringRef> Parts;
215   llvm::SplitString(Argv, Parts);
216   std::vector<std::string> Args = {Parts.begin(), Parts.end()};
217   ArgStripper S;
218   S.strip(Arg);
219   S.process(Args);
220   return printArgv(Args);
221 }
222 
223 TEST(ArgStripperTest, Spellings) {
224   // May use alternate prefixes.
225   EXPECT_EQ(strip("-pedantic", "clang -pedantic foo.cc"), "clang foo.cc");
226   EXPECT_EQ(strip("-pedantic", "clang --pedantic foo.cc"), "clang foo.cc");
227   EXPECT_EQ(strip("--pedantic", "clang -pedantic foo.cc"), "clang foo.cc");
228   EXPECT_EQ(strip("--pedantic", "clang --pedantic foo.cc"), "clang foo.cc");
229   // May use alternate names.
230   EXPECT_EQ(strip("-x", "clang -x c++ foo.cc"), "clang foo.cc");
231   EXPECT_EQ(strip("-x", "clang --language=c++ foo.cc"), "clang foo.cc");
232   EXPECT_EQ(strip("--language=", "clang -x c++ foo.cc"), "clang foo.cc");
233   EXPECT_EQ(strip("--language=", "clang --language=c++ foo.cc"),
234             "clang foo.cc");
235 }
236 
237 TEST(ArgStripperTest, UnknownFlag) {
238   EXPECT_EQ(strip("-xyzzy", "clang -xyzzy foo.cc"), "clang foo.cc");
239   EXPECT_EQ(strip("-xyz*", "clang -xyzzy foo.cc"), "clang foo.cc");
240   EXPECT_EQ(strip("-xyzzy", "clang -Xclang -xyzzy foo.cc"), "clang foo.cc");
241 }
242 
243 TEST(ArgStripperTest, Xclang) {
244   // Flags may be -Xclang escaped.
245   EXPECT_EQ(strip("-ast-dump", "clang -Xclang -ast-dump foo.cc"),
246             "clang foo.cc");
247   // Args may be -Xclang escaped.
248   EXPECT_EQ(strip("-add-plugin", "clang -Xclang -add-plugin -Xclang z foo.cc"),
249             "clang foo.cc");
250 }
251 
252 TEST(ArgStripperTest, ClangCL) {
253   // /I is a synonym for -I in clang-cl mode only.
254   // Not stripped by default.
255   EXPECT_EQ(strip("-I", "clang -I /usr/inc /Interesting/file.cc"),
256             "clang /Interesting/file.cc");
257   // Stripped when invoked as clang-cl.
258   EXPECT_EQ(strip("-I", "clang-cl -I /usr/inc /Interesting/file.cc"),
259             "clang-cl");
260   // Stripped when invoked as CL.EXE
261   EXPECT_EQ(strip("-I", "CL.EXE -I /usr/inc /Interesting/file.cc"), "CL.EXE");
262   // Stripped when passed --driver-mode=cl.
263   EXPECT_EQ(strip("-I", "cc -I /usr/inc /Interesting/file.cc --driver-mode=cl"),
264             "cc --driver-mode=cl");
265 }
266 
267 TEST(ArgStripperTest, ArgStyles) {
268   // Flag
269   EXPECT_EQ(strip("-Qn", "clang -Qn foo.cc"), "clang foo.cc");
270   EXPECT_EQ(strip("-Qn", "clang -QnZ foo.cc"), "clang -QnZ foo.cc");
271   // Joined
272   EXPECT_EQ(strip("-std=", "clang -std= foo.cc"), "clang foo.cc");
273   EXPECT_EQ(strip("-std=", "clang -std=c++11 foo.cc"), "clang foo.cc");
274   // Separate
275   EXPECT_EQ(strip("-mllvm", "clang -mllvm X foo.cc"), "clang foo.cc");
276   EXPECT_EQ(strip("-mllvm", "clang -mllvmX foo.cc"), "clang -mllvmX foo.cc");
277   // RemainingArgsJoined
278   EXPECT_EQ(strip("/link", "clang-cl /link b c d foo.cc"), "clang-cl");
279   EXPECT_EQ(strip("/link", "clang-cl /linka b c d foo.cc"), "clang-cl");
280   // CommaJoined
281   EXPECT_EQ(strip("-Wl,", "clang -Wl,x,y foo.cc"), "clang foo.cc");
282   EXPECT_EQ(strip("-Wl,", "clang -Wl, foo.cc"), "clang foo.cc");
283   // MultiArg
284   EXPECT_EQ(strip("-segaddr", "clang -segaddr a b foo.cc"), "clang foo.cc");
285   EXPECT_EQ(strip("-segaddr", "clang -segaddra b foo.cc"),
286             "clang -segaddra b foo.cc");
287   // JoinedOrSeparate
288   EXPECT_EQ(strip("-G", "clang -GX foo.cc"), "clang foo.cc");
289   EXPECT_EQ(strip("-G", "clang -G X foo.cc"), "clang foo.cc");
290   // JoinedAndSeparate
291   EXPECT_EQ(strip("-plugin-arg-", "clang -cc1 -plugin-arg-X Y foo.cc"),
292             "clang -cc1 foo.cc");
293   EXPECT_EQ(strip("-plugin-arg-", "clang -cc1 -plugin-arg- Y foo.cc"),
294             "clang -cc1 foo.cc");
295 }
296 
297 TEST(ArgStripperTest, EndOfList) {
298   // When we hit the end-of-args prematurely, we don't crash.
299   // We consume the incomplete args if we've matched the target option.
300   EXPECT_EQ(strip("-I", "clang -Xclang"), "clang -Xclang");
301   EXPECT_EQ(strip("-I", "clang -Xclang -I"), "clang");
302   EXPECT_EQ(strip("-I", "clang -I -Xclang"), "clang");
303   EXPECT_EQ(strip("-I", "clang -I"), "clang");
304 }
305 
306 TEST(ArgStripperTest, Multiple) {
307   ArgStripper S;
308   S.strip("-o");
309   S.strip("-c");
310   std::vector<std::string> Args = {"clang", "-o", "foo.o", "foo.cc", "-c"};
311   S.process(Args);
312   EXPECT_THAT(Args, ElementsAre("clang", "foo.cc"));
313 }
314 
315 TEST(ArgStripperTest, Warning) {
316   {
317     // -W is a flag name
318     ArgStripper S;
319     S.strip("-W");
320     std::vector<std::string> Args = {"clang", "-Wfoo", "-Wno-bar", "-Werror",
321                                      "foo.cc"};
322     S.process(Args);
323     EXPECT_THAT(Args, ElementsAre("clang", "foo.cc"));
324   }
325   {
326     // -Wfoo is not a flag name, matched literally.
327     ArgStripper S;
328     S.strip("-Wunused");
329     std::vector<std::string> Args = {"clang", "-Wunused", "-Wno-unused",
330                                      "foo.cc"};
331     S.process(Args);
332     EXPECT_THAT(Args, ElementsAre("clang", "-Wno-unused", "foo.cc"));
333   }
334 }
335 
336 TEST(ArgStripperTest, Define) {
337   {
338     // -D is a flag name
339     ArgStripper S;
340     S.strip("-D");
341     std::vector<std::string> Args = {"clang", "-Dfoo", "-Dbar=baz", "foo.cc"};
342     S.process(Args);
343     EXPECT_THAT(Args, ElementsAre("clang", "foo.cc"));
344   }
345   {
346     // -Dbar is not: matched literally
347     ArgStripper S;
348     S.strip("-Dbar");
349     std::vector<std::string> Args = {"clang", "-Dfoo", "-Dbar=baz", "foo.cc"};
350     S.process(Args);
351     EXPECT_THAT(Args, ElementsAre("clang", "-Dfoo", "-Dbar=baz", "foo.cc"));
352     S.strip("-Dfoo");
353     S.process(Args);
354     EXPECT_THAT(Args, ElementsAre("clang", "-Dbar=baz", "foo.cc"));
355     S.strip("-Dbar=*");
356     S.process(Args);
357     EXPECT_THAT(Args, ElementsAre("clang", "foo.cc"));
358   }
359 }
360 
361 TEST(ArgStripperTest, OrderDependent) {
362   ArgStripper S;
363   // If -include is stripped first, we see -pch as its arg and foo.pch remains.
364   // To get this case right, we must process -include-pch first.
365   S.strip("-include");
366   S.strip("-include-pch");
367   std::vector<std::string> Args = {"clang", "-include-pch", "foo.pch",
368                                    "foo.cc"};
369   S.process(Args);
370   EXPECT_THAT(Args, ElementsAre("clang", "foo.cc"));
371 }
372 
373 TEST(PrintArgvTest, All) {
374   std::vector<llvm::StringRef> Args = {"one",      "two",    "thr ee",
375                                        "f\"o\"ur", "fi\\ve", "$"};
376   const char *Expected = R"(one two "thr ee" "f\"o\"ur" "fi\\ve" $)";
377   EXPECT_EQ(Expected, printArgv(Args));
378 }
379 
380 TEST(CommandMangler, InputsAfterDashDash) {
381   const auto Mangler = CommandMangler::forTests();
382   {
383     tooling::CompileCommand Cmd;
384     Cmd.CommandLine = {"clang", "/Users/foo.cc"};
385     Mangler(Cmd, "/Users/foo.cc");
386     EXPECT_THAT(llvm::ArrayRef(Cmd.CommandLine).take_back(2),
387                 ElementsAre("--", "/Users/foo.cc"));
388     EXPECT_THAT(llvm::ArrayRef(Cmd.CommandLine).drop_back(2),
389                 Not(Contains("/Users/foo.cc")));
390   }
391   // In CL mode /U triggers an undef operation, hence `/Users/foo.cc` shouldn't
392   // be interpreted as a file.
393   {
394     tooling::CompileCommand Cmd;
395     Cmd.CommandLine = {"clang", "--driver-mode=cl", "bar.cc", "/Users/foo.cc"};
396     Mangler(Cmd, "bar.cc");
397     EXPECT_THAT(llvm::ArrayRef(Cmd.CommandLine).take_back(2),
398                 ElementsAre("--", "bar.cc"));
399     EXPECT_THAT(llvm::ArrayRef(Cmd.CommandLine).drop_back(2),
400                 Not(Contains("bar.cc")));
401   }
402   // All inputs but the main file is dropped.
403   {
404     tooling::CompileCommand Cmd;
405     Cmd.CommandLine = {"clang", "foo.cc", "bar.cc"};
406     Mangler(Cmd, "baz.cc");
407     EXPECT_THAT(llvm::ArrayRef(Cmd.CommandLine).take_back(2),
408                 ElementsAre("--", "baz.cc"));
409     EXPECT_THAT(
410         llvm::ArrayRef(Cmd.CommandLine).drop_back(2),
411         testing::AllOf(Not(Contains("foo.cc")), Not(Contains("bar.cc"))));
412   }
413 }
414 
415 TEST(CommandMangler, StripsMultipleArch) {
416   const auto Mangler = CommandMangler::forTests();
417   tooling::CompileCommand Cmd;
418   Cmd.CommandLine = {"clang", "-arch", "foo", "-arch", "bar", "/Users/foo.cc"};
419   Mangler(Cmd, "/Users/foo.cc");
420   EXPECT_EQ(llvm::count_if(Cmd.CommandLine,
421                            [](llvm::StringRef Arg) { return Arg == "-arch"; }),
422             0);
423 
424   // Single arch option is preserved.
425   Cmd.CommandLine = {"clang", "-arch", "foo", "/Users/foo.cc"};
426   Mangler(Cmd, "/Users/foo.cc");
427   EXPECT_EQ(llvm::count_if(Cmd.CommandLine,
428                            [](llvm::StringRef Arg) { return Arg == "-arch"; }),
429             1);
430 }
431 
432 TEST(CommandMangler, EmptyArgs) {
433   const auto Mangler = CommandMangler::forTests();
434   tooling::CompileCommand Cmd;
435   Cmd.CommandLine = {};
436   // Make sure we don't crash.
437   Mangler(Cmd, "foo.cc");
438 }
439 
440 TEST(CommandMangler, PathsAsPositional) {
441   const auto Mangler = CommandMangler::forTests();
442   tooling::CompileCommand Cmd;
443   Cmd.CommandLine = {
444       "clang",
445       "--driver-mode=cl",
446       "-I",
447       "foo",
448   };
449   // Make sure we don't crash.
450   Mangler(Cmd, "a.cc");
451   EXPECT_THAT(Cmd.CommandLine, Contains("foo"));
452 }
453 
454 TEST(CommandMangler, RespectsOriginalResourceDir) {
455   auto Mangler = CommandMangler::forTests();
456   Mangler.ResourceDir = testPath("fake/resources");
457 
458   {
459     tooling::CompileCommand Cmd;
460     Cmd.CommandLine = {"clang++", "-resource-dir", testPath("true/resources"),
461                        "foo.cc"};
462     Mangler(Cmd, "foo.cc");
463     EXPECT_THAT(llvm::join(Cmd.CommandLine, " "),
464                 HasSubstr("-resource-dir " + testPath("true/resources")));
465     EXPECT_THAT(llvm::join(Cmd.CommandLine, " "),
466                 Not(HasSubstr(testPath("fake/resources"))));
467   }
468 
469   {
470     tooling::CompileCommand Cmd;
471     Cmd.CommandLine = {"clang++", "-resource-dir=" + testPath("true/resources"),
472                        "foo.cc"};
473     Mangler(Cmd, "foo.cc");
474     EXPECT_THAT(llvm::join(Cmd.CommandLine, " "),
475                 HasSubstr("-resource-dir=" + testPath("true/resources")));
476     EXPECT_THAT(llvm::join(Cmd.CommandLine, " "),
477                 Not(HasSubstr(testPath("fake/resources"))));
478   }
479 }
480 
481 TEST(CommandMangler, RespectsOriginalSysroot) {
482   auto Mangler = CommandMangler::forTests();
483   Mangler.Sysroot = testPath("fake/sysroot");
484 
485   {
486     tooling::CompileCommand Cmd;
487     Cmd.CommandLine = {"clang++", "-isysroot", testPath("true/sysroot"),
488                        "foo.cc"};
489     Mangler(Cmd, "foo.cc");
490     EXPECT_THAT(llvm::join(Cmd.CommandLine, " "),
491                 HasSubstr("-isysroot " + testPath("true/sysroot")));
492     EXPECT_THAT(llvm::join(Cmd.CommandLine, " "),
493                 Not(HasSubstr(testPath("fake/sysroot"))));
494   }
495 
496   {
497     tooling::CompileCommand Cmd;
498     Cmd.CommandLine = {"clang++", "-isysroot" + testPath("true/sysroot"),
499                        "foo.cc"};
500     Mangler(Cmd, "foo.cc");
501     EXPECT_THAT(llvm::join(Cmd.CommandLine, " "),
502                 HasSubstr("-isysroot" + testPath("true/sysroot")));
503     EXPECT_THAT(llvm::join(Cmd.CommandLine, " "),
504                 Not(HasSubstr(testPath("fake/sysroot"))));
505   }
506 
507   {
508     tooling::CompileCommand Cmd;
509     Cmd.CommandLine = {"clang++", "--sysroot", testPath("true/sysroot"),
510                        "foo.cc"};
511     Mangler(Cmd, "foo.cc");
512     EXPECT_THAT(llvm::join(Cmd.CommandLine, " "),
513                 HasSubstr("--sysroot " + testPath("true/sysroot")));
514     EXPECT_THAT(llvm::join(Cmd.CommandLine, " "),
515                 Not(HasSubstr(testPath("fake/sysroot"))));
516   }
517 
518   {
519     tooling::CompileCommand Cmd;
520     Cmd.CommandLine = {"clang++", "--sysroot=" + testPath("true/sysroot"),
521                        "foo.cc"};
522     Mangler(Cmd, "foo.cc");
523     EXPECT_THAT(llvm::join(Cmd.CommandLine, " "),
524                 HasSubstr("--sysroot=" + testPath("true/sysroot")));
525     EXPECT_THAT(llvm::join(Cmd.CommandLine, " "),
526                 Not(HasSubstr(testPath("fake/sysroot"))));
527   }
528 }
529 } // namespace
530 } // namespace clangd
531 } // namespace clang
532