blob: 118ab61adae5df242db11a7c47755e602e062e8d [file] [log] [blame]
// Copyright 2022 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 <fidl/fidl.cpp.constraint.protocol.test/cpp/fidl.h>
#include <fidl/test.basic.protocol/cpp/fidl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/fit/defer.h>
#include <lib/sync/cpp/completion.h>
#include <gtest/gtest.h>
#include "src/lib/testing/predicates/status.h"
namespace {
using ::fidl_cpp_constraint_protocol_test::Constraint;
using ::test_basic_protocol::Values;
// An integration-style test that verifies that user-supplied async callbacks
// attached using |Then| with client lifetime are not invoked when the client is
// destroyed by the user (i.e. explicit cancellation) instead of due to errors.
template <typename ClientType>
void ThenWithClientLifetimeTest() {
auto [local, remote] = fidl::Endpoints<Values>::Create();
async::Loop loop(&kAsyncLoopConfigNeverAttachToThread);
ClientType client(std::move(local), loop.dispatcher());
bool destroyed = false;
client->Echo({"foo"}).Then(
[observer = fit::defer([&] { destroyed = true; })](fidl::Result<Values::Echo>& result) {
FAIL() << "Should not be invoked";
});
// Immediately start cancellation.
client = {};
ASSERT_FALSE(destroyed);
loop.RunUntilIdle();
// The callback should be destroyed without being called.
ASSERT_TRUE(destroyed);
}
TEST(Client, ThenWithClientLifetime) { ThenWithClientLifetimeTest<fidl::Client<Values>>(); }
TEST(SharedClient, ThenWithClientLifetime) {
ThenWithClientLifetimeTest<fidl::SharedClient<Values>>();
}
// An integration-style test that verifies that user-supplied async callbacks
// that takes |fidl::WireUnownedResult| are correctly notified when the binding
// is torn down by the user (i.e. explicit cancellation).
template <typename ClientType>
void ThenExactlyOnceTest() {
auto [local, remote] = fidl::Endpoints<Values>::Create();
async::Loop loop(&kAsyncLoopConfigNeverAttachToThread);
ClientType client(std::move(local), loop.dispatcher());
bool called = false;
bool destroyed = false;
auto callback_destruction_observer = fit::defer([&] { destroyed = true; });
client->Echo({"foo"}).ThenExactlyOnce([observer = std::move(callback_destruction_observer),
&called](fidl::Result<Values::Echo>& result) {
called = true;
EXPECT_FALSE(result.is_ok());
EXPECT_STATUS(ZX_ERR_CANCELED, result.error_value().status());
EXPECT_EQ(fidl::Reason::kUnbind, result.error_value().reason());
});
// Immediately start cancellation.
client = {};
loop.RunUntilIdle();
ASSERT_TRUE(called);
// The callback should be destroyed after being called.
ASSERT_TRUE(destroyed);
}
TEST(Client, ThenExactlyOnce) { ThenExactlyOnceTest<fidl::Client<Values>>(); }
TEST(SharedClient, ThenExactlyOnce) { ThenExactlyOnceTest<fidl::SharedClient<Values>>(); }
TEST(Client, PropagateEncodeError) {
using ::fidl_cpp_constraint_protocol_test::Bits;
auto [local, remote] = fidl::Endpoints<Constraint>::Create();
async::Loop loop(&kAsyncLoopConfigNeverAttachToThread);
fidl::Client client(std::move(local), loop.dispatcher());
static_assert(Bits::TryFrom(0xff) == std::nullopt, "0xff should have invalid bits");
client->Echo({{.bits = Bits(0xff)}}).ThenExactlyOnce([&](fidl::Result<Constraint::Echo>& result) {
// 0xff leads to strict bits constraint validation error.
ASSERT_TRUE(result.is_error());
EXPECT_EQ(result.error_value().reason(), fidl::Reason::kEncodeError);
});
client->Echo({{.bits = Bits::kA}}).ThenExactlyOnce([&](fidl::Result<Constraint::Echo>& result) {
// |Bits::kA| is valid, but this call is canceled due to the previous error.
ASSERT_TRUE(result.is_error());
EXPECT_EQ(result.error_value().reason(), fidl::Reason::kCanceledDueToOtherError);
EXPECT_EQ(result.error_value().underlying_reason().value(), fidl::Reason::kEncodeError);
loop.Quit();
});
loop.Run();
}
TEST(Client, TwoWayRememberErrorAfterUnbinding) {
using ::fidl_cpp_constraint_protocol_test::Bits;
auto [local, remote] = fidl::Endpoints<Constraint>::Create();
async::Loop loop(&kAsyncLoopConfigNeverAttachToThread);
struct EventHandler : public fidl::AsyncEventHandler<Constraint> {
explicit EventHandler(async::Loop& loop) : loop(loop) {}
void on_fidl_error(::fidl::UnbindInfo error) final {
EXPECT_EQ(error.reason(), fidl::Reason::kEncodeError);
loop.Quit();
}
async::Loop& loop;
};
EventHandler event_handler{loop};
fidl::Client client(std::move(local), loop.dispatcher(), &event_handler);
static_assert(Bits::TryFrom(0xff) == std::nullopt, "0xff should have invalid bits");
client->Echo({{.bits = Bits(0xff)}}).ThenExactlyOnce([&](fidl::Result<Constraint::Echo>& result) {
// 0xff leads to strict bits constraint validation error.
ASSERT_TRUE(result.is_error());
EXPECT_EQ(result.error_value().reason(), fidl::Reason::kEncodeError);
});
loop.Run();
loop.ResetQuit();
client->Echo({{.bits = Bits::kA}}).ThenExactlyOnce([&](fidl::Result<Constraint::Echo>& result) {
// |Bits::kA| is valid, but this call is canceled due to the previous error.
ASSERT_TRUE(result.is_error());
EXPECT_EQ(result.error_value().reason(), fidl::Reason::kCanceledDueToOtherError);
EXPECT_EQ(result.error_value().underlying_reason().value(), fidl::Reason::kEncodeError);
loop.Quit();
});
loop.Run();
}
TEST(Client, OneWayRememberErrorAfterUnbinding) {
auto [local, remote] = fidl::Endpoints<Values>::Create();
async::Loop loop(&kAsyncLoopConfigNeverAttachToThread);
struct EventHandler : public fidl::AsyncEventHandler<Values> {
explicit EventHandler(async::Loop& loop) : loop(loop) {}
void on_fidl_error(::fidl::UnbindInfo error) final {
EXPECT_EQ(error.reason(), fidl::Reason::kUnexpectedMessage);
loop.Quit();
}
async::Loop& loop;
};
EventHandler event_handler{loop};
fidl::Client client(std::move(local), loop.dispatcher(), &event_handler);
// Send a malformed message from the server endpoint.
uint8_t byte = 0x0;
ASSERT_OK(remote.channel().write(0, &byte, sizeof(byte), nullptr, 0));
loop.Run();
loop.ResetQuit();
fit::result<fidl::OneWayError> result = client->OneWay({"a"});
// The string is valid, but this call is canceled due to the previous error.
ASSERT_TRUE(result.is_error());
EXPECT_EQ(result.error_value().reason(), fidl::Reason::kCanceledDueToOtherError);
EXPECT_EQ(result.error_value().underlying_reason().value(), fidl::Reason::kUnexpectedMessage);
}
template <typename T>
struct ClientUnbindTest : public ::testing::Test {};
TYPED_TEST_SUITE_P(ClientUnbindTest);
TYPED_TEST_P(ClientUnbindTest, UnbindMaybeGetEndpoint) {
using Client = TypeParam;
auto [local, remote] = fidl::Endpoints<Constraint>::Create();
zx_handle_t handle_number = local.channel().get();
async::Loop loop(&kAsyncLoopConfigNeverAttachToThread);
Client client(std::move(local), loop.dispatcher());
fit::result<fidl::Error, fidl::ClientEnd<Constraint>> result = client.UnbindMaybeGetEndpoint();
ASSERT_TRUE(result.is_ok()) << result.error_value();
ASSERT_EQ(handle_number, result.value().channel().get());
}
TYPED_TEST_P(ClientUnbindTest, UnbindAfterFatalError) {
using Client = TypeParam;
using ::fidl_cpp_constraint_protocol_test::Bits;
auto endpoints = fidl::Endpoints<Constraint>::Create();
async::Loop loop(&kAsyncLoopConfigNeverAttachToThread);
Client client(std::move(endpoints.client), loop.dispatcher());
endpoints.server.Close(ZX_ERR_IO);
loop.RunUntilIdle();
fit::result result = client.UnbindMaybeGetEndpoint();
ASSERT_TRUE(result.is_error());
EXPECT_TRUE(result.error_value().is_peer_closed());
EXPECT_EQ(result.error_value().status(), ZX_ERR_IO);
}
TYPED_TEST_P(ClientUnbindTest, UnbindTwice) {
using Client = TypeParam;
auto [local, remote] = fidl::Endpoints<Constraint>::Create();
async::Loop loop(&kAsyncLoopConfigNeverAttachToThread);
Client client(std::move(local), loop.dispatcher());
{
fit::result result = client.UnbindMaybeGetEndpoint();
ASSERT_TRUE(result.is_ok()) << result.error_value();
}
{
fit::result result = client.UnbindMaybeGetEndpoint();
ASSERT_TRUE(result.is_error());
EXPECT_EQ(result.error_value().reason(), fidl::Reason::kUnbind);
}
}
TYPED_TEST_P(ClientUnbindTest, UnbindWithPendingCallsShouldFail) {
using Client = TypeParam;
auto [local, remote] = fidl::Endpoints<Constraint>::Create();
async::Loop loop(&kAsyncLoopConfigNeverAttachToThread);
Client client(std::move(local), loop.dispatcher());
client->Echo({}).ThenExactlyOnce([](auto&) {});
{
fit::result result = client.UnbindMaybeGetEndpoint();
ASSERT_TRUE(result.is_error());
EXPECT_EQ(result.error_value().reason(), fidl::Reason::kPendingTwoWayCallPreventsUnbind);
}
{
fit::result result = client.UnbindMaybeGetEndpoint();
ASSERT_TRUE(result.is_error());
EXPECT_EQ(result.error_value().reason(), fidl::Reason::kUnbind);
}
}
TYPED_TEST_P(ClientUnbindTest, UnbindAfterACompletedTwoWayCall) {
using Client = TypeParam;
auto [local, remote] = fidl::Endpoints<Constraint>::Create();
async::Loop loop(&kAsyncLoopConfigNeverAttachToThread);
zx_handle_t handle_number = local.channel().get();
Client client(std::move(local), loop.dispatcher());
client->Echo({}).ThenExactlyOnce([&](auto&) { loop.Quit(); });
class Server : public fidl::Server<Constraint> {
public:
void Echo(EchoRequest& request, EchoCompleter::Sync& completer) final {
completer.Reply({request.bits()});
}
} server;
fidl::ServerBinding<Constraint> binding{loop.dispatcher(), std::move(remote), &server,
fidl::kIgnoreBindingClosure};
loop.Run();
fit::result result = client.UnbindMaybeGetEndpoint();
ASSERT_TRUE(result.is_ok()) << result.error_value();
ASSERT_EQ(handle_number, result.value().channel().get());
}
REGISTER_TYPED_TEST_SUITE_P(ClientUnbindTest, UnbindMaybeGetEndpoint, UnbindAfterFatalError,
UnbindTwice, UnbindWithPendingCallsShouldFail,
UnbindAfterACompletedTwoWayCall);
using ClientTypes = testing::Types<fidl::Client<Constraint>, fidl::WireClient<Constraint>>;
INSTANTIATE_TYPED_TEST_SUITE_P(TestPrefix, ClientUnbindTest, ClientTypes);
} // namespace