blob: 46c4a2a0e4d00001063b0e32ad7077c524438117 [file] [log] [blame]
// Copyright 2021 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 "suite.h"
#include <fuchsia/debugdata/cpp/fidl.h>
#include <fuchsia/io/cpp/fidl.h>
#include <fuchsia/logger/cpp/fidl.h>
#include <fuchsia/sys/cpp/fidl.h>
#include <fuchsia/test/cpp/fidl.h>
#include <lib/async/cpp/executor.h>
#include <lib/async/cpp/task.h>
#include <lib/async/dispatcher.h>
#include <lib/fidl/cpp/interface_handle.h>
#include <lib/fidl/cpp/interface_ptr.h>
#include <lib/fidl/cpp/interface_request.h>
#include <lib/fpromise/barrier.h>
#include <lib/fpromise/bridge.h>
#include <lib/fpromise/promise.h>
#include <lib/sys/cpp/service_directory.h>
#include <lib/sys/cpp/termination_reason.h>
#include <lib/sys/cpp/testing/enclosing_environment.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/zx/socket.h>
#include <zircon/processargs.h>
#include <zircon/status.h>
#include <map>
#include <memory>
#include <set>
#include <utility>
#include <vector>
#include <src/lib/fxl/strings/string_printf.h>
#include <src/sys/run_test_component/component.h>
static const char kTestCaseName[] = "legacy_test";
constexpr char kEnvPrefix[] = "test_env_";
namespace {
std::unique_ptr<run::OutputCollector> AddOutputFileDescriptor(
int fileno, zx::socket sock, async_dispatcher_t* dispatcher,
fuchsia::sys::FileDescriptorPtr* out_file_descriptor) {
auto output_collector = run::OutputCollector::Create();
fuchsia::sys::FileDescriptorPtr file_descriptor =
std::make_unique<fuchsia::sys::FileDescriptor>();
file_descriptor->type0 = PA_HND(PA_FD, fileno);
file_descriptor->handle0 = output_collector->TakeServer();
*out_file_descriptor = std::move(file_descriptor);
output_collector->CollectOutput(
[sock = std::move(sock)](const std::string& s) {
auto status = sock.write(0 /* options */, s.c_str(), s.length(), nullptr);
if (status != ZX_OK) {
FX_LOGS(WARNING) << "Cannot write output to socket: " << zx_status_get_string(status)
<< "\n Output:" << s;
}
},
dispatcher);
return output_collector;
}
void send_failure(fidl::InterfacePtr<fuchsia::test::CaseListener>& case_listener) {
fuchsia::test::Result result;
result.set_status(fuchsia::test::Status::FAILED);
case_listener->Finished(std::move(result));
}
} // namespace
Suite::Suite(std::shared_ptr<sys::ServiceDirectory> parent_env_svc,
fuchsia::sys::EnvironmentPtr parent_env,
std::shared_ptr<run::TestMetadata> test_metadata,
std::shared_ptr<sys::ServiceDirectory> test_component_svc, std::string legacy_url,
async_dispatcher_t* dispatcher)
: parent_env_(std::move(parent_env)),
parent_env_svc_(std::move(parent_env_svc)),
test_component_svc_(std::move(test_component_svc)),
test_metadata_(std::move(test_metadata)),
legacy_url_(std::move(legacy_url)),
test_components_(std::make_shared<ComponentMap>()),
dispatcher_(dispatcher),
executor_(dispatcher) {}
Suite::~Suite() = default;
Suite::CaseIterator::CaseIterator(fidl::InterfaceRequest<fuchsia::test::CaseIterator> request,
async_dispatcher_t* dispatcher,
fit::function<void(CaseIterator*)> done_callback)
: binding_(this, std::move(request), dispatcher), done_callback_(std::move(done_callback)) {}
void Suite::CaseIterator::GetNext(GetNextCallback callback) {
if (get_next_call_count == 0) {
fuchsia::test::Case test_case;
test_case.set_name(std::string(kTestCaseName));
test_case.set_enabled(true);
std::vector<fuchsia::test::Case> cases;
cases.push_back(std::move(test_case));
callback(std::move(cases));
get_next_call_count++;
} else {
std::vector<fuchsia::test::Case> cases;
callback(std::move(cases));
// this would be removed
done_callback_(this);
}
}
void Suite::GetTests(fidl::InterfaceRequest<fuchsia::test::CaseIterator> iterator) {
auto case_iterator = std::make_unique<CaseIterator>(
std::move(iterator), dispatcher_,
[this](CaseIterator* case_iterator) { RemoveCaseInterator(case_iterator); });
case_iterators_.emplace(case_iterator.get(), std::move(case_iterator));
}
std::unique_ptr<Suite::CaseIterator> Suite::RemoveCaseInterator(CaseIterator* case_iterator) {
auto it = case_iterators_.find(case_iterator);
std::unique_ptr<Suite::CaseIterator> case_iterator_ptr;
if (it != case_iterators_.end()) {
case_iterator_ptr = std::move(it->second);
case_iterators_.erase(it);
}
return case_iterator_ptr;
}
void Suite::Run(std::vector<fuchsia::test::Invocation> tests, fuchsia::test::RunOptions options,
fidl::InterfaceHandle<fuchsia::test::RunListener> listener) {
auto listener_proxy = listener.Bind();
std::vector<std::string> args;
if (options.has_arguments()) {
args = std::move(*options.mutable_arguments());
}
if (options.has_parallel()) {
FX_LOGS(WARNING) << "Ignoring 'parallel'. Pass test specific flags, eg: for rust test pass in "
"--test-threads="
<< options.parallel();
}
fpromise::barrier barrier;
for (auto it = tests.begin(); it != tests.end(); it++) {
auto invocation = std::move(*it);
std::string test_case_name;
if (invocation.has_name()) {
test_case_name = invocation.name();
}
zx::socket out, err, out_client, err_client;
auto status = zx::socket::create(0, &out, &out_client);
if (status != ZX_OK) {
FX_LOGS(FATAL) << "cannot create socket: " << zx_status_get_string(status);
}
status = zx::socket::create(0, &err, &err_client);
if (status != ZX_OK) {
FX_LOGS(FATAL) << "cannot create socket: " << zx_status_get_string(status);
}
fidl::InterfacePtr<fuchsia::test::CaseListener> case_listener;
fuchsia::test::StdHandles std_handles;
std_handles.set_err(std::move(err_client));
std_handles.set_out(std::move(out_client));
listener_proxy->OnTestCaseStarted(std::move(invocation), std::move(std_handles),
case_listener.NewRequest());
if (test_case_name != kTestCaseName) {
std::string msg = fxl::StringPrintf("Invalid test case, expected: %s, got: %s\n",
kTestCaseName, test_case_name.c_str());
err.write(0, msg.c_str(), msg.length(), nullptr);
fuchsia::test::Result result;
result.set_status(fuchsia::test::Status::FAILED);
case_listener->Finished(std::move(result));
} else {
auto promise = RunTest(std::move(out), std::move(err), args, std::move(case_listener))
.wrap_with(barrier);
executor_.schedule_task(std::move(promise));
}
}
auto sync_promise = fpromise::make_promise([listener_proxy = std::move(listener_proxy)] {
FX_LOGS(INFO) << "Sending OnFinished for legacy tests";
listener_proxy->OnFinished();
});
executor_.schedule_task(barrier.sync().and_then(std::move(sync_promise)));
}
fpromise::promise<> Suite::RunTest(zx::socket out, zx::socket err,
const std::vector<std::string>& arguments,
fidl::InterfacePtr<fuchsia::test::CaseListener> case_listener) {
sys::testing::EnvironmentServices::ParentOverrides parent_overrides;
parent_overrides.debug_data_publisher_service_ =
std::make_shared<vfs::Service>([namespace_services = test_component_svc_](
zx::channel channel, async_dispatcher_t* /*unused*/) {
namespace_services->Connect(fuchsia::debugdata::Publisher::Name_, std::move(channel));
});
auto test_env_services = sys::testing::EnvironmentServices::CreateWithParentOverrides(
parent_env_, std::move(parent_overrides));
const auto& services = test_metadata_->services();
// Add these services to the environment if they are not injected and test
// doesn't request sys version of them.
std::set<std::string> services_to_add = {fuchsia::logger::LogSink::Name_,
fuchsia::logger::Log::Name_,
fuchsia::diagnostics::ArchiveAccessor::Name_};
for (auto& service : services) {
fuchsia::sys::LaunchInfo info;
auto status = service.second.Clone(&info);
if (status != ZX_OK) {
FX_LOGS(WARNING) << "Cannot clone launch info: " << _zx_status_get_string(status);
send_failure(case_listener);
return fpromise::make_ok_promise();
}
test_env_services->AddServiceWithLaunchInfo(std::move(info), service.first);
// remove from the set if found.
services_to_add.erase(service.first);
}
auto& requested_system_services = test_metadata_->system_services();
for (auto& service : requested_system_services) {
// remove from the set if found.
services_to_add.erase(service);
test_env_services->AllowParentService(service);
}
// Compute a common random suffix for the environments created to run the test.
uint32_t env_rand_suffix;
zx_cprng_draw(&env_rand_suffix, sizeof(env_rand_suffix));
for (const auto& service : services_to_add) {
test_env_services->AddService<void>(
[namespace_services = test_component_svc_, service](fidl::InterfaceRequest<void> request) {
namespace_services->Connect(service, request.TakeChannel());
},
service);
}
fuchsia::sys::EnvironmentOptions env_opt;
std::string env_label = fxl::StringPrintf("%s%08x", kEnvPrefix, env_rand_suffix);
env_opt.delete_storage_on_death = true;
auto enclosing_env = sys::testing::EnclosingEnvironment::Create(
env_label, parent_env_, std::move(test_env_services), env_opt);
auto launcher = enclosing_env->launcher_ptr();
auto info = fuchsia::sys::LaunchInfo{.url = legacy_url_, .arguments = arguments};
auto out_collector =
AddOutputFileDescriptor(STDOUT_FILENO, std::move(out), dispatcher_, &info.out);
auto err_collector =
AddOutputFileDescriptor(STDERR_FILENO, std::move(err), dispatcher_, &info.err);
auto svc = sys::ServiceDirectory::CreateWithRequest(&info.directory_request);
fuchsia::sys::ComponentControllerPtr contoller;
launcher->CreateComponent(std::move(info), contoller.NewRequest());
auto test_component = std::make_unique<run::Component>(
std::move(out_collector), std::move(err_collector), std::move(contoller), std::move(svc));
fpromise::bridge<> bridge;
test_component->controller().events().OnTerminated =
[this, url = legacy_url_, enclosing_env = std::move(enclosing_env),
completer = std::move(bridge.completer), case_listener = std::move(case_listener),
this_ptr = test_component.get()](
int64_t return_code, fuchsia::sys::TerminationReason termination_reason) mutable {
if (termination_reason != fuchsia::sys::TerminationReason::EXITED) {
FX_LOGS(WARNING) << "Test " << url << " failed with "
<< sys::HumanReadableTerminationReason(termination_reason);
}
FX_LOGS(INFO) << "Legacy test exited with return code " << return_code
<< ", collecting stdout";
auto status =
return_code == 0 ? fuchsia::test::Status::PASSED : fuchsia::test::Status::FAILED;
auto output_promise = this_ptr->SignalWhenOutputCollected();
FX_LOGS(INFO) << "Killing environment for legacy test";
fpromise::bridge<> bridge;
enclosing_env->Kill(bridge.completer.bind());
auto promise = bridge.consumer.promise().and_then(
[enclosing_env = std::move(enclosing_env), case_listener = std::move(case_listener),
status = status, completer = std::move(completer)]() mutable {
fuchsia::test::Result result;
result.set_status(status);
FX_LOGS(INFO) << "Sending finished event for legacy tests";
case_listener->Finished(std::move(result));
completer.complete_ok();
});
this->executor_.schedule_task(output_promise.and_then([this, this_ptr]() {
FX_LOGS(INFO) << "Done collecting standard output for legacy tests";
RemoveComponent(this_ptr);
}));
this->executor_.schedule_task(std::move(promise));
};
test_components_->emplace(test_component.get(), std::move(test_component));
return bridge.consumer.promise();
}
std::unique_ptr<run::Component> Suite::RemoveComponent(run::Component* ptr) {
if (!test_components_) {
return nullptr;
}
auto it = test_components_->find(ptr);
std::unique_ptr<run::Component> test_component_ptr;
if (it != test_components_->end()) {
test_component_ptr = std::move(it->second);
test_components_->erase(it);
}
return test_component_ptr;
}