1 | //===- unittest/Tooling/CompilationDatabaseTest.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 "clang/Tooling/CompilationDatabase.h" |
10 | #include "clang/Tooling/FileMatchTrie.h" |
11 | #include "clang/Tooling/JSONCompilationDatabase.h" |
12 | #include "clang/Tooling/Tooling.h" |
13 | #include "llvm/Support/Path.h" |
14 | #include "llvm/Support/TargetSelect.h" |
15 | #include "gmock/gmock.h" |
16 | #include "gtest/gtest.h" |
17 | #include <algorithm> |
18 | |
19 | namespace clang { |
20 | namespace tooling { |
21 | |
22 | using testing::ElementsAre; |
23 | using testing::EndsWith; |
24 | using testing::IsEmpty; |
25 | using testing::UnorderedElementsAreArray; |
26 | |
27 | static void expectFailure(StringRef JSONDatabase, StringRef Explanation) { |
28 | std::string ErrorMessage; |
29 | EXPECT_EQ(nullptr, |
30 | JSONCompilationDatabase::loadFromBuffer(JSONDatabase, ErrorMessage, |
31 | JSONCommandLineSyntax::Gnu)) |
32 | << "Expected an error because of: " << Explanation.str(); |
33 | } |
34 | |
35 | TEST(JSONCompilationDatabase, ErrsOnInvalidFormat) { |
36 | expectFailure(JSONDatabase: "" , Explanation: "Empty database" ); |
37 | expectFailure(JSONDatabase: "{" , Explanation: "Invalid JSON" ); |
38 | expectFailure(JSONDatabase: "[[]]" , Explanation: "Array instead of object" ); |
39 | expectFailure(JSONDatabase: "[{\"a\":[]}]" , Explanation: "Array instead of value" ); |
40 | expectFailure(JSONDatabase: "[{\"a\":\"b\"}]" , Explanation: "Unknown key" ); |
41 | expectFailure(JSONDatabase: "[{[]:\"\"}]" , Explanation: "Incorrectly typed entry" ); |
42 | expectFailure(JSONDatabase: "[{}]" , Explanation: "Empty entry" ); |
43 | expectFailure(JSONDatabase: "[{\"directory\":\"\",\"command\":\"\"}]" , Explanation: "Missing file" ); |
44 | expectFailure(JSONDatabase: "[{\"directory\":\"\",\"file\":\"\"}]" , Explanation: "Missing command or arguments" ); |
45 | expectFailure(JSONDatabase: "[{\"command\":\"\",\"file\":\"\"}]" , Explanation: "Missing directory" ); |
46 | expectFailure(JSONDatabase: "[{\"directory\":\"\",\"arguments\":[]}]" , Explanation: "Missing file" ); |
47 | expectFailure(JSONDatabase: "[{\"arguments\":\"\",\"file\":\"\"}]" , Explanation: "Missing directory" ); |
48 | expectFailure(JSONDatabase: "[{\"directory\":\"\",\"arguments\":\"\",\"file\":\"\"}]" , Explanation: "Arguments not array" ); |
49 | expectFailure(JSONDatabase: "[{\"directory\":\"\",\"command\":[],\"file\":\"\"}]" , Explanation: "Command not string" ); |
50 | expectFailure(JSONDatabase: "[{\"directory\":\"\",\"arguments\":[[]],\"file\":\"\"}]" , |
51 | Explanation: "Arguments contain non-string" ); |
52 | expectFailure(JSONDatabase: "[{\"output\":[]}]" , Explanation: "Expected strings as value." ); |
53 | } |
54 | |
55 | static std::vector<std::string> getAllFiles(StringRef JSONDatabase, |
56 | std::string &ErrorMessage, |
57 | JSONCommandLineSyntax Syntax) { |
58 | std::unique_ptr<CompilationDatabase> Database( |
59 | JSONCompilationDatabase::loadFromBuffer(DatabaseString: JSONDatabase, ErrorMessage, |
60 | Syntax)); |
61 | if (!Database) { |
62 | ADD_FAILURE() << ErrorMessage; |
63 | return std::vector<std::string>(); |
64 | } |
65 | auto Result = Database->getAllFiles(); |
66 | std::sort(first: Result.begin(), last: Result.end()); |
67 | return Result; |
68 | } |
69 | |
70 | static std::vector<CompileCommand> |
71 | getAllCompileCommands(JSONCommandLineSyntax Syntax, StringRef JSONDatabase, |
72 | std::string &ErrorMessage) { |
73 | std::unique_ptr<CompilationDatabase> Database( |
74 | JSONCompilationDatabase::loadFromBuffer(DatabaseString: JSONDatabase, ErrorMessage, |
75 | Syntax)); |
76 | if (!Database) { |
77 | ADD_FAILURE() << ErrorMessage; |
78 | return std::vector<CompileCommand>(); |
79 | } |
80 | return Database->getAllCompileCommands(); |
81 | } |
82 | |
83 | TEST(JSONCompilationDatabase, GetAllFiles) { |
84 | std::string ErrorMessage; |
85 | EXPECT_THAT(getAllFiles("[]" , ErrorMessage, JSONCommandLineSyntax::Gnu), |
86 | IsEmpty()) |
87 | << ErrorMessage; |
88 | |
89 | std::vector<std::string> expected_files; |
90 | SmallString<16> PathStorage; |
91 | llvm::sys::path::native(path: "//net/dir/file1" , result&: PathStorage); |
92 | expected_files.push_back(x: std::string(PathStorage.str())); |
93 | llvm::sys::path::native(path: "//net/dir/file2" , result&: PathStorage); |
94 | expected_files.push_back(x: std::string(PathStorage.str())); |
95 | llvm::sys::path::native(path: "//net/dir/file3" , result&: PathStorage); |
96 | expected_files.push_back(x: std::string(PathStorage.str())); |
97 | llvm::sys::path::native(path: "//net/file1" , result&: PathStorage); |
98 | expected_files.push_back(x: std::string(PathStorage.str())); |
99 | EXPECT_THAT(getAllFiles(R"json( |
100 | [ |
101 | { |
102 | "directory": "//net/dir", |
103 | "command": "command", |
104 | "file": "file1" |
105 | }, |
106 | { |
107 | "directory": "//net/dir", |
108 | "command": "command", |
109 | "file": "../file1" |
110 | }, |
111 | { |
112 | "directory": "//net/dir", |
113 | "command": "command", |
114 | "file": "file2" |
115 | }, |
116 | { |
117 | "directory": "//net/dir", |
118 | "command": "command", |
119 | "file": "//net/dir/foo/../file3" |
120 | } |
121 | ])json" , |
122 | ErrorMessage, JSONCommandLineSyntax::Gnu), |
123 | UnorderedElementsAreArray(expected_files)) |
124 | << ErrorMessage; |
125 | } |
126 | |
127 | TEST(JSONCompilationDatabase, GetAllCompileCommands) { |
128 | std::string ErrorMessage; |
129 | EXPECT_EQ( |
130 | 0u, getAllCompileCommands(JSONCommandLineSyntax::Gnu, "[]" , ErrorMessage) |
131 | .size()) |
132 | << ErrorMessage; |
133 | |
134 | StringRef Directory1("//net/dir1" ); |
135 | StringRef FileName1("file1" ); |
136 | StringRef Command1("command1" ); |
137 | StringRef Output1("file1.o" ); |
138 | StringRef Directory2("//net/dir2" ); |
139 | StringRef FileName2("file2" ); |
140 | StringRef Command2("command2" ); |
141 | StringRef Output2("" ); |
142 | |
143 | std::vector<CompileCommand> Commands = getAllCompileCommands( |
144 | Syntax: JSONCommandLineSyntax::Gnu, |
145 | JSONDatabase: ("[{\"directory\":\"" + Directory1 + "\"," + "\"command\":\"" + Command1 + |
146 | "\"," |
147 | "\"file\":\"" + |
148 | FileName1 + "\", \"output\":\"" + |
149 | Output1 + "\"}," |
150 | " {\"directory\":\"" + |
151 | Directory2 + "\"," + "\"command\":\"" + Command2 + "\"," |
152 | "\"file\":\"" + |
153 | FileName2 + "\"}]" ) |
154 | .str(), |
155 | ErrorMessage); |
156 | EXPECT_EQ(2U, Commands.size()) << ErrorMessage; |
157 | EXPECT_EQ(Directory1, Commands[0].Directory) << ErrorMessage; |
158 | EXPECT_EQ(FileName1, Commands[0].Filename) << ErrorMessage; |
159 | EXPECT_EQ(Output1, Commands[0].Output) << ErrorMessage; |
160 | ASSERT_EQ(1u, Commands[0].CommandLine.size()); |
161 | EXPECT_EQ(Command1, Commands[0].CommandLine[0]) << ErrorMessage; |
162 | EXPECT_EQ(Directory2, Commands[1].Directory) << ErrorMessage; |
163 | EXPECT_EQ(FileName2, Commands[1].Filename) << ErrorMessage; |
164 | EXPECT_EQ(Output2, Commands[1].Output) << ErrorMessage; |
165 | ASSERT_EQ(1u, Commands[1].CommandLine.size()); |
166 | EXPECT_EQ(Command2, Commands[1].CommandLine[0]) << ErrorMessage; |
167 | |
168 | // Check that order is preserved. |
169 | Commands = getAllCompileCommands( |
170 | Syntax: JSONCommandLineSyntax::Gnu, |
171 | JSONDatabase: ("[{\"directory\":\"" + Directory2 + "\"," + "\"command\":\"" + Command2 + |
172 | "\"," |
173 | "\"file\":\"" + |
174 | FileName2 + "\"}," |
175 | " {\"directory\":\"" + |
176 | Directory1 + "\"," + "\"command\":\"" + Command1 + "\"," |
177 | "\"file\":\"" + |
178 | FileName1 + "\"}]" ) |
179 | .str(), |
180 | ErrorMessage); |
181 | EXPECT_EQ(2U, Commands.size()) << ErrorMessage; |
182 | EXPECT_EQ(Directory2, Commands[0].Directory) << ErrorMessage; |
183 | EXPECT_EQ(FileName2, Commands[0].Filename) << ErrorMessage; |
184 | ASSERT_EQ(1u, Commands[0].CommandLine.size()); |
185 | EXPECT_EQ(Command2, Commands[0].CommandLine[0]) << ErrorMessage; |
186 | EXPECT_EQ(Directory1, Commands[1].Directory) << ErrorMessage; |
187 | EXPECT_EQ(FileName1, Commands[1].Filename) << ErrorMessage; |
188 | ASSERT_EQ(1u, Commands[1].CommandLine.size()); |
189 | EXPECT_EQ(Command1, Commands[1].CommandLine[0]) << ErrorMessage; |
190 | } |
191 | |
192 | static CompileCommand findCompileArgsInJsonDatabase(StringRef FileName, |
193 | std::string JSONDatabase, |
194 | std::string &ErrorMessage) { |
195 | std::unique_ptr<CompilationDatabase> Database( |
196 | JSONCompilationDatabase::loadFromBuffer(DatabaseString: JSONDatabase, ErrorMessage, |
197 | Syntax: JSONCommandLineSyntax::Gnu)); |
198 | if (!Database) |
199 | return CompileCommand(); |
200 | // Overwrite the string to verify we're not reading from it later. |
201 | JSONDatabase.assign(n: JSONDatabase.size(), c: '*'); |
202 | std::vector<CompileCommand> Commands = Database->getCompileCommands(FilePath: FileName); |
203 | EXPECT_LE(Commands.size(), 1u); |
204 | if (Commands.empty()) |
205 | return CompileCommand(); |
206 | return Commands[0]; |
207 | } |
208 | |
209 | TEST(JSONCompilationDatabase, ArgumentsPreferredOverCommand) { |
210 | StringRef Directory("//net/dir" ); |
211 | StringRef FileName("//net/dir/filename" ); |
212 | StringRef Command("command" ); |
213 | StringRef Arguments = "arguments" ; |
214 | Twine ArgumentsAccumulate; |
215 | std::string ErrorMessage; |
216 | CompileCommand FoundCommand = findCompileArgsInJsonDatabase( |
217 | FileName, |
218 | JSONDatabase: ("[{\"directory\":\"" + Directory + "\"," |
219 | "\"arguments\":[\"" + Arguments + "\"]," |
220 | "\"command\":\"" + Command + "\"," |
221 | "\"file\":\"" + FileName + "\"}]" ).str(), |
222 | ErrorMessage); |
223 | EXPECT_EQ(Directory, FoundCommand.Directory) << ErrorMessage; |
224 | EXPECT_EQ(1u, FoundCommand.CommandLine.size()) << ErrorMessage; |
225 | EXPECT_EQ(Arguments, FoundCommand.CommandLine[0]) << ErrorMessage; |
226 | } |
227 | |
228 | struct FakeComparator : public PathComparator { |
229 | ~FakeComparator() override {} |
230 | bool equivalent(StringRef FileA, StringRef FileB) const override { |
231 | return FileA.equals_insensitive(RHS: FileB); |
232 | } |
233 | }; |
234 | |
235 | class FileMatchTrieTest : public ::testing::Test { |
236 | protected: |
237 | FileMatchTrieTest() : Trie(new FakeComparator()) {} |
238 | |
239 | StringRef find(StringRef Path) { |
240 | llvm::raw_string_ostream ES(Error); |
241 | return Trie.findEquivalent(FileName: Path, Error&: ES); |
242 | } |
243 | |
244 | FileMatchTrie Trie; |
245 | std::string Error; |
246 | }; |
247 | |
248 | TEST_F(FileMatchTrieTest, InsertingRelativePath) { |
249 | Trie.insert(NewPath: "//net/path/file.cc" ); |
250 | Trie.insert(NewPath: "file.cc" ); |
251 | EXPECT_EQ("//net/path/file.cc" , find("//net/path/file.cc" )); |
252 | } |
253 | |
254 | TEST_F(FileMatchTrieTest, MatchingRelativePath) { |
255 | EXPECT_EQ("" , find("file.cc" )); |
256 | } |
257 | |
258 | TEST_F(FileMatchTrieTest, ReturnsBestResults) { |
259 | Trie.insert(NewPath: "//net/d/c/b.cc" ); |
260 | Trie.insert(NewPath: "//net/d/b/b.cc" ); |
261 | EXPECT_EQ("//net/d/b/b.cc" , find("//net/d/b/b.cc" )); |
262 | } |
263 | |
264 | TEST_F(FileMatchTrieTest, HandlesSymlinks) { |
265 | Trie.insert(NewPath: "//net/AA/file.cc" ); |
266 | EXPECT_EQ("//net/AA/file.cc" , find("//net/aa/file.cc" )); |
267 | } |
268 | |
269 | TEST_F(FileMatchTrieTest, ReportsSymlinkAmbiguity) { |
270 | Trie.insert(NewPath: "//net/Aa/file.cc" ); |
271 | Trie.insert(NewPath: "//net/aA/file.cc" ); |
272 | EXPECT_TRUE(find("//net/aa/file.cc" ).empty()); |
273 | EXPECT_EQ("Path is ambiguous" , Error); |
274 | } |
275 | |
276 | TEST_F(FileMatchTrieTest, LongerMatchingSuffixPreferred) { |
277 | Trie.insert(NewPath: "//net/src/Aa/file.cc" ); |
278 | Trie.insert(NewPath: "//net/src/aA/file.cc" ); |
279 | Trie.insert(NewPath: "//net/SRC/aa/file.cc" ); |
280 | EXPECT_EQ("//net/SRC/aa/file.cc" , find("//net/src/aa/file.cc" )); |
281 | } |
282 | |
283 | TEST_F(FileMatchTrieTest, EmptyTrie) { |
284 | EXPECT_TRUE(find("//net/some/path" ).empty()); |
285 | } |
286 | |
287 | TEST_F(FileMatchTrieTest, NoResult) { |
288 | Trie.insert(NewPath: "//net/somepath/otherfile.cc" ); |
289 | Trie.insert(NewPath: "//net/otherpath/somefile.cc" ); |
290 | EXPECT_EQ("" , find("//net/somepath/somefile.cc" )); |
291 | } |
292 | |
293 | TEST_F(FileMatchTrieTest, RootElementDifferent) { |
294 | Trie.insert(NewPath: "//net/path/file.cc" ); |
295 | Trie.insert(NewPath: "//net/otherpath/file.cc" ); |
296 | EXPECT_EQ("//net/path/file.cc" , find("//net/path/file.cc" )); |
297 | } |
298 | |
299 | TEST_F(FileMatchTrieTest, CannotResolveRelativePath) { |
300 | EXPECT_EQ("" , find("relative-path.cc" )); |
301 | EXPECT_EQ("Cannot resolve relative paths" , Error); |
302 | } |
303 | |
304 | TEST_F(FileMatchTrieTest, SingleFile) { |
305 | Trie.insert(NewPath: "/root/RootFile.cc" ); |
306 | EXPECT_EQ("" , find("/root/rootfile.cc" )); |
307 | // Add subpath to avoid `if (Children.empty())` special case |
308 | // which we hit at previous `find()`. |
309 | Trie.insert(NewPath: "/root/otherpath/OtherFile.cc" ); |
310 | EXPECT_EQ("" , find("/root/rootfile.cc" )); |
311 | } |
312 | |
313 | TEST(findCompileArgsInJsonDatabase, FindsNothingIfEmpty) { |
314 | std::string ErrorMessage; |
315 | CompileCommand NotFound = findCompileArgsInJsonDatabase( |
316 | FileName: "a-file.cpp" , JSONDatabase: "" , ErrorMessage); |
317 | EXPECT_TRUE(NotFound.CommandLine.empty()) << ErrorMessage; |
318 | EXPECT_TRUE(NotFound.Directory.empty()) << ErrorMessage; |
319 | } |
320 | |
321 | TEST(findCompileArgsInJsonDatabase, ReadsSingleEntry) { |
322 | StringRef Directory("//net/some/directory" ); |
323 | StringRef FileName("//net/path/to/a-file.cpp" ); |
324 | StringRef Command("//net/path/to/compiler and some arguments" ); |
325 | std::string ErrorMessage; |
326 | CompileCommand FoundCommand = findCompileArgsInJsonDatabase( |
327 | FileName, |
328 | JSONDatabase: ("[{\"directory\":\"" + Directory + "\"," + |
329 | "\"command\":\"" + Command + "\"," |
330 | "\"file\":\"" + FileName + "\"}]" ).str(), |
331 | ErrorMessage); |
332 | EXPECT_EQ(Directory, FoundCommand.Directory) << ErrorMessage; |
333 | ASSERT_EQ(4u, FoundCommand.CommandLine.size()) << ErrorMessage; |
334 | EXPECT_EQ("//net/path/to/compiler" , |
335 | FoundCommand.CommandLine[0]) << ErrorMessage; |
336 | EXPECT_EQ("and" , FoundCommand.CommandLine[1]) << ErrorMessage; |
337 | EXPECT_EQ("some" , FoundCommand.CommandLine[2]) << ErrorMessage; |
338 | EXPECT_EQ("arguments" , FoundCommand.CommandLine[3]) << ErrorMessage; |
339 | |
340 | CompileCommand NotFound = findCompileArgsInJsonDatabase( |
341 | FileName: "a-file.cpp" , |
342 | JSONDatabase: ("[{\"directory\":\"" + Directory + "\"," + |
343 | "\"command\":\"" + Command + "\"," |
344 | "\"file\":\"" + FileName + "\"}]" ).str(), |
345 | ErrorMessage); |
346 | EXPECT_TRUE(NotFound.Directory.empty()) << ErrorMessage; |
347 | EXPECT_TRUE(NotFound.CommandLine.empty()) << ErrorMessage; |
348 | } |
349 | |
350 | TEST(findCompileArgsInJsonDatabase, ReadsCompileCommandLinesWithSpaces) { |
351 | StringRef Directory("//net/some/directory" ); |
352 | StringRef FileName("//net/path/to/a-file.cpp" ); |
353 | StringRef Command("\\\"//net/path to compiler\\\" \\\"and an argument\\\"" ); |
354 | std::string ErrorMessage; |
355 | CompileCommand FoundCommand = findCompileArgsInJsonDatabase( |
356 | FileName, |
357 | JSONDatabase: ("[{\"directory\":\"" + Directory + "\"," + |
358 | "\"command\":\"" + Command + "\"," |
359 | "\"file\":\"" + FileName + "\"}]" ).str(), |
360 | ErrorMessage); |
361 | ASSERT_EQ(2u, FoundCommand.CommandLine.size()); |
362 | EXPECT_EQ("//net/path to compiler" , |
363 | FoundCommand.CommandLine[0]) << ErrorMessage; |
364 | EXPECT_EQ("and an argument" , FoundCommand.CommandLine[1]) << ErrorMessage; |
365 | } |
366 | |
367 | TEST(findCompileArgsInJsonDatabase, ReadsDirectoryWithSpaces) { |
368 | StringRef Directory("//net/some directory / with spaces" ); |
369 | StringRef FileName("//net/path/to/a-file.cpp" ); |
370 | StringRef Command("a command" ); |
371 | std::string ErrorMessage; |
372 | CompileCommand FoundCommand = findCompileArgsInJsonDatabase( |
373 | FileName, |
374 | JSONDatabase: ("[{\"directory\":\"" + Directory + "\"," + |
375 | "\"command\":\"" + Command + "\"," |
376 | "\"file\":\"" + FileName + "\"}]" ).str(), |
377 | ErrorMessage); |
378 | EXPECT_EQ(Directory, FoundCommand.Directory) << ErrorMessage; |
379 | } |
380 | |
381 | TEST(findCompileArgsInJsonDatabase, FindsEntry) { |
382 | StringRef Directory("//net/directory" ); |
383 | StringRef FileName("file" ); |
384 | StringRef Command("command" ); |
385 | std::string JsonDatabase = "[" ; |
386 | for (int I = 0; I < 10; ++I) { |
387 | if (I > 0) JsonDatabase += "," ; |
388 | JsonDatabase += |
389 | ("{\"directory\":\"" + Directory + Twine(I) + "\"," + |
390 | "\"command\":\"" + Command + Twine(I) + "\"," |
391 | "\"file\":\"" + FileName + Twine(I) + "\"}" ).str(); |
392 | } |
393 | JsonDatabase += "]" ; |
394 | std::string ErrorMessage; |
395 | CompileCommand FoundCommand = findCompileArgsInJsonDatabase( |
396 | FileName: "//net/directory4/file4" , JSONDatabase: JsonDatabase, ErrorMessage); |
397 | EXPECT_EQ("//net/directory4" , FoundCommand.Directory) << ErrorMessage; |
398 | ASSERT_EQ(1u, FoundCommand.CommandLine.size()) << ErrorMessage; |
399 | EXPECT_EQ("command4" , FoundCommand.CommandLine[0]) << ErrorMessage; |
400 | } |
401 | |
402 | TEST(findCompileArgsInJsonDatabase, ParsesCompilerWrappers) { |
403 | std::vector<std::pair<std::string, std::string>> Cases = { |
404 | {"distcc gcc foo.c" , "gcc foo.c" }, |
405 | {"gomacc clang++ foo.c" , "clang++ foo.c" }, |
406 | {"sccache clang++ foo.c" , "clang++ foo.c" }, |
407 | {"ccache gcc foo.c" , "gcc foo.c" }, |
408 | {"ccache.exe gcc foo.c" , "gcc foo.c" }, |
409 | {"ccache g++.exe foo.c" , "g++.exe foo.c" }, |
410 | {"ccache distcc gcc foo.c" , "gcc foo.c" }, |
411 | |
412 | {"distcc foo.c" , "distcc foo.c" }, |
413 | {"distcc -I/foo/bar foo.c" , "distcc -I/foo/bar foo.c" }, |
414 | }; |
415 | std::string ErrorMessage; |
416 | |
417 | for (const auto &Case : Cases) { |
418 | std::string DB = |
419 | R"([{"directory":"//net/dir", "file":"//net/dir/foo.c", "command":")" + |
420 | Case.first + "\"}]" ; |
421 | CompileCommand FoundCommand = |
422 | findCompileArgsInJsonDatabase(FileName: "//net/dir/foo.c" , JSONDatabase: DB, ErrorMessage); |
423 | EXPECT_EQ(Case.second, llvm::join(FoundCommand.CommandLine, " " )) |
424 | << Case.first; |
425 | } |
426 | } |
427 | |
428 | static std::vector<std::string> unescapeJsonCommandLine(StringRef Command) { |
429 | std::string JsonDatabase = |
430 | ("[{\"directory\":\"//net/root\", \"file\":\"test\", \"command\": \"" + |
431 | Command + "\"}]" ).str(); |
432 | std::string ErrorMessage; |
433 | CompileCommand FoundCommand = findCompileArgsInJsonDatabase( |
434 | FileName: "//net/root/test" , JSONDatabase: JsonDatabase, ErrorMessage); |
435 | EXPECT_TRUE(ErrorMessage.empty()) << ErrorMessage; |
436 | return FoundCommand.CommandLine; |
437 | } |
438 | |
439 | TEST(unescapeJsonCommandLine, ReturnsEmptyArrayOnEmptyString) { |
440 | std::vector<std::string> Result = unescapeJsonCommandLine(Command: "" ); |
441 | EXPECT_TRUE(Result.empty()); |
442 | } |
443 | |
444 | TEST(unescapeJsonCommandLine, SplitsOnSpaces) { |
445 | std::vector<std::string> Result = unescapeJsonCommandLine(Command: "a b c" ); |
446 | ASSERT_EQ(3ul, Result.size()); |
447 | EXPECT_EQ("a" , Result[0]); |
448 | EXPECT_EQ("b" , Result[1]); |
449 | EXPECT_EQ("c" , Result[2]); |
450 | } |
451 | |
452 | TEST(unescapeJsonCommandLine, MungesMultipleSpaces) { |
453 | std::vector<std::string> Result = unescapeJsonCommandLine(Command: " a b " ); |
454 | ASSERT_EQ(2ul, Result.size()); |
455 | EXPECT_EQ("a" , Result[0]); |
456 | EXPECT_EQ("b" , Result[1]); |
457 | } |
458 | |
459 | TEST(unescapeJsonCommandLine, UnescapesBackslashCharacters) { |
460 | std::vector<std::string> Backslash = unescapeJsonCommandLine(Command: "a\\\\\\\\" ); |
461 | ASSERT_EQ(1ul, Backslash.size()); |
462 | EXPECT_EQ("a\\" , Backslash[0]); |
463 | std::vector<std::string> Quote = unescapeJsonCommandLine(Command: "a\\\\\\\"" ); |
464 | ASSERT_EQ(1ul, Quote.size()); |
465 | EXPECT_EQ("a\"" , Quote[0]); |
466 | } |
467 | |
468 | TEST(unescapeJsonCommandLine, DoesNotMungeSpacesBetweenQuotes) { |
469 | std::vector<std::string> Result = unescapeJsonCommandLine(Command: "\\\" a b \\\"" ); |
470 | ASSERT_EQ(1ul, Result.size()); |
471 | EXPECT_EQ(" a b " , Result[0]); |
472 | } |
473 | |
474 | TEST(unescapeJsonCommandLine, AllowsMultipleQuotedArguments) { |
475 | std::vector<std::string> Result = unescapeJsonCommandLine( |
476 | Command: " \\\" a \\\" \\\" b \\\" " ); |
477 | ASSERT_EQ(2ul, Result.size()); |
478 | EXPECT_EQ(" a " , Result[0]); |
479 | EXPECT_EQ(" b " , Result[1]); |
480 | } |
481 | |
482 | TEST(unescapeJsonCommandLine, AllowsEmptyArgumentsInQuotes) { |
483 | std::vector<std::string> Result = unescapeJsonCommandLine( |
484 | Command: "\\\"\\\"\\\"\\\"" ); |
485 | ASSERT_EQ(1ul, Result.size()); |
486 | EXPECT_TRUE(Result[0].empty()) << Result[0]; |
487 | } |
488 | |
489 | TEST(unescapeJsonCommandLine, ParsesEscapedQuotesInQuotedStrings) { |
490 | std::vector<std::string> Result = unescapeJsonCommandLine( |
491 | Command: "\\\"\\\\\\\"\\\"" ); |
492 | ASSERT_EQ(1ul, Result.size()); |
493 | EXPECT_EQ("\"" , Result[0]); |
494 | } |
495 | |
496 | TEST(unescapeJsonCommandLine, ParsesMultipleArgumentsWithEscapedCharacters) { |
497 | std::vector<std::string> Result = unescapeJsonCommandLine( |
498 | Command: " \\\\\\\" \\\"a \\\\\\\" b \\\" \\\"and\\\\\\\\c\\\" \\\\\\\"" ); |
499 | ASSERT_EQ(4ul, Result.size()); |
500 | EXPECT_EQ("\"" , Result[0]); |
501 | EXPECT_EQ("a \" b " , Result[1]); |
502 | EXPECT_EQ("and\\c" , Result[2]); |
503 | EXPECT_EQ("\"" , Result[3]); |
504 | } |
505 | |
506 | TEST(unescapeJsonCommandLine, ParsesStringsWithoutSpacesIntoSingleArgument) { |
507 | std::vector<std::string> QuotedNoSpaces = unescapeJsonCommandLine( |
508 | Command: "\\\"a\\\"\\\"b\\\"" ); |
509 | ASSERT_EQ(1ul, QuotedNoSpaces.size()); |
510 | EXPECT_EQ("ab" , QuotedNoSpaces[0]); |
511 | |
512 | std::vector<std::string> MixedNoSpaces = unescapeJsonCommandLine( |
513 | Command: "\\\"a\\\"bcd\\\"ef\\\"\\\"\\\"\\\"g\\\"" ); |
514 | ASSERT_EQ(1ul, MixedNoSpaces.size()); |
515 | EXPECT_EQ("abcdefg" , MixedNoSpaces[0]); |
516 | } |
517 | |
518 | TEST(unescapeJsonCommandLine, ParsesQuotedStringWithoutClosingQuote) { |
519 | std::vector<std::string> Unclosed = unescapeJsonCommandLine(Command: "\\\"abc" ); |
520 | ASSERT_EQ(1ul, Unclosed.size()); |
521 | EXPECT_EQ("abc" , Unclosed[0]); |
522 | |
523 | std::vector<std::string> Empty = unescapeJsonCommandLine(Command: "\\\"" ); |
524 | ASSERT_EQ(1ul, Empty.size()); |
525 | EXPECT_EQ("" , Empty[0]); |
526 | } |
527 | |
528 | TEST(unescapeJsonCommandLine, ParsesSingleQuotedString) { |
529 | std::vector<std::string> Args = unescapeJsonCommandLine(Command: "a'\\\\b \\\"c\\\"'" ); |
530 | ASSERT_EQ(1ul, Args.size()); |
531 | EXPECT_EQ("a\\b \"c\"" , Args[0]); |
532 | } |
533 | |
534 | TEST(FixedCompilationDatabase, ReturnsFixedCommandLine) { |
535 | FixedCompilationDatabase Database("." , /*CommandLine*/ {"one" , "two" }); |
536 | StringRef FileName("source" ); |
537 | std::vector<CompileCommand> Result = |
538 | Database.getCompileCommands(FilePath: FileName); |
539 | ASSERT_EQ(1ul, Result.size()); |
540 | EXPECT_EQ("." , Result[0].Directory); |
541 | EXPECT_EQ(FileName, Result[0].Filename); |
542 | EXPECT_THAT(Result[0].CommandLine, |
543 | ElementsAre(EndsWith("clang-tool" ), "one" , "two" , "source" )); |
544 | } |
545 | |
546 | TEST(FixedCompilationDatabase, GetAllFiles) { |
547 | std::vector<std::string> CommandLine; |
548 | CommandLine.push_back(x: "one" ); |
549 | CommandLine.push_back(x: "two" ); |
550 | FixedCompilationDatabase Database("." , CommandLine); |
551 | |
552 | EXPECT_THAT(Database.getAllFiles(), IsEmpty()); |
553 | } |
554 | |
555 | TEST(FixedCompilationDatabase, GetAllCompileCommands) { |
556 | std::vector<std::string> CommandLine; |
557 | CommandLine.push_back(x: "one" ); |
558 | CommandLine.push_back(x: "two" ); |
559 | FixedCompilationDatabase Database("." , CommandLine); |
560 | |
561 | EXPECT_EQ(0ul, Database.getAllCompileCommands().size()); |
562 | } |
563 | |
564 | TEST(FixedCompilationDatabase, FromBuffer) { |
565 | const char *Data = R"( |
566 | |
567 | -DFOO=BAR |
568 | |
569 | --baz |
570 | |
571 | )" ; |
572 | std::string ErrorMsg; |
573 | auto CDB = |
574 | FixedCompilationDatabase::loadFromBuffer(Directory: "/cdb/dir" , Data, ErrorMsg); |
575 | |
576 | std::vector<CompileCommand> Result = CDB->getCompileCommands(FilePath: "/foo/bar.cc" ); |
577 | ASSERT_EQ(1ul, Result.size()); |
578 | EXPECT_EQ("/cdb/dir" , Result.front().Directory); |
579 | EXPECT_EQ("/foo/bar.cc" , Result.front().Filename); |
580 | EXPECT_THAT( |
581 | Result.front().CommandLine, |
582 | ElementsAre(EndsWith("clang-tool" ), "-DFOO=BAR" , "--baz" , "/foo/bar.cc" )); |
583 | } |
584 | |
585 | TEST(ParseFixedCompilationDatabase, ReturnsNullOnEmptyArgumentList) { |
586 | int Argc = 0; |
587 | std::string ErrorMsg; |
588 | std::unique_ptr<FixedCompilationDatabase> Database = |
589 | FixedCompilationDatabase::loadFromCommandLine(Argc, Argv: nullptr, ErrorMsg); |
590 | EXPECT_FALSE(Database); |
591 | EXPECT_TRUE(ErrorMsg.empty()); |
592 | EXPECT_EQ(0, Argc); |
593 | } |
594 | |
595 | TEST(ParseFixedCompilationDatabase, ReturnsNullWithoutDoubleDash) { |
596 | int Argc = 2; |
597 | const char *Argv[] = { "1" , "2" }; |
598 | std::string ErrorMsg; |
599 | std::unique_ptr<FixedCompilationDatabase> Database( |
600 | FixedCompilationDatabase::loadFromCommandLine(Argc, Argv, ErrorMsg)); |
601 | EXPECT_FALSE(Database); |
602 | EXPECT_TRUE(ErrorMsg.empty()); |
603 | EXPECT_EQ(2, Argc); |
604 | } |
605 | |
606 | TEST(ParseFixedCompilationDatabase, ReturnsArgumentsAfterDoubleDash) { |
607 | int Argc = 5; |
608 | const char *Argv[] = { |
609 | "1" , "2" , "--\0no-constant-folding" , "-DDEF3" , "-DDEF4" |
610 | }; |
611 | std::string ErrorMsg; |
612 | std::unique_ptr<FixedCompilationDatabase> Database( |
613 | FixedCompilationDatabase::loadFromCommandLine(Argc, Argv, ErrorMsg)); |
614 | ASSERT_TRUE((bool)Database); |
615 | ASSERT_TRUE(ErrorMsg.empty()); |
616 | std::vector<CompileCommand> Result = |
617 | Database->getCompileCommands(FilePath: "source" ); |
618 | ASSERT_EQ(1ul, Result.size()); |
619 | ASSERT_EQ("." , Result[0].Directory); |
620 | ASSERT_THAT(Result[0].CommandLine, ElementsAre(EndsWith("clang-tool" ), |
621 | "-DDEF3" , "-DDEF4" , "source" )); |
622 | EXPECT_EQ(2, Argc); |
623 | } |
624 | |
625 | TEST(ParseFixedCompilationDatabase, ReturnsEmptyCommandLine) { |
626 | int Argc = 3; |
627 | const char *Argv[] = { "1" , "2" , "--\0no-constant-folding" }; |
628 | std::string ErrorMsg; |
629 | std::unique_ptr<FixedCompilationDatabase> Database = |
630 | FixedCompilationDatabase::loadFromCommandLine(Argc, Argv, ErrorMsg); |
631 | ASSERT_TRUE((bool)Database); |
632 | ASSERT_TRUE(ErrorMsg.empty()); |
633 | std::vector<CompileCommand> Result = |
634 | Database->getCompileCommands(FilePath: "source" ); |
635 | ASSERT_EQ(1ul, Result.size()); |
636 | ASSERT_EQ("." , Result[0].Directory); |
637 | ASSERT_THAT(Result[0].CommandLine, |
638 | ElementsAre(EndsWith("clang-tool" ), "source" )); |
639 | EXPECT_EQ(2, Argc); |
640 | } |
641 | |
642 | TEST(ParseFixedCompilationDatabase, HandlesPositionalArgs) { |
643 | const char *Argv[] = {"1" , "2" , "--" , "-c" , "somefile.cpp" , "-DDEF3" }; |
644 | int Argc = std::size(Argv); |
645 | std::string ErrorMsg; |
646 | std::unique_ptr<FixedCompilationDatabase> Database = |
647 | FixedCompilationDatabase::loadFromCommandLine(Argc, Argv, ErrorMsg); |
648 | ASSERT_TRUE((bool)Database); |
649 | ASSERT_TRUE(ErrorMsg.empty()); |
650 | std::vector<CompileCommand> Result = Database->getCompileCommands(FilePath: "source" ); |
651 | ASSERT_EQ(1ul, Result.size()); |
652 | ASSERT_EQ("." , Result[0].Directory); |
653 | ASSERT_THAT(Result[0].CommandLine, |
654 | ElementsAre(EndsWith("clang-tool" ), "-c" , "-DDEF3" , "source" )); |
655 | EXPECT_EQ(2, Argc); |
656 | } |
657 | |
658 | TEST(ParseFixedCompilationDatabase, HandlesPositionalArgsHeader) { |
659 | const char *Argv[] = {"1" , "2" , "--" , "-xc++-header" , |
660 | "-c" , "somefile.h" , "-DDEF3" }; |
661 | int Argc = std::size(Argv); |
662 | std::string ErrorMsg; |
663 | std::unique_ptr<FixedCompilationDatabase> Database = |
664 | FixedCompilationDatabase::loadFromCommandLine(Argc, Argv, ErrorMsg); |
665 | ASSERT_TRUE((bool)Database); |
666 | ASSERT_TRUE(ErrorMsg.empty()); |
667 | std::vector<CompileCommand> Result = |
668 | Database->getCompileCommands(FilePath: "source" ); |
669 | ASSERT_EQ(1ul, Result.size()); |
670 | ASSERT_EQ("." , Result[0].Directory); |
671 | ASSERT_THAT(Result[0].CommandLine, |
672 | ElementsAre(EndsWith("clang-tool" ), "-xc++-header" , "-c" , |
673 | "-DDEF3" , "source" )); |
674 | EXPECT_EQ(2, Argc); |
675 | } |
676 | |
677 | TEST(ParseFixedCompilationDatabase, HandlesPositionalArgsSyntaxOnly) { |
678 | // Adjust the given command line arguments to ensure that any positional |
679 | // arguments in them are stripped. |
680 | const char *Argv[] = {"--" , "somefile.cpp" , "-fsyntax-only" , "-DDEF3" }; |
681 | int Argc = std::size(Argv); |
682 | std::string ErrorMessage; |
683 | std::unique_ptr<CompilationDatabase> Database = |
684 | FixedCompilationDatabase::loadFromCommandLine(Argc, Argv, ErrorMsg&: ErrorMessage); |
685 | ASSERT_TRUE((bool)Database); |
686 | ASSERT_TRUE(ErrorMessage.empty()); |
687 | std::vector<CompileCommand> Result = Database->getCompileCommands(FilePath: "source" ); |
688 | ASSERT_EQ(1ul, Result.size()); |
689 | ASSERT_EQ("." , Result[0].Directory); |
690 | ASSERT_THAT( |
691 | Result[0].CommandLine, |
692 | ElementsAre(EndsWith("clang-tool" ), "-fsyntax-only" , "-DDEF3" , "source" )); |
693 | } |
694 | |
695 | TEST(ParseFixedCompilationDatabase, HandlesArgv0) { |
696 | const char *Argv[] = {"1" , "2" , "--" , "mytool" , "somefile.cpp" }; |
697 | int Argc = std::size(Argv); |
698 | std::string ErrorMsg; |
699 | std::unique_ptr<FixedCompilationDatabase> Database = |
700 | FixedCompilationDatabase::loadFromCommandLine(Argc, Argv, ErrorMsg); |
701 | ASSERT_TRUE((bool)Database); |
702 | ASSERT_TRUE(ErrorMsg.empty()); |
703 | std::vector<CompileCommand> Result = |
704 | Database->getCompileCommands(FilePath: "source" ); |
705 | ASSERT_EQ(1ul, Result.size()); |
706 | ASSERT_EQ("." , Result[0].Directory); |
707 | std::vector<std::string> Expected; |
708 | ASSERT_THAT(Result[0].CommandLine, |
709 | ElementsAre(EndsWith("clang-tool" ), "source" )); |
710 | EXPECT_EQ(2, Argc); |
711 | } |
712 | |
713 | struct MemCDB : public CompilationDatabase { |
714 | using EntryMap = llvm::StringMap<SmallVector<CompileCommand, 1>>; |
715 | EntryMap Entries; |
716 | MemCDB(const EntryMap &E) : Entries(E) {} |
717 | |
718 | std::vector<CompileCommand> getCompileCommands(StringRef F) const override { |
719 | auto Ret = Entries.lookup(Key: F); |
720 | return {Ret.begin(), Ret.end()}; |
721 | } |
722 | |
723 | std::vector<std::string> getAllFiles() const override { |
724 | std::vector<std::string> Result; |
725 | for (const auto &Entry : Entries) |
726 | Result.push_back(x: std::string(Entry.first())); |
727 | return Result; |
728 | } |
729 | }; |
730 | |
731 | class MemDBTest : public ::testing::Test { |
732 | protected: |
733 | // Adds an entry to the underlying compilation database. |
734 | // A flag is injected: -D <File>, so the command used can be identified. |
735 | void add(StringRef File, StringRef Clang, StringRef Flags) { |
736 | SmallVector<StringRef, 8> Argv = {Clang, File, "-D" , File}; |
737 | llvm::SplitString(Source: Flags, OutFragments&: Argv); |
738 | |
739 | // Trim double quotation from the argumnets if any. |
740 | for (auto *It = Argv.begin(); It != Argv.end(); ++It) |
741 | *It = It->trim(Chars: "\"" ); |
742 | |
743 | SmallString<32> Dir; |
744 | llvm::sys::path::system_temp_directory(erasedOnReboot: false, result&: Dir); |
745 | |
746 | Entries[path(File)].push_back( |
747 | Elt: {Dir, path(File), {Argv.begin(), Argv.end()}, "foo.o" }); |
748 | } |
749 | void add(StringRef File, StringRef Flags = "" ) { add(File, Clang: "clang" , Flags); } |
750 | |
751 | // Turn a unix path fragment (foo/bar.h) into a native path (C:\tmp\foo\bar.h) |
752 | std::string path(llvm::SmallString<32> File) { |
753 | llvm::SmallString<32> Dir; |
754 | llvm::sys::path::system_temp_directory(erasedOnReboot: false, result&: Dir); |
755 | llvm::sys::path::native(path&: File); |
756 | llvm::SmallString<64> Result; |
757 | llvm::sys::path::append(path&: Result, a: Dir, b: File); |
758 | return std::string(Result.str()); |
759 | } |
760 | |
761 | MemCDB::EntryMap Entries; |
762 | }; |
763 | |
764 | class InterpolateTest : public MemDBTest { |
765 | protected: |
766 | // Look up the command from a relative path, and return it in string form. |
767 | // The input file is not included in the returned command. |
768 | std::string getCommand(llvm::StringRef F, bool MakeNative = true) { |
769 | auto Results = |
770 | inferMissingCompileCommands(std::make_unique<MemCDB>(args&: Entries)) |
771 | ->getCompileCommands(FilePath: MakeNative ? path(File: F) : F); |
772 | if (Results.empty()) |
773 | return "none" ; |
774 | // drop the input file argument, so tests don't have to deal with path(). |
775 | EXPECT_EQ(Results[0].CommandLine.back(), MakeNative ? path(F) : F) |
776 | << "Last arg should be the file" ; |
777 | Results[0].CommandLine.pop_back(); |
778 | EXPECT_EQ(Results[0].CommandLine.back(), "--" ) |
779 | << "Second-last arg should be --" ; |
780 | Results[0].CommandLine.pop_back(); |
781 | return llvm::join(R&: Results[0].CommandLine, Separator: " " ); |
782 | } |
783 | |
784 | // Parse the file whose command was used out of the Heuristic string. |
785 | std::string getProxy(llvm::StringRef F) { |
786 | auto Results = |
787 | inferMissingCompileCommands(std::make_unique<MemCDB>(args&: Entries)) |
788 | ->getCompileCommands(FilePath: path(File: F)); |
789 | if (Results.empty()) |
790 | return "none" ; |
791 | StringRef Proxy = Results.front().Heuristic; |
792 | if (!Proxy.consume_front(Prefix: "inferred from " )) |
793 | return "" ; |
794 | // We have a proxy file, convert back to a unix relative path. |
795 | // This is a bit messy, but we do need to test these strings somehow... |
796 | llvm::SmallString<32> TempDir; |
797 | llvm::sys::path::system_temp_directory(erasedOnReboot: false, result&: TempDir); |
798 | Proxy.consume_front(Prefix: TempDir); |
799 | Proxy.consume_front(Prefix: llvm::sys::path::get_separator()); |
800 | llvm::SmallString<32> Result = Proxy; |
801 | llvm::sys::path::native(path&: Result, style: llvm::sys::path::Style::posix); |
802 | return std::string(Result.str()); |
803 | } |
804 | }; |
805 | |
806 | TEST_F(InterpolateTest, Nearby) { |
807 | add(File: "dir/foo.cpp" ); |
808 | add(File: "dir/bar.cpp" ); |
809 | add(File: "an/other/foo.cpp" ); |
810 | |
811 | // great: dir and name both match (prefix or full, case insensitive) |
812 | EXPECT_EQ(getProxy("dir/f.cpp" ), "dir/foo.cpp" ); |
813 | EXPECT_EQ(getProxy("dir/FOO.cpp" ), "dir/foo.cpp" ); |
814 | // no name match. prefer matching dir, break ties by alpha |
815 | EXPECT_EQ(getProxy("dir/a.cpp" ), "dir/bar.cpp" ); |
816 | // an exact name match beats one segment of directory match |
817 | EXPECT_EQ(getProxy("some/other/bar.h" ), "dir/bar.cpp" ); |
818 | // two segments of directory match beat a prefix name match |
819 | EXPECT_EQ(getProxy("an/other/b.cpp" ), "an/other/foo.cpp" ); |
820 | // if nothing matches at all, we still get the closest alpha match |
821 | EXPECT_EQ(getProxy("below/some/obscure/path.cpp" ), "an/other/foo.cpp" ); |
822 | } |
823 | |
824 | TEST_F(InterpolateTest, Language) { |
825 | add(File: "dir/foo.cpp" , Flags: "-std=c++17" ); |
826 | add(File: "dir/bar.c" , Flags: "" ); |
827 | add(File: "dir/baz.cee" , Flags: "-x c" ); |
828 | add(File: "dir/aux.cpp" , Flags: "-std=c++17 -x objective-c++" ); |
829 | |
830 | // .h is ambiguous, so we add explicit language flags |
831 | EXPECT_EQ(getCommand("foo.h" ), |
832 | "clang -D dir/foo.cpp -x c++-header -std=c++17" ); |
833 | // Same thing if we have no extension. (again, we treat as header). |
834 | EXPECT_EQ(getCommand("foo" ), "clang -D dir/foo.cpp -x c++-header -std=c++17" ); |
835 | // and invalid extensions. |
836 | EXPECT_EQ(getCommand("foo.cce" ), |
837 | "clang -D dir/foo.cpp -x c++-header -std=c++17" ); |
838 | // and don't add -x if the inferred language is correct. |
839 | EXPECT_EQ(getCommand("foo.hpp" ), "clang -D dir/foo.cpp -std=c++17" ); |
840 | // respect -x if it's already there. |
841 | EXPECT_EQ(getCommand("baz.h" ), "clang -D dir/baz.cee -x c-header" ); |
842 | // prefer a worse match with the right extension. |
843 | EXPECT_EQ(getCommand("foo.c" ), "clang -D dir/bar.c" ); |
844 | Entries.erase(Key: path(File: StringRef("dir/bar.c" ))); |
845 | // Now we transfer across languages, so drop -std too. |
846 | EXPECT_EQ(getCommand("foo.c" ), "clang -D dir/foo.cpp" ); |
847 | // Prefer -x over -std when overriding language. |
848 | EXPECT_EQ(getCommand("aux.h" ), |
849 | "clang -D dir/aux.cpp -x objective-c++-header -std=c++17" ); |
850 | } |
851 | |
852 | TEST_F(InterpolateTest, Strip) { |
853 | add(File: "dir/foo.cpp" , Flags: "-o foo.o -Wall" ); |
854 | // the -o option and the input file are removed, but -Wall is preserved. |
855 | EXPECT_EQ(getCommand("dir/bar.cpp" ), "clang -D dir/foo.cpp -Wall" ); |
856 | } |
857 | |
858 | TEST_F(InterpolateTest, StripDoubleDash) { |
859 | add(File: "dir/foo.cpp" , Flags: "-o foo.o -std=c++14 -Wall -- dir/foo.cpp" ); |
860 | // input file and output option are removed |
861 | // -Wall flag isn't |
862 | // -std option gets re-added as the last argument before the input file |
863 | // -- is removed as it's not necessary - the new input file doesn't start with |
864 | // a dash |
865 | EXPECT_EQ(getCommand("dir/bar.cpp" ), "clang -D dir/foo.cpp -Wall -std=c++14" ); |
866 | } |
867 | |
868 | TEST_F(InterpolateTest, Case) { |
869 | add(File: "FOO/BAR/BAZ/SHOUT.cc" ); |
870 | add(File: "foo/bar/baz/quiet.cc" ); |
871 | // Case mismatches are completely ignored, so we choose the name match. |
872 | EXPECT_EQ(getProxy("foo/bar/baz/shout.C" ), "FOO/BAR/BAZ/SHOUT.cc" ); |
873 | } |
874 | |
875 | TEST_F(InterpolateTest, LanguagePreference) { |
876 | add(File: "foo/bar/baz/exact.C" ); |
877 | add(File: "foo/bar/baz/exact.c" ); |
878 | add(File: "other/random/path.cpp" ); |
879 | // Proxies for ".H" files are ".C" files, and not ".c files". |
880 | EXPECT_EQ(getProxy("foo/bar/baz/exact.H" ), "foo/bar/baz/exact.C" ); |
881 | } |
882 | |
883 | TEST_F(InterpolateTest, Aliasing) { |
884 | add(File: "foo.cpp" , Flags: "-faligned-new" ); |
885 | |
886 | // The interpolated command should keep the given flag as written, even though |
887 | // the flag is internally represented as an alias. |
888 | EXPECT_EQ(getCommand("foo.hpp" ), "clang -D foo.cpp -faligned-new" ); |
889 | } |
890 | |
891 | TEST_F(InterpolateTest, ClangCL) { |
892 | add(File: "foo.cpp" , Clang: "clang-cl" , Flags: "/W4" ); |
893 | |
894 | // Language flags should be added with CL syntax. |
895 | EXPECT_EQ(getCommand("foo.h" , false), "clang-cl -D foo.cpp /W4 /TP" ); |
896 | } |
897 | |
898 | TEST_F(InterpolateTest, DriverModes) { |
899 | add(File: "foo.cpp" , Clang: "clang-cl" , Flags: "--driver-mode=gcc" ); |
900 | add(File: "bar.cpp" , Clang: "clang" , Flags: "--driver-mode=cl" ); |
901 | |
902 | // --driver-mode overrides should be respected. |
903 | EXPECT_EQ(getCommand("foo.h" ), |
904 | "clang-cl -D foo.cpp --driver-mode=gcc -x c++-header" ); |
905 | EXPECT_EQ(getCommand("bar.h" , false), |
906 | "clang -D bar.cpp --driver-mode=cl /TP" ); |
907 | } |
908 | |
909 | TEST(TransferCompileCommandTest, Smoke) { |
910 | CompileCommand Cmd; |
911 | Cmd.Filename = "foo.cc" ; |
912 | Cmd.CommandLine = {"clang" , "-Wall" , "foo.cc" }; |
913 | Cmd.Directory = "dir" ; |
914 | CompileCommand Transferred = transferCompileCommand(std::move(Cmd), Filename: "foo.h" ); |
915 | EXPECT_EQ(Transferred.Filename, "foo.h" ); |
916 | EXPECT_THAT(Transferred.CommandLine, |
917 | ElementsAre("clang" , "-Wall" , "-x" , "c++-header" , "--" , "foo.h" )); |
918 | EXPECT_EQ(Transferred.Directory, "dir" ); |
919 | } |
920 | |
921 | TEST(CompileCommandTest, EqualityOperator) { |
922 | CompileCommand CCRef("/foo/bar" , "hello.c" , {"a" , "b" }, "hello.o" ); |
923 | CompileCommand CCTest = CCRef; |
924 | |
925 | EXPECT_TRUE(CCRef == CCTest); |
926 | EXPECT_FALSE(CCRef != CCTest); |
927 | |
928 | CCTest = CCRef; |
929 | CCTest.Directory = "/foo/baz" ; |
930 | EXPECT_FALSE(CCRef == CCTest); |
931 | EXPECT_TRUE(CCRef != CCTest); |
932 | |
933 | CCTest = CCRef; |
934 | CCTest.Filename = "bonjour.c" ; |
935 | EXPECT_FALSE(CCRef == CCTest); |
936 | EXPECT_TRUE(CCRef != CCTest); |
937 | |
938 | CCTest = CCRef; |
939 | CCTest.CommandLine.push_back(x: "c" ); |
940 | EXPECT_FALSE(CCRef == CCTest); |
941 | EXPECT_TRUE(CCRef != CCTest); |
942 | |
943 | CCTest = CCRef; |
944 | CCTest.Output = "bonjour.o" ; |
945 | EXPECT_FALSE(CCRef == CCTest); |
946 | EXPECT_TRUE(CCRef != CCTest); |
947 | } |
948 | |
949 | class TargetAndModeTest : public MemDBTest { |
950 | public: |
951 | TargetAndModeTest() { llvm::InitializeAllTargetInfos(); } |
952 | |
953 | protected: |
954 | // Look up the command from a relative path, and return it in string form. |
955 | std::string getCommand(llvm::StringRef F) { |
956 | auto Results = inferTargetAndDriverMode(Base: std::make_unique<MemCDB>(args&: Entries)) |
957 | ->getCompileCommands(FilePath: path(File: F)); |
958 | if (Results.empty()) |
959 | return "none" ; |
960 | return llvm::join(R&: Results[0].CommandLine, Separator: " " ); |
961 | } |
962 | }; |
963 | |
964 | TEST_F(TargetAndModeTest, TargetAndMode) { |
965 | add(File: "foo.cpp" , Clang: "clang-cl" , Flags: "" ); |
966 | add(File: "bar.cpp" , Clang: "clang++" , Flags: "" ); |
967 | |
968 | EXPECT_EQ(getCommand("foo.cpp" ), |
969 | "clang-cl --driver-mode=cl foo.cpp -D foo.cpp" ); |
970 | EXPECT_EQ(getCommand("bar.cpp" ), |
971 | "clang++ --driver-mode=g++ bar.cpp -D bar.cpp" ); |
972 | } |
973 | |
974 | class ExpandResponseFilesTest : public MemDBTest { |
975 | public: |
976 | ExpandResponseFilesTest() : FS(new llvm::vfs::InMemoryFileSystem) {} |
977 | |
978 | protected: |
979 | void addFile(StringRef File, StringRef Content) { |
980 | ASSERT_TRUE( |
981 | FS->addFile(File, 0, llvm::MemoryBuffer::getMemBufferCopy(Content))); |
982 | } |
983 | |
984 | std::string getCommand(llvm::StringRef F) { |
985 | auto Results = expandResponseFiles(Base: std::make_unique<MemCDB>(args&: Entries), FS) |
986 | ->getCompileCommands(FilePath: path(File: F)); |
987 | if (Results.empty()) |
988 | return "none" ; |
989 | return llvm::join(R&: Results[0].CommandLine, Separator: " " ); |
990 | } |
991 | |
992 | llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> FS; |
993 | }; |
994 | |
995 | TEST_F(ExpandResponseFilesTest, ExpandResponseFiles) { |
996 | addFile(File: path(File: StringRef("rsp1.rsp" )), Content: "-Dflag" ); |
997 | |
998 | add(File: "foo.cpp" , Clang: "clang" , Flags: "@rsp1.rsp" ); |
999 | add(File: "bar.cpp" , Clang: "clang" , Flags: "-Dflag" ); |
1000 | EXPECT_EQ(getCommand("foo.cpp" ), "clang foo.cpp -D foo.cpp -Dflag" ); |
1001 | EXPECT_EQ(getCommand("bar.cpp" ), "clang bar.cpp -D bar.cpp -Dflag" ); |
1002 | } |
1003 | |
1004 | TEST_F(ExpandResponseFilesTest, ExpandResponseFilesEmptyArgument) { |
1005 | addFile(File: path(File: StringRef("rsp1.rsp" )), Content: "-Dflag" ); |
1006 | |
1007 | add(File: "foo.cpp" , Clang: "clang" , Flags: "@rsp1.rsp \"\"" ); |
1008 | EXPECT_EQ(getCommand("foo.cpp" ), "clang foo.cpp -D foo.cpp -Dflag " ); |
1009 | } |
1010 | |
1011 | } // end namespace tooling |
1012 | } // end namespace clang |
1013 | |