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 //===----------------------------------------------------------------------===// 8 9 #include "clang/AST/DeclCXX.h" 10 #include "clang/AST/DeclGroup.h" 11 #include "clang/Frontend/FrontendAction.h" 12 #include "clang/Tooling/CompilationDatabase.h" 13 #include "clang/Tooling/FileMatchTrie.h" 14 #include "clang/Tooling/JSONCompilationDatabase.h" 15 #include "clang/Tooling/Tooling.h" 16 #include "llvm/Support/Path.h" 17 #include "llvm/Support/TargetSelect.h" 18 #include "gmock/gmock.h" 19 #include "gtest/gtest.h" 20 21 namespace clang { 22 namespace tooling { 23 24 using testing::ElementsAre; 25 using testing::EndsWith; 26 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(); 33 } 34 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."); 53 } 54 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>(); 64 } 65 return Database->getAllFiles(); 66 } 67 68 static std::vector<CompileCommand> 69 getAllCompileCommands(JSONCommandLineSyntax Syntax, StringRef JSONDatabase, 70 std::string &ErrorMessage) { 71 std::unique_ptr<CompilationDatabase> Database( 72 JSONCompilationDatabase::loadFromBuffer(JSONDatabase, ErrorMessage, 73 Syntax)); 74 if (!Database) { 75 ADD_FAILURE() << ErrorMessage; 76 return std::vector<CompileCommand>(); 77 } 78 return Database->getAllCompileCommands(); 79 } 80 81 TEST(JSONCompilationDatabase, GetAllFiles) { 82 std::string ErrorMessage; 83 EXPECT_EQ(std::vector<std::string>(), 84 getAllFiles("[]", ErrorMessage, JSONCommandLineSyntax::Gnu)) 85 << ErrorMessage; 86 87 std::vector<std::string> expected_files; 88 SmallString<16> PathStorage; 89 llvm::sys::path::native("//net/dir/file1", PathStorage); 90 expected_files.push_back(PathStorage.str()); 91 llvm::sys::path::native("//net/dir/file2", PathStorage); 92 expected_files.push_back(PathStorage.str()); 93 llvm::sys::path::native("//net/file1", PathStorage); 94 expected_files.push_back(PathStorage.str()); 95 EXPECT_EQ(expected_files, 96 getAllFiles("[{\"directory\":\"//net/dir\"," 97 "\"command\":\"command\"," 98 "\"file\":\"file1\"}," 99 " {\"directory\":\"//net/dir\"," 100 "\"command\":\"command\"," 101 "\"file\":\"../file1\"}," 102 " {\"directory\":\"//net/dir\"," 103 "\"command\":\"command\"," 104 "\"file\":\"file2\"}]", 105 ErrorMessage, JSONCommandLineSyntax::Gnu)) 106 << ErrorMessage; 107 } 108 109 TEST(JSONCompilationDatabase, GetAllCompileCommands) { 110 std::string ErrorMessage; 111 EXPECT_EQ( 112 0u, getAllCompileCommands(JSONCommandLineSyntax::Gnu, "[]", ErrorMessage) 113 .size()) 114 << ErrorMessage; 115 116 StringRef Directory1("//net/dir1"); 117 StringRef FileName1("file1"); 118 StringRef Command1("command1"); 119 StringRef Output1("file1.o"); 120 StringRef Directory2("//net/dir2"); 121 StringRef FileName2("file2"); 122 StringRef Command2("command2"); 123 StringRef Output2(""); 124 125 std::vector<CompileCommand> Commands = getAllCompileCommands( 126 JSONCommandLineSyntax::Gnu, 127 ("[{\"directory\":\"" + Directory1 + "\"," + "\"command\":\"" + Command1 + 128 "\"," 129 "\"file\":\"" + 130 FileName1 + "\", \"output\":\"" + 131 Output1 + "\"}," 132 " {\"directory\":\"" + 133 Directory2 + "\"," + "\"command\":\"" + Command2 + "\"," 134 "\"file\":\"" + 135 FileName2 + "\"}]") 136 .str(), 137 ErrorMessage); 138 EXPECT_EQ(2U, Commands.size()) << ErrorMessage; 139 EXPECT_EQ(Directory1, Commands[0].Directory) << ErrorMessage; 140 EXPECT_EQ(FileName1, Commands[0].Filename) << ErrorMessage; 141 EXPECT_EQ(Output1, Commands[0].Output) << ErrorMessage; 142 ASSERT_EQ(1u, Commands[0].CommandLine.size()); 143 EXPECT_EQ(Command1, Commands[0].CommandLine[0]) << ErrorMessage; 144 EXPECT_EQ(Directory2, Commands[1].Directory) << ErrorMessage; 145 EXPECT_EQ(FileName2, Commands[1].Filename) << ErrorMessage; 146 EXPECT_EQ(Output2, Commands[1].Output) << ErrorMessage; 147 ASSERT_EQ(1u, Commands[1].CommandLine.size()); 148 EXPECT_EQ(Command2, Commands[1].CommandLine[0]) << ErrorMessage; 149 150 // Check that order is preserved. 151 Commands = getAllCompileCommands( 152 JSONCommandLineSyntax::Gnu, 153 ("[{\"directory\":\"" + Directory2 + "\"," + "\"command\":\"" + Command2 + 154 "\"," 155 "\"file\":\"" + 156 FileName2 + "\"}," 157 " {\"directory\":\"" + 158 Directory1 + "\"," + "\"command\":\"" + Command1 + "\"," 159 "\"file\":\"" + 160 FileName1 + "\"}]") 161 .str(), 162 ErrorMessage); 163 EXPECT_EQ(2U, Commands.size()) << ErrorMessage; 164 EXPECT_EQ(Directory2, Commands[0].Directory) << ErrorMessage; 165 EXPECT_EQ(FileName2, Commands[0].Filename) << ErrorMessage; 166 ASSERT_EQ(1u, Commands[0].CommandLine.size()); 167 EXPECT_EQ(Command2, Commands[0].CommandLine[0]) << ErrorMessage; 168 EXPECT_EQ(Directory1, Commands[1].Directory) << ErrorMessage; 169 EXPECT_EQ(FileName1, Commands[1].Filename) << ErrorMessage; 170 ASSERT_EQ(1u, Commands[1].CommandLine.size()); 171 EXPECT_EQ(Command1, Commands[1].CommandLine[0]) << ErrorMessage; 172 } 173 174 static CompileCommand findCompileArgsInJsonDatabase(StringRef FileName, 175 StringRef JSONDatabase, 176 std::string &ErrorMessage) { 177 std::unique_ptr<CompilationDatabase> Database( 178 JSONCompilationDatabase::loadFromBuffer(JSONDatabase, ErrorMessage, 179 JSONCommandLineSyntax::Gnu)); 180 if (!Database) 181 return CompileCommand(); 182 std::vector<CompileCommand> Commands = Database->getCompileCommands(FileName); 183 EXPECT_LE(Commands.size(), 1u); 184 if (Commands.empty()) 185 return CompileCommand(); 186 return Commands[0]; 187 } 188 189 TEST(JSONCompilationDatabase, ArgumentsPreferredOverCommand) { 190 StringRef Directory("//net/dir"); 191 StringRef FileName("//net/dir/filename"); 192 StringRef Command("command"); 193 StringRef Arguments = "arguments"; 194 Twine ArgumentsAccumulate; 195 std::string ErrorMessage; 196 CompileCommand FoundCommand = findCompileArgsInJsonDatabase( 197 FileName, 198 ("[{\"directory\":\"" + Directory + "\"," 199 "\"arguments\":[\"" + Arguments + "\"]," 200 "\"command\":\"" + Command + "\"," 201 "\"file\":\"" + FileName + "\"}]").str(), 202 ErrorMessage); 203 EXPECT_EQ(Directory, FoundCommand.Directory) << ErrorMessage; 204 EXPECT_EQ(1u, FoundCommand.CommandLine.size()) << ErrorMessage; 205 EXPECT_EQ(Arguments, FoundCommand.CommandLine[0]) << ErrorMessage; 206 } 207 208 struct FakeComparator : public PathComparator { 209 ~FakeComparator() override {} 210 bool equivalent(StringRef FileA, StringRef FileB) const override { 211 return FileA.equals_lower(FileB); 212 } 213 }; 214 215 class FileMatchTrieTest : public ::testing::Test { 216 protected: 217 FileMatchTrieTest() : Trie(new FakeComparator()) {} 218 219 StringRef find(StringRef Path) { 220 llvm::raw_string_ostream ES(Error); 221 return Trie.findEquivalent(Path, ES); 222 } 223 224 FileMatchTrie Trie; 225 std::string Error; 226 }; 227 228 TEST_F(FileMatchTrieTest, InsertingRelativePath) { 229 Trie.insert("//net/path/file.cc"); 230 Trie.insert("file.cc"); 231 EXPECT_EQ("//net/path/file.cc", find("//net/path/file.cc")); 232 } 233 234 TEST_F(FileMatchTrieTest, MatchingRelativePath) { 235 EXPECT_EQ("", find("file.cc")); 236 } 237 238 TEST_F(FileMatchTrieTest, ReturnsBestResults) { 239 Trie.insert("//net/d/c/b.cc"); 240 Trie.insert("//net/d/b/b.cc"); 241 EXPECT_EQ("//net/d/b/b.cc", find("//net/d/b/b.cc")); 242 } 243 244 TEST_F(FileMatchTrieTest, HandlesSymlinks) { 245 Trie.insert("//net/AA/file.cc"); 246 EXPECT_EQ("//net/AA/file.cc", find("//net/aa/file.cc")); 247 } 248 249 TEST_F(FileMatchTrieTest, ReportsSymlinkAmbiguity) { 250 Trie.insert("//net/Aa/file.cc"); 251 Trie.insert("//net/aA/file.cc"); 252 EXPECT_TRUE(find("//net/aa/file.cc").empty()); 253 EXPECT_EQ("Path is ambiguous", Error); 254 } 255 256 TEST_F(FileMatchTrieTest, LongerMatchingSuffixPreferred) { 257 Trie.insert("//net/src/Aa/file.cc"); 258 Trie.insert("//net/src/aA/file.cc"); 259 Trie.insert("//net/SRC/aa/file.cc"); 260 EXPECT_EQ("//net/SRC/aa/file.cc", find("//net/src/aa/file.cc")); 261 } 262 263 TEST_F(FileMatchTrieTest, EmptyTrie) { 264 EXPECT_TRUE(find("//net/some/path").empty()); 265 } 266 267 TEST_F(FileMatchTrieTest, NoResult) { 268 Trie.insert("//net/somepath/otherfile.cc"); 269 Trie.insert("//net/otherpath/somefile.cc"); 270 EXPECT_EQ("", find("//net/somepath/somefile.cc")); 271 } 272 273 TEST_F(FileMatchTrieTest, RootElementDifferent) { 274 Trie.insert("//net/path/file.cc"); 275 Trie.insert("//net/otherpath/file.cc"); 276 EXPECT_EQ("//net/path/file.cc", find("//net/path/file.cc")); 277 } 278 279 TEST_F(FileMatchTrieTest, CannotResolveRelativePath) { 280 EXPECT_EQ("", find("relative-path.cc")); 281 EXPECT_EQ("Cannot resolve relative paths", Error); 282 } 283 284 TEST(findCompileArgsInJsonDatabase, FindsNothingIfEmpty) { 285 std::string ErrorMessage; 286 CompileCommand NotFound = findCompileArgsInJsonDatabase( 287 "a-file.cpp", "", ErrorMessage); 288 EXPECT_TRUE(NotFound.CommandLine.empty()) << ErrorMessage; 289 EXPECT_TRUE(NotFound.Directory.empty()) << ErrorMessage; 290 } 291 292 TEST(findCompileArgsInJsonDatabase, ReadsSingleEntry) { 293 StringRef Directory("//net/some/directory"); 294 StringRef FileName("//net/path/to/a-file.cpp"); 295 StringRef Command("//net/path/to/compiler and some arguments"); 296 std::string ErrorMessage; 297 CompileCommand FoundCommand = findCompileArgsInJsonDatabase( 298 FileName, 299 ("[{\"directory\":\"" + Directory + "\"," + 300 "\"command\":\"" + Command + "\"," 301 "\"file\":\"" + FileName + "\"}]").str(), 302 ErrorMessage); 303 EXPECT_EQ(Directory, FoundCommand.Directory) << ErrorMessage; 304 ASSERT_EQ(4u, FoundCommand.CommandLine.size()) << ErrorMessage; 305 EXPECT_EQ("//net/path/to/compiler", 306 FoundCommand.CommandLine[0]) << ErrorMessage; 307 EXPECT_EQ("and", FoundCommand.CommandLine[1]) << ErrorMessage; 308 EXPECT_EQ("some", FoundCommand.CommandLine[2]) << ErrorMessage; 309 EXPECT_EQ("arguments", FoundCommand.CommandLine[3]) << ErrorMessage; 310 311 CompileCommand NotFound = findCompileArgsInJsonDatabase( 312 "a-file.cpp", 313 ("[{\"directory\":\"" + Directory + "\"," + 314 "\"command\":\"" + Command + "\"," 315 "\"file\":\"" + FileName + "\"}]").str(), 316 ErrorMessage); 317 EXPECT_TRUE(NotFound.Directory.empty()) << ErrorMessage; 318 EXPECT_TRUE(NotFound.CommandLine.empty()) << ErrorMessage; 319 } 320 321 TEST(findCompileArgsInJsonDatabase, ReadsCompileCommandLinesWithSpaces) { 322 StringRef Directory("//net/some/directory"); 323 StringRef FileName("//net/path/to/a-file.cpp"); 324 StringRef Command("\\\"//net/path to compiler\\\" \\\"and an argument\\\""); 325 std::string ErrorMessage; 326 CompileCommand FoundCommand = findCompileArgsInJsonDatabase( 327 FileName, 328 ("[{\"directory\":\"" + Directory + "\"," + 329 "\"command\":\"" + Command + "\"," 330 "\"file\":\"" + FileName + "\"}]").str(), 331 ErrorMessage); 332 ASSERT_EQ(2u, FoundCommand.CommandLine.size()); 333 EXPECT_EQ("//net/path to compiler", 334 FoundCommand.CommandLine[0]) << ErrorMessage; 335 EXPECT_EQ("and an argument", FoundCommand.CommandLine[1]) << ErrorMessage; 336 } 337 338 TEST(findCompileArgsInJsonDatabase, ReadsDirectoryWithSpaces) { 339 StringRef Directory("//net/some directory / with spaces"); 340 StringRef FileName("//net/path/to/a-file.cpp"); 341 StringRef Command("a command"); 342 std::string ErrorMessage; 343 CompileCommand FoundCommand = findCompileArgsInJsonDatabase( 344 FileName, 345 ("[{\"directory\":\"" + Directory + "\"," + 346 "\"command\":\"" + Command + "\"," 347 "\"file\":\"" + FileName + "\"}]").str(), 348 ErrorMessage); 349 EXPECT_EQ(Directory, FoundCommand.Directory) << ErrorMessage; 350 } 351 352 TEST(findCompileArgsInJsonDatabase, FindsEntry) { 353 StringRef Directory("//net/directory"); 354 StringRef FileName("file"); 355 StringRef Command("command"); 356 std::string JsonDatabase = "["; 357 for (int I = 0; I < 10; ++I) { 358 if (I > 0) JsonDatabase += ","; 359 JsonDatabase += 360 ("{\"directory\":\"" + Directory + Twine(I) + "\"," + 361 "\"command\":\"" + Command + Twine(I) + "\"," 362 "\"file\":\"" + FileName + Twine(I) + "\"}").str(); 363 } 364 JsonDatabase += "]"; 365 std::string ErrorMessage; 366 CompileCommand FoundCommand = findCompileArgsInJsonDatabase( 367 "//net/directory4/file4", JsonDatabase, ErrorMessage); 368 EXPECT_EQ("//net/directory4", FoundCommand.Directory) << ErrorMessage; 369 ASSERT_EQ(1u, FoundCommand.CommandLine.size()) << ErrorMessage; 370 EXPECT_EQ("command4", FoundCommand.CommandLine[0]) << ErrorMessage; 371 } 372 373 TEST(findCompileArgsInJsonDatabase, ParsesCompilerWrappers) { 374 std::vector<std::pair<std::string, std::string>> Cases = { 375 {"distcc gcc foo.c", "gcc foo.c"}, 376 {"gomacc clang++ foo.c", "clang++ foo.c"}, 377 {"ccache gcc foo.c", "gcc foo.c"}, 378 {"ccache.exe gcc foo.c", "gcc foo.c"}, 379 {"ccache g++.exe foo.c", "g++.exe foo.c"}, 380 {"ccache distcc gcc foo.c", "gcc foo.c"}, 381 382 {"distcc foo.c", "distcc foo.c"}, 383 {"distcc -I/foo/bar foo.c", "distcc -I/foo/bar foo.c"}, 384 }; 385 std::string ErrorMessage; 386 387 for (const auto &Case : Cases) { 388 std::string DB = 389 R"([{"directory":"//net/dir", "file":"//net/dir/foo.c", "command":")" + 390 Case.first + "\"}]"; 391 CompileCommand FoundCommand = 392 findCompileArgsInJsonDatabase("//net/dir/foo.c", DB, ErrorMessage); 393 EXPECT_EQ(Case.second, llvm::join(FoundCommand.CommandLine, " ")) 394 << Case.first; 395 } 396 } 397 398 static std::vector<std::string> unescapeJsonCommandLine(StringRef Command) { 399 std::string JsonDatabase = 400 ("[{\"directory\":\"//net/root\", \"file\":\"test\", \"command\": \"" + 401 Command + "\"}]").str(); 402 std::string ErrorMessage; 403 CompileCommand FoundCommand = findCompileArgsInJsonDatabase( 404 "//net/root/test", JsonDatabase, ErrorMessage); 405 EXPECT_TRUE(ErrorMessage.empty()) << ErrorMessage; 406 return FoundCommand.CommandLine; 407 } 408 409 TEST(unescapeJsonCommandLine, ReturnsEmptyArrayOnEmptyString) { 410 std::vector<std::string> Result = unescapeJsonCommandLine(""); 411 EXPECT_TRUE(Result.empty()); 412 } 413 414 TEST(unescapeJsonCommandLine, SplitsOnSpaces) { 415 std::vector<std::string> Result = unescapeJsonCommandLine("a b c"); 416 ASSERT_EQ(3ul, Result.size()); 417 EXPECT_EQ("a", Result[0]); 418 EXPECT_EQ("b", Result[1]); 419 EXPECT_EQ("c", Result[2]); 420 } 421 422 TEST(unescapeJsonCommandLine, MungesMultipleSpaces) { 423 std::vector<std::string> Result = unescapeJsonCommandLine(" a b "); 424 ASSERT_EQ(2ul, Result.size()); 425 EXPECT_EQ("a", Result[0]); 426 EXPECT_EQ("b", Result[1]); 427 } 428 429 TEST(unescapeJsonCommandLine, UnescapesBackslashCharacters) { 430 std::vector<std::string> Backslash = unescapeJsonCommandLine("a\\\\\\\\"); 431 ASSERT_EQ(1ul, Backslash.size()); 432 EXPECT_EQ("a\\", Backslash[0]); 433 std::vector<std::string> Quote = unescapeJsonCommandLine("a\\\\\\\""); 434 ASSERT_EQ(1ul, Quote.size()); 435 EXPECT_EQ("a\"", Quote[0]); 436 } 437 438 TEST(unescapeJsonCommandLine, DoesNotMungeSpacesBetweenQuotes) { 439 std::vector<std::string> Result = unescapeJsonCommandLine("\\\" a b \\\""); 440 ASSERT_EQ(1ul, Result.size()); 441 EXPECT_EQ(" a b ", Result[0]); 442 } 443 444 TEST(unescapeJsonCommandLine, AllowsMultipleQuotedArguments) { 445 std::vector<std::string> Result = unescapeJsonCommandLine( 446 " \\\" a \\\" \\\" b \\\" "); 447 ASSERT_EQ(2ul, Result.size()); 448 EXPECT_EQ(" a ", Result[0]); 449 EXPECT_EQ(" b ", Result[1]); 450 } 451 452 TEST(unescapeJsonCommandLine, AllowsEmptyArgumentsInQuotes) { 453 std::vector<std::string> Result = unescapeJsonCommandLine( 454 "\\\"\\\"\\\"\\\""); 455 ASSERT_EQ(1ul, Result.size()); 456 EXPECT_TRUE(Result[0].empty()) << Result[0]; 457 } 458 459 TEST(unescapeJsonCommandLine, ParsesEscapedQuotesInQuotedStrings) { 460 std::vector<std::string> Result = unescapeJsonCommandLine( 461 "\\\"\\\\\\\"\\\""); 462 ASSERT_EQ(1ul, Result.size()); 463 EXPECT_EQ("\"", Result[0]); 464 } 465 466 TEST(unescapeJsonCommandLine, ParsesMultipleArgumentsWithEscapedCharacters) { 467 std::vector<std::string> Result = unescapeJsonCommandLine( 468 " \\\\\\\" \\\"a \\\\\\\" b \\\" \\\"and\\\\\\\\c\\\" \\\\\\\""); 469 ASSERT_EQ(4ul, Result.size()); 470 EXPECT_EQ("\"", Result[0]); 471 EXPECT_EQ("a \" b ", Result[1]); 472 EXPECT_EQ("and\\c", Result[2]); 473 EXPECT_EQ("\"", Result[3]); 474 } 475 476 TEST(unescapeJsonCommandLine, ParsesStringsWithoutSpacesIntoSingleArgument) { 477 std::vector<std::string> QuotedNoSpaces = unescapeJsonCommandLine( 478 "\\\"a\\\"\\\"b\\\""); 479 ASSERT_EQ(1ul, QuotedNoSpaces.size()); 480 EXPECT_EQ("ab", QuotedNoSpaces[0]); 481 482 std::vector<std::string> MixedNoSpaces = unescapeJsonCommandLine( 483 "\\\"a\\\"bcd\\\"ef\\\"\\\"\\\"\\\"g\\\""); 484 ASSERT_EQ(1ul, MixedNoSpaces.size()); 485 EXPECT_EQ("abcdefg", MixedNoSpaces[0]); 486 } 487 488 TEST(unescapeJsonCommandLine, ParsesQuotedStringWithoutClosingQuote) { 489 std::vector<std::string> Unclosed = unescapeJsonCommandLine("\\\"abc"); 490 ASSERT_EQ(1ul, Unclosed.size()); 491 EXPECT_EQ("abc", Unclosed[0]); 492 493 std::vector<std::string> Empty = unescapeJsonCommandLine("\\\""); 494 ASSERT_EQ(1ul, Empty.size()); 495 EXPECT_EQ("", Empty[0]); 496 } 497 498 TEST(unescapeJsonCommandLine, ParsesSingleQuotedString) { 499 std::vector<std::string> Args = unescapeJsonCommandLine("a'\\\\b \\\"c\\\"'"); 500 ASSERT_EQ(1ul, Args.size()); 501 EXPECT_EQ("a\\b \"c\"", Args[0]); 502 } 503 504 TEST(FixedCompilationDatabase, ReturnsFixedCommandLine) { 505 FixedCompilationDatabase Database(".", /*CommandLine*/ {"one", "two"}); 506 StringRef FileName("source"); 507 std::vector<CompileCommand> Result = 508 Database.getCompileCommands(FileName); 509 ASSERT_EQ(1ul, Result.size()); 510 EXPECT_EQ(".", Result[0].Directory); 511 EXPECT_EQ(FileName, Result[0].Filename); 512 EXPECT_THAT(Result[0].CommandLine, 513 ElementsAre(EndsWith("clang-tool"), "one", "two", "source")); 514 } 515 516 TEST(FixedCompilationDatabase, GetAllFiles) { 517 std::vector<std::string> CommandLine; 518 CommandLine.push_back("one"); 519 CommandLine.push_back("two"); 520 FixedCompilationDatabase Database(".", CommandLine); 521 522 EXPECT_EQ(0ul, Database.getAllFiles().size()); 523 } 524 525 TEST(FixedCompilationDatabase, GetAllCompileCommands) { 526 std::vector<std::string> CommandLine; 527 CommandLine.push_back("one"); 528 CommandLine.push_back("two"); 529 FixedCompilationDatabase Database(".", CommandLine); 530 531 EXPECT_EQ(0ul, Database.getAllCompileCommands().size()); 532 } 533 534 TEST(ParseFixedCompilationDatabase, ReturnsNullOnEmptyArgumentList) { 535 int Argc = 0; 536 std::string ErrorMsg; 537 std::unique_ptr<FixedCompilationDatabase> Database = 538 FixedCompilationDatabase::loadFromCommandLine(Argc, nullptr, ErrorMsg); 539 EXPECT_FALSE(Database); 540 EXPECT_TRUE(ErrorMsg.empty()); 541 EXPECT_EQ(0, Argc); 542 } 543 544 TEST(ParseFixedCompilationDatabase, ReturnsNullWithoutDoubleDash) { 545 int Argc = 2; 546 const char *Argv[] = { "1", "2" }; 547 std::string ErrorMsg; 548 std::unique_ptr<FixedCompilationDatabase> Database( 549 FixedCompilationDatabase::loadFromCommandLine(Argc, Argv, ErrorMsg)); 550 EXPECT_FALSE(Database); 551 EXPECT_TRUE(ErrorMsg.empty()); 552 EXPECT_EQ(2, Argc); 553 } 554 555 TEST(ParseFixedCompilationDatabase, ReturnsArgumentsAfterDoubleDash) { 556 int Argc = 5; 557 const char *Argv[] = { 558 "1", "2", "--\0no-constant-folding", "-DDEF3", "-DDEF4" 559 }; 560 std::string ErrorMsg; 561 std::unique_ptr<FixedCompilationDatabase> Database( 562 FixedCompilationDatabase::loadFromCommandLine(Argc, Argv, ErrorMsg)); 563 ASSERT_TRUE((bool)Database); 564 ASSERT_TRUE(ErrorMsg.empty()); 565 std::vector<CompileCommand> Result = 566 Database->getCompileCommands("source"); 567 ASSERT_EQ(1ul, Result.size()); 568 ASSERT_EQ(".", Result[0].Directory); 569 ASSERT_THAT(Result[0].CommandLine, ElementsAre(EndsWith("clang-tool"), 570 "-DDEF3", "-DDEF4", "source")); 571 EXPECT_EQ(2, Argc); 572 } 573 574 TEST(ParseFixedCompilationDatabase, ReturnsEmptyCommandLine) { 575 int Argc = 3; 576 const char *Argv[] = { "1", "2", "--\0no-constant-folding" }; 577 std::string ErrorMsg; 578 std::unique_ptr<FixedCompilationDatabase> Database = 579 FixedCompilationDatabase::loadFromCommandLine(Argc, Argv, ErrorMsg); 580 ASSERT_TRUE((bool)Database); 581 ASSERT_TRUE(ErrorMsg.empty()); 582 std::vector<CompileCommand> Result = 583 Database->getCompileCommands("source"); 584 ASSERT_EQ(1ul, Result.size()); 585 ASSERT_EQ(".", Result[0].Directory); 586 ASSERT_THAT(Result[0].CommandLine, 587 ElementsAre(EndsWith("clang-tool"), "source")); 588 EXPECT_EQ(2, Argc); 589 } 590 591 TEST(ParseFixedCompilationDatabase, HandlesPositionalArgs) { 592 const char *Argv[] = {"1", "2", "--", "-c", "somefile.cpp", "-DDEF3"}; 593 int Argc = sizeof(Argv) / sizeof(char*); 594 std::string ErrorMsg; 595 std::unique_ptr<FixedCompilationDatabase> Database = 596 FixedCompilationDatabase::loadFromCommandLine(Argc, Argv, ErrorMsg); 597 ASSERT_TRUE((bool)Database); 598 ASSERT_TRUE(ErrorMsg.empty()); 599 std::vector<CompileCommand> Result = 600 Database->getCompileCommands("source"); 601 ASSERT_EQ(1ul, Result.size()); 602 ASSERT_EQ(".", Result[0].Directory); 603 ASSERT_THAT(Result[0].CommandLine, 604 ElementsAre(EndsWith("clang-tool"), "-c", "-DDEF3", "source")); 605 EXPECT_EQ(2, Argc); 606 } 607 608 TEST(ParseFixedCompilationDatabase, HandlesPositionalArgsSyntaxOnly) { 609 // Adjust the given command line arguments to ensure that any positional 610 // arguments in them are stripped. 611 const char *Argv[] = {"--", "somefile.cpp", "-fsyntax-only", "-DDEF3"}; 612 int Argc = llvm::array_lengthof(Argv); 613 std::string ErrorMessage; 614 std::unique_ptr<CompilationDatabase> Database = 615 FixedCompilationDatabase::loadFromCommandLine(Argc, Argv, ErrorMessage); 616 ASSERT_TRUE((bool)Database); 617 ASSERT_TRUE(ErrorMessage.empty()); 618 std::vector<CompileCommand> Result = Database->getCompileCommands("source"); 619 ASSERT_EQ(1ul, Result.size()); 620 ASSERT_EQ(".", Result[0].Directory); 621 ASSERT_THAT( 622 Result[0].CommandLine, 623 ElementsAre(EndsWith("clang-tool"), "-fsyntax-only", "-DDEF3", "source")); 624 } 625 626 TEST(ParseFixedCompilationDatabase, HandlesArgv0) { 627 const char *Argv[] = {"1", "2", "--", "mytool", "somefile.cpp"}; 628 int Argc = sizeof(Argv) / sizeof(char*); 629 std::string ErrorMsg; 630 std::unique_ptr<FixedCompilationDatabase> Database = 631 FixedCompilationDatabase::loadFromCommandLine(Argc, Argv, ErrorMsg); 632 ASSERT_TRUE((bool)Database); 633 ASSERT_TRUE(ErrorMsg.empty()); 634 std::vector<CompileCommand> Result = 635 Database->getCompileCommands("source"); 636 ASSERT_EQ(1ul, Result.size()); 637 ASSERT_EQ(".", Result[0].Directory); 638 std::vector<std::string> Expected; 639 ASSERT_THAT(Result[0].CommandLine, 640 ElementsAre(EndsWith("clang-tool"), "source")); 641 EXPECT_EQ(2, Argc); 642 } 643 644 struct MemCDB : public CompilationDatabase { 645 using EntryMap = llvm::StringMap<SmallVector<CompileCommand, 1>>; 646 EntryMap Entries; 647 MemCDB(const EntryMap &E) : Entries(E) {} 648 649 std::vector<CompileCommand> getCompileCommands(StringRef F) const override { 650 auto Ret = Entries.lookup(F); 651 return {Ret.begin(), Ret.end()}; 652 } 653 654 std::vector<std::string> getAllFiles() const override { 655 std::vector<std::string> Result; 656 for (const auto &Entry : Entries) 657 Result.push_back(Entry.first()); 658 return Result; 659 } 660 }; 661 662 class MemDBTest : public ::testing::Test { 663 protected: 664 // Adds an entry to the underlying compilation database. 665 // A flag is injected: -D <File>, so the command used can be identified. 666 void add(StringRef File, StringRef Clang, StringRef Flags) { 667 SmallVector<StringRef, 8> Argv = {Clang, File, "-D", File}; 668 llvm::SplitString(Flags, Argv); 669 670 SmallString<32> Dir; 671 llvm::sys::path::system_temp_directory(false, Dir); 672 673 Entries[path(File)].push_back( 674 {Dir, path(File), {Argv.begin(), Argv.end()}, "foo.o"}); 675 } 676 void add(StringRef File, StringRef Flags = "") { add(File, "clang", Flags); } 677 678 // Turn a unix path fragment (foo/bar.h) into a native path (C:\tmp\foo\bar.h) 679 std::string path(llvm::SmallString<32> File) { 680 llvm::SmallString<32> Dir; 681 llvm::sys::path::system_temp_directory(false, Dir); 682 llvm::sys::path::native(File); 683 llvm::SmallString<64> Result; 684 llvm::sys::path::append(Result, Dir, File); 685 return Result.str(); 686 } 687 688 MemCDB::EntryMap Entries; 689 }; 690 691 class InterpolateTest : public MemDBTest { 692 protected: 693 // Look up the command from a relative path, and return it in string form. 694 // The input file is not included in the returned command. 695 std::string getCommand(llvm::StringRef F) { 696 auto Results = 697 inferMissingCompileCommands(std::make_unique<MemCDB>(Entries)) 698 ->getCompileCommands(path(F)); 699 if (Results.empty()) 700 return "none"; 701 // drop the input file argument, so tests don't have to deal with path(). 702 EXPECT_EQ(Results[0].CommandLine.back(), path(F)) 703 << "Last arg should be the file"; 704 Results[0].CommandLine.pop_back(); 705 return llvm::join(Results[0].CommandLine, " "); 706 } 707 708 // Parse the file whose command was used out of the Heuristic string. 709 std::string getProxy(llvm::StringRef F) { 710 auto Results = 711 inferMissingCompileCommands(std::make_unique<MemCDB>(Entries)) 712 ->getCompileCommands(path(F)); 713 if (Results.empty()) 714 return "none"; 715 StringRef Proxy = Results.front().Heuristic; 716 if (!Proxy.consume_front("inferred from ")) 717 return ""; 718 // We have a proxy file, convert back to a unix relative path. 719 // This is a bit messy, but we do need to test these strings somehow... 720 llvm::SmallString<32> TempDir; 721 llvm::sys::path::system_temp_directory(false, TempDir); 722 Proxy.consume_front(TempDir); 723 Proxy.consume_front(llvm::sys::path::get_separator()); 724 llvm::SmallString<32> Result = Proxy; 725 llvm::sys::path::native(Result, llvm::sys::path::Style::posix); 726 return Result.str(); 727 } 728 }; 729 730 TEST_F(InterpolateTest, Nearby) { 731 add("dir/foo.cpp"); 732 add("dir/bar.cpp"); 733 add("an/other/foo.cpp"); 734 735 // great: dir and name both match (prefix or full, case insensitive) 736 EXPECT_EQ(getProxy("dir/f.cpp"), "dir/foo.cpp"); 737 EXPECT_EQ(getProxy("dir/FOO.cpp"), "dir/foo.cpp"); 738 // no name match. prefer matching dir, break ties by alpha 739 EXPECT_EQ(getProxy("dir/a.cpp"), "dir/bar.cpp"); 740 // an exact name match beats one segment of directory match 741 EXPECT_EQ(getProxy("some/other/bar.h"), "dir/bar.cpp"); 742 // two segments of directory match beat a prefix name match 743 EXPECT_EQ(getProxy("an/other/b.cpp"), "an/other/foo.cpp"); 744 // if nothing matches at all, we still get the closest alpha match 745 EXPECT_EQ(getProxy("below/some/obscure/path.cpp"), "an/other/foo.cpp"); 746 } 747 748 TEST_F(InterpolateTest, Language) { 749 add("dir/foo.cpp", "-std=c++17"); 750 add("dir/bar.c", ""); 751 add("dir/baz.cee", "-x c"); 752 753 // .h is ambiguous, so we add explicit language flags 754 EXPECT_EQ(getCommand("foo.h"), 755 "clang -D dir/foo.cpp -x c++-header -std=c++17"); 756 // Same thing if we have no extension. (again, we treat as header). 757 EXPECT_EQ(getCommand("foo"), "clang -D dir/foo.cpp -x c++-header -std=c++17"); 758 // and invalid extensions. 759 EXPECT_EQ(getCommand("foo.cce"), 760 "clang -D dir/foo.cpp -x c++-header -std=c++17"); 761 // and don't add -x if the inferred language is correct. 762 EXPECT_EQ(getCommand("foo.hpp"), "clang -D dir/foo.cpp -std=c++17"); 763 // respect -x if it's already there. 764 EXPECT_EQ(getCommand("baz.h"), "clang -D dir/baz.cee -x c-header"); 765 // prefer a worse match with the right extension. 766 EXPECT_EQ(getCommand("foo.c"), "clang -D dir/bar.c"); 767 Entries.erase(path(StringRef("dir/bar.c"))); 768 // Now we transfer across languages, so drop -std too. 769 EXPECT_EQ(getCommand("foo.c"), "clang -D dir/foo.cpp"); 770 } 771 772 TEST_F(InterpolateTest, Strip) { 773 add("dir/foo.cpp", "-o foo.o -Wall"); 774 // the -o option and the input file are removed, but -Wall is preserved. 775 EXPECT_EQ(getCommand("dir/bar.cpp"), "clang -D dir/foo.cpp -Wall"); 776 } 777 778 TEST_F(InterpolateTest, Case) { 779 add("FOO/BAR/BAZ/SHOUT.cc"); 780 add("foo/bar/baz/quiet.cc"); 781 // Case mismatches are completely ignored, so we choose the name match. 782 EXPECT_EQ(getProxy("foo/bar/baz/shout.C"), "FOO/BAR/BAZ/SHOUT.cc"); 783 } 784 785 TEST_F(InterpolateTest, Aliasing) { 786 add("foo.cpp", "-faligned-new"); 787 788 // The interpolated command should keep the given flag as written, even though 789 // the flag is internally represented as an alias. 790 EXPECT_EQ(getCommand("foo.hpp"), "clang -D foo.cpp -faligned-new"); 791 } 792 793 TEST_F(InterpolateTest, ClangCL) { 794 add("foo.cpp", "clang-cl", "/W4"); 795 796 // Language flags should be added with CL syntax. 797 EXPECT_EQ(getCommand("foo.h"), "clang-cl -D foo.cpp /W4 /TP"); 798 } 799 800 TEST_F(InterpolateTest, DriverModes) { 801 add("foo.cpp", "clang-cl", "--driver-mode=gcc"); 802 add("bar.cpp", "clang", "--driver-mode=cl"); 803 804 // --driver-mode overrides should be respected. 805 EXPECT_EQ(getCommand("foo.h"), "clang-cl -D foo.cpp --driver-mode=gcc -x c++-header"); 806 EXPECT_EQ(getCommand("bar.h"), "clang -D bar.cpp --driver-mode=cl /TP"); 807 } 808 809 TEST(CompileCommandTest, EqualityOperator) { 810 CompileCommand CCRef("/foo/bar", "hello.c", {"a", "b"}, "hello.o"); 811 CompileCommand CCTest = CCRef; 812 813 EXPECT_TRUE(CCRef == CCTest); 814 EXPECT_FALSE(CCRef != CCTest); 815 816 CCTest = CCRef; 817 CCTest.Directory = "/foo/baz"; 818 EXPECT_FALSE(CCRef == CCTest); 819 EXPECT_TRUE(CCRef != CCTest); 820 821 CCTest = CCRef; 822 CCTest.Filename = "bonjour.c"; 823 EXPECT_FALSE(CCRef == CCTest); 824 EXPECT_TRUE(CCRef != CCTest); 825 826 CCTest = CCRef; 827 CCTest.CommandLine.push_back("c"); 828 EXPECT_FALSE(CCRef == CCTest); 829 EXPECT_TRUE(CCRef != CCTest); 830 831 CCTest = CCRef; 832 CCTest.Output = "bonjour.o"; 833 EXPECT_FALSE(CCRef == CCTest); 834 EXPECT_TRUE(CCRef != CCTest); 835 } 836 837 class TargetAndModeTest : public MemDBTest { 838 public: 839 TargetAndModeTest() { llvm::InitializeAllTargetInfos(); } 840 841 protected: 842 // Look up the command from a relative path, and return it in string form. 843 std::string getCommand(llvm::StringRef F) { 844 auto Results = inferTargetAndDriverMode(std::make_unique<MemCDB>(Entries)) 845 ->getCompileCommands(path(F)); 846 if (Results.empty()) 847 return "none"; 848 return llvm::join(Results[0].CommandLine, " "); 849 } 850 }; 851 852 TEST_F(TargetAndModeTest, TargetAndMode) { 853 add("foo.cpp", "clang-cl", ""); 854 add("bar.cpp", "clang++", ""); 855 856 EXPECT_EQ(getCommand("foo.cpp"), 857 "clang-cl --driver-mode=cl foo.cpp -D foo.cpp"); 858 EXPECT_EQ(getCommand("bar.cpp"), 859 "clang++ --driver-mode=g++ bar.cpp -D bar.cpp"); 860 } 861 862 } // end namespace tooling 863 } // end namespace clang 864