blob: a2e3fb5807a3134849e245fa56873685c133b3fb [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 <lib/zx/time.h>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "src/ledger/lib/coroutine/coroutine_impl.h"
#include "src/ledger/lib/logging/logging.h"
namespace coroutine {
namespace {
size_t Fact(size_t n) {
if (n == 0) {
return 1;
}
return Fact(n - 1) * n;
}
void UseStack() { EXPECT_EQ(Fact(5), 120u); }
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(current_handler->Yield(), ContinuationStatus::OK);
UseStack();
--result;
} while (result);
});
EXPECT_TRUE(handler);
EXPECT_EQ(result, kLoopCount);
for (int i = kLoopCount - 1; i >= 0; --i) {
handler->Resume(ContinuationStatus::OK);
EXPECT_EQ(result, i);
}
}
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(handler->Yield(), ContinuationStatus::OK);
UseStack();
}
handlers.erase(handlers.find(handler));
});
}
EXPECT_EQ(handlers.size(), nb_routines);
for (size_t i = 0; i < 2; ++i) {
for (CoroutineHandler* handler : handlers) {
handler->Resume(ContinuationStatus::OK);
}
}
EXPECT_EQ(handlers.size(), nb_routines);
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(SyncCall(handler, callable, &value), ContinuationStatus::OK);
UseStack();
received_value = value;
});
EXPECT_TRUE(callback);
EXPECT_EQ(received_value, 0u);
callback(1);
EXPECT_EQ(received_value, 1u);
}
TEST(Coroutine, SynchronousAsyncCall) {
CoroutineServiceImpl coroutine_service;
size_t received_value = 0;
coroutine_service.StartCoroutine([&received_value](CoroutineHandler* handler) {
UseStack();
EXPECT_EQ(
SyncCall(
handler, [](fit::function<void(size_t)> callback) { callback(1); }, &received_value),
ContinuationStatus::OK);
UseStack();
});
EXPECT_EQ(received_value, 1u);
}
TEST(Coroutine, DroppedAsyncCall) {
CoroutineServiceImpl coroutine_service;
bool ended = false;
coroutine_service.StartCoroutine([&ended](CoroutineHandler* handler) {
EXPECT_EQ(SyncCall(handler,
[](fit::function<void()> callback) {
// |callback| is dropped here.
}),
ContinuationStatus::INTERRUPTED);
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(SyncCall(handler,
[&callback](fit::function<void()> received_callback) {
callback = std::move(received_callback);
}),
ContinuationStatus::INTERRUPTED);
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(SyncCall(handler,
[&callback](fit::function<void()> received_callback) {
callback = std::move(received_callback);
}),
ContinuationStatus::INTERRUPTED);
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(status, ContinuationStatus::OK);
}
EXPECT_EQ(status, ContinuationStatus::INTERRUPTED);
}
#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(called_handler->Yield(), ContinuationStatus::OK);
UseStack();
++nb_coroutines_calls;
});
handler->Resume(ContinuationStatus::OK);
}
EXPECT_EQ(nb_coroutines_calls, 2u);
}
#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(handler2->Yield(), ContinuationStatus::OK);
routine2_done = true;
});
EXPECT_EQ(handler1->Yield(), ContinuationStatus::OK);
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(SyncCall(handler, callable, &value), ContinuationStatus::INTERRUPTED);
return;
});
EXPECT_TRUE(callback);
coroutine_handler->Resume(ContinuationStatus::INTERRUPTED);
callback(10);
EXPECT_EQ(value, 0u);
}
TEST(Coroutine, DoubleResume) {
// This tests the existence of a debug assertion.
// Skip this test when debug assertions are not enabled.
bool running_debug = false;
LEDGER_DCHECK(running_debug = true);
if (!running_debug) {
GTEST_SKIP();
}
CoroutineServiceImpl coroutine_service;
ASSERT_DEATH(
{
coroutine_service.StartCoroutine(
[](CoroutineHandler* handler) { handler->Resume(ContinuationStatus::INTERRUPTED); });
},
"Attempting to resume a running coroutine");
}
TEST(Coroutine, DoubleYield) {
// This tests the existence of a debug assertion.
// Skip this test when debug assertions are not enabled.
bool running_debug = false;
LEDGER_DCHECK(running_debug = true);
if (!running_debug) {
GTEST_SKIP();
}
CoroutineServiceImpl coroutine_service;
CoroutineHandler* coroutine_handler;
ASSERT_DEATH(
{
coroutine_service.StartCoroutine([&coroutine_handler](CoroutineHandler* handler) {
coroutine_handler = handler;
(void)handler->Yield();
});
(void)coroutine_handler->Yield();
},
"Attempting to yield from outside the coroutine");
}
} // namespace
} // namespace coroutine