1012 lines · cpp
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-exception6//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 19namespace clang {20namespace tooling {21 22using testing::ElementsAre;23using testing::EndsWith;24using testing::IsEmpty;25using testing::UnorderedElementsAreArray;26 27static 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 35TEST(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 55static 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 70static std::vector<CompileCommand>71getAllCompileCommands(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 83TEST(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 127TEST(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 192static 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 209TEST(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 228struct FakeComparator : public PathComparator {229 ~FakeComparator() override {}230 bool equivalent(StringRef FileA, StringRef FileB) const override {231 return FileA.equals_insensitive(FileB);232 }233};234 235class FileMatchTrieTest : public ::testing::Test {236protected: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 248TEST_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 254TEST_F(FileMatchTrieTest, MatchingRelativePath) {255 EXPECT_EQ("", find("file.cc"));256}257 258TEST_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 264TEST_F(FileMatchTrieTest, HandlesSymlinks) {265 Trie.insert("//net/AA/file.cc");266 EXPECT_EQ("//net/AA/file.cc", find("//net/aa/file.cc"));267}268 269TEST_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 276TEST_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 283TEST_F(FileMatchTrieTest, EmptyTrie) {284 EXPECT_TRUE(find("//net/some/path").empty());285}286 287TEST_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 293TEST_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 299TEST_F(FileMatchTrieTest, CannotResolveRelativePath) {300 EXPECT_EQ("", find("relative-path.cc"));301 EXPECT_EQ("Cannot resolve relative paths", Error);302}303 304TEST_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 case308 // which we hit at previous `find()`.309 Trie.insert("/root/otherpath/OtherFile.cc");310 EXPECT_EQ("", find("/root/rootfile.cc"));311}312 313TEST(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 321TEST(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 350TEST(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 367TEST(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 381TEST(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 402TEST(findCompileArgsInJsonDatabase, ParsesCompilerWrappers) {403 std::vector<std::pair<std::string, std::string>> Cases = {404 {"distcc gcc foo.c", "gcc foo.c"},405 {"sccache clang++ foo.c", "clang++ foo.c"},406 {"ccache gcc foo.c", "gcc foo.c"},407 {"ccache.exe gcc foo.c", "gcc foo.c"},408 {"ccache g++.exe foo.c", "g++.exe foo.c"},409 {"ccache distcc gcc foo.c", "gcc foo.c"},410 411 {"distcc foo.c", "distcc foo.c"},412 {"distcc -I/foo/bar foo.c", "distcc -I/foo/bar foo.c"},413 };414 std::string ErrorMessage;415 416 for (const auto &Case : Cases) {417 std::string DB =418 R"([{"directory":"//net/dir", "file":"//net/dir/foo.c", "command":")" +419 Case.first + "\"}]";420 CompileCommand FoundCommand =421 findCompileArgsInJsonDatabase("//net/dir/foo.c", DB, ErrorMessage);422 EXPECT_EQ(Case.second, llvm::join(FoundCommand.CommandLine, " "))423 << Case.first;424 }425}426 427static std::vector<std::string> unescapeJsonCommandLine(StringRef Command) {428 std::string JsonDatabase =429 ("[{\"directory\":\"//net/root\", \"file\":\"test\", \"command\": \"" +430 Command + "\"}]").str();431 std::string ErrorMessage;432 CompileCommand FoundCommand = findCompileArgsInJsonDatabase(433 "//net/root/test", JsonDatabase, ErrorMessage);434 EXPECT_TRUE(ErrorMessage.empty()) << ErrorMessage;435 return FoundCommand.CommandLine;436}437 438TEST(unescapeJsonCommandLine, ReturnsEmptyArrayOnEmptyString) {439 std::vector<std::string> Result = unescapeJsonCommandLine("");440 EXPECT_TRUE(Result.empty());441}442 443TEST(unescapeJsonCommandLine, SplitsOnSpaces) {444 std::vector<std::string> Result = unescapeJsonCommandLine("a b c");445 ASSERT_EQ(3ul, Result.size());446 EXPECT_EQ("a", Result[0]);447 EXPECT_EQ("b", Result[1]);448 EXPECT_EQ("c", Result[2]);449}450 451TEST(unescapeJsonCommandLine, MungesMultipleSpaces) {452 std::vector<std::string> Result = unescapeJsonCommandLine(" a b ");453 ASSERT_EQ(2ul, Result.size());454 EXPECT_EQ("a", Result[0]);455 EXPECT_EQ("b", Result[1]);456}457 458TEST(unescapeJsonCommandLine, UnescapesBackslashCharacters) {459 std::vector<std::string> Backslash = unescapeJsonCommandLine("a\\\\\\\\");460 ASSERT_EQ(1ul, Backslash.size());461 EXPECT_EQ("a\\", Backslash[0]);462 std::vector<std::string> Quote = unescapeJsonCommandLine("a\\\\\\\"");463 ASSERT_EQ(1ul, Quote.size());464 EXPECT_EQ("a\"", Quote[0]);465}466 467TEST(unescapeJsonCommandLine, DoesNotMungeSpacesBetweenQuotes) {468 std::vector<std::string> Result = unescapeJsonCommandLine("\\\" a b \\\"");469 ASSERT_EQ(1ul, Result.size());470 EXPECT_EQ(" a b ", Result[0]);471}472 473TEST(unescapeJsonCommandLine, AllowsMultipleQuotedArguments) {474 std::vector<std::string> Result = unescapeJsonCommandLine(475 " \\\" a \\\" \\\" b \\\" ");476 ASSERT_EQ(2ul, Result.size());477 EXPECT_EQ(" a ", Result[0]);478 EXPECT_EQ(" b ", Result[1]);479}480 481TEST(unescapeJsonCommandLine, AllowsEmptyArgumentsInQuotes) {482 std::vector<std::string> Result = unescapeJsonCommandLine(483 "\\\"\\\"\\\"\\\"");484 ASSERT_EQ(1ul, Result.size());485 EXPECT_TRUE(Result[0].empty()) << Result[0];486}487 488TEST(unescapeJsonCommandLine, ParsesEscapedQuotesInQuotedStrings) {489 std::vector<std::string> Result = unescapeJsonCommandLine(490 "\\\"\\\\\\\"\\\"");491 ASSERT_EQ(1ul, Result.size());492 EXPECT_EQ("\"", Result[0]);493}494 495TEST(unescapeJsonCommandLine, ParsesMultipleArgumentsWithEscapedCharacters) {496 std::vector<std::string> Result = unescapeJsonCommandLine(497 " \\\\\\\" \\\"a \\\\\\\" b \\\" \\\"and\\\\\\\\c\\\" \\\\\\\"");498 ASSERT_EQ(4ul, Result.size());499 EXPECT_EQ("\"", Result[0]);500 EXPECT_EQ("a \" b ", Result[1]);501 EXPECT_EQ("and\\c", Result[2]);502 EXPECT_EQ("\"", Result[3]);503}504 505TEST(unescapeJsonCommandLine, ParsesStringsWithoutSpacesIntoSingleArgument) {506 std::vector<std::string> QuotedNoSpaces = unescapeJsonCommandLine(507 "\\\"a\\\"\\\"b\\\"");508 ASSERT_EQ(1ul, QuotedNoSpaces.size());509 EXPECT_EQ("ab", QuotedNoSpaces[0]);510 511 std::vector<std::string> MixedNoSpaces = unescapeJsonCommandLine(512 "\\\"a\\\"bcd\\\"ef\\\"\\\"\\\"\\\"g\\\"");513 ASSERT_EQ(1ul, MixedNoSpaces.size());514 EXPECT_EQ("abcdefg", MixedNoSpaces[0]);515}516 517TEST(unescapeJsonCommandLine, ParsesQuotedStringWithoutClosingQuote) {518 std::vector<std::string> Unclosed = unescapeJsonCommandLine("\\\"abc");519 ASSERT_EQ(1ul, Unclosed.size());520 EXPECT_EQ("abc", Unclosed[0]);521 522 std::vector<std::string> Empty = unescapeJsonCommandLine("\\\"");523 ASSERT_EQ(1ul, Empty.size());524 EXPECT_EQ("", Empty[0]);525}526 527TEST(unescapeJsonCommandLine, ParsesSingleQuotedString) {528 std::vector<std::string> Args = unescapeJsonCommandLine("a'\\\\b \\\"c\\\"'");529 ASSERT_EQ(1ul, Args.size());530 EXPECT_EQ("a\\b \"c\"", Args[0]);531}532 533TEST(FixedCompilationDatabase, ReturnsFixedCommandLine) {534 FixedCompilationDatabase Database(".", /*CommandLine*/ {"one", "two"});535 StringRef FileName("source");536 std::vector<CompileCommand> Result =537 Database.getCompileCommands(FileName);538 ASSERT_EQ(1ul, Result.size());539 EXPECT_EQ(".", Result[0].Directory);540 EXPECT_EQ(FileName, Result[0].Filename);541 EXPECT_THAT(Result[0].CommandLine,542 ElementsAre(EndsWith("clang-tool"), "one", "two", "source"));543}544 545TEST(FixedCompilationDatabase, GetAllFiles) {546 std::vector<std::string> CommandLine;547 CommandLine.push_back("one");548 CommandLine.push_back("two");549 FixedCompilationDatabase Database(".", CommandLine);550 551 EXPECT_THAT(Database.getAllFiles(), IsEmpty());552}553 554TEST(FixedCompilationDatabase, GetAllCompileCommands) {555 std::vector<std::string> CommandLine;556 CommandLine.push_back("one");557 CommandLine.push_back("two");558 FixedCompilationDatabase Database(".", CommandLine);559 560 EXPECT_EQ(0ul, Database.getAllCompileCommands().size());561}562 563TEST(FixedCompilationDatabase, FromBuffer) {564 const char *Data = R"(565 566 -DFOO=BAR567 568--baz569 570 )";571 std::string ErrorMsg;572 auto CDB =573 FixedCompilationDatabase::loadFromBuffer("/cdb/dir", Data, ErrorMsg);574 575 std::vector<CompileCommand> Result = CDB->getCompileCommands("/foo/bar.cc");576 ASSERT_EQ(1ul, Result.size());577 EXPECT_EQ("/cdb/dir", Result.front().Directory);578 EXPECT_EQ("/foo/bar.cc", Result.front().Filename);579 EXPECT_THAT(580 Result.front().CommandLine,581 ElementsAre(EndsWith("clang-tool"), "-DFOO=BAR", "--baz", "/foo/bar.cc"));582}583 584TEST(ParseFixedCompilationDatabase, ReturnsNullOnEmptyArgumentList) {585 int Argc = 0;586 std::string ErrorMsg;587 std::unique_ptr<FixedCompilationDatabase> Database =588 FixedCompilationDatabase::loadFromCommandLine(Argc, nullptr, ErrorMsg);589 EXPECT_FALSE(Database);590 EXPECT_TRUE(ErrorMsg.empty());591 EXPECT_EQ(0, Argc);592}593 594TEST(ParseFixedCompilationDatabase, ReturnsNullWithoutDoubleDash) {595 int Argc = 2;596 const char *Argv[] = { "1", "2" };597 std::string ErrorMsg;598 std::unique_ptr<FixedCompilationDatabase> Database(599 FixedCompilationDatabase::loadFromCommandLine(Argc, Argv, ErrorMsg));600 EXPECT_FALSE(Database);601 EXPECT_TRUE(ErrorMsg.empty());602 EXPECT_EQ(2, Argc);603}604 605TEST(ParseFixedCompilationDatabase, ReturnsArgumentsAfterDoubleDash) {606 int Argc = 5;607 const char *Argv[] = {608 "1", "2", "--\0no-constant-folding", "-DDEF3", "-DDEF4"609 };610 std::string ErrorMsg;611 std::unique_ptr<FixedCompilationDatabase> Database(612 FixedCompilationDatabase::loadFromCommandLine(Argc, Argv, ErrorMsg));613 ASSERT_TRUE((bool)Database);614 ASSERT_TRUE(ErrorMsg.empty());615 std::vector<CompileCommand> Result =616 Database->getCompileCommands("source");617 ASSERT_EQ(1ul, Result.size());618 ASSERT_EQ(".", Result[0].Directory);619 ASSERT_THAT(Result[0].CommandLine, ElementsAre(EndsWith("clang-tool"),620 "-DDEF3", "-DDEF4", "source"));621 EXPECT_EQ(2, Argc);622}623 624TEST(ParseFixedCompilationDatabase, ReturnsEmptyCommandLine) {625 int Argc = 3;626 const char *Argv[] = { "1", "2", "--\0no-constant-folding" };627 std::string ErrorMsg;628 std::unique_ptr<FixedCompilationDatabase> Database =629 FixedCompilationDatabase::loadFromCommandLine(Argc, Argv, ErrorMsg);630 ASSERT_TRUE((bool)Database);631 ASSERT_TRUE(ErrorMsg.empty());632 std::vector<CompileCommand> Result =633 Database->getCompileCommands("source");634 ASSERT_EQ(1ul, Result.size());635 ASSERT_EQ(".", Result[0].Directory);636 ASSERT_THAT(Result[0].CommandLine,637 ElementsAre(EndsWith("clang-tool"), "source"));638 EXPECT_EQ(2, Argc);639}640 641TEST(ParseFixedCompilationDatabase, HandlesPositionalArgs) {642 const char *Argv[] = {"1", "2", "--", "-c", "somefile.cpp", "-DDEF3"};643 int Argc = std::size(Argv);644 std::string ErrorMsg;645 std::unique_ptr<FixedCompilationDatabase> Database =646 FixedCompilationDatabase::loadFromCommandLine(Argc, Argv, ErrorMsg);647 ASSERT_TRUE((bool)Database);648 ASSERT_TRUE(ErrorMsg.empty());649 std::vector<CompileCommand> Result = Database->getCompileCommands("source");650 ASSERT_EQ(1ul, Result.size());651 ASSERT_EQ(".", Result[0].Directory);652 ASSERT_THAT(Result[0].CommandLine,653 ElementsAre(EndsWith("clang-tool"), "-c", "-DDEF3", "source"));654 EXPECT_EQ(2, Argc);655}656 657TEST(ParseFixedCompilationDatabase, HandlesPositionalArgsHeader) {658 const char *Argv[] = {"1", "2", "--", "-xc++-header",659 "-c", "somefile.h", "-DDEF3"};660 int Argc = std::size(Argv);661 std::string ErrorMsg;662 std::unique_ptr<FixedCompilationDatabase> Database =663 FixedCompilationDatabase::loadFromCommandLine(Argc, Argv, ErrorMsg);664 ASSERT_TRUE((bool)Database);665 ASSERT_TRUE(ErrorMsg.empty());666 std::vector<CompileCommand> Result =667 Database->getCompileCommands("source");668 ASSERT_EQ(1ul, Result.size());669 ASSERT_EQ(".", Result[0].Directory);670 ASSERT_THAT(Result[0].CommandLine,671 ElementsAre(EndsWith("clang-tool"), "-xc++-header", "-c",672 "-DDEF3", "source"));673 EXPECT_EQ(2, Argc);674}675 676TEST(ParseFixedCompilationDatabase, HandlesPositionalArgsSyntaxOnly) {677 // Adjust the given command line arguments to ensure that any positional678 // arguments in them are stripped.679 const char *Argv[] = {"--", "somefile.cpp", "-fsyntax-only", "-DDEF3"};680 int Argc = std::size(Argv);681 std::string ErrorMessage;682 std::unique_ptr<CompilationDatabase> Database =683 FixedCompilationDatabase::loadFromCommandLine(Argc, Argv, ErrorMessage);684 ASSERT_TRUE((bool)Database);685 ASSERT_TRUE(ErrorMessage.empty());686 std::vector<CompileCommand> Result = Database->getCompileCommands("source");687 ASSERT_EQ(1ul, Result.size());688 ASSERT_EQ(".", Result[0].Directory);689 ASSERT_THAT(690 Result[0].CommandLine,691 ElementsAre(EndsWith("clang-tool"), "-fsyntax-only", "-DDEF3", "source"));692}693 694TEST(ParseFixedCompilationDatabase, HandlesArgv0) {695 const char *Argv[] = {"1", "2", "--", "mytool", "somefile.cpp"};696 int Argc = std::size(Argv);697 std::string ErrorMsg;698 std::unique_ptr<FixedCompilationDatabase> Database =699 FixedCompilationDatabase::loadFromCommandLine(Argc, Argv, ErrorMsg);700 ASSERT_TRUE((bool)Database);701 ASSERT_TRUE(ErrorMsg.empty());702 std::vector<CompileCommand> Result =703 Database->getCompileCommands("source");704 ASSERT_EQ(1ul, Result.size());705 ASSERT_EQ(".", Result[0].Directory);706 ASSERT_THAT(Result[0].CommandLine,707 ElementsAre(EndsWith("clang-tool"), "source"));708 EXPECT_EQ(2, Argc);709}710 711struct MemCDB : public CompilationDatabase {712 using EntryMap = llvm::StringMap<SmallVector<CompileCommand, 1>>;713 EntryMap Entries;714 MemCDB(const EntryMap &E) : Entries(E) {}715 716 std::vector<CompileCommand> getCompileCommands(StringRef F) const override {717 auto Ret = Entries.lookup(F);718 return {Ret.begin(), Ret.end()};719 }720 721 std::vector<std::string> getAllFiles() const override {722 std::vector<std::string> Result;723 for (const auto &Entry : Entries)724 Result.push_back(std::string(Entry.first()));725 return Result;726 }727};728 729class MemDBTest : public ::testing::Test {730protected:731 // Adds an entry to the underlying compilation database.732 // A flag is injected: -D <File>, so the command used can be identified.733 void add(StringRef File, StringRef Clang, StringRef Flags) {734 SmallVector<StringRef, 8> Argv = {Clang, File, "-D", File};735 llvm::SplitString(Flags, Argv);736 737 // Trim double quotation from the argumnets if any.738 for (auto *It = Argv.begin(); It != Argv.end(); ++It)739 *It = It->trim("\"");740 741 SmallString<32> Dir;742 llvm::sys::path::system_temp_directory(false, Dir);743 744 Entries[path(File)].push_back(745 {Dir, path(File), {Argv.begin(), Argv.end()}, "foo.o"});746 }747 void add(StringRef File, StringRef Flags = "") { add(File, "clang", Flags); }748 749 // Turn a unix path fragment (foo/bar.h) into a native path (C:\tmp\foo\bar.h)750 std::string path(llvm::SmallString<32> File) {751 llvm::SmallString<32> Dir;752 llvm::sys::path::system_temp_directory(false, Dir);753 llvm::sys::path::native(File);754 llvm::SmallString<64> Result;755 llvm::sys::path::append(Result, Dir, File);756 return std::string(Result.str());757 }758 759 MemCDB::EntryMap Entries;760};761 762class InterpolateTest : public MemDBTest {763protected:764 // Look up the command from a relative path, and return it in string form.765 // The input file is not included in the returned command.766 std::string getCommand(llvm::StringRef F, bool MakeNative = true) {767 auto Results =768 inferMissingCompileCommands(std::make_unique<MemCDB>(Entries))769 ->getCompileCommands(MakeNative ? path(F) : F);770 if (Results.empty())771 return "none";772 // drop the input file argument, so tests don't have to deal with path().773 EXPECT_EQ(Results[0].CommandLine.back(), MakeNative ? path(F) : F)774 << "Last arg should be the file";775 Results[0].CommandLine.pop_back();776 EXPECT_EQ(Results[0].CommandLine.back(), "--")777 << "Second-last arg should be --";778 Results[0].CommandLine.pop_back();779 return llvm::join(Results[0].CommandLine, " ");780 }781 782 // Parse the file whose command was used out of the Heuristic string.783 std::string getProxy(llvm::StringRef F) {784 auto Results =785 inferMissingCompileCommands(std::make_unique<MemCDB>(Entries))786 ->getCompileCommands(path(F));787 if (Results.empty())788 return "none";789 StringRef Proxy = Results.front().Heuristic;790 if (!Proxy.consume_front("inferred from "))791 return "";792 // We have a proxy file, convert back to a unix relative path.793 // This is a bit messy, but we do need to test these strings somehow...794 llvm::SmallString<32> TempDir;795 llvm::sys::path::system_temp_directory(false, TempDir);796 Proxy.consume_front(TempDir);797 Proxy.consume_front(llvm::sys::path::get_separator());798 llvm::SmallString<32> Result = Proxy;799 llvm::sys::path::native(Result, llvm::sys::path::Style::posix);800 return std::string(Result.str());801 }802};803 804TEST_F(InterpolateTest, Nearby) {805 add("dir/foo.cpp");806 add("dir/bar.cpp");807 add("an/other/foo.cpp");808 809 // great: dir and name both match (prefix or full, case insensitive)810 EXPECT_EQ(getProxy("dir/f.cpp"), "dir/foo.cpp");811 EXPECT_EQ(getProxy("dir/FOO.cpp"), "dir/foo.cpp");812 // no name match. prefer matching dir, break ties by alpha813 EXPECT_EQ(getProxy("dir/a.cpp"), "dir/bar.cpp");814 // an exact name match beats one segment of directory match815 EXPECT_EQ(getProxy("some/other/bar.h"), "dir/bar.cpp");816 // two segments of directory match beat a prefix name match817 EXPECT_EQ(getProxy("an/other/b.cpp"), "an/other/foo.cpp");818 // if nothing matches at all, we still get the closest alpha match819 EXPECT_EQ(getProxy("below/some/obscure/path.cpp"), "an/other/foo.cpp");820}821 822TEST_F(InterpolateTest, Language) {823 add("dir/foo.cpp", "-std=c++17");824 add("dir/bar.c", "");825 add("dir/baz.cee", "-x c");826 add("dir/aux.cpp", "-std=c++17 -x objective-c++");827 828 // .h is ambiguous, so we add explicit language flags829 EXPECT_EQ(getCommand("foo.h"),830 "clang -D dir/foo.cpp -x c++-header -std=c++17");831 // Same thing if we have no extension. (again, we treat as header).832 EXPECT_EQ(getCommand("foo"), "clang -D dir/foo.cpp -x c++-header -std=c++17");833 // and invalid extensions.834 EXPECT_EQ(getCommand("foo.cce"),835 "clang -D dir/foo.cpp -x c++-header -std=c++17");836 // and don't add -x if the inferred language is correct.837 EXPECT_EQ(getCommand("foo.hpp"), "clang -D dir/foo.cpp -std=c++17");838 // respect -x if it's already there.839 EXPECT_EQ(getCommand("baz.h"), "clang -D dir/baz.cee -x c-header");840 // prefer a worse match with the right extension.841 EXPECT_EQ(getCommand("foo.c"), "clang -D dir/bar.c");842 Entries.erase(path(StringRef("dir/bar.c")));843 // Now we transfer across languages, so drop -std too.844 EXPECT_EQ(getCommand("foo.c"), "clang -D dir/foo.cpp");845 // Prefer -x over -std when overriding language.846 EXPECT_EQ(getCommand("aux.h"),847 "clang -D dir/aux.cpp -x objective-c++-header -std=c++17");848}849 850TEST_F(InterpolateTest, Strip) {851 add("dir/foo.cpp", "-o foo.o -Wall");852 // the -o option and the input file are removed, but -Wall is preserved.853 EXPECT_EQ(getCommand("dir/bar.cpp"), "clang -D dir/foo.cpp -Wall");854}855 856TEST_F(InterpolateTest, StripDoubleDash) {857 add("dir/foo.cpp", "-o foo.o -std=c++14 -Wall -- dir/foo.cpp");858 // input file and output option are removed859 // -Wall flag isn't860 // -std option gets re-added as the last argument before the input file861 // -- is removed as it's not necessary - the new input file doesn't start with862 // a dash863 EXPECT_EQ(getCommand("dir/bar.cpp"), "clang -D dir/foo.cpp -Wall -std=c++14");864}865 866TEST_F(InterpolateTest, Case) {867 add("FOO/BAR/BAZ/SHOUT.cc");868 add("foo/bar/baz/quiet.cc");869 // Case mismatches are completely ignored, so we choose the name match.870 EXPECT_EQ(getProxy("foo/bar/baz/shout.C"), "FOO/BAR/BAZ/SHOUT.cc");871}872 873TEST_F(InterpolateTest, LanguagePreference) {874 add("foo/bar/baz/exact.C");875 add("foo/bar/baz/exact.c");876 add("other/random/path.cpp");877 // Proxies for ".H" files are ".C" files, and not ".c files".878 EXPECT_EQ(getProxy("foo/bar/baz/exact.H"), "foo/bar/baz/exact.C");879}880 881TEST_F(InterpolateTest, Aliasing) {882 add("foo.cpp", "-faligned-new");883 884 // The interpolated command should keep the given flag as written, even though885 // the flag is internally represented as an alias.886 EXPECT_EQ(getCommand("foo.hpp"), "clang -D foo.cpp -faligned-new");887}888 889TEST_F(InterpolateTest, ClangCL) {890 add("foo.cpp", "clang-cl", "/W4");891 892 // Language flags should be added with CL syntax.893 EXPECT_EQ(getCommand("foo.h", false), "clang-cl -D foo.cpp /W4 /TP");894}895 896TEST_F(InterpolateTest, DriverModes) {897 add("foo.cpp", "clang-cl", "--driver-mode=gcc");898 add("bar.cpp", "clang", "--driver-mode=cl");899 900 // --driver-mode overrides should be respected.901 EXPECT_EQ(getCommand("foo.h"),902 "clang-cl -D foo.cpp --driver-mode=gcc -x c++-header");903 EXPECT_EQ(getCommand("bar.h", false),904 "clang -D bar.cpp --driver-mode=cl /TP");905}906 907TEST(TransferCompileCommandTest, Smoke) {908 CompileCommand Cmd;909 Cmd.Filename = "foo.cc";910 Cmd.CommandLine = {"clang", "-Wall", "foo.cc"};911 Cmd.Directory = "dir";912 CompileCommand Transferred = transferCompileCommand(std::move(Cmd), "foo.h");913 EXPECT_EQ(Transferred.Filename, "foo.h");914 EXPECT_THAT(Transferred.CommandLine,915 ElementsAre("clang", "-Wall", "-x", "c++-header", "--", "foo.h"));916 EXPECT_EQ(Transferred.Directory, "dir");917}918 919TEST(CompileCommandTest, EqualityOperator) {920 CompileCommand CCRef("/foo/bar", "hello.c", {"a", "b"}, "hello.o");921 CompileCommand CCTest = CCRef;922 923 EXPECT_TRUE(CCRef == CCTest);924 EXPECT_FALSE(CCRef != CCTest);925 926 CCTest = CCRef;927 CCTest.Directory = "/foo/baz";928 EXPECT_FALSE(CCRef == CCTest);929 EXPECT_TRUE(CCRef != CCTest);930 931 CCTest = CCRef;932 CCTest.Filename = "bonjour.c";933 EXPECT_FALSE(CCRef == CCTest);934 EXPECT_TRUE(CCRef != CCTest);935 936 CCTest = CCRef;937 CCTest.CommandLine.push_back("c");938 EXPECT_FALSE(CCRef == CCTest);939 EXPECT_TRUE(CCRef != CCTest);940 941 CCTest = CCRef;942 CCTest.Output = "bonjour.o";943 EXPECT_FALSE(CCRef == CCTest);944 EXPECT_TRUE(CCRef != CCTest);945}946 947class TargetAndModeTest : public MemDBTest {948public:949 TargetAndModeTest() { llvm::InitializeAllTargetInfos(); }950 951protected:952 // Look up the command from a relative path, and return it in string form.953 std::string getCommand(llvm::StringRef F) {954 auto Results = inferTargetAndDriverMode(std::make_unique<MemCDB>(Entries))955 ->getCompileCommands(path(F));956 if (Results.empty())957 return "none";958 return llvm::join(Results[0].CommandLine, " ");959 }960};961 962TEST_F(TargetAndModeTest, TargetAndMode) {963 add("foo.cpp", "clang-cl", "");964 add("bar.cpp", "clang++", "");965 966 EXPECT_EQ(getCommand("foo.cpp"),967 "clang-cl --driver-mode=cl foo.cpp -D foo.cpp");968 EXPECT_EQ(getCommand("bar.cpp"),969 "clang++ --driver-mode=g++ bar.cpp -D bar.cpp");970}971 972class ExpandResponseFilesTest : public MemDBTest {973public:974 ExpandResponseFilesTest()975 : FS(llvm::makeIntrusiveRefCnt<llvm::vfs::InMemoryFileSystem>()) {}976 977protected:978 void addFile(StringRef File, StringRef Content) {979 ASSERT_TRUE(980 FS->addFile(File, 0, llvm::MemoryBuffer::getMemBufferCopy(Content)));981 }982 983 std::string getCommand(llvm::StringRef F) {984 auto Results = expandResponseFiles(std::make_unique<MemCDB>(Entries), FS)985 ->getCompileCommands(path(F));986 if (Results.empty())987 return "none";988 return llvm::join(Results[0].CommandLine, " ");989 }990 991 llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> FS;992};993 994TEST_F(ExpandResponseFilesTest, ExpandResponseFiles) {995 addFile(path(StringRef("rsp1.rsp")), "-Dflag");996 997 add("foo.cpp", "clang", "@rsp1.rsp");998 add("bar.cpp", "clang", "-Dflag");999 EXPECT_EQ(getCommand("foo.cpp"), "clang foo.cpp -D foo.cpp -Dflag");1000 EXPECT_EQ(getCommand("bar.cpp"), "clang bar.cpp -D bar.cpp -Dflag");1001}1002 1003TEST_F(ExpandResponseFilesTest, ExpandResponseFilesEmptyArgument) {1004 addFile(path(StringRef("rsp1.rsp")), "-Dflag");1005 1006 add("foo.cpp", "clang", "@rsp1.rsp \"\"");1007 EXPECT_EQ(getCommand("foo.cpp"), "clang foo.cpp -D foo.cpp -Dflag ");1008}1009 1010} // end namespace tooling1011} // end namespace clang1012