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