Run DCE after a LoopFlatten test to reduce spurious output [nfc]
[llvm-project.git] / clang / unittests / Tooling / CompilationDatabaseTest.cpp
blob5173d472486bfc2927057fc6c867c51962b1b59c
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 //===----------------------------------------------------------------------===//
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>
19 namespace clang {
20 namespace tooling {
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;
29 EXPECT_EQ(nullptr,
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,
60 Syntax));
61 if (!Database) {
62 ADD_FAILURE() << ErrorMessage;
63 return std::vector<std::string>();
65 auto Result = Database->getAllFiles();
66 std::sort(Result.begin(), Result.end());
67 return Result;
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,
75 Syntax));
76 if (!Database) {
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),
86 IsEmpty())
87 << ErrorMessage;
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",
104 "file": "file1"
107 "directory": "//net/dir",
108 "command": "command",
109 "file": "../file1"
112 "directory": "//net/dir",
113 "command": "command",
114 "file": "file2"
117 "directory": "//net/dir",
118 "command": "command",
119 "file": "//net/dir/foo/../file3"
121 ])json",
122 ErrorMessage, JSONCommandLineSyntax::Gnu),
123 UnorderedElementsAreArray(expected_files))
124 << ErrorMessage;
127 TEST(JSONCompilationDatabase, GetAllCompileCommands) {
128 std::string ErrorMessage;
129 EXPECT_EQ(
130 0u, getAllCompileCommands(JSONCommandLineSyntax::Gnu, "[]", ErrorMessage)
131 .size())
132 << 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 +
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;
168 // Check that order is preserved.
169 Commands = getAllCompileCommands(
170 JSONCommandLineSyntax::Gnu,
171 ("[{\"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;
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));
198 if (!Database)
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();
206 return Commands[0];
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 ("[{\"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;
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 {
236 protected:
237 FileMatchTrieTest() : Trie(new FakeComparator()) {}
239 StringRef find(StringRef Path) {
240 llvm::raw_string_ostream ES(Error);
241 return Trie.findEquivalent(Path, ES);
244 FileMatchTrie Trie;
245 std::string Error;
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(
327 FileName,
328 ("[{\"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;
340 CompileCommand NotFound = findCompileArgsInJsonDatabase(
341 "a-file.cpp",
342 ("[{\"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;
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 ("[{\"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;
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 ("[{\"directory\":\"" + Directory + "\"," +
375 "\"command\":\"" + Command + "\","
376 "\"file\":\"" + FileName + "\"}]").str(),
377 ErrorMessage);
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 += ",";
388 JsonDatabase +=
389 ("{\"directory\":\"" + Directory + Twine(I) + "\"," +
390 "\"command\":\"" + Command + Twine(I) + "\","
391 "\"file\":\"" + FileName + Twine(I) + "\"}").str();
393 JsonDatabase += "]";
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) {
418 std::string DB =
419 R"([{"directory":"//net/dir", "file":"//net/dir/foo.c", "command":")" +
420 Case.first + "\"}]";
421 CompileCommand FoundCommand =
422 findCompileArgsInJsonDatabase("//net/dir/foo.c", DB, ErrorMessage);
423 EXPECT_EQ(Case.second, llvm::join(FoundCommand.CommandLine, " "))
424 << Case.first;
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(
484 "\\\"\\\"\\\"\\\"");
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(
491 "\\\"\\\\\\\"\\\"");
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"(
567 -DFOO=BAR
569 --baz
572 std::string ErrorMsg;
573 auto CDB =
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);
580 EXPECT_THAT(
581 Result.front().CommandLine,
582 ElementsAre(EndsWith("clang-tool"), "-DFOO=BAR", "--baz", "/foo/bar.cc"));
585 TEST(ParseFixedCompilationDatabase, ReturnsNullOnEmptyArgumentList) {
586 int Argc = 0;
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());
592 EXPECT_EQ(0, Argc);
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);
606 TEST(ParseFixedCompilationDatabase, ReturnsArgumentsAfterDoubleDash) {
607 int Argc = 5;
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"));
622 EXPECT_EQ(2, Argc);
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("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);
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"));
656 EXPECT_EQ(2, Argc);
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);
672 ASSERT_THAT(
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"));
692 EXPECT_EQ(2, Argc);
695 struct MemCDB : public CompilationDatabase {
696 using EntryMap = llvm::StringMap<SmallVector<CompileCommand, 1>>;
697 EntryMap Entries;
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()));
709 return Result;
713 class MemDBTest : public ::testing::Test {
714 protected:
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("\"");
725 SmallString<32> Dir;
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 {
747 protected:
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) {
751 auto Results =
752 inferMissingCompileCommands(std::make_unique<MemCDB>(Entries))
753 ->getCompileCommands(MakeNative ? path(F) : F);
754 if (Results.empty())
755 return "none";
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) {
768 auto Results =
769 inferMissingCompileCommands(std::make_unique<MemCDB>(Entries))
770 ->getCompileCommands(path(F));
771 if (Results.empty())
772 return "none";
773 StringRef Proxy = Results.front().Heuristic;
774 if (!Proxy.consume_front("inferred from "))
775 return "";
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) {
789 add("dir/foo.cpp");
790 add("dir/bar.cpp");
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
843 // -Wall flag isn't
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
846 // a dash
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) {
892 CompileCommand Cmd;
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);
910 CCTest = CCRef;
911 CCTest.Directory = "/foo/baz";
912 EXPECT_FALSE(CCRef == CCTest);
913 EXPECT_TRUE(CCRef != CCTest);
915 CCTest = CCRef;
916 CCTest.Filename = "bonjour.c";
917 EXPECT_FALSE(CCRef == CCTest);
918 EXPECT_TRUE(CCRef != CCTest);
920 CCTest = CCRef;
921 CCTest.CommandLine.push_back("c");
922 EXPECT_FALSE(CCRef == CCTest);
923 EXPECT_TRUE(CCRef != CCTest);
925 CCTest = CCRef;
926 CCTest.Output = "bonjour.o";
927 EXPECT_FALSE(CCRef == CCTest);
928 EXPECT_TRUE(CCRef != CCTest);
931 class TargetAndModeTest : public MemDBTest {
932 public:
933 TargetAndModeTest() { llvm::InitializeAllTargetInfos(); }
935 protected:
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));
940 if (Results.empty())
941 return "none";
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 {
957 public:
958 ExpandResponseFilesTest() : FS(new llvm::vfs::InMemoryFileSystem) {}
960 protected:
961 void addFile(StringRef File, StringRef Content) {
962 ASSERT_TRUE(
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));
969 if (Results.empty())
970 return "none";
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