| // Copyright 2018 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 "garnet/bin/trace/tests/run_test.h" |
| |
| #include <fuchsia/sys/cpp/fidl.h> |
| #include <lib/async-loop/cpp/loop.h> |
| #include <lib/fdio/spawn.h> |
| #include <lib/fsl/types/type_converters.h> |
| #include <lib/sys/cpp/component_context.h> |
| #include <lib/zx/process.h> |
| #include <lib/zx/time.h> |
| #include <src/lib/fxl/log_settings.h> |
| #include <src/lib/fxl/logging.h> |
| #include <src/lib/fxl/strings/join_strings.h> |
| #include <src/lib/fxl/strings/string_printf.h> |
| #include <zircon/processargs.h> |
| #include <zircon/status.h> |
| #include <zircon/types.h> |
| |
| #include <string> |
| #include <vector> |
| |
| #include "garnet/bin/trace/spec.h" |
| #include "src/lib/files/file.h" |
| |
| // The "path" of the trace program from outside the trace package. |
| const char kTraceProgramUrl[] = |
| "fuchsia-pkg://fuchsia.com/trace#meta/trace.cmx"; |
| // The path of the trace program from within the trace package. |
| // const char kTracePackageProgramPath[] = "/pkg/bin/trace"; |
| |
| // Package path to use for spawned processes. |
| const char kSystemPackageTestPrefix[] = "/pkgfs/packages/trace_tests/0/"; |
| // Package path to use for launched processes. |
| const char kPackageTestPrefix[] = "/pkg/"; |
| |
| void AppendLoggingArgs(std::vector<std::string>* argv, const char* prefix) { |
| // Transfer our log settings to the subprogram. |
| auto log_settings = fxl::GetLogSettings(); |
| 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); |
| } |
| |
| static bool ReadTspec(const std::string& tspec_path, tracing::Spec* spec) { |
| std::string tspec_contents; |
| if (!files::ReadFileToString(tspec_path, &tspec_contents)) { |
| FXL_LOG(ERROR) << "Can't read test spec: " << tspec_path; |
| return false; |
| } |
| |
| if (!tracing::DecodeSpec(tspec_contents, spec)) { |
| FXL_LOG(ERROR) << "Error decoding test spec: " << tspec_path; |
| return false; |
| } |
| return true; |
| } |
| |
| static void BuildTraceProgramArgv(const std::string& relative_tspec_path, |
| const std::string& output_file_path, |
| std::vector<std::string>* argv) { |
| tracing::Spec spec; |
| if (!ReadTspec(kPackageTestPrefix + relative_tspec_path, &spec)) |
| return; |
| |
| argv->push_back(kTraceProgramUrl); |
| AppendLoggingArgs(argv, ""); |
| argv->push_back("record"); |
| argv->push_back(fxl::StringPrintf( |
| "--spec-file=%s", |
| (kSystemPackageTestPrefix + relative_tspec_path).c_str())); |
| argv->push_back( |
| fxl::StringPrintf("--output-file=%s", output_file_path.c_str())); |
| |
| AppendLoggingArgs(argv, "--append-args="); |
| |
| // Note that |relative_tspec_path| cannot have a comma. |
| argv->push_back(fxl::StringPrintf( |
| "--append-args=run,%s", |
| ((spec.spawn ? kSystemPackageTestPrefix : kPackageTestPrefix) + |
| relative_tspec_path) |
| .c_str())); |
| } |
| |
| static void BuildVerificationProgramArgv(const std::string& program_path, |
| const std::string& relative_tspec_path, |
| const std::string& output_file_path, |
| std::vector<std::string>* argv) { |
| argv->push_back(program_path); |
| |
| AppendLoggingArgs(argv, ""); |
| |
| argv->push_back("verify"); |
| argv->push_back(relative_tspec_path); |
| argv->push_back(output_file_path); |
| } |
| |
| zx_status_t SpawnProgram(const zx::job& job, |
| const std::vector<std::string>& argv, |
| zx_handle_t arg_handle, zx::process* out_process) { |
| std::vector<const char*> c_argv; |
| StringArgvToCArgv(argv, &c_argv); |
| |
| FXL_VLOG(1) << "Running " << fxl::JoinStrings(argv, " "); |
| |
| size_t action_count = 0; |
| fdio_spawn_action_t spawn_actions[1]; |
| if (arg_handle != ZX_HANDLE_INVALID) { |
| spawn_actions[0].action = FDIO_SPAWN_ACTION_ADD_HANDLE; |
| spawn_actions[0].h.id = PA_HND(PA_USER0, 0); |
| spawn_actions[0].h.handle = arg_handle; |
| action_count = 1; |
| } |
| |
| char err_msg[FDIO_SPAWN_ERR_MSG_MAX_LENGTH]; |
| auto status = |
| fdio_spawn_etc(job.get(), FDIO_SPAWN_CLONE_ALL, c_argv[0], c_argv.data(), |
| nullptr, action_count, &spawn_actions[0], |
| out_process->reset_and_get_address(), err_msg); |
| if (status != ZX_OK) { |
| FXL_LOG(ERROR) << "Spawning " << c_argv[0] << " failed: " << err_msg << ", " |
| << status; |
| return status; |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t WaitAndGetExitCode(const std::string& program_name, |
| const zx::process& process, int* out_exit_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) { |
| FXL_LOG(ERROR) << "Failed waiting for program " << program_name |
| << " to exit: " << zx_status_get_string(status); |
| return status; |
| } |
| |
| 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) { |
| FXL_LOG(ERROR) << "Error getting return code for program " << program_name |
| << ": " << zx_status_get_string(status); |
| return status; |
| } |
| |
| if (proc_info.return_code != 0) { |
| FXL_LOG(ERROR) << program_name << " exited with exit code " |
| << proc_info.return_code; |
| } |
| *out_exit_code = proc_info.return_code; |
| return ZX_OK; |
| } |
| |
| static bool LaunchTool(const std::vector<std::string>& argv) { |
| zx::job job{}; // -> default job |
| zx::process subprocess; |
| |
| auto status = SpawnProgram(job, argv, ZX_HANDLE_INVALID, &subprocess); |
| if (status != ZX_OK) { |
| return false; |
| } |
| |
| int exit_code; |
| status = WaitAndGetExitCode(argv[0], subprocess, &exit_code); |
| if (status != ZX_OK) { |
| return false; |
| } |
| if (exit_code != 0) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| static bool LaunchApp(sys::ComponentContext* context, const std::string& app, |
| const std::vector<std::string>& args) { |
| fuchsia::sys::LaunchInfo launch_info; |
| launch_info.url = std::string(app); |
| launch_info.arguments = fidl::To<fidl::VectorPtr<std::string>>(args); |
| |
| if (FXL_VLOG_IS_ON(1)) { |
| FXL_VLOG(1) << "Launching: " << launch_info.url << " " |
| << fxl::JoinStrings(args, " "); |
| } else { |
| FXL_LOG(INFO) << "Launching: " << launch_info.url; |
| } |
| |
| fuchsia::sys::ComponentControllerPtr component_controller; |
| int64_t return_code = INT64_MIN; |
| |
| // Attach to the current thread so that it's using the default async |
| // dispatcher, which is what the component controller machinery is using. |
| async::Loop loop(&kAsyncLoopConfigAttachToThread); |
| |
| fuchsia::sys::LauncherPtr launcher; |
| context->svc()->Connect(launcher.NewRequest()); |
| launcher->CreateComponent(std::move(launch_info), |
| component_controller.NewRequest()); |
| component_controller.set_error_handler( |
| [&loop, &return_code](zx_status_t error) { |
| // This can get run even after the app has exited, in the destructor. |
| if (return_code == INT64_MIN) { |
| FXL_LOG(INFO) << "Application terminated, status " << error; |
| return_code = error; |
| loop.Quit(); |
| } |
| }); |
| component_controller.events().OnTerminated = |
| [&loop, &return_code]( |
| int64_t rc, fuchsia::sys::TerminationReason termination_reason) { |
| FXL_LOG(INFO) << "Application exited with return reason/code " |
| << static_cast<int>(termination_reason) << "/" << rc; |
| switch (termination_reason) { |
| case fuchsia::sys::TerminationReason::UNKNOWN: |
| // Some non-zero value. |
| return_code = 1; |
| break; |
| case fuchsia::sys::TerminationReason::EXITED: |
| return_code = rc; |
| break; |
| default: |
| return_code = static_cast<int64_t>(termination_reason); |
| break; |
| } |
| loop.Quit(); |
| }; |
| |
| // We could add a timeout here but the general rule is to leave it to the |
| // watchdog timer. |
| |
| loop.Run(); |
| |
| FXL_LOG(INFO) << "return_code " << return_code; |
| return return_code == 0; |
| } |
| |
| bool RunTspec(sys::ComponentContext* context, |
| const std::string& relative_tspec_path, |
| const std::string& output_file_path) { |
| std::vector<std::string> argv; |
| BuildTraceProgramArgv(relative_tspec_path, output_file_path, &argv); |
| |
| FXL_LOG(INFO) << "Running tspec " << relative_tspec_path << ", output file " |
| << output_file_path; |
| |
| return LaunchApp(context, argv[0], |
| std::vector<std::string>(argv.begin() + 1, argv.end())); |
| } |
| |
| bool VerifyTspec(sys::ComponentContext* context, |
| const std::string& relative_tspec_path, |
| const std::string& output_file_path) { |
| tracing::Spec spec; |
| if (!ReadTspec(kPackageTestPrefix + relative_tspec_path, &spec)) { |
| return false; |
| } |
| |
| FXL_DCHECK(spec.app); |
| const std::string& program_path = *spec.app; |
| |
| std::vector<std::string> argv; |
| BuildVerificationProgramArgv( |
| program_path, |
| ((spec.spawn ? kSystemPackageTestPrefix : kPackageTestPrefix) + |
| relative_tspec_path), |
| output_file_path, &argv); |
| |
| FXL_LOG(INFO) << "Verifying tspec " << relative_tspec_path << ", output file " |
| << output_file_path; |
| |
| // For consistency we do the exact same thing that the trace program does. |
| // We also use the same function names for easier comparison. |
| if (spec.spawn) { |
| return LaunchTool(argv); |
| } else { |
| return LaunchApp(context, argv[0], |
| std::vector<std::string>(argv.begin() + 1, argv.end())); |
| } |
| } |