blob: 11a09597cec3aefe1e8ef59fd7b179f3f22133e6 [file] [edit]
/*
* SPDX-FileCopyrightText: Copyright OpenBMC Authors
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "MctpRequester.hpp"
#include <boost/asio/io_context.hpp>
#include <boost/system/error_code.hpp>
#include <sdbusplus/asio/connection.hpp>
#include <sdbusplus/asio/object_server.hpp>
#include <sdbusplus/asio/property.hpp>
#include <sdbusplus/exception.hpp>
#include <cerrno>
#include <chrono>
#include <functional>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <gtest/gtest.h>
/**
* Owns the private dbus-daemon and the shared bus objects for the whole
* binary. Bring-up failure is not fatal: DbusMockTestBase fixtures skip
* themselves, so pure-unit tests in the same binary still run.
*/
class DbusEnvironment : public ::testing::Environment
{
public:
void SetUp() override;
void TearDown() override;
};
// Base fixture for tests that need the shared D-Bus session.
class DbusMockTestBase : public ::testing::Test
{
protected:
// Valid only while hasBus().
static std::shared_ptr<sdbusplus::asio::connection>& bus();
static sdbusplus::asio::object_server& objects();
static mctp::MctpRequester& requester();
static boost::asio::io_context& ioContext();
static bool hasBus();
// GTEST_SKIP() only returns from this function; derived overrides must
// check testing::Test::IsSkipped() after delegating here.
void SetUp() override;
// Drains pending D-Bus work so the next test starts clean.
void TearDown() override;
// Run already-queued handlers so destructor-cancelled async ops finish.
static void drainPendingAsync();
/**
* Read a property from this connection's own object server. Async +
* io_context pump because sd-bus rejects self-directed synchronous calls
* with ELOOP. Throws SdBusError on error or timeout.
*/
template <typename PropertyType>
static PropertyType getProperty(const std::string& path,
const std::string& iface,
const std::string& prop)
{
// Heap-owned handler state: a late reply may fire in a later pump,
// after this frame has thrown ETIMEDOUT and unwound.
struct State
{
std::optional<boost::system::error_code> ec;
PropertyType value{};
};
auto state = std::make_shared<State>();
sdbusplus::asio::getProperty<PropertyType>(
*bus(), bus()->get_unique_name(), path, iface, prop,
[state](boost::system::error_code ec, PropertyType value) {
state->value = std::move(value);
state->ec = ec;
});
if (!pumpIoUntil([state] { return state->ec.has_value(); },
propertyReadTimeout))
{
throw sdbusplus::exception::SdBusError(ETIMEDOUT,
"getProperty timed out");
}
const boost::system::error_code callError =
state->ec.value_or(boost::system::error_code{});
if (callError)
{
throw sdbusplus::exception::SdBusError(
callError.value(),
("getProperty: " + callError.message()).c_str());
}
return std::move(state->value);
}
// Pump the shared io_context until done() holds; false on timeout. Needed
// by tests that wait on a timer inside the object under test.
static bool pumpIoUntil(const std::function<bool()>& done,
std::chrono::seconds timeout);
private:
friend class DbusEnvironment;
static constexpr std::chrono::seconds propertyReadTimeout{5};
static bool tryConnect();
// Fresh instance per environment SetUp, never deleted: a late
// ~connection() must not touch a destroyed io_uring service, and a
// recreated environment must never run the previous lifetime's queued
// raw-`this` handlers.
static boost::asio::io_context* io;
static std::shared_ptr<sdbusplus::asio::connection> conn;
static std::unique_ptr<sdbusplus::asio::object_server> objectServer;
static std::unique_ptr<mctp::MctpRequester> mctpRequester;
static bool available;
static std::string bringUpFailure;
};