| // 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<llcpp::fuchsia::shell::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(); |
| } |
| |
| llcpp::fuchsia::shell::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) { |
| std::string msg; |
| bool done = false; |
| enum Errs : zx_status_t { kNoContext = 1, kNoResult, kWrongAction }; |
| llcpp::fuchsia::shell::Shell::EventHandlers handlers; |
| handlers.on_error = [this, &msg, &done, action]( |
| llcpp::fuchsia::shell::Shell::OnErrorResponse* message) -> zx_status_t { |
| if (action == kError) { |
| done = true; |
| } |
| if (message->context_id == 0) { |
| global_error_stream_ << std::string(message->error_message.data(), |
| message->error_message.size()) |
| << "\n"; |
| } else { |
| InterpreterTestContext* context = GetContext(message->context_id); |
| if (context == nullptr) { |
| msg = "context == nullptr in on_error"; |
| return kNoContext; |
| } |
| for (const auto& location : message->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(message->error_message.data(), |
| message->error_message.size()) |
| << "\n"; |
| } |
| return ZX_OK; |
| }; |
| |
| handlers.on_dump_done = |
| [this, &done, &msg, |
| action](llcpp::fuchsia::shell::Shell::OnDumpDoneResponse* message) -> zx_status_t { |
| if (action == kDump) { |
| done = true; |
| } |
| InterpreterTestContext* context = GetContext(message->context_id); |
| if (context == nullptr) { |
| msg = "context == nullptr in on_dump_done"; |
| return kNoContext; |
| } |
| return ZX_OK; |
| }; |
| |
| handlers.on_execution_done = |
| [this, &msg, &done, |
| action](llcpp::fuchsia::shell::Shell::OnExecutionDoneResponse* message) -> zx_status_t { |
| if (action != kExecute) { |
| msg = "Expected action: kExecute was: " + std::to_string(action); |
| return kWrongAction; |
| } |
| done = true; |
| |
| InterpreterTestContext* context = GetContext(message->context_id); |
| if (context == nullptr) { |
| msg = "context == nullptr in on_execution_done"; |
| return kNoContext; |
| } |
| context->result = message->result; |
| return ZX_OK; |
| }; |
| |
| handlers.on_text_result = |
| [this, &msg, &done, |
| action](llcpp::fuchsia::shell::Shell::OnTextResultResponse* message) -> zx_status_t { |
| if (action == kTextResult) { |
| done = true; |
| } |
| InterpreterTestContext* context = GetContext(message->context_id); |
| if (context == nullptr) { |
| msg = "context == nullptr in on_text_result"; |
| return kNoContext; |
| } |
| std::string result_string(message->result.data(), message->result.size()); |
| if (last_text_result_partial_) { |
| if (text_results_.empty()) { |
| msg = "text results empty"; |
| return kNoResult; |
| } |
| text_results_.back() += result_string; |
| } else { |
| text_results_.emplace_back(std::move(result_string)); |
| } |
| last_text_result_partial_ = message->partial_result; |
| return ZX_OK; |
| }; |
| |
| handlers.on_result = |
| [this, &msg](llcpp::fuchsia::shell::Shell::OnResultResponse* message) -> zx_status_t { |
| InterpreterTestContext* context = GetContext(message->context_id); |
| if (context == nullptr) { |
| msg = "context == nullptr in on_text_result"; |
| return kNoContext; |
| } |
| if (message->partial_result) { |
| msg = " partial results not supported"; |
| return kNoResult; |
| } |
| shell::common::DeserializeResult deserialize; |
| results_.emplace_back(deserialize.Deserialize(message->nodes)); |
| return ZX_OK; |
| }; |
| while (!done) { |
| ::fidl::Result result = shell_->HandleEvents(handlers); |
| ASSERT_TRUE(result.ok()) << 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() { |
| zx_handle_t client_ch; |
| zx_handle_t server_ch; |
| zx_channel_create(0, &client_ch, &server_ch); |
| zx::channel client_channel(client_ch); |
| shell_ = std::make_unique<llcpp::fuchsia::shell::Shell::SyncClient>(std::move(client_channel)); |
| |
| // 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() = ""; |
| |
| zx::channel server_channel(server_ch); |
| // Creates a new connection to the server. |
| ASSERT_EQ(ZX_OK, shell_provider_->Connect("fuchsia.shell.Shell", std::move(server_channel))); |
| } |