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