blob: 99d573f75bcd6f5ed70c4f7692ab8390260ec289 [file] [log] [blame]
// Copyright 2017 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-testing/dispatcher_stub.h>
#include <lib/async/cpp/wait.h>
#include <lib/fit/defer.h>
#include <zxtest/zxtest.h>
namespace {
const zx_handle_t dummy_handle = static_cast<zx_handle_t>(1);
const zx_signals_t dummy_trigger = ZX_USER_SIGNAL_0;
const uint32_t dummy_options = 0x55;
const zx_packet_signal_t dummy_signal{.trigger = dummy_trigger,
.observed = ZX_USER_SIGNAL_0 | ZX_USER_SIGNAL_1,
.count = 0u,
.timestamp = 0u,
.reserved1 = 0u};
class MockDispatcher : public async::DispatcherStub {
public:
enum class Op {
NONE,
BEGIN_WAIT,
CANCEL_WAIT,
};
// DispatcherStub
zx_status_t BeginWait(async_wait_t* wait) override {
ZX_ASSERT(wait->object == dummy_handle);
// Using an invalid handle would cause a policy exception.
ZX_ASSERT_MSG(wait->object != last_dummy_handle_deleted,
"BeginWait() called with already-deleted object");
last_op = Op::BEGIN_WAIT;
last_wait = wait;
return next_status;
}
zx_status_t CancelWait(async_wait_t* wait) override {
ZX_ASSERT(wait->object == dummy_handle);
// Using an invalid handle would cause a policy exception.
ZX_ASSERT_MSG(wait->object != last_dummy_handle_deleted,
"CancelWait() called with already-deleted object");
last_op = Op::CANCEL_WAIT;
last_wait = wait;
return next_status;
}
// Test utilities
Op last_op = Op::NONE;
async_wait_t* last_wait = nullptr;
zx_handle_t last_dummy_handle_deleted = ZX_HANDLE_INVALID;
zx_status_t next_status = ZX_OK;
};
class Harness {
public:
explicit Harness(MockDispatcher* mock_dispatcher) : mock_dispatcher_(mock_dispatcher) { Reset(); }
virtual ~Harness() = default;
void Reset() {
handler_ran = false;
handler_deleted = false;
last_wait = nullptr;
last_status = ZX_ERR_INTERNAL;
last_signal = nullptr;
}
void Handler(async_dispatcher_t* dispatcher, async::WaitBase* wait, zx_status_t status,
const zx_packet_signal_t* signal) {
handler_ran = true;
last_wait = wait;
last_status = status;
last_signal = signal;
}
virtual async::WaitBase& wait() = 0;
virtual bool wait_has_handler() = 0;
virtual bool wait_retains_handler() = 0;
virtual zx_status_t BeginWait(async_dispatcher_t* dispatcher) = 0;
// Unaffected by Reset().
MockDispatcher* mock_dispatcher_ = nullptr;
// Reset() during construction, and when Reset() is called separately.
bool handler_ran;
bool handler_deleted;
async::WaitBase* last_wait;
zx_status_t last_status;
const zx_packet_signal_t* last_signal;
};
class LambdaHarness : public Harness {
public:
// The on_handler_destruct simulates the dummy_handle/object being deleted as soon as the
// handler is deleted (which is the soonest it should ever be deleted), and notes that the
// handler has actually been deleted (not just moved somewhere else).
LambdaHarness(MockDispatcher* mock_dispatcher, zx_handle_t object = ZX_HANDLE_INVALID,
zx_signals_t trigger = ZX_SIGNAL_NONE, uint32_t options = 0)
: Harness(mock_dispatcher),
wait_{object, trigger, options,
[this, on_handler_destruct = fit::defer([this] {
mock_dispatcher_->last_dummy_handle_deleted = dummy_handle;
handler_deleted = true;
})](async_dispatcher_t* dispatcher, async::Wait* wait, zx_status_t status,
const zx_packet_signal_t* signal) {
Handler(dispatcher, wait, status, signal);
}} {}
async::WaitBase& wait() override { return wait_; }
bool wait_has_handler() override { return wait_.has_handler(); }
bool wait_retains_handler() override { return true; }
zx_status_t BeginWait(async_dispatcher_t* dispatcher) override { return wait_.Begin(dispatcher); }
private:
async::Wait wait_;
};
class LambdaOnceHarness : public Harness {
public:
LambdaOnceHarness(MockDispatcher* mock_dispatcher, zx_handle_t object = ZX_HANDLE_INVALID,
zx_signals_t trigger = ZX_SIGNAL_NONE, uint32_t options = 0)
: Harness(mock_dispatcher), wait_{object, trigger, options} {}
async::WaitBase& wait() override { return wait_; }
bool wait_has_handler() override { return !handler_ran; }
bool wait_retains_handler() override { return false; }
zx_status_t BeginWait(async_dispatcher_t* dispatcher) override {
// The on_handler_destruct simulates the dummy_handle/object being deleted as soon as the
// handler is deleted (which is the soonest it should ever be deleted), and notes that the
// handler has actually been deleted (not just moved somewhere else).
//
// We only simulate this if there's not already a BeginWait() in progress.
//
// While it's true that if a client calls async::WaitOnce::Begin() on a WaitOnce instance that
// already has a wait in progress, passing in a handler which when deleted will delete the
// same object as the handler for the wait that's already in progress, that's a client bug.
// If a client really needed to attempt a second BeginWait() (it really doesn't need to), then
// it could co-own instead of double-own the object in each of two handlers, where the second
// handler's immediate deletion during failed BeginWait() would just de-ref, not actually
// delete.
auto on_handler_destruct = fit::defer([this, was_fresh_wait = !wait_.is_pending()] {
if (was_fresh_wait) {
mock_dispatcher_->last_dummy_handle_deleted = dummy_handle;
handler_deleted = true;
}
});
return wait_.Begin(dispatcher, [this, on_handler_destruct = std::move(on_handler_destruct)](
async_dispatcher_t* dispatcher, async::WaitOnce* wait,
zx_status_t status, const zx_packet_signal_t* signal) {
Handler(dispatcher, wait, status, signal);
});
}
private:
async::WaitOnce wait_;
};
class MethodHarness : public Harness {
public:
MethodHarness(MockDispatcher* mock_dispatcher, zx_handle_t object = ZX_HANDLE_INVALID,
zx_signals_t trigger = ZX_SIGNAL_NONE, uint32_t options = 0)
: Harness(mock_dispatcher), wait_{this, object, trigger, options} {}
async::WaitBase& wait() override { return wait_; }
bool wait_has_handler() override { return true; }
bool wait_retains_handler() override { return true; }
zx_status_t BeginWait(async_dispatcher_t* dispatcher) override { return wait_.Begin(dispatcher); }
private:
async::WaitMethod<Harness, &Harness::Handler> wait_;
};
TEST(WaitTests, wait_set_handler_test) {
{
async::Wait wait;
EXPECT_FALSE(wait.has_handler());
EXPECT_FALSE(wait.is_pending());
wait.set_handler([](async_dispatcher_t* dispatcher, async::Wait* wait, zx_status_t status,
const zx_packet_signal_t* signal) {});
EXPECT_TRUE(wait.has_handler());
}
{
async::Wait wait(ZX_HANDLE_INVALID, ZX_SIGNAL_NONE, 0,
[](async_dispatcher_t* dispatcher, async::Wait* wait, zx_status_t status,
const zx_packet_signal_t* signal) {});
EXPECT_TRUE(wait.has_handler());
EXPECT_FALSE(wait.is_pending());
}
}
template <typename Harness>
void wait_properties_test() {
MockDispatcher dispatcher;
Harness harness(&dispatcher);
EXPECT_EQ(ZX_HANDLE_INVALID, harness.wait().object());
harness.wait().set_object(dummy_handle);
EXPECT_EQ(dummy_handle, harness.wait().object());
EXPECT_EQ(ZX_SIGNAL_NONE, harness.wait().trigger());
harness.wait().set_trigger(dummy_trigger);
EXPECT_EQ(dummy_trigger, harness.wait().trigger());
EXPECT_EQ(0, harness.wait().options());
harness.wait().set_options(dummy_options);
EXPECT_EQ(dummy_options, harness.wait().options());
}
template <typename Harness>
void wait_begin_test() {
MockDispatcher dispatcher;
{
Harness harness(&dispatcher, dummy_handle, dummy_trigger, dummy_options);
EXPECT_FALSE(harness.wait().is_pending());
dispatcher.next_status = ZX_OK;
EXPECT_EQ(ZX_OK, harness.BeginWait(&dispatcher));
EXPECT_TRUE(harness.wait().is_pending());
EXPECT_EQ(MockDispatcher::Op::BEGIN_WAIT, dispatcher.last_op);
EXPECT_EQ(dummy_handle, dispatcher.last_wait->object);
EXPECT_EQ(dummy_trigger, dispatcher.last_wait->trigger);
EXPECT_EQ(dummy_options, dispatcher.last_wait->options);
EXPECT_FALSE(harness.handler_ran);
harness.Reset();
dispatcher.last_op = MockDispatcher::Op::NONE;
EXPECT_EQ(ZX_ERR_ALREADY_EXISTS, harness.BeginWait(&dispatcher));
EXPECT_EQ(MockDispatcher::Op::NONE, dispatcher.last_op);
EXPECT_FALSE(harness.handler_ran);
}
EXPECT_EQ(MockDispatcher::Op::CANCEL_WAIT, dispatcher.last_op);
// Pretend like we're using a new handle and happen to get dummy_handle value
// again.
dispatcher.last_dummy_handle_deleted = ZX_HANDLE_INVALID;
{
Harness harness(&dispatcher, dummy_handle, dummy_trigger, dummy_options);
EXPECT_FALSE(harness.wait().is_pending());
dispatcher.next_status = ZX_ERR_BAD_STATE;
EXPECT_EQ(ZX_ERR_BAD_STATE, harness.BeginWait(&dispatcher));
EXPECT_EQ(MockDispatcher::Op::BEGIN_WAIT, dispatcher.last_op);
EXPECT_FALSE(harness.wait().is_pending());
EXPECT_FALSE(harness.handler_ran);
}
EXPECT_EQ(MockDispatcher::Op::BEGIN_WAIT, dispatcher.last_op);
}
template <typename Harness>
void wait_cancel_test() {
MockDispatcher dispatcher;
{
Harness harness(&dispatcher, dummy_handle, dummy_trigger, dummy_options);
EXPECT_FALSE(harness.wait().is_pending());
EXPECT_EQ(ZX_ERR_NOT_FOUND, harness.wait().Cancel());
EXPECT_EQ(MockDispatcher::Op::NONE, dispatcher.last_op);
EXPECT_FALSE(harness.wait().is_pending());
EXPECT_EQ(ZX_OK, harness.BeginWait(&dispatcher));
EXPECT_EQ(MockDispatcher::Op::BEGIN_WAIT, dispatcher.last_op);
EXPECT_TRUE(harness.wait().is_pending());
EXPECT_EQ(ZX_OK, harness.wait().Cancel());
EXPECT_EQ(MockDispatcher::Op::CANCEL_WAIT, dispatcher.last_op);
EXPECT_FALSE(harness.wait().is_pending());
dispatcher.last_op = MockDispatcher::Op::NONE;
EXPECT_EQ(ZX_ERR_NOT_FOUND, harness.wait().Cancel());
EXPECT_EQ(MockDispatcher::Op::NONE, dispatcher.last_op);
EXPECT_FALSE(harness.wait().is_pending());
}
EXPECT_EQ(MockDispatcher::Op::NONE, dispatcher.last_op);
}
template <typename Harness>
void wait_run_handler_test() {
MockDispatcher dispatcher;
{
Harness harness(&dispatcher, dummy_handle, dummy_trigger, dummy_options);
EXPECT_FALSE(harness.wait().is_pending());
EXPECT_EQ(ZX_OK, harness.BeginWait(&dispatcher));
EXPECT_EQ(MockDispatcher::Op::BEGIN_WAIT, dispatcher.last_op);
EXPECT_TRUE(harness.wait().is_pending());
harness.Reset();
dispatcher.last_wait->handler(&dispatcher, dispatcher.last_wait, ZX_OK, &dummy_signal);
EXPECT_TRUE(harness.handler_ran);
EXPECT_EQ(&harness.wait(), harness.last_wait);
EXPECT_EQ(ZX_OK, harness.last_status);
EXPECT_EQ(&dummy_signal, harness.last_signal);
EXPECT_FALSE(harness.wait().is_pending());
dispatcher.last_op = MockDispatcher::Op::NONE;
EXPECT_EQ(ZX_ERR_NOT_FOUND, harness.wait().Cancel());
EXPECT_EQ(MockDispatcher::Op::NONE, dispatcher.last_op);
EXPECT_FALSE(harness.wait().is_pending());
EXPECT_EQ(harness.wait_retains_handler(), harness.wait_has_handler());
EXPECT_EQ(!harness.wait_retains_handler(), harness.handler_deleted);
}
EXPECT_EQ(MockDispatcher::Op::NONE, dispatcher.last_op);
}
TEST(WaitTests, unsupported_begin_wait_test) {
async::DispatcherStub dispatcher;
async_wait_t wait{};
EXPECT_EQ(ZX_ERR_NOT_SUPPORTED, async_begin_wait(&dispatcher, &wait), "valid args");
}
TEST(WaitTests, unsupported_cancel_wait_test) {
async::DispatcherStub dispatcher;
async_wait_t wait{};
EXPECT_EQ(ZX_ERR_NOT_SUPPORTED, async_cancel_wait(&dispatcher, &wait), "valid args");
}
} // namespace
TEST(WaitTests, wait_properties_test_LambdaHarness) { wait_properties_test<LambdaHarness>(); }
TEST(WaitTests, wait_properties_test_LambdaOnceHarness) {
wait_properties_test<LambdaOnceHarness>();
}
TEST(WaitTests, wait_properties_test_MethodHarness) { wait_properties_test<MethodHarness>(); }
TEST(WaitTests, wait_begin_test_LambdaHarness) { wait_begin_test<LambdaHarness>(); }
TEST(WaitTests, wait_begin_test_LambdaOnceHarness) { wait_begin_test<LambdaOnceHarness>(); }
TEST(WaitTests, wait_begin_test_MethodHarness) { wait_begin_test<MethodHarness>(); }
TEST(WaitTests, wait_cancel_test_LambdaHarness) { wait_cancel_test<LambdaHarness>(); }
TEST(WaitTests, wait_cancel_test_LambdaOnceHarness) { wait_cancel_test<LambdaOnceHarness>(); }
TEST(WaitTests, wait_cancel_test_MethodHarness) { wait_cancel_test<MethodHarness>(); }
TEST(WaitTests, wait_run_handler_test_LambdaHarness) { wait_run_handler_test<LambdaHarness>(); }
TEST(WaitTests, wait_run_handler_test_LambdaOnceHarness) {
wait_run_handler_test<LambdaOnceHarness>();
}
TEST(WaitTests, wait_run_handler_test_MethodHarness) { wait_run_handler_test<MethodHarness>(); }