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(File: "fake/clang" ); |
51 | Mangler.ResourceDir = testPath(File: "fake/resources" ); |
52 | Mangler.Sysroot = testPath(File: "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(File: "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(File: "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(File: "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(File: "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(File: "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 | F: [&] { llvm::sys::fs::remove_directories(path: 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(file: (TempDir + "/lib/bar" ).str().c_str(), mode: 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(File: "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(F: [OldPath = std::string(::getenv(name: "PATH" ))] { |
168 | ::setenv(name: "PATH" , value: OldPath.c_str(), replace: 1); |
169 | }); |
170 | ::setenv(name: "PATH" , value: (TempDir + "/bin" ).str().c_str(), /*overwrite=*/replace: 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(File: "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(x: [](std::vector<std::string> &Argv) { |
195 | for (auto &Arg : Argv) |
196 | for (char &C : Arg) |
197 | C = llvm::toUpper(x: C); |
198 | }); |
199 | Cfg.CompileFlags.Edits.push_back(x: [](std::vector<std::string> &Argv) { |
200 | Argv = tooling::getInsertArgumentAdjuster(Extra: "--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(Source: Argv, OutFragments&: 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(Arg: "-o" ); |
309 | S.strip(Arg: "-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(Arg: "-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(Arg: "-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(Arg: "-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(Arg: "-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(Arg: "-Dfoo" ); |
353 | S.process(Args); |
354 | EXPECT_THAT(Args, ElementsAre("clang" , "-Dbar=baz" , "foo.cc" )); |
355 | S.strip(Arg: "-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(Arg: "-include" ); |
366 | S.strip(Arg: "-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(File: "fake/resources" ); |
457 | |
458 | { |
459 | tooling::CompileCommand Cmd; |
460 | Cmd.CommandLine = {"clang++" , "-resource-dir" , testPath(File: "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(File: "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(File: "fake/sysroot" ); |
484 | |
485 | { |
486 | tooling::CompileCommand Cmd; |
487 | Cmd.CommandLine = {"clang++" , "-isysroot" , testPath(File: "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(File: "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(File: "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(File: "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 | |