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