diff options
author | eldritch horrors <pennae@lix.systems> | 2024-03-19 18:02:22 +0100 |
---|---|---|
committer | eldritch horrors <pennae@lix.systems> | 2024-07-03 11:46:53 +0000 |
commit | 73ddc4540f5f85ef9e01be4e2e362c6c88e47fa3 (patch) | |
tree | 6f6ff06799a365d2e2111c8b735111fdd587e166 /tests | |
parent | 45ac449d3970bc6abc4c04e0b3a3b6768a9cb753 (diff) |
libutil: generator type with on-yield value mapping
this will be the basis of non-boost coroutines in lix. anything that is
a boost coroutine *should* be representable with a Generator coroutine,
and many things that are not currently boost coroutines but behave much
like one (such as, notably, serializers) should be as well. this allows
us to greatly simplify many things that look like iteration but aren't.
Change-Id: I2cebcefa0148b631fb30df4c8cfa92167a407e34
Diffstat (limited to 'tests')
-rw-r--r-- | tests/unit/libutil/generator.cc | 214 | ||||
-rw-r--r-- | tests/unit/meson.build | 1 |
2 files changed, 215 insertions, 0 deletions
diff --git a/tests/unit/libutil/generator.cc b/tests/unit/libutil/generator.cc new file mode 100644 index 000000000..800e6ca8a --- /dev/null +++ b/tests/unit/libutil/generator.cc @@ -0,0 +1,214 @@ +#include "generator.hh" + +#include <concepts> +#include <cstdint> +#include <gtest/gtest.h> + +namespace nix { + +TEST(Generator, yields) +{ + auto g = []() -> Generator<int> { + co_yield 1; + co_yield 2; + }(); + + ASSERT_EQ(g.next(), 1); + ASSERT_EQ(g.next(), 2); + ASSERT_FALSE(g.next().has_value()); +} + +TEST(Generator, returns) +{ + { + auto g = []() -> Generator<int> { co_return; }(); + + ASSERT_FALSE(g.next().has_value()); + } + { + auto g = []() -> Generator<int> { + co_yield 1; + co_yield []() -> Generator<int> { co_return; }(); + co_yield 2; + co_yield []() -> Generator<int> { co_yield 10; }(); + co_yield 3; + (void) "dummy statement to force some more execution"; + }(); + + ASSERT_EQ(g.next(), 1); + ASSERT_EQ(g.next(), 2); + ASSERT_EQ(g.next(), 10); + ASSERT_EQ(g.next(), 3); + ASSERT_FALSE(g.next().has_value()); + } +} + +TEST(Generator, nests) +{ + auto g = []() -> Generator<int> { + co_yield 1; + co_yield []() -> Generator<int> { + co_yield 9; + co_yield []() -> Generator<int> { + co_yield 99; + co_yield 100; + }(); + }(); + + auto g2 = []() -> Generator<int> { + co_yield []() -> Generator<int> { + co_yield 2000; + co_yield 2001; + }(); + co_yield 1001; + }(); + + co_yield g2.next().value(); + co_yield std::move(g2); + co_yield 2; + }(); + + ASSERT_EQ(g.next(), 1); + ASSERT_EQ(g.next(), 9); + ASSERT_EQ(g.next(), 99); + ASSERT_EQ(g.next(), 100); + ASSERT_EQ(g.next(), 2000); + ASSERT_EQ(g.next(), 2001); + ASSERT_EQ(g.next(), 1001); + ASSERT_EQ(g.next(), 2); + ASSERT_FALSE(g.next().has_value()); +} + +TEST(Generator, nestsExceptions) +{ + auto g = []() -> Generator<int> { + co_yield 1; + co_yield []() -> Generator<int> { + co_yield 9; + throw 1; + co_yield 10; + }(); + co_yield 2; + }(); + + ASSERT_EQ(g.next(), 1); + ASSERT_EQ(g.next(), 9); + ASSERT_THROW(g.next(), int); +} + +TEST(Generator, exception) +{ + { + auto g = []() -> Generator<int> { + co_yield 1; + throw 1; + }(); + + ASSERT_EQ(g.next(), 1); + ASSERT_THROW(g.next(), int); + ASSERT_FALSE(g.next().has_value()); + } + { + auto g = []() -> Generator<int> { + throw 1; + co_return; + }(); + + ASSERT_THROW(g.next(), int); + ASSERT_FALSE(g.next().has_value()); + } +} + +namespace { +struct Transform +{ + int state = 0; + + std::pair<uint32_t, int> operator()(std::integral auto x) + { + return {x, state++}; + } + + Generator<std::pair<uint32_t, int>, Transform> operator()(const char *) + { + co_yield 9; + co_yield 19; + } + + Generator<std::pair<uint32_t, int>, Transform> operator()(Generator<int> && inner) + { + return [](auto g) mutable -> Generator<std::pair<uint32_t, int>, Transform> { + while (auto i = g.next()) { + co_yield *i; + } + }(std::move(inner)); + } +}; +} + +TEST(Generator, transform) +{ + auto g = []() -> Generator<std::pair<uint32_t, int>, Transform> { + co_yield int32_t(-1); + co_yield ""; + co_yield []() -> Generator<int> { co_yield 7; }(); + co_yield 20; + }(); + + ASSERT_EQ(g.next(), (std::pair<unsigned, int>{4294967295, 0})); + ASSERT_EQ(g.next(), (std::pair<unsigned, int>{9, 0})); + ASSERT_EQ(g.next(), (std::pair<unsigned, int>{19, 1})); + ASSERT_EQ(g.next(), (std::pair<unsigned, int>{7, 0})); + ASSERT_EQ(g.next(), (std::pair<unsigned, int>{20, 1})); + ASSERT_FALSE(g.next().has_value()); +} + +namespace { +struct ThrowTransform +{ + int operator()(int x) + { + return x; + } + + int operator()(bool) + { + throw 2; + } + + Generator<int, void> operator()(Generator<int> && inner) + { + throw false; + } +}; +} + +TEST(Generator, transformThrows) +{ + { + auto g = []() -> Generator<int, ThrowTransform> { + co_yield 1; + co_yield false; + co_yield 2; + }(); + + ASSERT_EQ(g.next(), 1); + ASSERT_THROW(g.next(), int); + ASSERT_FALSE(g.next().has_value()); + } + { + auto g = []() -> Generator<int, ThrowTransform> { + co_yield 1; + co_yield []() -> Generator<int> { + co_yield 2; + }(); + co_yield 3; + }(); + + ASSERT_EQ(g.next(), 1); + ASSERT_THROW(g.next(), bool); + ASSERT_FALSE(g.next().has_value()); + } +} + +} diff --git a/tests/unit/meson.build b/tests/unit/meson.build index 2b5471526..17d089f18 100644 --- a/tests/unit/meson.build +++ b/tests/unit/meson.build @@ -40,6 +40,7 @@ libutil_tests_sources = files( 'libutil/compression.cc', 'libutil/config.cc', 'libutil/escape-string.cc', + 'libutil/generator.cc', 'libutil/git.cc', 'libutil/hash.cc', 'libutil/hilite.cc', |