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