blob: 09aadd5a2fef7c4cf6df7b77fc63f742c39fb710 [file] [log] [blame]
// Copyright 2024 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/async/cpp/executor.h>
#include <gtest/gtest.h>
#include "src/lib/testing/loop_fixture/test_loop_fixture.h"
#include "src/performance/trace_manager/util.h"
class UtilsTest : public gtest::TestLoopFixture {
public:
UtilsTest() : executor_(dispatcher()) {}
async::Executor executor_;
};
TEST_F(UtilsTest, PromiseTimeout) {
// Check that an expired promise with a valid result returns the valid result instead of a timeout
// error.
auto check_ok = fpromise::make_promise([this](fpromise::context& context) {
auto already_done_fut = fpromise::make_future(tracing::with_timeout(
executor_, fpromise::make_ok_promise(1), zx::duration::infinite_past()));
ASSERT_TRUE(already_done_fut(context));
EXPECT_EQ(already_done_fut.value().value(), 1);
});
executor_.schedule_task(std::move(check_ok));
// Check that an expired promise with an error result returns the error result instead of a
// timeout error.
auto check_err = fpromise::make_promise([this](fpromise::context& context) {
auto already_done_fut = fpromise::make_future(tracing::with_timeout(
executor_, fpromise::make_error_promise(2), zx::duration::infinite_past()));
ASSERT_TRUE(already_done_fut(context));
EXPECT_EQ(already_done_fut.value().error(), 2);
});
executor_.schedule_task(std::move(check_err));
// Check that a pending promise that's not yet expired does indeed return pending.
auto check_pending = fpromise::make_promise([this](fpromise::context& context) {
auto always_pending = fpromise::make_promise([]() { return fpromise::pending(); });
auto pending_fut = fpromise::make_future(
tracing::with_timeout(executor_, std::move(always_pending), zx::sec(5)));
EXPECT_FALSE(pending_fut(context));
});
executor_.schedule_task(std::move(check_pending));
// Check that if the inner promise is still pending and the timer expired, we get an expired
// error.
auto check_expired = fpromise::make_promise([this](fpromise::context& context) {
auto always_pending = fpromise::make_promise([]() { return fpromise::pending(); });
auto pending_fut = fpromise::make_future(
tracing::with_timeout(executor_, std::move(always_pending), zx::sec(-5)));
ASSERT_TRUE(pending_fut(context));
EXPECT_TRUE(pending_fut.is_error());
});
executor_.schedule_task(std::move(check_expired));
}
TEST_F(UtilsTest, IncrementalPromiseProgress) {
// Check that if our promise returns pending and suspends itself it gets rescheduled when the
// inner task finishes.
auto not_yet_done =
executor_.MakeDelayedPromise(zx::sec(3)).and_then(fpromise::make_ok_promise(5));
auto pending_fut =
fpromise::make_future(tracing::with_timeout(executor_, std::move(not_yet_done), zx::sec(5)));
auto check_pending_then_finished = fpromise::make_promise(
[&pending_fut](fpromise::context& context)
-> fpromise::result<fpromise::result<int>, tracing::ResultTimedOut> {
if (!pending_fut(context)) {
return fpromise::pending();
}
return pending_fut.result();
});
executor_.schedule_task(std::move(check_pending_then_finished));
EXPECT_TRUE(RunLoopFor(zx::sec(1)));
ASSERT_TRUE(pending_fut.is_pending());
EXPECT_FALSE(RunLoopFor(zx::sec(1)));
ASSERT_TRUE(pending_fut.is_pending());
ASSERT_TRUE(RunLoopFor(zx::sec(1)));
ASSERT_TRUE(pending_fut.is_ok());
EXPECT_TRUE(pending_fut.value().is_ok());
EXPECT_EQ(pending_fut.value().value(), 5);
// We should see the timeout expire
EXPECT_TRUE(RunLoopFor(zx::sec(3)));
// Check that if our promise returns pending and suspends itself it gets rescheduled when the
// timer errors out.
auto wont_finish_in_time =
executor_.MakeDelayedPromise(zx::sec(5)).and_then(fpromise::make_ok_promise(5));
auto wont_finish_pending_fut = fpromise::make_future(
tracing::with_timeout(executor_, std::move(wont_finish_in_time), zx::sec(3)));
auto check_pending_then_timeout = fpromise::make_promise(
[&wont_finish_pending_fut](fpromise::context& context)
-> fpromise::result<fpromise::result<int>, tracing::ResultTimedOut> {
if (!wont_finish_pending_fut(context)) {
return fpromise::pending();
}
return wont_finish_pending_fut.result();
});
executor_.schedule_task(std::move(check_pending_then_timeout));
EXPECT_TRUE(RunLoopFor(zx::sec(1)));
ASSERT_TRUE(wont_finish_pending_fut.is_pending());
EXPECT_FALSE(RunLoopFor(zx::sec(1)));
ASSERT_TRUE(wont_finish_pending_fut.is_pending());
ASSERT_TRUE(RunLoopFor(zx::sec(1)));
EXPECT_TRUE(wont_finish_pending_fut.is_error());
}
TEST_F(UtilsTest, JoinedPromisesTimeout) {
// Check that a promise that suspends and resumes multiple times we don't get multiple timeouts.
auto delay1 = executor_.MakeDelayedPromise(zx::sec(1)).and_then([]() { return fpromise::ok(1); });
auto delay2 = executor_.MakeDelayedPromise(zx::sec(2)).and_then([]() { return fpromise::ok(2); });
auto delay3 = executor_.MakeDelayedPromise(zx::sec(3)).and_then([]() { return fpromise::ok(3); });
auto all_delays =
fpromise::join_promises(std::move(delay1), std::move(delay2), std::move(delay3));
auto with_timeout = tracing::with_timeout(executor_, std::move(all_delays), zx::sec(5));
auto pending_fut = fpromise::make_future(std::move(with_timeout));
auto check_pending_then_finished = fpromise::make_promise(
[&pending_fut](fpromise::context& context)
-> fpromise::result<
fpromise::result<
std::tuple<fpromise::result<int>, fpromise::result<int>, fpromise::result<int>>>,
tracing::ResultTimedOut> {
if (!pending_fut(context)) {
return fpromise::pending();
}
return pending_fut.result();
});
executor_.schedule_task(std::move(check_pending_then_finished));
EXPECT_TRUE(RunLoopFor(zx::sec(1)));
ASSERT_TRUE(pending_fut.is_pending());
EXPECT_TRUE(RunLoopFor(zx::sec(1)));
ASSERT_TRUE(pending_fut.is_pending());
ASSERT_TRUE(RunLoopFor(zx::sec(1)));
ASSERT_TRUE(pending_fut.is_ok());
EXPECT_TRUE(pending_fut.value().is_ok());
EXPECT_EQ(std::get<0>(pending_fut.value().value()).value(), 1);
EXPECT_EQ(std::get<1>(pending_fut.value().value()).value(), 2);
EXPECT_EQ(std::get<2>(pending_fut.value().value()).value(), 3);
// We should get one additional task from the timeout completing.
EXPECT_TRUE(RunLoopFor(zx::sec(2)));
// But no additional tasks after that
EXPECT_FALSE(RunLoopFor(zx::sec(1000000)));
}