From 73ddc4540f5f85ef9e01be4e2e362c6c88e47fa3 Mon Sep 17 00:00:00 2001 From: eldritch horrors Date: Tue, 19 Mar 2024 18:02:22 +0100 Subject: 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 --- tests/unit/libutil/generator.cc | 214 ++++++++++++++++++++++++++++++++++++++++ tests/unit/meson.build | 1 + 2 files changed, 215 insertions(+) create mode 100644 tests/unit/libutil/generator.cc (limited to 'tests') 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 +#include +#include + +namespace nix { + +TEST(Generator, yields) +{ + auto g = []() -> Generator { + 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 { co_return; }(); + + ASSERT_FALSE(g.next().has_value()); + } + { + auto g = []() -> Generator { + co_yield 1; + co_yield []() -> Generator { co_return; }(); + co_yield 2; + co_yield []() -> Generator { 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 { + co_yield 1; + co_yield []() -> Generator { + co_yield 9; + co_yield []() -> Generator { + co_yield 99; + co_yield 100; + }(); + }(); + + auto g2 = []() -> Generator { + co_yield []() -> Generator { + 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 { + co_yield 1; + co_yield []() -> Generator { + 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 { + co_yield 1; + throw 1; + }(); + + ASSERT_EQ(g.next(), 1); + ASSERT_THROW(g.next(), int); + ASSERT_FALSE(g.next().has_value()); + } + { + auto g = []() -> Generator { + throw 1; + co_return; + }(); + + ASSERT_THROW(g.next(), int); + ASSERT_FALSE(g.next().has_value()); + } +} + +namespace { +struct Transform +{ + int state = 0; + + std::pair operator()(std::integral auto x) + { + return {x, state++}; + } + + Generator, Transform> operator()(const char *) + { + co_yield 9; + co_yield 19; + } + + Generator, Transform> operator()(Generator && inner) + { + return [](auto g) mutable -> Generator, Transform> { + while (auto i = g.next()) { + co_yield *i; + } + }(std::move(inner)); + } +}; +} + +TEST(Generator, transform) +{ + auto g = []() -> Generator, Transform> { + co_yield int32_t(-1); + co_yield ""; + co_yield []() -> Generator { co_yield 7; }(); + co_yield 20; + }(); + + ASSERT_EQ(g.next(), (std::pair{4294967295, 0})); + ASSERT_EQ(g.next(), (std::pair{9, 0})); + ASSERT_EQ(g.next(), (std::pair{19, 1})); + ASSERT_EQ(g.next(), (std::pair{7, 0})); + ASSERT_EQ(g.next(), (std::pair{20, 1})); + ASSERT_FALSE(g.next().has_value()); +} + +namespace { +struct ThrowTransform +{ + int operator()(int x) + { + return x; + } + + int operator()(bool) + { + throw 2; + } + + Generator operator()(Generator && inner) + { + throw false; + } +}; +} + +TEST(Generator, transformThrows) +{ + { + auto g = []() -> Generator { + 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 { + co_yield 1; + co_yield []() -> Generator { + 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', -- cgit v1.2.3