From 7416c64dc6add30003e9b53164b7bb8c031aeb3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Kardos?= Date: Wed, 8 May 2024 12:31:58 +0200 Subject: [PATCH 1/7] generator - improve test coverage --- include/asyncpp/generator.hpp | 28 ++++++----- test/test_generator.cpp | 90 ++++++++++++++++++++++++++++++++++- 2 files changed, 105 insertions(+), 13 deletions(-) diff --git a/include/asyncpp/generator.hpp b/include/asyncpp/generator.hpp index 0ca53bd..a606df8 100644 --- a/include/asyncpp/generator.hpp +++ b/include/asyncpp/generator.hpp @@ -63,24 +63,25 @@ namespace impl_generator { explicit iterator(promise_type* promise) : m_promise(promise) {} reference operator*() const { - assert(valid() && "iterator not dereferencable"); + assert(dereferenceable() && "iterator not dereferencable"); return m_promise->get_result().get_or_throw(); } iterator& operator++() { - assert(valid() && "iterator not incrementable"); - get_handle().resume(); + assert(incrementable() && "iterator not incrementable"); + m_promise->get_result().clear(); + if (!get_handle().done()) { + get_handle().resume(); + } return *this; } - iterator operator++(int) { - auto copy = *this; + void operator++(int) { ++*this; - return copy; } bool operator==(const iterator& rhs) const noexcept { - return (m_promise == rhs.m_promise) || (!valid() && !rhs.m_promise); + return (m_promise == rhs.m_promise) || (!incrementable() && !rhs.incrementable()); } bool operator!=(const iterator& rhs) const noexcept { @@ -88,8 +89,12 @@ namespace impl_generator { } private: - bool valid() const noexcept { - return m_promise && !get_handle().done(); + bool dereferenceable() const noexcept { + return m_promise->get_result().has_value(); + } + + bool incrementable() const noexcept { + return m_promise && m_promise->get_result().has_value(); } auto get_handle() const noexcept { @@ -113,11 +118,10 @@ class [[nodiscard]] generator { generator(promise_type* promise) : m_promise(promise) {} generator() = default; - generator(generator&& rhs) noexcept : m_promise(rhs.m_promise) { rhs.m_promise = nullptr; } + generator(generator&& rhs) noexcept : m_promise(std::exchange(rhs.m_promise, nullptr)) {} generator& operator=(generator&& rhs) noexcept { release(); - m_promise = rhs.m_promise; - rhs.m_promise = nullptr; + m_promise = std::exchange(rhs.m_promise, nullptr); return *this; } generator(const generator&) = delete; diff --git a/test/test_generator.cpp b/test/test_generator.cpp index a574255..fca4be3 100644 --- a/test/test_generator.cpp +++ b/test/test_generator.cpp @@ -36,7 +36,7 @@ TEST_CASE("Generator: sequence of references", "[Generator]") { } -TEST_CASE("Generator: sequence of mvoeables", "[Generator]") { +TEST_CASE("Generator: sequence of movables", "[Generator]") { static const auto coro = [](int count) -> generator> { for (int i = 0; i < count; ++i) { co_yield std::make_unique(i); @@ -48,4 +48,92 @@ TEST_CASE("Generator: sequence of mvoeables", "[Generator]") { values.push_back(*item); } REQUIRE(values == std::vector{ 0, 1, 2, 3 }); +} + + +TEST_CASE("Generator: const iterators", "[Generator]") { + static const auto coro = []() -> generator { + co_yield 0; + }; + const auto g = coro(); + std::vector values; + for (auto it = g.cbegin(); it != g.cend(); ++it) { + values.push_back(*it); + } + REQUIRE(values == std::vector{ 0 }); +} + + +TEST_CASE("Generator: unhandled exceptions", "[Generator]") { + static const auto coro = []() -> generator { + throw std::runtime_error("test"); + co_return; + }; + const auto g = coro(); + REQUIRE_THROWS_AS(*g.begin(), std::runtime_error); +} + + +TEST_CASE("Generator: iterator++", "[Generator]") { + static const auto coro = []() -> generator { + co_yield 0; + }; + const auto g = coro(); + auto it = g.begin(); + it++; + REQUIRE(it == g.end()); +} + + +TEST_CASE("Generator: ++iterator", "[Generator]") { + static const auto coro = []() -> generator { + co_yield 0; + }; + const auto g = coro(); + auto it = g.begin(); + ++it; + REQUIRE(it == g.end()); +} + + +TEST_CASE("Generator: iterator begin", "[Generator]") { + static const auto coro = []() -> generator { + co_return; + }; + const auto g = coro(); + auto it = g.begin(); + REQUIRE(it == g.begin()); +} + + +TEST_CASE("Generator: move ctor", "[Generator]") { + static const auto coro = []() -> generator { + co_yield 0; + co_yield 1; + }; + auto g = coro(); + auto m = std::move(g); + auto it = m.begin(); + REQUIRE(*it == 0); + ++it; + REQUIRE(*it == 1); + ++it; + REQUIRE(it == m.end()); +} + + +TEST_CASE("Generator: move assign", "[Generator]") { + static const auto coro = []() -> generator { + co_yield 0; + co_yield 1; + }; + auto g = coro(); + generator m = coro(); + m = std::move(g); + auto it = m.begin(); + REQUIRE(*it == 0); + ++it; + REQUIRE(*it == 1); + ++it; + REQUIRE(it == m.end()); } \ No newline at end of file From f5af5fe8465aebd5ee4c7d7c9d06136d93011c0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Kardos?= Date: Wed, 8 May 2024 13:30:58 +0200 Subject: [PATCH 2/7] deduplicate lock.hpp --- include/asyncpp/lock.hpp | 177 ++++++++++--------------------- include/asyncpp/mutex.hpp | 2 +- include/asyncpp/shared_mutex.hpp | 4 +- src/mutex.cpp | 2 +- src/shared_mutex.cpp | 4 +- 5 files changed, 61 insertions(+), 128 deletions(-) diff --git a/include/asyncpp/lock.hpp b/include/asyncpp/lock.hpp index eb81972..e8f48bf 100644 --- a/include/asyncpp/lock.hpp +++ b/include/asyncpp/lock.hpp @@ -8,104 +8,99 @@ namespace asyncpp { -template -class locked_mutex { + +template +class basic_locked_mutex { friend Mutex; public: - locked_mutex(locked_mutex&&) = default; - locked_mutex& operator=(locked_mutex&&) = default; - locked_mutex(const locked_mutex&) = delete; - locked_mutex& operator=(const locked_mutex&) = delete; + basic_locked_mutex(basic_locked_mutex&&) = default; + basic_locked_mutex& operator=(basic_locked_mutex&&) = default; + basic_locked_mutex(const basic_locked_mutex&) = delete; + basic_locked_mutex& operator=(const basic_locked_mutex&) = delete; Mutex& mutex() const noexcept { return *m_mtx; } + static constexpr bool shared() noexcept { + return Shared; + } + private: - locked_mutex(Mutex* mtx) : m_mtx(mtx) {} + basic_locked_mutex(Mutex* mtx) : m_mtx(mtx) {} Mutex* m_mtx = nullptr; }; template -class locked_mutex_shared { - friend Mutex; +using exclusively_locked_mutex = basic_locked_mutex; +template +using shared_locked_mutex = basic_locked_mutex; -public: - locked_mutex_shared(locked_mutex_shared&&) = default; - locked_mutex_shared& operator=(locked_mutex_shared&&) = default; - locked_mutex_shared(const locked_mutex_shared&) = delete; - locked_mutex_shared& operator=(const locked_mutex_shared&) = delete; - Mutex& mutex() const noexcept { - return *m_mtx; - } -private: - locked_mutex_shared(Mutex* mtx) : m_mtx(mtx) {} - Mutex* m_mtx = nullptr; -}; +template +class basic_lock { + // NOTE: GCC bugs out on `auto (Mutex::*Lock)()`, that's why `Lock` is simply `auto`. + using mutex_awaitable_t = std::invoke_result_t; -template -class unique_lock { - using mutex_awaitable = std::invoke_result_t; struct awaitable { - unique_lock* m_lock; - mutex_awaitable m_awaitable; + basic_lock* m_owner; + mutex_awaitable_t m_impl; auto await_ready() noexcept { - return m_awaitable.await_ready(); + return m_impl.await_ready(); } template auto await_suspend(std::coroutine_handle enclosing) noexcept { - return m_awaitable.await_suspend(enclosing); + return m_impl.await_suspend(enclosing); } void await_resume() noexcept { - m_awaitable.await_resume(); - m_lock->m_owned = true; + m_impl.await_resume(); + m_owner->m_owned = true; } }; public: - unique_lock(Mutex& mtx, std::defer_lock_t) noexcept : m_mtx(&mtx), m_owned(false) {} - unique_lock(Mutex& mtx, std::adopt_lock_t) noexcept : m_mtx(&mtx), m_owned(true) {} - unique_lock(locked_mutex&& lk) noexcept : m_mtx(&lk.mutex()), m_owned(true) {} - unique_lock(unique_lock&& rhs) noexcept : m_mtx(rhs.m_mtx), m_owned(rhs.m_owned) { + basic_lock(Mutex& mtx, std::defer_lock_t) noexcept : m_mtx(&mtx), m_owned(false) {} + basic_lock(Mutex& mtx, std::adopt_lock_t) noexcept : m_mtx(&mtx), m_owned(true) {} + basic_lock(basic_locked_mutex&& lk) noexcept : m_mtx(&lk.mutex()), m_owned(true) {} + basic_lock(basic_lock&& rhs) noexcept : m_mtx(rhs.m_mtx), m_owned(rhs.m_owned) { rhs.m_mtx = nullptr; rhs.m_owned = false; } - unique_lock& operator=(unique_lock&& rhs) noexcept { + basic_lock& operator=(basic_lock&& rhs) noexcept { if (owns_lock()) { - m_mtx->unlock(); + (m_mtx->*Unlock)(); } m_mtx = std::exchange(rhs.m_mtx, nullptr); m_owned = std::exchange(rhs.m_owned, false); return *this; } - unique_lock(const unique_lock& rhs) = delete; - unique_lock& operator=(const unique_lock& rhs) = delete; - ~unique_lock() { + basic_lock(const basic_lock& rhs) = delete; + basic_lock& operator=(const basic_lock& rhs) = delete; + ~basic_lock() { if (owns_lock()) { - m_mtx->unlock(); + (m_mtx->*Unlock)(); } } bool try_lock() noexcept { assert(!owns_lock()); - m_owned = m_mtx->try_lock(); + m_owned = (m_mtx->*TryLock)(); return m_owned; } auto operator co_await() noexcept { assert(!owns_lock()); - return awaitable(this, m_mtx->exclusive()); + return awaitable{ this, (m_mtx->*Lock)() }; } void unlock() noexcept { assert(owns_lock()); - m_mtx->unlock(); + (m_mtx->*Unlock)(); m_owned = false; } @@ -128,92 +123,30 @@ class unique_lock { template -unique_lock(locked_mutex&& lk) -> unique_lock; - +class unique_lock : public basic_lock { + using basic_lock::basic_lock; +}; template -class shared_lock { - using mutex_awaitable = std::invoke_result_t; - struct awaitable { - shared_lock* m_lock; - mutex_awaitable m_awaitable; - - auto await_ready() noexcept { - return m_awaitable.await_ready(); - } - - template - auto await_suspend(std::coroutine_handle enclosing) noexcept { - return m_awaitable.await_suspend(enclosing); - } - - void await_resume() noexcept { - m_awaitable.await_resume(); - m_lock->m_owned = true; - } - }; - -public: - shared_lock(Mutex& mtx, std::defer_lock_t) noexcept : m_mtx(&mtx), m_owned(false) {} - shared_lock(Mutex& mtx, std::adopt_lock_t) noexcept : m_mtx(&mtx), m_owned(true) {} - shared_lock(locked_mutex_shared lk) noexcept : m_mtx(&lk.mutex()), m_owned(true) {} - shared_lock(shared_lock&& rhs) noexcept : m_mtx(rhs.m_mtx), m_owned(rhs.m_owned) { - rhs.m_mtx = nullptr; - rhs.m_owned = false; - } - shared_lock& operator=(shared_lock&& rhs) noexcept { - if (owns_lock()) { - m_mtx->unlock_shared(); - } - m_mtx = std::exchange(rhs.m_mtx, nullptr); - m_owned = std::exchange(rhs.m_owned, false); - return *this; - } - shared_lock(const shared_lock& rhs) = delete; - shared_lock& operator=(const shared_lock& rhs) = delete; - ~shared_lock() { - if (owns_lock()) { - m_mtx->unlock_shared(); - } - } - - bool try_lock() noexcept { - assert(!owns_lock()); - m_owned = m_mtx->try_lock_shared(); - return m_owned; - } - - auto operator co_await() noexcept { - assert(!owns_lock()); - return awaitable(this, m_mtx->shared()); - } - - void unlock() noexcept { - assert(owns_lock()); - m_mtx->unlock_shared(); - m_owned = false; - } - - Mutex& mutex() const noexcept { - return *m_mtx; - } +class shared_lock : public basic_lock { + using basic_lock::basic_lock; +}; - bool owns_lock() const noexcept { - return m_owned; - } - operator bool() const noexcept { - return owns_lock(); - } - -private: - Mutex* m_mtx; - bool m_owned = false; -}; +template +unique_lock(exclusively_locked_mutex) -> unique_lock; +template +unique_lock(Mutex&, std::adopt_lock_t) -> unique_lock; +template +unique_lock(Mutex&, std::defer_lock_t) -> unique_lock; template -shared_lock(locked_mutex_shared lk) -> shared_lock; +shared_lock(shared_locked_mutex) -> shared_lock; +template +shared_lock(Mutex&, std::adopt_lock_t) -> shared_lock; +template +shared_lock(Mutex&, std::defer_lock_t) -> shared_lock; } // namespace asyncpp \ No newline at end of file diff --git a/include/asyncpp/mutex.hpp b/include/asyncpp/mutex.hpp index 0cd77de..d45416c 100644 --- a/include/asyncpp/mutex.hpp +++ b/include/asyncpp/mutex.hpp @@ -22,7 +22,7 @@ class mutex { template Promise> bool await_suspend(std::coroutine_handle enclosing) noexcept; - locked_mutex await_resume() noexcept; + exclusively_locked_mutex await_resume() noexcept; }; bool add_awaiting(awaitable* waiting); diff --git a/include/asyncpp/shared_mutex.hpp b/include/asyncpp/shared_mutex.hpp index d0dc498..30c9a86 100644 --- a/include/asyncpp/shared_mutex.hpp +++ b/include/asyncpp/shared_mutex.hpp @@ -34,7 +34,7 @@ class shared_mutex { : basic_awaitable(owner, awaitable_type::exclusive) {} bool await_ready() const noexcept; - locked_mutex await_resume() const noexcept; + exclusively_locked_mutex await_resume() const noexcept; }; struct shared_awaitable : basic_awaitable { @@ -42,7 +42,7 @@ class shared_mutex { : basic_awaitable(owner, awaitable_type::shared) {} bool await_ready() const noexcept; - locked_mutex_shared await_resume() const noexcept; + shared_locked_mutex await_resume() const noexcept; }; bool add_awaiting(basic_awaitable* waiting); diff --git a/src/mutex.cpp b/src/mutex.cpp index 012841b..ced6874 100644 --- a/src/mutex.cpp +++ b/src/mutex.cpp @@ -11,7 +11,7 @@ bool mutex::awaitable::await_ready() const noexcept { } -locked_mutex mutex::awaitable::await_resume() noexcept { +exclusively_locked_mutex mutex::awaitable::await_resume() noexcept { assert(m_owner); return { m_owner }; } diff --git a/src/shared_mutex.cpp b/src/shared_mutex.cpp index 4386c13..3ba671e 100644 --- a/src/shared_mutex.cpp +++ b/src/shared_mutex.cpp @@ -17,13 +17,13 @@ bool shared_mutex::shared_awaitable::await_ready() const noexcept { } -locked_mutex shared_mutex::exclusive_awaitable::await_resume() const noexcept { +exclusively_locked_mutex shared_mutex::exclusive_awaitable::await_resume() const noexcept { assert(m_owner); return { m_owner }; } -locked_mutex_shared shared_mutex::shared_awaitable::await_resume() const noexcept { +shared_locked_mutex shared_mutex::shared_awaitable::await_resume() const noexcept { assert(m_owner); return { m_owner }; } From d50caacf04d480e28c3ba2df82705b6c905b2819 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Kardos?= Date: Wed, 8 May 2024 16:38:29 +0200 Subject: [PATCH 3/7] improve testing for lock.hpp --- include/asyncpp/lock.hpp | 4 --- test/test_mutex.cpp | 57 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 56 insertions(+), 5 deletions(-) diff --git a/include/asyncpp/lock.hpp b/include/asyncpp/lock.hpp index e8f48bf..05ff213 100644 --- a/include/asyncpp/lock.hpp +++ b/include/asyncpp/lock.hpp @@ -22,10 +22,6 @@ class basic_locked_mutex { return *m_mtx; } - static constexpr bool shared() noexcept { - return Shared; - } - private: basic_locked_mutex(Mutex* mtx) : m_mtx(mtx) {} Mutex* m_mtx = nullptr; diff --git a/test/test_mutex.cpp b/test/test_mutex.cpp index f7b7ab6..5b2af10 100644 --- a/test/test_mutex.cpp +++ b/test/test_mutex.cpp @@ -103,7 +103,7 @@ TEST_CASE("Mutex: unique lock try_lock", "[Mutex]") { } -TEST_CASE("Mutex: unique lock await", "[Mutex]") { +TEST_CASE("Mutex: unique lock await immediate", "[Mutex]") { mutex mtx; mtx_scope_clear guard(mtx); @@ -115,6 +115,20 @@ TEST_CASE("Mutex: unique lock await", "[Mutex]") { } +TEST_CASE("Mutex: unique lock await suspended", "[Mutex]") { + mutex mtx; + mtx_scope_clear guard(mtx); + + unique_lock lk(mtx, std::defer_lock); + mtx.try_lock(); + auto monitor = lock(lk); + mtx.unlock(); + REQUIRE(monitor.get_counters().done); + REQUIRE(lk.owns_lock()); + REQUIRE(mtx._debug_is_locked()); +} + + TEST_CASE("Mutex: unique lock start locked", "[Mutex]") { mutex mtx; mtx_scope_clear guard(mtx); @@ -151,4 +165,45 @@ TEST_CASE("Mutex: unique lock destructor", "[Mutex]") { } REQUIRE(!mtx._debug_is_locked()); +} + + +TEST_CASE("Mutex: unique lock move ctor", "[Mutex]") { + mutex mtx; + mtx_scope_clear guard(mtx); + + { + unique_lock lk(mtx, std::defer_lock); + lk.try_lock(); + REQUIRE(mtx._debug_is_locked()); + unique_lock copy{ std::move(lk) }; + REQUIRE(!lk.owns_lock()); + REQUIRE(mtx._debug_is_locked()); + } + + REQUIRE(!mtx._debug_is_locked()); +} + + +TEST_CASE("Mutex: unique lock move assign", "[Mutex]") { + mutex mtx1; + mutex mtx2; + mtx_scope_clear guard1(mtx1); + mtx_scope_clear guard2(mtx2); + + { + unique_lock lk1(mtx1, std::defer_lock); + unique_lock lk2(mtx2, std::defer_lock); + lk1.try_lock(); + lk2.try_lock(); + REQUIRE(mtx1._debug_is_locked()); + REQUIRE(mtx2._debug_is_locked()); + lk1 = std::move(lk2); + REQUIRE(!lk2.owns_lock()); + REQUIRE(!mtx1._debug_is_locked()); + REQUIRE(mtx2._debug_is_locked()); + } + + REQUIRE(!mtx1._debug_is_locked()); + REQUIRE(!mtx2._debug_is_locked()); } \ No newline at end of file From 698dc77a7e26bba8cb055e0f1116c3b194e2958d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Kardos?= Date: Fri, 10 May 2024 22:46:04 +0200 Subject: [PATCH 4/7] fix typo --- test/test_task.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_task.cpp b/test/test_task.cpp index 4dff269..da9fd48 100644 --- a/test/test_task.cpp +++ b/test/test_task.cpp @@ -94,7 +94,7 @@ TEMPLATE_TEST_CASE("Task: interleaving abandon", "[Task]", task, shared_tas TestType coro() { co_return 1; - }; + } void task() { sched.resume(); From 29d9c54da1d67ea910147539643fc3f85626fcda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Kardos?= Date: Sat, 11 May 2024 10:08:02 +0200 Subject: [PATCH 5/7] comment wording --- include/asyncpp/task.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/asyncpp/task.hpp b/include/asyncpp/task.hpp index 2e9021e..787687b 100644 --- a/include/asyncpp/task.hpp +++ b/include/asyncpp/task.hpp @@ -22,7 +22,7 @@ namespace impl_task { void await_suspend(std::coroutine_handle handle) const noexcept { auto& owner = handle.promise(); owner.m_event.set(std::move(owner.m_result)); - auto self = std::move(owner.m_self); // owner.m_self.reset() call method on owner after it's been deleted. + auto self = std::move(owner.m_self); // owner.m_self.reset() would call method on owner after it's been deleted. self.reset(); } constexpr void await_resume() const noexcept {} From ccb29ea65652bdc9f1676f0c5451f9e12cf496b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Kardos?= Date: Sat, 11 May 2024 10:25:22 +0200 Subject: [PATCH 6/7] stream bind/launch & interleaved tests --- include/asyncpp/stream.hpp | 45 ++++++++++++++++-- test/test_stream.cpp | 94 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 135 insertions(+), 4 deletions(-) diff --git a/include/asyncpp/stream.hpp b/include/asyncpp/stream.hpp index 5c80b16..47d05df 100644 --- a/include/asyncpp/stream.hpp +++ b/include/asyncpp/stream.hpp @@ -70,6 +70,8 @@ namespace impl_stream { assert(owner.m_event); assert(owner.m_result.has_value()); owner.m_event->set(std::move(owner.m_result)); + auto self = std::move(owner.m_self); // owner.m_self.reset() would call method on owner after it's been deleted. + self.reset(); } constexpr void await_resume() const noexcept {} @@ -100,6 +102,22 @@ namespace impl_stream { m_result = std::nullopt; } + void start() noexcept { + if (!INTERLEAVED(m_started.test_and_set())) { + m_self.reset(this); + m_event.emplace(); + resume(); + } + } + + void reset() noexcept { + INTERLEAVED(m_started.clear()); + } + + bool ready() const { + return m_event && m_event->ready(); + } + void resume() final { return m_scheduler ? m_scheduler->schedule(*this) : resume_now(); } @@ -121,6 +139,8 @@ namespace impl_stream { } private: + rc_ptr m_self; + std::atomic_flag m_started; std::optional>>> m_event; task_result>> m_result; }; @@ -148,6 +168,7 @@ namespace impl_stream { item await_resume() { assert(m_awaited->has_event()); + m_awaited->reset(); return { m_base.await_resume() }; } }; @@ -155,10 +176,9 @@ namespace impl_stream { template auto promise::await() noexcept { - m_event.emplace(); - auto aw = awaitable(m_event->operator co_await(), rc_ptr(this)); - resume(); - return aw; + start(); + assert(m_event); + return awaitable(m_event->operator co_await(), rc_ptr(this)); } } // namespace impl_stream @@ -180,6 +200,23 @@ class [[nodiscard]] stream { return m_promise->await(); } + bool ready() const { + assert(valid()); + return m_promise->ready(); + } + + void launch() { + assert(valid()); + m_promise->start(); + } + + void bind(scheduler& scheduler) { + assert(valid()); + if (m_promise) { + m_promise->m_scheduler = &scheduler; + } + } + bool valid() const { return !!m_promise; } diff --git a/test/test_stream.cpp b/test/test_stream.cpp index ef525d9..c19c836 100644 --- a/test/test_stream.cpp +++ b/test/test_stream.cpp @@ -1,8 +1,10 @@ +#include "helper_schedulers.hpp" #include "monitor_allocator.hpp" #include "monitor_task.hpp" #include #include +#include #include @@ -109,6 +111,98 @@ TEST_CASE("Stream: destroy", "[Stream]") { } +TEST_CASE("Stream: interleaving co_await", "[Stream]") { + struct scenario { + thread_locked_scheduler awaiter_sched; + thread_locked_scheduler awaited_sched; + stream result; + + scenario() { + constexpr auto awaited = []() -> stream { + co_yield 1; + }; + constexpr auto awaiter = [](stream awaited) -> stream { + const auto it = co_await awaited; + const auto value = *it; + co_yield value; + }; + + auto tmp = launch(awaited(), awaited_sched); + result = launch(awaiter(std::move(tmp)), awaiter_sched); + } + + void awaiter() { + awaiter_sched.resume(); + if (!result.ready()) { + INTERLEAVED_ACQUIRE(awaiter_sched.wait()); + awaiter_sched.resume(); + } + REQUIRE(1 == *join(result)); + result = {}; + } + + void awaited() { + awaited_sched.resume(); + } + }; + + INTERLEAVED_RUN( + scenario, + THREAD("awaited", &scenario::awaited), + THREAD("awaiter", &scenario::awaiter)); +} + + +TEST_CASE("Stream: interleaving abandon", "[Stream]") { + struct scenario : testing::validated_scenario { + thread_locked_scheduler sched; + stream result; + + scenario() { + result = launch(coro(), sched); + } + + stream coro() { + co_yield 1; + } + + void task() { + sched.resume(); + } + + void abandon() { + result = {}; + } + + void validate(const testing::path& path) override {} + }; + + INTERLEAVED_RUN( + scenario, + THREAD("task", &scenario::task), + THREAD("abandon", &scenario::abandon)); +} + + +TEST_CASE("Task: abandon (not started)", "[Task]") { + static const auto coro = []() -> stream { + co_return; + }; + static_cast(coro()); +} + + +TEST_CASE("Task: abandon (mid execution)", "[Task]") { + static const auto coro = []() -> stream { + co_yield 1; + co_yield 2; + co_return; + }; + auto s = coro(); + REQUIRE(1 == *join(s)); +} + + template auto allocator_free(std::allocator_arg_t, monitor_allocator<>& alloc) -> Stream { co_yield alloc; From 32b561ba9b54203055bd209d5c410edb3be3d0e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Kardos?= Date: Sat, 11 May 2024 10:42:44 +0200 Subject: [PATCH 7/7] join: test for exceptions --- test/test_join.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/test_join.cpp b/test/test_join.cpp index 93fe100..3a5c125 100644 --- a/test/test_join.cpp +++ b/test/test_join.cpp @@ -42,4 +42,15 @@ TEST_CASE("Join: ref", "[Join]") { int& result = join(t); REQUIRE(result == 1); REQUIRE(&result == &value); +} + + +TEST_CASE("Join: exception", "[Join]") { + static const auto coro = []() -> task { + throw std::runtime_error("test"); + co_return; + }; + + auto t = coro(); + REQUIRE_THROWS_AS(join(t), std::runtime_error); } \ No newline at end of file