blob: 0c832dd287f552f6ce0f834f7d1083aa969e4ce9 [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 "src/developer/tracing/lib/test_utils/run_program.h"
#include <lib/syslog/cpp/log_settings.h>
#include <lib/syslog/cpp/macros.h>
#include <zircon/processargs.h>
#include <zircon/status.h>
#include <zircon/types.h>
#include "src/lib/files/file.h"
#include "src/lib/fsl/types/type_converters.h"
#include "src/lib/fxl/strings/join_strings.h"
#include "src/lib/fxl/strings/string_printf.h"
namespace tracing {
namespace test {
void AppendLoggingArgs(std::vector<std::string>* argv, const char* prefix,
const syslog::LogSettings& log_settings) {
// Transfer our log settings to the subprogram.
std::string log_file_arg;
std::string verbose_or_quiet_arg;
if (log_settings.log_file != "") {
log_file_arg = fxl::StringPrintf("%s--log-file=%s", prefix, log_settings.log_file.c_str());
argv->push_back(log_file_arg);
}
if (log_settings.min_log_level != 0) {
if (log_settings.min_log_level < 0) {
verbose_or_quiet_arg =
fxl::StringPrintf("%s--verbose=%d", prefix, -log_settings.min_log_level);
} else {
verbose_or_quiet_arg = fxl::StringPrintf("%s--quiet=%d", prefix, log_settings.min_log_level);
}
argv->push_back(verbose_or_quiet_arg);
}
}
static void StringArgvToCArgv(const std::vector<std::string>& argv,
std::vector<const char*>* c_argv) {
for (const auto& arg : argv) {
c_argv->push_back(arg.c_str());
}
c_argv->push_back(nullptr);
}
zx_status_t SpawnProgram(const zx::job& job, const std::vector<std::string>& argv,
zx_handle_t arg_handle, zx::process* out_process) {
size_t num_actions = 0;
fdio_spawn_action_t spawn_actions[1];
if (arg_handle != ZX_HANDLE_INVALID) {
spawn_actions[num_actions].action = FDIO_SPAWN_ACTION_ADD_HANDLE;
spawn_actions[num_actions].h.id = PA_HND(PA_USER0, 0);
spawn_actions[num_actions].h.handle = arg_handle;
++num_actions;
}
return RunProgram(job, argv, num_actions, spawn_actions, out_process);
}
zx_status_t RunProgram(const zx::job& job, const std::vector<std::string>& argv, size_t num_actions,
const fdio_spawn_action_t* actions, zx::process* out_process) {
std::vector<const char*> c_argv;
StringArgvToCArgv(argv, &c_argv);
FX_LOGS(INFO) << "Running " << fxl::JoinStrings(argv, " ");
char err_msg[FDIO_SPAWN_ERR_MSG_MAX_LENGTH];
zx_status_t status =
fdio_spawn_etc(job.get(), FDIO_SPAWN_CLONE_ALL, c_argv[0], c_argv.data(), nullptr,
num_actions, actions, out_process->reset_and_get_address(), err_msg);
if (status != ZX_OK) {
FX_PLOGS(ERROR, status) << "Spawning " << c_argv[0] << " failed: " << err_msg;
return status;
}
return ZX_OK;
}
bool WaitAndGetReturnCode(const std::string& program_name, const zx::process& process,
int64_t* out_return_code) {
// Leave it to the test harness to provide a timeout. If it doesn't that's
// its bug.
auto status = process.wait_one(ZX_PROCESS_TERMINATED, zx::time::infinite(), nullptr);
if (status != ZX_OK) {
FX_PLOGS(ERROR, status) << "Failed waiting for program " << program_name << " to exit";
return false;
}
zx_info_process_t proc_info;
status = zx_object_get_info(process.get(), ZX_INFO_PROCESS, &proc_info, sizeof(proc_info),
nullptr, nullptr);
if (status != ZX_OK) {
FX_PLOGS(ERROR, status) << "Error getting return code for program " << program_name;
return false;
}
if (proc_info.return_code != 0) {
FX_LOGS(INFO) << program_name << " exited with return code " << proc_info.return_code;
}
*out_return_code = proc_info.return_code;
return true;
}
bool RunProgramAndWait(const zx::job& job, const std::vector<std::string>& argv, size_t num_actions,
const fdio_spawn_action_t* actions) {
zx::process subprocess;
auto status = RunProgram(job, argv, num_actions, actions, &subprocess);
if (status != ZX_OK) {
return false;
}
int64_t return_code;
if (!WaitAndGetReturnCode(argv[0], subprocess, &return_code)) {
return false;
}
if (return_code != 0) {
FX_LOGS(ERROR) << argv[0] << " exited with return code " << return_code;
return false;
}
return true;
}
bool RunComponent(sys::ComponentContext* context, const std::string& app,
const std::vector<std::string>& args,
std::unique_ptr<fuchsia::sys::FlatNamespace> flat_namespace,
fuchsia::sys::ComponentControllerPtr* component_controller) {
fuchsia::sys::LaunchInfo launch_info;
launch_info.url = std::string(app);
launch_info.arguments = fidl::To<fidl::VectorPtr<std::string>>(args);
launch_info.flat_namespace = std::move(flat_namespace);
FX_LOGS(INFO) << "Launching: " << launch_info.url << " " << fxl::JoinStrings(args, " ");
fuchsia::sys::LauncherPtr launcher;
zx_status_t status = context->svc()->Connect(launcher.NewRequest());
if (status != ZX_OK) {
FX_PLOGS(ERROR, status) << "context->svc()->Connect() failed for " << app;
return false;
}
launcher->CreateComponent(std::move(launch_info), component_controller->NewRequest());
return true;
}
bool WaitAndGetReturnCode(const std::string& program_name, async::Loop* loop,
fuchsia::sys::ComponentControllerPtr* component_controller,
int64_t* out_return_code) {
fuchsia::sys::TerminationReason termination_reason =
fuchsia::sys::TerminationReason::INTERNAL_ERROR;
// This value is not valid unless |termination_reason==EXITED|.
int64_t return_code = INT64_MIN;
component_controller->set_error_handler(
[&loop, &program_name, &termination_reason](zx_status_t error) {
FX_PLOGS(ERROR, error) << "Unexpected error waiting for " << program_name << " to exit";
termination_reason = fuchsia::sys::TerminationReason::UNKNOWN;
loop->Quit();
});
component_controller->events().OnTerminated =
[&loop, &program_name, &component_controller, &termination_reason, &return_code](
int64_t rc, fuchsia::sys::TerminationReason reason) {
FX_LOGS(INFO) << "Component " << program_name << " exited with return reason/code "
<< static_cast<int>(termination_reason) << "/" << rc;
// Disable the error handler. It can get called after we're done, e.g., during the
// destructor.
component_controller->set_error_handler([](zx_status_t error) {});
termination_reason = reason;
if (termination_reason == fuchsia::sys::TerminationReason::EXITED) {
return_code = rc;
}
loop->Quit();
};
// We could add a timeout here but the general rule is to leave it to the watchdog timer.
loop->Run();
if (termination_reason == fuchsia::sys::TerminationReason::EXITED) {
FX_LOGS(INFO) << program_name << ": return code " << return_code;
*out_return_code = return_code;
return true;
} else {
FX_LOGS(ERROR) << program_name << ": termination reason "
<< static_cast<int>(termination_reason);
return false;
}
}
bool RunComponentAndWait(async::Loop* loop, sys::ComponentContext* context, const std::string& app,
const std::vector<std::string>& args,
std::unique_ptr<fuchsia::sys::FlatNamespace> flat_namespace) {
fuchsia::sys::ComponentControllerPtr component_controller;
if (!RunComponent(context, app, args, std::move(flat_namespace), &component_controller)) {
return false;
}
int64_t return_code;
if (!WaitAndGetReturnCode(app, loop, &component_controller, &return_code)) {
return false;
}
return return_code == 0;
}
} // namespace test
} // namespace tracing