1 //===- unittest/Tooling/CompilationDatabaseTest.cpp -----------------------===//
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
7 //===----------------------------------------------------------------------===//
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"
22 using testing::ElementsAre
;
23 using testing::EndsWith
;
24 using testing::IsEmpty
;
25 using testing::UnorderedElementsAreArray
;
27 static void expectFailure(StringRef JSONDatabase
, StringRef Explanation
) {
28 std::string ErrorMessage
;
30 JSONCompilationDatabase::loadFromBuffer(JSONDatabase
, ErrorMessage
,
31 JSONCommandLineSyntax::Gnu
))
32 << "Expected an error because of: " << Explanation
.str();
35 TEST(JSONCompilationDatabase
, ErrsOnInvalidFormat
) {
36 expectFailure("", "Empty database");
37 expectFailure("{", "Invalid JSON");
38 expectFailure("[[]]", "Array instead of object");
39 expectFailure("[{\"a\":[]}]", "Array instead of value");
40 expectFailure("[{\"a\":\"b\"}]", "Unknown key");
41 expectFailure("[{[]:\"\"}]", "Incorrectly typed entry");
42 expectFailure("[{}]", "Empty entry");
43 expectFailure("[{\"directory\":\"\",\"command\":\"\"}]", "Missing file");
44 expectFailure("[{\"directory\":\"\",\"file\":\"\"}]", "Missing command or arguments");
45 expectFailure("[{\"command\":\"\",\"file\":\"\"}]", "Missing directory");
46 expectFailure("[{\"directory\":\"\",\"arguments\":[]}]", "Missing file");
47 expectFailure("[{\"arguments\":\"\",\"file\":\"\"}]", "Missing directory");
48 expectFailure("[{\"directory\":\"\",\"arguments\":\"\",\"file\":\"\"}]", "Arguments not array");
49 expectFailure("[{\"directory\":\"\",\"command\":[],\"file\":\"\"}]", "Command not string");
50 expectFailure("[{\"directory\":\"\",\"arguments\":[[]],\"file\":\"\"}]",
51 "Arguments contain non-string");
52 expectFailure("[{\"output\":[]}]", "Expected strings as value.");
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(JSONDatabase
, ErrorMessage
,
62 ADD_FAILURE() << ErrorMessage
;
63 return std::vector
<std::string
>();
65 auto Result
= Database
->getAllFiles();
66 std::sort(Result
.begin(), Result
.end());
70 static std::vector
<CompileCommand
>
71 getAllCompileCommands(JSONCommandLineSyntax Syntax
, StringRef JSONDatabase
,
72 std::string
&ErrorMessage
) {
73 std::unique_ptr
<CompilationDatabase
> Database(
74 JSONCompilationDatabase::loadFromBuffer(JSONDatabase
, ErrorMessage
,
77 ADD_FAILURE() << ErrorMessage
;
78 return std::vector
<CompileCommand
>();
80 return Database
->getAllCompileCommands();
83 TEST(JSONCompilationDatabase
, GetAllFiles
) {
84 std::string ErrorMessage
;
85 EXPECT_THAT(getAllFiles("[]", ErrorMessage
, JSONCommandLineSyntax::Gnu
),
89 std::vector
<std::string
> expected_files
;
90 SmallString
<16> PathStorage
;
91 llvm::sys::path::native("//net/dir/file1", PathStorage
);
92 expected_files
.push_back(std::string(PathStorage
.str()));
93 llvm::sys::path::native("//net/dir/file2", PathStorage
);
94 expected_files
.push_back(std::string(PathStorage
.str()));
95 llvm::sys::path::native("//net/dir/file3", PathStorage
);
96 expected_files
.push_back(std::string(PathStorage
.str()));
97 llvm::sys::path::native("//net/file1", PathStorage
);
98 expected_files
.push_back(std::string(PathStorage
.str()));
99 EXPECT_THAT(getAllFiles(R
"json(
102 "directory
": "//net/dir",
103 "command": "command",
107 "directory": "//net/dir",
108 "command": "command",
112 "directory": "//net/dir",
113 "command": "command",
117 "directory": "//net/dir",
118 "command": "command",
119 "file": "//net/dir/foo/../file3"
122 ErrorMessage, JSONCommandLineSyntax::Gnu),
123 UnorderedElementsAreArray(expected_files))
127 TEST(JSONCompilationDatabase, GetAllCompileCommands) {
128 std::string ErrorMessage;
130 0u, getAllCompileCommands(JSONCommandLineSyntax::Gnu, "[]", ErrorMessage)
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("");
143 std::vector
<CompileCommand
> Commands
= getAllCompileCommands(
144 JSONCommandLineSyntax::Gnu
,
145 ("[{\"directory\":\"" + Directory1
+ "\"," + "\"command\":\"" + Command1
+
148 FileName1
+ "\", \"output\":\"" +
150 " {\"directory\":\"" +
151 Directory2
+ "\"," + "\"command\":\"" + Command2
+ "\","
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
;
168 // Check that order is preserved.
169 Commands
= getAllCompileCommands(
170 JSONCommandLineSyntax::Gnu
,
171 ("[{\"directory\":\"" + Directory2
+ "\"," + "\"command\":\"" + Command2
+
175 " {\"directory\":\"" +
176 Directory1
+ "\"," + "\"command\":\"" + Command1
+ "\","
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
;
192 static CompileCommand
findCompileArgsInJsonDatabase(StringRef FileName
,
193 std::string JSONDatabase
,
194 std::string
&ErrorMessage
) {
195 std::unique_ptr
<CompilationDatabase
> Database(
196 JSONCompilationDatabase::loadFromBuffer(JSONDatabase
, ErrorMessage
,
197 JSONCommandLineSyntax::Gnu
));
199 return CompileCommand();
200 // Overwrite the string to verify we're not reading from it later.
201 JSONDatabase
.assign(JSONDatabase
.size(), '*');
202 std::vector
<CompileCommand
> Commands
= Database
->getCompileCommands(FileName
);
203 EXPECT_LE(Commands
.size(), 1u);
204 if (Commands
.empty())
205 return CompileCommand();
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(
218 ("[{\"directory\":\"" + Directory
+ "\","
219 "\"arguments\":[\"" + Arguments
+ "\"],"
220 "\"command\":\"" + Command
+ "\","
221 "\"file\":\"" + FileName
+ "\"}]").str(),
223 EXPECT_EQ(Directory
, FoundCommand
.Directory
) << ErrorMessage
;
224 EXPECT_EQ(1u, FoundCommand
.CommandLine
.size()) << ErrorMessage
;
225 EXPECT_EQ(Arguments
, FoundCommand
.CommandLine
[0]) << ErrorMessage
;
228 struct FakeComparator
: public PathComparator
{
229 ~FakeComparator() override
{}
230 bool equivalent(StringRef FileA
, StringRef FileB
) const override
{
231 return FileA
.equals_insensitive(FileB
);
235 class FileMatchTrieTest
: public ::testing::Test
{
237 FileMatchTrieTest() : Trie(new FakeComparator()) {}
239 StringRef
find(StringRef Path
) {
240 llvm::raw_string_ostream
ES(Error
);
241 return Trie
.findEquivalent(Path
, ES
);
248 TEST_F(FileMatchTrieTest
, InsertingRelativePath
) {
249 Trie
.insert("//net/path/file.cc");
250 Trie
.insert("file.cc");
251 EXPECT_EQ("//net/path/file.cc", find("//net/path/file.cc"));
254 TEST_F(FileMatchTrieTest
, MatchingRelativePath
) {
255 EXPECT_EQ("", find("file.cc"));
258 TEST_F(FileMatchTrieTest
, ReturnsBestResults
) {
259 Trie
.insert("//net/d/c/b.cc");
260 Trie
.insert("//net/d/b/b.cc");
261 EXPECT_EQ("//net/d/b/b.cc", find("//net/d/b/b.cc"));
264 TEST_F(FileMatchTrieTest
, HandlesSymlinks
) {
265 Trie
.insert("//net/AA/file.cc");
266 EXPECT_EQ("//net/AA/file.cc", find("//net/aa/file.cc"));
269 TEST_F(FileMatchTrieTest
, ReportsSymlinkAmbiguity
) {
270 Trie
.insert("//net/Aa/file.cc");
271 Trie
.insert("//net/aA/file.cc");
272 EXPECT_TRUE(find("//net/aa/file.cc").empty());
273 EXPECT_EQ("Path is ambiguous", Error
);
276 TEST_F(FileMatchTrieTest
, LongerMatchingSuffixPreferred
) {
277 Trie
.insert("//net/src/Aa/file.cc");
278 Trie
.insert("//net/src/aA/file.cc");
279 Trie
.insert("//net/SRC/aa/file.cc");
280 EXPECT_EQ("//net/SRC/aa/file.cc", find("//net/src/aa/file.cc"));
283 TEST_F(FileMatchTrieTest
, EmptyTrie
) {
284 EXPECT_TRUE(find("//net/some/path").empty());
287 TEST_F(FileMatchTrieTest
, NoResult
) {
288 Trie
.insert("//net/somepath/otherfile.cc");
289 Trie
.insert("//net/otherpath/somefile.cc");
290 EXPECT_EQ("", find("//net/somepath/somefile.cc"));
293 TEST_F(FileMatchTrieTest
, RootElementDifferent
) {
294 Trie
.insert("//net/path/file.cc");
295 Trie
.insert("//net/otherpath/file.cc");
296 EXPECT_EQ("//net/path/file.cc", find("//net/path/file.cc"));
299 TEST_F(FileMatchTrieTest
, CannotResolveRelativePath
) {
300 EXPECT_EQ("", find("relative-path.cc"));
301 EXPECT_EQ("Cannot resolve relative paths", Error
);
304 TEST_F(FileMatchTrieTest
, SingleFile
) {
305 Trie
.insert("/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("/root/otherpath/OtherFile.cc");
310 EXPECT_EQ("", find("/root/rootfile.cc"));
313 TEST(findCompileArgsInJsonDatabase
, FindsNothingIfEmpty
) {
314 std::string ErrorMessage
;
315 CompileCommand NotFound
= findCompileArgsInJsonDatabase(
316 "a-file.cpp", "", ErrorMessage
);
317 EXPECT_TRUE(NotFound
.CommandLine
.empty()) << ErrorMessage
;
318 EXPECT_TRUE(NotFound
.Directory
.empty()) << ErrorMessage
;
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(
328 ("[{\"directory\":\"" + Directory
+ "\"," +
329 "\"command\":\"" + Command
+ "\","
330 "\"file\":\"" + FileName
+ "\"}]").str(),
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
;
340 CompileCommand NotFound
= findCompileArgsInJsonDatabase(
342 ("[{\"directory\":\"" + Directory
+ "\"," +
343 "\"command\":\"" + Command
+ "\","
344 "\"file\":\"" + FileName
+ "\"}]").str(),
346 EXPECT_TRUE(NotFound
.Directory
.empty()) << ErrorMessage
;
347 EXPECT_TRUE(NotFound
.CommandLine
.empty()) << ErrorMessage
;
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(
357 ("[{\"directory\":\"" + Directory
+ "\"," +
358 "\"command\":\"" + Command
+ "\","
359 "\"file\":\"" + FileName
+ "\"}]").str(),
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
;
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(
374 ("[{\"directory\":\"" + Directory
+ "\"," +
375 "\"command\":\"" + Command
+ "\","
376 "\"file\":\"" + FileName
+ "\"}]").str(),
378 EXPECT_EQ(Directory
, FoundCommand
.Directory
) << ErrorMessage
;
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
+= ",";
389 ("{\"directory\":\"" + Directory
+ Twine(I
) + "\"," +
390 "\"command\":\"" + Command
+ Twine(I
) + "\","
391 "\"file\":\"" + FileName
+ Twine(I
) + "\"}").str();
394 std::string ErrorMessage
;
395 CompileCommand FoundCommand
= findCompileArgsInJsonDatabase(
396 "//net/directory4/file4", 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
;
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"},
412 {"distcc foo.c", "distcc foo.c"},
413 {"distcc -I/foo/bar foo.c", "distcc -I/foo/bar foo.c"},
415 std::string ErrorMessage
;
417 for (const auto &Case
: Cases
) {
419 R
"([{"directory
":"//net/dir", "file":"//net/dir/foo.c", "command":")" +
421 CompileCommand FoundCommand
=
422 findCompileArgsInJsonDatabase("//net/dir/foo.c", DB
, ErrorMessage
);
423 EXPECT_EQ(Case
.second
, llvm::join(FoundCommand
.CommandLine
, " "))
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 "//net/root/test", JsonDatabase
, ErrorMessage
);
435 EXPECT_TRUE(ErrorMessage
.empty()) << ErrorMessage
;
436 return FoundCommand
.CommandLine
;
439 TEST(unescapeJsonCommandLine
, ReturnsEmptyArrayOnEmptyString
) {
440 std::vector
<std::string
> Result
= unescapeJsonCommandLine("");
441 EXPECT_TRUE(Result
.empty());
444 TEST(unescapeJsonCommandLine
, SplitsOnSpaces
) {
445 std::vector
<std::string
> Result
= unescapeJsonCommandLine("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]);
452 TEST(unescapeJsonCommandLine
, MungesMultipleSpaces
) {
453 std::vector
<std::string
> Result
= unescapeJsonCommandLine(" a b ");
454 ASSERT_EQ(2ul, Result
.size());
455 EXPECT_EQ("a", Result
[0]);
456 EXPECT_EQ("b", Result
[1]);
459 TEST(unescapeJsonCommandLine
, UnescapesBackslashCharacters
) {
460 std::vector
<std::string
> Backslash
= unescapeJsonCommandLine("a\\\\\\\\");
461 ASSERT_EQ(1ul, Backslash
.size());
462 EXPECT_EQ("a\\", Backslash
[0]);
463 std::vector
<std::string
> Quote
= unescapeJsonCommandLine("a\\\\\\\"");
464 ASSERT_EQ(1ul, Quote
.size());
465 EXPECT_EQ("a\"", Quote
[0]);
468 TEST(unescapeJsonCommandLine
, DoesNotMungeSpacesBetweenQuotes
) {
469 std::vector
<std::string
> Result
= unescapeJsonCommandLine("\\\" a b \\\"");
470 ASSERT_EQ(1ul, Result
.size());
471 EXPECT_EQ(" a b ", Result
[0]);
474 TEST(unescapeJsonCommandLine
, AllowsMultipleQuotedArguments
) {
475 std::vector
<std::string
> Result
= unescapeJsonCommandLine(
476 " \\\" a \\\" \\\" b \\\" ");
477 ASSERT_EQ(2ul, Result
.size());
478 EXPECT_EQ(" a ", Result
[0]);
479 EXPECT_EQ(" b ", Result
[1]);
482 TEST(unescapeJsonCommandLine
, AllowsEmptyArgumentsInQuotes
) {
483 std::vector
<std::string
> Result
= unescapeJsonCommandLine(
485 ASSERT_EQ(1ul, Result
.size());
486 EXPECT_TRUE(Result
[0].empty()) << Result
[0];
489 TEST(unescapeJsonCommandLine
, ParsesEscapedQuotesInQuotedStrings
) {
490 std::vector
<std::string
> Result
= unescapeJsonCommandLine(
492 ASSERT_EQ(1ul, Result
.size());
493 EXPECT_EQ("\"", Result
[0]);
496 TEST(unescapeJsonCommandLine
, ParsesMultipleArgumentsWithEscapedCharacters
) {
497 std::vector
<std::string
> Result
= unescapeJsonCommandLine(
498 " \\\\\\\" \\\"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]);
506 TEST(unescapeJsonCommandLine
, ParsesStringsWithoutSpacesIntoSingleArgument
) {
507 std::vector
<std::string
> QuotedNoSpaces
= unescapeJsonCommandLine(
508 "\\\"a\\\"\\\"b\\\"");
509 ASSERT_EQ(1ul, QuotedNoSpaces
.size());
510 EXPECT_EQ("ab", QuotedNoSpaces
[0]);
512 std::vector
<std::string
> MixedNoSpaces
= unescapeJsonCommandLine(
513 "\\\"a\\\"bcd\\\"ef\\\"\\\"\\\"\\\"g\\\"");
514 ASSERT_EQ(1ul, MixedNoSpaces
.size());
515 EXPECT_EQ("abcdefg", MixedNoSpaces
[0]);
518 TEST(unescapeJsonCommandLine
, ParsesQuotedStringWithoutClosingQuote
) {
519 std::vector
<std::string
> Unclosed
= unescapeJsonCommandLine("\\\"abc");
520 ASSERT_EQ(1ul, Unclosed
.size());
521 EXPECT_EQ("abc", Unclosed
[0]);
523 std::vector
<std::string
> Empty
= unescapeJsonCommandLine("\\\"");
524 ASSERT_EQ(1ul, Empty
.size());
525 EXPECT_EQ("", Empty
[0]);
528 TEST(unescapeJsonCommandLine
, ParsesSingleQuotedString
) {
529 std::vector
<std::string
> Args
= unescapeJsonCommandLine("a'\\\\b \\\"c\\\"'");
530 ASSERT_EQ(1ul, Args
.size());
531 EXPECT_EQ("a\\b \"c\"", Args
[0]);
534 TEST(FixedCompilationDatabase
, ReturnsFixedCommandLine
) {
535 FixedCompilationDatabase
Database(".", /*CommandLine*/ {"one", "two"});
536 StringRef
FileName("source");
537 std::vector
<CompileCommand
> Result
=
538 Database
.getCompileCommands(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"));
546 TEST(FixedCompilationDatabase
, GetAllFiles
) {
547 std::vector
<std::string
> CommandLine
;
548 CommandLine
.push_back("one");
549 CommandLine
.push_back("two");
550 FixedCompilationDatabase
Database(".", CommandLine
);
552 EXPECT_THAT(Database
.getAllFiles(), IsEmpty());
555 TEST(FixedCompilationDatabase
, GetAllCompileCommands
) {
556 std::vector
<std::string
> CommandLine
;
557 CommandLine
.push_back("one");
558 CommandLine
.push_back("two");
559 FixedCompilationDatabase
Database(".", CommandLine
);
561 EXPECT_EQ(0ul, Database
.getAllCompileCommands().size());
564 TEST(FixedCompilationDatabase
, FromBuffer
) {
565 const char *Data
= R
"(
572 std::string ErrorMsg
;
574 FixedCompilationDatabase::loadFromBuffer("/cdb/dir", Data
, ErrorMsg
);
576 std::vector
<CompileCommand
> Result
= CDB
->getCompileCommands("/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
);
581 Result
.front().CommandLine
,
582 ElementsAre(EndsWith("clang-tool"), "-DFOO=BAR", "--baz", "/foo/bar.cc"));
585 TEST(ParseFixedCompilationDatabase
, ReturnsNullOnEmptyArgumentList
) {
587 std::string ErrorMsg
;
588 std::unique_ptr
<FixedCompilationDatabase
> Database
=
589 FixedCompilationDatabase::loadFromCommandLine(Argc
, nullptr, ErrorMsg
);
590 EXPECT_FALSE(Database
);
591 EXPECT_TRUE(ErrorMsg
.empty());
595 TEST(ParseFixedCompilationDatabase
, ReturnsNullWithoutDoubleDash
) {
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());
606 TEST(ParseFixedCompilationDatabase
, ReturnsArgumentsAfterDoubleDash
) {
608 const char *Argv
[] = {
609 "1", "2", "--\0no-constant-folding", "-DDEF3", "-DDEF4"
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("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"));
625 TEST(ParseFixedCompilationDatabase
, ReturnsEmptyCommandLine
) {
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("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"));
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
=
651 Database
->getCompileCommands("source");
652 ASSERT_EQ(1ul, Result
.size());
653 ASSERT_EQ(".", Result
[0].Directory
);
654 ASSERT_THAT(Result
[0].CommandLine
,
655 ElementsAre(EndsWith("clang-tool"), "-c", "-DDEF3", "source"));
659 TEST(ParseFixedCompilationDatabase
, HandlesPositionalArgsSyntaxOnly
) {
660 // Adjust the given command line arguments to ensure that any positional
661 // arguments in them are stripped.
662 const char *Argv
[] = {"--", "somefile.cpp", "-fsyntax-only", "-DDEF3"};
663 int Argc
= std::size(Argv
);
664 std::string ErrorMessage
;
665 std::unique_ptr
<CompilationDatabase
> Database
=
666 FixedCompilationDatabase::loadFromCommandLine(Argc
, Argv
, ErrorMessage
);
667 ASSERT_TRUE((bool)Database
);
668 ASSERT_TRUE(ErrorMessage
.empty());
669 std::vector
<CompileCommand
> Result
= Database
->getCompileCommands("source");
670 ASSERT_EQ(1ul, Result
.size());
671 ASSERT_EQ(".", Result
[0].Directory
);
673 Result
[0].CommandLine
,
674 ElementsAre(EndsWith("clang-tool"), "-fsyntax-only", "-DDEF3", "source"));
677 TEST(ParseFixedCompilationDatabase
, HandlesArgv0
) {
678 const char *Argv
[] = {"1", "2", "--", "mytool", "somefile.cpp"};
679 int Argc
= std::size(Argv
);
680 std::string ErrorMsg
;
681 std::unique_ptr
<FixedCompilationDatabase
> Database
=
682 FixedCompilationDatabase::loadFromCommandLine(Argc
, Argv
, ErrorMsg
);
683 ASSERT_TRUE((bool)Database
);
684 ASSERT_TRUE(ErrorMsg
.empty());
685 std::vector
<CompileCommand
> Result
=
686 Database
->getCompileCommands("source");
687 ASSERT_EQ(1ul, Result
.size());
688 ASSERT_EQ(".", Result
[0].Directory
);
689 std::vector
<std::string
> Expected
;
690 ASSERT_THAT(Result
[0].CommandLine
,
691 ElementsAre(EndsWith("clang-tool"), "source"));
695 struct MemCDB
: public CompilationDatabase
{
696 using EntryMap
= llvm::StringMap
<SmallVector
<CompileCommand
, 1>>;
698 MemCDB(const EntryMap
&E
) : Entries(E
) {}
700 std::vector
<CompileCommand
> getCompileCommands(StringRef F
) const override
{
701 auto Ret
= Entries
.lookup(F
);
702 return {Ret
.begin(), Ret
.end()};
705 std::vector
<std::string
> getAllFiles() const override
{
706 std::vector
<std::string
> Result
;
707 for (const auto &Entry
: Entries
)
708 Result
.push_back(std::string(Entry
.first()));
713 class MemDBTest
: public ::testing::Test
{
715 // Adds an entry to the underlying compilation database.
716 // A flag is injected: -D <File>, so the command used can be identified.
717 void add(StringRef File
, StringRef Clang
, StringRef Flags
) {
718 SmallVector
<StringRef
, 8> Argv
= {Clang
, File
, "-D", File
};
719 llvm::SplitString(Flags
, Argv
);
721 // Trim double quotation from the argumnets if any.
722 for (auto *It
= Argv
.begin(); It
!= Argv
.end(); ++It
)
723 *It
= It
->trim("\"");
726 llvm::sys::path::system_temp_directory(false, Dir
);
728 Entries
[path(File
)].push_back(
729 {Dir
, path(File
), {Argv
.begin(), Argv
.end()}, "foo.o"});
731 void add(StringRef File
, StringRef Flags
= "") { add(File
, "clang", Flags
); }
733 // Turn a unix path fragment (foo/bar.h) into a native path (C:\tmp\foo\bar.h)
734 std::string
path(llvm::SmallString
<32> File
) {
735 llvm::SmallString
<32> Dir
;
736 llvm::sys::path::system_temp_directory(false, Dir
);
737 llvm::sys::path::native(File
);
738 llvm::SmallString
<64> Result
;
739 llvm::sys::path::append(Result
, Dir
, File
);
740 return std::string(Result
.str());
743 MemCDB::EntryMap Entries
;
746 class InterpolateTest
: public MemDBTest
{
748 // Look up the command from a relative path, and return it in string form.
749 // The input file is not included in the returned command.
750 std::string
getCommand(llvm::StringRef F
, bool MakeNative
= true) {
752 inferMissingCompileCommands(std::make_unique
<MemCDB
>(Entries
))
753 ->getCompileCommands(MakeNative
? path(F
) : F
);
756 // drop the input file argument, so tests don't have to deal with path().
757 EXPECT_EQ(Results
[0].CommandLine
.back(), MakeNative
? path(F
) : F
)
758 << "Last arg should be the file";
759 Results
[0].CommandLine
.pop_back();
760 EXPECT_EQ(Results
[0].CommandLine
.back(), "--")
761 << "Second-last arg should be --";
762 Results
[0].CommandLine
.pop_back();
763 return llvm::join(Results
[0].CommandLine
, " ");
766 // Parse the file whose command was used out of the Heuristic string.
767 std::string
getProxy(llvm::StringRef F
) {
769 inferMissingCompileCommands(std::make_unique
<MemCDB
>(Entries
))
770 ->getCompileCommands(path(F
));
773 StringRef Proxy
= Results
.front().Heuristic
;
774 if (!Proxy
.consume_front("inferred from "))
776 // We have a proxy file, convert back to a unix relative path.
777 // This is a bit messy, but we do need to test these strings somehow...
778 llvm::SmallString
<32> TempDir
;
779 llvm::sys::path::system_temp_directory(false, TempDir
);
780 Proxy
.consume_front(TempDir
);
781 Proxy
.consume_front(llvm::sys::path::get_separator());
782 llvm::SmallString
<32> Result
= Proxy
;
783 llvm::sys::path::native(Result
, llvm::sys::path::Style::posix
);
784 return std::string(Result
.str());
788 TEST_F(InterpolateTest
, Nearby
) {
791 add("an/other/foo.cpp");
793 // great: dir and name both match (prefix or full, case insensitive)
794 EXPECT_EQ(getProxy("dir/f.cpp"), "dir/foo.cpp");
795 EXPECT_EQ(getProxy("dir/FOO.cpp"), "dir/foo.cpp");
796 // no name match. prefer matching dir, break ties by alpha
797 EXPECT_EQ(getProxy("dir/a.cpp"), "dir/bar.cpp");
798 // an exact name match beats one segment of directory match
799 EXPECT_EQ(getProxy("some/other/bar.h"), "dir/bar.cpp");
800 // two segments of directory match beat a prefix name match
801 EXPECT_EQ(getProxy("an/other/b.cpp"), "an/other/foo.cpp");
802 // if nothing matches at all, we still get the closest alpha match
803 EXPECT_EQ(getProxy("below/some/obscure/path.cpp"), "an/other/foo.cpp");
806 TEST_F(InterpolateTest
, Language
) {
807 add("dir/foo.cpp", "-std=c++17");
808 add("dir/bar.c", "");
809 add("dir/baz.cee", "-x c");
810 add("dir/aux.cpp", "-std=c++17 -x objective-c++");
812 // .h is ambiguous, so we add explicit language flags
813 EXPECT_EQ(getCommand("foo.h"),
814 "clang -D dir/foo.cpp -x c++-header -std=c++17");
815 // Same thing if we have no extension. (again, we treat as header).
816 EXPECT_EQ(getCommand("foo"), "clang -D dir/foo.cpp -x c++-header -std=c++17");
817 // and invalid extensions.
818 EXPECT_EQ(getCommand("foo.cce"),
819 "clang -D dir/foo.cpp -x c++-header -std=c++17");
820 // and don't add -x if the inferred language is correct.
821 EXPECT_EQ(getCommand("foo.hpp"), "clang -D dir/foo.cpp -std=c++17");
822 // respect -x if it's already there.
823 EXPECT_EQ(getCommand("baz.h"), "clang -D dir/baz.cee -x c-header");
824 // prefer a worse match with the right extension.
825 EXPECT_EQ(getCommand("foo.c"), "clang -D dir/bar.c");
826 Entries
.erase(path(StringRef("dir/bar.c")));
827 // Now we transfer across languages, so drop -std too.
828 EXPECT_EQ(getCommand("foo.c"), "clang -D dir/foo.cpp");
829 // Prefer -x over -std when overriding language.
830 EXPECT_EQ(getCommand("aux.h"),
831 "clang -D dir/aux.cpp -x objective-c++-header -std=c++17");
834 TEST_F(InterpolateTest
, Strip
) {
835 add("dir/foo.cpp", "-o foo.o -Wall");
836 // the -o option and the input file are removed, but -Wall is preserved.
837 EXPECT_EQ(getCommand("dir/bar.cpp"), "clang -D dir/foo.cpp -Wall");
840 TEST_F(InterpolateTest
, StripDoubleDash
) {
841 add("dir/foo.cpp", "-o foo.o -std=c++14 -Wall -- dir/foo.cpp");
842 // input file and output option are removed
844 // -std option gets re-added as the last argument before the input file
845 // -- is removed as it's not necessary - the new input file doesn't start with
847 EXPECT_EQ(getCommand("dir/bar.cpp"), "clang -D dir/foo.cpp -Wall -std=c++14");
850 TEST_F(InterpolateTest
, Case
) {
851 add("FOO/BAR/BAZ/SHOUT.cc");
852 add("foo/bar/baz/quiet.cc");
853 // Case mismatches are completely ignored, so we choose the name match.
854 EXPECT_EQ(getProxy("foo/bar/baz/shout.C"), "FOO/BAR/BAZ/SHOUT.cc");
857 TEST_F(InterpolateTest
, LanguagePreference
) {
858 add("foo/bar/baz/exact.C");
859 add("foo/bar/baz/exact.c");
860 add("other/random/path.cpp");
861 // Proxies for ".H" files are ".C" files, and not ".c files".
862 EXPECT_EQ(getProxy("foo/bar/baz/exact.H"), "foo/bar/baz/exact.C");
865 TEST_F(InterpolateTest
, Aliasing
) {
866 add("foo.cpp", "-faligned-new");
868 // The interpolated command should keep the given flag as written, even though
869 // the flag is internally represented as an alias.
870 EXPECT_EQ(getCommand("foo.hpp"), "clang -D foo.cpp -faligned-new");
873 TEST_F(InterpolateTest
, ClangCL
) {
874 add("foo.cpp", "clang-cl", "/W4");
876 // Language flags should be added with CL syntax.
877 EXPECT_EQ(getCommand("foo.h", false), "clang-cl -D foo.cpp /W4 /TP");
880 TEST_F(InterpolateTest
, DriverModes
) {
881 add("foo.cpp", "clang-cl", "--driver-mode=gcc");
882 add("bar.cpp", "clang", "--driver-mode=cl");
884 // --driver-mode overrides should be respected.
885 EXPECT_EQ(getCommand("foo.h"),
886 "clang-cl -D foo.cpp --driver-mode=gcc -x c++-header");
887 EXPECT_EQ(getCommand("bar.h", false),
888 "clang -D bar.cpp --driver-mode=cl /TP");
891 TEST(TransferCompileCommandTest
, Smoke
) {
893 Cmd
.Filename
= "foo.cc";
894 Cmd
.CommandLine
= {"clang", "-Wall", "foo.cc"};
895 Cmd
.Directory
= "dir";
896 CompileCommand Transferred
= transferCompileCommand(std::move(Cmd
), "foo.h");
897 EXPECT_EQ(Transferred
.Filename
, "foo.h");
898 EXPECT_THAT(Transferred
.CommandLine
,
899 ElementsAre("clang", "-Wall", "-x", "c++-header", "--", "foo.h"));
900 EXPECT_EQ(Transferred
.Directory
, "dir");
903 TEST(CompileCommandTest
, EqualityOperator
) {
904 CompileCommand
CCRef("/foo/bar", "hello.c", {"a", "b"}, "hello.o");
905 CompileCommand CCTest
= CCRef
;
907 EXPECT_TRUE(CCRef
== CCTest
);
908 EXPECT_FALSE(CCRef
!= CCTest
);
911 CCTest
.Directory
= "/foo/baz";
912 EXPECT_FALSE(CCRef
== CCTest
);
913 EXPECT_TRUE(CCRef
!= CCTest
);
916 CCTest
.Filename
= "bonjour.c";
917 EXPECT_FALSE(CCRef
== CCTest
);
918 EXPECT_TRUE(CCRef
!= CCTest
);
921 CCTest
.CommandLine
.push_back("c");
922 EXPECT_FALSE(CCRef
== CCTest
);
923 EXPECT_TRUE(CCRef
!= CCTest
);
926 CCTest
.Output
= "bonjour.o";
927 EXPECT_FALSE(CCRef
== CCTest
);
928 EXPECT_TRUE(CCRef
!= CCTest
);
931 class TargetAndModeTest
: public MemDBTest
{
933 TargetAndModeTest() { llvm::InitializeAllTargetInfos(); }
936 // Look up the command from a relative path, and return it in string form.
937 std::string
getCommand(llvm::StringRef F
) {
938 auto Results
= inferTargetAndDriverMode(std::make_unique
<MemCDB
>(Entries
))
939 ->getCompileCommands(path(F
));
942 return llvm::join(Results
[0].CommandLine
, " ");
946 TEST_F(TargetAndModeTest
, TargetAndMode
) {
947 add("foo.cpp", "clang-cl", "");
948 add("bar.cpp", "clang++", "");
950 EXPECT_EQ(getCommand("foo.cpp"),
951 "clang-cl --driver-mode=cl foo.cpp -D foo.cpp");
952 EXPECT_EQ(getCommand("bar.cpp"),
953 "clang++ --driver-mode=g++ bar.cpp -D bar.cpp");
956 class ExpandResponseFilesTest
: public MemDBTest
{
958 ExpandResponseFilesTest() : FS(new llvm::vfs::InMemoryFileSystem
) {}
961 void addFile(StringRef File
, StringRef Content
) {
963 FS
->addFile(File
, 0, llvm::MemoryBuffer::getMemBufferCopy(Content
)));
966 std::string
getCommand(llvm::StringRef F
) {
967 auto Results
= expandResponseFiles(std::make_unique
<MemCDB
>(Entries
), FS
)
968 ->getCompileCommands(path(F
));
971 return llvm::join(Results
[0].CommandLine
, " ");
974 llvm::IntrusiveRefCntPtr
<llvm::vfs::InMemoryFileSystem
> FS
;
977 TEST_F(ExpandResponseFilesTest
, ExpandResponseFiles
) {
978 addFile(path(StringRef("rsp1.rsp")), "-Dflag");
980 add("foo.cpp", "clang", "@rsp1.rsp");
981 add("bar.cpp", "clang", "-Dflag");
982 EXPECT_EQ(getCommand("foo.cpp"), "clang foo.cpp -D foo.cpp -Dflag");
983 EXPECT_EQ(getCommand("bar.cpp"), "clang bar.cpp -D bar.cpp -Dflag");
986 TEST_F(ExpandResponseFilesTest
, ExpandResponseFilesEmptyArgument
) {
987 addFile(path(StringRef("rsp1.rsp")), "-Dflag");
989 add("foo.cpp", "clang", "@rsp1.rsp \"\"");
990 EXPECT_EQ(getCommand("foo.cpp"), "clang foo.cpp -D foo.cpp -Dflag ");
993 } // end namespace tooling
994 } // end namespace clang