blob: 7eb0c51efce009a89d8a00f3365813006cb3b941 [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 <cstdio>
#include <cstdlib>
#include <limits>
#include <string>
#include <vector>
#include <src/lib/fxl/log_settings_command_line.h>
#include <src/lib/fxl/logging.h>
#include <src/lib/fxl/strings/string_printf.h>
#include <lib/sys/cpp/service_directory.h>
#include <zircon/processargs.h>
#include <zircon/syscalls.h>
#include "garnet/lib/debugger_utils/jobs.h"
#include "garnet/lib/debugger_utils/sysinfo.h"
#include "garnet/lib/debugger_utils/util.h"
#include "gtest/gtest.h"
#include "test_server.h"
namespace inferior_control {
TestServer::TestServer()
: Server(debugger_utils::GetRootJob(), debugger_utils::GetDefaultJob(),
sys::ServiceDirectory::CreateFromNamespace()) {
}
void TestServer::SetUp() {
ASSERT_TRUE(exception_port_.Run());
exception_port_started_ = true;
}
void TestServer::TearDown() {
// Before we close the exception port, make sure we've detached.
Process* inferior = current_process();
if (inferior && inferior->IsAttached()) {
inferior->Kill();
inferior->Detach();
}
if (exception_port_started_) {
// Tell the exception port to quit and wait for it to finish.
exception_port_.Quit();
exception_port_started_ = false;
}
EXPECT_TRUE(run_status_);
}
bool TestServer::Run() {
// Start the main loop.
zx_status_t status = message_loop_.Run();
FXL_LOG(INFO) << "Main loop exited, status "
<< debugger_utils::ZxErrorString(status);
// |run_status_| is checked by TearDown().
return true;
}
bool TestServer::SetupInferior(const std::vector<std::string>& argv,
zx::channel channel) {
FXL_LOG(INFO) << "Initializing program: " << argv[0];
auto inferior = new Process(this, this);
// We take over ownership of |inferior| here.
set_current_process(inferior);
// Transfer our log settings to the inferior.
std::vector<std::string> inferior_argv{argv};
for (auto arg : fxl::LogSettingsToArgv(fxl::GetLogSettings())) {
inferior_argv.push_back(arg);
}
std::unique_ptr<process::ProcessBuilder> builder;
if (!CreateProcessViaBuilder(argv[0], inferior_argv, &builder)) {
FXL_LOG(ERROR) << "Unable to create process builder";
return false;
}
builder->CloneAll();
if (channel) {
builder->AddHandle(PA_HND(PA_USER0, 0), std::move(channel));
}
if (!inferior->InitializeFromBuilder(std::move(builder))) {
FXL_LOG(ERROR) << "Unable to initialize inferior";
return false;
}
return true;
}
bool TestServer::RunHelperProgram() {
Process* inferior = current_process();
FXL_LOG(INFO) << "Starting program: " << inferior->GetName();
FXL_DCHECK(!inferior->IsLive());
if (!inferior->Start()) {
FXL_LOG(ERROR) << "failed to start process";
return false;
}
FXL_DCHECK(inferior->IsLive());
return true;
}
// This method is intended to be called at the end of tests.
// There are several things we check for successful exit, and it's easier
// to have them all in one place. Note that we use gtest macros instead of
// FXL_DCHECK because these all verify test conditions.
bool TestServer::TestSuccessfulExit() {
auto inferior = current_process();
if (inferior == nullptr) {
FXL_LOG(ERROR) << "inferior == nullptr";
return false;
}
if (inferior->IsAttached()) {
// The program should have terminated some how, in which case we would
// have detached. So it's likely the program is still running.
FXL_LOG(ERROR) << "inferior still attached";
return false;
}
if (inferior->IsLive()) {
FXL_LOG(ERROR) << "inferior still live";
return false;
}
if (!inferior->return_code_is_set() || inferior->return_code() != 0) {
FXL_LOG(ERROR) << "inferior didn't cleanly exit";
return false;
}
return true;
}
// This method is intended to be called at the end of tests.
// There are several things we check for successful exit, and it's easier
// to have them all in one place. Note that we use gtest macros instead of
// FXL_DCHECK because these all verify test conditions.
bool TestServer::TestFailureExit() {
auto inferior = current_process();
if (inferior == nullptr) {
FXL_LOG(ERROR) << "inferior == nullptr";
return false;
}
if (inferior->IsAttached()) {
// The program should have terminated some how, in which case we would
// have detached. So it's likely the program is still running.
FXL_LOG(ERROR) << "inferior still attached";
return false;
}
if (inferior->IsLive()) {
FXL_LOG(ERROR) << "inferior still live";
return false;
}
if (inferior->return_code_is_set() && inferior->return_code() == 0) {
FXL_LOG(ERROR) << "inferior successfully exited";
return false;
}
return true;
}
void TestServer::OnThreadStarting(Process* process, Thread* thread,
zx_handle_t eport,
const zx_exception_context_t& context) {
FXL_DCHECK(process);
FXL_DCHECK(thread);
switch (process->state()) {
case Process::State::kStarting:
case Process::State::kRunning:
break;
default:
FXL_DCHECK(false);
}
thread->ResumeFromException(eport);
}
void TestServer::OnThreadExiting(Process* process, Thread* thread,
zx_handle_t eport,
const zx_exception_context_t& context) {
FXL_DCHECK(process);
FXL_DCHECK(thread);
// We still have to "resume" the thread so that the o/s will complete the
// termination of the thread.
thread->ResumeForExit(eport);
}
void TestServer::OnProcessTermination(Process* process) {
FXL_DCHECK(process);
FXL_LOG(INFO) << fxl::StringPrintf("Process %s is gone, rc %d",
process->GetName().c_str(),
process->return_code());
// Process is gone, exit main loop.
PostQuitMessageLoop(true);
}
void TestServer::OnArchitecturalException(
Process* process, Thread* thread, zx_handle_t eport, zx_excp_type_t type,
const zx_exception_context_t& context) {
FXL_DCHECK(process);
FXL_DCHECK(thread);
PostQuitMessageLoop(true);
}
void TestServer::OnSyntheticException(
Process* process, Thread* thread, zx_handle_t eport, zx_excp_type_t type,
const zx_exception_context_t& context) {
FXL_DCHECK(process);
FXL_DCHECK(thread);
PostQuitMessageLoop(true);
}
} // namespace inferior_control