blob: 86a2a6917b0b521fbb9632a2a0d7cde1b1c5fc4c [file] [log] [blame]
// Copyright 2024 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 "test_util.h"
#include <fidl/fuchsia.hardware.power.suspend/cpp/fidl.h>
#include <fidl/fuchsia.power.system/cpp/fidl.h>
#include <fidl/test.suspendcontrol/cpp/fidl.h>
#include <lib/component/incoming/cpp/protocol.h>
#include <lib/fdio/directory.h>
#include <gtest/gtest.h>
namespace system_integration_utils {
namespace {
namespace fh_suspend = fuchsia_hardware_power_suspend;
template <typename T>
std::string ToString(const T& value) {
std::ostringstream buf;
buf << value;
return buf.str();
}
template <typename T>
std::string FidlString(const T& value) {
return ToString(fidl::ostream::Formatted<T>(value));
}
} // namespace
void Connector::Receive(ReceiveRequest& request, ReceiveCompleter::Sync& completer) {
zx_status_t status = fdio_service_connect(path_.c_str(), request.channel().release());
if (status != ZX_OK) {
completer.Close(status);
}
}
void TestLoopBase::Initialize() {
{
auto result = component::Connect<test_sagcontrol::State>();
ASSERT_EQ(ZX_OK, result.status_value());
sag_control_state_client_end_ = std::move(result.value());
}
{
auto result = component::Connect<test_suspendcontrol::Device>();
ASSERT_EQ(ZX_OK, result.status_value());
suspend_device_client_end_ = std::move(result.value());
}
{
auto result = component::Connect<fuchsia_driver_development::Manager>();
ASSERT_EQ(ZX_OK, result.status_value());
driver_manager_client_end_ = std::move(result.value());
}
{
fh_suspend::SuspendState state;
state.resume_latency(zx::usec(100).to_nsecs());
test_suspendcontrol::DeviceSetSuspendStatesRequest request;
std::vector<fh_suspend::SuspendState> states = {state};
request.suspend_states(states);
auto result = fidl::Call(suspend_device_client_end_)->SetSuspendStates(request);
ASSERT_TRUE(result.is_ok());
}
}
test_sagcontrol::SystemActivityGovernorState TestLoopBase::GetBootCompleteState() {
test_sagcontrol::SystemActivityGovernorState state;
state.execution_state_level(fuchsia_power_system::ExecutionStateLevel::kActive);
state.application_activity_level(fuchsia_power_system::ApplicationActivityLevel::kActive);
return state;
}
zx_status_t TestLoopBase::AwaitSystemSuspend() {
std::cout << "Awaiting suspend" << std::endl;
auto wait_result = fidl::Call(suspend_device_client_end_)->AwaitSuspend();
if (!wait_result.is_ok()) {
std::cout << "Failed to await suspend: " << wait_result.error_value() << std::endl;
return ZX_ERR_INTERNAL;
}
std::cout << "Suspend confirmed" << std::endl;
return ZX_OK;
}
bool TestLoopBase::SetBootComplete() {
{
auto status = fidl::WireCall(sag_control_state_client_end_)->SetBootComplete();
if (!status.ok()) {
std::cout << "Failed to SetBootComplete" << std::endl;
return false;
}
}
return true;
}
zx_status_t TestLoopBase::StartSystemResume() {
std::cout << "Starting system resume" << std::endl;
test_suspendcontrol::SuspendResult suspend_result;
// Assign suspend results to provide the appearance of a normal return from suspension.
suspend_result.suspend_duration(2);
suspend_result.suspend_overhead(1);
auto request = test_suspendcontrol::DeviceResumeRequest::WithResult(suspend_result);
auto resume_result = fidl::Call(suspend_device_client_end_)->Resume(request);
if (!resume_result.is_ok()) {
std::cout << "Failed to await suspend: " << resume_result.error_value() << std::endl;
return ZX_ERR_INTERNAL;
}
std::cout << "Resume started" << std::endl;
return ZX_OK;
}
zx_status_t TestLoopBase::ChangeSagState(test_sagcontrol::SystemActivityGovernorState state,
zx::duration poll_delay) {
while (true) {
std::cout << "Setting SAG state: " << FidlString(state) << std::endl;
auto set_result = fidl::Call(sag_control_state_client_end_)->Set(state);
if (!set_result.is_ok()) {
std::cout << "Failed to set SAG state: " << set_result.error_value() << std::endl;
return ZX_ERR_INTERNAL;
}
zx::nanosleep(zx::deadline_after(poll_delay));
const auto get_result = fidl::Call(sag_control_state_client_end_)->Get();
if (get_result.value() == state) {
break;
}
std::cout << "Retrying SAG state change. Last known state: " << FidlString(get_result.value())
<< std::endl;
set_result = fidl::Call(sag_control_state_client_end_)->Set(GetBootCompleteState());
if (!set_result.is_ok()) {
std::cout << "Failed to set boot complete SAG state: " << set_result.error_value()
<< std::endl;
return ZX_ERR_INTERNAL;
}
zx::nanosleep(zx::deadline_after(poll_delay));
}
std::cout << "SAG state change complete." << std::endl;
return ZX_OK;
}
void TestLoopBase::MatchInspectData(diagnostics::reader::ArchiveReader& reader,
const std::string& moniker,
const std::optional<std::string>& inspect_tree_name,
const std::vector<std::string>& inspect_path,
std::variant<bool, uint64_t> value) {
const auto selector = diagnostics::reader::MakeSelector(
moniker,
inspect_tree_name.has_value()
? std::optional<std::vector<std::string>>{{inspect_tree_name.value()}}
: std::nullopt,
std::vector<std::string>(inspect_path.begin(), inspect_path.end() - 1),
inspect_path[inspect_path.size() - 1]);
std::cout << "Matching inspect data for moniker = " << moniker << ", path = ";
for (const auto& path : inspect_path) {
std::cout << "[" << path << "]";
}
std::cout << ", selector = " << selector << std::endl;
bool match = false;
do {
auto result = RunPromise(reader.SetSelectors({selector}).GetInspectSnapshot());
auto data = result.take_value();
for (const auto& datum : data) {
bool* bool_value = std::get_if<bool>(&value);
if (bool_value != nullptr) {
const auto& actual_value = datum.GetByPath(inspect_path);
// IsBool will return false if the value at inspect_path doesn't exist yet,
// whereas GetBool will crash the test
if (!actual_value.IsBool()) {
std::cout << std::boolalpha << moniker << ": Expected value " << *bool_value
<< ", but got nothing for selector " << selector << ". Taking another snapshot."
<< std::endl;
// look through all the trees
continue;
}
if (actual_value.GetBool() == *bool_value) {
match = true;
std::cout << std::boolalpha << moniker << ": Got expected value " << *bool_value
<< " for selector " << selector << std::endl;
break;
} else {
std::cout << std::boolalpha << moniker << ": Expected value " << *bool_value
<< ", but got " << actual_value.GetBool() << ". Taking another snapshot."
<< std::endl;
}
}
uint64_t* uint64_value = std::get_if<uint64_t>(&value);
if (uint64_value != nullptr) {
const auto& actual_value = datum.GetByPath(inspect_path);
// IsUint64 will return false if the value at inspect_path doesn't exist yet,
// whereas GetUint64 will crash the test
if (!actual_value.IsUint64()) {
std::cout << moniker << ": Expected value " << *uint64_value
<< ", but got nothing for selector " << selector << ". Taking another snapshot."
<< std::endl;
// look through all the trees
continue;
}
if (actual_value.GetUint64() == *uint64_value) {
match = true;
std::cout << moniker << ": Got expected value " << *uint64_value << " for selector "
<< selector << std::endl;
break;
} else {
std::cout << moniker << ": Expected value " << *uint64_value << ", but got "
<< actual_value.GetUint64() << ". Taking another snapshot." << std::endl;
}
}
}
} while (!match);
}
zx::result<std::string> TestLoopBase::GetPowerElementId(diagnostics::reader::ArchiveReader& reader,
const std::string& pb_moniker,
const std::string& power_element_name) {
std::cout << "Searching for power element with name '" << power_element_name
<< "' in Power Broker's topology listing." << std::endl;
while (true) {
auto result = RunPromise(reader.SnapshotInspectUntilPresent({pb_moniker}));
auto data = result.take_value();
for (const auto& datum : data) {
if (datum.moniker() == pb_moniker && datum.payload().has_value()) {
auto topology = datum.payload().value()->GetByPath(
{"broker", "topology", "fuchsia.inspect.Graph", "topology"});
if (topology == nullptr) {
std::cout << "No topology listing in Power Broker's inspect data. ";
break;
}
for (const auto& child : topology->children()) {
auto name = datum
.GetByPath({"root", "broker", "topology", "fuchsia.inspect.Graph",
"topology", child.name(), "meta", "name"})
.GetString();
if (name == power_element_name) {
std::cout << "Power element '" << power_element_name << "' has ID '" << child.name()
<< "'." << std::endl;
return zx::ok(child.name());
}
}
std::cout << "Did not find power element in Power Broker's topology listing. ";
}
}
std::cout << "Taking another snapshot." << std::endl;
}
}
zx::eventpair TestLoopBase::PrepareDriver(std::string_view node_filter,
std::string_view driver_url_suffix,
bool expect_new_koid) {
// Find the node running our target driver.
std::cout << "Preparing driver '" << driver_url_suffix << "' for test..." << std::endl;
std::optional<std::string> found = std::nullopt;
uint64_t old_koid;
while (!found) {
auto node_vec = GetNodeInfo(node_filter);
for (auto& node : node_vec) {
if (node.bound_driver_url().has_value() &&
node.bound_driver_url().value().ends_with(driver_url_suffix)) {
std::cout << "driver found with moniker '" << node.moniker().value() << "'" << std::endl;
found.emplace(node.moniker().value());
old_koid = node.driver_host_koid().value();
break;
}
}
if (!found) {
std::cout << "driver not found, retrying... " << std::endl;
// Small loop delay.
RunLoopWithTimeout(zx::msec(10));
}
}
std::cout << "restarting driver with test dictionary..." << std::endl;
// Setup the power dictionary and restart the node with this dictionary.
auto dict_ref = CreateDictionaryForTest();
auto result =
fidl::Call(driver_manager_client_end_)->RestartWithDictionary({*found, std::move(dict_ref)});
EXPECT_EQ(true, result.is_ok());
if (!expect_new_koid) {
// Let the restart make progress, otherwise our next loop could run early enough to see the old
// instance of the target node since its not doing the koid comparison.
RunLoopWithTimeout(zx::sec(1));
}
std::cout << "checking for the driver to be up again..." << std::endl;
found = std::nullopt;
// Wait until the node is available again, possibly under a new driver host.
while (!found) {
auto node_vec = GetNodeInfo(node_filter);
for (auto& node : node_vec) {
if (node.bound_driver_url().has_value() &&
node.bound_driver_url().value().ends_with(driver_url_suffix)) {
if (node.driver_host_koid().has_value()) {
if (!expect_new_koid || old_koid != node.driver_host_koid().value()) {
found.emplace(node.moniker().value());
break;
}
}
}
}
if (!found) {
std::cout << "driver not found, retrying... " << std::endl;
// Small loop delay.
RunLoopWithTimeout(zx::msec(10));
}
}
std::cout << "proceeding with test! " << std::endl;
// Return the release_fence for the caller to hold on to.
return std::move(result.value().release_fence());
}
fuchsia_component_sandbox::DictionaryRef TestLoopBase::CreateDictionaryForTest() {
// Start a background loop to run the sandbox connectors.
sandbox_connector_loop_.StartThread("sandbox-loop");
// Counter for the capability store.
auto cap_store = component::Connect<fuchsia_component_sandbox::CapabilityStore>();
uint32_t dict_id = next_cap_id_++;
auto capstore_result = fidl::Call(*cap_store)->DictionaryCreate({dict_id});
EXPECT_EQ(true, capstore_result.is_ok());
auto [sag_client, sag_server] = fidl::Endpoints<fuchsia_component_sandbox::Receiver>::Create();
uint32_t sag_id = next_cap_id_++;
auto connector_res = fidl::Call(*cap_store)->ConnectorCreate({sag_id, {std::move(sag_client)}});
EXPECT_EQ(true, connector_res.is_ok());
auto [broker_client, broker_server] =
fidl::Endpoints<fuchsia_component_sandbox::Receiver>::Create();
uint32_t broker_id = next_cap_id_++;
connector_res = fidl::Call(*cap_store)->ConnectorCreate({broker_id, {std::move(broker_client)}});
EXPECT_EQ(true, connector_res.is_ok());
auto [cpu_element_client, cpu_element_server] =
fidl::Endpoints<fuchsia_component_sandbox::Receiver>::Create();
uint32_t cpu_element_id = next_cap_id_++;
connector_res =
fidl::Call(*cap_store)->ConnectorCreate({cpu_element_id, {std::move(cpu_element_client)}});
EXPECT_EQ(true, connector_res.is_ok());
auto insert_res =
fidl::Call(*cap_store)
->DictionaryInsert({dict_id, {"fuchsia.power.system.ActivityGovernor", sag_id}});
EXPECT_EQ(true, insert_res.is_ok());
insert_res = fidl::Call(*cap_store)
->DictionaryInsert({dict_id, {"fuchsia.power.broker.Topology", broker_id}});
EXPECT_EQ(true, insert_res.is_ok());
insert_res =
fidl::Call(*cap_store)
->DictionaryInsert({dict_id, {"fuchsia.power.system.CpuElementManager", cpu_element_id}});
EXPECT_EQ(true, insert_res.is_ok());
auto dict_export = fidl::Call(*cap_store)->Export({dict_id});
auto dict_ref = std::move(dict_export->capability().dictionary().value());
sag_connector_.emplace(async_patterns::PassDispatcher,
std::string("/svc/fuchsia.power.system.ActivityGovernor"),
std::move(sag_server));
broker_connector_.emplace(async_patterns::PassDispatcher,
std::string("/svc/fuchsia.power.broker.Topology"),
std::move(broker_server));
cpu_element_connector_.emplace(async_patterns::PassDispatcher,
std::string("/svc/fuchsia.power.system.CpuElementManager"),
std::move(cpu_element_server));
return dict_ref;
}
std::vector<fuchsia_driver_development::NodeInfo> TestLoopBase::GetNodeInfo(
std::string_view node_filter) {
std::vector<fuchsia_driver_development::NodeInfo> result;
auto [info_client, info_server] =
fidl::Endpoints<fuchsia_driver_development::NodeInfoIterator>::Create();
auto info = fidl::Call(driver_manager_client_end_)
->GetNodeInfo({{std::string(node_filter)}, std::move(info_server), false});
EXPECT_EQ(true, info.is_ok());
while (true) {
auto info_next = fidl::Call(info_client)->GetNext();
EXPECT_EQ(true, info_next.is_ok());
if (info_next->nodes().empty()) {
break;
}
for (auto& node : info_next->nodes()) {
result.push_back(node);
}
}
return result;
}
} // namespace system_integration_utils