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   llvm::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 TEST(JSONCompilationDatabase, GetAllFiles) {
55   std::string ErrorMessage;
56   EXPECT_EQ(std::vector<std::string>(),
57             getAllFiles("[]", ErrorMessage)) << ErrorMessage;
58 
59   std::vector<std::string> expected_files;
60   SmallString<16> PathStorage;
61   llvm::sys::path::native("//net/dir/file1", PathStorage);
62   expected_files.push_back(PathStorage.str());
63   llvm::sys::path::native("//net/dir/file2", PathStorage);
64   expected_files.push_back(PathStorage.str());
65   EXPECT_EQ(expected_files, getAllFiles(
66     "[{\"directory\":\"//net/dir\","
67       "\"command\":\"command\","
68       "\"file\":\"file1\"},"
69     " {\"directory\":\"//net/dir\","
70       "\"command\":\"command\","
71       "\"file\":\"file2\"}]",
72     ErrorMessage)) << ErrorMessage;
73 }
74 
75 static CompileCommand findCompileArgsInJsonDatabase(StringRef FileName,
76                                                     StringRef JSONDatabase,
77                                                     std::string &ErrorMessage) {
78   llvm::OwningPtr<CompilationDatabase> Database(
79       JSONCompilationDatabase::loadFromBuffer(JSONDatabase, ErrorMessage));
80   if (!Database)
81     return CompileCommand();
82   std::vector<CompileCommand> Commands = Database->getCompileCommands(FileName);
83   EXPECT_LE(Commands.size(), 1u);
84   if (Commands.empty())
85     return CompileCommand();
86   return Commands[0];
87 }
88 
89 struct FakeComparator : public PathComparator {
90   virtual ~FakeComparator() {}
91   virtual bool equivalent(StringRef FileA, StringRef FileB) const {
92     return FileA.equals_lower(FileB);
93   }
94 };
95 
96 class FileMatchTrieTest : public ::testing::Test {
97 protected:
98   FileMatchTrieTest() : Trie(new FakeComparator()) {}
99 
100   StringRef find(StringRef Path) {
101     llvm::raw_string_ostream ES(Error);
102     return Trie.findEquivalent(Path, ES);
103   }
104 
105   FileMatchTrie Trie;
106   std::string Error;
107 };
108 
109 TEST_F(FileMatchTrieTest, InsertingRelativePath) {
110   Trie.insert("//net/path/file.cc");
111   Trie.insert("file.cc");
112   EXPECT_EQ("//net/path/file.cc", find("//net/path/file.cc"));
113 }
114 
115 TEST_F(FileMatchTrieTest, MatchingRelativePath) {
116   EXPECT_EQ("", find("file.cc"));
117 }
118 
119 TEST_F(FileMatchTrieTest, ReturnsBestResults) {
120   Trie.insert("//net/d/c/b.cc");
121   Trie.insert("//net/d/b/b.cc");
122   EXPECT_EQ("//net/d/b/b.cc", find("//net/d/b/b.cc"));
123 }
124 
125 TEST_F(FileMatchTrieTest, HandlesSymlinks) {
126   Trie.insert("//net/AA/file.cc");
127   EXPECT_EQ("//net/AA/file.cc", find("//net/aa/file.cc"));
128 }
129 
130 TEST_F(FileMatchTrieTest, ReportsSymlinkAmbiguity) {
131   Trie.insert("//net/Aa/file.cc");
132   Trie.insert("//net/aA/file.cc");
133   EXPECT_TRUE(find("//net/aa/file.cc").empty());
134   EXPECT_EQ("Path is ambiguous", Error);
135 }
136 
137 TEST_F(FileMatchTrieTest, LongerMatchingSuffixPreferred) {
138   Trie.insert("//net/src/Aa/file.cc");
139   Trie.insert("//net/src/aA/file.cc");
140   Trie.insert("//net/SRC/aa/file.cc");
141   EXPECT_EQ("//net/SRC/aa/file.cc", find("//net/src/aa/file.cc"));
142 }
143 
144 TEST_F(FileMatchTrieTest, EmptyTrie) {
145   EXPECT_TRUE(find("//net/some/path").empty());
146 }
147 
148 TEST_F(FileMatchTrieTest, NoResult) {
149   Trie.insert("//net/somepath/otherfile.cc");
150   Trie.insert("//net/otherpath/somefile.cc");
151   EXPECT_EQ("", find("//net/somepath/somefile.cc"));
152 }
153 
154 TEST_F(FileMatchTrieTest, RootElementDifferent) {
155   Trie.insert("//net/path/file.cc");
156   Trie.insert("//net/otherpath/file.cc");
157   EXPECT_EQ("//net/path/file.cc", find("//net/path/file.cc"));
158 }
159 
160 TEST_F(FileMatchTrieTest, CannotResolveRelativePath) {
161   EXPECT_EQ("", find("relative-path.cc"));
162   EXPECT_EQ("Cannot resolve relative paths", Error);
163 }
164 
165 TEST(findCompileArgsInJsonDatabase, FindsNothingIfEmpty) {
166   std::string ErrorMessage;
167   CompileCommand NotFound = findCompileArgsInJsonDatabase(
168     "a-file.cpp", "", ErrorMessage);
169   EXPECT_TRUE(NotFound.CommandLine.empty()) << ErrorMessage;
170   EXPECT_TRUE(NotFound.Directory.empty()) << ErrorMessage;
171 }
172 
173 TEST(findCompileArgsInJsonDatabase, ReadsSingleEntry) {
174   StringRef Directory("//net/some/directory");
175   StringRef FileName("//net/path/to/a-file.cpp");
176   StringRef Command("//net/path/to/compiler and some arguments");
177   std::string ErrorMessage;
178   CompileCommand FoundCommand = findCompileArgsInJsonDatabase(
179     FileName,
180     ("[{\"directory\":\"" + Directory + "\"," +
181        "\"command\":\"" + Command + "\","
182        "\"file\":\"" + FileName + "\"}]").str(),
183     ErrorMessage);
184   EXPECT_EQ(Directory, FoundCommand.Directory) << ErrorMessage;
185   ASSERT_EQ(4u, FoundCommand.CommandLine.size()) << ErrorMessage;
186   EXPECT_EQ("//net/path/to/compiler",
187             FoundCommand.CommandLine[0]) << ErrorMessage;
188   EXPECT_EQ("and", FoundCommand.CommandLine[1]) << ErrorMessage;
189   EXPECT_EQ("some", FoundCommand.CommandLine[2]) << ErrorMessage;
190   EXPECT_EQ("arguments", FoundCommand.CommandLine[3]) << ErrorMessage;
191 
192   CompileCommand NotFound = findCompileArgsInJsonDatabase(
193     "a-file.cpp",
194     ("[{\"directory\":\"" + Directory + "\"," +
195        "\"command\":\"" + Command + "\","
196        "\"file\":\"" + FileName + "\"}]").str(),
197     ErrorMessage);
198   EXPECT_TRUE(NotFound.Directory.empty()) << ErrorMessage;
199   EXPECT_TRUE(NotFound.CommandLine.empty()) << ErrorMessage;
200 }
201 
202 TEST(findCompileArgsInJsonDatabase, ReadsCompileCommandLinesWithSpaces) {
203   StringRef Directory("//net/some/directory");
204   StringRef FileName("//net/path/to/a-file.cpp");
205   StringRef Command("\\\"//net/path to compiler\\\" \\\"and an argument\\\"");
206   std::string ErrorMessage;
207   CompileCommand FoundCommand = findCompileArgsInJsonDatabase(
208     FileName,
209     ("[{\"directory\":\"" + Directory + "\"," +
210        "\"command\":\"" + Command + "\","
211        "\"file\":\"" + FileName + "\"}]").str(),
212     ErrorMessage);
213   ASSERT_EQ(2u, FoundCommand.CommandLine.size());
214   EXPECT_EQ("//net/path to compiler",
215             FoundCommand.CommandLine[0]) << ErrorMessage;
216   EXPECT_EQ("and an argument", FoundCommand.CommandLine[1]) << ErrorMessage;
217 }
218 
219 TEST(findCompileArgsInJsonDatabase, ReadsDirectoryWithSpaces) {
220   StringRef Directory("//net/some directory / with spaces");
221   StringRef FileName("//net/path/to/a-file.cpp");
222   StringRef Command("a command");
223   std::string ErrorMessage;
224   CompileCommand FoundCommand = findCompileArgsInJsonDatabase(
225     FileName,
226     ("[{\"directory\":\"" + Directory + "\"," +
227        "\"command\":\"" + Command + "\","
228        "\"file\":\"" + FileName + "\"}]").str(),
229     ErrorMessage);
230   EXPECT_EQ(Directory, FoundCommand.Directory) << ErrorMessage;
231 }
232 
233 TEST(findCompileArgsInJsonDatabase, FindsEntry) {
234   StringRef Directory("//net/directory");
235   StringRef FileName("file");
236   StringRef Command("command");
237   std::string JsonDatabase = "[";
238   for (int I = 0; I < 10; ++I) {
239     if (I > 0) JsonDatabase += ",";
240     JsonDatabase +=
241       ("{\"directory\":\"" + Directory + Twine(I) + "\"," +
242         "\"command\":\"" + Command + Twine(I) + "\","
243         "\"file\":\"" + FileName + Twine(I) + "\"}").str();
244   }
245   JsonDatabase += "]";
246   std::string ErrorMessage;
247   CompileCommand FoundCommand = findCompileArgsInJsonDatabase(
248     "//net/directory4/file4", JsonDatabase, ErrorMessage);
249   EXPECT_EQ("//net/directory4", FoundCommand.Directory) << ErrorMessage;
250   ASSERT_EQ(1u, FoundCommand.CommandLine.size()) << ErrorMessage;
251   EXPECT_EQ("command4", FoundCommand.CommandLine[0]) << ErrorMessage;
252 }
253 
254 static std::vector<std::string> unescapeJsonCommandLine(StringRef Command) {
255   std::string JsonDatabase =
256     ("[{\"directory\":\"//net/root\", \"file\":\"test\", \"command\": \"" +
257      Command + "\"}]").str();
258   std::string ErrorMessage;
259   CompileCommand FoundCommand = findCompileArgsInJsonDatabase(
260     "//net/root/test", JsonDatabase, ErrorMessage);
261   EXPECT_TRUE(ErrorMessage.empty()) << ErrorMessage;
262   return FoundCommand.CommandLine;
263 }
264 
265 TEST(unescapeJsonCommandLine, ReturnsEmptyArrayOnEmptyString) {
266   std::vector<std::string> Result = unescapeJsonCommandLine("");
267   EXPECT_TRUE(Result.empty());
268 }
269 
270 TEST(unescapeJsonCommandLine, SplitsOnSpaces) {
271   std::vector<std::string> Result = unescapeJsonCommandLine("a b c");
272   ASSERT_EQ(3ul, Result.size());
273   EXPECT_EQ("a", Result[0]);
274   EXPECT_EQ("b", Result[1]);
275   EXPECT_EQ("c", Result[2]);
276 }
277 
278 TEST(unescapeJsonCommandLine, MungesMultipleSpaces) {
279   std::vector<std::string> Result = unescapeJsonCommandLine("   a   b   ");
280   ASSERT_EQ(2ul, Result.size());
281   EXPECT_EQ("a", Result[0]);
282   EXPECT_EQ("b", Result[1]);
283 }
284 
285 TEST(unescapeJsonCommandLine, UnescapesBackslashCharacters) {
286   std::vector<std::string> Backslash = unescapeJsonCommandLine("a\\\\\\\\");
287   ASSERT_EQ(1ul, Backslash.size());
288   EXPECT_EQ("a\\", Backslash[0]);
289   std::vector<std::string> Quote = unescapeJsonCommandLine("a\\\\\\\"");
290   ASSERT_EQ(1ul, Quote.size());
291   EXPECT_EQ("a\"", Quote[0]);
292 }
293 
294 TEST(unescapeJsonCommandLine, DoesNotMungeSpacesBetweenQuotes) {
295   std::vector<std::string> Result = unescapeJsonCommandLine("\\\"  a  b  \\\"");
296   ASSERT_EQ(1ul, Result.size());
297   EXPECT_EQ("  a  b  ", Result[0]);
298 }
299 
300 TEST(unescapeJsonCommandLine, AllowsMultipleQuotedArguments) {
301   std::vector<std::string> Result = unescapeJsonCommandLine(
302       "  \\\" a \\\"  \\\" b \\\"  ");
303   ASSERT_EQ(2ul, Result.size());
304   EXPECT_EQ(" a ", Result[0]);
305   EXPECT_EQ(" b ", Result[1]);
306 }
307 
308 TEST(unescapeJsonCommandLine, AllowsEmptyArgumentsInQuotes) {
309   std::vector<std::string> Result = unescapeJsonCommandLine(
310       "\\\"\\\"\\\"\\\"");
311   ASSERT_EQ(1ul, Result.size());
312   EXPECT_TRUE(Result[0].empty()) << Result[0];
313 }
314 
315 TEST(unescapeJsonCommandLine, ParsesEscapedQuotesInQuotedStrings) {
316   std::vector<std::string> Result = unescapeJsonCommandLine(
317       "\\\"\\\\\\\"\\\"");
318   ASSERT_EQ(1ul, Result.size());
319   EXPECT_EQ("\"", Result[0]);
320 }
321 
322 TEST(unescapeJsonCommandLine, ParsesMultipleArgumentsWithEscapedCharacters) {
323   std::vector<std::string> Result = unescapeJsonCommandLine(
324       "  \\\\\\\"  \\\"a \\\\\\\" b \\\"     \\\"and\\\\\\\\c\\\"   \\\\\\\"");
325   ASSERT_EQ(4ul, Result.size());
326   EXPECT_EQ("\"", Result[0]);
327   EXPECT_EQ("a \" b ", Result[1]);
328   EXPECT_EQ("and\\c", Result[2]);
329   EXPECT_EQ("\"", Result[3]);
330 }
331 
332 TEST(unescapeJsonCommandLine, ParsesStringsWithoutSpacesIntoSingleArgument) {
333   std::vector<std::string> QuotedNoSpaces = unescapeJsonCommandLine(
334       "\\\"a\\\"\\\"b\\\"");
335   ASSERT_EQ(1ul, QuotedNoSpaces.size());
336   EXPECT_EQ("ab", QuotedNoSpaces[0]);
337 
338   std::vector<std::string> MixedNoSpaces = unescapeJsonCommandLine(
339       "\\\"a\\\"bcd\\\"ef\\\"\\\"\\\"\\\"g\\\"");
340   ASSERT_EQ(1ul, MixedNoSpaces.size());
341   EXPECT_EQ("abcdefg", MixedNoSpaces[0]);
342 }
343 
344 TEST(unescapeJsonCommandLine, ParsesQuotedStringWithoutClosingQuote) {
345   std::vector<std::string> Unclosed = unescapeJsonCommandLine("\\\"abc");
346   ASSERT_EQ(1ul, Unclosed.size());
347   EXPECT_EQ("abc", Unclosed[0]);
348 
349   std::vector<std::string> Empty = unescapeJsonCommandLine("\\\"");
350   ASSERT_EQ(1ul, Empty.size());
351   EXPECT_EQ("", Empty[0]);
352 }
353 
354 TEST(FixedCompilationDatabase, ReturnsFixedCommandLine) {
355   std::vector<std::string> CommandLine;
356   CommandLine.push_back("one");
357   CommandLine.push_back("two");
358   FixedCompilationDatabase Database(".", CommandLine);
359   std::vector<CompileCommand> Result =
360     Database.getCompileCommands("source");
361   ASSERT_EQ(1ul, Result.size());
362   std::vector<std::string> ExpectedCommandLine(1, "clang-tool");
363   ExpectedCommandLine.insert(ExpectedCommandLine.end(),
364                              CommandLine.begin(), CommandLine.end());
365   ExpectedCommandLine.push_back("source");
366   EXPECT_EQ(".", Result[0].Directory);
367   EXPECT_EQ(ExpectedCommandLine, Result[0].CommandLine);
368 }
369 
370 TEST(FixedCompilationDatabase, GetAllFiles) {
371   std::vector<std::string> CommandLine;
372   CommandLine.push_back("one");
373   CommandLine.push_back("two");
374   FixedCompilationDatabase Database(".", CommandLine);
375 
376   EXPECT_EQ(0ul, Database.getAllFiles().size());
377 }
378 
379 TEST(ParseFixedCompilationDatabase, ReturnsNullOnEmptyArgumentList) {
380   int Argc = 0;
381   llvm::OwningPtr<FixedCompilationDatabase> Database(
382       FixedCompilationDatabase::loadFromCommandLine(Argc, NULL));
383   EXPECT_FALSE(Database);
384   EXPECT_EQ(0, Argc);
385 }
386 
387 TEST(ParseFixedCompilationDatabase, ReturnsNullWithoutDoubleDash) {
388   int Argc = 2;
389   const char *Argv[] = { "1", "2" };
390   llvm::OwningPtr<FixedCompilationDatabase> Database(
391       FixedCompilationDatabase::loadFromCommandLine(Argc, Argv));
392   EXPECT_FALSE(Database);
393   EXPECT_EQ(2, Argc);
394 }
395 
396 TEST(ParseFixedCompilationDatabase, ReturnsArgumentsAfterDoubleDash) {
397   int Argc = 5;
398   const char *Argv[] = { "1", "2", "--\0no-constant-folding", "3", "4" };
399   llvm::OwningPtr<FixedCompilationDatabase> Database(
400       FixedCompilationDatabase::loadFromCommandLine(Argc, Argv));
401   ASSERT_TRUE(Database);
402   std::vector<CompileCommand> Result =
403     Database->getCompileCommands("source");
404   ASSERT_EQ(1ul, Result.size());
405   ASSERT_EQ(".", Result[0].Directory);
406   std::vector<std::string> CommandLine;
407   CommandLine.push_back("clang-tool");
408   CommandLine.push_back("3");
409   CommandLine.push_back("4");
410   CommandLine.push_back("source");
411   ASSERT_EQ(CommandLine, Result[0].CommandLine);
412   EXPECT_EQ(2, Argc);
413 }
414 
415 TEST(ParseFixedCompilationDatabase, ReturnsEmptyCommandLine) {
416   int Argc = 3;
417   const char *Argv[] = { "1", "2", "--\0no-constant-folding" };
418   llvm::OwningPtr<FixedCompilationDatabase> Database(
419       FixedCompilationDatabase::loadFromCommandLine(Argc, Argv));
420   ASSERT_TRUE(Database);
421   std::vector<CompileCommand> Result =
422     Database->getCompileCommands("source");
423   ASSERT_EQ(1ul, Result.size());
424   ASSERT_EQ(".", Result[0].Directory);
425   std::vector<std::string> CommandLine;
426   CommandLine.push_back("clang-tool");
427   CommandLine.push_back("source");
428   ASSERT_EQ(CommandLine, Result[0].CommandLine);
429   EXPECT_EQ(2, Argc);
430 }
431 
432 } // end namespace tooling
433 } // end namespace clang
434