| // 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 "src/developer/debug/debug_agent/zircon_component_manager.h" |
| |
| #include <fidl/fuchsia.component/cpp/fidl.h> |
| #include <fidl/fuchsia.io/cpp/fidl.h> |
| #include <fidl/fuchsia.sys2/cpp/fidl.h> |
| #include <fidl/fuchsia.test.manager/cpp/fidl.h> |
| #include <lib/async/default.h> |
| #include <lib/component/incoming/cpp/protocol.h> |
| #include <lib/fit/defer.h> |
| #include <lib/syslog/cpp/macros.h> |
| #include <unistd.h> |
| #include <zircon/errors.h> |
| #include <zircon/processargs.h> |
| #include <zircon/types.h> |
| |
| #include <cstdint> |
| #include <memory> |
| #include <optional> |
| #include <string> |
| #include <utility> |
| |
| #include "src/developer/debug/debug_agent/debug_agent.h" |
| #include "src/developer/debug/debug_agent/debugged_process.h" |
| #include "src/developer/debug/debug_agent/filter.h" |
| #include "src/developer/debug/debug_agent/stdio_handles.h" |
| #include "src/developer/debug/debug_agent/test_realm.h" |
| #include "src/developer/debug/ipc/records.h" |
| #include "src/developer/debug/shared/logging/file_line_function.h" |
| #include "src/developer/debug/shared/logging/logging.h" |
| #include "src/developer/debug/shared/message_loop.h" |
| #include "src/developer/debug/shared/status.h" |
| #include "src/lib/diagnostics/accessor2logger/log_message.h" |
| #include "src/lib/fxl/memory/ref_counted.h" |
| #include "src/lib/fxl/memory/ref_ptr.h" |
| |
| namespace debug_agent { |
| |
| namespace { |
| |
| // Maximum time we wait for reading "elf/job_id" in the runtime directory. |
| constexpr uint64_t kMaxWaitMsForJobId = 1000; |
| |
| // Helper to simplify request pipelining. |
| template <typename Protocol> |
| fidl::ServerEnd<Protocol> CreateEndpointsAndBind(fidl::Client<Protocol>& client) { |
| auto [client_end, server_end] = fidl::Endpoints<Protocol>::Create(); |
| client.Bind(std::move(client_end), async_get_default_dispatcher()); |
| return std::move(server_end); |
| } |
| |
| // Read the content of "elf/job_id" in the runtime directory of an ELF component. |
| // |
| // |callback| will be issued with ZX_KOID_INVALID if there's any error. |
| // |moniker| is only used for error logging. |
| void ReadElfJobId(fidl::Client<fuchsia_io::Directory> runtime_dir, const std::string& moniker, |
| fit::callback<void(zx_koid_t)> cb) { |
| fidl::Client<fuchsia_io::File> job_id_file; |
| auto open_res = runtime_dir->Open( |
| {fuchsia_io::OpenFlags::kRightReadable, |
| {}, |
| "elf/job_id", |
| fidl::ServerEnd<fuchsia_io::Node>(CreateEndpointsAndBind(job_id_file).TakeChannel())}); |
| if (!open_res.is_ok()) { |
| LOGS(Error) << "Failed to open elf/job_id for " << moniker; |
| return cb(ZX_KOID_INVALID); |
| } |
| job_id_file->Read(fuchsia_io::kMaxTransferSize) |
| .Then([cb = cb.share(), moniker](fidl::Result<fuchsia_io::File::Read>& res) mutable { |
| if (!cb) { |
| return; |
| } |
| if (!res.is_ok()) { |
| if (res.error_value().is_framework_error() && |
| res.error_value().framework_error().is_peer_closed()) { |
| // Runtime directory is not served, mute the log. |
| } else { |
| LOGS(Warn) << "Failed to read elf/job_id for " << moniker << ": " << res.error_value(); |
| } |
| return cb(ZX_KOID_INVALID); |
| } |
| std::string job_id_str(reinterpret_cast<const char*>(res->data().data()), |
| res->data().size()); |
| // We use std::strtoull here because std::stoull is not exception-safe. |
| char* end; |
| zx_koid_t job_id = std::strtoull(job_id_str.c_str(), &end, 10); |
| if (end != job_id_str.c_str() + job_id_str.size()) { |
| LOGS(Warn) << "Invalid elf/job_id for " << moniker << ": " << job_id_str; |
| return cb(ZX_KOID_INVALID); |
| } |
| cb(job_id); |
| }); |
| debug::MessageLoop::Current()->PostTimer( |
| FROM_HERE, kMaxWaitMsForJobId, |
| [cb = std::move(cb), file = std::move(job_id_file), moniker]() mutable { |
| if (cb) { |
| LOGS(Warn) << "Timeout reading elf/job_id for " << moniker; |
| cb(ZX_KOID_INVALID); |
| } |
| }); |
| } |
| |
| std::string SeverityToString(int32_t severity) { |
| switch (severity) { |
| case fuchsia_logging::LOG_TRACE: |
| return "TRACE"; |
| case fuchsia_logging::LOG_DEBUG: |
| return "DEBUG"; |
| case fuchsia_logging::LOG_INFO: |
| return "INFO"; |
| case fuchsia_logging::LOG_WARNING: |
| return "WARNING"; |
| case fuchsia_logging::LOG_ERROR: |
| return "ERROR"; |
| case fuchsia_logging::LOG_FATAL: |
| return "FATAL"; |
| } |
| return "INVALID"; |
| } |
| |
| void SendLogs(DebugAgent* debug_agent, std::vector<fuchsia::diagnostics::FormattedContent> batch) { |
| debug_ipc::NotifyIO notify; |
| notify.timestamp = GetNowTimestamp(); |
| notify.process_koid = 0; |
| notify.type = debug_ipc::NotifyIO::Type::kStderr; |
| |
| for (auto& content : batch) { |
| auto res = |
| diagnostics::accessor2logger::ConvertFormattedContentToLogMessages(std::move(content)); |
| if (res.is_error()) { |
| LOGS(Warn) << "Failed to parse log: " << res.error(); |
| } else { |
| for (auto& msg : res.value()) { |
| if (msg.is_error()) { |
| LOGS(Warn) << "Failed to parse log: " << msg.error(); |
| } else { |
| notify.data += SeverityToString(msg.value().severity) + ": "; |
| notify.data.insert(notify.data.end(), msg.value().msg.begin(), msg.value().msg.end()); |
| notify.data.push_back('\n'); |
| } |
| } |
| } |
| } |
| |
| debug_agent->SendNotification(notify); |
| } |
| |
| } // namespace |
| |
| void ZirconComponentManager::GetNextComponentEvent() { |
| event_stream_client_->GetNext().Then([this](auto& result) { |
| if (result.is_error()) { |
| LOGS(Error) << "Failed to GetNextComponentEvent: " << result.error_value(); |
| } else { |
| for (auto& event : result->events()) { |
| OnComponentEvent(std::move(event)); |
| } |
| GetNextComponentEvent(); |
| } |
| }); |
| } |
| |
| ZirconComponentManager::ZirconComponentManager(SystemInterface* system_interface) |
| : ComponentManager(system_interface), weak_factory_(this) { |
| // 1. Subscribe to "debug_started" and "stopped" events. |
| auto event_stream_res = component::Connect<fuchsia_component::EventStream>(); |
| if (!event_stream_res.is_ok()) { |
| LOGS(Error) << "Failed to connect to fuchsia.component.EventStream: " |
| << event_stream_res.status_string(); |
| } else { |
| fidl::SyncClient client(std::move(*event_stream_res)); |
| auto res = client->WaitForReady(); |
| if (!res.is_ok()) { |
| LOGS(Error) << "Failed to WaitForReady: " << res.error_value(); |
| } else { |
| event_stream_client_.Bind(client.TakeClientEnd(), async_get_default_dispatcher()); |
| GetNextComponentEvent(); |
| } |
| } |
| |
| // 2. List existing components via fuchsia.sys2.RealmQuery. |
| auto realm_query_res = |
| component::Connect<fuchsia_sys2::RealmQuery>("/svc/fuchsia.sys2.RealmQuery.root"); |
| if (!realm_query_res.is_ok()) { |
| LOGS(Error) << "Failed to connect to fuchsia.sys2.RealmQuery.root: " |
| << realm_query_res.status_string(); |
| return; |
| } |
| fidl::SyncClient realm_query(std::move(*realm_query_res)); |
| |
| auto all_instances_res = realm_query->GetAllInstances(); |
| if (!all_instances_res.is_ok()) { |
| LOGS(Error) << "Failed to GetAllInstances: " << all_instances_res.error_value(); |
| return; |
| } |
| |
| fidl::SyncClient instance_it(std::move(all_instances_res->iterator())); |
| auto deferred_ready = |
| std::make_shared<fit::deferred_callback>([weak_this = weak_factory_.GetWeakPtr()] { |
| if (weak_this && weak_this->ready_callback_) |
| weak_this->ready_callback_(); |
| }); |
| while (true) { |
| auto instances_res = instance_it->Next(); |
| if (!instances_res.is_ok() || instances_res->infos().empty()) { |
| break; |
| } |
| for (auto& instance : instances_res->infos()) { |
| if (!instance.moniker() || instance.moniker()->empty() || !instance.url() || |
| instance.url()->empty()) { |
| continue; |
| } |
| if (!instance.resolved_info() || !instance.resolved_info()->execution_info()) { |
| // The component is not running. |
| continue; |
| } |
| std::string moniker = *instance.moniker(); |
| fidl::Client<fuchsia_io::Directory> runtime_dir; |
| auto open_res = realm_query->Open( |
| {moniker, |
| fuchsia_sys2::OpenDirType::kRuntimeDir, |
| fuchsia_io::OpenFlags::kRightReadable, |
| {}, |
| ".", |
| fidl::ServerEnd<fuchsia_io::Node>(CreateEndpointsAndBind(runtime_dir).TakeChannel())}); |
| if (!open_res.is_ok()) { |
| continue; |
| } |
| ReadElfJobId(std::move(runtime_dir), moniker, |
| [weak_this = weak_factory_.GetWeakPtr(), moniker, url = *instance.url(), |
| deferred_ready](zx_koid_t job_id) { |
| if (weak_this && job_id != ZX_KOID_INVALID) { |
| weak_this->running_component_info_.insert(std::make_pair( |
| job_id, debug_ipc::ComponentInfo{.moniker = moniker, .url = url})); |
| } |
| }); |
| } |
| } |
| } |
| |
| void ZirconComponentManager::SetReadyCallback(fit::callback<void()> callback) { |
| if (ready_callback_) { |
| ready_callback_ = std::move(callback); |
| } else { |
| debug::MessageLoop::Current()->PostTask(FROM_HERE, |
| [cb = std::move(callback)]() mutable { cb(); }); |
| } |
| } |
| |
| void ZirconComponentManager::OnComponentEvent(fuchsia_component::Event event) { |
| if (!event.payload() || !event.header() || !event.header()->event_type() || |
| !event.header()->component_url() || !event.header()->moniker() || |
| event.header()->moniker()->empty()) { |
| if (event.header()) { |
| DEBUG_LOG(Process) << "Did not process EventType = " |
| << static_cast<int>(*event.header()->event_type()); |
| } |
| return; |
| } |
| |
| const auto& moniker = *event.header()->moniker(); |
| const auto& url = *event.header()->component_url(); |
| switch (*event.header()->event_type()) { |
| case fuchsia_component::EventType::kDiscovered: { |
| if (debug_agent_) { |
| debug_agent_->OnComponentDiscovered(moniker, url); |
| } |
| break; |
| } |
| case fuchsia_component::EventType::kDebugStarted: |
| if (debug_agent_) { |
| debug_agent_->OnComponentStarted(moniker, url); |
| } |
| if (event.payload()->debug_started() && event.payload()->debug_started()->runtime_dir()) { |
| auto& runtime_dir = *event.payload()->debug_started()->runtime_dir(); |
| auto& break_on_start = *event.payload()->debug_started()->break_on_start(); |
| ReadElfJobId({std::move(runtime_dir), async_get_default_dispatcher()}, moniker, |
| [weak_this = weak_factory_.GetWeakPtr(), moniker, url, |
| break_on_start = std::move(break_on_start)](zx_koid_t job_id) mutable { |
| if (weak_this && job_id != ZX_KOID_INVALID) { |
| weak_this->running_component_info_.emplace(std::make_pair( |
| job_id, debug_ipc::ComponentInfo{.moniker = moniker, .url = url})); |
| DEBUG_LOG(Process) << "Component started job_id=" << job_id |
| << " moniker=" << moniker << " url=" << url; |
| } |
| // Explicitly reset break_on_start to indicate the component manager that |
| // processes can be spawned. |
| break_on_start.reset(); |
| }); |
| } |
| break; |
| case fuchsia_component::EventType::kStopped: { |
| if (debug_agent_) { |
| debug_agent_->OnComponentExited(moniker, *event.header()->component_url()); |
| } |
| for (auto it = running_component_info_.begin(); it != running_component_info_.end(); it++) { |
| if (it->second.moniker == moniker) { |
| DEBUG_LOG(Process) << "Component stopped job_id=" << it->first |
| << " moniker=" << it->second.moniker << " url=" << it->second.url; |
| running_component_info_.erase(it); |
| expected_v2_components_.erase(moniker); |
| break; |
| } |
| } |
| break; |
| } |
| default: |
| FX_NOTREACHED(); |
| } |
| } |
| |
| std::vector<debug_ipc::ComponentInfo> ZirconComponentManager::FindComponentInfo( |
| zx_koid_t job_koid) const { |
| auto [start, end] = running_component_info_.equal_range(job_koid); |
| if (start == running_component_info_.end()) { |
| // Not found. |
| return {}; |
| } |
| |
| std::vector<debug_ipc::ComponentInfo> components; |
| components.reserve(std::distance(start, end)); |
| for (auto& i = start; i != end; ++i) { |
| components.push_back(i->second); |
| } |
| |
| return components; |
| } |
| |
| // We need a class to help to launch a test because the lifecycle of GetEvents callbacks |
| // are undetermined. |
| class ZirconComponentManager::TestLauncher : public fxl::RefCountedThreadSafe<TestLauncher> { |
| public: |
| // This function can only be called once. |
| debug::Status Launch(std::string url, std::optional<std::string> realm, |
| std::vector<std::string> case_filters, |
| ZirconComponentManager* component_manager, DebugAgent* debug_agent) { |
| test_url_ = std::move(url); |
| component_manager_ = component_manager->GetWeakPtr(); |
| debug_agent_ = debug_agent ? debug_agent->GetWeakPtr() : nullptr; |
| |
| if (component_manager->running_tests_info_.count(test_url_)) |
| return debug::Status("Test " + test_url_ + " is already launched"); |
| |
| auto run_builder_res = component::Connect<fuchsia_test_manager::RunBuilder>(); |
| if (!run_builder_res.is_ok()) { |
| return debug::ZxStatus(run_builder_res.error_value()); |
| } |
| fidl::SyncClient run_builder(std::move(*run_builder_res)); |
| |
| DEBUG_LOG(Process) << "Launching test url=" << test_url_; |
| |
| fuchsia_test_manager::RunOptions run_options; |
| run_options.case_filters_to_run() = std::move(case_filters); |
| if (realm) { |
| auto get_test_realm_res = GetTestRealmAndOffers(*realm); |
| if (!get_test_realm_res.is_ok()) { |
| return get_test_realm_res.error_value(); |
| } |
| |
| auto add_suite_in_realm_res = run_builder->AddSuiteInRealm( |
| {std::move(get_test_realm_res->realm), std::move(get_test_realm_res->offers), |
| get_test_realm_res->test_collection, test_url_, std::move(run_options), |
| CreateEndpointsAndBind(suite_controller_)}); |
| if (!add_suite_in_realm_res.is_ok()) { |
| return debug::ZxStatus(add_suite_in_realm_res.error_value().status(), |
| add_suite_in_realm_res.error_value().FormatDescription()); |
| } |
| } else { |
| auto add_suite_res = run_builder->AddSuite( |
| {test_url_, std::move(run_options), CreateEndpointsAndBind(suite_controller_)}); |
| if (!add_suite_res.is_ok()) { |
| return debug::ZxStatus(add_suite_res.error_value().status(), |
| add_suite_res.error_value().FormatDescription()); |
| } |
| } |
| auto build_res = run_builder->Build(CreateEndpointsAndBind(run_controller_)); |
| if (!build_res.is_ok()) { |
| return debug::ZxStatus(build_res.error_value().status(), |
| build_res.error_value().FormatDescription()); |
| } |
| run_controller_->GetEvents().Then( |
| [self = fxl::RefPtr<TestLauncher>(this)](auto& res) { self->OnRunEvents(std::move(res)); }); |
| suite_controller_->GetEvents().Then([self = fxl::RefPtr<TestLauncher>(this)](auto& res) { |
| self->OnSuiteEvents(std::move(res)); |
| }); |
| component_manager->running_tests_info_[test_url_] = {}; |
| return debug::Status(); |
| } |
| |
| ~TestLauncher() { |
| DEBUG_LOG(Process) << "Test finished url=" << test_url_; |
| if (debug_agent_) { |
| debug_agent_->OnTestComponentExited(test_url_); |
| } |
| } |
| |
| private: |
| // Stdout and stderr are in case_artifact. Logs are in suite_artifact. Others are ignored. |
| // NOTE: custom.component_moniker in suite_artifact is NOT the moniker of the test! |
| void OnSuiteEvents(fidl::Result<fuchsia_test_manager::SuiteController::GetEvents> result) { |
| if (!component_manager_ || !result.is_ok() || result->events().empty()) { |
| (void)suite_controller_.UnbindMaybeGetEndpoint(); // Otherwise run_controller won't return. |
| if (result.is_error()) |
| LOGS(Warn) << "Failed to launch test: " << result.error_value(); |
| if (component_manager_) |
| component_manager_->running_tests_info_.erase(test_url_); |
| return; |
| } |
| for (auto& event : result->events()) { |
| FX_CHECK(event.payload()); |
| if (event.payload()->case_found()) { |
| auto& test_info = component_manager_->running_tests_info_[test_url_]; |
| // Test cases should come in order. |
| FX_CHECK(test_info.case_names.size() == event.payload()->case_found()->identifier()); |
| test_info.case_names.push_back(event.payload()->case_found()->test_case_name()); |
| if (event.payload()->case_found()->test_case_name().find_first_of('.') != |
| std::string::npos) { |
| test_info.ignored_process = 1; |
| } |
| } else if (event.payload()->case_artifact()) { |
| if (auto proc = GetDebuggedProcess(event.payload()->case_artifact()->identifier())) { |
| auto& artifact = event.payload()->case_artifact()->artifact(); |
| if (artifact.stdout_()) { |
| proc->SetStdout(std::move(artifact.stdout_().value())); |
| } else if (artifact.stderr_()) { |
| proc->SetStderr(std::move(artifact.stderr_().value())); |
| } |
| } else { |
| // This usually happens when the process has terminated, e.g. |
| // - Rust test runner prints an extra message after the test finishes. |
| // - The process is killed by the debugger. |
| // |
| // Don't print anything because it's very common. |
| } |
| } else if (event.payload()->suite_artifact()) { |
| auto& artifact = event.payload()->suite_artifact()->artifact(); |
| if (artifact.log()) { |
| FX_CHECK(artifact.log()->batch()); |
| log_listener_.Bind(artifact.log()->batch().value().TakeChannel()); |
| log_listener_->GetNext( |
| [self = fxl::RefPtr<TestLauncher>(this)](auto res) { self->OnLog(std::move(res)); }); |
| } |
| } |
| } |
| suite_controller_->GetEvents().Then([self = fxl::RefPtr<TestLauncher>(this)](auto& res) { |
| self->OnSuiteEvents(std::move(res)); |
| }); |
| } |
| |
| // See the comment above |running_tests_info_| for the logic. |
| DebuggedProcess* GetDebuggedProcess(uint32_t test_identifier) { |
| auto& test_info = component_manager_->running_tests_info_[test_url_]; |
| auto& pids = test_info.pids; |
| auto proc_idx = test_identifier + test_info.ignored_process; |
| if (proc_idx < pids.size() && debug_agent_) { |
| return debug_agent_->GetDebuggedProcess(pids[proc_idx]); |
| } |
| return nullptr; |
| } |
| |
| // Unused but required by the test framework. |
| void OnRunEvents(fidl::Result<fuchsia_test_manager::RunController::GetEvents> result) { |
| if (result.is_ok() && !result->events().empty()) { |
| FX_LOGS_FIRST_N(WARNING, 1) << "Not implemented yet"; |
| run_controller_->GetEvents().Then([self = fxl::RefPtr<TestLauncher>(this)](auto& res) { |
| self->OnRunEvents(std::move(res)); |
| }); |
| } else { |
| (void)run_controller_.UnbindMaybeGetEndpoint(); |
| } |
| } |
| |
| // Handle logs. |
| void OnLog(fuchsia::diagnostics::BatchIterator_GetNext_Result result) { |
| if (result.is_response() && !result.response().batch.empty()) { |
| if (debug_agent_) { |
| SendLogs(debug_agent_.get(), std::move(result.response().batch)); |
| } |
| log_listener_->GetNext( |
| [self = fxl::RefPtr<TestLauncher>(this)](auto res) { self->OnLog(std::move(res)); }); |
| } else { |
| if (result.is_err()) |
| LOGS(Error) << "Failed to read log"; |
| log_listener_.Unbind(); // Otherwise archivist won't terminate. |
| } |
| } |
| |
| fxl::WeakPtr<DebugAgent> debug_agent_; |
| fxl::WeakPtr<ZirconComponentManager> component_manager_; |
| std::string test_url_; |
| fidl::Client<fuchsia_test_manager::RunController> run_controller_; |
| fidl::Client<fuchsia_test_manager::SuiteController> suite_controller_; |
| fuchsia::diagnostics::BatchIteratorPtr log_listener_; // accessor2logger is still using hlcpp. |
| }; |
| |
| debug::Status ZirconComponentManager::LaunchTest(std::string url, std::optional<std::string> realm, |
| std::vector<std::string> case_filters) { |
| return fxl::MakeRefCounted<TestLauncher>()->Launch(std::move(url), std::move(realm), |
| std::move(case_filters), this, debug_agent_); |
| } |
| |
| debug::Status ZirconComponentManager::LaunchComponent(std::string url) { |
| constexpr char kParentMoniker[] = "core"; |
| constexpr char kCollection[] = "ffx-laboratory"; |
| |
| // url: fuchsia-pkg://fuchsia.com/crasher#meta/cpp_crasher.cm |
| size_t name_start = url.find_last_of('/') + 1; |
| // name: cpp_crasher |
| std::string name = url.substr(name_start, url.find_last_of('.') - name_start); |
| // moniker: core/ffx-laboratory:cpp_crasher |
| std::string moniker = std::string(kParentMoniker) + "/" + kCollection + ":" + name; |
| |
| if (expected_v2_components_.count(moniker)) { |
| return debug::Status(url + " is already launched"); |
| } |
| |
| auto connect_res = component::Connect<fuchsia_sys2::LifecycleController>( |
| "/svc/fuchsia.sys2.LifecycleController.root"); |
| if (!connect_res.is_ok()) |
| return debug::ZxStatus(connect_res.error_value()); |
| fidl::SyncClient lifecycle_controller(std::move(*connect_res)); |
| |
| DEBUG_LOG(Process) << "Launching component url=" << url << " moniker=" << moniker; |
| |
| auto create_child = [&]() { |
| fuchsia_component_decl::Child child_decl; |
| child_decl.name() = name; |
| child_decl.url() = url; |
| child_decl.startup() = fuchsia_component_decl::StartupMode::kLazy; |
| return lifecycle_controller->CreateInstance( |
| {kParentMoniker, {kCollection}, std::move(child_decl), {}}); |
| }; |
| auto create_res = create_child(); |
| if (create_res.is_error() && create_res.error_value().is_domain_error() && |
| create_res.error_value().domain_error() == |
| fuchsia_sys2::CreateError::kInstanceAlreadyExists) { |
| auto destroy_res = lifecycle_controller->DestroyInstance({kParentMoniker, {name, kCollection}}); |
| if (destroy_res.is_error()) { |
| return debug::Status("Failed to destroy component " + moniker + ": " + |
| destroy_res.error_value().FormatDescription()); |
| } |
| create_res = create_child(); |
| } |
| if (create_res.is_error()) { |
| return debug::Status("Failed to create the component: " + |
| create_res.error_value().FormatDescription()); |
| } |
| |
| fidl::Client<fuchsia_component::Binder> binder_client_end; |
| auto start_res = |
| lifecycle_controller->StartInstance({moniker, CreateEndpointsAndBind(binder_client_end)}); |
| if (start_res.is_error()) { |
| return debug::Status("Failed to start the component: " + |
| start_res.error_value().FormatDescription()); |
| } |
| |
| expected_v2_components_.insert(moniker); |
| return debug::Status(); |
| } |
| |
| bool ZirconComponentManager::OnProcessStart(const ProcessHandle& process, StdioHandles* out_stdio, |
| std::string* process_name_override) { |
| for (const auto& component : ComponentManager::FindComponentInfo(process)) { |
| if (expected_v2_components_.count(component.moniker)) { |
| // It'll be erased in the stopped event. |
| return true; |
| } |
| if (auto it = running_tests_info_.find(component.url); it != running_tests_info_.end()) { |
| size_t idx = it->second.pids.size(); |
| it->second.pids.push_back(process.GetKoid()); |
| if (idx < it->second.ignored_process) { |
| return false; |
| } |
| idx -= it->second.ignored_process; |
| if (idx < it->second.case_names.size()) { |
| *process_name_override = it->second.case_names[idx]; |
| } |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| } // namespace debug_agent |