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