mirror of
https://github.com/Gericom/teak-llvm.git
synced 2025-06-19 11:35:51 -04:00

Summary: The historic behavior of TestTU is to gather diagnostics and otherwise ignore them. So if a test has a syntax error, and doesn't assert diagnostics, it silently misbehaves. This can be annoying when developing tests, as evidenced by various tests gaining "assert no diagnostics" where that's not really the point of the test. This patch aims to make that default behavior. For the first error (not warning), TestTU will call ADD_FAILURE(). This can be suppressed with a comment containing "error-ok". For now that will suppress any errors in the TU. We can make this stricter later -verify style. (-verify itself is hard to reuse because of DiagnosticConsumer interfaces...) A magic-comment was chosen over a TestTU option because of table-driven tests. In addition to the behavior change, this patch: - adds //error-ok where we're knowingly testing invalid code (e.g. for diagnostics, crash-resilience, or token-level tests) - fixes a bunch of errors in the checked-in tests, mostly trivial (missing ;) - removes a bunch of now-redundant instances of "assert no diagnostics" Reviewers: kadircet Subscribers: ilya-biryukov, MaskRay, jkorous, arphaman, usaxena95, cfe-commits Tags: #clang Differential Revision: https://reviews.llvm.org/D73199
519 lines
16 KiB
C++
519 lines
16 KiB
C++
//===-- SelectionTests.cpp - ----------------------------------------------===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
#include "Annotations.h"
|
|
#include "Selection.h"
|
|
#include "SourceCode.h"
|
|
#include "TestTU.h"
|
|
#include "clang/AST/Decl.h"
|
|
#include "llvm/Support/Casting.h"
|
|
#include "gmock/gmock.h"
|
|
#include "gtest/gtest.h"
|
|
|
|
namespace clang {
|
|
namespace clangd {
|
|
namespace {
|
|
using ::testing::UnorderedElementsAreArray;
|
|
|
|
SelectionTree makeSelectionTree(const StringRef MarkedCode, ParsedAST &AST) {
|
|
Annotations Test(MarkedCode);
|
|
switch (Test.points().size()) {
|
|
case 1: // Point selection.
|
|
return SelectionTree(AST.getASTContext(), AST.getTokens(),
|
|
cantFail(positionToOffset(Test.code(), Test.point())));
|
|
case 2: // Range selection.
|
|
return SelectionTree(
|
|
AST.getASTContext(), AST.getTokens(),
|
|
cantFail(positionToOffset(Test.code(), Test.points()[0])),
|
|
cantFail(positionToOffset(Test.code(), Test.points()[1])));
|
|
default:
|
|
ADD_FAILURE() << "Expected 1-2 points for selection.\n" << MarkedCode;
|
|
return SelectionTree(AST.getASTContext(), AST.getTokens(), 0u, 0u);
|
|
}
|
|
}
|
|
|
|
Range nodeRange(const SelectionTree::Node *N, ParsedAST &AST) {
|
|
if (!N)
|
|
return Range{};
|
|
const SourceManager &SM = AST.getSourceManager();
|
|
const LangOptions &LangOpts = AST.getLangOpts();
|
|
StringRef Buffer = SM.getBufferData(SM.getMainFileID());
|
|
if (llvm::isa_and_nonnull<TranslationUnitDecl>(N->ASTNode.get<Decl>()))
|
|
return Range{Position{}, offsetToPosition(Buffer, Buffer.size())};
|
|
auto FileRange =
|
|
toHalfOpenFileRange(SM, LangOpts, N->ASTNode.getSourceRange());
|
|
assert(FileRange && "We should be able to get the File Range");
|
|
return Range{
|
|
offsetToPosition(Buffer, SM.getFileOffset(FileRange->getBegin())),
|
|
offsetToPosition(Buffer, SM.getFileOffset(FileRange->getEnd()))};
|
|
}
|
|
|
|
std::string nodeKind(const SelectionTree::Node *N) {
|
|
return N ? N->kind() : "<null>";
|
|
}
|
|
|
|
std::vector<const SelectionTree::Node *> allNodes(const SelectionTree &T) {
|
|
std::vector<const SelectionTree::Node *> Result = {&T.root()};
|
|
for (unsigned I = 0; I < Result.size(); ++I) {
|
|
const SelectionTree::Node *N = Result[I];
|
|
Result.insert(Result.end(), N->Children.begin(), N->Children.end());
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
// Returns true if Common is a descendent of Root.
|
|
// Verifies nothing is selected above Common.
|
|
bool verifyCommonAncestor(const SelectionTree::Node &Root,
|
|
const SelectionTree::Node *Common,
|
|
StringRef MarkedCode) {
|
|
if (&Root == Common)
|
|
return true;
|
|
if (Root.Selected)
|
|
ADD_FAILURE() << "Selected nodes outside common ancestor\n" << MarkedCode;
|
|
bool Seen = false;
|
|
for (const SelectionTree::Node *Child : Root.Children)
|
|
if (verifyCommonAncestor(*Child, Common, MarkedCode)) {
|
|
if (Seen)
|
|
ADD_FAILURE() << "Saw common ancestor twice\n" << MarkedCode;
|
|
Seen = true;
|
|
}
|
|
return Seen;
|
|
}
|
|
|
|
TEST(SelectionTest, CommonAncestor) {
|
|
struct Case {
|
|
// Selection is between ^marks^.
|
|
// common ancestor marked with a [[range]].
|
|
const char *Code;
|
|
const char *CommonAncestorKind;
|
|
};
|
|
Case Cases[] = {
|
|
{
|
|
R"cpp(
|
|
template <typename T>
|
|
int x = [[T::^U::]]ccc();
|
|
)cpp",
|
|
"NestedNameSpecifierLoc",
|
|
},
|
|
{
|
|
R"cpp(
|
|
struct AAA { struct BBB { static int ccc(); };};
|
|
int x = AAA::[[B^B^B]]::ccc();
|
|
)cpp",
|
|
"RecordTypeLoc",
|
|
},
|
|
{
|
|
R"cpp(
|
|
struct AAA { struct BBB { static int ccc(); };};
|
|
int x = AAA::[[B^BB^]]::ccc();
|
|
)cpp",
|
|
"RecordTypeLoc",
|
|
},
|
|
{
|
|
R"cpp(
|
|
struct AAA { struct BBB { static int ccc(); };};
|
|
int x = [[AAA::BBB::c^c^c]]();
|
|
)cpp",
|
|
"DeclRefExpr",
|
|
},
|
|
{
|
|
R"cpp(
|
|
struct AAA { struct BBB { static int ccc(); };};
|
|
int x = [[AAA::BBB::cc^c(^)]];
|
|
)cpp",
|
|
"CallExpr",
|
|
},
|
|
|
|
{
|
|
R"cpp(
|
|
void foo() { [[if (1^11) { return; } else {^ }]] }
|
|
)cpp",
|
|
"IfStmt",
|
|
},
|
|
{
|
|
R"cpp(
|
|
int x(int);
|
|
#define M(foo) x(foo)
|
|
int a = 42;
|
|
int b = M([[^a]]);
|
|
)cpp",
|
|
"DeclRefExpr",
|
|
},
|
|
{
|
|
R"cpp(
|
|
void foo();
|
|
#define CALL_FUNCTION(X) X()
|
|
void bar() { CALL_FUNCTION([[f^o^o]]); }
|
|
)cpp",
|
|
"DeclRefExpr",
|
|
},
|
|
{
|
|
R"cpp(
|
|
void foo();
|
|
#define CALL_FUNCTION(X) X()
|
|
void bar() { [[CALL_FUNC^TION(fo^o)]]; }
|
|
)cpp",
|
|
"CallExpr",
|
|
},
|
|
{
|
|
R"cpp(
|
|
void foo();
|
|
#define CALL_FUNCTION(X) X()
|
|
void bar() { [[C^ALL_FUNC^TION(foo)]]; }
|
|
)cpp",
|
|
"CallExpr",
|
|
},
|
|
{
|
|
R"cpp(
|
|
void foo();
|
|
#define CALL_FUNCTION(X) X^()^
|
|
void bar() { CALL_FUNCTION(foo); }
|
|
)cpp",
|
|
nullptr,
|
|
},
|
|
{
|
|
R"cpp(
|
|
struct S { S(const char*); };
|
|
S [[s ^= "foo"]];
|
|
)cpp",
|
|
"CXXConstructExpr",
|
|
},
|
|
{
|
|
R"cpp(
|
|
struct S { S(const char*); };
|
|
[[S ^s = "foo"]];
|
|
)cpp",
|
|
"VarDecl",
|
|
},
|
|
{
|
|
R"cpp(
|
|
[[^void]] (*S)(int) = nullptr;
|
|
)cpp",
|
|
"BuiltinTypeLoc",
|
|
},
|
|
{
|
|
R"cpp(
|
|
[[void (*S)^(int)]] = nullptr;
|
|
)cpp",
|
|
"FunctionProtoTypeLoc",
|
|
},
|
|
{
|
|
R"cpp(
|
|
[[void (^*S)(int)]] = nullptr;
|
|
)cpp",
|
|
"FunctionProtoTypeLoc",
|
|
},
|
|
{
|
|
R"cpp(
|
|
[[void (*^S)(int) = nullptr]];
|
|
)cpp",
|
|
"VarDecl",
|
|
},
|
|
{
|
|
R"cpp(
|
|
[[void ^(*S)(int)]] = nullptr;
|
|
)cpp",
|
|
"FunctionProtoTypeLoc",
|
|
},
|
|
{
|
|
R"cpp(
|
|
struct S {
|
|
int foo() const;
|
|
int bar() { return [[f^oo]](); }
|
|
};
|
|
)cpp",
|
|
"MemberExpr", // Not implicit CXXThisExpr, or its implicit cast!
|
|
},
|
|
{
|
|
R"cpp(
|
|
auto lambda = [](const char*){ return 0; };
|
|
int x = lambda([["y^"]]);
|
|
)cpp",
|
|
"StringLiteral", // Not DeclRefExpr to operator()!
|
|
},
|
|
|
|
// Point selections.
|
|
{"void foo() { [[^foo]](); }", "DeclRefExpr"},
|
|
{"void foo() { [[f^oo]](); }", "DeclRefExpr"},
|
|
{"void foo() { [[fo^o]](); }", "DeclRefExpr"},
|
|
{"void foo() { [[foo^()]]; }", "CallExpr"},
|
|
{"void foo() { [[foo^]] (); }", "DeclRefExpr"},
|
|
{"int bar; void foo() [[{ foo (); }]]^", "CompoundStmt"},
|
|
{"int x = [[42]]^;", "IntegerLiteral"},
|
|
|
|
// Ignores whitespace, comments, and semicolons in the selection.
|
|
{"void foo() { [[foo^()]]; /*comment*/^}", "CallExpr"},
|
|
|
|
// Tricky case: FunctionTypeLoc in FunctionDecl has a hole in it.
|
|
{"[[^void]] foo();", "BuiltinTypeLoc"},
|
|
{"[[void foo^()]];", "FunctionProtoTypeLoc"},
|
|
{"[[^void foo^()]];", "FunctionDecl"},
|
|
{"[[void ^foo()]];", "FunctionDecl"},
|
|
// Tricky case: two VarDecls share a specifier.
|
|
{"[[int ^a]], b;", "VarDecl"},
|
|
{"[[int a, ^b]];", "VarDecl"},
|
|
// Tricky case: CXXConstructExpr wants to claim the whole init range.
|
|
{
|
|
R"cpp(
|
|
struct X { X(int); };
|
|
class Y {
|
|
X x;
|
|
Y() : [[^x(4)]] {}
|
|
};
|
|
)cpp",
|
|
"CXXCtorInitializer", // Not the CXXConstructExpr!
|
|
},
|
|
// Tricky case: anonymous struct is a sibling of the VarDecl.
|
|
{"[[st^ruct {int x;}]] y;", "CXXRecordDecl"},
|
|
{"[[struct {int x;} ^y]];", "VarDecl"},
|
|
{"struct {[[int ^x]];} y;", "FieldDecl"},
|
|
// FIXME: the AST has no location info for qualifiers.
|
|
{"const [[a^uto]] x = 42;", "AutoTypeLoc"},
|
|
{"[[co^nst auto x = 42]];", "VarDecl"},
|
|
|
|
{"^", nullptr},
|
|
{"void foo() { [[foo^^]] (); }", "DeclRefExpr"},
|
|
|
|
// FIXME: Ideally we'd get a declstmt or the VarDecl itself here.
|
|
// This doesn't happen now; the RAV doesn't traverse a node containing ;.
|
|
{"int x = 42;^", nullptr},
|
|
|
|
// Common ancestor is logically TUDecl, but we never return that.
|
|
{"^int x; int y;^", nullptr},
|
|
|
|
// Node types that have caused problems in the past.
|
|
{"template <typename T> void foo() { [[^T]] t; }",
|
|
"TemplateTypeParmTypeLoc"},
|
|
|
|
// No crash
|
|
{
|
|
R"cpp(
|
|
template <class T> struct Foo {};
|
|
template <[[template<class> class /*cursor here*/^U]]>
|
|
struct Foo<U<int>*> {};
|
|
)cpp",
|
|
"TemplateTemplateParmDecl"},
|
|
|
|
// Foreach has a weird AST, ensure we can select parts of the range init.
|
|
// This used to fail, because the DeclStmt for C claimed the whole range.
|
|
{
|
|
R"cpp(
|
|
struct Str {
|
|
const char *begin();
|
|
const char *end();
|
|
};
|
|
Str makeStr(const char*);
|
|
void loop() {
|
|
for (const char C : [[mak^eStr("foo"^)]])
|
|
;
|
|
}
|
|
)cpp",
|
|
"CallExpr"},
|
|
|
|
// User-defined literals are tricky: is 12_i one token or two?
|
|
// For now we treat it as one, and the UserDefinedLiteral as a leaf.
|
|
{
|
|
R"cpp(
|
|
struct Foo{};
|
|
Foo operator""_ud(unsigned long long);
|
|
Foo x = [[^12_ud]];
|
|
)cpp",
|
|
"UserDefinedLiteral"},
|
|
{
|
|
R"cpp(
|
|
int a;
|
|
decltype([[^a]] + a) b;
|
|
)cpp",
|
|
"DeclRefExpr"},
|
|
};
|
|
for (const Case &C : Cases) {
|
|
Annotations Test(C.Code);
|
|
|
|
TestTU TU;
|
|
TU.Code = Test.code();
|
|
|
|
// FIXME: Auto-completion in a template requires disabling delayed template
|
|
// parsing.
|
|
TU.ExtraArgs.push_back("-fno-delayed-template-parsing");
|
|
|
|
auto AST = TU.build();
|
|
auto T = makeSelectionTree(C.Code, AST);
|
|
EXPECT_EQ("TranslationUnitDecl", nodeKind(&T.root())) << C.Code;
|
|
|
|
if (Test.ranges().empty()) {
|
|
// If no [[range]] is marked in the example, there should be no selection.
|
|
EXPECT_FALSE(T.commonAncestor()) << C.Code << "\n" << T;
|
|
} else {
|
|
// If there is an expected selection, common ancestor should exist
|
|
// with the appropriate node type.
|
|
EXPECT_EQ(C.CommonAncestorKind, nodeKind(T.commonAncestor()))
|
|
<< C.Code << "\n"
|
|
<< T;
|
|
// Convert the reported common ancestor to a range and verify it.
|
|
EXPECT_EQ(nodeRange(T.commonAncestor(), AST), Test.range())
|
|
<< C.Code << "\n"
|
|
<< T;
|
|
|
|
// Check that common ancestor is reachable on exactly one path from root,
|
|
// and no nodes outside it are selected.
|
|
EXPECT_TRUE(verifyCommonAncestor(T.root(), T.commonAncestor(), C.Code))
|
|
<< C.Code;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Regression test: this used to match the injected X, not the outer X.
|
|
TEST(SelectionTest, InjectedClassName) {
|
|
const char* Code = "struct ^X { int x; };";
|
|
auto AST = TestTU::withCode(Annotations(Code).code()).build();
|
|
auto T = makeSelectionTree(Code, AST);
|
|
ASSERT_EQ("CXXRecordDecl", nodeKind(T.commonAncestor())) << T;
|
|
auto *D = dyn_cast<CXXRecordDecl>(T.commonAncestor()->ASTNode.get<Decl>());
|
|
EXPECT_FALSE(D->isInjectedClassName());
|
|
}
|
|
|
|
// FIXME: Doesn't select the binary operator node in
|
|
// #define FOO(X) X + 1
|
|
// int a, b = [[FOO(a)]];
|
|
TEST(SelectionTest, Selected) {
|
|
// Selection with ^marks^.
|
|
// Partially selected nodes marked with a [[range]].
|
|
// Completely selected nodes marked with a $C[[range]].
|
|
const char *Cases[] = {
|
|
R"cpp( int abc, xyz = [[^ab^c]]; )cpp",
|
|
R"cpp( int abc, xyz = [[a^bc^]]; )cpp",
|
|
R"cpp( int abc, xyz = $C[[^abc^]]; )cpp",
|
|
R"cpp(
|
|
void foo() {
|
|
[[if ([[1^11]]) $C[[{
|
|
$C[[return]];
|
|
}]] else [[{^
|
|
}]]]]
|
|
char z;
|
|
}
|
|
)cpp",
|
|
R"cpp(
|
|
template <class T>
|
|
struct unique_ptr {};
|
|
void foo(^$C[[unique_ptr<$C[[unique_ptr<$C[[int]]>]]>]]^ a) {}
|
|
)cpp",
|
|
R"cpp(int a = [[5 >^> 1]];)cpp",
|
|
R"cpp(
|
|
#define ECHO(X) X
|
|
ECHO(EC^HO($C[[int]]) EC^HO(a));
|
|
)cpp",
|
|
R"cpp( $C[[^$C[[int]] a^]]; )cpp",
|
|
R"cpp( $C[[^$C[[int]] a = $C[[5]]^]]; )cpp",
|
|
};
|
|
for (const char *C : Cases) {
|
|
Annotations Test(C);
|
|
auto AST = TestTU::withCode(Test.code()).build();
|
|
auto T = makeSelectionTree(C, AST);
|
|
|
|
std::vector<Range> Complete, Partial;
|
|
for (const SelectionTree::Node *N : allNodes(T))
|
|
if (N->Selected == SelectionTree::Complete)
|
|
Complete.push_back(nodeRange(N, AST));
|
|
else if (N->Selected == SelectionTree::Partial)
|
|
Partial.push_back(nodeRange(N, AST));
|
|
EXPECT_THAT(Complete, UnorderedElementsAreArray(Test.ranges("C"))) << C;
|
|
EXPECT_THAT(Partial, UnorderedElementsAreArray(Test.ranges())) << C;
|
|
}
|
|
}
|
|
|
|
TEST(SelectionTest, PathologicalPreprocessor) {
|
|
const char *Case = R"cpp(
|
|
#define MACRO while(1)
|
|
void test() {
|
|
#include "Expand.inc"
|
|
br^eak;
|
|
}
|
|
)cpp";
|
|
Annotations Test(Case);
|
|
auto TU = TestTU::withCode(Test.code());
|
|
TU.AdditionalFiles["Expand.inc"] = "MACRO\n";
|
|
auto AST = TU.build();
|
|
EXPECT_THAT(AST.getDiagnostics(), ::testing::IsEmpty());
|
|
auto T = makeSelectionTree(Case, AST);
|
|
|
|
EXPECT_EQ("BreakStmt", T.commonAncestor()->kind());
|
|
EXPECT_EQ("WhileStmt", T.commonAncestor()->Parent->kind());
|
|
}
|
|
|
|
TEST(SelectionTest, IncludedFile) {
|
|
const char *Case = R"cpp(
|
|
void test() {
|
|
#include "Exp^and.inc"
|
|
break;
|
|
}
|
|
)cpp";
|
|
Annotations Test(Case);
|
|
auto TU = TestTU::withCode(Test.code());
|
|
TU.AdditionalFiles["Expand.inc"] = "while(1)\n";
|
|
auto AST = TU.build();
|
|
auto T = makeSelectionTree(Case, AST);
|
|
|
|
EXPECT_EQ("WhileStmt", T.commonAncestor()->kind());
|
|
}
|
|
|
|
TEST(SelectionTest, MacroArgExpansion) {
|
|
// If a macro arg is expanded several times, we consider them all selected.
|
|
const char *Case = R"cpp(
|
|
int mul(int, int);
|
|
#define SQUARE(X) mul(X, X);
|
|
int nine = SQUARE(^3);
|
|
)cpp";
|
|
Annotations Test(Case);
|
|
auto AST = TestTU::withCode(Test.code()).build();
|
|
auto T = makeSelectionTree(Case, AST);
|
|
// Unfortunately, this makes the common ancestor the CallExpr...
|
|
// FIXME: hack around this by picking one?
|
|
EXPECT_EQ("CallExpr", T.commonAncestor()->kind());
|
|
EXPECT_FALSE(T.commonAncestor()->Selected);
|
|
EXPECT_EQ(2u, T.commonAncestor()->Children.size());
|
|
for (const auto* N : T.commonAncestor()->Children) {
|
|
EXPECT_EQ("IntegerLiteral", N->kind());
|
|
EXPECT_TRUE(N->Selected);
|
|
}
|
|
|
|
// Verify that the common assert() macro doesn't suffer from this.
|
|
// (This is because we don't associate the stringified token with the arg).
|
|
Case = R"cpp(
|
|
void die(const char*);
|
|
#define assert(x) (x ? (void)0 : die(#x))
|
|
void foo() { assert(^42); }
|
|
)cpp";
|
|
Test = Annotations(Case);
|
|
AST = TestTU::withCode(Test.code()).build();
|
|
T = makeSelectionTree(Case, AST);
|
|
|
|
EXPECT_EQ("IntegerLiteral", T.commonAncestor()->kind());
|
|
}
|
|
|
|
TEST(SelectionTest, Implicit) {
|
|
const char* Test = R"cpp(
|
|
struct S { S(const char*); };
|
|
int f(S);
|
|
int x = f("^");
|
|
)cpp";
|
|
auto AST = TestTU::withCode(Annotations(Test).code()).build();
|
|
auto T = makeSelectionTree(Test, AST);
|
|
|
|
const SelectionTree::Node *Str = T.commonAncestor();
|
|
EXPECT_EQ("StringLiteral", nodeKind(Str)) << "Implicit selected?";
|
|
EXPECT_EQ("ImplicitCastExpr", nodeKind(Str->Parent));
|
|
EXPECT_EQ("CXXConstructExpr", nodeKind(Str->Parent->Parent));
|
|
EXPECT_EQ(Str, &Str->Parent->Parent->ignoreImplicit())
|
|
<< "Didn't unwrap " << nodeKind(&Str->Parent->Parent->ignoreImplicit());
|
|
|
|
EXPECT_EQ("CXXConstructExpr", nodeKind(&Str->outerImplicit()));
|
|
}
|
|
|
|
} // namespace
|
|
} // namespace clangd
|
|
} // namespace clang
|