1 //===- unittest/Tooling/CompilationDatabaseTest.cpp -----------------------===//
2 //
3 //                     The LLVM Compiler Infrastructure
4 //
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
7 //
8 //===----------------------------------------------------------------------===//
9 
10 #include "clang/AST/ASTConsumer.h"
11 #include "clang/AST/DeclCXX.h"
12 #include "clang/AST/DeclGroup.h"
13 #include "clang/Frontend/FrontendAction.h"
14 #include "clang/Tooling/FileMatchTrie.h"
15 #include "clang/Tooling/JSONCompilationDatabase.h"
16 #include "clang/Tooling/Tooling.h"
17 #include "llvm/Support/PathV2.h"
18 #include "gtest/gtest.h"
19 
20 namespace clang {
21 namespace tooling {
22 
23 static void expectFailure(StringRef JSONDatabase, StringRef Explanation) {
24   std::string ErrorMessage;
25   EXPECT_EQ(NULL, JSONCompilationDatabase::loadFromBuffer(JSONDatabase,
26                                                           ErrorMessage))
27     << "Expected an error because of: " << Explanation;
28 }
29 
30 TEST(JSONCompilationDatabase, ErrsOnInvalidFormat) {
31   expectFailure("", "Empty database");
32   expectFailure("{", "Invalid JSON");
33   expectFailure("[[]]", "Array instead of object");
34   expectFailure("[{\"a\":[]}]", "Array instead of value");
35   expectFailure("[{\"a\":\"b\"}]", "Unknown key");
36   expectFailure("[{[]:\"\"}]", "Incorrectly typed entry");
37   expectFailure("[{}]", "Empty entry");
38   expectFailure("[{\"directory\":\"\",\"command\":\"\"}]", "Missing file");
39   expectFailure("[{\"directory\":\"\",\"file\":\"\"}]", "Missing command");
40   expectFailure("[{\"command\":\"\",\"file\":\"\"}]", "Missing directory");
41 }
42 
43 static std::vector<std::string> getAllFiles(StringRef JSONDatabase,
44                                             std::string &ErrorMessage) {
45   OwningPtr<CompilationDatabase> Database(
46       JSONCompilationDatabase::loadFromBuffer(JSONDatabase, ErrorMessage));
47   if (!Database) {
48     ADD_FAILURE() << ErrorMessage;
49     return std::vector<std::string>();
50   }
51   return Database->getAllFiles();
52 }
53 
54 static std::vector<CompileCommand> getAllCompileCommands(StringRef JSONDatabase,
55                                                     std::string &ErrorMessage) {
56   OwningPtr<CompilationDatabase> Database(
57       JSONCompilationDatabase::loadFromBuffer(JSONDatabase, ErrorMessage));
58   if (!Database) {
59     ADD_FAILURE() << ErrorMessage;
60     return std::vector<CompileCommand>();
61   }
62   return Database->getAllCompileCommands();
63 }
64 
65 TEST(JSONCompilationDatabase, GetAllFiles) {
66   std::string ErrorMessage;
67   EXPECT_EQ(std::vector<std::string>(),
68             getAllFiles("[]", ErrorMessage)) << ErrorMessage;
69 
70   std::vector<std::string> expected_files;
71   SmallString<16> PathStorage;
72   llvm::sys::path::native("//net/dir/file1", PathStorage);
73   expected_files.push_back(PathStorage.str());
74   llvm::sys::path::native("//net/dir/file2", PathStorage);
75   expected_files.push_back(PathStorage.str());
76   EXPECT_EQ(expected_files, getAllFiles(
77     "[{\"directory\":\"//net/dir\","
78       "\"command\":\"command\","
79       "\"file\":\"file1\"},"
80     " {\"directory\":\"//net/dir\","
81       "\"command\":\"command\","
82       "\"file\":\"file2\"}]",
83     ErrorMessage)) << ErrorMessage;
84 }
85 
86 TEST(JSONCompilationDatabase, GetAllCompileCommands) {
87   std::string ErrorMessage;
88   EXPECT_EQ(0u,
89             getAllCompileCommands("[]", ErrorMessage).size()) << ErrorMessage;
90 
91   StringRef Directory1("//net/dir1");
92   StringRef FileName1("file1");
93   StringRef Command1("command1");
94   StringRef Directory2("//net/dir2");
95   StringRef FileName2("file1");
96   StringRef Command2("command1");
97 
98   std::vector<CompileCommand> Commands = getAllCompileCommands(
99       ("[{\"directory\":\"" + Directory1 + "\"," +
100              "\"command\":\"" + Command1 + "\","
101              "\"file\":\"" + FileName1 + "\"},"
102        " {\"directory\":\"" + Directory2 + "\"," +
103              "\"command\":\"" + Command2 + "\","
104              "\"file\":\"" + FileName2 + "\"}]").str(),
105       ErrorMessage);
106   EXPECT_EQ(2U, Commands.size()) << ErrorMessage;
107   EXPECT_EQ(Directory1, Commands[0].Directory) << ErrorMessage;
108   ASSERT_EQ(1u, Commands[0].CommandLine.size());
109   EXPECT_EQ(Command1, Commands[0].CommandLine[0]) << ErrorMessage;
110   EXPECT_EQ(Directory2, Commands[1].Directory) << ErrorMessage;
111   ASSERT_EQ(1u, Commands[1].CommandLine.size());
112   EXPECT_EQ(Command2, Commands[1].CommandLine[0]) << ErrorMessage;
113 }
114 
115 static CompileCommand findCompileArgsInJsonDatabase(StringRef FileName,
116                                                     StringRef JSONDatabase,
117                                                     std::string &ErrorMessage) {
118   OwningPtr<CompilationDatabase> Database(
119       JSONCompilationDatabase::loadFromBuffer(JSONDatabase, ErrorMessage));
120   if (!Database)
121     return CompileCommand();
122   std::vector<CompileCommand> Commands = Database->getCompileCommands(FileName);
123   EXPECT_LE(Commands.size(), 1u);
124   if (Commands.empty())
125     return CompileCommand();
126   return Commands[0];
127 }
128 
129 struct FakeComparator : public PathComparator {
130   virtual ~FakeComparator() {}
131   virtual bool equivalent(StringRef FileA, StringRef FileB) const {
132     return FileA.equals_lower(FileB);
133   }
134 };
135 
136 class FileMatchTrieTest : public ::testing::Test {
137 protected:
138   FileMatchTrieTest() : Trie(new FakeComparator()) {}
139 
140   StringRef find(StringRef Path) {
141     llvm::raw_string_ostream ES(Error);
142     return Trie.findEquivalent(Path, ES);
143   }
144 
145   FileMatchTrie Trie;
146   std::string Error;
147 };
148 
149 TEST_F(FileMatchTrieTest, InsertingRelativePath) {
150   Trie.insert("//net/path/file.cc");
151   Trie.insert("file.cc");
152   EXPECT_EQ("//net/path/file.cc", find("//net/path/file.cc"));
153 }
154 
155 TEST_F(FileMatchTrieTest, MatchingRelativePath) {
156   EXPECT_EQ("", find("file.cc"));
157 }
158 
159 TEST_F(FileMatchTrieTest, ReturnsBestResults) {
160   Trie.insert("//net/d/c/b.cc");
161   Trie.insert("//net/d/b/b.cc");
162   EXPECT_EQ("//net/d/b/b.cc", find("//net/d/b/b.cc"));
163 }
164 
165 TEST_F(FileMatchTrieTest, HandlesSymlinks) {
166   Trie.insert("//net/AA/file.cc");
167   EXPECT_EQ("//net/AA/file.cc", find("//net/aa/file.cc"));
168 }
169 
170 TEST_F(FileMatchTrieTest, ReportsSymlinkAmbiguity) {
171   Trie.insert("//net/Aa/file.cc");
172   Trie.insert("//net/aA/file.cc");
173   EXPECT_TRUE(find("//net/aa/file.cc").empty());
174   EXPECT_EQ("Path is ambiguous", Error);
175 }
176 
177 TEST_F(FileMatchTrieTest, LongerMatchingSuffixPreferred) {
178   Trie.insert("//net/src/Aa/file.cc");
179   Trie.insert("//net/src/aA/file.cc");
180   Trie.insert("//net/SRC/aa/file.cc");
181   EXPECT_EQ("//net/SRC/aa/file.cc", find("//net/src/aa/file.cc"));
182 }
183 
184 TEST_F(FileMatchTrieTest, EmptyTrie) {
185   EXPECT_TRUE(find("//net/some/path").empty());
186 }
187 
188 TEST_F(FileMatchTrieTest, NoResult) {
189   Trie.insert("//net/somepath/otherfile.cc");
190   Trie.insert("//net/otherpath/somefile.cc");
191   EXPECT_EQ("", find("//net/somepath/somefile.cc"));
192 }
193 
194 TEST_F(FileMatchTrieTest, RootElementDifferent) {
195   Trie.insert("//net/path/file.cc");
196   Trie.insert("//net/otherpath/file.cc");
197   EXPECT_EQ("//net/path/file.cc", find("//net/path/file.cc"));
198 }
199 
200 TEST_F(FileMatchTrieTest, CannotResolveRelativePath) {
201   EXPECT_EQ("", find("relative-path.cc"));
202   EXPECT_EQ("Cannot resolve relative paths", Error);
203 }
204 
205 TEST(findCompileArgsInJsonDatabase, FindsNothingIfEmpty) {
206   std::string ErrorMessage;
207   CompileCommand NotFound = findCompileArgsInJsonDatabase(
208     "a-file.cpp", "", ErrorMessage);
209   EXPECT_TRUE(NotFound.CommandLine.empty()) << ErrorMessage;
210   EXPECT_TRUE(NotFound.Directory.empty()) << ErrorMessage;
211 }
212 
213 TEST(findCompileArgsInJsonDatabase, ReadsSingleEntry) {
214   StringRef Directory("//net/some/directory");
215   StringRef FileName("//net/path/to/a-file.cpp");
216   StringRef Command("//net/path/to/compiler and some arguments");
217   std::string ErrorMessage;
218   CompileCommand FoundCommand = findCompileArgsInJsonDatabase(
219     FileName,
220     ("[{\"directory\":\"" + Directory + "\"," +
221        "\"command\":\"" + Command + "\","
222        "\"file\":\"" + FileName + "\"}]").str(),
223     ErrorMessage);
224   EXPECT_EQ(Directory, FoundCommand.Directory) << ErrorMessage;
225   ASSERT_EQ(4u, FoundCommand.CommandLine.size()) << ErrorMessage;
226   EXPECT_EQ("//net/path/to/compiler",
227             FoundCommand.CommandLine[0]) << ErrorMessage;
228   EXPECT_EQ("and", FoundCommand.CommandLine[1]) << ErrorMessage;
229   EXPECT_EQ("some", FoundCommand.CommandLine[2]) << ErrorMessage;
230   EXPECT_EQ("arguments", FoundCommand.CommandLine[3]) << ErrorMessage;
231 
232   CompileCommand NotFound = findCompileArgsInJsonDatabase(
233     "a-file.cpp",
234     ("[{\"directory\":\"" + Directory + "\"," +
235        "\"command\":\"" + Command + "\","
236        "\"file\":\"" + FileName + "\"}]").str(),
237     ErrorMessage);
238   EXPECT_TRUE(NotFound.Directory.empty()) << ErrorMessage;
239   EXPECT_TRUE(NotFound.CommandLine.empty()) << ErrorMessage;
240 }
241 
242 TEST(findCompileArgsInJsonDatabase, ReadsCompileCommandLinesWithSpaces) {
243   StringRef Directory("//net/some/directory");
244   StringRef FileName("//net/path/to/a-file.cpp");
245   StringRef Command("\\\"//net/path to compiler\\\" \\\"and an argument\\\"");
246   std::string ErrorMessage;
247   CompileCommand FoundCommand = findCompileArgsInJsonDatabase(
248     FileName,
249     ("[{\"directory\":\"" + Directory + "\"," +
250        "\"command\":\"" + Command + "\","
251        "\"file\":\"" + FileName + "\"}]").str(),
252     ErrorMessage);
253   ASSERT_EQ(2u, FoundCommand.CommandLine.size());
254   EXPECT_EQ("//net/path to compiler",
255             FoundCommand.CommandLine[0]) << ErrorMessage;
256   EXPECT_EQ("and an argument", FoundCommand.CommandLine[1]) << ErrorMessage;
257 }
258 
259 TEST(findCompileArgsInJsonDatabase, ReadsDirectoryWithSpaces) {
260   StringRef Directory("//net/some directory / with spaces");
261   StringRef FileName("//net/path/to/a-file.cpp");
262   StringRef Command("a command");
263   std::string ErrorMessage;
264   CompileCommand FoundCommand = findCompileArgsInJsonDatabase(
265     FileName,
266     ("[{\"directory\":\"" + Directory + "\"," +
267        "\"command\":\"" + Command + "\","
268        "\"file\":\"" + FileName + "\"}]").str(),
269     ErrorMessage);
270   EXPECT_EQ(Directory, FoundCommand.Directory) << ErrorMessage;
271 }
272 
273 TEST(findCompileArgsInJsonDatabase, FindsEntry) {
274   StringRef Directory("//net/directory");
275   StringRef FileName("file");
276   StringRef Command("command");
277   std::string JsonDatabase = "[";
278   for (int I = 0; I < 10; ++I) {
279     if (I > 0) JsonDatabase += ",";
280     JsonDatabase +=
281       ("{\"directory\":\"" + Directory + Twine(I) + "\"," +
282         "\"command\":\"" + Command + Twine(I) + "\","
283         "\"file\":\"" + FileName + Twine(I) + "\"}").str();
284   }
285   JsonDatabase += "]";
286   std::string ErrorMessage;
287   CompileCommand FoundCommand = findCompileArgsInJsonDatabase(
288     "//net/directory4/file4", JsonDatabase, ErrorMessage);
289   EXPECT_EQ("//net/directory4", FoundCommand.Directory) << ErrorMessage;
290   ASSERT_EQ(1u, FoundCommand.CommandLine.size()) << ErrorMessage;
291   EXPECT_EQ("command4", FoundCommand.CommandLine[0]) << ErrorMessage;
292 }
293 
294 static std::vector<std::string> unescapeJsonCommandLine(StringRef Command) {
295   std::string JsonDatabase =
296     ("[{\"directory\":\"//net/root\", \"file\":\"test\", \"command\": \"" +
297      Command + "\"}]").str();
298   std::string ErrorMessage;
299   CompileCommand FoundCommand = findCompileArgsInJsonDatabase(
300     "//net/root/test", JsonDatabase, ErrorMessage);
301   EXPECT_TRUE(ErrorMessage.empty()) << ErrorMessage;
302   return FoundCommand.CommandLine;
303 }
304 
305 TEST(unescapeJsonCommandLine, ReturnsEmptyArrayOnEmptyString) {
306   std::vector<std::string> Result = unescapeJsonCommandLine("");
307   EXPECT_TRUE(Result.empty());
308 }
309 
310 TEST(unescapeJsonCommandLine, SplitsOnSpaces) {
311   std::vector<std::string> Result = unescapeJsonCommandLine("a b c");
312   ASSERT_EQ(3ul, Result.size());
313   EXPECT_EQ("a", Result[0]);
314   EXPECT_EQ("b", Result[1]);
315   EXPECT_EQ("c", Result[2]);
316 }
317 
318 TEST(unescapeJsonCommandLine, MungesMultipleSpaces) {
319   std::vector<std::string> Result = unescapeJsonCommandLine("   a   b   ");
320   ASSERT_EQ(2ul, Result.size());
321   EXPECT_EQ("a", Result[0]);
322   EXPECT_EQ("b", Result[1]);
323 }
324 
325 TEST(unescapeJsonCommandLine, UnescapesBackslashCharacters) {
326   std::vector<std::string> Backslash = unescapeJsonCommandLine("a\\\\\\\\");
327   ASSERT_EQ(1ul, Backslash.size());
328   EXPECT_EQ("a\\", Backslash[0]);
329   std::vector<std::string> Quote = unescapeJsonCommandLine("a\\\\\\\"");
330   ASSERT_EQ(1ul, Quote.size());
331   EXPECT_EQ("a\"", Quote[0]);
332 }
333 
334 TEST(unescapeJsonCommandLine, DoesNotMungeSpacesBetweenQuotes) {
335   std::vector<std::string> Result = unescapeJsonCommandLine("\\\"  a  b  \\\"");
336   ASSERT_EQ(1ul, Result.size());
337   EXPECT_EQ("  a  b  ", Result[0]);
338 }
339 
340 TEST(unescapeJsonCommandLine, AllowsMultipleQuotedArguments) {
341   std::vector<std::string> Result = unescapeJsonCommandLine(
342       "  \\\" a \\\"  \\\" b \\\"  ");
343   ASSERT_EQ(2ul, Result.size());
344   EXPECT_EQ(" a ", Result[0]);
345   EXPECT_EQ(" b ", Result[1]);
346 }
347 
348 TEST(unescapeJsonCommandLine, AllowsEmptyArgumentsInQuotes) {
349   std::vector<std::string> Result = unescapeJsonCommandLine(
350       "\\\"\\\"\\\"\\\"");
351   ASSERT_EQ(1ul, Result.size());
352   EXPECT_TRUE(Result[0].empty()) << Result[0];
353 }
354 
355 TEST(unescapeJsonCommandLine, ParsesEscapedQuotesInQuotedStrings) {
356   std::vector<std::string> Result = unescapeJsonCommandLine(
357       "\\\"\\\\\\\"\\\"");
358   ASSERT_EQ(1ul, Result.size());
359   EXPECT_EQ("\"", Result[0]);
360 }
361 
362 TEST(unescapeJsonCommandLine, ParsesMultipleArgumentsWithEscapedCharacters) {
363   std::vector<std::string> Result = unescapeJsonCommandLine(
364       "  \\\\\\\"  \\\"a \\\\\\\" b \\\"     \\\"and\\\\\\\\c\\\"   \\\\\\\"");
365   ASSERT_EQ(4ul, Result.size());
366   EXPECT_EQ("\"", Result[0]);
367   EXPECT_EQ("a \" b ", Result[1]);
368   EXPECT_EQ("and\\c", Result[2]);
369   EXPECT_EQ("\"", Result[3]);
370 }
371 
372 TEST(unescapeJsonCommandLine, ParsesStringsWithoutSpacesIntoSingleArgument) {
373   std::vector<std::string> QuotedNoSpaces = unescapeJsonCommandLine(
374       "\\\"a\\\"\\\"b\\\"");
375   ASSERT_EQ(1ul, QuotedNoSpaces.size());
376   EXPECT_EQ("ab", QuotedNoSpaces[0]);
377 
378   std::vector<std::string> MixedNoSpaces = unescapeJsonCommandLine(
379       "\\\"a\\\"bcd\\\"ef\\\"\\\"\\\"\\\"g\\\"");
380   ASSERT_EQ(1ul, MixedNoSpaces.size());
381   EXPECT_EQ("abcdefg", MixedNoSpaces[0]);
382 }
383 
384 TEST(unescapeJsonCommandLine, ParsesQuotedStringWithoutClosingQuote) {
385   std::vector<std::string> Unclosed = unescapeJsonCommandLine("\\\"abc");
386   ASSERT_EQ(1ul, Unclosed.size());
387   EXPECT_EQ("abc", Unclosed[0]);
388 
389   std::vector<std::string> Empty = unescapeJsonCommandLine("\\\"");
390   ASSERT_EQ(1ul, Empty.size());
391   EXPECT_EQ("", Empty[0]);
392 }
393 
394 TEST(FixedCompilationDatabase, ReturnsFixedCommandLine) {
395   std::vector<std::string> CommandLine;
396   CommandLine.push_back("one");
397   CommandLine.push_back("two");
398   FixedCompilationDatabase Database(".", CommandLine);
399   std::vector<CompileCommand> Result =
400     Database.getCompileCommands("source");
401   ASSERT_EQ(1ul, Result.size());
402   std::vector<std::string> ExpectedCommandLine(1, "clang-tool");
403   ExpectedCommandLine.insert(ExpectedCommandLine.end(),
404                              CommandLine.begin(), CommandLine.end());
405   ExpectedCommandLine.push_back("source");
406   EXPECT_EQ(".", Result[0].Directory);
407   EXPECT_EQ(ExpectedCommandLine, Result[0].CommandLine);
408 }
409 
410 TEST(FixedCompilationDatabase, GetAllFiles) {
411   std::vector<std::string> CommandLine;
412   CommandLine.push_back("one");
413   CommandLine.push_back("two");
414   FixedCompilationDatabase Database(".", CommandLine);
415 
416   EXPECT_EQ(0ul, Database.getAllFiles().size());
417 }
418 
419 TEST(FixedCompilationDatabase, GetAllCompileCommands) {
420   std::vector<std::string> CommandLine;
421   CommandLine.push_back("one");
422   CommandLine.push_back("two");
423   FixedCompilationDatabase Database(".", CommandLine);
424 
425   EXPECT_EQ(0ul, Database.getAllCompileCommands().size());
426 }
427 
428 TEST(ParseFixedCompilationDatabase, ReturnsNullOnEmptyArgumentList) {
429   int Argc = 0;
430   OwningPtr<FixedCompilationDatabase> Database(
431       FixedCompilationDatabase::loadFromCommandLine(Argc, NULL));
432   EXPECT_FALSE(Database);
433   EXPECT_EQ(0, Argc);
434 }
435 
436 TEST(ParseFixedCompilationDatabase, ReturnsNullWithoutDoubleDash) {
437   int Argc = 2;
438   const char *Argv[] = { "1", "2" };
439   OwningPtr<FixedCompilationDatabase> Database(
440       FixedCompilationDatabase::loadFromCommandLine(Argc, Argv));
441   EXPECT_FALSE(Database);
442   EXPECT_EQ(2, Argc);
443 }
444 
445 TEST(ParseFixedCompilationDatabase, ReturnsArgumentsAfterDoubleDash) {
446   int Argc = 5;
447   const char *Argv[] = { "1", "2", "--\0no-constant-folding", "3", "4" };
448   OwningPtr<FixedCompilationDatabase> Database(
449       FixedCompilationDatabase::loadFromCommandLine(Argc, Argv));
450   ASSERT_TRUE(Database);
451   std::vector<CompileCommand> Result =
452     Database->getCompileCommands("source");
453   ASSERT_EQ(1ul, Result.size());
454   ASSERT_EQ(".", Result[0].Directory);
455   std::vector<std::string> CommandLine;
456   CommandLine.push_back("clang-tool");
457   CommandLine.push_back("3");
458   CommandLine.push_back("4");
459   CommandLine.push_back("source");
460   ASSERT_EQ(CommandLine, Result[0].CommandLine);
461   EXPECT_EQ(2, Argc);
462 }
463 
464 TEST(ParseFixedCompilationDatabase, ReturnsEmptyCommandLine) {
465   int Argc = 3;
466   const char *Argv[] = { "1", "2", "--\0no-constant-folding" };
467   OwningPtr<FixedCompilationDatabase> Database(
468       FixedCompilationDatabase::loadFromCommandLine(Argc, Argv));
469   ASSERT_TRUE(Database);
470   std::vector<CompileCommand> Result =
471     Database->getCompileCommands("source");
472   ASSERT_EQ(1ul, Result.size());
473   ASSERT_EQ(".", Result[0].Directory);
474   std::vector<std::string> CommandLine;
475   CommandLine.push_back("clang-tool");
476   CommandLine.push_back("source");
477   ASSERT_EQ(CommandLine, Result[0].CommandLine);
478   EXPECT_EQ(2, Argc);
479 }
480 
481 } // end namespace tooling
482 } // end namespace clang
483