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(Colour::Red.paint("Stopped"), |r| {
87            r.execution_info
88                .map_or(Colour::Yellow.paint("Resolved"), |_| Colour::Green.paint("Running"))
89        });
90
91        table.add_row(row!(state, instance.moniker.to_string(), instance.url));
92    }
93    table
94}
95
96pub async fn get_instances_matching_filter(
97    filter: Option<ListFilter>,
98    realm_query: &fsys::RealmQueryProxy,
99) -> Result<Vec<Instance>> {
100    let instances = get_all_instances(realm_query).await?;
101
102    let mut instances = match filter {
103        Some(ListFilter::Running) => instances
104            .into_iter()
105            .filter(|i| i.resolved_info.as_ref().map_or(false, |r| r.execution_info.is_some()))
106            .collect(),
107        Some(ListFilter::Stopped) => instances
108            .into_iter()
109            .filter(|i| i.resolved_info.as_ref().map_or(true, |r| r.execution_info.is_none()))
110            .collect(),
111        Some(ListFilter::Ancestor(m)) => filter_ancestors(instances, m),
112        Some(ListFilter::Descendant(m)) => filter_descendants(instances, m),
113        Some(ListFilter::Relative(m)) => filter_relatives(instances, m),
114        _ => instances,
115    };
116
117    instances.sort_by_key(|c| c.moniker.to_string());
118
119    Ok(instances)
120}
121
122fn filter_ancestors(instances: Vec<Instance>, child_str: String) -> Vec<Instance> {
123    let mut ancestors = HashSet::new();
124
125    // Find monikers with this child as the leaf.
126    for instance in &instances {
127        if let Some(child) = instance.moniker.leaf() {
128            if child.to_string() == child_str {
129                // Add this moniker to ancestor list.
130                let mut cur_moniker = instance.moniker.clone();
131                ancestors.insert(cur_moniker.clone());
132
133                // Loop over parents of this moniker and add them to ancestor list.
134                while let Some(parent) = cur_moniker.parent() {
135                    ancestors.insert(parent.clone());
136                    cur_moniker = parent;
137                }
138            }
139        }
140    }
141
142    instances.into_iter().filter(|i| ancestors.contains(&i.moniker)).collect()
143}
144
145fn filter_descendants(instances: Vec<Instance>, child_str: String) -> Vec<Instance> {
146    let mut descendants = HashSet::new();
147
148    // Find monikers with this child as the leaf.
149    for instance in &instances {
150        if let Some(child) = instance.moniker.leaf() {
151            if child.to_string() == child_str {
152                // Get all descendants of this moniker.
153                for possible_child_instance in &instances {
154                    if possible_child_instance.moniker.has_prefix(&instance.moniker) {
155                        descendants.insert(possible_child_instance.moniker.clone());
156                    }
157                }
158            }
159        }
160    }
161
162    instances.into_iter().filter(|i| descendants.contains(&i.moniker)).collect()
163}
164
165fn filter_relatives(instances: Vec<Instance>, child_str: String) -> Vec<Instance> {
166    let mut relatives = HashSet::new();
167
168    // Find monikers with this child as the leaf.
169    for instance in &instances {
170        if let Some(child) = instance.moniker.leaf() {
171            if child.to_string() == child_str {
172                // Loop over parents of this moniker and add them to relatives list.
173                let mut cur_moniker = instance.moniker.clone();
174                while let Some(parent) = cur_moniker.parent() {
175                    relatives.insert(parent.clone());
176                    cur_moniker = parent;
177                }
178
179                // Get all descendants of this moniker and add them to relatives list.
180                for possible_child_instance in &instances {
181                    if possible_child_instance.moniker.has_prefix(&instance.moniker) {
182                        relatives.insert(possible_child_instance.moniker.clone());
183                    }
184                }
185            }
186        }
187    }
188
189    instances.into_iter().filter(|i| relatives.contains(&i.moniker)).collect()
190}
191
192#[cfg(test)]
193mod tests {
194    use super::*;
195    use crate::test_utils::*;
196    use moniker::Moniker;
197    use std::collections::HashMap;
198
199    fn create_query() -> fsys::RealmQueryProxy {
200        // Serve RealmQuery for CML components.
201        let query = serve_realm_query(
202            vec![
203                fsys::Instance {
204                    moniker: Some("./".to_string()),
205                    url: Some("fuchsia-pkg://fuchsia.com/root#meta/root.cm".to_string()),
206                    instance_id: None,
207                    resolved_info: Some(fsys::ResolvedInfo {
208                        resolved_url: Some(
209                            "fuchsia-pkg://fuchsia.com/root#meta/root.cm".to_string(),
210                        ),
211                        execution_info: None,
212                        ..Default::default()
213                    }),
214                    ..Default::default()
215                },
216                fsys::Instance {
217                    moniker: Some("./core".to_string()),
218                    url: Some("fuchsia-pkg://fuchsia.com/core#meta/core.cm".to_string()),
219                    instance_id: None,
220                    resolved_info: Some(fsys::ResolvedInfo {
221                        resolved_url: Some(
222                            "fuchsia-pkg://fuchsia.com/core#meta/core.cm".to_string(),
223                        ),
224                        execution_info: Some(fsys::ExecutionInfo {
225                            start_reason: Some("Debugging Workflow".to_string()),
226                            ..Default::default()
227                        }),
228                        ..Default::default()
229                    }),
230                    ..Default::default()
231                },
232                fsys::Instance {
233                    moniker: Some("./core/appmgr".to_string()),
234                    url: Some("fuchsia-pkg://fuchsia.com/appmgr#meta/appmgr.cm".to_string()),
235                    instance_id: None,
236                    resolved_info: Some(fsys::ResolvedInfo {
237                        resolved_url: Some(
238                            "fuchsia-pkg://fuchsia.com/appmgr#meta/appmgr.cm".to_string(),
239                        ),
240                        execution_info: Some(fsys::ExecutionInfo {
241                            start_reason: Some("Debugging Workflow".to_string()),
242                            ..Default::default()
243                        }),
244                        ..Default::default()
245                    }),
246                    ..Default::default()
247                },
248            ],
249            HashMap::new(),
250            HashMap::new(),
251            HashMap::new(),
252        );
253        query
254    }
255
256    #[fuchsia::test]
257    async fn no_filter() {
258        let query = create_query();
259
260        let instances = get_instances_matching_filter(None, &query).await.unwrap();
261        assert_eq!(
262            instances.iter().map(|i| i.moniker.clone()).collect::<Vec<_>>(),
263            vec![
264                Moniker::root(),
265                Moniker::parse_str("/core").unwrap(),
266                Moniker::parse_str("/core/appmgr").unwrap(),
267            ]
268        );
269    }
270
271    #[fuchsia::test]
272    async fn running_only() {
273        let query = create_query();
274
275        let instances =
276            get_instances_matching_filter(Some(ListFilter::Running), &query).await.unwrap();
277        assert_eq!(
278            instances.iter().map(|i| i.moniker.clone()).collect::<Vec<_>>(),
279            vec![Moniker::parse_str("/core").unwrap(), Moniker::parse_str("/core/appmgr").unwrap(),]
280        );
281    }
282
283    #[fuchsia::test]
284    async fn stopped_only() {
285        let query = create_query();
286
287        let instances =
288            get_instances_matching_filter(Some(ListFilter::Stopped), &query).await.unwrap();
289        assert_eq!(
290            instances.iter().map(|i| i.moniker.clone()).collect::<Vec<_>>(),
291            [Moniker::root()]
292        );
293    }
294
295    #[fuchsia::test]
296    async fn descendants_only() {
297        let query = create_query();
298
299        let instances =
300            get_instances_matching_filter(Some(ListFilter::Descendant("core".to_string())), &query)
301                .await
302                .unwrap();
303        assert_eq!(
304            instances.iter().map(|i| i.moniker.clone()).collect::<Vec<_>>(),
305            vec![Moniker::parse_str("/core").unwrap(), Moniker::parse_str("/core/appmgr").unwrap(),]
306        );
307    }
308
309    #[fuchsia::test]
310    async fn ancestors_only() {
311        let query = create_query();
312
313        let instances =
314            get_instances_matching_filter(Some(ListFilter::Ancestor("core".to_string())), &query)
315                .await
316                .unwrap();
317        assert_eq!(
318            instances.iter().map(|i| i.moniker.clone()).collect::<Vec<_>>(),
319            vec![Moniker::root(), Moniker::parse_str("/core").unwrap()]
320        );
321    }
322
323    #[fuchsia::test]
324    async fn relative_only() {
325        let query = create_query();
326
327        let instances =
328            get_instances_matching_filter(Some(ListFilter::Relative("core".to_string())), &query)
329                .await
330                .unwrap();
331        assert_eq!(
332            instances.iter().map(|i| i.moniker.clone()).collect::<Vec<_>>(),
333            vec![
334                Moniker::root(),
335                Moniker::parse_str("/core").unwrap(),
336                Moniker::parse_str("/core/appmgr").unwrap(),
337            ]
338        );
339    }
340}