// Copyright Advanced Micro Devices, Inc., or its affiliates. // SPDX-License-Identifier: MIT #include #include #include #include #include "SimpleFixture.hpp" namespace rocRollerTest { using namespace rocRoller; static_assert(std::input_iterator::iterator>); static_assert(std::ranges::input_range>); static_assert(CInputRangeOf, int>); class GeneratorTest : public SimpleFixture { }; template rocRoller::Generator Take(size_t n, Generator gen) { auto it = gen.begin(); for(size_t i = 0; i < n && it != gen.end(); ++i, ++it) { co_yield *it; } } template Generator range(T begin, T end, T inc) { for(T val = begin; val < end; val += inc) { co_yield val; } } template Generator range(T begin, T end) { co_yield range(begin, end, 1); } template Generator range(T end) { co_yield range(0, end); } template Generator range() { for(T i = 0; true; i++) co_yield i; } template rocRoller::Generator fibonacci() { T a = 1; T b = 1; co_yield a; co_yield b; while(true) { a = a + b; co_yield a; b = a + b; co_yield b; } } TEST_F(GeneratorTest, Fibonacci) { auto fibs = fibonacci(); auto iter = fibs.begin(); EXPECT_EQ(1, *iter); ++iter; EXPECT_EQ(1, *iter); ++iter; EXPECT_EQ(2, *iter); ++iter; EXPECT_EQ(3, *iter); ++iter; EXPECT_EQ(5, *iter); ++iter; EXPECT_EQ(8, *iter); ++iter; EXPECT_EQ(13, *iter); } // Overload of yield_value in Generator::promise_type allows yielding a sequence directly. // Equivalent to the following code, but more efficient. // for(auto item: seq) // co_yield item // Analogous to Python's `yield from` statement. template Generator identity(Generator seq) { co_yield std::move(seq); } TEST_F(GeneratorTest, YieldFromGenerator) { auto fib1 = fibonacci(); auto fib2 = identity(fibonacci()); auto iter1 = fib1.begin(); auto iter2 = fib2.begin(); for(int i = 0; i < 10; ++i, iter1++, iter2++) EXPECT_EQ(*iter1, *iter2) << i; } TEST_F(GeneratorTest, YieldFromContainer) { auto foo = []() -> Generator { std::vector x; co_yield x; co_yield x; co_yield 5; x.push_back(1); co_yield x; AssertFatal(x.size() == 1, ShowValue(x.size())); x.push_back(2); co_yield x; AssertFatal(x.size() == 2, x.size()); // It's not required for the implementation of Generator // to clear x here but the current implementation should. co_yield std::move(x); AssertFatal(x.size() == 0, x.size()); x.push_back(29); co_yield x; co_yield Take(5, fibonacci()).to(); co_yield {9, 8, 7, 6}; co_yield std::vector(); }; std::vector expected{5, 1, 1, 2, 1, 2, 29, 1, 2, 3, 5, 9, 8, 7, 6}; EXPECT_EQ(expected, foo().to()); { auto gen = foo(); auto iter = gen.begin(); for(size_t i = 0; i < expected.size(); i++) { if(i % 3 == 0) EXPECT_EQ(expected[i], *iter) << i; ++iter; } } } TEST_F(GeneratorTest, YieldFromSkippingDereference) { auto bar = []() -> Generator { co_yield range(5); co_yield range(5, 7); co_yield range(0); co_yield range(0); co_yield 7; co_yield range(0); co_yield range(8, 10); co_yield {10, 11, 12}; co_yield range(13, 15); co_yield 15; co_yield std::vector{}; co_yield range(16, 18); co_yield std::vector{}; co_yield std::vector{}; co_yield std::vector{}; co_yield 18; co_yield range(19, 30); }; const int rangeLimit = 30; ASSERT_EQ(range(rangeLimit).to(), bar().to()); for(int denominator = 1; denominator < rangeLimit; denominator++) { for(int remainder = 0; remainder < denominator; remainder++) { auto gen = bar(); auto iter = gen.begin(); for(size_t i = 0; i < rangeLimit; i++) { if(i % denominator == remainder) EXPECT_EQ(i, *iter) << ShowValue(denominator) << ShowValue(remainder) << ShowValue(i); ++iter; } ++iter; } } auto gen = bar(); auto iter = gen.begin(); for(size_t i = 0; i < rangeLimit; i++) ++iter; EXPECT_TRUE(iter == gen.end()); } TEST_F(GeneratorTest, StateEnum) { for(int i = 0; i < static_cast(GeneratorState::Count); i++) { auto enumVal = static_cast(i); EXPECT_EQ(enumVal, fromString(toString(enumVal))); } } TEST_F(GeneratorTest, Assignment) { auto fib = fibonacci(); EXPECT_EQ(GeneratorState::NoValue, fib.state()); auto iter = fib.begin(); EXPECT_EQ(GeneratorState::NoValue, fib.state()); EXPECT_TRUE(iter != fib.end()); EXPECT_EQ(GeneratorState::HasValue, fib.state()); EXPECT_EQ(1, *iter); EXPECT_EQ(GeneratorState::HasValue, fib.state()); ++iter; EXPECT_EQ(GeneratorState::NoValue, fib.state()); EXPECT_EQ(1, *iter); ++iter; EXPECT_EQ(2, *iter); ++iter; EXPECT_EQ(3, *iter); fib = fibonacci().take(4); iter = fib.begin(); EXPECT_EQ(1, *iter); ++iter; EXPECT_EQ(1, *iter); ++iter; EXPECT_EQ(2, *iter); ++iter; EXPECT_EQ(3, *iter); ++iter; EXPECT_TRUE(iter == fib.end()); EXPECT_EQ(GeneratorState::Done, fib.state()); EXPECT_THROW(std::ignore = *iter, std::runtime_error); } TEST_F(GeneratorTest, Ranges) { auto r = range(0, 5, 1); std::vector v(r.begin(), r.end()); std::vector v2({0, 1, 2, 3, 4}); EXPECT_EQ(v2, v); r = range(1, 5); v = std::vector(r.begin(), r.end()); v2 = {1, 2, 3, 4}; EXPECT_EQ(v2, v); r = range(7); v = std::vector(r.begin(), r.end()); v2 = {0, 1, 2, 3, 4, 5, 6}; EXPECT_EQ(v2, v); } TEST_F(GeneratorTest, IteratorSemantics) { auto fibs = fibonacci(); EXPECT_EQ(GeneratorState::NoValue, fibs.state()); auto iter1 = fibs.begin(); EXPECT_EQ(GeneratorState::NoValue, fibs.state()); EXPECT_EQ(1, *iter1); EXPECT_EQ(GeneratorState::HasValue, fibs.state()); ++iter1; EXPECT_EQ(GeneratorState::NoValue, fibs.state()); ++iter1; EXPECT_EQ(2, *iter1); // Calling .begin() on the generator should not advance it. auto iter2 = fibs.begin(); EXPECT_EQ(2, *iter2); auto iter3 = iter2++; EXPECT_EQ(3, *iter2); EXPECT_EQ(3, *iter1); EXPECT_EQ(2, *iter3); ++iter2; EXPECT_EQ(5, *iter2); EXPECT_EQ(5, *iter1); EXPECT_EQ(2, *iter3); auto iter4 = iter3++; EXPECT_EQ(2, *iter3); EXPECT_EQ(2, *iter4); ++iter4; EXPECT_THROW(*iter4, std::runtime_error); } /** * Yields the lowest value from the front of each generator. */ template Generator MergeLess(std::vector>&& gens) { if(gens.empty()) co_return; std::vector::iterator> iters; iters.reserve(gens.size()); for(auto& g : gens) { // cppcheck-suppress useStlAlgorithm iters.push_back(g.begin()); } bool any = true; while(any) { auto theVal = std::numeric_limits::max(); size_t theIdx = 0; any = false; for(size_t i = 0; i < iters.size(); i++) { if(iters[i] != gens[i].end() && *iters[i] < theVal) { theVal = *iters[i]; theIdx = i; any = true; } } if(any) { co_yield theVal; ++iters[theIdx]; } } } TEST_F(GeneratorTest, MergeLess) { std::vector> gens; gens.push_back(std::move(range(5))); gens.push_back(Take(5, fibonacci())); auto ref = std::vector{0, 1, 1, 1, 2, 2, 3, 3, 4, 5}; auto g2 = MergeLess(std::move(gens)); std::vector v(g2.begin(), g2.end()); EXPECT_EQ(ref, v); } TEST_F(GeneratorTest, ToContainer) { auto g = range(0, 5, 1); auto r = g.to(); std::vector v = {0, 1, 2, 3, 4}; auto s = range(0, 5, 1).to(); EXPECT_EQ(r, v); EXPECT_EQ(s, std::set(v.begin(), v.end())); auto fibs = fibonacci(); auto fibs1 = Take(5, std::move(fibs)).to(); std::vector fibsE1 = {1, 1, 2, 3, 5}; EXPECT_EQ(fibs1, fibsE1); } Generator testWithRef(std::vector const& v) { for(int i = 0; i < v.size(); i++) { co_yield v[i]; } } // cppcheck-suppress passedByValue Generator testWithoutRef(std::vector v) { for(int i = 0; i < v.size(); i++) { co_yield v[i]; } } TEST_F(GeneratorTest, TestVectorParams) { std::vector a = {0, 1, 2}; // Vectors as separate values { for(int i : testWithRef(a)) { EXPECT_EQ(i, a.at(i)); } for(int i : testWithoutRef(a)) { EXPECT_EQ(i, a.at(i)); } } // Vectors constructed in place { // FAILS on GCC // for(int i : testWithRef(std::vector{0, 1, 2})) // { // EXPECT_EQ(i, a.at(i)); // } for(int i : testWithoutRef(std::vector{0, 1, 2})) { EXPECT_EQ(i, a.at(i)); } } // Initializer lists { // FAILS on GCC // for(int i : testWithRef({0, 1, 2})) // { // EXPECT_EQ(i, a.at(i)); // } for(int i : testWithoutRef({0, 1, 2})) { EXPECT_EQ(i, a.at(i)); } } } TEST_F(GeneratorTest, GeneratorFilter) { auto func = []() -> Generator { co_yield 3; co_yield 2; co_yield 9; co_yield 17; co_yield 4; co_yield 4; }; EXPECT_EQ((std::vector{9, 17, 4, 4}), filter([](int x) { return x > 3; }, func()).to()); EXPECT_EQ((std::vector{3, 2}), filter([](int x) { return x < 4; }, func()).to()); EXPECT_EQ((std::vector{}), filter([](int x) { return x < 2; }, func()).to()); EXPECT_EQ( (std::vector{2, 8, 34, 144, 610, 2584}), filter([](int x) { return x % 2 == 0; }, Take(20, fibonacci())).to()); EXPECT_EQ( (std::vector{1, 1, 3, 5, 13, 21, 55, 89, 233, 377, 987, 1597, 4181, 6765}), filter([](int x) { return x % 2 != 0; }, Take(20, fibonacci())).to()); } TEST_F(GeneratorTest, ConvenienceFunctions) { auto isEven = [](auto x) { return x % 2 == 0; }; auto isOdd = [](auto x) { return x % 2 != 0; }; auto isPositive = [](auto x) { return x > 0; }; auto isNegative = [](auto x) { return x < 0; }; auto square = [](auto x) { return x * x; }; auto tenSquaresOfOdds = range().filter(isOdd).map(square).take(10).to(); std::vector expected = {1, 9, 25, 49, 81, 121, 169, 225, 289, 361}; EXPECT_EQ(expected, tenSquaresOfOdds); auto fiveSquaresOfEvens = take(5, filter(isPositive, map(square, filter(isEven, range())))) .to(); expected = {4, 16, 36, 64, 100}; EXPECT_EQ(expected, fiveSquaresOfEvens); std::vector threeSquares; auto gen = range().filter(isPositive).take(3); std::transform(gen.begin(), gen.end(), std::back_insert_iterator(threeSquares), square); expected = {1, 4, 9}; EXPECT_EQ(expected, threeSquares); EXPECT_FALSE(empty(range())); EXPECT_FALSE(range().empty()); EXPECT_TRUE(range(100).filter(isNegative).empty()); EXPECT_TRUE(empty(range(100).filter(isNegative))); EXPECT_FALSE(only(range())); EXPECT_FALSE(range().only()); EXPECT_FALSE(range(100).filter(isNegative).only()); EXPECT_FALSE(only(range(100).filter(isNegative))); EXPECT_EQ(1, range(1, 2).only()); EXPECT_EQ(1, only(range(1, 2))); } enum class ExceptionOrder { Beginning, Middle, End, Count }; std::ostream& operator<<(std::ostream& stream, ExceptionOrder order) { switch(order) { case ExceptionOrder::Beginning: return stream << "Beginning"; case ExceptionOrder::Middle: return stream << "Middle"; case ExceptionOrder::End: return stream << "End"; default: case ExceptionOrder::Count: return stream << "INVALID"; } } class GeneratorExceptionTest : public ::testing::TestWithParam> { }; TEST_P(GeneratorExceptionTest, GeneratorExceptions) { auto genThatThrows = [&]() -> Generator { auto throwOrder = std::get<0>(GetParam()); if(throwOrder != ExceptionOrder::Beginning) co_yield 5; throw std::runtime_error("Foo"); if(throwOrder != ExceptionOrder::End) co_yield 6; }; auto genThatYields = [&]() -> Generator { auto yieldOrder = std::get<1>(GetParam()); if(yieldOrder != ExceptionOrder::Beginning) co_yield 7; co_yield genThatThrows(); if(yieldOrder != ExceptionOrder::End) co_yield 8; }; auto callThrowDirectly = [&]() { auto x = genThatThrows().to(); }; auto callYieldFrom = [&]() { auto x = genThatYields().to(); }; auto iterateThroughDirectly = [&]() { auto gen = genThatThrows(); auto iter = gen.begin(); for(int i = 0; i < 2; ++i) ++iter; }; auto iterateThroughFrom = [&]() { auto gen = genThatYields(); auto iter = gen.begin(); for(int i = 0; i < 4; ++i) ++iter; }; EXPECT_THROW(callThrowDirectly(), std::runtime_error); EXPECT_THROW(callYieldFrom(), std::runtime_error); EXPECT_THROW(iterateThroughDirectly(), std::runtime_error); EXPECT_THROW(iterateThroughFrom(), std::runtime_error); } INSTANTIATE_TEST_SUITE_P(GeneratorTest, GeneratorExceptionTest, ::testing::Combine(::testing::Values(ExceptionOrder::Beginning, ExceptionOrder::Middle, ExceptionOrder::End), ::testing::Values(ExceptionOrder::Beginning, ExceptionOrder::Middle, ExceptionOrder::End))); }