blob: 5d97a33deb1e478158d2ac9c173af981fa85ef22 [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 <runtests-utils/fuchsia-run-test.h>
#include <errno.h>
#include <libgen.h>
#include <stdio.h>
#include <unistd.h>
#include <fbl/auto_call.h>
#include <fbl/string.h>
#include <fbl/string_printf.h>
#include <fbl/unique_fd.h>
#include <lib/fdio/spawn.h>
#include <lib/zx/job.h>
#include <lib/zx/process.h>
#include <sys/stat.h>
#include <zircon/status.h>
namespace runtests {
// Path to helper binary which can run test as a component. This binary takes
// component url as its parameter.
constexpr char kRunTestComponentPath[] = "/system/bin/run_test_component";
fbl::String DirectoryName(const fbl::String& path) {
char* cpath = strndup(path.data(), path.length());
fbl::String ret(dirname(cpath));
free(cpath);
return ret;
}
fbl::String BaseName(const fbl::String& path) {
char* cpath = strndup(path.data(), path.length());
fbl::String ret(basename(cpath));
free(cpath);
return ret;
}
void TestFileComponentInfo(const fbl::String path,
fbl::String* component_url_out,
fbl::String* cmx_file_path_out) {
if (strncmp(path.c_str(), kPkgPrefix, strlen(kPkgPrefix)) != 0) {
return;
}
const auto folder_path = DirectoryName(DirectoryName(path));
// folder_path should also start with |kPkgPrefix| and should not be equal
// to |kPkgPrefix|.
if (strncmp(folder_path.c_str(), kPkgPrefix, strlen(kPkgPrefix)) != 0 ||
folder_path == fbl::String(kPkgPrefix)) {
return;
}
const char* package_name = path.c_str() + strlen(kPkgPrefix);
// find occurence of first '/'
size_t i = 0;
while (package_name[i] != '\0' && package_name[i] != '/') {
i++;
}
const fbl::String package_name_str(package_name, i);
const auto test_file_name = BaseName(path);
*cmx_file_path_out = fbl::StringPrintf(
"%s/meta/%s.cmx", folder_path.c_str(), test_file_name.c_str());
*component_url_out =
fbl::StringPrintf("fuchsia-pkg://fuchsia.com/%s#meta/%s.cmx",
package_name_str.c_str(), test_file_name.c_str());
}
fbl::unique_ptr<Result> FuchsiaRunTest(const char* argv[],
const char* output_filename) {
// The arguments passed to fdio_spaws_etc. May be overridden.
const char** args = argv;
// calculate size of argv
size_t argc = 0;
while (argv[argc] != nullptr) {
argc++;
}
// Values used when running the test as a component.
const char* component_launch_args[argc + 2];
fbl::String component_url;
fbl::String cmx_file_path;
const char* path = argv[0];
TestFileComponentInfo(path, &component_url, &cmx_file_path);
struct stat s;
// If we get a non empty |cmx_file_path|, check that it exists, and if
// present launch the test as component using generated |component_url|.
if (cmx_file_path != "" && stat(cmx_file_path.c_str(), &s) == 0) {
if (stat(kRunTestComponentPath, &s) != 0) {
// TODO(anmittal): Make this a error once we have a stable
// system and we can run all tests as components.
fprintf(stderr,
"WARNING: Cannot find '%s', running '%s' as normal test "
"binary.",
kRunTestComponentPath, path);
} else {
component_launch_args[0] = kRunTestComponentPath;
component_launch_args[1] = component_url.c_str();
for (size_t i = 1; i <= argc; i++) {
component_launch_args[1 + i] = argv[i];
}
args = component_launch_args;
}
}
// If |output_filename| is provided, prepare the file descriptors that will
// be used to tee the stdout/stderr of the test into the associated file.
fbl::unique_fd fds[2];
size_t fdio_action_count = 1; // At least one for SET_NAME.
if (output_filename != nullptr) {
int temp_fds[2] = {-1, -1};
if (pipe(temp_fds)) {
fprintf(stderr, "FAILURE: Failed to create pipe: %s\n",
strerror(errno));
return fbl::make_unique<Result>(path, FAILED_TO_LAUNCH, 0);
}
fds[0].reset(temp_fds[0]);
fds[1].reset(temp_fds[1]);
fdio_action_count += 2; // Plus two for CLONE_FD and TRANSFER_FD.
}
// If |output_filename| is not provided, then we will ignore all but the
// first action.
const fdio_spawn_action_t fdio_actions[] = {
{.action = FDIO_SPAWN_ACTION_SET_NAME, .name = {.data = path}},
{.action = FDIO_SPAWN_ACTION_CLONE_FD,
.fd = {.local_fd = fds[1].get(), .target_fd = STDOUT_FILENO}},
{.action = FDIO_SPAWN_ACTION_TRANSFER_FD,
.fd = {.local_fd = fds[1].get(), .target_fd = STDERR_FILENO}},
};
zx_status_t status = ZX_OK;
zx::job test_job;
status = zx::job::create(*zx::job::default_job(), 0, &test_job);
if (status != ZX_OK) {
fprintf(stderr, "FAILURE: zx::job::create() returned %d\n", status);
return fbl::make_unique<Result>(path, FAILED_TO_LAUNCH, 0);
}
auto auto_call_kill_job =
fbl::MakeAutoCall([&test_job]() { test_job.kill(); });
status =
test_job.set_property(ZX_PROP_NAME, "run-test", sizeof("run-test"));
if (status != ZX_OK) {
fprintf(stderr, "FAILURE: set_property() returned %d\n", status);
return fbl::make_unique<Result>(path, FAILED_TO_LAUNCH, 0);
}
fds[1].release(); // To avoid double close since fdio_spawn_etc() closes it.
zx::process process;
char err_msg[FDIO_SPAWN_ERR_MSG_MAX_LENGTH];
status = fdio_spawn_etc(test_job.get(), FDIO_SPAWN_CLONE_ALL, args[0], args,
nullptr, fdio_action_count, fdio_actions,
process.reset_and_get_address(), err_msg);
if (status != ZX_OK) {
fprintf(stderr, "FAILURE: Failed to launch %s: %d (%s): %s\n", path,
status, zx_status_get_string(status), err_msg);
return fbl::make_unique<Result>(path, FAILED_TO_LAUNCH, 0);
}
// Tee output.
if (output_filename != nullptr) {
FILE* output_file = fopen(output_filename, "w");
if (output_file == nullptr) {
fprintf(stderr, "FAILURE: Could not open output file at %s: %s\n",
output_filename, strerror(errno));
return fbl::make_unique<Result>(path, FAILED_DURING_IO, 0);
}
char buf[1024];
ssize_t bytes_read = 0;
while ((bytes_read = read(fds[0].get(), buf, sizeof(buf))) > 0) {
fwrite(buf, 1, bytes_read, output_file);
fwrite(buf, 1, bytes_read, stdout);
}
if (fclose(output_file)) {
fprintf(stderr, "FAILURE: Could not close %s: %s", output_filename,
strerror(errno));
return fbl::make_unique<Result>(path, FAILED_DURING_IO, 0);
}
}
status =
process.wait_one(ZX_PROCESS_TERMINATED, zx::time::infinite(), nullptr);
if (status != ZX_OK) {
fprintf(stderr, "FAILURE: Failed to wait for process exiting %s: %d\n",
path, status);
return fbl::make_unique<Result>(path, FAILED_TO_WAIT, 0);
}
// read the return code
zx_info_process_t proc_info;
status = process.get_info(ZX_INFO_PROCESS, &proc_info, sizeof(proc_info),
nullptr, nullptr);
if (status != ZX_OK) {
fprintf(stderr, "FAILURE: Failed to get process return code %s: %d\n",
path, status);
return fbl::make_unique<Result>(path, FAILED_TO_RETURN_CODE, 0);
}
if (proc_info.return_code != 0) {
fprintf(stderr, "FAILURE: %s exited with nonzero status: %" PRId64 "\n",
path, proc_info.return_code);
return fbl::make_unique<Result>(path, FAILED_NONZERO_RETURN_CODE,
proc_info.return_code);
}
fprintf(stderr, "PASSED: %s passed\n", path);
return fbl::make_unique<Result>(path, SUCCESS, 0);
}
} // namespace runtests