| // Copyright 2017 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <lib/fit/defer.h> |
| #include <lib/fit/function.h> |
| |
| #include "gtest/gtest.h" |
| #include "peridot/bin/ledger/coroutine/coroutine_impl.h" |
| |
| namespace coroutine { |
| namespace { |
| |
| size_t Fact(size_t n) { |
| if (n == 0) { |
| return 1; |
| } |
| return Fact(n - 1) * n; |
| } |
| |
| void UseStack() { EXPECT_EQ(120u, Fact(5)); } |
| |
| TEST(Coroutine, SingleRoutine) { |
| CoroutineServiceImpl coroutine_service; |
| |
| CoroutineHandler* handler = nullptr; |
| constexpr int kLoopCount = 10; |
| int result = kLoopCount; |
| |
| coroutine_service.StartCoroutine( |
| [&handler, &result](CoroutineHandler* current_handler) { |
| handler = current_handler; |
| UseStack(); |
| do { |
| EXPECT_EQ(ContinuationStatus::OK, current_handler->Yield()); |
| UseStack(); |
| --result; |
| } while (result); |
| }); |
| |
| EXPECT_TRUE(handler); |
| EXPECT_EQ(kLoopCount, result); |
| |
| for (int i = kLoopCount - 1; i >= 0; --i) { |
| handler->Resume(ContinuationStatus::OK); |
| EXPECT_EQ(i, result); |
| } |
| } |
| |
| TEST(Coroutine, ManyRoutines) { |
| constexpr size_t nb_routines = 1000; |
| |
| CoroutineServiceImpl coroutine_service; |
| |
| std::set<CoroutineHandler*> handlers; |
| |
| for (size_t i = 0; i < nb_routines; ++i) { |
| coroutine_service.StartCoroutine([&handlers](CoroutineHandler* handler) { |
| handlers.insert(handler); |
| UseStack(); |
| |
| for (size_t i = 0; i < 3; ++i) { |
| EXPECT_EQ(ContinuationStatus::OK, handler->Yield()); |
| UseStack(); |
| } |
| |
| handlers.erase(handlers.find(handler)); |
| }); |
| } |
| |
| EXPECT_EQ(nb_routines, handlers.size()); |
| |
| for (size_t i = 0; i < 2; ++i) { |
| for (CoroutineHandler* handler : handlers) { |
| handler->Resume(ContinuationStatus::OK); |
| } |
| } |
| |
| EXPECT_EQ(nb_routines, handlers.size()); |
| |
| for (size_t i = 0; i < nb_routines; ++i) { |
| (*handlers.begin())->Resume(ContinuationStatus::OK); |
| } |
| |
| EXPECT_TRUE(handlers.empty()); |
| } |
| |
| TEST(Coroutine, AsyncCall) { |
| CoroutineServiceImpl coroutine_service; |
| |
| fit::function<void(size_t)> callback; |
| auto callable = [&callback](fit::function<void(size_t)> called_callback) { |
| callback = std::move(called_callback); |
| }; |
| |
| size_t received_value = 0; |
| coroutine_service.StartCoroutine( |
| [callable, &received_value](CoroutineHandler* handler) { |
| UseStack(); |
| size_t value; |
| EXPECT_EQ(ContinuationStatus::OK, SyncCall(handler, callable, &value)); |
| UseStack(); |
| received_value = value; |
| }); |
| |
| EXPECT_TRUE(callback); |
| EXPECT_EQ(0u, received_value); |
| |
| callback(1); |
| |
| EXPECT_EQ(1u, received_value); |
| } |
| |
| TEST(Coroutine, SynchronousAsyncCall) { |
| CoroutineServiceImpl coroutine_service; |
| |
| size_t received_value = 0; |
| coroutine_service.StartCoroutine( |
| [&received_value](CoroutineHandler* handler) { |
| UseStack(); |
| EXPECT_EQ(ContinuationStatus::OK, |
| SyncCall( |
| handler, |
| [](fit::function<void(size_t)> callback) { callback(1); }, |
| &received_value)); |
| UseStack(); |
| }); |
| EXPECT_EQ(1u, received_value); |
| } |
| |
| TEST(Coroutine, DroppedAsyncCall) { |
| CoroutineServiceImpl coroutine_service; |
| |
| bool ended = false; |
| coroutine_service.StartCoroutine([&ended](CoroutineHandler* handler) { |
| EXPECT_EQ(ContinuationStatus::INTERRUPTED, |
| SyncCall(handler, [](fit::function<void()> callback) { |
| // |callback| is dropped here. |
| })); |
| ended = true; |
| }); |
| EXPECT_TRUE(ended); |
| } |
| |
| TEST(Coroutine, DroppedAsyncCallAsynchronously) { |
| CoroutineServiceImpl coroutine_service; |
| |
| bool ended = false; |
| fit::function<void()> callback; |
| |
| coroutine_service.StartCoroutine([&ended, |
| &callback](CoroutineHandler* handler) { |
| EXPECT_EQ( |
| ContinuationStatus::INTERRUPTED, |
| SyncCall(handler, [&callback](fit::function<void()> received_callback) { |
| callback = std::move(received_callback); |
| })); |
| ended = true; |
| }); |
| |
| EXPECT_FALSE(ended); |
| EXPECT_TRUE(callback); |
| callback = [] {}; |
| |
| EXPECT_TRUE(ended); |
| } |
| |
| TEST(Coroutine, RunAndDroppedAsyncCallAfterCoroutineDeletion) { |
| bool ended = false; |
| fit::function<void()> callback; |
| { |
| CoroutineServiceImpl coroutine_service; |
| |
| coroutine_service.StartCoroutine([&ended, |
| &callback](CoroutineHandler* handler) { |
| EXPECT_EQ(ContinuationStatus::INTERRUPTED, |
| SyncCall(handler, |
| [&callback](fit::function<void()> received_callback) { |
| callback = std::move(received_callback); |
| })); |
| ended = true; |
| }); |
| |
| EXPECT_FALSE(ended); |
| EXPECT_TRUE(callback); |
| } |
| |
| EXPECT_TRUE(ended); |
| callback(); |
| } |
| |
| TEST(Coroutine, Interrupt) { |
| ContinuationStatus status = ContinuationStatus::OK; |
| |
| { |
| CoroutineServiceImpl coroutine_service; |
| |
| coroutine_service.StartCoroutine([&status](CoroutineHandler* handler) { |
| UseStack(); |
| status = handler->Yield(); |
| UseStack(); |
| }); |
| |
| EXPECT_EQ(ContinuationStatus::OK, status); |
| } |
| |
| EXPECT_EQ(ContinuationStatus::INTERRUPTED, status); |
| } |
| |
| #if !__has_feature(address_sanitizer) |
| TEST(Coroutine, ReuseStack) { |
| CoroutineServiceImpl coroutine_service; |
| CoroutineHandler* handler = nullptr; |
| uintptr_t stack_pointer = 0; |
| size_t nb_coroutines_calls = 0; |
| |
| for (size_t i = 0; i < 2; ++i) { |
| coroutine_service.StartCoroutine( |
| [&handler, &stack_pointer, |
| &nb_coroutines_calls](CoroutineHandler* called_handler) { |
| UseStack(); |
| int a; |
| uintptr_t addr = reinterpret_cast<uintptr_t>(&a); |
| if (stack_pointer == 0) { |
| stack_pointer = addr; |
| } |
| EXPECT_EQ(addr, stack_pointer); |
| handler = called_handler; |
| EXPECT_EQ(ContinuationStatus::OK, called_handler->Yield()); |
| UseStack(); |
| |
| ++nb_coroutines_calls; |
| }); |
| handler->Resume(ContinuationStatus::OK); |
| } |
| |
| EXPECT_EQ(2u, nb_coroutines_calls); |
| } |
| #endif // !__has_feature(address_sanitizer) |
| |
| TEST(Coroutine, ResumeCoroutineInOtherCoroutineDestructor) { |
| CoroutineServiceImpl coroutine_service; |
| CoroutineHandler* handler1 = nullptr; |
| CoroutineHandler* handler2 = nullptr; |
| bool routine1_done = false; |
| bool routine2_done = false; |
| |
| coroutine_service.StartCoroutine([&](CoroutineHandler* local_handler1) { |
| handler1 = local_handler1; |
| auto autocall = |
| fit::defer([&] { handler1->Resume(ContinuationStatus::OK); }); |
| coroutine_service.StartCoroutine( |
| [&handler2, &routine2_done, |
| autocall = std::move(autocall)](CoroutineHandler* local_handler2) { |
| handler2 = local_handler2; |
| EXPECT_EQ(ContinuationStatus::OK, handler2->Yield()); |
| routine2_done = true; |
| }); |
| EXPECT_EQ(ContinuationStatus::OK, handler1->Yield()); |
| routine1_done = true; |
| }); |
| |
| handler2->Resume(ContinuationStatus::OK); |
| |
| EXPECT_TRUE(routine1_done); |
| EXPECT_TRUE(routine2_done); |
| } |
| |
| TEST(Coroutine, AsyncCallCapture) { |
| CoroutineServiceImpl coroutine_service; |
| |
| fit::function<void(size_t)> callback; |
| auto callable = [&callback](fit::function<void(size_t)> called_callback) { |
| callback = std::move(called_callback); |
| }; |
| |
| size_t value = 0; |
| CoroutineHandler* coroutine_handler; |
| coroutine_service.StartCoroutine( |
| [callable, &coroutine_handler, &value](CoroutineHandler* handler) { |
| coroutine_handler = handler; |
| EXPECT_EQ(ContinuationStatus::INTERRUPTED, |
| SyncCall(handler, callable, &value)); |
| return; |
| }); |
| |
| EXPECT_TRUE(callback); |
| |
| coroutine_handler->Resume(ContinuationStatus::INTERRUPTED); |
| |
| callback(10); |
| |
| EXPECT_EQ(0u, value); |
| } |
| |
| } // namespace |
| } // namespace coroutine |