blob: 53713bbafb189938629f0f8a66795561fa8aba07 [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.
#ifndef SRC_MEDIA_AUDIO_LIB_TEST_TEST_FIXTURE_H_
#define SRC_MEDIA_AUDIO_LIB_TEST_TEST_FIXTURE_H_
#include <lib/fidl/cpp/binding.h>
#include <lib/fidl/cpp/interface_ptr.h>
#include <lib/fit/function.h>
#include <zircon/errors.h>
#include <deque>
#include <initializer_list>
#include <optional>
#include <unordered_map>
#include "src/lib/testing/loop_fixture/real_loop_fixture.h"
namespace media::audio::test {
// TestFixture wraps a RealLoopFixture with methods to check for FIDL errors and callbacks.
// For example, to check for disconnection:
//
// SomeInterfacePtr ptr;
// environment->ConnectToService(ptr.NewRequest());
// AddErrorHandler(ptr, "SomeInterface");
//
// ... do something that should disconnect ptr ...
//
// ExpectDisconnect(ptr);
//
// Or, to check that a sequence of callbacks are executed as expected:
//
// SomeInterfacePtr ptr;
// environment->ConnectToService(ptr.NewRequest());
// AddErrorHandler(ptr, "SomeInterface");
//
// int b;
// ptr.events().OnA = AddCallback("A")
// ptr.events().OnB = AddCallback("B", [&b](int x) { b = x; });
//
// // This verifies that callbacks A and B are executed, in that order, that B
// // is called with the correct argument, and that the ErrorHandler is not called.
// ExpectCallback();
// EXPECT_EQ(b, 42);
//
class TestFixture : public ::gtest::RealLoopFixture {
public:
struct ErrorHandler {
std::string name;
zx_status_t error_code = ZX_OK; // set after the ErrorHandler is triggered
zx_status_t expected_error_code = ZX_OK; // expected error for ExpectErrors
};
// Add a new ErrorHandler for the given protocol. If this ErrorHandler triggers unexpectedly,
// the given name will be included in the test failure message. The InterfacePtr must
// live for the duration of this TestFixture.
template <class T>
void AddErrorHandler(fidl::InterfacePtr<T>& ptr, std::string name) {
auto [h, cb] = NewErrorHandler(name);
ptr.set_error_handler(std::move(cb));
error_handlers_[ptr.channel().get()] = h;
}
template <class T>
void AddErrorHandler(fidl::Binding<T>& binding, std::string name) {
auto [h, cb] = NewErrorHandler(name);
binding.set_error_handler(std::move(cb));
error_handlers_[binding.channel().get()] = h;
}
// Retrieves a previously-added error handler.
// Useful for direct calls to ExpectErrors or ExpectDisconnects. Tests that
// use ExpectError or ExpectDisconnect won't need this.
template <class T>
std::shared_ptr<ErrorHandler> ErrorHandlerFor(fidl::InterfacePtr<T>& ptr) {
auto eh = error_handlers_[ptr.channel().get()];
FX_CHECK(eh);
return eh;
}
template <class T>
std::shared_ptr<ErrorHandler> ErrorHandlerFor(fidl::Binding<T>& binding) {
auto eh = error_handlers_[binding.channel().get()];
FX_CHECK(eh);
return eh;
}
// Add an expected callback to the pending set.
// Callbacks are expected to occur in the order in which they are added.
// Optionally, providew a custom function to invoke when the expected callback is triggered.
auto AddCallback(const std::string& name);
template <typename Callable>
auto AddCallback(const std::string& name, Callable callback);
// Like AddCallback, but allow the callback to happen in any order.
auto AddCallbackUnordered(const std::string& name);
template <typename Callable>
auto AddCallbackUnordered(const std::string& name, Callable callback);
// Add an unexpected callback. The test will fail if this callback is triggered.
auto AddUnexpectedCallback(const std::string& name) {
return [name](auto&&...) { ADD_FAILURE() << "Got unexpected callback " << name; };
}
// Wait until all pending callbacks are drained. Fails if an error is encountered.
// Callbacks are expected to occur in the order they are added. After this method
// returns, the pending callback set is emptied and new callbacks may be added for
// a future call to ExpectCallback.
void ExpectCallback();
// Wait for the given ErrorHandlers to trigger with their expected errors. Fails if
// different errors are found or if errors are triggered in different ErrorHandlers.
void ExpectErrors(const std::vector<std::shared_ptr<ErrorHandler>>& errors);
// Shorthand to expect many disconnect errors.
void ExpectDisconnects(const std::vector<std::shared_ptr<ErrorHandler>>& errors) {
std::vector<std::shared_ptr<ErrorHandler>> handlers;
for (auto eh : errors) {
eh->expected_error_code = ZX_ERR_PEER_CLOSED;
}
ExpectErrors(errors);
}
// Shorthand to expect a single error.
template <class T>
void ExpectError(fidl::InterfacePtr<T>& ptr, zx_status_t expected_error) {
auto eh = ErrorHandlerFor(ptr);
eh->expected_error_code = expected_error;
ExpectErrors({eh});
}
template <class T>
void ExpectDisconnect(fidl::InterfacePtr<T>& ptr) {
ExpectError(ptr, ZX_ERR_PEER_CLOSED);
}
// Verifies that no unexpected errors have occurred so far.
void ExpectNoUnexpectedErrors(const std::string& msg_for_failure);
// Reports whether any ErrorHandlers have triggered.
bool ErrorOccurred();
// Promote to public so that non-subclasses can advance through time.
using ::gtest::RealLoopFixture::RunLoop;
using ::gtest::RealLoopFixture::RunLoopUntil;
using ::gtest::RealLoopFixture::RunLoopUntilIdle;
using ::gtest::RealLoopFixture::RunLoopWithTimeout;
using ::gtest::RealLoopFixture::RunLoopWithTimeoutOrUntil;
protected:
void TearDown() override;
private:
struct PendingCallback {
std::string name;
int64_t seqno = 0;
bool ordered;
};
auto AddCallbackInternal(const std::string& name, bool ordered) {
auto pb = NewPendingCallback(name, ordered);
return [this, pb](auto&&...) { pb->seqno = next_seqno_++; };
}
template <typename Callable>
auto AddCallbackInternal(const std::string& name, Callable callback, bool ordered) {
auto pb = NewPendingCallback(name, ordered);
return [this, pb, callback = std::move(callback)](auto&&... args) {
pb->seqno = next_seqno_++;
callback(std::forward<decltype(args)>(args)...);
};
}
std::pair<std::shared_ptr<ErrorHandler>, fit::function<void(zx_status_t)>> NewErrorHandler(
const std::string& name);
std::shared_ptr<PendingCallback> NewPendingCallback(const std::string& name, bool ordered);
void ExpectErrorsInternal(const std::vector<std::shared_ptr<ErrorHandler>>& errors);
std::unordered_map<zx_handle_t, std::shared_ptr<ErrorHandler>> error_handlers_;
std::deque<std::shared_ptr<PendingCallback>> pending_callbacks_;
int64_t next_seqno_ = 1;
bool new_error_ = false;
};
// These must be defined in the header file, because of the auto return type, but
// also must be defined after AddCallbackInternal.
inline auto TestFixture::AddCallback(const std::string& name) {
return AddCallbackInternal(name, true);
}
template <typename Callable>
inline auto TestFixture::AddCallback(const std::string& name, Callable callback) {
return AddCallbackInternal(name, callback, true);
}
inline auto TestFixture::AddCallbackUnordered(const std::string& name) {
return AddCallbackInternal(name, false);
}
template <typename Callable>
inline auto TestFixture::AddCallbackUnordered(const std::string& name, Callable callback) {
return AddCallbackInternal(name, callback, false);
}
} // namespace media::audio::test
#endif // SRC_MEDIA_AUDIO_LIB_TEST_TEST_FIXTURE_H_