blob: ff151cd7e87a8032e29e2bfe9b3f4bb345f729ae [file] [log] [blame]
// 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 <stdio.h>
#include <fuchsia/logger/cpp/fidl.h>
#include <fuchsia/sys/cpp/fidl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/component/cpp/termination_reason.h>
#include <lib/fdio/util.h>
#include "garnet/bin/run_test_component/env_config.h"
#include "garnet/bin/run_test_component/run_test_component.h"
#include "garnet/bin/run_test_component/test_metadata.h"
#include "lib/component/cpp/startup_context.h"
#include "lib/component/cpp/testing/enclosing_environment.h"
#include "lib/component/cpp/testing/test_util.h"
#include "lib/fxl/files/file.h"
#include "lib/fxl/files/glob.h"
#include "lib/fxl/strings/string_printf.h"
using fuchsia::sys::TerminationReason;
namespace {
constexpr char kEnv[] = "env_for_test";
constexpr char kConfigPath[] =
"/pkgfs/packages/run_test_component/0/data/environment.config";
void PrintUsage() {
fprintf(stderr, R"(
Usage: run_test_component <test_url> [arguments...]
run_test_component <test_prefix> [arguments...]
*test_url* takes the form of component manifest URL which uniquely
identifies a test component. Example:
fuchsia-pkg://fuchsia.com/component_hello_world#meta/hello.cmx
if *test_prefix* is provided, this tool will glob over /pkgfs and look for
matching cmx files. If multiple files are found, it will print
corresponding component URLs and exit. If there is only one match, it
will generate a component URL and execute the test.
example:
run_test_component run_test_component_unit
will match /pkgfs/packages/run_test_component_unittests/meta/run_test_component_unittests.cmx and run it.
)");
}
bool ConnectToRequiredEnvironment(const run::EnvironmentType& env_type,
zx::channel request) {
std::string current_env;
files::ReadFileToString("/hub/name", &current_env);
std::string svc_path = "/hub/svc";
switch (env_type) {
case run::EnvironmentType::ROOT:
if (current_env != "app") {
fprintf(stderr,
"Cannot run test in root environment as this utility was "
"started in '%s' environment",
current_env.c_str());
return false;
}
break;
case run::EnvironmentType::SYS:
if (current_env == "app") {
files::Glob glob("/hub/r/sys/*/svc");
if (glob.size() != 1) {
fprintf(stderr, "Cannot run test. Something wrong with hub.");
return false;
}
svc_path = *(glob.begin());
} else if (current_env != "sys") {
fprintf(stderr,
"Cannot run test in sys environment as this utility was "
"started in '%s' environment",
current_env.c_str());
return false;
}
break;
}
// launch test
zx::channel h1, h2;
zx_status_t status;
if ((status = zx::channel::create(0, &h1, &h2)) != ZX_OK) {
fprintf(stderr, "Cannot create channel, status: %d", status);
return false;
}
if ((status = fdio_service_connect(svc_path.c_str(), h1.release())) !=
ZX_OK) {
fprintf(stderr, "Cannot connect to %s, status: %d", svc_path.c_str(),
status);
return false;
}
if ((status = fdio_service_connect_at(
h2.get(), fuchsia::sys::Environment::Name_, request.release())) !=
ZX_OK) {
fprintf(stderr, "Cannot connect to env service, status: %d", status);
return false;
}
return true;
}
} // namespace
int main(int argc, const char** argv) {
run::EnvironmentConfig config;
config.ParseFromFile(kConfigPath);
if (config.HasError()) {
fprintf(stderr, "Error parsing config file %s: %s\n", kConfigPath,
config.error_str().c_str());
return 1;
}
auto parse_result = run::ParseArgs(argc, argv, "/pkgfs/packages");
if (parse_result.error) {
if (parse_result.error_msg != "") {
fprintf(stderr, "%s\n", parse_result.error_msg.c_str());
}
PrintUsage();
return 1;
}
if (parse_result.matching_urls.size() > 1) {
fprintf(stderr, "Found multiple matching components. Did you mean?\n");
for (auto url : parse_result.matching_urls) {
fprintf(stderr, "%s\n", url.c_str());
}
return 1;
} else if (parse_result.matching_urls.size() == 1) {
fprintf(stdout, "Found one matching component. Running: %s\n",
parse_result.matching_urls[0].c_str());
}
std::string program_name = parse_result.launch_info.url;
run::TestMetadata test_metadata;
if (!test_metadata.ParseFromFile(parse_result.cmx_file_path)) {
fprintf(stderr, "Error parsing cmx %s: %s\n",
parse_result.cmx_file_path.c_str(),
test_metadata.error_str().c_str());
return 1;
}
async::Loop loop(&kAsyncLoopConfigAttachToThread);
auto context = component::StartupContext::CreateFromStartupInfo();
fuchsia::sys::ComponentControllerPtr controller;
fuchsia::sys::EnvironmentPtr parent_env;
fuchsia::sys::LauncherPtr launcher;
std::unique_ptr<component::testing::EnclosingEnvironment> enclosing_env;
auto map_entry = config.url_map().find(parse_result.launch_info.url);
if (map_entry != config.url_map().end()) {
if (test_metadata.HasServices()) {
fprintf(stderr,
"Cannot run this test in sys/root environment as it defines "
"services in its '%s' facets\n",
run::kFuchsiaTest);
return 1;
}
if (!ConnectToRequiredEnvironment(map_entry->second,
parent_env.NewRequest().TakeChannel())) {
return 1;
}
parse_result.launch_info.out =
component::testing::CloneFileDescriptor(STDOUT_FILENO);
parse_result.launch_info.err =
component::testing::CloneFileDescriptor(STDERR_FILENO);
parent_env->GetLauncher(launcher.NewRequest());
} else {
context->ConnectToEnvironmentService(parent_env.NewRequest());
auto env_services =
component::testing::EnvironmentServices::Create(parent_env);
auto services = test_metadata.TakeServices();
bool provide_real_log_sink = true;
for (auto& service : services) {
env_services->AddServiceWithLaunchInfo(std::move(service.second),
service.first);
if (service.first == fuchsia::logger::LogSink::Name_) {
// don't add global log sink service if test component is injecting
// it.
provide_real_log_sink = false;
}
}
if (provide_real_log_sink) {
env_services->AllowParentService(fuchsia::logger::LogSink::Name_);
}
auto& system_services = test_metadata.system_services();
for (auto& service : system_services) {
env_services->AllowParentService(service);
}
enclosing_env = component::testing::EnclosingEnvironment::Create(
kEnv, parent_env, std::move(env_services));
launcher = enclosing_env->launcher_ptr();
}
launcher->CreateComponent(std::move(parse_result.launch_info),
controller.NewRequest());
controller.events().OnTerminated = [&program_name](
int64_t return_code,
TerminationReason termination_reason) {
if (termination_reason != TerminationReason::EXITED) {
fprintf(stderr, "%s: %s\n", program_name.c_str(),
component::HumanReadableTerminationReason(termination_reason)
.c_str());
}
zx_process_exit(return_code);
};
loop.Run();
return 0;
}