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 | {"sccache clang++ foo.c", "clang++ foo.c"}, |
406 | {"ccache gcc foo.c", "gcc foo.c"}, |
407 | {"ccache.exe gcc foo.c", "gcc foo.c"}, |
408 | {"ccache g++.exe foo.c", "g++.exe foo.c"}, |
409 | {"ccache distcc gcc foo.c", "gcc foo.c"}, |
410 | |
411 | {"distcc foo.c", "distcc foo.c"}, |
412 | {"distcc -I/foo/bar foo.c", "distcc -I/foo/bar foo.c"}, |
413 | }; |
414 | std::string ErrorMessage; |
415 | |
416 | for (const auto &Case : Cases) { |
417 | std::string DB = |
418 | R"([{"directory":"//net/dir", "file":"//net/dir/foo.c", "command":")"+ |
419 | Case.first + "\"}]"; |
420 | CompileCommand FoundCommand = |
421 | findCompileArgsInJsonDatabase(FileName: "//net/dir/foo.c", JSONDatabase: DB, ErrorMessage); |
422 | EXPECT_EQ(Case.second, llvm::join(FoundCommand.CommandLine, " ")) |
423 | << Case.first; |
424 | } |
425 | } |
426 | |
427 | static std::vector<std::string> unescapeJsonCommandLine(StringRef Command) { |
428 | std::string JsonDatabase = |
429 | ("[{\"directory\":\"//net/root\", \"file\":\"test\", \"command\": \""+ |
430 | Command + "\"}]").str(); |
431 | std::string ErrorMessage; |
432 | CompileCommand FoundCommand = findCompileArgsInJsonDatabase( |
433 | FileName: "//net/root/test", JSONDatabase: JsonDatabase, ErrorMessage); |
434 | EXPECT_TRUE(ErrorMessage.empty()) << ErrorMessage; |
435 | return FoundCommand.CommandLine; |
436 | } |
437 | |
438 | TEST(unescapeJsonCommandLine, ReturnsEmptyArrayOnEmptyString) { |
439 | std::vector<std::string> Result = unescapeJsonCommandLine(Command: ""); |
440 | EXPECT_TRUE(Result.empty()); |
441 | } |
442 | |
443 | TEST(unescapeJsonCommandLine, SplitsOnSpaces) { |
444 | std::vector<std::string> Result = unescapeJsonCommandLine(Command: "a b c"); |
445 | ASSERT_EQ(3ul, Result.size()); |
446 | EXPECT_EQ("a", Result[0]); |
447 | EXPECT_EQ("b", Result[1]); |
448 | EXPECT_EQ("c", Result[2]); |
449 | } |
450 | |
451 | TEST(unescapeJsonCommandLine, MungesMultipleSpaces) { |
452 | std::vector<std::string> Result = unescapeJsonCommandLine(Command: " a b "); |
453 | ASSERT_EQ(2ul, Result.size()); |
454 | EXPECT_EQ("a", Result[0]); |
455 | EXPECT_EQ("b", Result[1]); |
456 | } |
457 | |
458 | TEST(unescapeJsonCommandLine, UnescapesBackslashCharacters) { |
459 | std::vector<std::string> Backslash = unescapeJsonCommandLine(Command: "a\\\\\\\\"); |
460 | ASSERT_EQ(1ul, Backslash.size()); |
461 | EXPECT_EQ("a\\", Backslash[0]); |
462 | std::vector<std::string> Quote = unescapeJsonCommandLine(Command: "a\\\\\\\""); |
463 | ASSERT_EQ(1ul, Quote.size()); |
464 | EXPECT_EQ("a\"", Quote[0]); |
465 | } |
466 | |
467 | TEST(unescapeJsonCommandLine, DoesNotMungeSpacesBetweenQuotes) { |
468 | std::vector<std::string> Result = unescapeJsonCommandLine(Command: "\\\" a b \\\""); |
469 | ASSERT_EQ(1ul, Result.size()); |
470 | EXPECT_EQ(" a b ", Result[0]); |
471 | } |
472 | |
473 | TEST(unescapeJsonCommandLine, AllowsMultipleQuotedArguments) { |
474 | std::vector<std::string> Result = unescapeJsonCommandLine( |
475 | Command: " \\\" a \\\" \\\" b \\\" "); |
476 | ASSERT_EQ(2ul, Result.size()); |
477 | EXPECT_EQ(" a ", Result[0]); |
478 | EXPECT_EQ(" b ", Result[1]); |
479 | } |
480 | |
481 | TEST(unescapeJsonCommandLine, AllowsEmptyArgumentsInQuotes) { |
482 | std::vector<std::string> Result = unescapeJsonCommandLine( |
483 | Command: "\\\"\\\"\\\"\\\""); |
484 | ASSERT_EQ(1ul, Result.size()); |
485 | EXPECT_TRUE(Result[0].empty()) << Result[0]; |
486 | } |
487 | |
488 | TEST(unescapeJsonCommandLine, ParsesEscapedQuotesInQuotedStrings) { |
489 | std::vector<std::string> Result = unescapeJsonCommandLine( |
490 | Command: "\\\"\\\\\\\"\\\""); |
491 | ASSERT_EQ(1ul, Result.size()); |
492 | EXPECT_EQ("\"", Result[0]); |
493 | } |
494 | |
495 | TEST(unescapeJsonCommandLine, ParsesMultipleArgumentsWithEscapedCharacters) { |
496 | std::vector<std::string> Result = unescapeJsonCommandLine( |
497 | Command: " \\\\\\\" \\\"a \\\\\\\" b \\\" \\\"and\\\\\\\\c\\\" \\\\\\\""); |
498 | ASSERT_EQ(4ul, Result.size()); |
499 | EXPECT_EQ("\"", Result[0]); |
500 | EXPECT_EQ("a \" b ", Result[1]); |
501 | EXPECT_EQ("and\\c", Result[2]); |
502 | EXPECT_EQ("\"", Result[3]); |
503 | } |
504 | |
505 | TEST(unescapeJsonCommandLine, ParsesStringsWithoutSpacesIntoSingleArgument) { |
506 | std::vector<std::string> QuotedNoSpaces = unescapeJsonCommandLine( |
507 | Command: "\\\"a\\\"\\\"b\\\""); |
508 | ASSERT_EQ(1ul, QuotedNoSpaces.size()); |
509 | EXPECT_EQ("ab", QuotedNoSpaces[0]); |
510 | |
511 | std::vector<std::string> MixedNoSpaces = unescapeJsonCommandLine( |
512 | Command: "\\\"a\\\"bcd\\\"ef\\\"\\\"\\\"\\\"g\\\""); |
513 | ASSERT_EQ(1ul, MixedNoSpaces.size()); |
514 | EXPECT_EQ("abcdefg", MixedNoSpaces[0]); |
515 | } |
516 | |
517 | TEST(unescapeJsonCommandLine, ParsesQuotedStringWithoutClosingQuote) { |
518 | std::vector<std::string> Unclosed = unescapeJsonCommandLine(Command: "\\\"abc"); |
519 | ASSERT_EQ(1ul, Unclosed.size()); |
520 | EXPECT_EQ("abc", Unclosed[0]); |
521 | |
522 | std::vector<std::string> Empty = unescapeJsonCommandLine(Command: "\\\""); |
523 | ASSERT_EQ(1ul, Empty.size()); |
524 | EXPECT_EQ("", Empty[0]); |
525 | } |
526 | |
527 | TEST(unescapeJsonCommandLine, ParsesSingleQuotedString) { |
528 | std::vector<std::string> Args = unescapeJsonCommandLine(Command: "a'\\\\b \\\"c\\\"'"); |
529 | ASSERT_EQ(1ul, Args.size()); |
530 | EXPECT_EQ("a\\b \"c\"", Args[0]); |
531 | } |
532 | |
533 | TEST(FixedCompilationDatabase, ReturnsFixedCommandLine) { |
534 | FixedCompilationDatabase Database(".", /*CommandLine*/ { "one", "two"}); |
535 | StringRef FileName("source"); |
536 | std::vector<CompileCommand> Result = |
537 | Database.getCompileCommands(FilePath: FileName); |
538 | ASSERT_EQ(1ul, Result.size()); |
539 | EXPECT_EQ(".", Result[0].Directory); |
540 | EXPECT_EQ(FileName, Result[0].Filename); |
541 | EXPECT_THAT(Result[0].CommandLine, |
542 | ElementsAre(EndsWith("clang-tool"), "one", "two", "source")); |
543 | } |
544 | |
545 | TEST(FixedCompilationDatabase, GetAllFiles) { |
546 | std::vector<std::string> CommandLine; |
547 | CommandLine.push_back(x: "one"); |
548 | CommandLine.push_back(x: "two"); |
549 | FixedCompilationDatabase Database(".", CommandLine); |
550 | |
551 | EXPECT_THAT(Database.getAllFiles(), IsEmpty()); |
552 | } |
553 | |
554 | TEST(FixedCompilationDatabase, GetAllCompileCommands) { |
555 | std::vector<std::string> CommandLine; |
556 | CommandLine.push_back(x: "one"); |
557 | CommandLine.push_back(x: "two"); |
558 | FixedCompilationDatabase Database(".", CommandLine); |
559 | |
560 | EXPECT_EQ(0ul, Database.getAllCompileCommands().size()); |
561 | } |
562 | |
563 | TEST(FixedCompilationDatabase, FromBuffer) { |
564 | const char *Data = R"( |
565 | |
566 | -DFOO=BAR |
567 | |
568 | --baz |
569 | |
570 | )"; |
571 | std::string ErrorMsg; |
572 | auto CDB = |
573 | FixedCompilationDatabase::loadFromBuffer(Directory: "/cdb/dir", Data, ErrorMsg); |
574 | |
575 | std::vector<CompileCommand> Result = CDB->getCompileCommands(FilePath: "/foo/bar.cc"); |
576 | ASSERT_EQ(1ul, Result.size()); |
577 | EXPECT_EQ("/cdb/dir", Result.front().Directory); |
578 | EXPECT_EQ("/foo/bar.cc", Result.front().Filename); |
579 | EXPECT_THAT( |
580 | Result.front().CommandLine, |
581 | ElementsAre(EndsWith("clang-tool"), "-DFOO=BAR", "--baz", "/foo/bar.cc")); |
582 | } |
583 | |
584 | TEST(ParseFixedCompilationDatabase, ReturnsNullOnEmptyArgumentList) { |
585 | int Argc = 0; |
586 | std::string ErrorMsg; |
587 | std::unique_ptr<FixedCompilationDatabase> Database = |
588 | FixedCompilationDatabase::loadFromCommandLine(Argc, Argv: nullptr, ErrorMsg); |
589 | EXPECT_FALSE(Database); |
590 | EXPECT_TRUE(ErrorMsg.empty()); |
591 | EXPECT_EQ(0, Argc); |
592 | } |
593 | |
594 | TEST(ParseFixedCompilationDatabase, ReturnsNullWithoutDoubleDash) { |
595 | int Argc = 2; |
596 | const char *Argv[] = { "1", "2"}; |
597 | std::string ErrorMsg; |
598 | std::unique_ptr<FixedCompilationDatabase> Database( |
599 | FixedCompilationDatabase::loadFromCommandLine(Argc, Argv, ErrorMsg)); |
600 | EXPECT_FALSE(Database); |
601 | EXPECT_TRUE(ErrorMsg.empty()); |
602 | EXPECT_EQ(2, Argc); |
603 | } |
604 | |
605 | TEST(ParseFixedCompilationDatabase, ReturnsArgumentsAfterDoubleDash) { |
606 | int Argc = 5; |
607 | const char *Argv[] = { |
608 | "1", "2", "--\0no-constant-folding", "-DDEF3", "-DDEF4" |
609 | }; |
610 | std::string ErrorMsg; |
611 | std::unique_ptr<FixedCompilationDatabase> Database( |
612 | FixedCompilationDatabase::loadFromCommandLine(Argc, Argv, ErrorMsg)); |
613 | ASSERT_TRUE((bool)Database); |
614 | ASSERT_TRUE(ErrorMsg.empty()); |
615 | std::vector<CompileCommand> Result = |
616 | Database->getCompileCommands(FilePath: "source"); |
617 | ASSERT_EQ(1ul, Result.size()); |
618 | ASSERT_EQ(".", Result[0].Directory); |
619 | ASSERT_THAT(Result[0].CommandLine, ElementsAre(EndsWith("clang-tool"), |
620 | "-DDEF3", "-DDEF4", "source")); |
621 | EXPECT_EQ(2, Argc); |
622 | } |
623 | |
624 | TEST(ParseFixedCompilationDatabase, ReturnsEmptyCommandLine) { |
625 | int Argc = 3; |
626 | const char *Argv[] = { "1", "2", "--\0no-constant-folding"}; |
627 | std::string ErrorMsg; |
628 | std::unique_ptr<FixedCompilationDatabase> Database = |
629 | FixedCompilationDatabase::loadFromCommandLine(Argc, Argv, ErrorMsg); |
630 | ASSERT_TRUE((bool)Database); |
631 | ASSERT_TRUE(ErrorMsg.empty()); |
632 | std::vector<CompileCommand> Result = |
633 | Database->getCompileCommands(FilePath: "source"); |
634 | ASSERT_EQ(1ul, Result.size()); |
635 | ASSERT_EQ(".", Result[0].Directory); |
636 | ASSERT_THAT(Result[0].CommandLine, |
637 | ElementsAre(EndsWith("clang-tool"), "source")); |
638 | EXPECT_EQ(2, Argc); |
639 | } |
640 | |
641 | TEST(ParseFixedCompilationDatabase, HandlesPositionalArgs) { |
642 | const char *Argv[] = {"1", "2", "--", "-c", "somefile.cpp", "-DDEF3"}; |
643 | int Argc = std::size(Argv); |
644 | std::string ErrorMsg; |
645 | std::unique_ptr<FixedCompilationDatabase> Database = |
646 | FixedCompilationDatabase::loadFromCommandLine(Argc, Argv, ErrorMsg); |
647 | ASSERT_TRUE((bool)Database); |
648 | ASSERT_TRUE(ErrorMsg.empty()); |
649 | std::vector<CompileCommand> Result = Database->getCompileCommands(FilePath: "source"); |
650 | ASSERT_EQ(1ul, Result.size()); |
651 | ASSERT_EQ(".", Result[0].Directory); |
652 | ASSERT_THAT(Result[0].CommandLine, |
653 | ElementsAre(EndsWith("clang-tool"), "-c", "-DDEF3", "source")); |
654 | EXPECT_EQ(2, Argc); |
655 | } |
656 | |
657 | TEST(ParseFixedCompilationDatabase, HandlesPositionalArgsHeader) { |
658 | const char *Argv[] = {"1", "2", "--", "-xc++-header", |
659 | "-c", "somefile.h", "-DDEF3"}; |
660 | int Argc = std::size(Argv); |
661 | std::string ErrorMsg; |
662 | std::unique_ptr<FixedCompilationDatabase> Database = |
663 | FixedCompilationDatabase::loadFromCommandLine(Argc, Argv, ErrorMsg); |
664 | ASSERT_TRUE((bool)Database); |
665 | ASSERT_TRUE(ErrorMsg.empty()); |
666 | std::vector<CompileCommand> Result = |
667 | Database->getCompileCommands(FilePath: "source"); |
668 | ASSERT_EQ(1ul, Result.size()); |
669 | ASSERT_EQ(".", Result[0].Directory); |
670 | ASSERT_THAT(Result[0].CommandLine, |
671 | ElementsAre(EndsWith("clang-tool"), "-xc++-header", "-c", |
672 | "-DDEF3", "source")); |
673 | EXPECT_EQ(2, Argc); |
674 | } |
675 | |
676 | TEST(ParseFixedCompilationDatabase, HandlesPositionalArgsSyntaxOnly) { |
677 | // Adjust the given command line arguments to ensure that any positional |
678 | // arguments in them are stripped. |
679 | const char *Argv[] = {"--", "somefile.cpp", "-fsyntax-only", "-DDEF3"}; |
680 | int Argc = std::size(Argv); |
681 | std::string ErrorMessage; |
682 | std::unique_ptr<CompilationDatabase> Database = |
683 | FixedCompilationDatabase::loadFromCommandLine(Argc, Argv, ErrorMsg&: ErrorMessage); |
684 | ASSERT_TRUE((bool)Database); |
685 | ASSERT_TRUE(ErrorMessage.empty()); |
686 | std::vector<CompileCommand> Result = Database->getCompileCommands(FilePath: "source"); |
687 | ASSERT_EQ(1ul, Result.size()); |
688 | ASSERT_EQ(".", Result[0].Directory); |
689 | ASSERT_THAT( |
690 | Result[0].CommandLine, |
691 | ElementsAre(EndsWith("clang-tool"), "-fsyntax-only", "-DDEF3", "source")); |
692 | } |
693 | |
694 | TEST(ParseFixedCompilationDatabase, HandlesArgv0) { |
695 | const char *Argv[] = {"1", "2", "--", "mytool", "somefile.cpp"}; |
696 | int Argc = std::size(Argv); |
697 | std::string ErrorMsg; |
698 | std::unique_ptr<FixedCompilationDatabase> Database = |
699 | FixedCompilationDatabase::loadFromCommandLine(Argc, Argv, ErrorMsg); |
700 | ASSERT_TRUE((bool)Database); |
701 | ASSERT_TRUE(ErrorMsg.empty()); |
702 | std::vector<CompileCommand> Result = |
703 | Database->getCompileCommands(FilePath: "source"); |
704 | ASSERT_EQ(1ul, Result.size()); |
705 | ASSERT_EQ(".", Result[0].Directory); |
706 | ASSERT_THAT(Result[0].CommandLine, |
707 | ElementsAre(EndsWith("clang-tool"), "source")); |
708 | EXPECT_EQ(2, Argc); |
709 | } |
710 | |
711 | struct MemCDB : public CompilationDatabase { |
712 | using EntryMap = llvm::StringMap<SmallVector<CompileCommand, 1>>; |
713 | EntryMap Entries; |
714 | MemCDB(const EntryMap &E) : Entries(E) {} |
715 | |
716 | std::vector<CompileCommand> getCompileCommands(StringRef F) const override { |
717 | auto Ret = Entries.lookup(Key: F); |
718 | return {Ret.begin(), Ret.end()}; |
719 | } |
720 | |
721 | std::vector<std::string> getAllFiles() const override { |
722 | std::vector<std::string> Result; |
723 | for (const auto &Entry : Entries) |
724 | Result.push_back(x: std::string(Entry.first())); |
725 | return Result; |
726 | } |
727 | }; |
728 | |
729 | class MemDBTest : public ::testing::Test { |
730 | protected: |
731 | // Adds an entry to the underlying compilation database. |
732 | // A flag is injected: -D <File>, so the command used can be identified. |
733 | void add(StringRef File, StringRef Clang, StringRef Flags) { |
734 | SmallVector<StringRef, 8> Argv = {Clang, File, "-D", File}; |
735 | llvm::SplitString(Source: Flags, OutFragments&: Argv); |
736 | |
737 | // Trim double quotation from the argumnets if any. |
738 | for (auto *It = Argv.begin(); It != Argv.end(); ++It) |
739 | *It = It->trim(Chars: "\""); |
740 | |
741 | SmallString<32> Dir; |
742 | llvm::sys::path::system_temp_directory(erasedOnReboot: false, result&: Dir); |
743 | |
744 | Entries[path(File)].push_back( |
745 | Elt: {Dir, path(File), {Argv.begin(), Argv.end()}, "foo.o"}); |
746 | } |
747 | void add(StringRef File, StringRef Flags = "") { add(File, Clang: "clang", Flags); } |
748 | |
749 | // Turn a unix path fragment (foo/bar.h) into a native path (C:\tmp\foo\bar.h) |
750 | std::string path(llvm::SmallString<32> File) { |
751 | llvm::SmallString<32> Dir; |
752 | llvm::sys::path::system_temp_directory(erasedOnReboot: false, result&: Dir); |
753 | llvm::sys::path::native(path&: File); |
754 | llvm::SmallString<64> Result; |
755 | llvm::sys::path::append(path&: Result, a: Dir, b: File); |
756 | return std::string(Result.str()); |
757 | } |
758 | |
759 | MemCDB::EntryMap Entries; |
760 | }; |
761 | |
762 | class InterpolateTest : public MemDBTest { |
763 | protected: |
764 | // Look up the command from a relative path, and return it in string form. |
765 | // The input file is not included in the returned command. |
766 | std::string getCommand(llvm::StringRef F, bool MakeNative = true) { |
767 | auto Results = |
768 | inferMissingCompileCommands(std::make_unique<MemCDB>(args&: Entries)) |
769 | ->getCompileCommands(FilePath: MakeNative ? path(File: F) : F); |
770 | if (Results.empty()) |
771 | return "none"; |
772 | // drop the input file argument, so tests don't have to deal with path(). |
773 | EXPECT_EQ(Results[0].CommandLine.back(), MakeNative ? path(F) : F) |
774 | << "Last arg should be the file"; |
775 | Results[0].CommandLine.pop_back(); |
776 | EXPECT_EQ(Results[0].CommandLine.back(), "--") |
777 | << "Second-last arg should be --"; |
778 | Results[0].CommandLine.pop_back(); |
779 | return llvm::join(R&: Results[0].CommandLine, Separator: " "); |
780 | } |
781 | |
782 | // Parse the file whose command was used out of the Heuristic string. |
783 | std::string getProxy(llvm::StringRef F) { |
784 | auto Results = |
785 | inferMissingCompileCommands(std::make_unique<MemCDB>(args&: Entries)) |
786 | ->getCompileCommands(FilePath: path(File: F)); |
787 | if (Results.empty()) |
788 | return "none"; |
789 | StringRef Proxy = Results.front().Heuristic; |
790 | if (!Proxy.consume_front(Prefix: "inferred from ")) |
791 | return ""; |
792 | // We have a proxy file, convert back to a unix relative path. |
793 | // This is a bit messy, but we do need to test these strings somehow... |
794 | llvm::SmallString<32> TempDir; |
795 | llvm::sys::path::system_temp_directory(erasedOnReboot: false, result&: TempDir); |
796 | Proxy.consume_front(Prefix: TempDir); |
797 | Proxy.consume_front(Prefix: llvm::sys::path::get_separator()); |
798 | llvm::SmallString<32> Result = Proxy; |
799 | llvm::sys::path::native(path&: Result, style: llvm::sys::path::Style::posix); |
800 | return std::string(Result.str()); |
801 | } |
802 | }; |
803 | |
804 | TEST_F(InterpolateTest, Nearby) { |
805 | add(File: "dir/foo.cpp"); |
806 | add(File: "dir/bar.cpp"); |
807 | add(File: "an/other/foo.cpp"); |
808 | |
809 | // great: dir and name both match (prefix or full, case insensitive) |
810 | EXPECT_EQ(getProxy("dir/f.cpp"), "dir/foo.cpp"); |
811 | EXPECT_EQ(getProxy("dir/FOO.cpp"), "dir/foo.cpp"); |
812 | // no name match. prefer matching dir, break ties by alpha |
813 | EXPECT_EQ(getProxy("dir/a.cpp"), "dir/bar.cpp"); |
814 | // an exact name match beats one segment of directory match |
815 | EXPECT_EQ(getProxy("some/other/bar.h"), "dir/bar.cpp"); |
816 | // two segments of directory match beat a prefix name match |
817 | EXPECT_EQ(getProxy("an/other/b.cpp"), "an/other/foo.cpp"); |
818 | // if nothing matches at all, we still get the closest alpha match |
819 | EXPECT_EQ(getProxy("below/some/obscure/path.cpp"), "an/other/foo.cpp"); |
820 | } |
821 | |
822 | TEST_F(InterpolateTest, Language) { |
823 | add(File: "dir/foo.cpp", Flags: "-std=c++17"); |
824 | add(File: "dir/bar.c", Flags: ""); |
825 | add(File: "dir/baz.cee", Flags: "-x c"); |
826 | add(File: "dir/aux.cpp", Flags: "-std=c++17 -x objective-c++"); |
827 | |
828 | // .h is ambiguous, so we add explicit language flags |
829 | EXPECT_EQ(getCommand("foo.h"), |
830 | "clang -D dir/foo.cpp -x c++-header -std=c++17"); |
831 | // Same thing if we have no extension. (again, we treat as header). |
832 | EXPECT_EQ(getCommand("foo"), "clang -D dir/foo.cpp -x c++-header -std=c++17"); |
833 | // and invalid extensions. |
834 | EXPECT_EQ(getCommand("foo.cce"), |
835 | "clang -D dir/foo.cpp -x c++-header -std=c++17"); |
836 | // and don't add -x if the inferred language is correct. |
837 | EXPECT_EQ(getCommand("foo.hpp"), "clang -D dir/foo.cpp -std=c++17"); |
838 | // respect -x if it's already there. |
839 | EXPECT_EQ(getCommand("baz.h"), "clang -D dir/baz.cee -x c-header"); |
840 | // prefer a worse match with the right extension. |
841 | EXPECT_EQ(getCommand("foo.c"), "clang -D dir/bar.c"); |
842 | Entries.erase(Key: path(File: StringRef("dir/bar.c"))); |
843 | // Now we transfer across languages, so drop -std too. |
844 | EXPECT_EQ(getCommand("foo.c"), "clang -D dir/foo.cpp"); |
845 | // Prefer -x over -std when overriding language. |
846 | EXPECT_EQ(getCommand("aux.h"), |
847 | "clang -D dir/aux.cpp -x objective-c++-header -std=c++17"); |
848 | } |
849 | |
850 | TEST_F(InterpolateTest, Strip) { |
851 | add(File: "dir/foo.cpp", Flags: "-o foo.o -Wall"); |
852 | // the -o option and the input file are removed, but -Wall is preserved. |
853 | EXPECT_EQ(getCommand("dir/bar.cpp"), "clang -D dir/foo.cpp -Wall"); |
854 | } |
855 | |
856 | TEST_F(InterpolateTest, StripDoubleDash) { |
857 | add(File: "dir/foo.cpp", Flags: "-o foo.o -std=c++14 -Wall -- dir/foo.cpp"); |
858 | // input file and output option are removed |
859 | // -Wall flag isn't |
860 | // -std option gets re-added as the last argument before the input file |
861 | // -- is removed as it's not necessary - the new input file doesn't start with |
862 | // a dash |
863 | EXPECT_EQ(getCommand("dir/bar.cpp"), "clang -D dir/foo.cpp -Wall -std=c++14"); |
864 | } |
865 | |
866 | TEST_F(InterpolateTest, Case) { |
867 | add(File: "FOO/BAR/BAZ/SHOUT.cc"); |
868 | add(File: "foo/bar/baz/quiet.cc"); |
869 | // Case mismatches are completely ignored, so we choose the name match. |
870 | EXPECT_EQ(getProxy("foo/bar/baz/shout.C"), "FOO/BAR/BAZ/SHOUT.cc"); |
871 | } |
872 | |
873 | TEST_F(InterpolateTest, LanguagePreference) { |
874 | add(File: "foo/bar/baz/exact.C"); |
875 | add(File: "foo/bar/baz/exact.c"); |
876 | add(File: "other/random/path.cpp"); |
877 | // Proxies for ".H" files are ".C" files, and not ".c files". |
878 | EXPECT_EQ(getProxy("foo/bar/baz/exact.H"), "foo/bar/baz/exact.C"); |
879 | } |
880 | |
881 | TEST_F(InterpolateTest, Aliasing) { |
882 | add(File: "foo.cpp", Flags: "-faligned-new"); |
883 | |
884 | // The interpolated command should keep the given flag as written, even though |
885 | // the flag is internally represented as an alias. |
886 | EXPECT_EQ(getCommand("foo.hpp"), "clang -D foo.cpp -faligned-new"); |
887 | } |
888 | |
889 | TEST_F(InterpolateTest, ClangCL) { |
890 | add(File: "foo.cpp", Clang: "clang-cl", Flags: "/W4"); |
891 | |
892 | // Language flags should be added with CL syntax. |
893 | EXPECT_EQ(getCommand("foo.h", false), "clang-cl -D foo.cpp /W4 /TP"); |
894 | } |
895 | |
896 | TEST_F(InterpolateTest, DriverModes) { |
897 | add(File: "foo.cpp", Clang: "clang-cl", Flags: "--driver-mode=gcc"); |
898 | add(File: "bar.cpp", Clang: "clang", Flags: "--driver-mode=cl"); |
899 | |
900 | // --driver-mode overrides should be respected. |
901 | EXPECT_EQ(getCommand("foo.h"), |
902 | "clang-cl -D foo.cpp --driver-mode=gcc -x c++-header"); |
903 | EXPECT_EQ(getCommand("bar.h", false), |
904 | "clang -D bar.cpp --driver-mode=cl /TP"); |
905 | } |
906 | |
907 | TEST(TransferCompileCommandTest, Smoke) { |
908 | CompileCommand Cmd; |
909 | Cmd.Filename = "foo.cc"; |
910 | Cmd.CommandLine = {"clang", "-Wall", "foo.cc"}; |
911 | Cmd.Directory = "dir"; |
912 | CompileCommand Transferred = transferCompileCommand(std::move(Cmd), Filename: "foo.h"); |
913 | EXPECT_EQ(Transferred.Filename, "foo.h"); |
914 | EXPECT_THAT(Transferred.CommandLine, |
915 | ElementsAre("clang", "-Wall", "-x", "c++-header", "--", "foo.h")); |
916 | EXPECT_EQ(Transferred.Directory, "dir"); |
917 | } |
918 | |
919 | TEST(CompileCommandTest, EqualityOperator) { |
920 | CompileCommand CCRef("/foo/bar", "hello.c", { "a", "b"}, "hello.o"); |
921 | CompileCommand CCTest = CCRef; |
922 | |
923 | EXPECT_TRUE(CCRef == CCTest); |
924 | EXPECT_FALSE(CCRef != CCTest); |
925 | |
926 | CCTest = CCRef; |
927 | CCTest.Directory = "/foo/baz"; |
928 | EXPECT_FALSE(CCRef == CCTest); |
929 | EXPECT_TRUE(CCRef != CCTest); |
930 | |
931 | CCTest = CCRef; |
932 | CCTest.Filename = "bonjour.c"; |
933 | EXPECT_FALSE(CCRef == CCTest); |
934 | EXPECT_TRUE(CCRef != CCTest); |
935 | |
936 | CCTest = CCRef; |
937 | CCTest.CommandLine.push_back(x: "c"); |
938 | EXPECT_FALSE(CCRef == CCTest); |
939 | EXPECT_TRUE(CCRef != CCTest); |
940 | |
941 | CCTest = CCRef; |
942 | CCTest.Output = "bonjour.o"; |
943 | EXPECT_FALSE(CCRef == CCTest); |
944 | EXPECT_TRUE(CCRef != CCTest); |
945 | } |
946 | |
947 | class TargetAndModeTest : public MemDBTest { |
948 | public: |
949 | TargetAndModeTest() { llvm::InitializeAllTargetInfos(); } |
950 | |
951 | protected: |
952 | // Look up the command from a relative path, and return it in string form. |
953 | std::string getCommand(llvm::StringRef F) { |
954 | auto Results = inferTargetAndDriverMode(Base: std::make_unique<MemCDB>(args&: Entries)) |
955 | ->getCompileCommands(FilePath: path(File: F)); |
956 | if (Results.empty()) |
957 | return "none"; |
958 | return llvm::join(R&: Results[0].CommandLine, Separator: " "); |
959 | } |
960 | }; |
961 | |
962 | TEST_F(TargetAndModeTest, TargetAndMode) { |
963 | add(File: "foo.cpp", Clang: "clang-cl", Flags: ""); |
964 | add(File: "bar.cpp", Clang: "clang++", Flags: ""); |
965 | |
966 | EXPECT_EQ(getCommand("foo.cpp"), |
967 | "clang-cl --driver-mode=cl foo.cpp -D foo.cpp"); |
968 | EXPECT_EQ(getCommand("bar.cpp"), |
969 | "clang++ --driver-mode=g++ bar.cpp -D bar.cpp"); |
970 | } |
971 | |
972 | class ExpandResponseFilesTest : public MemDBTest { |
973 | public: |
974 | ExpandResponseFilesTest() : FS(new llvm::vfs::InMemoryFileSystem) {} |
975 | |
976 | protected: |
977 | void addFile(StringRef File, StringRef Content) { |
978 | ASSERT_TRUE( |
979 | FS->addFile(File, 0, llvm::MemoryBuffer::getMemBufferCopy(Content))); |
980 | } |
981 | |
982 | std::string getCommand(llvm::StringRef F) { |
983 | auto Results = expandResponseFiles(Base: std::make_unique<MemCDB>(args&: Entries), FS) |
984 | ->getCompileCommands(FilePath: path(File: F)); |
985 | if (Results.empty()) |
986 | return "none"; |
987 | return llvm::join(R&: Results[0].CommandLine, Separator: " "); |
988 | } |
989 | |
990 | llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> FS; |
991 | }; |
992 | |
993 | TEST_F(ExpandResponseFilesTest, ExpandResponseFiles) { |
994 | addFile(File: path(File: StringRef("rsp1.rsp")), Content: "-Dflag"); |
995 | |
996 | add(File: "foo.cpp", Clang: "clang", Flags: "@rsp1.rsp"); |
997 | add(File: "bar.cpp", Clang: "clang", Flags: "-Dflag"); |
998 | EXPECT_EQ(getCommand("foo.cpp"), "clang foo.cpp -D foo.cpp -Dflag"); |
999 | EXPECT_EQ(getCommand("bar.cpp"), "clang bar.cpp -D bar.cpp -Dflag"); |
1000 | } |
1001 | |
1002 | TEST_F(ExpandResponseFilesTest, ExpandResponseFilesEmptyArgument) { |
1003 | addFile(File: path(File: StringRef("rsp1.rsp")), Content: "-Dflag"); |
1004 | |
1005 | add(File: "foo.cpp", Clang: "clang", Flags: "@rsp1.rsp \"\""); |
1006 | EXPECT_EQ(getCommand("foo.cpp"), "clang foo.cpp -D foo.cpp -Dflag "); |
1007 | } |
1008 | |
1009 | } // end namespace tooling |
1010 | } // end namespace clang |
1011 |
Definitions
- expectFailure
- getAllFiles
- getAllCompileCommands
- findCompileArgsInJsonDatabase
- FakeComparator
- ~FakeComparator
- equivalent
- FileMatchTrieTest
- FileMatchTrieTest
- find
- unescapeJsonCommandLine
- MemCDB
- MemCDB
- getCompileCommands
- getAllFiles
- MemDBTest
- add
- add
- path
- InterpolateTest
- getCommand
- getProxy
- TargetAndModeTest
- TargetAndModeTest
- getCommand
- ExpandResponseFilesTest
- ExpandResponseFilesTest
- addFile
Update your C++ knowledge – Modern C++11/14/17 Training
Find out more