aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authoreldritch horrors <pennae@lix.systems>2024-03-19 18:02:22 +0100
committereldritch horrors <pennae@lix.systems>2024-07-03 11:46:53 +0000
commit73ddc4540f5f85ef9e01be4e2e362c6c88e47fa3 (patch)
tree6f6ff06799a365d2e2111c8b735111fdd587e166 /tests
parent45ac449d3970bc6abc4c04e0b3a3b6768a9cb753 (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.cc214
-rw-r--r--tests/unit/meson.build1
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',