| // 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. |
| |
| use async_trait::async_trait; |
| use component_debug_fdomain::cli::{list_cmd_print, list_cmd_serialized}; |
| use component_debug_fdomain::realm::Instance; |
| use errors::ffx_error; |
| use ffx_component::rcs::connect_to_realm_query_f; |
| use ffx_component_list_args::ComponentListCommand; |
| use ffx_writer::{ToolIO as _, VerifiedMachineWriter}; |
| use fho::{FfxMain, FfxTool}; |
| use schemars::JsonSchema; |
| use serde::Serialize; |
| use target_holders::fdomain::RemoteControlProxyHolder; |
| |
| #[derive(FfxTool)] |
| pub struct ListTool { |
| #[command] |
| cmd: ComponentListCommand, |
| rcs: RemoteControlProxyHolder, |
| } |
| |
| fho::embedded_plugin!(ListTool); |
| |
| #[derive(Debug, Serialize, JsonSchema)] |
| #[serde(rename_all = "snake_case")] |
| pub struct ListOutput { |
| instances: Vec<Instance>, |
| } |
| |
| #[async_trait(?Send)] |
| impl FfxMain for ListTool { |
| type Writer = VerifiedMachineWriter<ListOutput>; |
| |
| async fn main(self, mut writer: Self::Writer) -> fho::Result<()> { |
| let realm_query = connect_to_realm_query_f(&self.rcs).await?; |
| // All errors from component_debug library are user-visible. |
| if writer.is_machine() { |
| let instances = list_cmd_serialized(self.cmd.filter, realm_query) |
| .await |
| .map_err(|e| ffx_error!(e))?; |
| let output = ListOutput { instances }; |
| writer.machine(&output)?; |
| } else { |
| list_cmd_print(self.cmd.filter, self.cmd.verbose, realm_query, writer) |
| .await |
| .map_err(|e| ffx_error!(e))?; |
| } |
| Ok(()) |
| } |
| } |
| |
| #[cfg(test)] |
| mod test { |
| use super::*; |
| use crate::ListTool; |
| |
| use anyhow::Result; |
| use component_debug_fdomain::realm::ResolvedInfo; |
| use fdomain_client::fidl::ServerEnd; |
| use fdomain_fuchsia_developer_remotecontrol::{ |
| RemoteControlMarker, RemoteControlProxy, RemoteControlRequest, |
| }; |
| use ffx_component_list_args::ComponentListCommand; |
| use ffx_writer::{Format, TestBuffers}; |
| use fho::FfxMain; |
| use futures::TryStreamExt; |
| use moniker::Moniker; |
| use pretty_assertions::assert_eq; |
| use serde_json::json; |
| use std::rc::Rc; |
| use std::sync::Arc; |
| |
| pub fn setup_fake_rcs( |
| components: Vec<&str>, |
| client: Arc<fdomain_client::Client>, |
| ) -> RemoteControlProxy { |
| let mut mock_realm_query_builder = iquery_test_support::MockRealmQueryBuilder::prefilled(); |
| for c in components { |
| mock_realm_query_builder = |
| mock_realm_query_builder.when(c).moniker(format!("{c}").as_ref()).add(); |
| } |
| |
| let mock_realm_query = mock_realm_query_builder.build(); |
| let (proxy, mut stream) = client.create_proxy_and_stream::<RemoteControlMarker>(); |
| fuchsia_async::Task::local(async move { |
| let _client = client; |
| let querier = Rc::new(mock_realm_query); |
| while let Ok(Some(req)) = stream.try_next().await { |
| match req { |
| RemoteControlRequest::ConnectCapability { |
| moniker, |
| capability_set, |
| capability_name, |
| server_channel, |
| responder, |
| } => { |
| assert_eq!(moniker, "toolbox"); |
| assert_eq!(capability_set, rcs::OpenDirType::NamespaceDir); |
| assert_eq!(capability_name, "svc/fuchsia.sys2.RealmQuery.root"); |
| let querier = Rc::clone(&querier); |
| fuchsia_async::Task::local(querier.serve_f(ServerEnd::new(server_channel))) |
| .detach(); |
| responder.send(Ok(())).unwrap(); |
| } |
| e @ _ => unreachable!("Not implemented: {:?}", e), |
| } |
| } |
| }) |
| .detach(); |
| proxy |
| } |
| |
| #[fuchsia::test] |
| async fn test_schema() -> Result<()> { |
| let client = fdomain_local::local_client(|| Err(zx_status::Status::NOT_SUPPORTED)); |
| let cmd = ComponentListCommand { filter: None, verbose: false }; |
| let tool = ListTool { cmd, rcs: setup_fake_rcs(vec![], client).into() }; |
| let buffers = TestBuffers::default(); |
| |
| let writer = <ListTool as FfxMain>::Writer::new_test(Some(Format::Json), &buffers); |
| |
| let result = tool.main(writer).await; |
| assert!(result.is_ok()); |
| |
| let output = buffers.into_stdout_str(); |
| |
| let err = format!("schema not valid {output}"); |
| let json = serde_json::from_str(&output).expect(&err); |
| let err = format!("json must adhere to schema: {json}"); |
| <ListTool as FfxMain>::Writer::verify_schema(&json).expect(&err); |
| |
| let want = ListOutput { |
| instances: vec![ |
| Instance { |
| environment: None, |
| moniker: Moniker::parse_str("example/component")?, |
| resolved_info: Some(ResolvedInfo { |
| execution_info: None, |
| resolved_url: "".to_string(), |
| }), |
| url: "".to_string(), |
| instance_id: None, |
| }, |
| Instance { |
| environment: None, |
| moniker: Moniker::parse_str("foo/bar/thing:instance")?, |
| resolved_info: Some(ResolvedInfo { |
| execution_info: None, |
| resolved_url: "".to_string(), |
| }), |
| url: "".to_string(), |
| instance_id: None, |
| }, |
| Instance { |
| environment: None, |
| moniker: Moniker::parse_str("foo/component")?, |
| resolved_info: Some(ResolvedInfo { |
| execution_info: None, |
| resolved_url: "".to_string(), |
| }), |
| url: "".to_string(), |
| instance_id: None, |
| }, |
| Instance { |
| environment: None, |
| moniker: Moniker::parse_str("other/component")?, |
| resolved_info: Some(ResolvedInfo { |
| execution_info: None, |
| resolved_url: "".to_string(), |
| }), |
| url: "".to_string(), |
| instance_id: None, |
| }, |
| ], |
| }; |
| assert_eq!(json, json!(want)); |
| |
| Ok(()) |
| } |
| } |