AimRT/_deps/libunifex-src/test/async_manual_reset_event_test.cpp
2025-01-12 20:42:42 +08:00

253 lines
6.5 KiB
C++

/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* Licensed under the Apache License Version 2.0 with LLVM Exceptions
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* https://llvm.org/LICENSE.txt
*
* 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 <unifex/async_manual_reset_event.hpp>
#include <unifex/inline_scheduler.hpp>
#include <unifex/inplace_stop_token.hpp>
#include <unifex/sender_concepts.hpp>
#include <unifex/single_thread_context.hpp>
#include <unifex/sync_wait.hpp>
#include <unifex/then.hpp>
#include <unifex/with_query_value.hpp>
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <exception>
#include <memory>
#include <stdexcept>
#include <thread>
using testing::Invoke;
using testing::_;
using unifex::async_manual_reset_event;
using unifex::connect;
using unifex::get_scheduler;
using unifex::get_stop_token;
using unifex::inline_scheduler;
using unifex::inplace_stop_source;
using unifex::inplace_stop_token;
using unifex::schedule;
using unifex::single_thread_context;
using unifex::start;
using unifex::sync_wait;
using unifex::tag_t;
using unifex::then;
using unifex::with_query_value;
namespace {
struct mock_receiver_impl {
MOCK_METHOD(void, set_value, (), ());
MOCK_METHOD(void, set_error, (std::exception_ptr), (noexcept));
};
// mock_receiver_impl cannot be used directly as a receiver because the MOCK
// macros make the type non-movable, non-copyable. Receivers must be movable.
struct mock_receiver {
mock_receiver(inline_scheduler& scheduler)
: impl(std::make_unique<mock_receiver_impl>()), scheduler(&scheduler) {}
void set_value() {
impl->set_value();
}
void set_error(std::exception_ptr e) noexcept {
impl->set_error(e);
}
void set_done() noexcept {
std::terminate();
}
std::unique_ptr<mock_receiver_impl> impl;
inline_scheduler* scheduler;
friend inline_scheduler tag_invoke(
tag_t<get_scheduler>, const mock_receiver& self) noexcept {
return *self.scheduler;
}
};
} // namespace
struct async_manual_reset_event_test : testing::Test {
inline_scheduler scheduler;
mock_receiver receiver{scheduler};
mock_receiver_impl& receiverImpl = *receiver.impl;
};
TEST_F(async_manual_reset_event_test, default_constructor_leaves_baton_unready) {
async_manual_reset_event evt;
EXPECT_FALSE(evt.ready());
}
TEST_F(async_manual_reset_event_test, can_construct_initially_ready_baton) {
async_manual_reset_event evt{true};
EXPECT_TRUE(evt.ready());
}
TEST_F(async_manual_reset_event_test, set_makes_unready_baton_ready) {
async_manual_reset_event evt;
evt.set();
EXPECT_TRUE(evt.ready());
}
TEST_F(async_manual_reset_event_test, sender_completes_after_set_when_connected_to_unready_baton) {
async_manual_reset_event evt;
auto op = connect(evt.async_wait(), std::move(receiver));
{
EXPECT_CALL(receiverImpl, set_value()).Times(0);
EXPECT_CALL(receiverImpl, set_error(_)).Times(0);
start(op);
}
EXPECT_CALL(receiverImpl, set_value()).Times(1);
EXPECT_CALL(receiverImpl, set_error(_)).Times(0);
evt.set();
}
TEST_F(async_manual_reset_event_test, sender_completes_inline_when_connected_to_ready_baton) {
async_manual_reset_event evt{true};
auto op = connect(evt.async_wait(), std::move(receiver));
EXPECT_CALL(receiverImpl, set_value()).Times(1);
EXPECT_CALL(receiverImpl, set_error(_)).Times(0);
start(op);
}
TEST_F(async_manual_reset_event_test, exception_from_set_value_sent_to_set_error) {
async_manual_reset_event evt{true};
auto op = connect(evt.async_wait(), std::move(receiver));
EXPECT_CALL(receiverImpl, set_value())
.WillOnce(Invoke([]() -> void {
throw std::runtime_error("from set_value()");
}));
EXPECT_CALL(receiverImpl, set_error(_))
.WillOnce(Invoke([](std::exception_ptr eptr) noexcept {
try {
std::rethrow_exception(eptr);
} catch (const std::runtime_error& e) {
EXPECT_STREQ(e.what(), "from set_value()");
}
}));
start(op);
}
template <typename Scheduler>
static std::thread::id getThreadId(Scheduler& scheduler) {
return sync_wait(then(schedule(scheduler), [] {
return std::this_thread::get_id();
})).value();
}
TEST_F(
async_manual_reset_event_test,
set_value_reschedules_when_invoked_from_async_wait) {
single_thread_context thread;
auto scheduler = thread.get_scheduler();
const auto expectedThreadId = getThreadId(scheduler);
ASSERT_NE(expectedThreadId, std::this_thread::get_id());
async_manual_reset_event evt{true};
auto actualThreadId = sync_wait(then(
with_query_value(evt.async_wait(), get_scheduler, scheduler),
[] { return std::this_thread::get_id(); })).value();
EXPECT_EQ(expectedThreadId, actualThreadId);
}
TEST_F(
async_manual_reset_event_test,
set_value_reschedules_when_invoked_from_set) {
single_thread_context thread;
auto scheduler = thread.get_scheduler();
const auto expectedThreadId = getThreadId(scheduler);
ASSERT_NE(expectedThreadId, std::this_thread::get_id());
async_manual_reset_event evt1, evt2;
auto op = connect(
with_query_value(evt1.async_wait(), get_scheduler, scheduler),
std::move(receiver));
start(op);
std::thread::id actualThreadId{};
EXPECT_CALL(receiverImpl, set_value())
.WillOnce(Invoke([&actualThreadId, &evt2] {
actualThreadId = std::this_thread::get_id();
evt2.set();
}));
evt1.set();
sync_wait(evt2.async_wait());
EXPECT_EQ(expectedThreadId, actualThreadId);
}
TEST_F(
async_manual_reset_event_test,
set_value_ignores_the_receivers_stop_token_when_rescheduling) {
inplace_stop_source stopSource;
stopSource.request_stop();
single_thread_context thread;
auto scheduler = thread.get_scheduler();
const auto expectedThreadId = getThreadId(scheduler);
ASSERT_NE(expectedThreadId, std::this_thread::get_id());
async_manual_reset_event evt{true};
auto actualThreadId = sync_wait(then(
with_query_value(
with_query_value(evt.async_wait(), get_scheduler, scheduler),
get_stop_token,
stopSource.get_token()),
[] { return std::this_thread::get_id(); }));
ASSERT_TRUE(actualThreadId);
EXPECT_EQ(expectedThreadId, *actualThreadId);
}