blob: 5ecbe0078d6e0187bd00af9b95638a8a4c4bd22d [file] [log] [blame]
// Copyright 2020 The Chromium OS 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 <array>
#include <base/test/task_environment.h>
#include <base/threading/sequenced_task_runner_handle.h>
#include <base/threading/platform_thread.h>
#include <base/threading/thread.h>
#include <gtest/gtest.h>
#include <string>
#include <base/test/test_timeouts.h>
#include "vm_tools/concierge/future.h"
namespace vm_tools {
class FutureTest : public ::testing::Test {
private:
void SetUp() override {
ASSERT_TRUE(worker_thread_.Start());
task_runner_ = worker_thread_.task_runner();
}
void TearDown() override {
task_runner_ = nullptr;
worker_thread_.Stop();
}
protected:
// Both single and multi threaded usage of future is tested
base::test::TaskEnvironment task_environment_;
base::Thread worker_thread_{"worker thread"};
scoped_refptr<base::SequencedTaskRunner> task_runner_;
};
// The comments below assume basic knowledge of the promise/future programming
// model.
TEST_F(FutureTest, Tutorial) {
{
// Dispatching a basic function to |task_runner_|, and returns a future to
// wait on
auto sum = Async<int>(
task_runner_, // Either the current or a different thread would work
base::BindOnce([](int x, int y) { return Resolve<int>(x + y); }, 4, 3));
// Blocking wait on the future returned by the |Async| function.
// A |RunLoop| with |kNestableTasksAllowed| is used in Get() to prevent
// deadlock while waiting for the task to finish on the same thread.
EXPECT_EQ(sum.Get().val, 7);
}
{
auto sum =
AsyncNoReject<int>(
task_runner_,
base::BindOnce(
[](int x, int y) {
return x +
y; // Use AsyncNoReject here to avoid boiletplate code
},
1, 2))
// Chain another async function. The return of the previous func |x
// + y| needs to match the type of |z|.
//
// The Then func will be posted to the same task_runner by default.
// Use |future.Via(another_task_runner)| to change the task_runner
// in the middle of a chain if needed.
.ThenNoReject(base::BindOnce([](int z) { return z + 5; }));
EXPECT_EQ(sum.Get().val, 8);
}
{
Future<int> future =
Async(task_runner_, base::BindOnce([]() {
// Reject, bypass any |Then| after here and before the first
// |OnReject|
return Reject<int>();
}))
.Then(base::BindOnce([](int x) {
// no-op, as any |Then| func after a reject is bypassed
return Resolve<int>(1);
}))
// Here, the default error type is void. If a user defined error
// type is used, the reject handling func will take an Error arg.
// See FutureTest - RejectTypes for an example of that
.OnReject(base::BindOnce([]() {
// Can either |Resolve| or |Reject| here.
return Resolve<int>(2);
}));
GetResult<int> ret = future.Get();
EXPECT_EQ(ret.val, 2);
EXPECT_FALSE(ret.rejected); // handled, therefore false
}
}
TEST_F(FutureTest, VoidType) {
{
Async(task_runner_, base::BindOnce([]() { return Resolve<void>(); })).Get();
}
}
TEST_F(FutureTest, Chaining) {
{
auto future =
Async(task_runner_,
base::BindOnce([](int x, int y) { return Resolve<int>(x + y); },
4, 3))
.Then(base::BindOnce([](int z) { return Resolve<int>(z * 2); }));
EXPECT_EQ(future.Get().val, 14);
}
{
auto future =
Async(task_runner_,
base::BindOnce([](int x, int y) { return Resolve<int>(x + y); },
4, 3))
.Then(base::BindOnce([](int z) { return Resolve<int>(z * 2); }))
.Then(base::BindOnce([](int z) { return Resolve<int>(z * 3); }));
EXPECT_EQ(future.Get().val, 42);
}
}
TEST_F(FutureTest, Reject) {
// Reject, Future<void>
{
int x = 1;
Future<void> future =
Async(task_runner_, base::BindOnce([]() { return Reject<void>(); }))
.ThenNoReject(base::BindOnce(
[](int* x) {
*x *=
2; // no-op, as Reject above should break the then chain
},
base::Unretained(&x)))
.OnReject(base::BindOnce(
[](int* x) {
*x *= 3;
return Resolve<void>();
},
base::Unretained(&x)));
EXPECT_FALSE(future.Get().rejected);
EXPECT_EQ(x, 3);
}
// Reject again, Future<int>
{
Future<int> future =
Async(task_runner_, base::BindOnce([]() { return Reject<int>(); }))
.Then(base::BindOnce([](int x) {
// no-op, as Reject above should break the then chain
return Resolve<int>(1);
}))
.OnReject(base::BindOnce([]() { return Reject<int>(); }));
EXPECT_TRUE(future.Get().rejected);
}
// Resolve, Future<int>
{
Future<int> future =
Async(task_runner_, base::BindOnce([]() { return Resolve<int>(1); }))
.Then(base::BindOnce([](int x) { return Resolve<int>(x * 2); }))
.OnReject(base::BindOnce([]() {
return Resolve<int>(
3); // should be no-op as no promise is rejected
}));
GetResult<int> ret = future.Get();
EXPECT_FALSE(ret.rejected);
EXPECT_EQ(ret.val, 2);
}
}
TEST_F(FutureTest, RejectTypes) {
{
Future<int, std::string> future =
Async(task_runner_,
base::BindOnce([]() { return Reject<int, std::string>("a"); }))
.OnReject(base::BindOnce([](std::string err) {
if (err == "a") {
return Resolve<int, std::string>(5);
} else {
return Reject<int, std::string>("noooo");
}
}));
GetResult<int, std::string> ret = future.Get();
EXPECT_FALSE(ret.rejected);
EXPECT_EQ(ret.val, 5);
}
{
Future<int, std::string> future =
Async(task_runner_,
base::BindOnce([]() { return Reject<int, std::string>("b"); }))
.OnReject(base::BindOnce([](std::string err) {
if (err == "a") {
return Resolve<int, std::string>(5);
} else {
return Reject<int, std::string>("noooo");
}
}));
GetResult<int, std::string> ret = future.Get();
EXPECT_TRUE(ret.rejected);
EXPECT_EQ(ret.err, "noooo");
}
}
TEST_F(FutureTest, Tuple) {
{
auto future =
Async(task_runner_, base::BindOnce([]() {
return Resolve<
std::tuple<int, int8_t, int16_t, int32_t, int64_t>>(
{1, 2, 3, 4, 5});
}))
.Then(
// tuple is automatically unpacked
base::BindOnce([](int a, int b, int c, int d, int e) {
return Resolve<int>(a + b + c + d + e);
}));
EXPECT_EQ(future.Get().val, 15);
}
{
auto future =
Async(task_runner_, base::BindOnce([]() {
return Resolve<
std::tuple<int, int8_t, int16_t, int32_t, int64_t>>(
{1, 2, 3, 4, 5});
}))
.ThenNoReject(
// Taking a std::tuple without unpacking should still work
base::BindOnce(
[](std::tuple<int, int8_t, int16_t, int32_t, int64_t> t) {
return std::get<0>(t) + std::get<1>(t) + std::get<2>(t) +
std::get<3>(t) + std::get<4>(t);
}));
EXPECT_EQ(future.Get().val, 15);
}
}
TEST_F(FutureTest, Array) {
{
auto future =
Async(task_runner_, base::BindOnce([]() {
return Resolve<std::array<int, 5>>({1, 2, 3, 4, 5});
// std::array is automatically unpacked
}))
.Then(base::BindOnce([](int a, int b, int c, int d, int e) {
return Resolve<int>(a * b * c * d * e);
}));
EXPECT_EQ(future.Get().val, 120);
}
{
auto future = Async(task_runner_, base::BindOnce([]() {
return Resolve<std::array<int, 5>>({1, 2, 3, 4, 5});
}))
.Then(base::BindOnce([](std::array<int, 5> a) {
// Taking a std::array without unpacking should still
// work
return Resolve<int>(a[0] * a[1] * a[2] * a[3] * a[4]);
}));
EXPECT_EQ(future.Get().val, 120);
}
}
TEST_F(FutureTest, Collect) {
// Different threads
{
std::vector<Future<int>> futures;
constexpr int n = 10;
for (int i = 0; i < n; ++i) {
futures.push_back(
Async(task_runner_,
base::BindOnce([](int x) { return Resolve<int>(x); }, i)));
}
Future<std::vector<int>> future = Collect(task_runner_, std::move(futures));
GetResult<std::vector<int>> ret = future.Get();
for (int i = 0; i < n; ++i) {
EXPECT_EQ(ret.val[i], i);
}
EXPECT_FALSE(ret.rejected);
}
// Same thread
{
std::vector<Future<int>> futures;
constexpr int n = 10;
for (int i = 0; i < n; ++i) {
futures.push_back(
Async(base::SequencedTaskRunnerHandle::Get(),
base::BindOnce([](int x) { return Resolve<int>(x); }, i)));
}
Future<std::vector<int>> future =
Collect(base::SequencedTaskRunnerHandle::Get(), std::move(futures));
GetResult<std::vector<int>> ret = future.GetWithRunLoop();
for (int i = 0; i < n; ++i) {
EXPECT_EQ(ret.val[i], i);
}
EXPECT_FALSE(ret.rejected);
}
// Same thread, rejected
{
std::vector<Future<int>> futures;
constexpr int n = 10;
for (int i = 0; i < n; ++i) {
futures.push_back(Async(base::SequencedTaskRunnerHandle::Get(),
base::BindOnce(
[](int x) {
if (x == 7) {
return Reject<int>();
} else {
return Resolve<int>(x);
}
},
i)));
}
Future<std::vector<int>> future =
Collect(base::SequencedTaskRunnerHandle::Get(), std::move(futures));
GetResult<std::vector<int>> ret = future.GetWithRunLoop();
EXPECT_TRUE(ret.rejected);
}
}
TEST_F(FutureTest, Flatten) {
// Worker thread
AsyncNoReject(task_runner_,
base::BindOnce([]() { return ResolvedFuture(true); }))
.Flatten()
.Get();
// Same thread
EXPECT_TRUE(
AsyncNoReject(base::SequencedTaskRunnerHandle::Get(),
base::BindOnce([]() { return ResolvedFuture(true); }))
.Flatten()
.GetWithRunLoop()
.val);
AsyncNoReject(base::SequencedTaskRunnerHandle::Get(),
base::BindOnce([]() { return ResolvedFuture<void>(); }))
.Flatten()
.GetWithRunLoop();
{
auto ret = AsyncNoReject(base::SequencedTaskRunnerHandle::Get(),
base::BindOnce([]() { return 2; }))
.ThenNoReject(base::BindOnce([](int x) {
return AsyncNoReject(
base::SequencedTaskRunnerHandle::Get(),
base::BindOnce([](int x) { return x * 3; }, x));
}))
.Flatten()
.GetWithRunLoop();
EXPECT_EQ(ret.val, 6);
EXPECT_FALSE(ret.rejected);
}
{
auto ret = AsyncNoReject(base::SequencedTaskRunnerHandle::Get(),
base::BindOnce([]() { return 2; }))
.ThenNoReject(base::BindOnce([](int x) {
return Async(base::SequencedTaskRunnerHandle::Get(),
base::BindOnce(
[](int x) { return Reject<int>(); }, x));
}))
.Flatten()
.GetWithRunLoop();
EXPECT_TRUE(ret.rejected);
}
}
TEST_F(FutureTest, NoDeadlock) {
// Fulfill promise in another thread
{
Promise<bool> promise;
Future<bool> future = promise.GetFuture(task_runner_);
task_runner_->PostDelayedTask(
FROM_HERE,
base::BindOnce([](Promise<bool> promise) { promise.SetValue(true); },
std::move(promise)),
base::TimeDelta::FromMilliseconds(10));
EXPECT_TRUE(future.Get().val);
}
// Nested run loops
{
base::RunLoop loop;
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(
[](base::Closure closure) {
Promise<bool> promise;
Future<bool> future =
promise.GetFuture(base::SequencedTaskRunnerHandle::Get());
base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE,
base::BindOnce(
[](Promise<bool> promise) { promise.SetValue(true); },
std::move(promise)),
base::TimeDelta::FromMilliseconds(10));
EXPECT_TRUE(future.GetWithRunLoop().val);
closure.Run();
},
loop.QuitClosure()));
loop.Run();
}
{
Promise<bool> promise;
Future<bool> future =
promise.GetFuture(base::SequencedTaskRunnerHandle::Get());
task_runner_->PostDelayedTask(
FROM_HERE,
base::BindOnce(
[](Promise<bool> promise,
scoped_refptr<base::SequencedTaskRunner> main_thread_runner) {
main_thread_runner->PostTask(
FROM_HERE,
base::BindOnce(
[](Promise<bool> promise) { promise.SetValue(true); },
std::move(promise)));
},
std::move(promise), base::SequencedTaskRunnerHandle::Get()),
base::TimeDelta::FromMilliseconds(10));
EXPECT_TRUE(future.GetWithRunLoop().val);
}
{
Promise<bool> promise;
Future<bool> future =
promise.GetFuture(base::SequencedTaskRunnerHandle::Get());
task_runner_->PostTask(
FROM_HERE,
base::BindOnce(
[](Promise<bool> promise,
scoped_refptr<base::SequencedTaskRunner> main_thread_runner) {
base::PlatformThread::Sleep(
base::TimeDelta::FromMilliseconds(10));
main_thread_runner->PostTask(
FROM_HERE,
base::BindOnce(
[](Promise<bool> promise) { promise.SetValue(true); },
std::move(promise)));
},
std::move(promise), base::SequencedTaskRunnerHandle::Get()));
EXPECT_TRUE(future.GetWithRunLoop().val);
}
}
TEST_F(FutureTest, SameThread) {
{
auto sum = Async(
base::SequencedTaskRunnerHandle::Get(),
base::BindOnce([](int x, int y) { return Resolve<int>(x + y); }, 4, 3));
EXPECT_EQ(sum.GetWithRunLoop().val, 7);
}
{
auto future = Async(base::SequencedTaskRunnerHandle::Get(),
base::BindOnce([]() { return Resolve<void>(); }));
future.GetWithRunLoop();
}
{
Promise<bool> promise;
Future<bool> future =
promise.GetFuture(base::SequencedTaskRunnerHandle::Get());
promise.SetValue(true);
EXPECT_EQ(future.GetWithRunLoop().val, true);
}
{
auto func = []() {
Promise<bool> promise;
Future<bool> future =
promise.GetFuture(base::SequencedTaskRunnerHandle::Get());
promise.SetValue(true);
return future;
};
func().GetWithRunLoop();
}
{
Promise<void> promise;
Future<void> future =
promise.GetFuture(base::SequencedTaskRunnerHandle::Get());
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce([](Promise<void> promise) { promise.SetValue(); },
std::move(promise)));
future.GetWithRunLoop();
}
}
TEST_F(FutureTest, WaitFor) {
{
Promise<bool> promise;
Future<bool> future = promise.GetFuture(task_runner_);
task_runner_->PostDelayedTask(
FROM_HERE,
base::BindOnce([](Promise<bool> promise) { promise.SetValue(true); },
std::move(promise)),
base::TimeDelta::FromMilliseconds(1000));
EXPECT_FALSE(future.WaitFor(base::TimeDelta::FromMilliseconds(200)));
EXPECT_FALSE(future.WaitFor(base::TimeDelta::FromMilliseconds(400)));
EXPECT_TRUE(future.WaitFor(base::TimeDelta::FromMilliseconds(600)));
EXPECT_TRUE(future.Get().val);
}
}
} // namespace vm_tools