blob: c5543d0fbe2db09fababa1f70183012a6499dd0e [file] [log] [blame]
// Copyright 2019 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.test.coding.fuchsia/cpp/wire.h>
#include <fidl/test.basic.protocol/cpp/wire.h>
#include <lib/fidl/cpp/wire/status.h>
#include <lib/fidl/cpp/wire/string_view.h>
#include <lib/fidl/cpp/wire/transaction.h>
#include <lib/sync/completion.h>
#include <limits.h>
#include <zircon/errors.h>
#include <zircon/syscalls.h>
#include <thread>
#include <type_traits>
#include <zxtest/zxtest.h>
#include "src/lib/fidl/llcpp/tests/dispatcher/lsan_disabler.h"
namespace {
class Transaction : public fidl::Transaction {
public:
Transaction() : Transaction(nullptr, nullptr) {}
explicit Transaction(sync_completion_t* wait, sync_completion_t* signal)
: Transaction(wait, signal, std::make_shared<std::optional<fidl::UnbindInfo>>(std::nullopt)) {
}
explicit Transaction(sync_completion_t* wait, sync_completion_t* signal,
const std::shared_ptr<std::optional<fidl::UnbindInfo>>& error)
: wait_(wait), signal_(signal), error_(error) {}
std::unique_ptr<fidl::Transaction> TakeOwnership() override {
return std::make_unique<Transaction>(wait_, signal_, error_);
}
zx_status_t Reply(fidl::OutgoingMessage* message, fidl::WriteOptions write_options) override {
if (wait_ && signal_) {
sync_completion_signal(signal_);
sync_completion_wait(wait_, ZX_TIME_INFINITE);
}
return ZX_OK;
}
void Close(zx_status_t epitaph) override {}
void InternalError(fidl::UnbindInfo error, fidl::ErrorOrigin origin) override {
error_->emplace(error);
fidl::Transaction::InternalError(error, origin);
}
~Transaction() override = default;
const std::optional<fidl::UnbindInfo>& error() const { return *error_; }
private:
sync_completion_t* wait_;
sync_completion_t* signal_;
std::shared_ptr<std::optional<fidl::UnbindInfo>> error_;
};
using OneWayCompleter = fidl::WireServer<::test_basic_protocol::Values>::OneWayCompleter::Sync;
TEST(LlcppTransaction, one_way_completer_reply_not_needed) {
Transaction txn{};
OneWayCompleter completer(&txn);
EXPECT_FALSE(completer.is_reply_needed());
}
using SyncCompleter = fidl::WireServer<::test_basic_protocol::ValueEcho>::EchoCompleter::Sync;
using AsyncCompleter = fidl::WireServer<::test_basic_protocol::ValueEcho>::EchoCompleter::Async;
// A sync completer being destroyed without replying (but needing one) should crash
TEST(LlcppTransaction, no_reply_asserts) {
Transaction txn{};
ASSERT_DEATH([&] { SyncCompleter completer(&txn); }, "no reply should crash");
}
// A completer being destroyed without replying (but not needing one) should not crash
TEST(LlcppTransaction, no_expected_reply_doesnt_assert) {
Transaction txn{};
fidl::Completer<fidl::CompleterBase>::Sync completer(&txn);
}
// An async completer being destroyed without replying (but needing one) should error
TEST(LlcppTransaction, async_no_reply_errors) {
Transaction txn{};
SyncCompleter sync_completer(&txn);
AsyncCompleter async_completer = sync_completer.ToAsync();
EXPECT_EQ(txn.error(), std::nullopt);
{ AsyncCompleter abandoned = std::move(async_completer); }
EXPECT_EQ(txn.error().value().reason(), fidl::Reason::kAbandonedAsyncReply);
}
// A completer replying twice should crash
TEST(LlcppTransaction, double_reply_asserts) {
Transaction txn{};
SyncCompleter completer(&txn);
completer.Reply("");
ASSERT_DEATH([&] { fidl_testing::RunWithLsanDisabled([&] { completer.Reply(""); }); },
"second reply should crash");
}
// It is allowed to reply and then close
TEST(LlcppTransaction, reply_then_close_doesnt_assert) {
Transaction txn{};
SyncCompleter completer(&txn);
EXPECT_TRUE(completer.is_reply_needed());
completer.Reply("");
EXPECT_FALSE(completer.is_reply_needed());
completer.Close(ZX_ERR_INVALID_ARGS);
EXPECT_FALSE(completer.is_reply_needed());
}
// It is not allowed to close then reply
TEST(LlcppTransaction, close_then_reply_asserts) {
Transaction txn{};
SyncCompleter completer(&txn);
EXPECT_TRUE(completer.is_reply_needed());
completer.Close(ZX_ERR_INVALID_ARGS);
EXPECT_FALSE(completer.is_reply_needed());
ASSERT_DEATH([&] { fidl_testing::RunWithLsanDisabled([&] { completer.Reply(""); }); },
"reply after close should crash");
}
// It is not allowed to be accessed from multiple threads simultaneously
TEST(LlcppTransaction, concurrent_access_asserts) {
sync_completion_t signal, wait;
Transaction txn{&signal, &wait};
SyncCompleter completer(&txn);
std::thread t([&] { completer.Reply(""); });
sync_completion_wait(&wait, ZX_TIME_INFINITE);
// TODO(https://fxbug.dev/42132024): Hide assertion failed messages from output - they are confusing.
ASSERT_DEATH([&] { fidl_testing::RunWithLsanDisabled([&] { completer.Reply(""); }); },
"concurrent access should crash");
ASSERT_DEATH([&] { completer.Close(ZX_OK); }, "concurrent access should crash");
ASSERT_DEATH([&] { completer.EnableNextDispatch(); }, "concurrent access should crash");
ASSERT_DEATH([&] { completer.ToAsync(); }, "concurrent access should crash");
sync_completion_signal(&signal);
t.join(); // Don't accidentally invoke ~Completer() while `t` is still in Reply().
}
// If there is a serialization error, it does not need to be closed or replied to.
TEST(LlcppTransaction, transaction_error) {
Transaction txn{};
fidl::WireServer<::fidl_test_coding_fuchsia::Llcpp>::EnumActionCompleter::Sync completer(&txn);
// We are using the fact that 2 isn't a valid enum value to cause an error.
EXPECT_FALSE(txn.error().has_value());
completer.Reply(static_cast<fidl_test_coding_fuchsia::wire::TestEnum>(2));
EXPECT_TRUE(txn.error().has_value());
EXPECT_EQ(fidl::Reason::kEncodeError, txn.error()->reason());
EXPECT_STATUS(ZX_ERR_INVALID_ARGS, txn.error()->status());
}
TEST(CompleterResultOfReply, CalledWithoutMakingAReply) {
Transaction txn{};
SyncCompleter completer(&txn);
ASSERT_DEATH([&] { (void)completer.result_of_reply(); });
// Passivate the completer.
completer.Close(ZX_OK);
}
TEST(CompleterResultOfReply, Ok) {
Transaction txn{};
SyncCompleter completer(&txn);
completer.Reply("");
EXPECT_OK(completer.result_of_reply().status());
}
TEST(CompleterResultOfReply, EncodeError) {
Transaction txn{};
fidl::WireServer<::fidl_test_coding_fuchsia::Llcpp>::EnumActionCompleter::Sync completer(&txn);
// We are using the fact that 2 isn't a valid enum value to cause an error.
EXPECT_FALSE(txn.error().has_value());
completer.Reply(static_cast<fidl_test_coding_fuchsia::wire::TestEnum>(2));
fidl::Status result = completer.result_of_reply();
EXPECT_EQ(fidl::Reason::kEncodeError, result.reason());
EXPECT_STATUS(ZX_ERR_INVALID_ARGS, result.status());
}
TEST(CompleterResultOfReply, TransportError) {
class FakeTransportErrorTransaction : public fidl::Transaction {
std::unique_ptr<Transaction> TakeOwnership() override { ZX_PANIC("Unused"); }
zx_status_t Reply(fidl::OutgoingMessage* message,
fidl::WriteOptions write_options = {}) override {
return ZX_ERR_ACCESS_DENIED;
}
void Close(zx_status_t epitaph) override {}
};
FakeTransportErrorTransaction txn{};
SyncCompleter completer(&txn);
completer.Reply("");
fidl::Status result = completer.result_of_reply();
EXPECT_EQ(fidl::Reason::kTransportError, result.reason());
EXPECT_STATUS(ZX_ERR_ACCESS_DENIED, result.status());
}
namespace test_async_completer_deleted_methods {
template <typename T, typename = void>
struct test : std::false_type {};
template <typename T>
struct test<T, std::void_t<decltype(std::declval<T>().EnableNextDispatch())>> : std::true_type {};
// Invoking `FooCompleter::Async::EnableNextDispatch` should be a compile-time error.
TEST(LlcppCompleter, AsyncCompleterCannotEnableNextDispatch) {
Transaction txn{};
SyncCompleter completer(&txn);
static_assert(test<decltype(completer)>::value);
static_assert(!test<decltype(completer.ToAsync())>::value);
// Not relevant to the test, but required to neutralize the completer.
completer.Close(ZX_OK);
}
} // namespace test_async_completer_deleted_methods
namespace test_sync_completer_deleted_methods {
template <typename T>
T TryToMove(T t) {
return std::move(t);
}
template <typename T, typename = void>
struct test : std::false_type {};
template <typename T>
struct test<T, std::void_t<decltype(TryToMove<T>(std::declval<T>()))>> : std::true_type {};
// Invoking move construction on `FooCompleter::Sync` should be a compile-time error.
TEST(LlcppCompleter, SyncCompleterCannotBeMoved) {
Transaction txn{};
SyncCompleter completer(&txn);
// Sync one cannot be moved.
static_assert(!test<decltype(completer)>::value);
// Async one can be moved.
static_assert(test<decltype(completer.ToAsync())>::value);
// Not relevant to the test, but required to neutralize the completer.
completer.Close(ZX_OK);
}
} // namespace test_sync_completer_deleted_methods
} // namespace