diff options
Diffstat (limited to 'llvm')
| -rw-r--r-- | llvm/include/llvm/Testing/Support/Annotations.h | 90 | ||||
| -rw-r--r-- | llvm/lib/Testing/Support/Annotations.cpp | 95 | ||||
| -rw-r--r-- | llvm/lib/Testing/Support/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | llvm/unittests/Support/AnnotationsTest.cpp | 112 | ||||
| -rw-r--r-- | llvm/unittests/Support/CMakeLists.txt | 1 | 
5 files changed, 299 insertions, 0 deletions
diff --git a/llvm/include/llvm/Testing/Support/Annotations.h b/llvm/include/llvm/Testing/Support/Annotations.h new file mode 100644 index 00000000000..aad1a44f4ec --- /dev/null +++ b/llvm/include/llvm/Testing/Support/Annotations.h @@ -0,0 +1,90 @@ +//===--- Annotations.h - Annotated source code for tests ---------*- C++-*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// +#ifndef LLVM_TESTING_SUPPORT_ANNOTATIONS_H +#define LLVM_TESTING_SUPPORT_ANNOTATIONS_H + +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/ADT/StringRef.h" +#include <tuple> +#include <vector> + +namespace llvm { + +/// 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<size_t> PP = Example.points();   // all unnamed points +///    size_t P = Example.point();                  // there must be exactly one +///    llvm::Range R = Example.range("fail");       // find named ranges +/// +/// Points/ranges are coordinated 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. +/// +/// FIXME: the choice of the marking syntax makes it impossible to represent +///        some of the C++ and Objective C constructs (including common ones +///        like C++ attributes). We can fix this by: +///          1. introducing an escaping mechanism for the special characters, +///          2. making characters for marking points and ranges configurable, +///          3. changing the syntax to something less commonly used, +///          4. ... +class Annotations { +public: +  /// Two offsets pointing to a continuous substring. End is not included, i.e. +  /// represents a half-open range. +  struct Range { +    size_t Begin = 0; +    size_t End = 0; + +    friend bool operator==(const Range &L, const Range &R) { +      return std::tie(L.Begin, L.End) == std::tie(R.Begin, R.End); +    } +    friend bool operator!=(const Range &L, const Range &R) { return !(L == R); } +  }; + +  /// 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. +  size_t point(llvm::StringRef Name = "") const; +  /// Returns the position of all points marked by ^ (or $name^) in the text. +  std::vector<size_t> 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<size_t, 1>> Points; +  llvm::StringMap<llvm::SmallVector<Range, 1>> Ranges; +}; + +llvm::raw_ostream &operator<<(llvm::raw_ostream &O, +                              const llvm::Annotations::Range &R); + +} // namespace llvm + +#endif diff --git a/llvm/lib/Testing/Support/Annotations.cpp b/llvm/lib/Testing/Support/Annotations.cpp new file mode 100644 index 00000000000..09c572011d3 --- /dev/null +++ b/llvm/lib/Testing/Support/Annotations.cpp @@ -0,0 +1,95 @@ +//===--- Annotations.cpp - Annotated source code for unit tests --*- C++-*-===// +// +// 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 "llvm/Testing/Support/Annotations.h" + +#include "llvm/ADT/StringExtras.h" +#include "llvm/Support/FormatVariadic.h" +#include "llvm/Support/raw_ostream.h" + +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(llvm::StringRef Text) { +  auto Require = [Text](bool Assertion, const char *Msg) { +    require(Assertion, Msg, Text); +  }; +  llvm::Optional<llvm::StringRef> Name; +  llvm::SmallVector<std::pair<llvm::StringRef, size_t>, 8> OpenRanges; + +  Code.reserve(Text.size()); +  while (!Text.empty()) { +    if (Text.consume_front("^")) { +      Points[Name.getValueOr("")].push_back(Code.size()); +      Name = llvm::None; +      continue; +    } +    if (Text.consume_front("[[")) { +      OpenRanges.emplace_back(Name.getValueOr(""), Code.size()); +      Name = llvm::None; +      continue; +    } +    Require(!Name, "$name should be followed by ^ or [["); +    if (Text.consume_front("]]")) { +      Require(!OpenRanges.empty(), "unmatched ]]"); +      Range R; +      R.Begin = OpenRanges.back().second; +      R.End = Code.size(); +      Ranges[OpenRanges.back().first].push_back(R); +      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 [["); +} + +size_t 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<size_t> Annotations::points(llvm::StringRef Name) const { +  auto P = Points.lookup(Name); +  return {P.begin(), P.end()}; +} + +Annotations::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<Annotations::Range> +Annotations::ranges(llvm::StringRef Name) const { +  auto R = Ranges.lookup(Name); +  return {R.begin(), R.end()}; +} + +llvm::raw_ostream &llvm::operator<<(llvm::raw_ostream &O, +                                    const llvm::Annotations::Range &R) { +  return O << llvm::formatv("[{0}, {1})", R.Begin, R.End); +} diff --git a/llvm/lib/Testing/Support/CMakeLists.txt b/llvm/lib/Testing/Support/CMakeLists.txt index c10a81015c5..fe460aeefc9 100644 --- a/llvm/lib/Testing/Support/CMakeLists.txt +++ b/llvm/lib/Testing/Support/CMakeLists.txt @@ -2,6 +2,7 @@ add_definitions(-DGTEST_LANG_CXX11=1)  add_definitions(-DGTEST_HAS_TR1_TUPLE=0)  add_llvm_library(LLVMTestingSupport +  Annotations.cpp    Error.cpp    SupportHelpers.cpp diff --git a/llvm/unittests/Support/AnnotationsTest.cpp b/llvm/unittests/Support/AnnotationsTest.cpp new file mode 100644 index 00000000000..bf66f178924 --- /dev/null +++ b/llvm/unittests/Support/AnnotationsTest.cpp @@ -0,0 +1,112 @@ +//===----- unittests/AnnotationsTest.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 "llvm/Testing/Support/Annotations.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using ::testing::ElementsAre; +using ::testing::IsEmpty; + +namespace { +llvm::Annotations::Range range(size_t Begin, size_t End) { +  llvm::Annotations::Range R; +  R.Begin = Begin; +  R.End = End; +  return R; +} + +TEST(AnnotationsTest, CleanedCode) { +  EXPECT_EQ(llvm::Annotations("foo^bar$nnn[[baz$^[[qux]]]]").code(), +            "foobarbazqux"); +} + +TEST(AnnotationsTest, Points) { +  // A single point. +  EXPECT_EQ(llvm::Annotations("^ab").point(), 0u); +  EXPECT_EQ(llvm::Annotations("a^b").point(), 1u); +  EXPECT_EQ(llvm::Annotations("ab^").point(), 2u); + +  // Multiple points. +  EXPECT_THAT(llvm::Annotations("^a^bc^d^").points(), +              ElementsAre(0u, 1u, 3u, 4u)); + +  // No points. +  EXPECT_THAT(llvm::Annotations("ab[[cd]]").points(), IsEmpty()); + +  // Consecutive points. +  EXPECT_THAT(llvm::Annotations("ab^^^cd").points(), ElementsAre(2u, 2u, 2u)); +} + +TEST(AnnotationsTest, Ranges) { +  // A single range. +  EXPECT_EQ(llvm::Annotations("[[a]]bc").range(), range(0, 1)); +  EXPECT_EQ(llvm::Annotations("a[[bc]]d").range(), range(1, 3)); +  EXPECT_EQ(llvm::Annotations("ab[[cd]]").range(), range(2, 4)); + +  // Empty range. +  EXPECT_EQ(llvm::Annotations("[[]]ab").range(), range(0, 0)); +  EXPECT_EQ(llvm::Annotations("a[[]]b").range(), range(1, 1)); +  EXPECT_EQ(llvm::Annotations("ab[[]]").range(), range(2, 2)); + +  // Multiple ranges. +  EXPECT_THAT(llvm::Annotations("[[a]][[b]]cd[[ef]]ef").ranges(), +              ElementsAre(range(0, 1), range(1, 2), range(4, 6))); + +  // No ranges. +  EXPECT_THAT(llvm::Annotations("ab^c^defef").ranges(), IsEmpty()); +} + +TEST(AnnotationsTest, Nested) { +  llvm::Annotations Annotated("a[[f^oo^bar[[b[[a]]z]]]]bcdef"); +  EXPECT_THAT(Annotated.points(), ElementsAre(2u, 4u)); +  EXPECT_THAT(Annotated.ranges(), +              ElementsAre(range(8, 9), range(7, 10), range(1, 10))); +} + +TEST(AnnotationsTest, Named) { +  // A single named point or range. +  EXPECT_EQ(llvm::Annotations("a$foo^b").point("foo"), 1u); +  EXPECT_EQ(llvm::Annotations("a$foo[[b]]cdef").range("foo"), range(1, 2)); + +  // Empty names should also work. +  EXPECT_EQ(llvm::Annotations("a$^b").point(""), 1u); +  EXPECT_EQ(llvm::Annotations("a$[[b]]cdef").range(""), range(1, 2)); + +  // Multiple named points. +  llvm::Annotations Annotated("a$p1^bcd$p2^123$p1^345"); +  EXPECT_THAT(Annotated.points(), IsEmpty()); +  EXPECT_THAT(Annotated.points("p1"), ElementsAre(1u, 7u)); +  EXPECT_EQ(Annotated.point("p2"), 4u); +} + +TEST(AnnotationsTest, Errors) { +  // Annotations use llvm_unreachable, it will only crash in debug mode. +#ifndef NDEBUG +  // point() and range() crash on zero or multiple ranges. +  EXPECT_DEATH(llvm::Annotations("ab[[c]]def").point(), +               "expected exactly one point"); +  EXPECT_DEATH(llvm::Annotations("a^b^cdef").point(), +               "expected exactly one point"); + +  EXPECT_DEATH(llvm::Annotations("a^bcdef").range(), +               "expected exactly one range"); +  EXPECT_DEATH(llvm::Annotations("a[[b]]c[[d]]ef").range(), +               "expected exactly one range"); + +  EXPECT_DEATH(llvm::Annotations("$foo^a$foo^a").point("foo"), +               "expected exactly one point"); +  EXPECT_DEATH(llvm::Annotations("$foo[[a]]bc$foo[[a]]").range("foo"), +               "expected exactly one range"); + +  // Parsing failures. +  EXPECT_DEATH(llvm::Annotations("ff[[fdfd"), "unmatched \\[\\["); +  EXPECT_DEATH(llvm::Annotations("ff[[fdjsfjd]]xxx]]"), "unmatched ]]"); +  EXPECT_DEATH(llvm::Annotations("ff$fdsfd"), "unterminated \\$name"); +#endif +} +} // namespace diff --git a/llvm/unittests/Support/CMakeLists.txt b/llvm/unittests/Support/CMakeLists.txt index fd839bc90b4..da2b501878a 100644 --- a/llvm/unittests/Support/CMakeLists.txt +++ b/llvm/unittests/Support/CMakeLists.txt @@ -5,6 +5,7 @@ set(LLVM_LINK_COMPONENTS  add_llvm_unittest(SupportTests    AlignOfTest.cpp    AllocatorTest.cpp +  AnnotationsTest.cpp    ARMAttributeParser.cpp    ArrayRecyclerTest.cpp    BinaryStreamTest.cpp  | 

