blob: 9268367cb8d845f433f4ef4614857c2927856227 [file] [log] [blame]
// Copyright 2020 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 "src/developer/shell/interpreter/test/interpreter_test.h"
#include <lib/fdio/directory.h>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
#include "fuchsia/shell/llcpp/fidl.h"
#include "fuchsia/sys/cpp/fidl.h"
#include "lib/async-loop/default.h"
#include "zircon/status.h"
// Adds an object to the builder with the names, values, and types as given in parallel arrays.
shell::console::AstBuilder::NodePair AddObject(
shell::console::AstBuilder& builder, std::vector<std::string>& names,
std::vector<shell::console::AstBuilder::NodeId>& values,
std::vector<fuchsia_shell::wire::ShellType>&& types) {
EXPECT_EQ(names.size(), values.size())
<< "Test incorrect - mismatch in keys and values for constructing object";
EXPECT_EQ(names.size(), types.size())
<< "Test incorrect - mismatch in fields and types for constructing object";
builder.OpenObject();
for (size_t i = 0; i < names.size(); i++) {
builder.AddField(names[i], values[i], std::move(types[i]));
}
return builder.CloseObject();
}
fuchsia_shell::wire::ExecuteResult InterpreterTestContext::GetResult() const {
std::string string = error_stream.str();
if (!string.empty()) {
std::cout << string;
}
return result;
}
InterpreterTest::InterpreterTest()
: loop_(&kAsyncLoopConfigAttachToCurrentThread),
context_(sys::ComponentContext::CreateAndServeOutgoingDirectory()) {
::fidl::InterfaceHandle<fuchsia::io::Directory> directory;
fuchsia::sys::LaunchInfo launch_info;
launch_info.url = "fuchsia-pkg://fuchsia.com/shell_server#meta/shell_server.cmx";
launch_info.directory_request = directory.NewRequest().TakeChannel();
fuchsia::sys::LauncherPtr launcher;
context_->svc()->Connect(launcher.NewRequest());
launcher->CreateComponent(std::move(launch_info), controller_.NewRequest());
shell_provider_ = std::make_unique<sys::ServiceDirectory>(std::move(directory));
}
void InterpreterTest::Finish(FinishAction action) {
std::vector<std::string> no_errors;
Finish(action, no_errors);
}
void InterpreterTest::Finish(FinishAction action, const std::vector<std::string>& expected_errors) {
Run(action);
// Shutdown the interpreter (that also closes the channel => we can't use it anymore after this
// call).
auto errors = shell().Shutdown();
// Checks if the errors are what we expected.
bool ok = true;
if (expected_errors.size() != errors->errors.count()) {
ok = false;
} else {
for (size_t i = 0; i < expected_errors.size(); ++i) {
if (expected_errors[i] != std::string(errors->errors[i].data(), errors->errors[i].size())) {
ok = false;
break;
}
}
}
if (!ok) {
std::cout << "Shutdown incorrect\n";
if (!expected_errors.empty()) {
std::cout << "Expected:\n";
for (const auto& error : expected_errors) {
std::cout << " " << error << '\n';
}
if (errors->errors.empty()) {
std::cout << "Got no error\n";
} else {
std::cout << "Got:\n";
}
}
for (const auto& error : errors->errors) {
std::cout << " " << std::string(error.data(), error.size()) << '\n';
}
ASSERT_TRUE(ok);
}
if (action != kError) {
std::string global_errors = global_error_stream_.str();
if (!global_errors.empty()) {
std::cout << global_errors;
}
}
}
void InterpreterTest::Run(FinishAction action) {
class EventHandler : public fidl::WireSyncEventHandler<fuchsia_shell::Shell> {
public:
EventHandler(InterpreterTest* test, FinishAction action) : test_(test), action_(action) {}
const std::string& msg() const { return msg_; }
bool done() const { return done_; }
bool ok() const { return ok_; }
void OnError(fidl::WireResponse<fuchsia_shell::Shell::OnError>* event) override {
if (action_ == kError) {
done_ = true;
}
if (event->context_id == 0) {
test_->global_error_stream_
<< std::string(event->error_message.data(), event->error_message.size()) << "\n";
} else {
InterpreterTestContext* context = test_->GetContext(event->context_id);
if (context == nullptr) {
msg_ = "context == nullptr in on_error";
ok_ = false;
} else {
for (const auto& location : event->locations) {
if (location.has_node_id()) {
context->error_stream << "node " << location.node_id().file_id << ':'
<< location.node_id().node_id << ' ';
}
}
context->error_stream << std::string(event->error_message.data(),
event->error_message.size())
<< "\n";
}
}
}
void OnDumpDone(fidl::WireResponse<fuchsia_shell::Shell::OnDumpDone>* event) override {
if (action_ == kDump) {
done_ = true;
}
InterpreterTestContext* context = test_->GetContext(event->context_id);
if (context == nullptr) {
msg_ = "context == nullptr in on_dump_done";
ok_ = false;
}
}
void OnExecutionDone(
fidl::WireResponse<fuchsia_shell::Shell::OnExecutionDone>* event) override {
if (action_ != kExecute) {
msg_ = "Expected action: kExecute was: " + std::to_string(action_);
ok_ = false;
return;
}
done_ = true;
InterpreterTestContext* context = test_->GetContext(event->context_id);
if (context == nullptr) {
msg_ = "context == nullptr in on_execution_done";
ok_ = false;
return;
}
context->result = event->result;
}
void OnTextResult(fidl::WireResponse<fuchsia_shell::Shell::OnTextResult>* event) override {
if (action_ == kTextResult) {
done_ = true;
}
InterpreterTestContext* context = test_->GetContext(event->context_id);
if (context == nullptr) {
msg_ = "context == nullptr in on_text_result";
ok_ = false;
return;
}
std::string result_string(event->result.data(), event->result.size());
if (test_->last_text_result_partial_) {
if (test_->text_results_.empty()) {
msg_ = "text results empty";
ok_ = false;
return;
}
test_->text_results_.back() += result_string;
} else {
test_->text_results_.emplace_back(std::move(result_string));
}
test_->last_text_result_partial_ = event->partial_result;
}
void OnResult(fidl::WireResponse<fuchsia_shell::Shell::OnResult>* event) override {
InterpreterTestContext* context = test_->GetContext(event->context_id);
if (context == nullptr) {
msg_ = "context == nullptr in on_text_result";
ok_ = false;
return;
}
if (event->partial_result) {
msg_ = " partial results not supported";
ok_ = false;
return;
}
shell::common::DeserializeResult deserialize;
test_->results_.emplace_back(deserialize.Deserialize(event->nodes));
}
zx_status_t Unknown() override { return ZX_ERR_NOT_SUPPORTED; }
private:
InterpreterTest* const test_;
const FinishAction action_;
std::string msg_;
bool done_ = false;
bool ok_ = true;
};
EventHandler event_handler(this, action);
while (!event_handler.done()) {
::fidl::Result result = shell_->HandleOneEvent(event_handler);
ASSERT_TRUE(result.ok() && event_handler.ok()) << event_handler.msg();
}
};
InterpreterTestContext* InterpreterTest::CreateContext() {
uint64_t id = ++last_context_id_;
auto context = std::make_unique<InterpreterTestContext>(id);
auto result = context.get();
contexts_.emplace(id, std::move(context));
return result;
}
InterpreterTestContext* InterpreterTest::GetContext(uint64_t context_id) {
auto result = contexts_.find(context_id);
if (result == contexts_.end()) {
return nullptr;
}
return result->second.get();
}
void InterpreterTest::SetUp() {
auto endpoints = fidl::CreateEndpoints<fuchsia_shell::Shell>();
shell_ =
std::make_unique<fidl::WireSyncClient<fuchsia_shell::Shell>>(std::move(endpoints->client));
// Reset context ids.
last_context_id_ = 0;
// Resets the global error stream for the test (to be able to run multiple tests).
global_error_stream_.str() = "";
// Creates a new connection to the server.
ASSERT_EQ(ZX_OK, shell_provider_->Connect("fuchsia.shell.Shell",
std::move(endpoints->server.channel())));
}