llvm.org GIT mirror llvm / 497e112
[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 Ilya Biryukov 1 year, 7 months ago
5 changed file(s) with 299 addition(s) and 0 deletion(s). Raw diff Collapse all Expand all
0 //===--- Annotations.h - Annotated source code for tests ---------*- C++-*-===//
1 //
2 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
3 // See https://llvm.org/LICENSE.txt for license information.
4 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
5 //
6 //===----------------------------------------------------------------------===//
7 #ifndef LLVM_TESTING_SUPPORT_ANNOTATIONS_H
8 #define LLVM_TESTING_SUPPORT_ANNOTATIONS_H
9
10 #include "llvm/ADT/SmallVector.h"
11 #include "llvm/ADT/StringMap.h"
12 #include "llvm/ADT/StringRef.h"
13 #include
14 #include
15
16 namespace llvm {
17
18 /// Annotations lets you mark points and ranges inside source code, for tests:
19 ///
20 /// Annotations Example(R"cpp(
21 /// int complete() { x.pri^ } // ^ indicates a point
22 /// void err() { [["hello" == 42]]; } // [[this is a range]]
23 /// $definition^class Foo{}; // points can be named: "definition"
24 /// $fail[[static_assert(false, "")]] // ranges can be named too: "fail"
25 /// )cpp");
26 ///
27 /// StringRef Code = Example.code(); // annotations stripped.
28 /// std::vector PP = Example.points(); // all unnamed points
29 /// size_t P = Example.point(); // there must be exactly one
30 /// llvm::Range R = Example.range("fail"); // find named ranges
31 ///
32 /// Points/ranges are coordinated into `code()` which is stripped of
33 /// annotations.
34 ///
35 /// Ranges may be nested (and points can be inside ranges), but there's no way
36 /// to define general overlapping ranges.
37 ///
38 /// FIXME: the choice of the marking syntax makes it impossible to represent
39 /// some of the C++ and Objective C constructs (including common ones
40 /// like C++ attributes). We can fix this by:
41 /// 1. introducing an escaping mechanism for the special characters,
42 /// 2. making characters for marking points and ranges configurable,
43 /// 3. changing the syntax to something less commonly used,
44 /// 4. ...
45 class Annotations {
46 public:
47 /// Two offsets pointing to a continuous substring. End is not included, i.e.
48 /// represents a half-open range.
49 struct Range {
50 size_t Begin = 0;
51 size_t End = 0;
52
53 friend bool operator==(const Range &L, const Range &R) {
54 return std::tie(L.Begin, L.End) == std::tie(R.Begin, R.End);
55 }
56 friend bool operator!=(const Range &L, const Range &R) { return !(L == R); }
57 };
58
59 /// Parses the annotations from Text. Crashes if it's malformed.
60 Annotations(llvm::StringRef Text);
61
62 /// The input text with all annotations stripped.
63 /// All points and ranges are relative to this stripped text.
64 llvm::StringRef code() const { return Code; }
65
66 /// Returns the position of the point marked by ^ (or $name^) in the text.
67 /// Crashes if there isn't exactly one.
68 size_t point(llvm::StringRef Name = "") const;
69 /// Returns the position of all points marked by ^ (or $name^) in the text.
70 std::vector points(llvm::StringRef Name = "") const;
71
72 /// Returns the location of the range marked by [[ ]] (or $name[[ ]]).
73 /// Crashes if there isn't exactly one.
74 Range range(llvm::StringRef Name = "") const;
75 /// Returns the location of all ranges marked by [[ ]] (or $name[[ ]]).
76 std::vector ranges(llvm::StringRef Name = "") const;
77
78 private:
79 std::string Code;
80 llvm::StringMap> Points;
81 llvm::StringMap> Ranges;
82 };
83
84 llvm::raw_ostream &operator<<(llvm::raw_ostream &O,
85 const llvm::Annotations::Range &R);
86
87 } // namespace llvm
88
89 #endif
0 //===--- Annotations.cpp - Annotated source code for unit tests --*- C++-*-===//
1 //
2 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
3 // See https://llvm.org/LICENSE.txt for license information.
4 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
5 //
6 //===----------------------------------------------------------------------===//
7
8 #include "llvm/Testing/Support/Annotations.h"
9
10 #include "llvm/ADT/StringExtras.h"
11 #include "llvm/Support/FormatVariadic.h"
12 #include "llvm/Support/raw_ostream.h"
13
14 using namespace llvm;
15
16 // Crash if the assertion fails, printing the message and testcase.
17 // More elegant error handling isn't needed for unit tests.
18 static void require(bool Assertion, const char *Msg, llvm::StringRef Code) {
19 if (!Assertion) {
20 llvm::errs() << "Annotated testcase: " << Msg << "\n" << Code << "\n";
21 llvm_unreachable("Annotated testcase assertion failed!");
22 }
23 }
24
25 Annotations::Annotations(llvm::StringRef Text) {
26 auto Require = [Text](bool Assertion, const char *Msg) {
27 require(Assertion, Msg, Text);
28 };
29 llvm::Optional Name;
30 llvm::SmallVector, 8> OpenRanges;
31
32 Code.reserve(Text.size());
33 while (!Text.empty()) {
34 if (Text.consume_front("^")) {
35 Points[Name.getValueOr("")].push_back(Code.size());
36 Name = llvm::None;
37 continue;
38 }
39 if (Text.consume_front("[[")) {
40 OpenRanges.emplace_back(Name.getValueOr(""), Code.size());
41 Name = llvm::None;
42 continue;
43 }
44 Require(!Name, "$name should be followed by ^ or [[");
45 if (Text.consume_front("]]")) {
46 Require(!OpenRanges.empty(), "unmatched ]]");
47 Range R;
48 R.Begin = OpenRanges.back().second;
49 R.End = Code.size();
50 Ranges[OpenRanges.back().first].push_back(R);
51 OpenRanges.pop_back();
52 continue;
53 }
54 if (Text.consume_front("$")) {
55 Name = Text.take_while(llvm::isAlnum);
56 Text = Text.drop_front(Name->size());
57 continue;
58 }
59 Code.push_back(Text.front());
60 Text = Text.drop_front();
61 }
62 Require(!Name, "unterminated $name");
63 Require(OpenRanges.empty(), "unmatched [[");
64 }
65
66 size_t Annotations::point(llvm::StringRef Name) const {
67 auto I = Points.find(Name);
68 require(I != Points.end() && I->getValue().size() == 1,
69 "expected exactly one point", Code);
70 return I->getValue()[0];
71 }
72
73 std::vector Annotations::points(llvm::StringRef Name) const {
74 auto P = Points.lookup(Name);
75 return {P.begin(), P.end()};
76 }
77
78 Annotations::Range Annotations::range(llvm::StringRef Name) const {
79 auto I = Ranges.find(Name);
80 require(I != Ranges.end() && I->getValue().size() == 1,
81 "expected exactly one range", Code);
82 return I->getValue()[0];
83 }
84
85 std::vector
86 Annotations::ranges(llvm::StringRef Name) const {
87 auto R = Ranges.lookup(Name);
88 return {R.begin(), R.end()};
89 }
90
91 llvm::raw_ostream &llvm::operator<<(llvm::raw_ostream &O,
92 const llvm::Annotations::Range &R) {
93 return O << llvm::formatv("[{0}, {1})", R.Begin, R.End);
94 }
11 add_definitions(-DGTEST_HAS_TR1_TUPLE=0)
22
33 add_llvm_library(LLVMTestingSupport
4 Annotations.cpp
45 Error.cpp
56 SupportHelpers.cpp
67
0 //===----- unittests/AnnotationsTest.cpp ----------------------------------===//
1 //
2 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
3 // See https://llvm.org/LICENSE.txt for license information.
4 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
5 //
6 //===----------------------------------------------------------------------===//
7 #include "llvm/Testing/Support/Annotations.h"
8 #include "gmock/gmock.h"
9 #include "gtest/gtest.h"
10
11 using ::testing::ElementsAre;
12 using ::testing::IsEmpty;
13
14 namespace {
15 llvm::Annotations::Range range(size_t Begin, size_t End) {
16 llvm::Annotations::Range R;
17 R.Begin = Begin;
18 R.End = End;
19 return R;
20 }
21
22 TEST(AnnotationsTest, CleanedCode) {
23 EXPECT_EQ(llvm::Annotations("foo^bar$nnn[[baz$^[[qux]]]]").code(),
24 "foobarbazqux");
25 }
26
27 TEST(AnnotationsTest, Points) {
28 // A single point.
29 EXPECT_EQ(llvm::Annotations("^ab").point(), 0u);
30 EXPECT_EQ(llvm::Annotations("a^b").point(), 1u);
31 EXPECT_EQ(llvm::Annotations("ab^").point(), 2u);
32
33 // Multiple points.
34 EXPECT_THAT(llvm::Annotations("^a^bc^d^").points(),
35 ElementsAre(0u, 1u, 3u, 4u));
36
37 // No points.
38 EXPECT_THAT(llvm::Annotations("ab[[cd]]").points(), IsEmpty());
39
40 // Consecutive points.
41 EXPECT_THAT(llvm::Annotations("ab^^^cd").points(), ElementsAre(2u, 2u, 2u));
42 }
43
44 TEST(AnnotationsTest, Ranges) {
45 // A single range.
46 EXPECT_EQ(llvm::Annotations("[[a]]bc").range(), range(0, 1));
47 EXPECT_EQ(llvm::Annotations("a[[bc]]d").range(), range(1, 3));
48 EXPECT_EQ(llvm::Annotations("ab[[cd]]").range(), range(2, 4));
49
50 // Empty range.
51 EXPECT_EQ(llvm::Annotations("[[]]ab").range(), range(0, 0));
52 EXPECT_EQ(llvm::Annotations("a[[]]b").range(), range(1, 1));
53 EXPECT_EQ(llvm::Annotations("ab[[]]").range(), range(2, 2));
54
55 // Multiple ranges.
56 EXPECT_THAT(llvm::Annotations("[[a]][[b]]cd[[ef]]ef").ranges(),
57 ElementsAre(range(0, 1), range(1, 2), range(4, 6)));
58
59 // No ranges.
60 EXPECT_THAT(llvm::Annotations("ab^c^defef").ranges(), IsEmpty());
61 }
62
63 TEST(AnnotationsTest, Nested) {
64 llvm::Annotations Annotated("a[[f^oo^bar[[b[[a]]z]]]]bcdef");
65 EXPECT_THAT(Annotated.points(), ElementsAre(2u, 4u));
66 EXPECT_THAT(Annotated.ranges(),
67 ElementsAre(range(8, 9), range(7, 10), range(1, 10)));
68 }
69
70 TEST(AnnotationsTest, Named) {
71 // A single named point or range.
72 EXPECT_EQ(llvm::Annotations("a$foo^b").point("foo"), 1u);
73 EXPECT_EQ(llvm::Annotations("a$foo[[b]]cdef").range("foo"), range(1, 2));
74
75 // Empty names should also work.
76 EXPECT_EQ(llvm::Annotations("a$^b").point(""), 1u);
77 EXPECT_EQ(llvm::Annotations("a$[[b]]cdef").range(""), range(1, 2));
78
79 // Multiple named points.
80 llvm::Annotations Annotated("a$p1^bcd$p2^123$p1^345");
81 EXPECT_THAT(Annotated.points(), IsEmpty());
82 EXPECT_THAT(Annotated.points("p1"), ElementsAre(1u, 7u));
83 EXPECT_EQ(Annotated.point("p2"), 4u);
84 }
85
86 TEST(AnnotationsTest, Errors) {
87 // Annotations use llvm_unreachable, it will only crash in debug mode.
88 #ifndef NDEBUG
89 // point() and range() crash on zero or multiple ranges.
90 EXPECT_DEATH(llvm::Annotations("ab[[c]]def").point(),
91 "expected exactly one point");
92 EXPECT_DEATH(llvm::Annotations("a^b^cdef").point(),
93 "expected exactly one point");
94
95 EXPECT_DEATH(llvm::Annotations("a^bcdef").range(),
96 "expected exactly one range");
97 EXPECT_DEATH(llvm::Annotations("a[[b]]c[[d]]ef").range(),
98 "expected exactly one range");
99
100 EXPECT_DEATH(llvm::Annotations("$foo^a$foo^a").point("foo"),
101 "expected exactly one point");
102 EXPECT_DEATH(llvm::Annotations("$foo[[a]]bc$foo[[a]]").range("foo"),
103 "expected exactly one range");
104
105 // Parsing failures.
106 EXPECT_DEATH(llvm::Annotations("ff[[fdfd"), "unmatched \\[\\[");
107 EXPECT_DEATH(llvm::Annotations("ff[[fdjsfjd]]xxx]]"), "unmatched ]]");
108 EXPECT_DEATH(llvm::Annotations("ff$fdsfd"), "unterminated \\$name");
109 #endif
110 }
111 } // namespace
44 add_llvm_unittest(SupportTests
55 AlignOfTest.cpp
66 AllocatorTest.cpp
7 AnnotationsTest.cpp
78 ARMAttributeParser.cpp
89 ArrayRecyclerTest.cpp
910 BinaryStreamTest.cpp