blob: f4630b46f2c22c7be43c621b5e0f3a11db96165d [file]
// Copyright 2025 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 <atomic>
#include "pw_async2/await.h"
#include "pw_async2/channel.h"
#include "pw_async2/dispatcher_for_test.h"
#include "pw_async2/internal/channel_test_util.h"
#include "pw_containers/vector.h"
#include "pw_function/function.h"
#include "pw_thread/test_thread_context.h"
#include "pw_thread/thread.h"
#include "pw_unit_test/framework.h"
namespace {
using pw::async2::ChannelStorage;
using pw::async2::CreateSpscChannel;
using pw::async2::Dispatcher;
using pw::async2::DispatcherForTest;
using pw::async2::Receiver;
using pw::async2::Sender;
using IntFunction = pw::InlineFunction<int()>;
using MoveOnlyInt = pw::async2::test::MoveOnlyInt;
using namespace std::chrono_literals;
constexpr pw::Status AsStatus(pw::Status status) { return status; }
template <typename T>
constexpr pw::Status AsStatus(const pw::Result<T>& result) {
return result.status();
}
template <typename T>
struct ChannelAdapter {
using ReceiverTask = pw::async2::test::ReceiverTask<T>;
using ReservedSenderTask = pw::async2::test::ReservedSenderTask<T, int>;
using SenderTask = pw::async2::test::SenderTask<T, int>;
pw::Status BlockingSend(Sender<T>& sender,
Dispatcher& dispatcher,
int value) {
return sender.BlockingSend(dispatcher, T(value));
}
pw::Status BlockingSend(Sender<T>& sender,
Dispatcher& dispatcher,
int value,
pw::chrono::SystemClock::duration timeout) {
return sender.BlockingSend(dispatcher, T(value), timeout);
}
pw::Result<T> BlockingReceive(Receiver<T>& receiver, Dispatcher& dispatcher) {
return receiver.BlockingReceive(dispatcher);
}
pw::Result<T> BlockingReceive(Receiver<T>& receiver,
Dispatcher& dispatcher,
pw::chrono::SystemClock::duration timeout) {
return receiver.BlockingReceive(dispatcher, timeout);
}
void TrySend(Sender<T>& sender, int value) {
PW_TEST_ASSERT_OK(sender.TrySend(T(value)));
}
void ExpectBlockingReceiveResult(const pw::Result<T>& result, int expected) {
ASSERT_TRUE(result.ok());
EXPECT_EQ(*result, expected);
}
pw::Vector<int, 10> GetReceivedBy(
const pw::async2::test::ReceiverTask<T>& receiver_task) {
pw::Vector<int, 10> received;
for (const auto& v : receiver_task.received()) {
received.push_back(v);
}
return received;
}
void ValidateReceived(std::initializer_list<int> expected,
const ReceiverTask& receiver_task) {
auto received = GetReceivedBy(receiver_task);
ASSERT_EQ(received.size(), expected.size());
size_t i = 0;
for (auto value : expected) {
EXPECT_EQ(received[i++], value);
}
}
ChannelStorage<T, 2> storage;
};
template <>
struct ChannelAdapter<IntFunction> {
using ReceiverTask = pw::async2::test::ReceiverTask<IntFunction>;
using ReservedSenderTask =
pw::async2::test::ReservedSenderTask<IntFunction, int>;
using SenderTask = pw::async2::test::SenderTask<IntFunction, int>;
pw::Status BlockingSend(Sender<IntFunction>& sender,
Dispatcher& dispatcher,
int value) {
return sender.BlockingSend(dispatcher, [value]() { return value; });
}
pw::Status BlockingSend(Sender<IntFunction>& sender,
Dispatcher& dispatcher,
int value,
pw::chrono::SystemClock::duration timeout) {
return sender.BlockingSend(
dispatcher, [value]() { return value; }, timeout);
}
pw::Result<IntFunction> BlockingReceive(Receiver<IntFunction>& receiver,
Dispatcher& dispatcher) {
return receiver.BlockingReceive(dispatcher);
}
pw::Result<IntFunction> BlockingReceive(
Receiver<IntFunction>& receiver,
Dispatcher& dispatcher,
pw::chrono::SystemClock::duration timeout) {
return receiver.BlockingReceive(dispatcher, timeout);
}
void TrySend(Sender<IntFunction>& sender, int value) {
PW_TEST_ASSERT_OK(sender.TrySend([value]() { return value; }));
}
void ExpectBlockingReceiveResult(const pw::Result<IntFunction>& result,
int expected) {
ASSERT_TRUE(result.ok());
EXPECT_EQ((*result)(), expected);
}
pw::Vector<int, 10> GetReceivedBy(
const pw::async2::test::ReceiverTask<IntFunction>& receiver_task) {
pw::Vector<int, 10> received;
for (const auto& v : receiver_task.received()) {
received.push_back(v());
}
return received;
}
void ValidateReceived(std::initializer_list<int> expected,
const ReceiverTask& receiver_task) {
auto received = GetReceivedBy(receiver_task);
ASSERT_EQ(received.size(), expected.size());
size_t i = 0;
for (auto value : expected) {
EXPECT_EQ(received[i++], value);
}
}
ChannelStorage<IntFunction, 2> storage;
};
template <>
struct ChannelAdapter<void> {
using ReceiverTask = pw::async2::test::ReceiverTask<void>;
using ReservedSenderTask = pw::async2::test::ReservedSenderTask<void>;
using SenderTask = pw::async2::test::SenderTask<void>;
pw::Status BlockingSend(Sender<void>& sender,
Dispatcher& dispatcher,
int /*value*/) {
return sender.BlockingSend(dispatcher);
}
pw::Status BlockingSend(Sender<void>& sender,
Dispatcher& dispatcher,
int /*value*/,
pw::chrono::SystemClock::duration timeout) {
return sender.BlockingSend(dispatcher, timeout);
}
pw::Status BlockingReceive(Receiver<void>& receiver, Dispatcher& dispatcher) {
return receiver.BlockingReceive(dispatcher);
}
pw::Status BlockingReceive(Receiver<void>& receiver,
Dispatcher& dispatcher,
pw::chrono::SystemClock::duration timeout) {
return receiver.BlockingReceive(dispatcher, timeout);
}
void TrySend(Sender<void>& sender, int /*value*/) {
PW_TEST_ASSERT_OK(sender.TrySend());
}
void ExpectBlockingReceiveResult(pw::Status status, int /*expected*/) {
EXPECT_TRUE(status.ok());
}
void ValidateReceived(std::initializer_list<int> expected,
const ReceiverTask& receiver_task) {
EXPECT_EQ(receiver_task.received(), expected.size());
}
ChannelStorage<void, 2> storage;
};
template <typename T>
class ChannelThreadTest : public ::testing::Test, ChannelAdapter<T> {
using ReceiverTask = typename ChannelAdapter<T>::ReceiverTask;
using ReservedSenderTask = typename ChannelAdapter<T>::ReservedSenderTask;
using SenderTask = typename ChannelAdapter<T>::SenderTask;
using ChannelAdapter<T>::storage;
using ChannelAdapter<T>::BlockingSend;
using ChannelAdapter<T>::BlockingReceive;
using ChannelAdapter<T>::TrySend;
using ChannelAdapter<T>::ExpectBlockingReceiveResult;
using ChannelAdapter<T>::ValidateReceived;
public:
void BlockingSend() {
auto [channel, sender, receiver] = CreateSpscChannel(storage);
channel.Release();
struct {
DispatcherForTest& dispatcher;
Sender<T>& sender;
ChannelAdapter<T>& adapter;
size_t send_count = 0;
} sender_context{dispatcher, sender, *this};
pw::thread::test::TestThreadContext context;
pw::Thread sender_thread(context.options(), [&sender_context]() {
pw::Status last_status;
for (int i = 0; i < 10; ++i) {
last_status = sender_context.adapter.BlockingSend(
sender_context.sender, sender_context.dispatcher, i);
if (last_status.ok()) {
++sender_context.send_count;
} else {
break;
}
}
sender_context.sender.Disconnect();
sender_context.dispatcher.Release();
EXPECT_EQ(last_status, pw::OkStatus());
});
auto receiver_task = ReceiverTask(std::move(receiver));
dispatcher.Post(receiver_task);
dispatcher.RunToCompletionUntilReleased();
sender_thread.join();
EXPECT_EQ(sender_context.send_count, 10u);
ValidateReceived({0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, receiver_task);
}
void BlockingSendChannelCloses() {
auto [channel, sender, receiver] = CreateSpscChannel(storage);
channel.Release();
struct {
DispatcherForTest& dispatcher;
Sender<T>& sender;
ChannelAdapter<T>& adapter;
size_t send_count = 0;
} sender_context{dispatcher, sender, *this};
pw::thread::test::TestThreadContext context;
pw::Thread sender_thread(context.options(), [&sender_context]() {
pw::Status last_status;
for (int i = 0; i < 10; ++i) {
last_status = sender_context.adapter.BlockingSend(
sender_context.sender, sender_context.dispatcher, i);
if (last_status.ok()) {
++sender_context.send_count;
} else {
break;
}
}
EXPECT_EQ(last_status, pw::Status::FailedPrecondition());
EXPECT_FALSE(sender_context.sender.is_open());
sender_context.dispatcher.Release();
});
constexpr size_t kDisconnectAfter = 3;
auto receiver_task = ReceiverTask(std::move(receiver), kDisconnectAfter);
dispatcher.Post(receiver_task);
dispatcher.RunToCompletionUntilReleased();
sender_thread.join();
EXPECT_GE(sender_context.send_count, kDisconnectAfter);
EXPECT_LE(sender_context.send_count, kDisconnectAfter + storage.capacity());
ValidateReceived({0, 1, 2}, receiver_task);
}
void BlockingSendTimeout() {
auto [channel, sender, receiver] = CreateSpscChannel(storage);
channel.Release();
struct {
DispatcherForTest& dispatcher;
Sender<T>& sender;
ChannelAdapter<T>& adapter;
size_t send_count = 0;
} sender_context{dispatcher, sender, *this};
pw::thread::test::TestThreadContext context;
pw::Thread sender_thread(context.options(), [&sender_context]() {
pw::Status last_status;
for (int i = 0; i < 10; ++i) {
last_status = sender_context.adapter.BlockingSend(
sender_context.sender, sender_context.dispatcher, i, 200ms);
if (last_status.ok()) {
++sender_context.send_count;
} else {
break;
}
}
EXPECT_EQ(last_status, pw::Status::DeadlineExceeded());
EXPECT_TRUE(sender_context.sender.is_open());
sender_context.dispatcher.Release();
});
dispatcher.RunToCompletionUntilReleased();
sender_thread.join();
EXPECT_EQ(sender_context.send_count, 2u);
}
void BlockingSendReturnsImmediatelyIfSpaceAvailable() {
auto [channel, sender, receiver] = CreateSpscChannel(storage);
channel.Release();
pw::Status sent = BlockingSend(
sender, dispatcher, 0, pw::chrono::SystemClock::duration(0));
ASSERT_EQ(sent, pw::OkStatus());
sent = BlockingSend(
sender, dispatcher, 1, pw::chrono::SystemClock::duration(0));
ASSERT_EQ(sent, pw::OkStatus());
sent = BlockingSend(
sender, dispatcher, 2, pw::chrono::SystemClock::duration(0));
ASSERT_EQ(sent, pw::Status::DeadlineExceeded());
}
void BlockingReceive() {
auto [channel, sender, receiver] = CreateSpscChannel(storage);
channel.Release();
struct {
DispatcherForTest& dispatcher;
Receiver<T>& receiver;
ChannelAdapter<T>& adapter;
size_t receive_count = 0;
} receiver_context{dispatcher, receiver, *this};
pw::thread::test::TestThreadContext context;
pw::Thread receiver_thread(context.options(), [&receiver_context]() {
pw::Status last_status;
int expected = 0;
while (true) {
auto result = receiver_context.adapter.BlockingReceive(
receiver_context.receiver, receiver_context.dispatcher);
last_status = AsStatus(result);
if (last_status.ok()) {
receiver_context.adapter.ExpectBlockingReceiveResult(result,
expected);
++receiver_context.receive_count;
++expected;
} else {
break;
}
}
EXPECT_EQ(last_status, pw::Status::FailedPrecondition());
EXPECT_FALSE(receiver_context.receiver.is_open());
receiver_context.dispatcher.Release();
});
auto sender_task =
SenderTask(std::move(sender), {0, 1, 2, 3, 4, 5, 6, 7, 8, 9});
dispatcher.Post(sender_task);
dispatcher.RunToCompletionUntilReleased();
receiver_thread.join();
EXPECT_EQ(receiver_context.receive_count, 10u);
}
void BlockingReceiveTimeout() {
auto [channel, sender, receiver] = CreateSpscChannel(storage);
channel.Release();
// Push some values into the channel upfront.
TrySend(sender, 0);
TrySend(sender, 1);
struct {
DispatcherForTest& dispatcher;
Receiver<T>& receiver;
ChannelAdapter<T>& adapter;
size_t receive_count = 0;
} receiver_context{dispatcher, receiver, *this};
pw::thread::test::TestThreadContext context;
pw::Thread receiver_thread(context.options(), [&receiver_context]() {
pw::Status last_status;
for (int i = 0; i < 10; ++i) {
auto result = receiver_context.adapter.BlockingReceive(
receiver_context.receiver, receiver_context.dispatcher, 200ms);
last_status = AsStatus(result);
if (last_status.ok()) {
receiver_context.adapter.ExpectBlockingReceiveResult(result, i);
++receiver_context.receive_count;
} else {
break;
}
}
EXPECT_EQ(last_status, pw::Status::DeadlineExceeded());
EXPECT_TRUE(receiver_context.receiver.is_open());
receiver_context.dispatcher.Release();
});
dispatcher.RunToCompletionUntilReleased();
receiver_thread.join();
EXPECT_EQ(receiver_context.receive_count, 2u);
}
void BlockingReceiveReturnsExistingValueImmediately() {
auto [channel, sender, receiver] = CreateSpscChannel(storage);
channel.Release();
TrySend(sender, 0);
TrySend(sender, 1);
auto result = BlockingReceive(
receiver, dispatcher, pw::chrono::SystemClock::duration(0));
EXPECT_EQ(AsStatus(result), pw::OkStatus());
ExpectBlockingReceiveResult(result, 0);
result = BlockingReceive(
receiver, dispatcher, pw::chrono::SystemClock::duration(0));
EXPECT_EQ(AsStatus(result), pw::OkStatus());
ExpectBlockingReceiveResult(result, 1);
result = BlockingReceive(
receiver, dispatcher, pw::chrono::SystemClock::duration(0));
EXPECT_EQ(AsStatus(result), pw::Status::DeadlineExceeded());
}
void BlockingSendAlreadyClosed() {
auto [channel, sender, receiver] = CreateSpscChannel(storage);
channel.Release();
sender.Disconnect();
struct {
DispatcherForTest& dispatcher;
Sender<T>& sender;
ChannelAdapter<T>& adapter;
} sender_context{dispatcher, sender, *this};
pw::thread::test::TestThreadContext context;
pw::Thread sender_thread(context.options(), [&sender_context]() {
EXPECT_EQ(sender_context.adapter.BlockingSend(
sender_context.sender, sender_context.dispatcher, 1),
pw::Status::FailedPrecondition());
});
dispatcher.AllowBlocking();
dispatcher.RunToCompletion();
sender_thread.join();
}
void BlockingReceiveAlreadyClosed() {
auto [channel, sender, receiver] = CreateSpscChannel(storage);
channel.Release();
receiver.Disconnect();
struct {
DispatcherForTest& dispatcher;
Receiver<T>& receiver;
ChannelAdapter<T>& adapter;
} receiver_context{dispatcher, receiver, *this};
pw::thread::test::TestThreadContext context;
pw::Thread receiver_thread(context.options(), [&receiver_context]() {
auto result = receiver_context.adapter.BlockingReceive(
receiver_context.receiver, receiver_context.dispatcher);
EXPECT_EQ(AsStatus(result), pw::Status::FailedPrecondition());
});
dispatcher.AllowBlocking();
dispatcher.RunToCompletion();
receiver_thread.join();
}
void BlockingReceiveClosedWithData() {
auto [channel, sender, receiver] = CreateSpscChannel(storage);
channel.Release();
TrySend(sender, 1);
sender.Disconnect();
struct {
DispatcherForTest& dispatcher;
Receiver<T>& receiver;
ChannelAdapter<T>& adapter;
} receiver_context{dispatcher, receiver, *this};
pw::thread::test::TestThreadContext context;
pw::Thread receiver_thread(context.options(), [&receiver_context]() {
// Channel is closed, but receiver should still be able to
// read the data
EXPECT_FALSE(receiver_context.receiver.is_open());
auto result = receiver_context.adapter.BlockingReceive(
receiver_context.receiver, receiver_context.dispatcher);
EXPECT_EQ(AsStatus(result), pw::OkStatus());
receiver_context.adapter.ExpectBlockingReceiveResult(result, 1);
// Now the channel is empty and closed.
EXPECT_FALSE(receiver_context.receiver.is_open());
auto result2 = receiver_context.adapter.BlockingReceive(
receiver_context.receiver, receiver_context.dispatcher);
EXPECT_EQ(AsStatus(result2), pw::Status::FailedPrecondition());
});
dispatcher.AllowBlocking();
dispatcher.RunToCompletion();
receiver_thread.join();
}
private:
DispatcherForTest dispatcher;
};
using IntChannelThreadTest = ChannelThreadTest<int>;
using IntFunctionChannelThreadTest = ChannelThreadTest<IntFunction>;
using MoveOnlyIntChannelThreadTest = ChannelThreadTest<MoveOnlyInt>;
using NotificationChannelThreadTest = ChannelThreadTest<void>;
#define TEST_CHANNEL_THREADS(test_name) \
TEST_F(IntChannelThreadTest, test_name) { test_name(); } \
TEST_F(IntFunctionChannelThreadTest, test_name) { test_name(); } \
TEST_F(MoveOnlyIntChannelThreadTest, test_name) { test_name(); } \
TEST_F(NotificationChannelThreadTest, test_name) { test_name(); }
TEST_CHANNEL_THREADS(BlockingSend)
TEST_CHANNEL_THREADS(BlockingSendChannelCloses)
TEST_CHANNEL_THREADS(BlockingSendTimeout)
TEST_CHANNEL_THREADS(BlockingSendReturnsImmediatelyIfSpaceAvailable)
TEST_CHANNEL_THREADS(BlockingReceive)
TEST_CHANNEL_THREADS(BlockingReceiveTimeout)
TEST_CHANNEL_THREADS(BlockingReceiveReturnsExistingValueImmediately)
TEST_CHANNEL_THREADS(BlockingSendAlreadyClosed)
TEST_CHANNEL_THREADS(BlockingReceiveAlreadyClosed)
TEST_CHANNEL_THREADS(BlockingReceiveClosedWithData)
} // namespace