Imported Upstream version 6.10.0.49

Former-commit-id: 1d6753294b2993e1fbf92de9366bb9544db4189b
This commit is contained in:
Xamarin Public Jenkins (auto-signing)
2020-01-16 16:38:04 +00:00
parent d94e79959b
commit 468663ddbb
48518 changed files with 2789335 additions and 61176 deletions

View File

@@ -0,0 +1,87 @@
//===--- Annotations.cpp - Annotated source code for unit tests -*- C++-*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===---------------------------------------------------------------------===//
#include "Annotations.h"
#include "SourceCode.h"
namespace clang {
namespace clangd {
using namespace llvm;
// Crash if the assertion fails, printing the message and testcase.
// More elegant error handling isn't needed for unit tests.
static void require(bool Assertion, const char *Msg, llvm::StringRef Code) {
if (!Assertion) {
llvm::errs() << "Annotated testcase: " << Msg << "\n" << Code << "\n";
llvm_unreachable("Annotated testcase assertion failed!");
}
}
Annotations::Annotations(StringRef Text) {
auto Here = [this] { return offsetToPosition(Code, Code.size()); };
auto Require = [Text](bool Assertion, const char *Msg) {
require(Assertion, Msg, Text);
};
Optional<StringRef> Name;
SmallVector<std::pair<StringRef, Position>, 8> OpenRanges;
Code.reserve(Text.size());
while (!Text.empty()) {
if (Text.consume_front("^")) {
Points[Name.getValueOr("")].push_back(Here());
Name = None;
continue;
}
if (Text.consume_front("[[")) {
OpenRanges.emplace_back(Name.getValueOr(""), Here());
Name = None;
continue;
}
Require(!Name, "$name should be followed by ^ or [[");
if (Text.consume_front("]]")) {
Require(!OpenRanges.empty(), "unmatched ]]");
Ranges[OpenRanges.back().first].push_back(
{OpenRanges.back().second, Here()});
OpenRanges.pop_back();
continue;
}
if (Text.consume_front("$")) {
Name = Text.take_while(llvm::isAlnum);
Text = Text.drop_front(Name->size());
continue;
}
Code.push_back(Text.front());
Text = Text.drop_front();
}
Require(!Name, "unterminated $name");
Require(OpenRanges.empty(), "unmatched [[");
}
Position Annotations::point(llvm::StringRef Name) const {
auto I = Points.find(Name);
require(I != Points.end() && I->getValue().size() == 1,
"expected exactly one point", Code);
return I->getValue()[0];
}
std::vector<Position> Annotations::points(llvm::StringRef Name) const {
auto P = Points.lookup(Name);
return {P.begin(), P.end()};
}
Range Annotations::range(llvm::StringRef Name) const {
auto I = Ranges.find(Name);
require(I != Ranges.end() && I->getValue().size() == 1,
"expected exactly one range", Code);
return I->getValue()[0];
}
std::vector<Range> Annotations::ranges(llvm::StringRef Name) const {
auto R = Ranges.lookup(Name);
return {R.begin(), R.end()};
}
} // namespace clangd
} // namespace clang

View File

@@ -0,0 +1,69 @@
//===--- Annotations.h - Annotated source code for tests --------*- C++-*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===---------------------------------------------------------------------===//
//
// Annotations lets you mark points and ranges inside source code, for tests:
//
// Annotations Example(R"cpp(
// int complete() { x.pri^ } // ^ indicates a point
// void err() { [["hello" == 42]]; } // [[this is a range]]
// $definition^class Foo{}; // points can be named: "definition"
// $fail[[static_assert(false, "")]] // ranges can be named too: "fail"
// )cpp");
//
// StringRef Code = Example.code(); // annotations stripped.
// std::vector<Position> PP = Example.points(); // all unnamed points
// Position P = Example.point(); // there must be exactly one
// Range R = Example.range("fail"); // find named ranges
//
// Points/ranges are coordinates into `code()` which is stripped of annotations.
//
// Ranges may be nested (and points can be inside ranges), but there's no way
// to define general overlapping ranges.
//
//===---------------------------------------------------------------------===//
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_ANNOTATIONS_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_ANNOTATIONS_H
#include "Protocol.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringMap.h"
#include "llvm/ADT/StringRef.h"
namespace clang {
namespace clangd {
class Annotations {
public:
// Parses the annotations from Text. Crashes if it's malformed.
Annotations(llvm::StringRef Text);
// The input text with all annotations stripped.
// All points and ranges are relative to this stripped text.
llvm::StringRef code() const { return Code; }
// Returns the position of the point marked by ^ (or $name^) in the text.
// Crashes if there isn't exactly one.
Position point(llvm::StringRef Name = "") const;
// Returns the position of all points marked by ^ (or $name^) in the text.
std::vector<Position> points(llvm::StringRef Name = "") const;
// Returns the location of the range marked by [[ ]] (or $name[[ ]]).
// Crashes if there isn't exactly one.
Range range(llvm::StringRef Name = "") const;
// Returns the location of all ranges marked by [[ ]] (or $name[[ ]]).
std::vector<Range> ranges(llvm::StringRef Name = "") const;
private:
std::string Code;
llvm::StringMap<llvm::SmallVector<Position, 1>> Points;
llvm::StringMap<llvm::SmallVector<Range, 1>> Ranges;
};
} // namespace clangd
} // namespace clang
#endif

View File

@@ -0,0 +1,39 @@
set(LLVM_LINK_COMPONENTS
support
)
get_filename_component(CLANGD_SOURCE_DIR
${CMAKE_CURRENT_SOURCE_DIR}/../../clangd REALPATH)
include_directories(
${CLANGD_SOURCE_DIR}
)
add_extra_unittest(ClangdTests
Annotations.cpp
ClangdTests.cpp
CodeCompleteTests.cpp
CodeCompletionStringsTests.cpp
ContextTests.cpp
FileIndexTests.cpp
FuzzyMatchTests.cpp
IndexTests.cpp
JSONExprTests.cpp
TestFS.cpp
TraceTests.cpp
SourceCodeTests.cpp
SymbolCollectorTests.cpp
XRefsTests.cpp
)
target_link_libraries(ClangdTests
PRIVATE
clangBasic
clangDaemon
clangFormat
clangFrontend
clangIndex
clangSema
clangTooling
clangToolingCore
LLVMSupport
)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,142 @@
//===-- CodeCompletionStringsTests.cpp --------------------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include "CodeCompletionStrings.h"
#include "clang/Sema/CodeCompleteConsumer.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
namespace clang {
namespace clangd {
namespace {
class CompletionStringTest : public ::testing::Test {
public:
CompletionStringTest()
: Allocator(std::make_shared<clang::GlobalCodeCompletionAllocator>()),
CCTUInfo(Allocator), Builder(*Allocator, CCTUInfo) {}
protected:
void labelAndInsertText(const CodeCompletionString &CCS,
bool EnableSnippets = false) {
Label.clear();
InsertText.clear();
getLabelAndInsertText(CCS, &Label, &InsertText, EnableSnippets);
}
std::shared_ptr<clang::GlobalCodeCompletionAllocator> Allocator;
CodeCompletionTUInfo CCTUInfo;
CodeCompletionBuilder Builder;
std::string Label;
std::string InsertText;
};
TEST_F(CompletionStringTest, Detail) {
Builder.AddResultTypeChunk("result");
Builder.AddResultTypeChunk("redundant result no no");
EXPECT_EQ(getDetail(*Builder.TakeString()), "result");
}
TEST_F(CompletionStringTest, FilterText) {
Builder.AddTypedTextChunk("typed");
Builder.AddTypedTextChunk("redundant typed no no");
auto *S = Builder.TakeString();
EXPECT_EQ(getFilterText(*S), "typed");
}
TEST_F(CompletionStringTest, Documentation) {
Builder.addBriefComment("Is this brief?");
EXPECT_EQ(getDocumentation(*Builder.TakeString()), "Is this brief?");
}
TEST_F(CompletionStringTest, DocumentationWithAnnotation) {
Builder.addBriefComment("Is this brief?");
Builder.AddAnnotation("Ano");
EXPECT_EQ(getDocumentation(*Builder.TakeString()),
"Annotation: Ano\n\nIs this brief?");
}
TEST_F(CompletionStringTest, MultipleAnnotations) {
Builder.AddAnnotation("Ano1");
Builder.AddAnnotation("Ano2");
Builder.AddAnnotation("Ano3");
EXPECT_EQ(getDocumentation(*Builder.TakeString()),
"Annotations: Ano1 Ano2 Ano3\n");
}
TEST_F(CompletionStringTest, SimpleLabelAndInsert) {
Builder.AddTypedTextChunk("X");
Builder.AddResultTypeChunk("result no no");
labelAndInsertText(*Builder.TakeString());
EXPECT_EQ(Label, "X");
EXPECT_EQ(InsertText, "X");
}
TEST_F(CompletionStringTest, FunctionPlainText) {
Builder.AddResultTypeChunk("result no no");
Builder.AddTypedTextChunk("Foo");
Builder.AddChunk(CodeCompletionString::CK_LeftParen);
Builder.AddPlaceholderChunk("p1");
Builder.AddChunk(CodeCompletionString::CK_Comma);
Builder.AddPlaceholderChunk("p2");
Builder.AddChunk(CodeCompletionString::CK_RightParen);
Builder.AddChunk(CodeCompletionString::CK_HorizontalSpace);
Builder.AddInformativeChunk("const");
labelAndInsertText(*Builder.TakeString());
EXPECT_EQ(Label, "Foo(p1, p2) const");
EXPECT_EQ(InsertText, "Foo");
}
TEST_F(CompletionStringTest, FunctionSnippet) {
Builder.AddResultTypeChunk("result no no");
Builder.addBriefComment("Foo's comment");
Builder.AddTypedTextChunk("Foo");
Builder.AddChunk(CodeCompletionString::CK_LeftParen);
Builder.AddPlaceholderChunk("p1");
Builder.AddChunk(CodeCompletionString::CK_Comma);
Builder.AddPlaceholderChunk("p2");
Builder.AddChunk(CodeCompletionString::CK_RightParen);
auto *CCS = Builder.TakeString();
labelAndInsertText(*CCS);
EXPECT_EQ(Label, "Foo(p1, p2)");
EXPECT_EQ(InsertText, "Foo");
labelAndInsertText(*CCS, /*EnableSnippets=*/true);
EXPECT_EQ(Label, "Foo(p1, p2)");
EXPECT_EQ(InsertText, "Foo(${1:p1}, ${2:p2})");
EXPECT_EQ(getDocumentation(*CCS), "Foo's comment");
EXPECT_EQ(getFilterText(*CCS), "Foo");
}
TEST_F(CompletionStringTest, EscapeSnippet) {
Builder.AddTypedTextChunk("Foo");
Builder.AddChunk(CodeCompletionString::CK_LeftParen);
Builder.AddPlaceholderChunk("$p}1\\");
Builder.AddChunk(CodeCompletionString::CK_RightParen);
labelAndInsertText(*Builder.TakeString(), /*EnableSnippets=*/true);
EXPECT_EQ(Label, "Foo($p}1\\)");
EXPECT_EQ(InsertText, "Foo(${1:\\$p\\}1\\\\})");
}
TEST_F(CompletionStringTest, IgnoreInformativeQualifier) {
Builder.AddTypedTextChunk("X");
Builder.AddInformativeChunk("info ok");
Builder.AddInformativeChunk("info no no::");
labelAndInsertText(*Builder.TakeString());
EXPECT_EQ(Label, "Xinfo ok");
EXPECT_EQ(InsertText, "X");
}
} // namespace
} // namespace clangd
} // namespace clang

View File

@@ -0,0 +1,57 @@
//===-- ContextTests.cpp - Context tests ------------------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include "Context.h"
#include "gtest/gtest.h"
namespace clang {
namespace clangd {
TEST(ContextTests, Simple) {
Key<int> IntParam;
Key<int> ExtraIntParam;
Context Ctx = Context::empty().derive(IntParam, 10).derive(ExtraIntParam, 20);
EXPECT_EQ(*Ctx.get(IntParam), 10);
EXPECT_EQ(*Ctx.get(ExtraIntParam), 20);
}
TEST(ContextTests, MoveOps) {
Key<std::unique_ptr<int>> Param;
Context Ctx = Context::empty().derive(Param, llvm::make_unique<int>(10));
EXPECT_EQ(**Ctx.get(Param), 10);
Context NewCtx = std::move(Ctx);
EXPECT_EQ(**NewCtx.get(Param), 10);
}
TEST(ContextTests, Builders) {
Key<int> ParentParam;
Key<int> ParentAndChildParam;
Key<int> ChildParam;
Context ParentCtx =
Context::empty().derive(ParentParam, 10).derive(ParentAndChildParam, 20);
Context ChildCtx =
ParentCtx.derive(ParentAndChildParam, 30).derive(ChildParam, 40);
EXPECT_EQ(*ParentCtx.get(ParentParam), 10);
EXPECT_EQ(*ParentCtx.get(ParentAndChildParam), 20);
EXPECT_EQ(ParentCtx.get(ChildParam), nullptr);
EXPECT_EQ(*ChildCtx.get(ParentParam), 10);
EXPECT_EQ(*ChildCtx.get(ParentAndChildParam), 30);
EXPECT_EQ(*ChildCtx.get(ChildParam), 40);
}
} // namespace clangd
} // namespace clang

View File

@@ -0,0 +1,182 @@
//===-- FileIndexTests.cpp ---------------------------*- C++ -*-----------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include "index/FileIndex.h"
#include "clang/Frontend/CompilerInvocation.h"
#include "clang/Frontend/PCHContainerOperations.h"
#include "clang/Frontend/Utils.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
using testing::UnorderedElementsAre;
namespace clang {
namespace clangd {
namespace {
Symbol symbol(llvm::StringRef ID) {
Symbol Sym;
Sym.ID = SymbolID(ID);
Sym.Name = ID;
return Sym;
}
std::unique_ptr<SymbolSlab> numSlab(int Begin, int End) {
SymbolSlab::Builder Slab;
for (int i = Begin; i <= End; i++)
Slab.insert(symbol(std::to_string(i)));
return llvm::make_unique<SymbolSlab>(std::move(Slab).build());
}
std::vector<std::string>
getSymbolNames(const std::vector<const Symbol *> &Symbols) {
std::vector<std::string> Names;
for (const Symbol *Sym : Symbols)
Names.push_back(Sym->Name);
return Names;
}
TEST(FileSymbolsTest, UpdateAndGet) {
FileSymbols FS;
EXPECT_THAT(getSymbolNames(*FS.allSymbols()), UnorderedElementsAre());
FS.update("f1", numSlab(1, 3));
EXPECT_THAT(getSymbolNames(*FS.allSymbols()),
UnorderedElementsAre("1", "2", "3"));
}
TEST(FileSymbolsTest, Overlap) {
FileSymbols FS;
FS.update("f1", numSlab(1, 3));
FS.update("f2", numSlab(3, 5));
EXPECT_THAT(getSymbolNames(*FS.allSymbols()),
UnorderedElementsAre("1", "2", "3", "3", "4", "5"));
}
TEST(FileSymbolsTest, SnapshotAliveAfterRemove) {
FileSymbols FS;
FS.update("f1", numSlab(1, 3));
auto Symbols = FS.allSymbols();
EXPECT_THAT(getSymbolNames(*Symbols), UnorderedElementsAre("1", "2", "3"));
FS.update("f1", nullptr);
EXPECT_THAT(getSymbolNames(*FS.allSymbols()), UnorderedElementsAre());
EXPECT_THAT(getSymbolNames(*Symbols), UnorderedElementsAre("1", "2", "3"));
}
std::vector<std::string> match(const SymbolIndex &I,
const FuzzyFindRequest &Req) {
std::vector<std::string> Matches;
auto Ctx = Context::empty();
I.fuzzyFind(Ctx, Req, [&](const Symbol &Sym) {
Matches.push_back(
(Sym.Scope + (Sym.Scope.empty() ? "" : "::") + Sym.Name).str());
});
return Matches;
}
/// Create an ParsedAST for \p Code. Returns None if \p Code is empty.
llvm::Optional<ParsedAST> build(std::string Path, llvm::StringRef Code) {
Context Ctx = Context::empty();
if (Code.empty())
return llvm::None;
const char *Args[] = {"clang", "-xc++", Path.c_str()};
auto CI = createInvocationFromCommandLine(Args);
auto Buf = llvm::MemoryBuffer::getMemBuffer(Code);
auto AST = ParsedAST::Build(Ctx, std::move(CI), nullptr, std::move(Buf),
std::make_shared<PCHContainerOperations>(),
vfs::getRealFileSystem());
assert(AST.hasValue());
return std::move(*AST);
}
TEST(FileIndexTest, IndexAST) {
FileIndex M;
auto Ctx = Context::empty();
M.update(
Ctx, "f1",
build("f1", "namespace ns { void f() {} class X {}; }").getPointer());
FuzzyFindRequest Req;
Req.Query = "";
Req.Scopes = {"ns"};
EXPECT_THAT(match(M, Req), UnorderedElementsAre("ns::f", "ns::X"));
}
TEST(FileIndexTest, NoLocal) {
FileIndex M;
auto Ctx = Context::empty();
M.update(
Ctx, "f1",
build("f1", "namespace ns { void f() { int local = 0; } class X {}; }")
.getPointer());
FuzzyFindRequest Req;
Req.Query = "";
EXPECT_THAT(match(M, Req), UnorderedElementsAre("ns", "ns::f", "ns::X"));
}
TEST(FileIndexTest, IndexMultiASTAndDeduplicate) {
FileIndex M;
auto Ctx = Context::empty();
M.update(
Ctx, "f1",
build("f1", "namespace ns { void f() {} class X {}; }").getPointer());
M.update(
Ctx, "f2",
build("f2", "namespace ns { void ff() {} class X {}; }").getPointer());
FuzzyFindRequest Req;
Req.Query = "";
Req.Scopes = {"ns"};
EXPECT_THAT(match(M, Req), UnorderedElementsAre("ns::f", "ns::X", "ns::ff"));
}
TEST(FileIndexTest, RemoveAST) {
FileIndex M;
auto Ctx = Context::empty();
M.update(
Ctx, "f1",
build("f1", "namespace ns { void f() {} class X {}; }").getPointer());
FuzzyFindRequest Req;
Req.Query = "";
Req.Scopes = {"ns"};
EXPECT_THAT(match(M, Req), UnorderedElementsAre("ns::f", "ns::X"));
M.update(Ctx, "f1", nullptr);
EXPECT_THAT(match(M, Req), UnorderedElementsAre());
}
TEST(FileIndexTest, RemoveNonExisting) {
FileIndex M;
auto Ctx = Context::empty();
M.update(Ctx, "no", nullptr);
EXPECT_THAT(match(M, FuzzyFindRequest()), UnorderedElementsAre());
}
TEST(FileIndexTest, ClassMembers) {
FileIndex M;
auto Ctx = Context::empty();
M.update(Ctx, "f1",
build("f1", "class X { static int m1; int m2;};").getPointer());
FuzzyFindRequest Req;
Req.Query = "";
EXPECT_THAT(match(M, Req), UnorderedElementsAre("X", "X::m1", "X::m2"));
}
} // namespace
} // namespace clangd
} // namespace clang

View File

@@ -0,0 +1,252 @@
//===-- FuzzyMatchTests.cpp - String fuzzy matcher tests --------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include "FuzzyMatch.h"
#include "llvm/ADT/StringExtras.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
namespace clang {
namespace clangd {
namespace {
using namespace llvm;
using testing::Not;
struct ExpectedMatch {
ExpectedMatch(StringRef Annotated) : Word(Annotated), Annotated(Annotated) {
for (char C : "[]")
Word.erase(std::remove(Word.begin(), Word.end(), C), Word.end());
}
std::string Word;
StringRef Annotated;
};
raw_ostream &operator<<(raw_ostream &OS, const ExpectedMatch &M) {
return OS << "'" << M.Word << "' as " << M.Annotated;
}
struct MatchesMatcher : public testing::MatcherInterface<StringRef> {
ExpectedMatch Candidate;
MatchesMatcher(ExpectedMatch Candidate) : Candidate(std::move(Candidate)) {}
void DescribeTo(::std::ostream *OS) const override {
raw_os_ostream(*OS) << "Matches " << Candidate;
}
bool MatchAndExplain(StringRef Pattern,
testing::MatchResultListener *L) const override {
std::unique_ptr<raw_ostream> OS(
L->stream() ? (raw_ostream *)(new raw_os_ostream(*L->stream()))
: new raw_null_ostream());
FuzzyMatcher Matcher(Pattern);
auto Result = Matcher.match(Candidate.Word);
auto AnnotatedMatch = Matcher.dumpLast(*OS << "\n");
return Result && AnnotatedMatch == Candidate.Annotated;
}
};
// Accepts patterns that match a given word.
// Dumps the debug tables on match failure.
testing::Matcher<StringRef> matches(StringRef M) {
return testing::MakeMatcher<StringRef>(new MatchesMatcher(M));
}
TEST(FuzzyMatch, Matches) {
EXPECT_THAT("u_p", matches("[u]nique[_p]tr"));
EXPECT_THAT("up", matches("[u]nique_[p]tr"));
EXPECT_THAT("uq", matches("[u]ni[q]ue_ptr"));
EXPECT_THAT("qp", Not(matches("unique_ptr")));
EXPECT_THAT("log", Not(matches("SVGFEMorphologyElement")));
EXPECT_THAT("tit", matches("win.[tit]"));
EXPECT_THAT("title", matches("win.[title]"));
EXPECT_THAT("WordCla", matches("[Word]Character[Cla]ssifier"));
EXPECT_THAT("WordCCla", matches("[WordC]haracter[Cla]ssifier"));
EXPECT_THAT("dete", Not(matches("editor.quickSuggestionsDelay")));
EXPECT_THAT("highlight", matches("editorHover[Highlight]"));
EXPECT_THAT("hhighlight", matches("editor[H]over[Highlight]"));
EXPECT_THAT("dhhighlight", Not(matches("editorHoverHighlight")));
EXPECT_THAT("-moz", matches("[-moz]-foo"));
EXPECT_THAT("moz", matches("-[moz]-foo"));
EXPECT_THAT("moza", matches("-[moz]-[a]nimation"));
EXPECT_THAT("ab", matches("[ab]A"));
EXPECT_THAT("ccm", matches("[c]a[cm]elCase"));
EXPECT_THAT("bti", Not(matches("the_black_knight")));
EXPECT_THAT("ccm", Not(matches("camelCase")));
EXPECT_THAT("cmcm", Not(matches("camelCase")));
EXPECT_THAT("BK", matches("the_[b]lack_[k]night"));
EXPECT_THAT("KeyboardLayout=", Not(matches("KeyboardLayout")));
EXPECT_THAT("LLL", matches("SVisual[L]ogger[L]ogs[L]ist"));
EXPECT_THAT("LLLL", Not(matches("SVilLoLosLi")));
EXPECT_THAT("LLLL", Not(matches("SVisualLoggerLogsList")));
EXPECT_THAT("TEdit", matches("[T]ext[Edit]"));
EXPECT_THAT("TEdit", matches("[T]ext[Edit]or"));
EXPECT_THAT("TEdit", matches("[Te]xte[dit]"));
EXPECT_THAT("TEdit", matches("[t]ext_[edit]"));
EXPECT_THAT("TEditDit", matches("[T]ext[Edit]or[D]ecorat[i]on[T]ype"));
EXPECT_THAT("TEdit", matches("[T]ext[Edit]orDecorationType"));
EXPECT_THAT("Tedit", matches("[T]ext[Edit]"));
EXPECT_THAT("ba", Not(matches("?AB?")));
EXPECT_THAT("bkn", matches("the_[b]lack_[kn]ight"));
EXPECT_THAT("bt", matches("the_[b]lack_knigh[t]"));
EXPECT_THAT("ccm", matches("[c]amelCase[cm]"));
EXPECT_THAT("fdm", matches("[f]in[dM]odel"));
EXPECT_THAT("fob", matches("[fo]o[b]ar"));
EXPECT_THAT("fobz", Not(matches("foobar")));
EXPECT_THAT("foobar", matches("[foobar]"));
EXPECT_THAT("form", matches("editor.[form]atOnSave"));
EXPECT_THAT("g p", matches("[G]it:[ P]ull"));
EXPECT_THAT("g p", matches("[G]it:[ P]ull"));
EXPECT_THAT("gip", matches("[Gi]t: [P]ull"));
EXPECT_THAT("gip", matches("[Gi]t: [P]ull"));
EXPECT_THAT("gp", matches("[G]it: [P]ull"));
EXPECT_THAT("gp", matches("[G]it_Git_[P]ull"));
EXPECT_THAT("is", matches("[I]mport[S]tatement"));
EXPECT_THAT("is", matches("[is]Valid"));
EXPECT_THAT("lowrd", matches("[low]Wo[rd]"));
EXPECT_THAT("myvable", matches("[myva]ria[ble]"));
EXPECT_THAT("no", Not(matches("")));
EXPECT_THAT("no", Not(matches("match")));
EXPECT_THAT("ob", Not(matches("foobar")));
EXPECT_THAT("sl", matches("[S]Visual[L]oggerLogsList"));
EXPECT_THAT("sllll", matches("[S]Visua[lL]ogger[L]ogs[L]ist"));
EXPECT_THAT("Three", matches("H[T]ML[HRE]l[e]ment"));
EXPECT_THAT("Three", matches("[Three]"));
EXPECT_THAT("fo", Not(matches("barfoo")));
EXPECT_THAT("fo", matches("bar_[fo]o"));
EXPECT_THAT("fo", matches("bar_[Fo]o"));
EXPECT_THAT("fo", matches("bar [fo]o"));
EXPECT_THAT("fo", matches("bar.[fo]o"));
EXPECT_THAT("fo", matches("bar/[fo]o"));
EXPECT_THAT("fo", matches("bar\\[fo]o"));
EXPECT_THAT(
"aaaaaa",
matches("[aaaaaa]aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"));
EXPECT_THAT("baba", Not(matches("ababababab")));
EXPECT_THAT("fsfsfs", Not(matches("dsafdsafdsafdsafdsafdsafdsafasdfdsa")));
EXPECT_THAT("fsfsfsfsfsfsfsf",
Not(matches("dsafdsafdsafdsafdsafdsafdsafasdfdsafdsafdsafdsafdsfd"
"safdsfdfdfasdnfdsajfndsjnafjndsajlknfdsa")));
EXPECT_THAT(" g", matches("[ g]roup"));
EXPECT_THAT("g", matches(" [g]roup"));
EXPECT_THAT("g g", Not(matches(" groupGroup")));
EXPECT_THAT("g g", matches(" [g]roup[ G]roup"));
EXPECT_THAT(" g g", matches("[ ] [g]roup[ G]roup"));
EXPECT_THAT("zz", matches("[zz]Group"));
EXPECT_THAT("zzg", matches("[zzG]roup"));
EXPECT_THAT("g", matches("zz[G]roup"));
EXPECT_THAT("aaaa", matches("_a_[aaaa]")); // Prefer consecutive.
EXPECT_THAT("printf", matches("s[printf]"));
EXPECT_THAT("str", matches("o[str]eam"));
}
struct RankMatcher : public testing::MatcherInterface<StringRef> {
std::vector<ExpectedMatch> RankedStrings;
RankMatcher(std::initializer_list<ExpectedMatch> RankedStrings)
: RankedStrings(RankedStrings) {}
void DescribeTo(::std::ostream *OS) const override {
raw_os_ostream O(*OS);
O << "Ranks strings in order: [";
for (const auto &Str : RankedStrings)
O << "\n\t" << Str;
O << "\n]";
}
bool MatchAndExplain(StringRef Pattern,
testing::MatchResultListener *L) const override {
std::unique_ptr<raw_ostream> OS(
L->stream() ? (raw_ostream *)(new raw_os_ostream(*L->stream()))
: new raw_null_ostream());
FuzzyMatcher Matcher(Pattern);
const ExpectedMatch *LastMatch;
Optional<float> LastScore;
bool Ok = true;
for (const auto &Str : RankedStrings) {
auto Score = Matcher.match(Str.Word);
if (!Score) {
*OS << "\nDoesn't match '" << Str.Word << "'";
Matcher.dumpLast(*OS << "\n");
Ok = false;
} else {
std::string Buf;
llvm::raw_string_ostream Info(Buf);
auto AnnotatedMatch = Matcher.dumpLast(Info);
if (AnnotatedMatch != Str.Annotated) {
*OS << "\nMatched " << Str.Word << " as " << AnnotatedMatch
<< " instead of " << Str.Annotated << "\n"
<< Info.str();
Ok = false;
} else if (LastScore && *LastScore < *Score) {
*OS << "\nRanks '" << Str.Word << "'=" << *Score << " above '"
<< LastMatch->Word << "'=" << *LastScore << "\n"
<< Info.str();
Matcher.match(LastMatch->Word);
Matcher.dumpLast(*OS << "\n");
Ok = false;
}
}
LastMatch = &Str;
LastScore = Score;
}
return Ok;
}
};
// Accepts patterns that match all the strings and rank them in the given order.
// Dumps the debug tables on match failure.
template <typename... T> testing::Matcher<StringRef> ranks(T... RankedStrings) {
return testing::MakeMatcher<StringRef>(
new RankMatcher{ExpectedMatch(RankedStrings)...});
}
TEST(FuzzyMatch, Ranking) {
EXPECT_THAT("eb", ranks("[e]mplace_[b]ack", "[e]m[b]ed"));
EXPECT_THAT("cons",
ranks("[cons]ole", "[Cons]ole", "ArrayBuffer[Cons]tructor"));
EXPECT_THAT("foo", ranks("[foo]", "[Foo]"));
EXPECT_THAT("onMess",
ranks("[onMess]age", "[onmess]age", "[on]This[M]ega[Es]cape[s]"));
EXPECT_THAT("CC", ranks("[C]amel[C]ase", "[c]amel[C]ase"));
EXPECT_THAT("cC", ranks("[c]amel[C]ase", "[C]amel[C]ase"));
EXPECT_THAT("p", ranks("[p]arse", "[p]osix", "[p]afdsa", "[p]ath", "[p]"));
EXPECT_THAT("pa", ranks("[pa]rse", "[pa]th", "[pa]fdsa"));
EXPECT_THAT("log", ranks("[log]", "Scroll[Log]icalPosition"));
EXPECT_THAT("e", ranks("[e]lse", "Abstract[E]lement"));
EXPECT_THAT("workbench.sideb",
ranks("[workbench.sideB]ar.location",
"[workbench.]editor.default[SideB]ySideLayout"));
EXPECT_THAT("editor.r", ranks("[editor.r]enderControlCharacter",
"[editor.]overview[R]ulerlanes",
"diff[Editor.r]enderSideBySide"));
EXPECT_THAT("-mo", ranks("[-mo]z-columns", "[-]ms-ime-[mo]de"));
EXPECT_THAT("convertModelPosition",
ranks("[convertModelPosition]ToViewPosition",
"[convert]ViewTo[ModelPosition]"));
EXPECT_THAT("is", ranks("[is]ValidViewletId", "[i]mport [s]tatement"));
EXPECT_THAT("title", ranks("window.[title]",
"files.[t]r[i]m[T]rai[l]ingWhit[e]space"));
EXPECT_THAT("strcpy", ranks("[strcpy]", "[strcpy]_s", "[str]n[cpy]"));
EXPECT_THAT("close", ranks("workbench.quickOpen.[close]OnFocusOut",
"[c]ss.[l]int.imp[o]rt[S]tat[e]ment",
"[c]ss.co[lo]rDecorator[s].[e]nable"));
}
} // namespace
} // namespace clangd
} // namespace clang

View File

@@ -0,0 +1,212 @@
//===-- IndexTests.cpp -------------------------------*- C++ -*-----------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include "index/Index.h"
#include "index/MemIndex.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
using testing::UnorderedElementsAre;
using testing::Pointee;
namespace clang {
namespace clangd {
namespace {
Symbol symbol(llvm::StringRef QName) {
Symbol Sym;
Sym.ID = SymbolID(QName.str());
size_t Pos = QName.rfind("::");
if (Pos == llvm::StringRef::npos) {
Sym.Name = QName;
Sym.Scope = "";
} else {
Sym.Name = QName.substr(Pos + 2);
Sym.Scope = QName.substr(0, Pos);
}
return Sym;
}
MATCHER_P(Named, N, "") { return arg.Name == N; }
TEST(SymbolSlab, FindAndIterate) {
SymbolSlab::Builder B;
B.insert(symbol("Z"));
B.insert(symbol("Y"));
B.insert(symbol("X"));
EXPECT_EQ(nullptr, B.find(SymbolID("W")));
for (const char *Sym : {"X", "Y", "Z"})
EXPECT_THAT(B.find(SymbolID(Sym)), Pointee(Named(Sym)));
SymbolSlab S = std::move(B).build();
EXPECT_THAT(S, UnorderedElementsAre(Named("X"), Named("Y"), Named("Z")));
EXPECT_EQ(S.end(), S.find(SymbolID("W")));
for (const char *Sym : {"X", "Y", "Z"})
EXPECT_THAT(*S.find(SymbolID(Sym)), Named(Sym));
}
struct SlabAndPointers {
SymbolSlab Slab;
std::vector<const Symbol *> Pointers;
};
// Create a slab of symbols with the given qualified names as both IDs and
// names. The life time of the slab is managed by the returned shared pointer.
// If \p WeakSymbols is provided, it will be pointed to the managed object in
// the returned shared pointer.
std::shared_ptr<std::vector<const Symbol *>>
generateSymbols(std::vector<std::string> QualifiedNames,
std::weak_ptr<SlabAndPointers> *WeakSymbols = nullptr) {
SymbolSlab::Builder Slab;
for (llvm::StringRef QName : QualifiedNames)
Slab.insert(symbol(QName));
auto Storage = std::make_shared<SlabAndPointers>();
Storage->Slab = std::move(Slab).build();
for (const auto &Sym : Storage->Slab)
Storage->Pointers.push_back(&Sym);
if (WeakSymbols)
*WeakSymbols = Storage;
auto *Pointers = &Storage->Pointers;
return {std::move(Storage), Pointers};
}
// Create a slab of symbols with IDs and names [Begin, End], otherwise identical
// to the `generateSymbols` above.
std::shared_ptr<std::vector<const Symbol *>>
generateNumSymbols(int Begin, int End,
std::weak_ptr<SlabAndPointers> *WeakSymbols = nullptr) {
std::vector<std::string> Names;
for (int i = Begin; i <= End; i++)
Names.push_back(std::to_string(i));
return generateSymbols(Names, WeakSymbols);
}
std::vector<std::string> match(const SymbolIndex &I,
const FuzzyFindRequest &Req) {
std::vector<std::string> Matches;
auto Ctx = Context::empty();
I.fuzzyFind(Ctx, Req, [&](const Symbol &Sym) {
Matches.push_back(
(Sym.Scope + (Sym.Scope.empty() ? "" : "::") + Sym.Name).str());
});
return Matches;
}
TEST(MemIndexTest, MemIndexSymbolsRecycled) {
MemIndex I;
std::weak_ptr<SlabAndPointers> Symbols;
I.build(generateNumSymbols(0, 10, &Symbols));
FuzzyFindRequest Req;
Req.Query = "7";
EXPECT_THAT(match(I, Req), UnorderedElementsAre("7"));
EXPECT_FALSE(Symbols.expired());
// Release old symbols.
I.build(generateNumSymbols(0, 0));
EXPECT_TRUE(Symbols.expired());
}
TEST(MemIndexTest, MemIndexMatchSubstring) {
MemIndex I;
I.build(generateNumSymbols(5, 25));
FuzzyFindRequest Req;
Req.Query = "5";
EXPECT_THAT(match(I, Req), UnorderedElementsAre("5", "15", "25"));
}
TEST(MemIndexTest, MemIndexDeduplicate) {
auto Symbols = generateNumSymbols(0, 10);
// Inject some duplicates and make sure we only match the same symbol once.
auto Sym = symbol("7");
Symbols->push_back(&Sym);
Symbols->push_back(&Sym);
Symbols->push_back(&Sym);
FuzzyFindRequest Req;
Req.Query = "7";
MemIndex I;
I.build(std::move(Symbols));
auto Matches = match(I, Req);
EXPECT_EQ(Matches.size(), 1u);
}
TEST(MemIndexTest, MemIndexLimitedNumMatches) {
MemIndex I;
I.build(generateNumSymbols(0, 100));
FuzzyFindRequest Req;
Req.Query = "5";
Req.MaxCandidateCount = 3;
auto Matches = match(I, Req);
EXPECT_EQ(Matches.size(), Req.MaxCandidateCount);
}
TEST(MemIndexTest, MatchQualifiedNamesWithoutSpecificScope) {
MemIndex I;
I.build(generateSymbols({"a::xyz", "b::yz", "yz"}));
FuzzyFindRequest Req;
Req.Query = "y";
auto Matches = match(I, Req);
EXPECT_THAT(match(I, Req), UnorderedElementsAre("a::xyz", "b::yz", "yz"));
}
TEST(MemIndexTest, MatchQualifiedNamesWithGlobalScope) {
MemIndex I;
I.build(generateSymbols({"a::xyz", "b::yz", "yz"}));
FuzzyFindRequest Req;
Req.Query = "y";
Req.Scopes = {""};
auto Matches = match(I, Req);
EXPECT_THAT(match(I, Req), UnorderedElementsAre("yz"));
}
TEST(MemIndexTest, MatchQualifiedNamesWithOneScope) {
MemIndex I;
I.build(generateSymbols({"a::xyz", "a::yy", "a::xz", "b::yz", "yz"}));
FuzzyFindRequest Req;
Req.Query = "y";
Req.Scopes = {"a"};
auto Matches = match(I, Req);
EXPECT_THAT(match(I, Req), UnorderedElementsAre("a::xyz", "a::yy"));
}
TEST(MemIndexTest, MatchQualifiedNamesWithMultipleScopes) {
MemIndex I;
I.build(generateSymbols({"a::xyz", "a::yy", "a::xz", "b::yz", "yz"}));
FuzzyFindRequest Req;
Req.Query = "y";
Req.Scopes = {"a", "b"};
auto Matches = match(I, Req);
EXPECT_THAT(match(I, Req), UnorderedElementsAre("a::xyz", "a::yy", "b::yz"));
}
TEST(MemIndexTest, NoMatchNestedScopes) {
MemIndex I;
I.build(generateSymbols({"a::xyz", "a::b::yy"}));
FuzzyFindRequest Req;
Req.Query = "y";
Req.Scopes = {"a"};
auto Matches = match(I, Req);
EXPECT_THAT(match(I, Req), UnorderedElementsAre("a::xyz"));
}
TEST(MemIndexTest, IgnoreCases) {
MemIndex I;
I.build(generateSymbols({"ns::ABC", "ns::abc"}));
FuzzyFindRequest Req;
Req.Query = "AB";
Req.Scopes = {"ns"};
auto Matches = match(I, Req);
EXPECT_THAT(match(I, Req), UnorderedElementsAre("ns::ABC", "ns::abc"));
}
} // namespace
} // namespace clangd
} // namespace clang

View File

@@ -0,0 +1,293 @@
//===-- JSONExprTests.cpp - JSON expression unit tests ----------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include "JSONExpr.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
namespace clang {
namespace clangd {
namespace json {
void PrintTo(const Expr &E, std::ostream *OS) {
llvm::raw_os_ostream(*OS) << llvm::formatv("{0:2}", E);
}
namespace {
std::string s(const Expr &E) { return llvm::formatv("{0}", E).str(); }
std::string sp(const Expr &E) { return llvm::formatv("{0:2}", E).str(); }
TEST(JSONExprTests, Types) {
EXPECT_EQ("true", s(true));
EXPECT_EQ("null", s(nullptr));
EXPECT_EQ("2.5", s(2.5));
EXPECT_EQ(R"("foo")", s("foo"));
EXPECT_EQ("[1,2,3]", s({1, 2, 3}));
EXPECT_EQ(R"({"x":10,"y":20})", s(obj{{"x", 10}, {"y", 20}}));
}
TEST(JSONExprTests, Constructors) {
// Lots of edge cases around empty and singleton init lists.
EXPECT_EQ("[[[3]]]", s({{{3}}}));
EXPECT_EQ("[[[]]]", s({{{}}}));
EXPECT_EQ("[[{}]]", s({{obj{}}}));
EXPECT_EQ(R"({"A":{"B":{}}})", s(obj{{"A", obj{{"B", obj{}}}}}));
EXPECT_EQ(R"({"A":{"B":{"X":"Y"}}})",
s(obj{{"A", obj{{"B", obj{{"X", "Y"}}}}}}));
}
TEST(JSONExprTests, StringOwnership) {
char X[] = "Hello";
Expr Alias = static_cast<const char *>(X);
X[1] = 'a';
EXPECT_EQ(R"("Hallo")", s(Alias));
std::string Y = "Hello";
Expr Copy = Y;
Y[1] = 'a';
EXPECT_EQ(R"("Hello")", s(Copy));
}
TEST(JSONExprTests, CanonicalOutput) {
// Objects are sorted (but arrays aren't)!
EXPECT_EQ(R"({"a":1,"b":2,"c":3})", s(obj{{"a", 1}, {"c", 3}, {"b", 2}}));
EXPECT_EQ(R"(["a","c","b"])", s({"a", "c", "b"}));
EXPECT_EQ("3", s(3.0));
}
TEST(JSONExprTests, Escaping) {
std::string test = {
0, // Strings may contain nulls.
'\b', '\f', // Have mnemonics, but we escape numerically.
'\r', '\n', '\t', // Escaped with mnemonics.
'S', '\"', '\\', // Printable ASCII characters.
'\x7f', // Delete is not escaped.
'\xce', '\x94', // Non-ASCII UTF-8 is not escaped.
};
std::string teststring = R"("\u0000\u0008\u000c\r\n\tS\"\\)"
"\x7f\xCE\x94\"";
EXPECT_EQ(teststring, s(test));
EXPECT_EQ(R"({"object keys are\nescaped":true})",
s(obj{{"object keys are\nescaped", true}}));
}
TEST(JSONExprTests, PrettyPrinting) {
const char str[] = R"({
"empty_array": [],
"empty_object": {},
"full_array": [
1,
null
],
"full_object": {
"nested_array": [
{
"property": "value"
}
]
}
})";
EXPECT_EQ(str, sp(obj{
{"empty_object", obj{}},
{"empty_array", {}},
{"full_array", {1, nullptr}},
{"full_object",
obj{
{"nested_array",
{obj{
{"property", "value"},
}}},
}},
}));
}
TEST(JSONTest, Parse) {
auto Compare = [](llvm::StringRef S, Expr Expected) {
if (auto E = parse(S)) {
// Compare both string forms and with operator==, in case we have bugs.
EXPECT_EQ(*E, Expected);
EXPECT_EQ(sp(*E), sp(Expected));
} else {
handleAllErrors(E.takeError(), [S](const llvm::ErrorInfoBase &E) {
FAIL() << "Failed to parse JSON >>> " << S << " <<<: " << E.message();
});
}
};
Compare(R"(true)", true);
Compare(R"(false)", false);
Compare(R"(null)", nullptr);
Compare(R"(42)", 42);
Compare(R"(2.5)", 2.5);
Compare(R"(2e50)", 2e50);
Compare(R"(1.2e3456789)", std::numeric_limits<double>::infinity());
Compare(R"("foo")", "foo");
Compare(R"("\"\\\b\f\n\r\t")", "\"\\\b\f\n\r\t");
Compare(R"("\u0000")", llvm::StringRef("\0", 1));
Compare("\"\x7f\"", "\x7f");
Compare(R"("\ud801\udc37")", u8"\U00010437"); // UTF16 surrogate pair escape.
Compare("\"\xE2\x82\xAC\xF0\x9D\x84\x9E\"", u8"\u20ac\U0001d11e"); // UTF8
Compare(
R"("LoneLeading=\ud801, LoneTrailing=\udc01, LeadingLeadingTrailing=\ud801\ud801\udc37")",
u8"LoneLeading=\ufffd, LoneTrailing=\ufffd, "
u8"LeadingLeadingTrailing=\ufffd\U00010437"); // Invalid unicode.
Compare(R"({"":0,"":0})", obj{{"", 0}});
Compare(R"({"obj":{},"arr":[]})", obj{{"obj", obj{}}, {"arr", {}}});
Compare(R"({"\n":{"\u0000":[[[[]]]]}})",
obj{{"\n", obj{
{llvm::StringRef("\0", 1), {{{{}}}}},
}}});
Compare("\r[\n\t] ", {});
}
TEST(JSONTest, ParseErrors) {
auto ExpectErr = [](llvm::StringRef Msg, llvm::StringRef S) {
if (auto E = parse(S)) {
// Compare both string forms and with operator==, in case we have bugs.
FAIL() << "Parsed JSON >>> " << S << " <<< but wanted error: " << Msg;
} else {
handleAllErrors(E.takeError(), [S, Msg](const llvm::ErrorInfoBase &E) {
EXPECT_THAT(E.message(), testing::HasSubstr(Msg)) << S;
});
}
};
ExpectErr("Unexpected EOF", "");
ExpectErr("Unexpected EOF", "[");
ExpectErr("Text after end of document", "[][]");
ExpectErr("Invalid bareword", "fuzzy");
ExpectErr("Expected , or ]", "[2?]");
ExpectErr("Expected object key", "{a:2}");
ExpectErr("Expected : after object key", R"({"a",2})");
ExpectErr("Expected , or } after object property", R"({"a":2 "b":3})");
ExpectErr("Expected JSON value", R"([&%!])");
ExpectErr("Invalid number", "1e1.0");
ExpectErr("Unterminated string", R"("abc\"def)");
ExpectErr("Control character in string", "\"abc\ndef\"");
ExpectErr("Invalid escape sequence", R"("\030")");
ExpectErr("Invalid \\u escape sequence", R"("\usuck")");
ExpectErr("[3:3, byte=19]", R"({
"valid": 1,
invalid: 2
})");
}
TEST(JSONTest, Inspection) {
llvm::Expected<Expr> Doc = parse(R"(
{
"null": null,
"boolean": false,
"number": 2.78,
"string": "json",
"array": [null, true, 3.14, "hello", [1,2,3], {"time": "arrow"}],
"object": {"fruit": "banana"}
}
)");
EXPECT_TRUE(!!Doc);
obj *O = Doc->asObject();
ASSERT_TRUE(O);
EXPECT_FALSE(O->getNull("missing"));
EXPECT_FALSE(O->getNull("boolean"));
EXPECT_TRUE(O->getNull("null"));
EXPECT_EQ(O->getNumber("number"), llvm::Optional<double>(2.78));
EXPECT_FALSE(O->getInteger("number"));
EXPECT_EQ(O->getString("string"), llvm::Optional<llvm::StringRef>("json"));
ASSERT_FALSE(O->getObject("missing"));
ASSERT_FALSE(O->getObject("array"));
ASSERT_TRUE(O->getObject("object"));
EXPECT_EQ(*O->getObject("object"), (obj{{"fruit", "banana"}}));
ary *A = O->getArray("array");
ASSERT_TRUE(A);
EXPECT_EQ(A->getBoolean(1), llvm::Optional<bool>(true));
ASSERT_TRUE(A->getArray(4));
EXPECT_EQ(*A->getArray(4), (ary{1, 2, 3}));
EXPECT_EQ(A->getArray(4)->getInteger(1), llvm::Optional<int64_t>(2));
int I = 0;
for (Expr &E : *A) {
if (I++ == 5) {
ASSERT_TRUE(E.asObject());
EXPECT_EQ(E.asObject()->getString("time"),
llvm::Optional<llvm::StringRef>("arrow"));
} else
EXPECT_FALSE(E.asObject());
}
}
// Sample struct with typical JSON-mapping rules.
struct CustomStruct {
CustomStruct() : B(false) {}
CustomStruct(std::string S, llvm::Optional<int> I, bool B)
: S(S), I(I), B(B) {}
std::string S;
llvm::Optional<int> I;
bool B;
};
inline bool operator==(const CustomStruct &L, const CustomStruct &R) {
return L.S == R.S && L.I == R.I && L.B == R.B;
}
inline std::ostream &operator<<(std::ostream &OS, const CustomStruct &S) {
return OS << "(" << S.S << ", " << (S.I ? std::to_string(*S.I) : "None")
<< ", " << S.B << ")";
}
bool fromJSON(const json::Expr &E, CustomStruct &R) {
ObjectMapper O(E);
if (!O || !O.map("str", R.S) || !O.map("int", R.I))
return false;
O.map("bool", R.B);
return true;
}
TEST(JSONTest, Deserialize) {
std::map<std::string, std::vector<CustomStruct>> R;
CustomStruct ExpectedStruct = {"foo", 42, true};
std::map<std::string, std::vector<CustomStruct>> Expected;
Expr J = obj{{"foo", ary{
obj{
{"str", "foo"},
{"int", 42},
{"bool", true},
{"unknown", "ignored"},
},
obj{{"str", "bar"}},
obj{
{"str", "baz"},
{"bool", "string"}, // OK, deserialize ignores.
},
}}};
Expected["foo"] = {
CustomStruct("foo", 42, true),
CustomStruct("bar", llvm::None, false),
CustomStruct("baz", llvm::None, false),
};
ASSERT_TRUE(fromJSON(J, R));
EXPECT_EQ(R, Expected);
CustomStruct V;
EXPECT_FALSE(fromJSON(nullptr, V)) << "Not an object " << V;
EXPECT_FALSE(fromJSON(obj{}, V)) << "Missing required field " << V;
EXPECT_FALSE(fromJSON(obj{{"str", 1}}, V)) << "Wrong type " << V;
// Optional<T> must parse as the correct type if present.
EXPECT_FALSE(fromJSON(obj{{"str", 1}, {"int", "string"}}, V))
<< "Wrong type for Optional<T> " << V;
}
} // namespace
} // namespace json
} // namespace clangd
} // namespace clang

View File

@@ -0,0 +1,113 @@
//===-- Matchers.h ----------------------------------------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// GMock matchers that aren't specific to particular tests.
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_CLANG_TOOLS_EXTRA_UNITTESTS_CLANGD_MATCHERS_H
#define LLVM_CLANG_TOOLS_EXTRA_UNITTESTS_CLANGD_MATCHERS_H
#include "Protocol.h"
#include "gmock/gmock.h"
namespace clang {
namespace clangd {
using ::testing::Matcher;
// EXPECT_IFF expects matcher if condition is true, and Not(matcher) if false.
// This is hard to write as a function, because matchers may be polymorphic.
#define EXPECT_IFF(condition, value, matcher) \
do { \
if (condition) \
EXPECT_THAT(value, matcher); \
else \
EXPECT_THAT(value, ::testing::Not(matcher)); \
} while (0)
// HasSubsequence(m1, m2, ...) matches a vector containing elements that match
// m1, m2 ... in that order.
//
// SubsequenceMatcher implements this once the type of vector is known.
template <typename T>
class SubsequenceMatcher
: public ::testing::MatcherInterface<const std::vector<T> &> {
std::vector<Matcher<T>> Matchers;
public:
SubsequenceMatcher(std::vector<Matcher<T>> M) : Matchers(M) {}
void DescribeTo(std::ostream *OS) const override {
*OS << "Contains the subsequence [";
const char *Sep = "";
for (const auto &M : Matchers) {
*OS << Sep;
M.DescribeTo(OS);
Sep = ", ";
}
*OS << "]";
}
bool MatchAndExplain(const std::vector<T> &V,
::testing::MatchResultListener *L) const override {
std::vector<int> Matches(Matchers.size());
size_t I = 0;
for (size_t J = 0; I < Matchers.size() && J < V.size(); ++J)
if (Matchers[I].Matches(V[J]))
Matches[I++] = J;
if (I == Matchers.size()) // We exhausted all matchers.
return true;
if (L->IsInterested()) {
*L << "\n Matched:";
for (size_t K = 0; K < I; ++K) {
*L << "\n\t";
Matchers[K].DescribeTo(L->stream());
*L << " ==> " << ::testing::PrintToString(V[Matches[K]]);
}
*L << "\n\t";
Matchers[I].DescribeTo(L->stream());
*L << " ==> no subsequent match";
}
return false;
}
};
// PolySubsequenceMatcher implements a "polymorphic" SubsequenceMatcher.
// It captures the types of the element matchers, and can be converted to
// Matcher<vector<T>> if each matcher can be converted to Matcher<T>.
// This allows HasSubsequence() to accept polymorphic matchers like Not().
template <typename... M> class PolySubsequenceMatcher {
std::tuple<M...> Matchers;
public:
PolySubsequenceMatcher(M &&... Args)
: Matchers(std::make_tuple(std::forward<M>(Args)...)) {}
template <typename T> operator Matcher<const std::vector<T> &>() const {
return ::testing::MakeMatcher(new SubsequenceMatcher<T>(
TypedMatchers<T>(llvm::index_sequence_for<M...>{})));
}
private:
template <typename T, size_t... I>
std::vector<Matcher<T>> TypedMatchers(llvm::index_sequence<I...>) const {
return {std::get<I>(Matchers)...};
}
};
// HasSubsequence(m1, m2, ...) matches a vector containing elements that match
// m1, m2 ... in that order.
// The real implementation is in SubsequenceMatcher.
template <typename... Args>
PolySubsequenceMatcher<Args...> HasSubsequence(Args &&... M) {
return PolySubsequenceMatcher<Args...>(std::forward<Args>(M)...);
}
} // namespace clangd
} // namespace clang
#endif

View File

@@ -0,0 +1,72 @@
//===-- SourceCodeTests.cpp ------------------------------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include "SourceCode.h"
#include "llvm/Support/raw_os_ostream.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
namespace clang{
namespace clangd {
namespace {
MATCHER_P2(Pos, Line, Col, "") {
return arg.line == Line && arg.character == Col;
}
const char File[] = R"(0:0 = 0
1:0 = 8
2:0 = 16)";
TEST(SourceCodeTests, PositionToOffset) {
// line out of bounds
EXPECT_EQ(0u, positionToOffset(File, Position{-1, 2}));
// first line
EXPECT_EQ(0u, positionToOffset(File, Position{0, -1})); // out of range
EXPECT_EQ(0u, positionToOffset(File, Position{0, 0})); // first character
EXPECT_EQ(3u, positionToOffset(File, Position{0, 3})); // middle character
EXPECT_EQ(6u, positionToOffset(File, Position{0, 6})); // last character
EXPECT_EQ(7u, positionToOffset(File, Position{0, 7})); // the newline itself
EXPECT_EQ(8u, positionToOffset(File, Position{0, 8})); // out of range
// middle line
EXPECT_EQ(8u, positionToOffset(File, Position{1, -1})); // out of range
EXPECT_EQ(8u, positionToOffset(File, Position{1, 0})); // first character
EXPECT_EQ(11u, positionToOffset(File, Position{1, 3})); // middle character
EXPECT_EQ(14u, positionToOffset(File, Position{1, 6})); // last character
EXPECT_EQ(15u, positionToOffset(File, Position{1, 7})); // the newline itself
EXPECT_EQ(16u, positionToOffset(File, Position{1, 8})); // out of range
// last line
EXPECT_EQ(16u, positionToOffset(File, Position{2, -1})); // out of range
EXPECT_EQ(16u, positionToOffset(File, Position{2, 0})); // first character
EXPECT_EQ(19u, positionToOffset(File, Position{2, 3})); // middle character
EXPECT_EQ(23u, positionToOffset(File, Position{2, 7})); // last character
EXPECT_EQ(24u, positionToOffset(File, Position{2, 8})); // EOF
EXPECT_EQ(24u, positionToOffset(File, Position{2, 9})); // out of range
// line out of bounds
EXPECT_EQ(24u, positionToOffset(File, Position{3, 1}));
}
TEST(SourceCodeTests, OffsetToPosition) {
EXPECT_THAT(offsetToPosition(File, 0), Pos(0, 0)) << "start of file";
EXPECT_THAT(offsetToPosition(File, 3), Pos(0, 3)) << "in first line";
EXPECT_THAT(offsetToPosition(File, 6), Pos(0, 6)) << "end of first line";
EXPECT_THAT(offsetToPosition(File, 7), Pos(0, 7)) << "first newline";
EXPECT_THAT(offsetToPosition(File, 8), Pos(1, 0)) << "start of second line";
EXPECT_THAT(offsetToPosition(File, 11), Pos(1, 3)) << "in second line";
EXPECT_THAT(offsetToPosition(File, 14), Pos(1, 6)) << "end of second line";
EXPECT_THAT(offsetToPosition(File, 15), Pos(1, 7)) << "second newline";
EXPECT_THAT(offsetToPosition(File, 16), Pos(2, 0)) << "start of last line";
EXPECT_THAT(offsetToPosition(File, 19), Pos(2, 3)) << "in last line";
EXPECT_THAT(offsetToPosition(File, 23), Pos(2, 7)) << "end of last line";
EXPECT_THAT(offsetToPosition(File, 24), Pos(2, 8)) << "EOF";
EXPECT_THAT(offsetToPosition(File, 25), Pos(2, 8)) << "out of bounds";
}
} // namespace
} // namespace clangd
} // namespace clang

View File

@@ -0,0 +1,159 @@
//===-- SymbolCollectorTests.cpp -------------------------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include "index/SymbolCollector.h"
#include "index/SymbolYAML.h"
#include "clang/Index/IndexingAction.h"
#include "clang/Basic/FileManager.h"
#include "clang/Basic/FileSystemOptions.h"
#include "clang/Basic/VirtualFileSystem.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Index/IndexingAction.h"
#include "clang/Tooling/Tooling.h"
#include "llvm/ADT/IntrusiveRefCntPtr.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/MemoryBuffer.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include <memory>
#include <string>
using testing::Eq;
using testing::Field;
using testing::UnorderedElementsAre;
// GMock helpers for matching Symbol.
MATCHER_P(QName, Name, "") {
return (arg.Scope + (arg.Scope.empty() ? "" : "::") + arg.Name).str() == Name;
}
namespace clang {
namespace clangd {
namespace {
class SymbolIndexActionFactory : public tooling::FrontendActionFactory {
public:
SymbolIndexActionFactory() = default;
clang::FrontendAction *create() override {
index::IndexingOptions IndexOpts;
IndexOpts.SystemSymbolFilter =
index::IndexingOptions::SystemSymbolFilterKind::All;
IndexOpts.IndexFunctionLocals = false;
Collector = std::make_shared<SymbolCollector>();
FrontendAction *Action =
index::createIndexingAction(Collector, IndexOpts, nullptr).release();
return Action;
}
std::shared_ptr<SymbolCollector> Collector;
};
class SymbolCollectorTest : public ::testing::Test {
public:
bool runSymbolCollector(StringRef HeaderCode, StringRef MainCode) {
llvm::IntrusiveRefCntPtr<vfs::InMemoryFileSystem> InMemoryFileSystem(
new vfs::InMemoryFileSystem);
llvm::IntrusiveRefCntPtr<FileManager> Files(
new FileManager(FileSystemOptions(), InMemoryFileSystem));
const std::string FileName = "symbol.cc";
const std::string HeaderName = "symbols.h";
auto Factory = llvm::make_unique<SymbolIndexActionFactory>();
tooling::ToolInvocation Invocation(
{"symbol_collector", "-fsyntax-only", "-std=c++11", FileName},
Factory->create(), Files.get(),
std::make_shared<PCHContainerOperations>());
InMemoryFileSystem->addFile(HeaderName, 0,
llvm::MemoryBuffer::getMemBuffer(HeaderCode));
std::string Content = "#include\"" + std::string(HeaderName) + "\"";
Content += "\n" + MainCode.str();
InMemoryFileSystem->addFile(FileName, 0,
llvm::MemoryBuffer::getMemBuffer(Content));
Invocation.run();
Symbols = Factory->Collector->takeSymbols();
return true;
}
protected:
SymbolSlab Symbols;
};
TEST_F(SymbolCollectorTest, CollectSymbol) {
const std::string Header = R"(
class Foo {
void f();
};
void f1();
inline void f2() {}
)";
const std::string Main = R"(
namespace {
void ff() {} // ignore
}
void f1() {}
)";
runSymbolCollector(Header, Main);
EXPECT_THAT(Symbols, UnorderedElementsAre(QName("Foo"), QName("Foo::f"),
QName("f1"), QName("f2")));
}
TEST_F(SymbolCollectorTest, YAMLConversions) {
const std::string YAML1 = R"(
---
ID: 057557CEBF6E6B2DD437FBF60CC58F352D1DF856
Name: 'Foo1'
Scope: 'clang'
SymInfo:
Kind: Function
Lang: Cpp
CanonicalDeclaration:
StartOffset: 0
EndOffset: 1
FilePath: /path/foo.h
...
)";
const std::string YAML2 = R"(
---
ID: 057557CEBF6E6B2DD437FBF60CC58F352D1DF858
Name: 'Foo2'
Scope: 'clang'
SymInfo:
Kind: Function
Lang: Cpp
CanonicalDeclaration:
StartOffset: 10
EndOffset: 12
FilePath: /path/foo.h
...
)";
auto Symbols1 = SymbolFromYAML(YAML1);
EXPECT_THAT(Symbols1,
UnorderedElementsAre(QName("clang::Foo1")));
auto Symbols2 = SymbolFromYAML(YAML2);
EXPECT_THAT(Symbols2,
UnorderedElementsAre(QName("clang::Foo2")));
std::string ConcatenatedYAML =
SymbolToYAML(Symbols1) + SymbolToYAML(Symbols2);
auto ConcatenatedSymbols = SymbolFromYAML(ConcatenatedYAML);
EXPECT_THAT(ConcatenatedSymbols,
UnorderedElementsAre(QName("clang::Foo1"),
QName("clang::Foo2")));
}
} // namespace
} // namespace clangd
} // namespace clang

View File

@@ -0,0 +1,179 @@
//===-- TestFS.cpp ----------------------------------------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include "TestFS.h"
#include "llvm/Support/Errc.h"
#include "gtest/gtest.h"
namespace clang {
namespace clangd {
namespace {
/// An implementation of vfs::FileSystem that only allows access to
/// files and folders inside a set of whitelisted directories.
///
/// FIXME(ibiryukov): should it also emulate access to parents of whitelisted
/// directories with only whitelisted contents?
class FilteredFileSystem : public vfs::FileSystem {
public:
/// The paths inside \p WhitelistedDirs should be absolute
FilteredFileSystem(std::vector<std::string> WhitelistedDirs,
IntrusiveRefCntPtr<vfs::FileSystem> InnerFS)
: WhitelistedDirs(std::move(WhitelistedDirs)), InnerFS(InnerFS) {
assert(std::all_of(WhitelistedDirs.begin(), WhitelistedDirs.end(),
[](const std::string &Path) -> bool {
return llvm::sys::path::is_absolute(Path);
}) &&
"Not all WhitelistedDirs are absolute");
}
virtual llvm::ErrorOr<vfs::Status> status(const Twine &Path) {
if (!isInsideWhitelistedDir(Path))
return llvm::errc::no_such_file_or_directory;
return InnerFS->status(Path);
}
virtual llvm::ErrorOr<std::unique_ptr<vfs::File>>
openFileForRead(const Twine &Path) {
if (!isInsideWhitelistedDir(Path))
return llvm::errc::no_such_file_or_directory;
return InnerFS->openFileForRead(Path);
}
llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>>
getBufferForFile(const Twine &Name, int64_t FileSize = -1,
bool RequiresNullTerminator = true,
bool IsVolatile = false) {
if (!isInsideWhitelistedDir(Name))
return llvm::errc::no_such_file_or_directory;
return InnerFS->getBufferForFile(Name, FileSize, RequiresNullTerminator,
IsVolatile);
}
virtual vfs::directory_iterator dir_begin(const Twine &Dir,
std::error_code &EC) {
if (!isInsideWhitelistedDir(Dir)) {
EC = llvm::errc::no_such_file_or_directory;
return vfs::directory_iterator();
}
return InnerFS->dir_begin(Dir, EC);
}
virtual std::error_code setCurrentWorkingDirectory(const Twine &Path) {
return InnerFS->setCurrentWorkingDirectory(Path);
}
virtual llvm::ErrorOr<std::string> getCurrentWorkingDirectory() const {
return InnerFS->getCurrentWorkingDirectory();
}
bool exists(const Twine &Path) {
if (!isInsideWhitelistedDir(Path))
return false;
return InnerFS->exists(Path);
}
std::error_code makeAbsolute(SmallVectorImpl<char> &Path) const {
return InnerFS->makeAbsolute(Path);
}
private:
bool isInsideWhitelistedDir(const Twine &InputPath) const {
SmallString<128> Path;
InputPath.toVector(Path);
if (makeAbsolute(Path))
return false;
for (const auto &Dir : WhitelistedDirs) {
if (Path.startswith(Dir))
return true;
}
return false;
}
std::vector<std::string> WhitelistedDirs;
IntrusiveRefCntPtr<vfs::FileSystem> InnerFS;
};
/// Create a vfs::FileSystem that has access only to temporary directories
/// (obtained by calling system_temp_directory).
IntrusiveRefCntPtr<vfs::FileSystem> getTempOnlyFS() {
llvm::SmallString<128> TmpDir1;
llvm::sys::path::system_temp_directory(/*erasedOnReboot=*/false, TmpDir1);
llvm::SmallString<128> TmpDir2;
llvm::sys::path::system_temp_directory(/*erasedOnReboot=*/true, TmpDir2);
std::vector<std::string> TmpDirs;
TmpDirs.push_back(TmpDir1.str());
if (TmpDir1 != TmpDir2)
TmpDirs.push_back(TmpDir2.str());
return new FilteredFileSystem(std::move(TmpDirs), vfs::getRealFileSystem());
}
} // namespace
IntrusiveRefCntPtr<vfs::FileSystem>
buildTestFS(llvm::StringMap<std::string> const &Files) {
IntrusiveRefCntPtr<vfs::InMemoryFileSystem> MemFS(
new vfs::InMemoryFileSystem);
for (auto &FileAndContents : Files)
MemFS->addFile(FileAndContents.first(), time_t(),
llvm::MemoryBuffer::getMemBuffer(FileAndContents.second,
FileAndContents.first()));
auto OverlayFS = IntrusiveRefCntPtr<vfs::OverlayFileSystem>(
new vfs::OverlayFileSystem(getTempOnlyFS()));
OverlayFS->pushOverlay(std::move(MemFS));
return OverlayFS;
}
Tagged<IntrusiveRefCntPtr<vfs::FileSystem>>
MockFSProvider::getTaggedFileSystem(PathRef File) {
if (ExpectedFile) {
EXPECT_EQ(*ExpectedFile, File);
}
auto FS = buildTestFS(Files);
return make_tagged(FS, Tag);
}
MockCompilationDatabase::MockCompilationDatabase()
: ExtraClangFlags({"-ffreestanding"}) {} // Avoid implicit stdc-predef.h.
llvm::Optional<tooling::CompileCommand>
MockCompilationDatabase::getCompileCommand(PathRef File) const {
if (ExtraClangFlags.empty())
return llvm::None;
auto CommandLine = ExtraClangFlags;
CommandLine.insert(CommandLine.begin(), "clang");
CommandLine.insert(CommandLine.end(), File.str());
return {tooling::CompileCommand(llvm::sys::path::parent_path(File),
llvm::sys::path::filename(File),
std::move(CommandLine), "")};
}
static const char *getVirtualTestRoot() {
#ifdef LLVM_ON_WIN32
return "C:\\clangd-test";
#else
return "/clangd-test";
#endif
}
llvm::SmallString<32> getVirtualTestFilePath(PathRef File) {
assert(llvm::sys::path::is_relative(File) && "FileName should be relative");
llvm::SmallString<32> Path;
llvm::sys::path::append(Path, getVirtualTestRoot(), File);
return Path;
}
} // namespace clangd
} // namespace clang

View File

@@ -0,0 +1,52 @@
//===-- TestFS.h ------------------------------------------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// Allows setting up fake filesystem environments for tests.
//
//===----------------------------------------------------------------------===//
#include "ClangdServer.h"
#include "clang/Basic/VirtualFileSystem.h"
#include "llvm/ADT/IntrusiveRefCntPtr.h"
#include "llvm/Support/Path.h"
namespace clang {
namespace clangd {
// Builds a VFS that provides access to the provided files, plus temporary
// directories.
llvm::IntrusiveRefCntPtr<vfs::FileSystem>
buildTestFS(llvm::StringMap<std::string> const &Files);
// A VFS provider that returns TestFSes containing a provided set of files.
class MockFSProvider : public FileSystemProvider {
public:
Tagged<IntrusiveRefCntPtr<vfs::FileSystem>>
getTaggedFileSystem(PathRef File) override;
llvm::Optional<SmallString<32>> ExpectedFile;
llvm::StringMap<std::string> Files;
VFSTag Tag = VFSTag();
};
// A Compilation database that returns a fixed set of compile flags.
class MockCompilationDatabase : public GlobalCompilationDatabase {
public:
MockCompilationDatabase();
llvm::Optional<tooling::CompileCommand>
getCompileCommand(PathRef File) const override;
std::vector<std::string> ExtraClangFlags;
};
// Returns a suitable absolute path for this OS.
llvm::SmallString<32> getVirtualTestFilePath(PathRef File);
} // namespace clangd
} // namespace clang

View File

@@ -0,0 +1,130 @@
//===-- TraceTests.cpp - Tracing unit tests ---------------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include "Context.h"
#include "Trace.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/Support/SourceMgr.h"
#include "llvm/Support/Threading.h"
#include "llvm/Support/YAMLParser.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
namespace clang {
namespace clangd {
namespace {
using namespace llvm;
MATCHER_P(StringNode, Val, "") {
if (arg->getType() != yaml::Node::NK_Scalar) {
*result_listener << "is a " << arg->getVerbatimTag();
return false;
}
SmallString<32> S;
return Val == static_cast<yaml::ScalarNode *>(arg)->getValue(S);
}
// Checks that N is a Mapping (JS object) with the expected scalar properties.
// The object must have all the Expected properties, but may have others.
bool VerifyObject(yaml::Node &N, std::map<std::string, std::string> Expected) {
auto *M = dyn_cast<yaml::MappingNode>(&N);
if (!M) {
ADD_FAILURE() << "Not an object";
return false;
}
bool Match = true;
SmallString<32> Tmp;
for (auto Prop : *M) {
auto *K = dyn_cast_or_null<yaml::ScalarNode>(Prop.getKey());
if (!K)
continue;
std::string KS = K->getValue(Tmp).str();
auto I = Expected.find(KS);
if (I == Expected.end())
continue; // Ignore properties with no assertion.
auto *V = dyn_cast_or_null<yaml::ScalarNode>(Prop.getValue());
if (!V) {
ADD_FAILURE() << KS << " is not a string";
Match = false;
}
std::string VS = V->getValue(Tmp).str();
if (VS != I->second) {
ADD_FAILURE() << KS << " expected " << I->second << " but actual " << VS;
Match = false;
}
Expected.erase(I);
}
for (const auto &P : Expected) {
ADD_FAILURE() << P.first << " missing, expected " << P.second;
Match = false;
}
return Match;
}
TEST(TraceTest, SmokeTest) {
// Capture some events.
std::string JSON;
{
raw_string_ostream OS(JSON);
auto JSONTracer = trace::createJSONTracer(OS);
trace::Session Session(*JSONTracer);
{
trace::Span S(Context::empty(), "A");
trace::log(Context::empty(), "B");
}
}
// Get the root JSON object using the YAML parser.
SourceMgr SM;
yaml::Stream Stream(JSON, SM);
auto Doc = Stream.begin();
ASSERT_NE(Doc, Stream.end());
auto *Root = dyn_cast_or_null<yaml::MappingNode>(Doc->getRoot());
ASSERT_NE(Root, nullptr) << "Root should be an object";
// Check whether we expect thread name events on this platform.
SmallString<32> ThreadName;
llvm::get_thread_name(ThreadName);
bool ThreadsHaveNames = !ThreadName.empty();
// We expect in order:
// displayTimeUnit: "ns"
// traceEvents: [process name, thread name, start span, log, end span]
// (The order doesn't matter, but the YAML parser is awkward to use otherwise)
auto Prop = Root->begin();
ASSERT_NE(Prop, Root->end()) << "Expected displayTimeUnit property";
ASSERT_THAT(Prop->getKey(), StringNode("displayTimeUnit"));
EXPECT_THAT(Prop->getValue(), StringNode("ns"));
ASSERT_NE(++Prop, Root->end()) << "Expected traceEvents property";
EXPECT_THAT(Prop->getKey(), StringNode("traceEvents"));
auto *Events = dyn_cast_or_null<yaml::SequenceNode>(Prop->getValue());
ASSERT_NE(Events, nullptr) << "traceEvents should be an array";
auto Event = Events->begin();
ASSERT_NE(Event, Events->end()) << "Expected process name";
EXPECT_TRUE(VerifyObject(*Event, {{"ph", "M"}, {"name", "process_name"}}));
if (ThreadsHaveNames) {
ASSERT_NE(++Event, Events->end()) << "Expected thread name";
EXPECT_TRUE(VerifyObject(*Event, {{"ph", "M"}, {"name", "thread_name"}}));
}
ASSERT_NE(++Event, Events->end()) << "Expected span start";
EXPECT_TRUE(VerifyObject(*Event, {{"ph", "B"}, {"name", "A"}}));
ASSERT_NE(++Event, Events->end()) << "Expected log message";
EXPECT_TRUE(VerifyObject(*Event, {{"ph", "i"}, {"name", "Log"}}));
ASSERT_NE(++Event, Events->end()) << "Expected span end";
EXPECT_TRUE(VerifyObject(*Event, {{"ph", "E"}}));
ASSERT_EQ(++Event, Events->end());
ASSERT_EQ(++Prop, Root->end());
}
} // namespace
} // namespace clangd
} // namespace clang

View File

@@ -0,0 +1,218 @@
//===-- XRefsTests.cpp ---------------------------*- C++ -*--------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include "Annotations.h"
#include "ClangdUnit.h"
#include "Matchers.h"
#include "XRefs.h"
#include "clang/Frontend/CompilerInvocation.h"
#include "clang/Frontend/PCHContainerOperations.h"
#include "clang/Frontend/Utils.h"
#include "llvm/Support/Path.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
namespace clang {
namespace clangd {
using namespace llvm;
void PrintTo(const DocumentHighlight &V, std::ostream *O) {
llvm::raw_os_ostream OS(*O);
OS << V.range;
if (V.kind == DocumentHighlightKind::Read)
OS << "(r)";
if (V.kind == DocumentHighlightKind::Write)
OS << "(w)";
}
namespace {
using testing::ElementsAre;
using testing::Field;
using testing::Matcher;
using testing::UnorderedElementsAreArray;
// FIXME: this is duplicated with FileIndexTests. Share it.
ParsedAST build(StringRef Code) {
auto CI = createInvocationFromCommandLine({"clang", "-xc++", "Foo.cpp"});
auto Buf = MemoryBuffer::getMemBuffer(Code);
auto AST = ParsedAST::Build(
Context::empty(), std::move(CI), nullptr, std::move(Buf),
std::make_shared<PCHContainerOperations>(), vfs::getRealFileSystem());
assert(AST.hasValue());
return std::move(*AST);
}
// Extracts ranges from an annotated example, and constructs a matcher for a
// highlight set. Ranges should be named $read/$write as appropriate.
Matcher<const std::vector<DocumentHighlight> &>
HighlightsFrom(const Annotations &Test) {
std::vector<DocumentHighlight> Expected;
auto Add = [&](const Range &R, DocumentHighlightKind K) {
Expected.emplace_back();
Expected.back().range = R;
Expected.back().kind = K;
};
for (const auto &Range : Test.ranges())
Add(Range, DocumentHighlightKind::Text);
for (const auto &Range : Test.ranges("read"))
Add(Range, DocumentHighlightKind::Read);
for (const auto &Range : Test.ranges("write"))
Add(Range, DocumentHighlightKind::Write);
return UnorderedElementsAreArray(Expected);
}
TEST(HighlightsTest, All) {
const char *Tests[] = {
R"cpp(// Local variable
int main() {
int [[bonjour]];
$write[[^bonjour]] = 2;
int test1 = $read[[bonjour]];
}
)cpp",
R"cpp(// Struct
namespace ns1 {
struct [[MyClass]] {
static void foo([[MyClass]]*) {}
};
} // namespace ns1
int main() {
ns1::[[My^Class]]* Params;
}
)cpp",
R"cpp(// Function
int [[^foo]](int) {}
int main() {
[[foo]]([[foo]](42));
auto *X = &[[foo]];
}
)cpp",
};
for (const char *Test : Tests) {
Annotations T(Test);
auto AST = build(T.code());
EXPECT_THAT(findDocumentHighlights(Context::empty(), AST, T.point()),
HighlightsFrom(T))
<< Test;
}
}
MATCHER_P(RangeIs, R, "") { return arg.range == R; }
TEST(GoToDefinition, All) {
const char *Tests[] = {
R"cpp(// Local variable
int main() {
[[int bonjour]];
^bonjour = 2;
int test1 = bonjour;
}
)cpp",
R"cpp(// Struct
namespace ns1 {
[[struct MyClass {}]];
} // namespace ns1
int main() {
ns1::My^Class* Params;
}
)cpp",
R"cpp(// Function definition via pointer
[[int foo(int) {}]]
int main() {
auto *X = &^foo;
}
)cpp",
R"cpp(// Function declaration via call
[[int foo(int)]];
int main() {
return ^foo(42);
}
)cpp",
R"cpp(// Field
struct Foo { [[int x]]; };
int main() {
Foo bar;
bar.^x;
}
)cpp",
R"cpp(// Field, member initializer
struct Foo {
[[int x]];
Foo() : ^x(0) {}
};
)cpp",
R"cpp(// Field, GNU old-style field designator
struct Foo { [[int x]]; };
int main() {
Foo bar = { ^x : 1 };
}
)cpp",
R"cpp(// Field, field designator
struct Foo { [[int x]]; };
int main() {
Foo bar = { .^x = 2 };
}
)cpp",
R"cpp(// Method call
struct Foo { [[int x()]]; };
int main() {
Foo bar;
bar.^x();
}
)cpp",
R"cpp(// Typedef
[[typedef int Foo]];
int main() {
^Foo bar;
}
)cpp",
/* FIXME: clangIndex doesn't handle template type parameters
R"cpp(// Template type parameter
template <[[typename T]]>
void foo() { ^T t; }
)cpp", */
R"cpp(// Namespace
[[namespace ns {
struct Foo { static void bar(); }
}]] // namespace ns
int main() { ^ns::Foo::bar(); }
)cpp",
R"cpp(// Macro
#define MACRO 0
#define [[MACRO 1]]
int main() { return ^MACRO; }
#define MACRO 2
#undef macro
)cpp",
};
for (const char *Test : Tests) {
Annotations T(Test);
auto AST = build(T.code());
EXPECT_THAT(findDefinitions(Context::empty(), AST, T.point()),
ElementsAre(RangeIs(T.range())))
<< Test;
}
}
} // namespace
} // namespace clangd
} // namespace clang