blob: 626bb7a383644dd397402db55ed404c55e801cd2 [file]
// Copyright 2026 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
#include "pw_async2/box.h"
#include "pw_allocator/testing.h"
#include "pw_async2/channel.h"
#include "pw_async2/dispatcher_for_test.h"
#include "pw_async2/func_task.h"
#include "pw_async2/future.h"
#include "pw_async2/select.h"
#include "pw_async2/transform.h"
#include "pw_async2/try.h"
#include "pw_async2/value_future.h"
#include "pw_unit_test/framework.h"
namespace {
using pw::async2::BoxedFuture;
using pw::async2::ChannelStorage;
using pw::async2::Context;
using pw::async2::CreateSpscChannel;
using pw::async2::DispatcherForTest;
using pw::async2::Pending;
using pw::async2::Poll;
using pw::async2::Ready;
using pw::async2::ValueProvider;
using pw::async2::experimental::Map;
using pw::async2::experimental::Then;
TEST(BoxedFuture, TypeErasesIntFuture) {
pw::allocator::test::AllocatorForTest<256> alloc;
ValueProvider<int> provider;
BoxedFuture<int> boxed = BoxFuture(alloc, provider.Get());
ASSERT_TRUE(boxed.is_pendable());
static_assert(pw::async2::Future<decltype(boxed)>);
EXPECT_TRUE(boxed.is_pendable());
EXPECT_FALSE(boxed.is_complete());
pw::async2::DispatcherForTest dispatcher;
int result = 0;
pw::async2::FuncTask task([&](Context& cx) -> Poll<> {
PW_TRY_READY_ASSIGN(result, boxed.Pend(cx));
return Ready();
});
dispatcher.Post(task);
EXPECT_TRUE(dispatcher.RunUntilStalled());
EXPECT_TRUE(boxed.is_pendable());
EXPECT_FALSE(boxed.is_complete());
EXPECT_EQ(result, 0);
provider.Resolve(42);
EXPECT_FALSE(dispatcher.RunUntilStalled());
EXPECT_FALSE(boxed.is_pendable());
EXPECT_TRUE(boxed.is_complete());
EXPECT_EQ(result, 42);
EXPECT_GT(alloc.deallocate_size(), 0u);
}
TEST(BoxedFuture, TypeErasesVoidFuture) {
pw::allocator::test::AllocatorForTest<256> alloc;
ValueProvider<void> provider;
BoxedFuture<void> boxed = BoxFuture(alloc, provider.Get());
ASSERT_TRUE(boxed.is_pendable());
static_assert(pw::async2::Future<decltype(boxed)>);
EXPECT_TRUE(boxed.is_pendable());
EXPECT_FALSE(boxed.is_complete());
pw::async2::DispatcherForTest dispatcher;
pw::async2::FuncTask task([&](Context& cx) -> Poll<> {
Poll<> p = boxed.Pend(cx);
if (p.IsPending()) {
return Pending();
}
return Ready();
});
dispatcher.Post(task);
EXPECT_TRUE(dispatcher.RunUntilStalled());
EXPECT_TRUE(boxed.is_pendable());
EXPECT_FALSE(boxed.is_complete());
provider.Resolve();
dispatcher.AllowBlocking();
dispatcher.RunToCompletion();
EXPECT_FALSE(boxed.is_pendable());
EXPECT_TRUE(boxed.is_complete());
EXPECT_GT(alloc.deallocate_size(), 0u);
}
TEST(BoxedFuture, TypeErasesCombinator) {
pw::allocator::test::AllocatorForTest<256> alloc;
ValueProvider<int> provider_int;
ValueProvider<char> provider_char;
BoxedFuture<pw::OptionalTuple<int, char>> boxed =
BoxFuture(alloc, Select(provider_int.Get(), provider_char.Get()));
ASSERT_TRUE(boxed.is_pendable());
static_assert(pw::async2::Future<decltype(boxed)>);
pw::async2::DispatcherForTest dispatcher;
pw::OptionalTuple<int, char> result;
pw::async2::FuncTask task([&](Context& cx) -> Poll<> {
PW_TRY_READY_ASSIGN(result, boxed.Pend(cx));
return Ready();
});
dispatcher.Post(task);
EXPECT_TRUE(dispatcher.RunUntilStalled());
EXPECT_TRUE(boxed.is_pendable());
EXPECT_FALSE(boxed.is_complete());
provider_int.Resolve(42);
EXPECT_FALSE(dispatcher.RunUntilStalled());
EXPECT_FALSE(boxed.is_pendable());
EXPECT_TRUE(boxed.is_complete());
EXPECT_TRUE(result.has_value<0>());
EXPECT_EQ(result.value<0>(), 42);
EXPECT_GT(alloc.deallocate_size(), 0u);
}
TEST(BoxedFuture, TypeErasesFutureChain) {
pw::allocator::test::AllocatorForTest<256> alloc;
DispatcherForTest dispatcher;
ValueProvider<int> provider;
ChannelStorage<int, 1> storage;
auto [handle, sender, receiver] = CreateSpscChannel(storage);
// clang-format off
BoxedFuture<bool> future = BoxFuture(
alloc,
provider.Get()
| Map([](int x) { return x * 4; })
| Then([s = std::move(sender)](int x) mutable { return s.Send(x); }));
// clang-format on
EXPECT_TRUE(future.is_pendable());
EXPECT_FALSE(future.is_complete());
provider.Resolve(5);
auto result = dispatcher.RunInTaskUntilStalled(future);
EXPECT_EQ(result, Ready(true));
pw::Result<int> received = receiver.TryReceive();
EXPECT_TRUE(received.ok());
EXPECT_EQ(*received, 20);
EXPECT_FALSE(future.is_pendable());
EXPECT_TRUE(future.is_complete());
}
TEST(BoxedFuture, AllocationFailure) {
pw::allocator::test::AllocatorForTest<256> alloc;
alloc.Exhaust();
ValueProvider<int> provider;
BoxedFuture<int> boxed = BoxFuture(alloc, provider.Get());
EXPECT_FALSE(boxed.is_pendable());
EXPECT_FALSE(boxed.is_complete());
}
TEST(BoxedFuture, DestructorDeallocates) {
pw::allocator::test::AllocatorForTest<256> alloc;
ValueProvider<int> provider;
EXPECT_EQ(alloc.deallocate_size(), 0u);
{
BoxedFuture<int> boxed = BoxFuture(alloc, provider.Get());
EXPECT_TRUE(boxed.is_pendable());
}
EXPECT_GT(alloc.deallocate_size(), 0u);
}
TEST(BoxedFuture, MoveAssignmentDeallocatesOriginal) {
pw::allocator::test::AllocatorForTest<256> alloc;
ValueProvider<int> provider_1;
ValueProvider<int> provider_2;
BoxedFuture<int> boxed_1 = BoxFuture(alloc, provider_1.Get());
BoxedFuture<int> boxed_2 = BoxFuture(alloc, provider_2.Get());
EXPECT_TRUE(boxed_1.is_pendable());
EXPECT_TRUE(boxed_2.is_pendable());
EXPECT_EQ(alloc.deallocate_size(), 0u);
boxed_1 = std::move(boxed_2);
EXPECT_GT(alloc.deallocate_size(), 0u);
// NOLINTNEXTLINE(bugprone-use-after-move)
EXPECT_FALSE(boxed_2.is_pendable());
EXPECT_TRUE(boxed_1.is_pendable());
EXPECT_FALSE(boxed_1.is_complete());
pw::async2::DispatcherForTest dispatcher;
int result = 0;
pw::async2::FuncTask task([&](Context& cx) -> Poll<> {
PW_TRY_READY_ASSIGN(result, boxed_1.Pend(cx));
return Ready();
});
dispatcher.Post(task);
EXPECT_TRUE(dispatcher.RunUntilStalled());
EXPECT_TRUE(boxed_1.is_pendable());
provider_2.Resolve(1234);
EXPECT_FALSE(dispatcher.RunUntilStalled());
EXPECT_FALSE(boxed_1.is_pendable());
EXPECT_TRUE(boxed_1.is_complete());
EXPECT_EQ(result, 1234);
}
} // namespace