Skip to content
This repository was archived by the owner on Apr 23, 2020. It is now read-only.

Commit 497e112

Browse files
committed
[Testing] Move clangd::Annotations to llvm testing support
Summary: Annotations allow writing nice-looking unit test code when one needs access to locations from the source code, e.g. running code completion at particular offsets in a file. See comments in Annotations.cpp for more details on the API. Also got rid of a duplicate annotations parsing code in clang's code complete tests. Reviewers: gribozavr, sammccall Reviewed By: gribozavr Subscribers: mgorny, hiraditya, ioeric, MaskRay, jkorous, arphaman, kadircet, jdoerfert, cfe-commits, llvm-commits Tags: #clang, #llvm Differential Revision: https://reviews.llvm.org/D59814 git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@359179 91177308-0d34-0410-b5e6-96231b3b80d8
1 parent 774be28 commit 497e112

File tree

5 files changed

+299
-0
lines changed

5 files changed

+299
-0
lines changed

Diff for: include/llvm/Testing/Support/Annotations.h

+90
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
//===--- Annotations.h - Annotated source code for tests ---------*- C++-*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
#ifndef LLVM_TESTING_SUPPORT_ANNOTATIONS_H
9+
#define LLVM_TESTING_SUPPORT_ANNOTATIONS_H
10+
11+
#include "llvm/ADT/SmallVector.h"
12+
#include "llvm/ADT/StringMap.h"
13+
#include "llvm/ADT/StringRef.h"
14+
#include <tuple>
15+
#include <vector>
16+
17+
namespace llvm {
18+
19+
/// Annotations lets you mark points and ranges inside source code, for tests:
20+
///
21+
/// Annotations Example(R"cpp(
22+
/// int complete() { x.pri^ } // ^ indicates a point
23+
/// void err() { [["hello" == 42]]; } // [[this is a range]]
24+
/// $definition^class Foo{}; // points can be named: "definition"
25+
/// $fail[[static_assert(false, "")]] // ranges can be named too: "fail"
26+
/// )cpp");
27+
///
28+
/// StringRef Code = Example.code(); // annotations stripped.
29+
/// std::vector<size_t> PP = Example.points(); // all unnamed points
30+
/// size_t P = Example.point(); // there must be exactly one
31+
/// llvm::Range R = Example.range("fail"); // find named ranges
32+
///
33+
/// Points/ranges are coordinated into `code()` which is stripped of
34+
/// annotations.
35+
///
36+
/// Ranges may be nested (and points can be inside ranges), but there's no way
37+
/// to define general overlapping ranges.
38+
///
39+
/// FIXME: the choice of the marking syntax makes it impossible to represent
40+
/// some of the C++ and Objective C constructs (including common ones
41+
/// like C++ attributes). We can fix this by:
42+
/// 1. introducing an escaping mechanism for the special characters,
43+
/// 2. making characters for marking points and ranges configurable,
44+
/// 3. changing the syntax to something less commonly used,
45+
/// 4. ...
46+
class Annotations {
47+
public:
48+
/// Two offsets pointing to a continuous substring. End is not included, i.e.
49+
/// represents a half-open range.
50+
struct Range {
51+
size_t Begin = 0;
52+
size_t End = 0;
53+
54+
friend bool operator==(const Range &L, const Range &R) {
55+
return std::tie(L.Begin, L.End) == std::tie(R.Begin, R.End);
56+
}
57+
friend bool operator!=(const Range &L, const Range &R) { return !(L == R); }
58+
};
59+
60+
/// Parses the annotations from Text. Crashes if it's malformed.
61+
Annotations(llvm::StringRef Text);
62+
63+
/// The input text with all annotations stripped.
64+
/// All points and ranges are relative to this stripped text.
65+
llvm::StringRef code() const { return Code; }
66+
67+
/// Returns the position of the point marked by ^ (or $name^) in the text.
68+
/// Crashes if there isn't exactly one.
69+
size_t point(llvm::StringRef Name = "") const;
70+
/// Returns the position of all points marked by ^ (or $name^) in the text.
71+
std::vector<size_t> points(llvm::StringRef Name = "") const;
72+
73+
/// Returns the location of the range marked by [[ ]] (or $name[[ ]]).
74+
/// Crashes if there isn't exactly one.
75+
Range range(llvm::StringRef Name = "") const;
76+
/// Returns the location of all ranges marked by [[ ]] (or $name[[ ]]).
77+
std::vector<Range> ranges(llvm::StringRef Name = "") const;
78+
79+
private:
80+
std::string Code;
81+
llvm::StringMap<llvm::SmallVector<size_t, 1>> Points;
82+
llvm::StringMap<llvm::SmallVector<Range, 1>> Ranges;
83+
};
84+
85+
llvm::raw_ostream &operator<<(llvm::raw_ostream &O,
86+
const llvm::Annotations::Range &R);
87+
88+
} // namespace llvm
89+
90+
#endif

Diff for: lib/Testing/Support/Annotations.cpp

+95
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
//===--- Annotations.cpp - Annotated source code for unit tests --*- C++-*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "llvm/Testing/Support/Annotations.h"
10+
11+
#include "llvm/ADT/StringExtras.h"
12+
#include "llvm/Support/FormatVariadic.h"
13+
#include "llvm/Support/raw_ostream.h"
14+
15+
using namespace llvm;
16+
17+
// Crash if the assertion fails, printing the message and testcase.
18+
// More elegant error handling isn't needed for unit tests.
19+
static void require(bool Assertion, const char *Msg, llvm::StringRef Code) {
20+
if (!Assertion) {
21+
llvm::errs() << "Annotated testcase: " << Msg << "\n" << Code << "\n";
22+
llvm_unreachable("Annotated testcase assertion failed!");
23+
}
24+
}
25+
26+
Annotations::Annotations(llvm::StringRef Text) {
27+
auto Require = [Text](bool Assertion, const char *Msg) {
28+
require(Assertion, Msg, Text);
29+
};
30+
llvm::Optional<llvm::StringRef> Name;
31+
llvm::SmallVector<std::pair<llvm::StringRef, size_t>, 8> OpenRanges;
32+
33+
Code.reserve(Text.size());
34+
while (!Text.empty()) {
35+
if (Text.consume_front("^")) {
36+
Points[Name.getValueOr("")].push_back(Code.size());
37+
Name = llvm::None;
38+
continue;
39+
}
40+
if (Text.consume_front("[[")) {
41+
OpenRanges.emplace_back(Name.getValueOr(""), Code.size());
42+
Name = llvm::None;
43+
continue;
44+
}
45+
Require(!Name, "$name should be followed by ^ or [[");
46+
if (Text.consume_front("]]")) {
47+
Require(!OpenRanges.empty(), "unmatched ]]");
48+
Range R;
49+
R.Begin = OpenRanges.back().second;
50+
R.End = Code.size();
51+
Ranges[OpenRanges.back().first].push_back(R);
52+
OpenRanges.pop_back();
53+
continue;
54+
}
55+
if (Text.consume_front("$")) {
56+
Name = Text.take_while(llvm::isAlnum);
57+
Text = Text.drop_front(Name->size());
58+
continue;
59+
}
60+
Code.push_back(Text.front());
61+
Text = Text.drop_front();
62+
}
63+
Require(!Name, "unterminated $name");
64+
Require(OpenRanges.empty(), "unmatched [[");
65+
}
66+
67+
size_t Annotations::point(llvm::StringRef Name) const {
68+
auto I = Points.find(Name);
69+
require(I != Points.end() && I->getValue().size() == 1,
70+
"expected exactly one point", Code);
71+
return I->getValue()[0];
72+
}
73+
74+
std::vector<size_t> Annotations::points(llvm::StringRef Name) const {
75+
auto P = Points.lookup(Name);
76+
return {P.begin(), P.end()};
77+
}
78+
79+
Annotations::Range Annotations::range(llvm::StringRef Name) const {
80+
auto I = Ranges.find(Name);
81+
require(I != Ranges.end() && I->getValue().size() == 1,
82+
"expected exactly one range", Code);
83+
return I->getValue()[0];
84+
}
85+
86+
std::vector<Annotations::Range>
87+
Annotations::ranges(llvm::StringRef Name) const {
88+
auto R = Ranges.lookup(Name);
89+
return {R.begin(), R.end()};
90+
}
91+
92+
llvm::raw_ostream &llvm::operator<<(llvm::raw_ostream &O,
93+
const llvm::Annotations::Range &R) {
94+
return O << llvm::formatv("[{0}, {1})", R.Begin, R.End);
95+
}

Diff for: lib/Testing/Support/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ add_definitions(-DGTEST_LANG_CXX11=1)
22
add_definitions(-DGTEST_HAS_TR1_TUPLE=0)
33

44
add_llvm_library(LLVMTestingSupport
5+
Annotations.cpp
56
Error.cpp
67
SupportHelpers.cpp
78

Diff for: unittests/Support/AnnotationsTest.cpp

+112
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
//===----- unittests/AnnotationsTest.cpp ----------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
#include "llvm/Testing/Support/Annotations.h"
9+
#include "gmock/gmock.h"
10+
#include "gtest/gtest.h"
11+
12+
using ::testing::ElementsAre;
13+
using ::testing::IsEmpty;
14+
15+
namespace {
16+
llvm::Annotations::Range range(size_t Begin, size_t End) {
17+
llvm::Annotations::Range R;
18+
R.Begin = Begin;
19+
R.End = End;
20+
return R;
21+
}
22+
23+
TEST(AnnotationsTest, CleanedCode) {
24+
EXPECT_EQ(llvm::Annotations("foo^bar$nnn[[baz$^[[qux]]]]").code(),
25+
"foobarbazqux");
26+
}
27+
28+
TEST(AnnotationsTest, Points) {
29+
// A single point.
30+
EXPECT_EQ(llvm::Annotations("^ab").point(), 0u);
31+
EXPECT_EQ(llvm::Annotations("a^b").point(), 1u);
32+
EXPECT_EQ(llvm::Annotations("ab^").point(), 2u);
33+
34+
// Multiple points.
35+
EXPECT_THAT(llvm::Annotations("^a^bc^d^").points(),
36+
ElementsAre(0u, 1u, 3u, 4u));
37+
38+
// No points.
39+
EXPECT_THAT(llvm::Annotations("ab[[cd]]").points(), IsEmpty());
40+
41+
// Consecutive points.
42+
EXPECT_THAT(llvm::Annotations("ab^^^cd").points(), ElementsAre(2u, 2u, 2u));
43+
}
44+
45+
TEST(AnnotationsTest, Ranges) {
46+
// A single range.
47+
EXPECT_EQ(llvm::Annotations("[[a]]bc").range(), range(0, 1));
48+
EXPECT_EQ(llvm::Annotations("a[[bc]]d").range(), range(1, 3));
49+
EXPECT_EQ(llvm::Annotations("ab[[cd]]").range(), range(2, 4));
50+
51+
// Empty range.
52+
EXPECT_EQ(llvm::Annotations("[[]]ab").range(), range(0, 0));
53+
EXPECT_EQ(llvm::Annotations("a[[]]b").range(), range(1, 1));
54+
EXPECT_EQ(llvm::Annotations("ab[[]]").range(), range(2, 2));
55+
56+
// Multiple ranges.
57+
EXPECT_THAT(llvm::Annotations("[[a]][[b]]cd[[ef]]ef").ranges(),
58+
ElementsAre(range(0, 1), range(1, 2), range(4, 6)));
59+
60+
// No ranges.
61+
EXPECT_THAT(llvm::Annotations("ab^c^defef").ranges(), IsEmpty());
62+
}
63+
64+
TEST(AnnotationsTest, Nested) {
65+
llvm::Annotations Annotated("a[[f^oo^bar[[b[[a]]z]]]]bcdef");
66+
EXPECT_THAT(Annotated.points(), ElementsAre(2u, 4u));
67+
EXPECT_THAT(Annotated.ranges(),
68+
ElementsAre(range(8, 9), range(7, 10), range(1, 10)));
69+
}
70+
71+
TEST(AnnotationsTest, Named) {
72+
// A single named point or range.
73+
EXPECT_EQ(llvm::Annotations("a$foo^b").point("foo"), 1u);
74+
EXPECT_EQ(llvm::Annotations("a$foo[[b]]cdef").range("foo"), range(1, 2));
75+
76+
// Empty names should also work.
77+
EXPECT_EQ(llvm::Annotations("a$^b").point(""), 1u);
78+
EXPECT_EQ(llvm::Annotations("a$[[b]]cdef").range(""), range(1, 2));
79+
80+
// Multiple named points.
81+
llvm::Annotations Annotated("a$p1^bcd$p2^123$p1^345");
82+
EXPECT_THAT(Annotated.points(), IsEmpty());
83+
EXPECT_THAT(Annotated.points("p1"), ElementsAre(1u, 7u));
84+
EXPECT_EQ(Annotated.point("p2"), 4u);
85+
}
86+
87+
TEST(AnnotationsTest, Errors) {
88+
// Annotations use llvm_unreachable, it will only crash in debug mode.
89+
#ifndef NDEBUG
90+
// point() and range() crash on zero or multiple ranges.
91+
EXPECT_DEATH(llvm::Annotations("ab[[c]]def").point(),
92+
"expected exactly one point");
93+
EXPECT_DEATH(llvm::Annotations("a^b^cdef").point(),
94+
"expected exactly one point");
95+
96+
EXPECT_DEATH(llvm::Annotations("a^bcdef").range(),
97+
"expected exactly one range");
98+
EXPECT_DEATH(llvm::Annotations("a[[b]]c[[d]]ef").range(),
99+
"expected exactly one range");
100+
101+
EXPECT_DEATH(llvm::Annotations("$foo^a$foo^a").point("foo"),
102+
"expected exactly one point");
103+
EXPECT_DEATH(llvm::Annotations("$foo[[a]]bc$foo[[a]]").range("foo"),
104+
"expected exactly one range");
105+
106+
// Parsing failures.
107+
EXPECT_DEATH(llvm::Annotations("ff[[fdfd"), "unmatched \\[\\[");
108+
EXPECT_DEATH(llvm::Annotations("ff[[fdjsfjd]]xxx]]"), "unmatched ]]");
109+
EXPECT_DEATH(llvm::Annotations("ff$fdsfd"), "unterminated \\$name");
110+
#endif
111+
}
112+
} // namespace

Diff for: unittests/Support/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ set(LLVM_LINK_COMPONENTS
55
add_llvm_unittest(SupportTests
66
AlignOfTest.cpp
77
AllocatorTest.cpp
8+
AnnotationsTest.cpp
89
ARMAttributeParser.cpp
910
ArrayRecyclerTest.cpp
1011
BinaryStreamTest.cpp

0 commit comments

Comments
 (0)