1 //===-- CompileCommandsTests.cpp ------------------------------------------===//
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 "CompileCommands.h"
12 #include "support/Context.h"
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/Support/FileSystem.h"
22 #include "llvm/Support/Path.h"
23 #include "llvm/Support/Process.h"
24 #include "llvm/Support/TargetSelect.h"
26 #include "gmock/gmock.h"
27 #include "gtest/gtest.h"
34 using ::testing::Contains
;
35 using ::testing::ElementsAre
;
36 using ::testing::HasSubstr
;
39 // Sadly, CommandMangler::detect(), which contains much of the logic, is
40 // a bunch of untested integration glue. We test the string manipulation here
41 // assuming its results are correct.
43 // Make use of all features and assert the exact command we get out.
44 // Other tests just verify presence/absence of certain args.
45 TEST(CommandMangler
, Everything
) {
46 llvm::InitializeAllTargetInfos(); // As in ClangdMain
47 std::string Target
= getAnyTargetForTesting();
48 auto Mangler
= CommandMangler::forTests();
49 Mangler
.ClangPath
= testPath("fake/clang");
50 Mangler
.ResourceDir
= testPath("fake/resources");
51 Mangler
.Sysroot
= testPath("fake/sysroot");
52 tooling::CompileCommand Cmd
;
53 Cmd
.CommandLine
= {Target
+ "-clang++", "--", "foo.cc", "bar.cc"};
54 Mangler(Cmd
, "foo.cc");
55 EXPECT_THAT(Cmd
.CommandLine
,
56 ElementsAre(testPath("fake/" + Target
+ "-clang++"),
57 "--target=" + Target
, "--driver-mode=g++",
58 "-resource-dir=" + testPath("fake/resources"),
59 "-isysroot", testPath("fake/sysroot"), "--",
63 TEST(CommandMangler
, FilenameMismatch
) {
64 auto Mangler
= CommandMangler::forTests();
65 Mangler
.ClangPath
= testPath("clang");
66 // Our compile flags refer to foo.cc...
67 tooling::CompileCommand Cmd
;
68 Cmd
.CommandLine
= {"clang", "foo.cc"};
69 // but we're applying it to foo.h...
70 Mangler(Cmd
, "foo.h");
71 // so transferCompileCommand should add -x c++-header to preserve semantics.
72 EXPECT_THAT(Cmd
.CommandLine
, ElementsAre(testPath("clang"), "-x",
73 "c++-header", "--", "foo.h"));
76 TEST(CommandMangler
, ResourceDir
) {
77 auto Mangler
= CommandMangler::forTests();
78 Mangler
.ResourceDir
= testPath("fake/resources");
79 tooling::CompileCommand Cmd
;
80 Cmd
.CommandLine
= {"clang++", "foo.cc"};
81 Mangler(Cmd
, "foo.cc");
82 EXPECT_THAT(Cmd
.CommandLine
,
83 Contains("-resource-dir=" + testPath("fake/resources")));
86 TEST(CommandMangler
, Sysroot
) {
87 auto Mangler
= CommandMangler::forTests();
88 Mangler
.Sysroot
= testPath("fake/sysroot");
90 tooling::CompileCommand Cmd
;
91 Cmd
.CommandLine
= {"clang++", "foo.cc"};
92 Mangler(Cmd
, "foo.cc");
93 EXPECT_THAT(llvm::join(Cmd
.CommandLine
, " "),
94 HasSubstr("-isysroot " + testPath("fake/sysroot")));
97 TEST(CommandMangler
, ClangPath
) {
98 auto Mangler
= CommandMangler::forTests();
99 Mangler
.ClangPath
= testPath("fake/clang");
101 tooling::CompileCommand Cmd
;
102 Cmd
.CommandLine
= {"clang++", "foo.cc"};
103 Mangler(Cmd
, "foo.cc");
104 EXPECT_EQ(testPath("fake/clang++"), Cmd
.CommandLine
.front());
106 Cmd
.CommandLine
= {"unknown-binary", "foo.cc"};
107 Mangler(Cmd
, "foo.cc");
108 EXPECT_EQ(testPath("fake/unknown-binary"), Cmd
.CommandLine
.front());
110 Cmd
.CommandLine
= {testPath("path/clang++"), "foo.cc"};
111 Mangler(Cmd
, "foo.cc");
112 EXPECT_EQ(testPath("path/clang++"), Cmd
.CommandLine
.front());
114 Cmd
.CommandLine
= {"foo/unknown-binary", "foo.cc"};
115 Mangler(Cmd
, "foo.cc");
116 EXPECT_EQ("foo/unknown-binary", Cmd
.CommandLine
.front());
119 // Only run the PATH/symlink resolving test on unix, we need to fiddle
120 // with permissions and environment variables...
124 *result_listener
<< arg
.message();
130 TEST(CommandMangler
, ClangPathResolve
) {
131 // Set up filesystem:
134 // foo -> temp/lib/bar
137 llvm::SmallString
<256> TempDir
;
138 ASSERT_THAT(llvm::sys::fs::createUniqueDirectory("ClangPathResolve", TempDir
),
140 // /var/tmp is a symlink on Mac. Resolve it so we're asserting the right path.
141 ASSERT_THAT(llvm::sys::fs::real_path(TempDir
.str(), TempDir
), ok());
142 auto CleanDir
= llvm::make_scope_exit(
143 [&] { llvm::sys::fs::remove_directories(TempDir
); });
144 ASSERT_THAT(llvm::sys::fs::create_directory(TempDir
+ "/bin"), ok());
145 ASSERT_THAT(llvm::sys::fs::create_directory(TempDir
+ "/lib"), ok());
147 ASSERT_THAT(llvm::sys::fs::openFileForWrite(TempDir
+ "/lib/bar", FD
), ok());
148 ASSERT_THAT(llvm::sys::Process::SafelyCloseFileDescriptor(FD
), ok());
149 ::chmod((TempDir
+ "/lib/bar").str().c_str(), 0755); // executable
151 llvm::sys::fs::create_link(TempDir
+ "/lib/bar", TempDir
+ "/bin/foo"),
154 // Test the case where the driver is an absolute path to a symlink.
155 auto Mangler
= CommandMangler::forTests();
156 Mangler
.ClangPath
= testPath("fake/clang");
157 tooling::CompileCommand Cmd
;
158 Cmd
.CommandLine
= {(TempDir
+ "/bin/foo").str(), "foo.cc"};
159 Mangler(Cmd
, "foo.cc");
160 // Directory based on resolved symlink, basename preserved.
161 EXPECT_EQ((TempDir
+ "/lib/foo").str(), Cmd
.CommandLine
.front());
163 // Set PATH to point to temp/bin so we can find 'foo' on it.
164 ASSERT_TRUE(::getenv("PATH"));
166 llvm::make_scope_exit([OldPath
= std::string(::getenv("PATH"))] {
167 ::setenv("PATH", OldPath
.c_str(), 1);
169 ::setenv("PATH", (TempDir
+ "/bin").str().c_str(), /*overwrite=*/1);
171 // Test the case where the driver is a $PATH-relative path to a symlink.
172 Mangler
= CommandMangler::forTests();
173 Mangler
.ClangPath
= testPath("fake/clang");
174 // Driver found on PATH.
175 Cmd
.CommandLine
= {"foo", "foo.cc"};
176 Mangler(Cmd
, "foo.cc");
177 // Found the symlink and resolved the path as above.
178 EXPECT_EQ((TempDir
+ "/lib/foo").str(), Cmd
.CommandLine
.front());
180 // Symlink not resolved with -no-canonical-prefixes.
181 Cmd
.CommandLine
= {"foo", "-no-canonical-prefixes", "foo.cc"};
182 Mangler(Cmd
, "foo.cc");
183 EXPECT_EQ((TempDir
+ "/bin/foo").str(), Cmd
.CommandLine
.front());
187 TEST(CommandMangler
, ConfigEdits
) {
188 auto Mangler
= CommandMangler::forTests();
189 tooling::CompileCommand Cmd
;
190 Cmd
.CommandLine
= {"clang++", "foo.cc"};
193 Cfg
.CompileFlags
.Edits
.push_back([](std::vector
<std::string
> &Argv
) {
194 for (auto &Arg
: Argv
)
196 C
= llvm::toUpper(C
);
198 Cfg
.CompileFlags
.Edits
.push_back([](std::vector
<std::string
> &Argv
) {
199 Argv
= tooling::getInsertArgumentAdjuster("--hello")(Argv
, "");
201 WithContextValue
WithConfig(Config::Key
, std::move(Cfg
));
202 Mangler(Cmd
, "foo.cc");
204 // Edits are applied in given order and before other mangling and they always
205 // go before filename. `--driver-mode=g++` here is in lower case because
206 // options inserted by addTargetAndModeForProgramName are not editable,
207 // see discussion in https://reviews.llvm.org/D138546
208 EXPECT_THAT(Cmd
.CommandLine
,
209 ElementsAre(_
, "--driver-mode=g++", "--hello", "--", "FOO.CC"));
212 TEST(CommandMangler
, ExpandedResponseFiles
) {
213 SmallString
<1024> Path
;
215 ASSERT_FALSE(llvm::sys::fs::createTemporaryFile("args", "", FD
, Path
));
216 llvm::raw_fd_ostream
OutStream(FD
, true);
217 OutStream
<< "-Wall";
220 auto Mangler
= CommandMangler::forTests();
221 tooling::CompileCommand Cmd
;
222 Cmd
.CommandLine
= {"clang", ("@" + Path
).str(), "foo.cc"};
223 Mangler(Cmd
, "foo.cc");
224 EXPECT_THAT(Cmd
.CommandLine
, ElementsAre(_
, "-Wall", "--", "foo.cc"));
227 static std::string
strip(llvm::StringRef Arg
, llvm::StringRef Argv
) {
228 llvm::SmallVector
<llvm::StringRef
> Parts
;
229 llvm::SplitString(Argv
, Parts
);
230 std::vector
<std::string
> Args
= {Parts
.begin(), Parts
.end()};
234 return printArgv(Args
);
237 TEST(ArgStripperTest
, Spellings
) {
238 // May use alternate prefixes.
239 EXPECT_EQ(strip("-pedantic", "clang -pedantic foo.cc"), "clang foo.cc");
240 EXPECT_EQ(strip("-pedantic", "clang --pedantic foo.cc"), "clang foo.cc");
241 EXPECT_EQ(strip("--pedantic", "clang -pedantic foo.cc"), "clang foo.cc");
242 EXPECT_EQ(strip("--pedantic", "clang --pedantic foo.cc"), "clang foo.cc");
243 // May use alternate names.
244 EXPECT_EQ(strip("-x", "clang -x c++ foo.cc"), "clang foo.cc");
245 EXPECT_EQ(strip("-x", "clang --language=c++ foo.cc"), "clang foo.cc");
246 EXPECT_EQ(strip("--language=", "clang -x c++ foo.cc"), "clang foo.cc");
247 EXPECT_EQ(strip("--language=", "clang --language=c++ foo.cc"),
251 TEST(ArgStripperTest
, UnknownFlag
) {
252 EXPECT_EQ(strip("-xyzzy", "clang -xyzzy foo.cc"), "clang foo.cc");
253 EXPECT_EQ(strip("-xyz*", "clang -xyzzy foo.cc"), "clang foo.cc");
254 EXPECT_EQ(strip("-xyzzy", "clang -Xclang -xyzzy foo.cc"), "clang foo.cc");
257 TEST(ArgStripperTest
, Xclang
) {
258 // Flags may be -Xclang escaped.
259 EXPECT_EQ(strip("-ast-dump", "clang -Xclang -ast-dump foo.cc"),
261 // Args may be -Xclang escaped.
262 EXPECT_EQ(strip("-add-plugin", "clang -Xclang -add-plugin -Xclang z foo.cc"),
266 TEST(ArgStripperTest
, ClangCL
) {
267 // /I is a synonym for -I in clang-cl mode only.
268 // Not stripped by default.
269 EXPECT_EQ(strip("-I", "clang -I /usr/inc /Interesting/file.cc"),
270 "clang /Interesting/file.cc");
271 // Stripped when invoked as clang-cl.
272 EXPECT_EQ(strip("-I", "clang-cl -I /usr/inc /Interesting/file.cc"),
274 // Stripped when invoked as CL.EXE
275 EXPECT_EQ(strip("-I", "CL.EXE -I /usr/inc /Interesting/file.cc"), "CL.EXE");
276 // Stripped when passed --driver-mode=cl.
277 EXPECT_EQ(strip("-I", "cc -I /usr/inc /Interesting/file.cc --driver-mode=cl"),
278 "cc --driver-mode=cl");
281 TEST(ArgStripperTest
, ArgStyles
) {
283 EXPECT_EQ(strip("-Qn", "clang -Qn foo.cc"), "clang foo.cc");
284 EXPECT_EQ(strip("-Qn", "clang -QnZ foo.cc"), "clang -QnZ foo.cc");
286 EXPECT_EQ(strip("-std=", "clang -std= foo.cc"), "clang foo.cc");
287 EXPECT_EQ(strip("-std=", "clang -std=c++11 foo.cc"), "clang foo.cc");
289 EXPECT_EQ(strip("-mllvm", "clang -mllvm X foo.cc"), "clang foo.cc");
290 EXPECT_EQ(strip("-mllvm", "clang -mllvmX foo.cc"), "clang -mllvmX foo.cc");
291 // RemainingArgsJoined
292 EXPECT_EQ(strip("/link", "clang-cl /link b c d foo.cc"), "clang-cl");
293 EXPECT_EQ(strip("/link", "clang-cl /linka b c d foo.cc"), "clang-cl");
295 EXPECT_EQ(strip("-Wl,", "clang -Wl,x,y foo.cc"), "clang foo.cc");
296 EXPECT_EQ(strip("-Wl,", "clang -Wl, foo.cc"), "clang foo.cc");
298 EXPECT_EQ(strip("-segaddr", "clang -segaddr a b foo.cc"), "clang foo.cc");
299 EXPECT_EQ(strip("-segaddr", "clang -segaddra b foo.cc"),
300 "clang -segaddra b foo.cc");
302 EXPECT_EQ(strip("-G", "clang -GX foo.cc"), "clang foo.cc");
303 EXPECT_EQ(strip("-G", "clang -G X foo.cc"), "clang foo.cc");
305 EXPECT_EQ(strip("-plugin-arg-", "clang -cc1 -plugin-arg-X Y foo.cc"),
306 "clang -cc1 foo.cc");
307 EXPECT_EQ(strip("-plugin-arg-", "clang -cc1 -plugin-arg- Y foo.cc"),
308 "clang -cc1 foo.cc");
311 TEST(ArgStripperTest
, EndOfList
) {
312 // When we hit the end-of-args prematurely, we don't crash.
313 // We consume the incomplete args if we've matched the target option.
314 EXPECT_EQ(strip("-I", "clang -Xclang"), "clang -Xclang");
315 EXPECT_EQ(strip("-I", "clang -Xclang -I"), "clang");
316 EXPECT_EQ(strip("-I", "clang -I -Xclang"), "clang");
317 EXPECT_EQ(strip("-I", "clang -I"), "clang");
320 TEST(ArgStripperTest
, Multiple
) {
324 std::vector
<std::string
> Args
= {"clang", "-o", "foo.o", "foo.cc", "-c"};
326 EXPECT_THAT(Args
, ElementsAre("clang", "foo.cc"));
329 TEST(ArgStripperTest
, Warning
) {
334 std::vector
<std::string
> Args
= {"clang", "-Wfoo", "-Wno-bar", "-Werror",
337 EXPECT_THAT(Args
, ElementsAre("clang", "foo.cc"));
340 // -Wfoo is not a flag name, matched literally.
343 std::vector
<std::string
> Args
= {"clang", "-Wunused", "-Wno-unused",
346 EXPECT_THAT(Args
, ElementsAre("clang", "-Wno-unused", "foo.cc"));
350 TEST(ArgStripperTest
, Define
) {
355 std::vector
<std::string
> Args
= {"clang", "-Dfoo", "-Dbar=baz", "foo.cc"};
357 EXPECT_THAT(Args
, ElementsAre("clang", "foo.cc"));
360 // -Dbar is not: matched literally
363 std::vector
<std::string
> Args
= {"clang", "-Dfoo", "-Dbar=baz", "foo.cc"};
365 EXPECT_THAT(Args
, ElementsAre("clang", "-Dfoo", "-Dbar=baz", "foo.cc"));
368 EXPECT_THAT(Args
, ElementsAre("clang", "-Dbar=baz", "foo.cc"));
371 EXPECT_THAT(Args
, ElementsAre("clang", "foo.cc"));
375 TEST(ArgStripperTest
, OrderDependent
) {
377 // If -include is stripped first, we see -pch as its arg and foo.pch remains.
378 // To get this case right, we must process -include-pch first.
380 S
.strip("-include-pch");
381 std::vector
<std::string
> Args
= {"clang", "-include-pch", "foo.pch",
384 EXPECT_THAT(Args
, ElementsAre("clang", "foo.cc"));
387 TEST(PrintArgvTest
, All
) {
388 std::vector
<llvm::StringRef
> Args
= {
389 "one", "two", "thr ee", "f\"o\"ur", "fi\\ve", "$"
391 const char *Expected
= R
"(one two "thr ee
" "f
\"o
\"ur
" "fi
\\ve
" $)";
392 EXPECT_EQ(Expected
, printArgv(Args
));
395 TEST(CommandMangler
, InputsAfterDashDash
) {
396 const auto Mangler
= CommandMangler::forTests();
398 tooling::CompileCommand Cmd
;
399 Cmd
.CommandLine
= {"clang", "/Users/foo.cc"};
400 Mangler(Cmd
, "/Users/foo.cc");
401 EXPECT_THAT(llvm::ArrayRef(Cmd
.CommandLine
).take_back(2),
402 ElementsAre("--", "/Users/foo.cc"));
403 EXPECT_THAT(llvm::ArrayRef(Cmd
.CommandLine
).drop_back(2),
404 Not(Contains("/Users/foo.cc")));
406 // In CL mode /U triggers an undef operation, hence `/Users/foo.cc` shouldn't
407 // be interpreted as a file.
409 tooling::CompileCommand Cmd
;
410 Cmd
.CommandLine
= {"clang", "--driver-mode=cl", "bar.cc", "/Users/foo.cc"};
411 Mangler(Cmd
, "bar.cc");
412 EXPECT_THAT(llvm::ArrayRef(Cmd
.CommandLine
).take_back(2),
413 ElementsAre("--", "bar.cc"));
414 EXPECT_THAT(llvm::ArrayRef(Cmd
.CommandLine
).drop_back(2),
415 Not(Contains("bar.cc")));
417 // All inputs but the main file is dropped.
419 tooling::CompileCommand Cmd
;
420 Cmd
.CommandLine
= {"clang", "foo.cc", "bar.cc"};
421 Mangler(Cmd
, "baz.cc");
422 EXPECT_THAT(llvm::ArrayRef(Cmd
.CommandLine
).take_back(2),
423 ElementsAre("--", "baz.cc"));
425 llvm::ArrayRef(Cmd
.CommandLine
).drop_back(2),
426 testing::AllOf(Not(Contains("foo.cc")), Not(Contains("bar.cc"))));
430 TEST(CommandMangler
, StripsMultipleArch
) {
431 const auto Mangler
= CommandMangler::forTests();
432 tooling::CompileCommand Cmd
;
433 Cmd
.CommandLine
= {"clang", "-arch", "foo", "-arch", "bar", "/Users/foo.cc"};
434 Mangler(Cmd
, "/Users/foo.cc");
435 EXPECT_EQ(llvm::count_if(Cmd
.CommandLine
,
436 [](llvm::StringRef Arg
) { return Arg
== "-arch"; }),
439 // Single arch option is preserved.
440 Cmd
.CommandLine
= {"clang", "-arch", "foo", "/Users/foo.cc"};
441 Mangler(Cmd
, "/Users/foo.cc");
442 EXPECT_EQ(llvm::count_if(Cmd
.CommandLine
,
443 [](llvm::StringRef Arg
) { return Arg
== "-arch"; }),
447 TEST(CommandMangler
, EmptyArgs
) {
448 const auto Mangler
= CommandMangler::forTests();
449 tooling::CompileCommand Cmd
;
450 Cmd
.CommandLine
= {};
451 // Make sure we don't crash.
452 Mangler(Cmd
, "foo.cc");
455 TEST(CommandMangler
, PathsAsPositional
) {
456 const auto Mangler
= CommandMangler::forTests();
457 tooling::CompileCommand Cmd
;
464 // Make sure we don't crash.
465 Mangler(Cmd
, "a.cc");
466 EXPECT_THAT(Cmd
.CommandLine
, Contains("foo"));
469 } // namespace clangd