blob: c040e1ad5259b1762c67d7246388b4bcbb465967 [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_allocator/testing.h"
#include "pw_async2/coro.h"
#include "pw_async2/dispatcher_for_test.h"
#include "pw_async2/fallible_coro_task.h"
#include "pw_unit_test/framework.h"
namespace {
using ::pw::allocator::test::AllocatorForTest;
using ::pw::async2::Coro;
using ::pw::async2::CoroContext;
using ::pw::async2::CoroTask;
using ::pw::async2::DispatcherForTest;
using ::pw::async2::FallibleCoroTask;
Coro<int> SimpleCoro(CoroContext, int value) { co_return value; }
class AllocateTaskCoroTest : public ::testing::Test {
protected:
AllocatorForTest<1024> alloc_;
DispatcherForTest dispatcher_;
};
TEST_F(AllocateTaskCoroTest, AllocatesWithRvalueCoro) {
auto task = dispatcher_.Post(alloc_, SimpleCoro(alloc_, 42));
ASSERT_NE(task, nullptr);
dispatcher_.RunToCompletion();
EXPECT_EQ(task->value(), 42);
}
TEST_F(AllocateTaskCoroTest, AllocatesWithCoroFunc) {
auto task = dispatcher_.Post<SimpleCoro>(alloc_, 123);
ASSERT_NE(task, nullptr);
dispatcher_.RunToCompletion();
EXPECT_EQ(task->value(), 123);
}
// Test with move-only argument to ensure forwarding works
struct MoveOnly {
MoveOnly(int v) : val(v) {}
MoveOnly(MoveOnly&&) = default;
MoveOnly(const MoveOnly&) = delete;
int val;
};
Coro<int> MoveOnlyCoro(CoroContext, MoveOnly m) { co_return m.val; }
TEST_F(AllocateTaskCoroTest, ForwardsArguments) {
auto task = dispatcher_.Post<MoveOnlyCoro>(alloc_, MoveOnly(456));
ASSERT_NE(task, nullptr);
dispatcher_.RunToCompletion();
EXPECT_EQ(task->value(), 456);
}
TEST_F(AllocateTaskCoroTest, AllocationFailure) {
alloc_.Exhaust();
EXPECT_EQ(dispatcher_.Post<SimpleCoro>(alloc_, 0), nullptr);
}
TEST_F(AllocateTaskCoroTest, AllocateAsSharedPtr) {
auto task = alloc_.MakeShared<CoroTask<int>>(SimpleCoro(alloc_, 42));
ASSERT_NE(task, nullptr);
dispatcher_.PostShared(task);
dispatcher_.RunToCompletion();
EXPECT_EQ(task->value(), 42);
}
TEST_F(AllocateTaskCoroTest, AllocateFallibleAsSharedPtr) {
auto task = alloc_.MakeShared<FallibleCoroTask<int>>(SimpleCoro(alloc_, 42),
[] { FAIL(); });
ASSERT_NE(task, nullptr);
dispatcher_.PostShared(task);
dispatcher_.RunToCompletion();
EXPECT_EQ(task->value(), 42);
}
TEST_F(AllocateTaskCoroTest, AllocateFallibleCoroTaskAsTask) {
auto task = dispatcher_.Post<FallibleCoroTask<int>>(
alloc_, SimpleCoro(alloc_, 42), [] { FAIL(); });
ASSERT_NE(task, nullptr);
dispatcher_.RunToCompletion();
EXPECT_EQ(task->value(), 42);
}
Coro<int> NestedCoroutineInvocations(CoroContext cx, int value) {
if (value == 0) {
co_return 0;
}
co_return value + co_await NestedCoroutineInvocations(cx, value - 1);
}
TEST_F(AllocateTaskCoroTest, AllocatesFallibleWithCoroRvalueAndErrorHandler) {
struct FailIfCalled {
void operator()() { FAIL(); }
} fail_if_called;
static_assert(
std::is_same_v<
decltype(dispatcher_.Post(
alloc_, NestedCoroutineInvocations(alloc_, 2), fail_if_called)),
pw::SharedPtr<FallibleCoroTask<int, FailIfCalled>>>,
"Error handler should be decayed unless explicitly specified");
static_assert(
std::is_same_v<
decltype(dispatcher_.Post<int, FailIfCalled&>(
alloc_, NestedCoroutineInvocations(alloc_, 3), fail_if_called)),
pw::SharedPtr<FallibleCoroTask<int, FailIfCalled&>>>,
"Error handler should not be decayed when explicitly specified");
auto task = dispatcher_.Post(
alloc_, NestedCoroutineInvocations(alloc_, 3), fail_if_called);
ASSERT_NE(task, nullptr);
dispatcher_.RunToCompletion();
EXPECT_EQ(task->value(), 6);
}
TEST_F(AllocateTaskCoroTest, FallibleCoroTaskNestedCoroutineAllocationFailure) {
bool handler_ran = false;
auto task = dispatcher_.Post(alloc_,
NestedCoroutineInvocations(alloc_, 42),
[&handler_ran] { handler_ran = true; });
ASSERT_NE(task, nullptr);
alloc_.Exhaust();
dispatcher_.RunToCompletion();
EXPECT_TRUE(handler_ran);
EXPECT_FALSE(task->has_value());
}
struct MemberCoroClass {
int value;
Coro<int> MemberCoro(CoroContext, int v) { co_return value + v; }
};
TEST_F(AllocateTaskCoroTest, AllocatesWithMemberCoro) {
MemberCoroClass obj{100};
auto task = dispatcher_.Post<&MemberCoroClass::MemberCoro>(alloc_, obj, 42);
ASSERT_NE(task, nullptr);
dispatcher_.RunToCompletion();
EXPECT_EQ(task->value(), 142);
}
TEST_F(AllocateTaskCoroTest, AllocatesWithMemberCoroPointer) {
MemberCoroClass obj{200};
auto task = dispatcher_.Post<&MemberCoroClass::MemberCoro>(alloc_, &obj, 42);
ASSERT_NE(task, nullptr);
dispatcher_.RunToCompletion();
EXPECT_EQ(task->value(), 242);
}
TEST_F(AllocateTaskCoroTest, AllocatesByCallingMemberCoro) {
MemberCoroClass obj{300};
auto task = dispatcher_.Post(alloc_, obj.MemberCoro(alloc_, 42));
ASSERT_NE(task, nullptr);
dispatcher_.RunToCompletion();
EXPECT_EQ(task->value(), 342);
}
} // namespace