#include #include #include #include "test_util.h" #include "uvco/combinators.h" #include "uvco/loop/loop.h" #include "uvco/promise/multipromise.h" #include "uvco/promise/promise.h" #include "uvco/promise/select.h" #include "uvco/run.h" #include "uvco/timer.h" using namespace uvco; namespace { TEST(SelectTest, selectBasic) { auto setup = [](const Loop &) -> Promise { Promise promise1 = []() -> Promise { co_await yield(); co_return 123; }(); Promise promise2 = []() -> Promise { co_await yield(); co_await yield(); co_return 234; }(); auto selectSet = SelectSet{promise1, promise2}; auto selected = co_await selectSet; EXPECT_EQ(selected.size(), 1); EXPECT_EQ(co_await *std::get<0>(selected[0]), 123); // Selecting an already finished promise is okay. auto selectSet2 = SelectSet{promise1, promise2}; auto selected2 = co_await selectSet2; EXPECT_EQ(selected2.size(), 1); EXPECT_EQ(co_await *std::get<1>(selected2[0]), 234); }; run_loop(setup); } TEST(SelectTest, selectReturnsSimultaneously) { auto simultaneousSelect = [](const Loop &) -> Promise { auto promise1 = []() -> Promise { co_await yield(); co_return 1; }; auto promise2 = []() -> Promise { co_await yield(); co_return 2; }; auto promiseObject1 = promise1(); auto promiseObject2 = promise2(); auto selectSet = SelectSet{promiseObject1, promiseObject2}; auto selected = co_await selectSet; EXPECT_EQ(selected.size(), 1); EXPECT_EQ(co_await *std::get<0>(selected[0]), 1); selected = co_await SelectSet{promiseObject1, promiseObject2}; EXPECT_EQ(selected.size(), 1); EXPECT_EQ(co_await *std::get<1>(selected[0]), 2); co_return; }; run_loop(simultaneousSelect); } TEST(SelectTest, selectLateReady) { auto lateReady = [](const Loop &loop) -> Promise { auto inner = [&]() -> Promise { auto fast = []() -> Promise { co_await yield(); co_return 42; }; auto slow = [&]() -> Promise { co_await sleep(loop, 100); co_return 84; }; auto promise1 = fast(); auto promise2 = slow(); auto selectSet = SelectSet{promise1, promise2}; auto selected = co_await selectSet; EXPECT_EQ(selected.size(), 1); EXPECT_EQ(co_await *std::get<0>(selected[0]), 42); co_return; // now the other coroutine might still be running, and wake up the // non-existing coroutine inner(). }; co_await inner(); }; run_loop(lateReady); } TEST(SelectTest, selectSetMany) { auto firstPass = [](const Loop &) -> Promise { auto promise1 = []() -> Promise { co_await yield(); co_return 1; }; auto promise2 = []() -> Promise { co_await yield(); co_await yield(); co_return 2; }; auto promise3 = []() -> Promise { co_await yield(); co_await yield(); co_return 3; }; auto promise4 = []() -> Promise { co_await yield(); co_await yield(); co_return 4; }; auto promiseObject1 = promise1(); auto promiseObject1a = promise1(); auto promiseObject2 = promise2(); auto promiseObject2a = promise2(); auto promiseObject3 = promise3(); auto promiseObject3a = promise3(); auto promiseObject4 = promise4(); auto promiseObject4a = promise4(); auto selectSet = SelectSet{ promiseObject1, promiseObject2, promiseObject3, promiseObject4, promiseObject1a, promiseObject2a, promiseObject3a, promiseObject4a}; const auto selected = co_await selectSet; EXPECT_EQ(selected.size(), 2); EXPECT_EQ(co_await *std::get<0>(selected[0]), 1); EXPECT_EQ(co_await *std::get<4>(selected[1]), 1); co_return; }; run_loop(firstPass); } TEST(SelectTest, onlyCheckOne) { auto setup = [](const Loop &) -> Promise { auto promise1 = []() -> Promise { co_await yield(); co_return; }; auto promise2 = []() -> Promise { co_await yield(); co_await yield(); co_await yield(); co_return; }; auto promiseObject1 = promise1(); auto promiseObject2 = promise2(); { auto selectSet = SelectSet{promiseObject1, promiseObject2}; auto selected = co_await selectSet; EXPECT_EQ(selected.size(), 1); co_await *std::get<0>(selected[0]); } { auto selectSet = SelectSet{promiseObject1, promiseObject2}; auto selected = co_await selectSet; EXPECT_EQ(selected.size(), 1); co_await *std::get<1>(selected[0]); } // The second promise is not checked; it is finished anyway after co_return, // in order to free the loop. co_return; }; run_loop(setup); } TEST(SelectTest, selectVoid) { auto setup = [](const Loop &) -> Promise { auto promise1 = []() -> Promise { co_await yield(); }; auto promise2 = []() -> Promise { co_await yield(); co_await yield(); }; auto promiseObject1 = promise1(); auto promiseObject2 = promise2(); auto selectSet = SelectSet{promiseObject1, promiseObject2}; auto selected = co_await selectSet; EXPECT_EQ(selected.size(), 1); co_await *std::get<0>(selected[0]); co_return; }; run_loop(setup); } TEST(SelectTest, DISABLED_benchmark) { auto setup = [](const Loop &) -> Promise { constexpr unsigned count = 1000000; // Finely orchestrated generators, yielding one after another. MultiPromise gen1 = []() -> MultiPromise { for (unsigned i = 0; i < count; ++i) { co_yield i; co_await yield(); } }(); MultiPromise gen2 = []() -> MultiPromise { for (unsigned i = 0; i < count; ++i) { co_await yield(); co_yield i; co_await yield(); } }(); // Each iteration takes about 500 ns on my unsignedel Core i5-7300U @ 2.6 // GHz (your machine is likely faster). for (unsigned i = 0; i < count; ++i) { Promise> promise1 = gen1.next(); Promise> promise2 = gen2.next(); // Ensure that all promises are awaited .Imagine the next() coroutines as // "background threads"; the uvco rules say that only one coroutine may // await a promise at a time, so we can't issue two calls to // `MultiPromise::next()` right after another. auto result1 = co_await SelectSet{promise1, promise2}; EXPECT_EQ(result1.size(), 1); EXPECT_EQ(co_await *std::get<0>(result1[0]), i); auto result2 = co_await SelectSet{promise1, promise2}; EXPECT_EQ(result2.size(), 1); EXPECT_EQ(co_await *std::get<1>(result2[0]), i); } co_return; }; run_loop(setup); } TEST(SelectTest, reliableSelectLoop) { auto setup = [](const Loop &) -> Promise { constexpr unsigned count = 10; // Finely orchestrated generators, yielding one after another. MultiPromise gen1 = []() -> MultiPromise { for (unsigned i = 0; i < count; ++i) { co_yield i; co_await yield(); } }(); MultiPromise gen2 = []() -> MultiPromise { for (unsigned i = 0; i < count; ++i) { co_await yield(); co_await yield(); co_yield i; } }(); Promise> promise1 = gen1.next(); Promise> promise2 = gen2.next(); bool promise1Done = false; bool promise2Done = false; while (!promise1Done || !promise2Done) { auto result = co_await SelectSet{promise1, promise2}; for (auto &promise : result) { switch (promise.index()) { case 0: if (co_await *std::get<0>(promise) == std::nullopt) { promise1Done = true; } else { promise1 = gen1.next(); } break; case 1: if (co_await *std::get<1>(promise) == std::nullopt) { promise2Done = true; } else { promise2 = gen2.next(); } break; default: EXPECT_FALSE(true); co_return; } } } EXPECT_FALSE((co_await gen1.next()).has_value()); EXPECT_FALSE((co_await gen2.next()).has_value()); EXPECT_FALSE((co_await gen1).has_value()); EXPECT_FALSE((co_await gen2).has_value()); co_return; }; run_loop(setup); } } // namespace