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/CompilationDatabase.h"
15 #include "clang/Tooling/Tooling.h"
16 #include "gtest/gtest.h"
17 
18 namespace clang {
19 namespace tooling {
20 
21 static void expectFailure(StringRef JSONDatabase, StringRef Explanation) {
22   std::string ErrorMessage;
23   EXPECT_EQ(NULL, JSONCompilationDatabase::loadFromBuffer(JSONDatabase,
24                                                           ErrorMessage))
25     << "Expected an error because of: " << Explanation;
26 }
27 
28 TEST(JSONCompilationDatabase, ErrsOnInvalidFormat) {
29   expectFailure("", "Empty database");
30   expectFailure("{", "Invalid JSON");
31   expectFailure("[[]]", "Array instead of object");
32   expectFailure("[{\"a\":[]}]", "Array instead of value");
33   expectFailure("[{\"a\":\"b\"}]", "Unknown key");
34   expectFailure("[{[]:\"\"}]", "Incorrectly typed entry");
35   expectFailure("[{}]", "Empty entry");
36   expectFailure("[{\"directory\":\"\",\"command\":\"\"}]", "Missing file");
37   expectFailure("[{\"directory\":\"\",\"file\":\"\"}]", "Missing command");
38   expectFailure("[{\"command\":\"\",\"file\":\"\"}]", "Missing directory");
39 }
40 
41 static CompileCommand findCompileArgsInJsonDatabase(StringRef FileName,
42                                                     StringRef JSONDatabase,
43                                                     std::string &ErrorMessage) {
44   llvm::OwningPtr<CompilationDatabase> Database(
45       JSONCompilationDatabase::loadFromBuffer(JSONDatabase, ErrorMessage));
46   if (!Database)
47     return CompileCommand();
48   std::vector<CompileCommand> Commands = Database->getCompileCommands(FileName);
49   EXPECT_LE(Commands.size(), 1u);
50   if (Commands.empty())
51     return CompileCommand();
52   return Commands[0];
53 }
54 
55 TEST(findCompileArgsInJsonDatabase, FindsNothingIfEmpty) {
56   std::string ErrorMessage;
57   CompileCommand NotFound = findCompileArgsInJsonDatabase(
58     "a-file.cpp", "", ErrorMessage);
59   EXPECT_TRUE(NotFound.CommandLine.empty()) << ErrorMessage;
60   EXPECT_TRUE(NotFound.Directory.empty()) << ErrorMessage;
61 }
62 
63 TEST(findCompileArgsInJsonDatabase, ReadsSingleEntry) {
64   StringRef Directory("/some/directory");
65   StringRef FileName("/path/to/a-file.cpp");
66   StringRef Command("/path/to/compiler and some arguments");
67   std::string ErrorMessage;
68   CompileCommand FoundCommand = findCompileArgsInJsonDatabase(
69     FileName,
70     ("[{\"directory\":\"" + Directory + "\"," +
71        "\"command\":\"" + Command + "\","
72        "\"file\":\"" + FileName + "\"}]").str(),
73     ErrorMessage);
74   EXPECT_EQ(Directory, FoundCommand.Directory) << ErrorMessage;
75   ASSERT_EQ(4u, FoundCommand.CommandLine.size()) << ErrorMessage;
76   EXPECT_EQ("/path/to/compiler", FoundCommand.CommandLine[0]) << ErrorMessage;
77   EXPECT_EQ("and", FoundCommand.CommandLine[1]) << ErrorMessage;
78   EXPECT_EQ("some", FoundCommand.CommandLine[2]) << ErrorMessage;
79   EXPECT_EQ("arguments", FoundCommand.CommandLine[3]) << ErrorMessage;
80 
81   CompileCommand NotFound = findCompileArgsInJsonDatabase(
82     "a-file.cpp",
83     ("[{\"directory\":\"" + Directory + "\"," +
84        "\"command\":\"" + Command + "\","
85        "\"file\":\"" + FileName + "\"}]").str(),
86     ErrorMessage);
87   EXPECT_TRUE(NotFound.Directory.empty()) << ErrorMessage;
88   EXPECT_TRUE(NotFound.CommandLine.empty()) << ErrorMessage;
89 }
90 
91 TEST(findCompileArgsInJsonDatabase, ReadsCompileCommandLinesWithSpaces) {
92   StringRef Directory("/some/directory");
93   StringRef FileName("/path/to/a-file.cpp");
94   StringRef Command("\\\"/path to compiler\\\" \\\"and an argument\\\"");
95   std::string ErrorMessage;
96   CompileCommand FoundCommand = findCompileArgsInJsonDatabase(
97     FileName,
98     ("[{\"directory\":\"" + Directory + "\"," +
99        "\"command\":\"" + Command + "\","
100        "\"file\":\"" + FileName + "\"}]").str(),
101     ErrorMessage);
102   ASSERT_EQ(2u, FoundCommand.CommandLine.size());
103   EXPECT_EQ("/path to compiler", FoundCommand.CommandLine[0]) << ErrorMessage;
104   EXPECT_EQ("and an argument", FoundCommand.CommandLine[1]) << ErrorMessage;
105 }
106 
107 TEST(findCompileArgsInJsonDatabase, ReadsDirectoryWithSpaces) {
108   StringRef Directory("/some directory / with spaces");
109   StringRef FileName("/path/to/a-file.cpp");
110   StringRef Command("a command");
111   std::string ErrorMessage;
112   CompileCommand FoundCommand = findCompileArgsInJsonDatabase(
113     FileName,
114     ("[{\"directory\":\"" + Directory + "\"," +
115        "\"command\":\"" + Command + "\","
116        "\"file\":\"" + FileName + "\"}]").str(),
117     ErrorMessage);
118   EXPECT_EQ(Directory, FoundCommand.Directory) << ErrorMessage;
119 }
120 
121 TEST(findCompileArgsInJsonDatabase, FindsEntry) {
122   StringRef Directory("directory");
123   StringRef FileName("file");
124   StringRef Command("command");
125   std::string JsonDatabase = "[";
126   for (int I = 0; I < 10; ++I) {
127     if (I > 0) JsonDatabase += ",";
128     JsonDatabase +=
129       ("{\"directory\":\"" + Directory + Twine(I) + "\"," +
130         "\"command\":\"" + Command + Twine(I) + "\","
131         "\"file\":\"" + FileName + Twine(I) + "\"}").str();
132   }
133   JsonDatabase += "]";
134   std::string ErrorMessage;
135   CompileCommand FoundCommand = findCompileArgsInJsonDatabase(
136     "file4", JsonDatabase, ErrorMessage);
137   EXPECT_EQ("directory4", FoundCommand.Directory) << ErrorMessage;
138   ASSERT_EQ(1u, FoundCommand.CommandLine.size()) << ErrorMessage;
139   EXPECT_EQ("command4", FoundCommand.CommandLine[0]) << ErrorMessage;
140 }
141 
142 static std::vector<std::string> unescapeJsonCommandLine(StringRef Command) {
143   std::string JsonDatabase =
144     ("[{\"directory\":\"\", \"file\":\"test\", \"command\": \"" +
145      Command + "\"}]").str();
146   std::string ErrorMessage;
147   CompileCommand FoundCommand = findCompileArgsInJsonDatabase(
148     "test", JsonDatabase, ErrorMessage);
149   EXPECT_TRUE(ErrorMessage.empty()) << ErrorMessage;
150   return FoundCommand.CommandLine;
151 }
152 
153 TEST(unescapeJsonCommandLine, ReturnsEmptyArrayOnEmptyString) {
154   std::vector<std::string> Result = unescapeJsonCommandLine("");
155   EXPECT_TRUE(Result.empty());
156 }
157 
158 TEST(unescapeJsonCommandLine, SplitsOnSpaces) {
159   std::vector<std::string> Result = unescapeJsonCommandLine("a b c");
160   ASSERT_EQ(3ul, Result.size());
161   EXPECT_EQ("a", Result[0]);
162   EXPECT_EQ("b", Result[1]);
163   EXPECT_EQ("c", Result[2]);
164 }
165 
166 TEST(unescapeJsonCommandLine, MungesMultipleSpaces) {
167   std::vector<std::string> Result = unescapeJsonCommandLine("   a   b   ");
168   ASSERT_EQ(2ul, Result.size());
169   EXPECT_EQ("a", Result[0]);
170   EXPECT_EQ("b", Result[1]);
171 }
172 
173 TEST(unescapeJsonCommandLine, UnescapesBackslashCharacters) {
174   std::vector<std::string> Backslash = unescapeJsonCommandLine("a\\\\\\\\");
175   ASSERT_EQ(1ul, Backslash.size());
176   EXPECT_EQ("a\\", Backslash[0]);
177   std::vector<std::string> Quote = unescapeJsonCommandLine("a\\\\\\\"");
178   ASSERT_EQ(1ul, Quote.size());
179   EXPECT_EQ("a\"", Quote[0]);
180 }
181 
182 TEST(unescapeJsonCommandLine, DoesNotMungeSpacesBetweenQuotes) {
183   std::vector<std::string> Result = unescapeJsonCommandLine("\\\"  a  b  \\\"");
184   ASSERT_EQ(1ul, Result.size());
185   EXPECT_EQ("  a  b  ", Result[0]);
186 }
187 
188 TEST(unescapeJsonCommandLine, AllowsMultipleQuotedArguments) {
189   std::vector<std::string> Result = unescapeJsonCommandLine(
190       "  \\\" a \\\"  \\\" b \\\"  ");
191   ASSERT_EQ(2ul, Result.size());
192   EXPECT_EQ(" a ", Result[0]);
193   EXPECT_EQ(" b ", Result[1]);
194 }
195 
196 TEST(unescapeJsonCommandLine, AllowsEmptyArgumentsInQuotes) {
197   std::vector<std::string> Result = unescapeJsonCommandLine(
198       "\\\"\\\"\\\"\\\"");
199   ASSERT_EQ(1ul, Result.size());
200   EXPECT_TRUE(Result[0].empty()) << Result[0];
201 }
202 
203 TEST(unescapeJsonCommandLine, ParsesEscapedQuotesInQuotedStrings) {
204   std::vector<std::string> Result = unescapeJsonCommandLine(
205       "\\\"\\\\\\\"\\\"");
206   ASSERT_EQ(1ul, Result.size());
207   EXPECT_EQ("\"", Result[0]);
208 }
209 
210 TEST(unescapeJsonCommandLine, ParsesMultipleArgumentsWithEscapedCharacters) {
211   std::vector<std::string> Result = unescapeJsonCommandLine(
212       "  \\\\\\\"  \\\"a \\\\\\\" b \\\"     \\\"and\\\\\\\\c\\\"   \\\\\\\"");
213   ASSERT_EQ(4ul, Result.size());
214   EXPECT_EQ("\"", Result[0]);
215   EXPECT_EQ("a \" b ", Result[1]);
216   EXPECT_EQ("and\\c", Result[2]);
217   EXPECT_EQ("\"", Result[3]);
218 }
219 
220 TEST(unescapeJsonCommandLine, ParsesStringsWithoutSpacesIntoSingleArgument) {
221   std::vector<std::string> QuotedNoSpaces = unescapeJsonCommandLine(
222       "\\\"a\\\"\\\"b\\\"");
223   ASSERT_EQ(1ul, QuotedNoSpaces.size());
224   EXPECT_EQ("ab", QuotedNoSpaces[0]);
225 
226   std::vector<std::string> MixedNoSpaces = unescapeJsonCommandLine(
227       "\\\"a\\\"bcd\\\"ef\\\"\\\"\\\"\\\"g\\\"");
228   ASSERT_EQ(1ul, MixedNoSpaces.size());
229   EXPECT_EQ("abcdefg", MixedNoSpaces[0]);
230 }
231 
232 TEST(unescapeJsonCommandLine, ParsesQuotedStringWithoutClosingQuote) {
233   std::vector<std::string> Unclosed = unescapeJsonCommandLine("\\\"abc");
234   ASSERT_EQ(1ul, Unclosed.size());
235   EXPECT_EQ("abc", Unclosed[0]);
236 
237   std::vector<std::string> Empty = unescapeJsonCommandLine("\\\"");
238   ASSERT_EQ(1ul, Empty.size());
239   EXPECT_EQ("", Empty[0]);
240 }
241 
242 TEST(FixedCompilationDatabase, ReturnsFixedCommandLine) {
243   std::vector<std::string> CommandLine;
244   CommandLine.push_back("one");
245   CommandLine.push_back("two");
246   FixedCompilationDatabase Database(".", CommandLine);
247   std::vector<CompileCommand> Result =
248     Database.getCompileCommands("source");
249   ASSERT_EQ(1ul, Result.size());
250   std::vector<std::string> ExpectedCommandLine(1, "clang-tool");
251   ExpectedCommandLine.insert(ExpectedCommandLine.end(),
252                              CommandLine.begin(), CommandLine.end());
253   ExpectedCommandLine.push_back("source");
254   EXPECT_EQ(".", Result[0].Directory);
255   EXPECT_EQ(ExpectedCommandLine, Result[0].CommandLine);
256 }
257 
258 TEST(ParseFixedCompilationDatabase, ReturnsNullOnEmptyArgumentList) {
259   int Argc = 0;
260   llvm::OwningPtr<FixedCompilationDatabase> Database(
261       FixedCompilationDatabase::loadFromCommandLine(Argc, NULL));
262   EXPECT_FALSE(Database);
263   EXPECT_EQ(0, Argc);
264 }
265 
266 TEST(ParseFixedCompilationDatabase, ReturnsNullWithoutDoubleDash) {
267   int Argc = 2;
268   const char *Argv[] = { "1", "2" };
269   llvm::OwningPtr<FixedCompilationDatabase> Database(
270       FixedCompilationDatabase::loadFromCommandLine(Argc, Argv));
271   EXPECT_FALSE(Database);
272   EXPECT_EQ(2, Argc);
273 }
274 
275 TEST(ParseFixedCompilationDatabase, ReturnsArgumentsAfterDoubleDash) {
276   int Argc = 5;
277   const char *Argv[] = { "1", "2", "--\0no-constant-folding", "3", "4" };
278   llvm::OwningPtr<FixedCompilationDatabase> Database(
279       FixedCompilationDatabase::loadFromCommandLine(Argc, Argv));
280   ASSERT_TRUE(Database);
281   std::vector<CompileCommand> Result =
282     Database->getCompileCommands("source");
283   ASSERT_EQ(1ul, Result.size());
284   ASSERT_EQ(".", Result[0].Directory);
285   std::vector<std::string> CommandLine;
286   CommandLine.push_back("clang-tool");
287   CommandLine.push_back("3");
288   CommandLine.push_back("4");
289   CommandLine.push_back("source");
290   ASSERT_EQ(CommandLine, Result[0].CommandLine);
291   EXPECT_EQ(2, Argc);
292 }
293 
294 TEST(ParseFixedCompilationDatabase, ReturnsEmptyCommandLine) {
295   int Argc = 3;
296   const char *Argv[] = { "1", "2", "--\0no-constant-folding" };
297   llvm::OwningPtr<FixedCompilationDatabase> Database(
298       FixedCompilationDatabase::loadFromCommandLine(Argc, Argv));
299   ASSERT_TRUE(Database);
300   std::vector<CompileCommand> Result =
301     Database->getCompileCommands("source");
302   ASSERT_EQ(1ul, Result.size());
303   ASSERT_EQ(".", Result[0].Directory);
304   std::vector<std::string> CommandLine;
305   CommandLine.push_back("clang-tool");
306   CommandLine.push_back("source");
307   ASSERT_EQ(CommandLine, Result[0].CommandLine);
308   EXPECT_EQ(2, Argc);
309 }
310 
311 } // end namespace tooling
312 } // end namespace clang
313