#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;
};
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) {
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) {
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) {
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;
}
}
#if LLVM_ENABLE_ABI_BREAKING_CHECKS
TEST(FallibleIteratorTest, RegularLoopExitRequiresErrorCheck) {
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) {
FallibleCollection C({{ValidItem, ValidLink},
{ValidItem, InvalidLink},
{ValidItem, ValidLink},
{ValidItem, InvalidLink}});
{
Error Err = Error::success();
auto I = make_fallible_itr(FallibleCollectionWalker(C, 0), Err);
++I;
EXPECT_THAT_ERROR(std::move(Err), Succeeded());
}
{
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";
}
{
Error Err = Error::success();
auto I = make_fallible_itr(FallibleCollectionWalker(C, 3), Err);
--I;
EXPECT_THAT_ERROR(std::move(Err), Succeeded());
}
{
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) {
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) {
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); Expected<Item> V2 = *I;
EXPECT_THAT_ERROR(V2.takeError(), Failed());
++I;
EXPECT_NE(I, E); Expected<Item> V3 = *I;
EXPECT_THAT_ERROR(V3.takeError(), Succeeded());
++I;
EXPECT_EQ(I, E);
cantFail(std::move(Err));
}
}