292 lines
		
	
	
		
			8.4 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			292 lines
		
	
	
		
			8.4 KiB
		
	
	
	
		
			C++
		
	
	
	
//===- unittests/ADT/FallibleIteratorTest.cpp - fallible_iterator.h tests -===//
 | 
						|
//
 | 
						|
// 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/ADT/fallible_iterator.h"
 | 
						|
#include "llvm/Testing/Support/Error.h"
 | 
						|
 | 
						|
#include "gtest/gtest-spi.h"
 | 
						|
#include "gtest/gtest.h"
 | 
						|
 | 
						|
#include <utility>
 | 
						|
#include <vector>
 | 
						|
 | 
						|
using namespace llvm;
 | 
						|
 | 
						|
namespace {
 | 
						|
 | 
						|
using ItemValid = enum { ValidItem, InvalidItem };
 | 
						|
using LinkValid = enum { ValidLink, InvalidLink };
 | 
						|
 | 
						|
class Item {
 | 
						|
public:
 | 
						|
  Item(ItemValid V) : V(V) {}
 | 
						|
  bool isValid() const { return V == ValidItem; }
 | 
						|
 | 
						|
private:
 | 
						|
  ItemValid V;
 | 
						|
};
 | 
						|
 | 
						|
// A utility to mock "bad collections". It supports both invalid items,
 | 
						|
// where the dereference operator may return an Error, and bad links
 | 
						|
// where the inc/dec operations may return an Error.
 | 
						|
// Each element of the mock collection contains a pair of a (possibly broken)
 | 
						|
// item and link.
 | 
						|
using FallibleCollection = std::vector<std::pair<Item, LinkValid>>;
 | 
						|
 | 
						|
class FallibleCollectionWalker {
 | 
						|
public:
 | 
						|
  FallibleCollectionWalker(FallibleCollection &C, unsigned Idx)
 | 
						|
      : C(C), Idx(Idx) {}
 | 
						|
 | 
						|
  Item &operator*() { return C[Idx].first; }
 | 
						|
 | 
						|
  const Item &operator*() const { return C[Idx].first; }
 | 
						|
 | 
						|
  Error inc() {
 | 
						|
    assert(Idx != C.size() && "Walking off end of (mock) collection");
 | 
						|
    if (C[Idx].second == ValidLink) {
 | 
						|
      ++Idx;
 | 
						|
      return Error::success();
 | 
						|
    }
 | 
						|
    return make_error<StringError>("cant get next object in (mock) collection",
 | 
						|
                                   inconvertibleErrorCode());
 | 
						|
  }
 | 
						|
 | 
						|
  Error dec() {
 | 
						|
    assert(Idx != 0 && "Walking off start of (mock) collection");
 | 
						|
    --Idx;
 | 
						|
    if (C[Idx].second == ValidLink)
 | 
						|
      return Error::success();
 | 
						|
    return make_error<StringError>("cant get prev object in (mock) collection",
 | 
						|
                                   inconvertibleErrorCode());
 | 
						|
  }
 | 
						|
 | 
						|
  friend bool operator==(const FallibleCollectionWalker &LHS,
 | 
						|
                         const FallibleCollectionWalker &RHS) {
 | 
						|
    assert(&LHS.C == &RHS.C && "Comparing iterators across collectionss.");
 | 
						|
    return LHS.Idx == RHS.Idx;
 | 
						|
  }
 | 
						|
 | 
						|
private:
 | 
						|
  FallibleCollection &C;
 | 
						|
  unsigned Idx;
 | 
						|
};
 | 
						|
 | 
						|
class FallibleCollectionWalkerWithStructDeref
 | 
						|
    : public FallibleCollectionWalker {
 | 
						|
public:
 | 
						|
  using FallibleCollectionWalker::FallibleCollectionWalker;
 | 
						|
 | 
						|
  Item *operator->() { return &this->operator*(); }
 | 
						|
 | 
						|
  const Item *operator->() const { return &this->operator*(); }
 | 
						|
};
 | 
						|
 | 
						|
class FallibleCollectionWalkerWithFallibleDeref
 | 
						|
    : public FallibleCollectionWalker {
 | 
						|
public:
 | 
						|
  using FallibleCollectionWalker::FallibleCollectionWalker;
 | 
						|
 | 
						|
  Expected<Item &> operator*() {
 | 
						|
    auto &I = FallibleCollectionWalker::operator*();
 | 
						|
    if (!I.isValid())
 | 
						|
      return make_error<StringError>("bad item", inconvertibleErrorCode());
 | 
						|
    return I;
 | 
						|
  }
 | 
						|
 | 
						|
  Expected<const Item &> operator*() const {
 | 
						|
    const auto &I = FallibleCollectionWalker::operator*();
 | 
						|
    if (!I.isValid())
 | 
						|
      return make_error<StringError>("bad item", inconvertibleErrorCode());
 | 
						|
    return I;
 | 
						|
  }
 | 
						|
};
 | 
						|
 | 
						|
TEST(FallibleIteratorTest, BasicSuccess) {
 | 
						|
 | 
						|
  // Check that a basic use-case involing successful iteration over a
 | 
						|
  // "FallibleCollection" works.
 | 
						|
 | 
						|
  FallibleCollection C({{ValidItem, ValidLink}, {ValidItem, ValidLink}});
 | 
						|
 | 
						|
  FallibleCollectionWalker begin(C, 0);
 | 
						|
  FallibleCollectionWalker end(C, 2);
 | 
						|
 | 
						|
  Error Err = Error::success();
 | 
						|
  for (auto &Elem :
 | 
						|
       make_fallible_range<FallibleCollectionWalker>(begin, end, Err))
 | 
						|
    EXPECT_TRUE(Elem.isValid());
 | 
						|
  cantFail(std::move(Err));
 | 
						|
}
 | 
						|
 | 
						|
TEST(FallibleIteratorTest, BasicFailure) {
 | 
						|
 | 
						|
  // Check that a iteration failure (due to the InvalidLink state on element one
 | 
						|
  // of the fallible collection) breaks out of the loop and raises an Error.
 | 
						|
 | 
						|
  FallibleCollection C({{ValidItem, ValidLink}, {ValidItem, InvalidLink}});
 | 
						|
 | 
						|
  FallibleCollectionWalker begin(C, 0);
 | 
						|
  FallibleCollectionWalker end(C, 2);
 | 
						|
 | 
						|
  Error Err = Error::success();
 | 
						|
  for (auto &Elem :
 | 
						|
       make_fallible_range<FallibleCollectionWalker>(begin, end, Err))
 | 
						|
    EXPECT_TRUE(Elem.isValid());
 | 
						|
 | 
						|
  EXPECT_THAT_ERROR(std::move(Err), Failed()) << "Expected failure value";
 | 
						|
}
 | 
						|
 | 
						|
TEST(FallibleIteratorTest, NoRedundantErrorCheckOnEarlyExit) {
 | 
						|
 | 
						|
  // Check that an early return from the loop body does not require a redundant
 | 
						|
  // check of Err.
 | 
						|
 | 
						|
  FallibleCollection C({{ValidItem, ValidLink}, {ValidItem, ValidLink}});
 | 
						|
 | 
						|
  FallibleCollectionWalker begin(C, 0);
 | 
						|
  FallibleCollectionWalker end(C, 2);
 | 
						|
 | 
						|
  Error Err = Error::success();
 | 
						|
  for (auto &Elem :
 | 
						|
       make_fallible_range<FallibleCollectionWalker>(begin, end, Err)) {
 | 
						|
    (void)Elem;
 | 
						|
    return;
 | 
						|
  }
 | 
						|
  // Err not checked, but should be ok because we exit from the loop
 | 
						|
  // body.
 | 
						|
}
 | 
						|
 | 
						|
#if LLVM_ENABLE_ABI_BREAKING_CHECKS
 | 
						|
TEST(FallibleIteratorTest, RegularLoopExitRequiresErrorCheck) {
 | 
						|
 | 
						|
  // Check that Err must be checked after a normal (i.e. not early) loop exit
 | 
						|
  // by failing to check and expecting program death (due to the unchecked
 | 
						|
  // error).
 | 
						|
 | 
						|
  EXPECT_DEATH(
 | 
						|
      {
 | 
						|
        FallibleCollection C({{ValidItem, ValidLink}, {ValidItem, ValidLink}});
 | 
						|
 | 
						|
        FallibleCollectionWalker begin(C, 0);
 | 
						|
        FallibleCollectionWalker end(C, 2);
 | 
						|
 | 
						|
        Error Err = Error::success();
 | 
						|
        for (auto &Elem :
 | 
						|
             make_fallible_range<FallibleCollectionWalker>(begin, end, Err))
 | 
						|
          (void)Elem;
 | 
						|
      },
 | 
						|
      "Program aborted due to an unhandled Error:")
 | 
						|
      << "Normal (i.e. not early) loop exit should require an error check";
 | 
						|
}
 | 
						|
#endif
 | 
						|
 | 
						|
TEST(FallibleIteratorTest, RawIncrementAndDecrementBehavior) {
 | 
						|
 | 
						|
  // Check the exact behavior of increment / decrement.
 | 
						|
 | 
						|
  FallibleCollection C({{ValidItem, ValidLink},
 | 
						|
                        {ValidItem, InvalidLink},
 | 
						|
                        {ValidItem, ValidLink},
 | 
						|
                        {ValidItem, InvalidLink}});
 | 
						|
 | 
						|
  {
 | 
						|
    // One increment from begin succeeds.
 | 
						|
    Error Err = Error::success();
 | 
						|
    auto I = make_fallible_itr(FallibleCollectionWalker(C, 0), Err);
 | 
						|
    ++I;
 | 
						|
    EXPECT_THAT_ERROR(std::move(Err), Succeeded());
 | 
						|
  }
 | 
						|
 | 
						|
  {
 | 
						|
    // Two increments from begin fail.
 | 
						|
    Error Err = Error::success();
 | 
						|
    auto I = make_fallible_itr(FallibleCollectionWalker(C, 0), Err);
 | 
						|
    ++I;
 | 
						|
    EXPECT_THAT_ERROR(std::move(Err), Succeeded());
 | 
						|
    ++I;
 | 
						|
    EXPECT_THAT_ERROR(std::move(Err), Failed()) << "Expected failure value";
 | 
						|
  }
 | 
						|
 | 
						|
  {
 | 
						|
    // One decement from element three succeeds.
 | 
						|
    Error Err = Error::success();
 | 
						|
    auto I = make_fallible_itr(FallibleCollectionWalker(C, 3), Err);
 | 
						|
    --I;
 | 
						|
    EXPECT_THAT_ERROR(std::move(Err), Succeeded());
 | 
						|
  }
 | 
						|
 | 
						|
  {
 | 
						|
    // One decement from element three succeeds.
 | 
						|
    Error Err = Error::success();
 | 
						|
    auto I = make_fallible_itr(FallibleCollectionWalker(C, 3), Err);
 | 
						|
    --I;
 | 
						|
    EXPECT_THAT_ERROR(std::move(Err), Succeeded());
 | 
						|
    --I;
 | 
						|
    EXPECT_THAT_ERROR(std::move(Err), Failed());
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
TEST(FallibleIteratorTest, CheckStructDerefOperatorSupport) {
 | 
						|
  // Check that the fallible_iterator wrapper forwards through to the
 | 
						|
  // underlying iterator's structure dereference operator if present.
 | 
						|
 | 
						|
  FallibleCollection C({{ValidItem, ValidLink},
 | 
						|
                        {ValidItem, ValidLink},
 | 
						|
                        {InvalidItem, InvalidLink}});
 | 
						|
 | 
						|
  FallibleCollectionWalkerWithStructDeref begin(C, 0);
 | 
						|
 | 
						|
  {
 | 
						|
    Error Err = Error::success();
 | 
						|
    auto I = make_fallible_itr(begin, Err);
 | 
						|
    EXPECT_TRUE(I->isValid());
 | 
						|
    cantFail(std::move(Err));
 | 
						|
  }
 | 
						|
 | 
						|
  {
 | 
						|
    Error Err = Error::success();
 | 
						|
    const auto I = make_fallible_itr(begin, Err);
 | 
						|
    EXPECT_TRUE(I->isValid());
 | 
						|
    cantFail(std::move(Err));
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
TEST(FallibleIteratorTest, CheckDerefToExpectedSupport) {
 | 
						|
 | 
						|
  // Check that the fallible_iterator wrapper forwards value types, in
 | 
						|
  // particular llvm::Expected, correctly.
 | 
						|
 | 
						|
  FallibleCollection C({{ValidItem, ValidLink},
 | 
						|
                        {InvalidItem, ValidLink},
 | 
						|
                        {ValidItem, ValidLink}});
 | 
						|
 | 
						|
  FallibleCollectionWalkerWithFallibleDeref begin(C, 0);
 | 
						|
  FallibleCollectionWalkerWithFallibleDeref end(C, 3);
 | 
						|
 | 
						|
  Error Err = Error::success();
 | 
						|
  auto I = make_fallible_itr(begin, Err);
 | 
						|
  auto E = make_fallible_end(end);
 | 
						|
 | 
						|
  Expected<Item> V1 = *I;
 | 
						|
  EXPECT_THAT_ERROR(V1.takeError(), Succeeded());
 | 
						|
  ++I;
 | 
						|
  EXPECT_NE(I, E); // Implicitly check error.
 | 
						|
  Expected<Item> V2 = *I;
 | 
						|
  EXPECT_THAT_ERROR(V2.takeError(), Failed());
 | 
						|
  ++I;
 | 
						|
  EXPECT_NE(I, E); // Implicitly check error.
 | 
						|
  Expected<Item> V3 = *I;
 | 
						|
  EXPECT_THAT_ERROR(V3.takeError(), Succeeded());
 | 
						|
  ++I;
 | 
						|
  EXPECT_EQ(I, E);
 | 
						|
  cantFail(std::move(Err));
 | 
						|
}
 | 
						|
 | 
						|
} // namespace
 |