blob: ce56332da5ae041aa8145318912a7f910c6a1f9f [file] [log] [blame]
// 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