component_debug/cli/
list.rs

1// Copyright 2023 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::realm::{get_all_instances, Instance};
6use ansi_term::Colour;
7use anyhow::Result;
8use prettytable::format::consts::FORMAT_CLEAN;
9use prettytable::{cell, row, Table};
10use std::collections::HashSet;
11use std::str::FromStr;
12
13use flex_fuchsia_sys2 as fsys;
14
15/// Filters that can be applied when listing components
16#[derive(Debug, PartialEq)]
17pub enum ListFilter {
18    Running,
19    Stopped,
20    /// Filters components that are an ancestor of the component with the given name.
21    /// Includes the named component.
22    Ancestor(String),
23    /// Filters components that are a descendant of the component with the given name.
24    /// Includes the named component.
25    Descendant(String),
26    /// Filters components that are a relative (either an ancestor or a descendant) of the
27    /// component with the given name. Includes the named component.
28    Relative(String),
29}
30
31impl FromStr for ListFilter {
32    type Err = &'static str;
33
34    fn from_str(s: &str) -> Result<Self, Self::Err> {
35        match s {
36            "running" => Ok(ListFilter::Running),
37            "stopped" => Ok(ListFilter::Stopped),
38            filter => match filter.split_once(":") {
39                Some((function, arg)) => match function {
40                    "ancestor" | "ancestors" => Ok(ListFilter::Ancestor(arg.to_string())),
41                    "descendant" | "descendants" => Ok(ListFilter::Descendant(arg.to_string())),
42                    "relative" | "relatives" => Ok(ListFilter::Relative(arg.to_string())),
43                    _ => Err("unknown function for list filter."),
44                },
45                None => Err("list filter should be 'running', 'stopped', 'ancestors:<component_name>', 'descendants:<component_name>', or 'relatives:<component_name>'."),
46            },
47        }
48    }
49}
50
51pub async fn list_cmd_print<W: std::io::Write>(
52    filter: Option<ListFilter>,
53    verbose: bool,
54    realm_query: fsys::RealmQueryProxy,
55    mut writer: W,
56) -> Result<()> {
57    let instances = get_instances_matching_filter(filter, &realm_query).await?;
58
59    if verbose {
60        let table = create_table(instances);
61        table.print(&mut writer)?;
62    } else {
63        for instance in instances {
64            writeln!(writer, "{}", instance.moniker)?;
65        }
66    }
67
68    Ok(())
69}
70
71pub async fn list_cmd_serialized(
72    filter: Option<ListFilter>,
73    realm_query: fsys::RealmQueryProxy,
74) -> Result<Vec<Instance>> {
75    let basic_infos = get_instances_matching_filter(filter, &realm_query).await?;
76    Ok(basic_infos)
77}
78
79/// Creates a verbose table containing information about all instances.
80fn create_table(instances: Vec<Instance>) -> Table {
81    let mut table = Table::new();
82    table.set_format(*FORMAT_CLEAN);
83    table.set_titles(row!("State", "Moniker", "URL"));
84
85    for instance in instances {
86        let state = instance.resolved_info.map_or_else(
87            || Colour::Red.paint("Stopped"),
88            |r| {
89                r.execution_info.map_or_else(
90                    || Colour::Yellow.paint("Resolved"),
91                    |_| Colour::Green.paint("Running"),
92                )
93            },
94        );
95
96        table.add_row(row!(state, instance.moniker.to_string(), instance.url));
97    }
98    table
99}
100
101pub async fn get_instances_matching_filter(
102    filter: Option<ListFilter>,
103    realm_query: &fsys::RealmQueryProxy,
104) -> Result<Vec<Instance>> {
105    let instances = get_all_instances(realm_query).await?;
106
107    let mut instances = match filter {
108        Some(ListFilter::Running) => instances
109            .into_iter()
110            .filter(|i| i.resolved_info.as_ref().map_or(false, |r| r.execution_info.is_some()))
111            .collect(),
112        Some(ListFilter::Stopped) => instances
113            .into_iter()
114            .filter(|i| i.resolved_info.as_ref().map_or(true, |r| r.execution_info.is_none()))
115            .collect(),
116        Some(ListFilter::Ancestor(m)) => filter_ancestors(instances, m),
117        Some(ListFilter::Descendant(m)) => filter_descendants(instances, m),
118        Some(ListFilter::Relative(m)) => filter_relatives(instances, m),
119        _ => instances,
120    };
121
122    instances.sort_by_key(|c| c.moniker.to_string());
123
124    Ok(instances)
125}
126
127fn filter_ancestors(instances: Vec<Instance>, child_str: String) -> Vec<Instance> {
128    let mut ancestors = HashSet::new();
129
130    // Find monikers with this child as the leaf.
131    for instance in &instances {
132        if let Some(child) = instance.moniker.leaf() {
133            if *child == child_str {
134                // Add this moniker to ancestor list.
135                let mut cur_moniker = instance.moniker.clone();
136                ancestors.insert(cur_moniker.clone());
137
138                // Loop over parents of this moniker and add them to ancestor list.
139                while let Some(parent) = cur_moniker.parent() {
140                    ancestors.insert(parent.clone());
141                    cur_moniker = parent;
142                }
143            }
144        }
145    }
146
147    instances.into_iter().filter(|i| ancestors.contains(&i.moniker)).collect()
148}
149
150fn filter_descendants(instances: Vec<Instance>, child_str: String) -> Vec<Instance> {
151    let mut descendants = HashSet::new();
152
153    // Find monikers with this child as the leaf.
154    for instance in &instances {
155        if let Some(child) = instance.moniker.leaf() {
156            if *child == child_str {
157                // Get all descendants of this moniker.
158                for possible_child_instance in &instances {
159                    if possible_child_instance.moniker.has_prefix(&instance.moniker) {
160                        descendants.insert(possible_child_instance.moniker.clone());
161                    }
162                }
163            }
164        }
165    }
166
167    instances.into_iter().filter(|i| descendants.contains(&i.moniker)).collect()
168}
169
170fn filter_relatives(instances: Vec<Instance>, child_str: String) -> Vec<Instance> {
171    let mut relatives = HashSet::new();
172
173    // Find monikers with this child as the leaf.
174    for instance in &instances {
175        if let Some(child) = instance.moniker.leaf() {
176            if *child == child_str {
177                // Loop over parents of this moniker and add them to relatives list.
178                let mut cur_moniker = instance.moniker.clone();
179                while let Some(parent) = cur_moniker.parent() {
180                    relatives.insert(parent.clone());
181                    cur_moniker = parent;
182                }
183
184                // Get all descendants of this moniker and add them to relatives list.
185                for possible_child_instance in &instances {
186                    if possible_child_instance.moniker.has_prefix(&instance.moniker) {
187                        relatives.insert(possible_child_instance.moniker.clone());
188                    }
189                }
190            }
191        }
192    }
193
194    instances.into_iter().filter(|i| relatives.contains(&i.moniker)).collect()
195}
196
197#[cfg(test)]
198mod tests {
199    use super::*;
200    use crate::test_utils::*;
201    use moniker::Moniker;
202    use std::collections::HashMap;
203
204    fn create_query() -> fsys::RealmQueryProxy {
205        // Serve RealmQuery for CML components.
206        let query = serve_realm_query(
207            vec![
208                fsys::Instance {
209                    moniker: Some("./".to_string()),
210                    url: Some("fuchsia-pkg://fuchsia.com/root#meta/root.cm".to_string()),
211                    instance_id: None,
212                    resolved_info: Some(fsys::ResolvedInfo {
213                        resolved_url: Some(
214                            "fuchsia-pkg://fuchsia.com/root#meta/root.cm".to_string(),
215                        ),
216                        execution_info: None,
217                        ..Default::default()
218                    }),
219                    ..Default::default()
220                },
221                fsys::Instance {
222                    moniker: Some("./core".to_string()),
223                    url: Some("fuchsia-pkg://fuchsia.com/core#meta/core.cm".to_string()),
224                    instance_id: None,
225                    resolved_info: Some(fsys::ResolvedInfo {
226                        resolved_url: Some(
227                            "fuchsia-pkg://fuchsia.com/core#meta/core.cm".to_string(),
228                        ),
229                        execution_info: Some(fsys::ExecutionInfo {
230                            start_reason: Some("Debugging Workflow".to_string()),
231                            ..Default::default()
232                        }),
233                        ..Default::default()
234                    }),
235                    ..Default::default()
236                },
237                fsys::Instance {
238                    moniker: Some("./core/appmgr".to_string()),
239                    url: Some("fuchsia-pkg://fuchsia.com/appmgr#meta/appmgr.cm".to_string()),
240                    instance_id: None,
241                    resolved_info: Some(fsys::ResolvedInfo {
242                        resolved_url: Some(
243                            "fuchsia-pkg://fuchsia.com/appmgr#meta/appmgr.cm".to_string(),
244                        ),
245                        execution_info: Some(fsys::ExecutionInfo {
246                            start_reason: Some("Debugging Workflow".to_string()),
247                            ..Default::default()
248                        }),
249                        ..Default::default()
250                    }),
251                    ..Default::default()
252                },
253            ],
254            HashMap::new(),
255            HashMap::new(),
256            HashMap::new(),
257        );
258        query
259    }
260
261    #[fuchsia::test]
262    async fn no_filter() {
263        let query = create_query();
264
265        let instances = get_instances_matching_filter(None, &query).await.unwrap();
266        assert_eq!(
267            instances.iter().map(|i| i.moniker.clone()).collect::<Vec<_>>(),
268            vec![
269                Moniker::root(),
270                Moniker::parse_str("/core").unwrap(),
271                Moniker::parse_str("/core/appmgr").unwrap(),
272            ]
273        );
274    }
275
276    #[fuchsia::test]
277    async fn running_only() {
278        let query = create_query();
279
280        let instances =
281            get_instances_matching_filter(Some(ListFilter::Running), &query).await.unwrap();
282        assert_eq!(
283            instances.iter().map(|i| i.moniker.clone()).collect::<Vec<_>>(),
284            vec![Moniker::parse_str("/core").unwrap(), Moniker::parse_str("/core/appmgr").unwrap(),]
285        );
286    }
287
288    #[fuchsia::test]
289    async fn stopped_only() {
290        let query = create_query();
291
292        let instances =
293            get_instances_matching_filter(Some(ListFilter::Stopped), &query).await.unwrap();
294        assert_eq!(
295            instances.iter().map(|i| i.moniker.clone()).collect::<Vec<_>>(),
296            [Moniker::root()]
297        );
298    }
299
300    #[fuchsia::test]
301    async fn descendants_only() {
302        let query = create_query();
303
304        let instances =
305            get_instances_matching_filter(Some(ListFilter::Descendant("core".to_string())), &query)
306                .await
307                .unwrap();
308        assert_eq!(
309            instances.iter().map(|i| i.moniker.clone()).collect::<Vec<_>>(),
310            vec![Moniker::parse_str("/core").unwrap(), Moniker::parse_str("/core/appmgr").unwrap(),]
311        );
312    }
313
314    #[fuchsia::test]
315    async fn ancestors_only() {
316        let query = create_query();
317
318        let instances =
319            get_instances_matching_filter(Some(ListFilter::Ancestor("core".to_string())), &query)
320                .await
321                .unwrap();
322        assert_eq!(
323            instances.iter().map(|i| i.moniker.clone()).collect::<Vec<_>>(),
324            vec![Moniker::root(), Moniker::parse_str("/core").unwrap()]
325        );
326    }
327
328    #[fuchsia::test]
329    async fn relative_only() {
330        let query = create_query();
331
332        let instances =
333            get_instances_matching_filter(Some(ListFilter::Relative("core".to_string())), &query)
334                .await
335                .unwrap();
336        assert_eq!(
337            instances.iter().map(|i| i.moniker.clone()).collect::<Vec<_>>(),
338            vec![
339                Moniker::root(),
340                Moniker::parse_str("/core").unwrap(),
341                Moniker::parse_str("/core/appmgr").unwrap(),
342            ]
343        );
344    }
345}