1use crate::constants;
6use crate::diagnostics::{GlobalConnectionStats, TRACE_CATEGORY};
7use crate::identity::ComponentIdentity;
8use crate::inspect::collector::{self as collector, InspectData};
9use crate::pipeline::ComponentAllowlist;
10use diagnostics_data::{self as schema, InspectHandleName};
11use diagnostics_hierarchy::DiagnosticsHierarchy;
12use fidl::endpoints::Proxy;
13use flyweights::FlyStr;
14use fuchsia_async::{self as fasync, DurationExt, TimeoutExt};
15use fuchsia_inspect::reader::snapshot::{Snapshot, SnapshotTree};
16use futures::channel::oneshot;
17use futures::{FutureExt, Stream};
18use log::warn;
19use selectors::SelectorExt;
20use std::collections::HashMap;
21use std::future::Future;
22use std::sync::{Arc, Weak};
23use std::time::Duration;
24use zx::{self as zx, AsHandleRef};
25use {
26 fidl_fuchsia_diagnostics as fdiagnostics, fidl_fuchsia_inspect as finspect,
27 fidl_fuchsia_io as fio, fuchsia_trace as ftrace, inspect_fidl_load as deprecated_inspect,
28};
29
30#[derive(Debug)]
31pub enum InspectHandle {
32 Tree {
33 proxy: finspect::TreeProxy,
34 name: Option<FlyStr>,
35 },
36 Directory {
37 proxy: fio::DirectoryProxy,
38 },
39 Escrow {
40 vmo: Arc<zx::Vmo>,
41 name: Option<FlyStr>,
42 token: finspect::EscrowToken,
43 related_koid: zx::Koid,
44 },
45}
46
47impl InspectHandle {
48 fn is_closed(&self) -> bool {
49 match self {
50 Self::Directory { proxy } => proxy.as_channel().is_closed(),
51 Self::Tree { proxy, .. } => proxy.as_channel().is_closed(),
52 Self::Escrow { .. } => false,
53 }
54 }
55
56 async fn on_closed(&self) -> Result<zx::Signals, zx::Status> {
57 match self {
58 Self::Tree { proxy, .. } => proxy.on_closed().await,
59 Self::Directory { proxy, .. } => proxy.on_closed().await,
60 Self::Escrow { token, .. } => {
61 fasync::OnSignals::new(&token.token, fidl::Signals::OBJECT_PEER_CLOSED).await
62 }
63 }
64 }
65
66 pub fn koid(&self) -> zx::Koid {
67 match self {
68 Self::Directory { proxy } => {
69 proxy.as_channel().as_handle_ref().get_koid().expect("DirectoryProxy has koid")
70 }
71 Self::Tree { proxy, .. } => {
72 proxy.as_channel().as_handle_ref().get_koid().expect("TreeProxy has koid")
73 }
74 Self::Escrow { related_koid, .. } => *related_koid,
77 }
78 }
79
80 pub fn tree<T: Into<FlyStr>>(proxy: finspect::TreeProxy, name: Option<T>) -> Self {
81 InspectHandle::Tree { proxy, name: name.map(|n| n.into()) }
82 }
83
84 pub fn escrow<T: Into<FlyStr>>(
85 vmo: zx::Vmo,
86 token: finspect::EscrowToken,
87 name: Option<T>,
88 ) -> Self {
89 let related_koid = token.token.basic_info().unwrap().related_koid;
90 InspectHandle::Escrow {
91 vmo: Arc::new(vmo),
92 name: name.map(|n| n.into()),
93 token,
94 related_koid,
95 }
96 }
97
98 pub fn directory(proxy: fio::DirectoryProxy) -> Self {
99 InspectHandle::Directory { proxy }
100 }
101}
102
103struct StoredInspectHandle {
104 handle: Arc<InspectHandle>,
105 _control: oneshot::Sender<()>,
108}
109
110#[derive(Default)]
111pub struct InspectArtifactsContainer {
112 inspect_handles: HashMap<zx::Koid, StoredInspectHandle>,
114}
115
116impl InspectArtifactsContainer {
117 pub fn remove_handle(&mut self, koid: zx::Koid) -> (Option<Arc<InspectHandle>>, usize) {
119 let stored = self.inspect_handles.remove(&koid);
120 (stored.map(|stored| stored.handle), self.inspect_handles.len())
121 }
122
123 pub fn push_handle(
128 &mut self,
129 handle: InspectHandle,
130 on_closed: impl FnOnce(zx::Koid),
131 ) -> Option<impl Future<Output = ()>> {
132 if !self.inspect_handles.is_empty() && matches!(handle, InspectHandle::Directory { .. }) {
133 return None;
134 }
135 let (control_snd, control_rcv) = oneshot::channel();
136 let handle = Arc::new(handle);
137 let stored = StoredInspectHandle { _control: control_snd, handle: Arc::clone(&handle) };
138 let koid = handle.koid();
139 self.inspect_handles.insert(koid, stored);
140 Some(async move {
141 if !handle.is_closed() {
142 let closed_fut = handle.on_closed();
143 futures::pin_mut!(closed_fut);
144 let _ = futures::future::select(closed_fut, control_rcv).await;
145 }
146 on_closed(koid);
147 })
148 }
149
150 pub fn create_unpopulated(
154 &self,
155 identity: &Arc<ComponentIdentity>,
156 allowlist: ComponentAllowlist,
157 dynamic_selectors: &Option<impl AsRef<[fdiagnostics::Selector]>>,
158 ) -> Option<UnpopulatedInspectDataContainer> {
159 if self.inspect_handles.is_empty() {
160 return None;
161 }
162
163 if allowlist.all_filtered_out() {
164 return None;
165 }
166
167 let mut dynamic_selectors = dynamic_selectors.as_ref().map(|all_components| {
169 all_components
170 .as_ref()
171 .iter()
172 .filter(|s| matches!(identity.moniker.matches_selector(s), Ok(true)))
173 .peekable()
174 });
175
176 if let Some(None) = dynamic_selectors.as_mut().map(|s| s.peek()) {
177 return None;
178 }
179
180 Some(UnpopulatedInspectDataContainer {
181 identity: Arc::clone(identity),
182 component_allowlist: allowlist,
183 inspect_handles: self
184 .inspect_handles
185 .values()
186 .filter(|s| {
187 Self::name_filters_satisfied(s.handle.as_ref(), dynamic_selectors.clone())
190 })
191 .map(|s| Arc::downgrade(&s.handle))
192 .collect::<Vec<_>>(),
193 })
194 }
195
196 fn name_filters_satisfied<'a>(
197 handle: &InspectHandle,
198 mut dynamic_selectors: Option<impl Iterator<Item = &'a fdiagnostics::Selector>>,
199 ) -> bool {
200 let Some(ref mut selectors) = dynamic_selectors else {
201 return true;
202 };
203 match handle {
204 InspectHandle::Tree { name, .. } | InspectHandle::Escrow { name, .. } => {
205 selectors.any(|s| {
206 let name = match name.as_ref() {
207 Some(fly) => fly.as_str(),
208 None => finspect::DEFAULT_TREE_NAME,
209 };
210 selectors::match_tree_name_against_selector(name, s)
211 })
212 }
213 InspectHandle::Directory { .. } => selectors.any(|s| {
214 matches!(s.tree_names.as_ref(), None | Some(fdiagnostics::TreeNames::All(_)))
215 }),
216 }
217 }
218}
219
220#[cfg(test)]
221impl InspectArtifactsContainer {
222 pub(crate) fn handles(&self) -> impl ExactSizeIterator<Item = &InspectHandle> {
223 self.inspect_handles.values().map(|stored| stored.handle.as_ref())
224 }
225}
226
227static TIMEOUT_MESSAGE: &str = "Exceeded per-component time limit for fetching diagnostics data";
228
229#[derive(Debug)]
230pub enum ReadSnapshot {
231 Single(Snapshot),
232 Tree(SnapshotTree),
233 Finished(DiagnosticsHierarchy),
234}
235
236#[derive(Debug)]
239pub struct SnapshotData {
240 pub name: Option<InspectHandleName>,
242 pub timestamp: zx::BootInstant,
244 pub errors: Vec<schema::InspectError>,
246 pub snapshot: Option<ReadSnapshot>,
249 pub escrowed: bool,
251}
252
253impl SnapshotData {
254 async fn new(
255 name: Option<InspectHandleName>,
256 data: InspectData,
257 lazy_child_timeout: zx::MonotonicDuration,
258 identity: &ComponentIdentity,
259 parent_trace_id: ftrace::Id,
260 ) -> SnapshotData {
261 let trace_id = ftrace::Id::random();
262 let _trace_guard = ftrace::async_enter!(
263 trace_id,
264 TRACE_CATEGORY,
265 c"SnapshotData::new",
266 "parent_trace_id" => u64::from(parent_trace_id),
269 "trace_id" => u64::from(trace_id),
270 "moniker" => identity.to_string().as_ref(),
271 "filename" => name
272 .as_ref()
273 .and_then(InspectHandleName::as_filename)
274 .unwrap_or(""),
275 "name" => name
276 .as_ref()
277 .and_then(InspectHandleName::as_name)
278 .unwrap_or("")
279 );
280 match data {
281 InspectData::Tree(tree) => {
282 let lazy_child_timeout =
283 Duration::from_nanos(lazy_child_timeout.into_nanos() as u64);
284 match SnapshotTree::try_from_with_timeout(&tree, lazy_child_timeout).await {
285 Ok(snapshot_tree) => {
286 SnapshotData::successful(ReadSnapshot::Tree(snapshot_tree), name, false)
287 }
288 Err(e) => SnapshotData::failed(
289 schema::InspectError { message: format!("{e:?}") },
290 name,
291 false,
292 ),
293 }
294 }
295 InspectData::DeprecatedFidl(inspect_proxy) => {
296 match deprecated_inspect::load_hierarchy(inspect_proxy).await {
297 Ok(hierarchy) => {
298 SnapshotData::successful(ReadSnapshot::Finished(hierarchy), name, false)
299 }
300 Err(e) => SnapshotData::failed(
301 schema::InspectError { message: format!("{e:?}") },
302 name,
303 false,
304 ),
305 }
306 }
307 InspectData::Vmo { data: vmo, escrowed } => match Snapshot::try_from(vmo.as_ref()) {
308 Ok(snapshot) => {
309 SnapshotData::successful(ReadSnapshot::Single(snapshot), name, escrowed)
310 }
311 Err(e) => SnapshotData::failed(
312 schema::InspectError { message: format!("{e:?}") },
313 name,
314 escrowed,
315 ),
316 },
317 InspectData::File(contents) => match Snapshot::try_from(contents) {
318 Ok(snapshot) => {
319 SnapshotData::successful(ReadSnapshot::Single(snapshot), name, false)
320 }
321 Err(e) => SnapshotData::failed(
322 schema::InspectError { message: format!("{e:?}") },
323 name,
324 false,
325 ),
326 },
327 }
328 }
329
330 fn successful(
332 snapshot: ReadSnapshot,
333 name: Option<InspectHandleName>,
334 escrowed: bool,
335 ) -> SnapshotData {
336 SnapshotData {
337 name,
338 timestamp: zx::BootInstant::get(),
339 errors: Vec::new(),
340 snapshot: Some(snapshot),
341 escrowed,
342 }
343 }
344
345 fn failed(
347 error: schema::InspectError,
348 name: Option<InspectHandleName>,
349 escrowed: bool,
350 ) -> SnapshotData {
351 SnapshotData {
352 name,
353 timestamp: zx::BootInstant::get(),
354 errors: vec![error],
355 snapshot: None,
356 escrowed,
357 }
358 }
359}
360
361pub struct PopulatedInspectDataContainer {
366 pub identity: Arc<ComponentIdentity>,
367 pub snapshot: SnapshotData,
372 pub component_allowlist: ComponentAllowlist,
373}
374
375enum Status {
376 Begin,
377 Pending(collector::InspectHandleDeque),
378}
379
380struct State {
381 status: Status,
382 unpopulated: Arc<UnpopulatedInspectDataContainer>,
383 batch_timeout: zx::MonotonicDuration,
384 elapsed_time: zx::MonotonicDuration,
385 global_stats: Arc<GlobalConnectionStats>,
386 trace_guard: Arc<Option<ftrace::AsyncScope>>,
387 trace_id: ftrace::Id,
388}
389
390impl State {
391 fn into_pending(
392 self,
393 pending: collector::InspectHandleDeque,
394 start_time: zx::MonotonicInstant,
395 ) -> Self {
396 Self {
397 unpopulated: self.unpopulated,
398 status: Status::Pending(pending),
399 batch_timeout: self.batch_timeout,
400 global_stats: self.global_stats,
401 elapsed_time: self.elapsed_time + (zx::MonotonicInstant::get() - start_time),
402 trace_guard: self.trace_guard,
403 trace_id: self.trace_id,
404 }
405 }
406
407 fn add_elapsed_time(&mut self, start_time: zx::MonotonicInstant) {
408 self.elapsed_time += zx::MonotonicInstant::get() - start_time
409 }
410
411 async fn iterate(
412 mut self,
413 start_time: zx::MonotonicInstant,
414 ) -> Option<(PopulatedInspectDataContainer, State)> {
415 loop {
416 match &mut self.status {
417 Status::Begin => {
418 let data_map =
419 collector::populate_data_map(&self.unpopulated.inspect_handles).await;
420 self = self.into_pending(data_map, start_time);
421 }
422 Status::Pending(ref mut pending) => match pending.pop_front() {
423 None => {
424 self.global_stats.record_component_duration(
425 self.unpopulated.identity.moniker.to_string(),
426 self.elapsed_time + (zx::MonotonicInstant::get() - start_time),
427 );
428 return None;
429 }
430 Some((name, data)) => {
431 let snapshot = SnapshotData::new(
432 name,
433 data,
434 self.batch_timeout / constants::LAZY_NODE_TIMEOUT_PROPORTION,
435 &self.unpopulated.identity,
436 self.trace_id,
437 )
438 .await;
439 let result = PopulatedInspectDataContainer {
440 identity: Arc::clone(&self.unpopulated.identity),
441 snapshot,
442 component_allowlist: self.unpopulated.component_allowlist.clone(),
443 };
444 self.add_elapsed_time(start_time);
445 return Some((result, self));
446 }
447 },
448 }
449 }
450 }
451}
452
453#[derive(Debug)]
457pub struct UnpopulatedInspectDataContainer {
458 pub identity: Arc<ComponentIdentity>,
459 pub inspect_handles: Vec<Weak<InspectHandle>>,
462 pub component_allowlist: ComponentAllowlist,
465}
466
467impl UnpopulatedInspectDataContainer {
468 pub fn populate(
471 self,
472 timeout: i64,
473 global_stats: Arc<GlobalConnectionStats>,
474 parent_trace_id: ftrace::Id,
475 ) -> impl Stream<Item = PopulatedInspectDataContainer> {
476 let trace_id = ftrace::Id::random();
477 let trace_guard = ftrace::async_enter!(
478 trace_id,
479 TRACE_CATEGORY,
480 c"ReaderServer::stream.populate",
481 "parent_trace_id" => u64::from(parent_trace_id),
484 "trace_id" => u64::from(trace_id),
485 "moniker" => self.identity.to_string().as_ref()
486 );
487 let this = Arc::new(self);
488 let state = State {
489 status: Status::Begin,
490 unpopulated: this,
491 batch_timeout: zx::MonotonicDuration::from_seconds(timeout),
492 global_stats,
493 elapsed_time: zx::MonotonicDuration::ZERO,
494 trace_guard: Arc::new(trace_guard),
495 trace_id,
496 };
497
498 futures::stream::unfold(state, |state| {
499 let unpopulated_for_timeout = Arc::clone(&state.unpopulated);
500 let timeout = state.batch_timeout;
501 let elapsed_time = state.elapsed_time;
502 let global_stats = Arc::clone(&state.global_stats);
503 let start_time = zx::MonotonicInstant::get();
504 let trace_guard = Arc::clone(&state.trace_guard);
505 let trace_id = state.trace_id;
506
507 state
508 .iterate(start_time)
509 .on_timeout((timeout - elapsed_time).after_now(), move || {
510 warn!(identity:? = unpopulated_for_timeout.identity.moniker; "{TIMEOUT_MESSAGE}");
511 global_stats.add_timeout();
512 let result = PopulatedInspectDataContainer {
513 identity: Arc::clone(&unpopulated_for_timeout.identity),
514 component_allowlist: unpopulated_for_timeout.component_allowlist.clone(),
515 snapshot: SnapshotData::failed(
516 schema::InspectError { message: TIMEOUT_MESSAGE.to_string() },
517 None,
518 false,
519 ),
520 };
521 Some((
522 result,
523 State {
524 status: Status::Pending(collector::InspectHandleDeque::new()),
525 unpopulated: unpopulated_for_timeout,
526 batch_timeout: timeout,
527 global_stats,
528 elapsed_time: elapsed_time + (zx::MonotonicInstant::get() - start_time),
529 trace_guard,
530 trace_id,
531 },
532 ))
533 })
534 .boxed()
535 })
536 }
537}
538
539#[cfg(test)]
540mod test {
541 use super::*;
542 use fuchsia_inspect::Node;
543
544 use futures::StreamExt;
545 use std::sync::LazyLock;
546
547 static EMPTY_IDENTITY: LazyLock<Arc<ComponentIdentity>> =
548 LazyLock::new(|| Arc::new(ComponentIdentity::unknown()));
549
550 static O_K_IDENTITY: LazyLock<Arc<ComponentIdentity>> =
551 LazyLock::new(|| Arc::new(ComponentIdentity::from(vec!["o", "k"])));
552
553 #[fuchsia::test]
554 async fn population_times_out() {
555 let (directory, mut stream) =
558 fidl::endpoints::create_proxy_and_stream::<fio::DirectoryMarker>();
559 fasync::Task::spawn(async move {
560 while stream.next().await.is_some() {
561 fasync::Timer::new(fasync::MonotonicInstant::after(
562 zx::MonotonicDuration::from_seconds(100000),
563 ))
564 .await;
565 }
566 })
567 .detach();
568
569 let handle = Arc::new(InspectHandle::directory(directory));
570 let container = UnpopulatedInspectDataContainer {
571 identity: EMPTY_IDENTITY.clone(),
572 inspect_handles: vec![Arc::downgrade(&handle)],
573 component_allowlist: ComponentAllowlist::new_disabled(),
574 };
575 let mut stream = container.populate(
576 0,
577 Arc::new(GlobalConnectionStats::new(Node::default())),
578 ftrace::Id::random(),
579 );
580 let res = stream.next().await.unwrap();
581 assert_eq!(res.snapshot.name, None);
582 assert_eq!(
583 res.snapshot.errors,
584 vec![schema::InspectError { message: TIMEOUT_MESSAGE.to_string() }]
585 );
586 }
587
588 #[fuchsia::test]
589 async fn no_inspect_files_do_not_give_an_error_response() {
590 let directory = Arc::new(InspectHandle::directory(
591 fuchsia_fs::directory::open_in_namespace("/tmp", fio::PERM_READABLE).unwrap(),
592 ));
593 let container = UnpopulatedInspectDataContainer {
594 identity: EMPTY_IDENTITY.clone(),
595 inspect_handles: vec![Arc::downgrade(&directory)],
596 component_allowlist: ComponentAllowlist::new_disabled(),
597 };
598 let mut stream = container.populate(
599 1000000,
600 Arc::new(GlobalConnectionStats::new(Node::default())),
601 ftrace::Id::random(),
602 );
603 assert!(stream.next().await.is_none());
604 }
605
606 #[fuchsia::test]
607 fn only_one_directory_proxy_is_populated() {
608 let _executor = fuchsia_async::LocalExecutor::new();
609 let (directory, _) = fidl::endpoints::create_proxy::<fio::DirectoryMarker>();
610 let mut container = InspectArtifactsContainer::default();
611 let _rx = container.push_handle(InspectHandle::directory(directory), |_| {});
612 let (directory2, _) = fidl::endpoints::create_proxy::<fio::DirectoryMarker>();
613 assert!(container.push_handle(InspectHandle::directory(directory2), |_| {}).is_none());
614 }
615
616 fn inspect_artifacts_container_with_one_dir() -> InspectArtifactsContainer {
617 let (directory, _) = fidl::endpoints::create_proxy_and_stream::<fio::DirectoryMarker>();
618 let handle = InspectHandle::directory(directory);
619 let mut container = InspectArtifactsContainer::default();
620 container.push_handle(handle, |_| {});
621 container
622 }
623
624 #[fuchsia::test]
625 fn prefilter_on_names_for_directories() {
626 let _executor = fuchsia_async::LocalExecutor::new();
627 let all_selector = fdiagnostics::Selector {
628 tree_names: Some(fdiagnostics::TreeNames::All(fdiagnostics::All {})),
629 component_selector: Some(fdiagnostics::ComponentSelector {
630 moniker_segments: Some(vec![
631 fdiagnostics::StringSelector::ExactMatch("o".to_string()),
632 fdiagnostics::StringSelector::ExactMatch("k".to_string()),
633 ]),
634 ..Default::default()
635 }),
636 tree_selector: Some(fdiagnostics::TreeSelector::SubtreeSelector(
637 fdiagnostics::SubtreeSelector {
638 node_path: vec![fdiagnostics::StringSelector::ExactMatch("root".to_string())],
639 },
640 )),
641 ..Default::default()
642 };
643 let selectors = Some(vec![all_selector]);
644
645 let iac = inspect_artifacts_container_with_one_dir();
646 let container = iac
647 .create_unpopulated(&O_K_IDENTITY, ComponentAllowlist::new_disabled(), &selectors)
648 .unwrap();
649
650 assert_eq!(1, container.inspect_handles.len());
651
652 let selector_with_wrong_tree_name = fdiagnostics::Selector {
653 tree_names: Some(fdiagnostics::TreeNames::Some(vec!["a".to_string()])),
654 component_selector: Some(fdiagnostics::ComponentSelector {
655 moniker_segments: Some(vec![
656 fdiagnostics::StringSelector::ExactMatch("o".to_string()),
657 fdiagnostics::StringSelector::ExactMatch("k".to_string()),
658 ]),
659 ..Default::default()
660 }),
661 tree_selector: Some(fdiagnostics::TreeSelector::SubtreeSelector(
662 fdiagnostics::SubtreeSelector {
663 node_path: vec![fdiagnostics::StringSelector::ExactMatch("root".to_string())],
664 },
665 )),
666 ..Default::default()
667 };
668 let selectors = Some(vec![selector_with_wrong_tree_name]);
669
670 let iac = inspect_artifacts_container_with_one_dir();
671 let container = iac
672 .create_unpopulated(&O_K_IDENTITY, ComponentAllowlist::new_disabled(), &selectors)
673 .unwrap();
674
675 assert_eq!(0, container.inspect_handles.len());
676
677 let selector_with_no_tree_names = fdiagnostics::Selector {
678 tree_names: None,
679 component_selector: Some(fdiagnostics::ComponentSelector {
680 moniker_segments: Some(vec![
681 fdiagnostics::StringSelector::ExactMatch("o".to_string()),
682 fdiagnostics::StringSelector::ExactMatch("k".to_string()),
683 ]),
684 ..Default::default()
685 }),
686 tree_selector: Some(fdiagnostics::TreeSelector::SubtreeSelector(
687 fdiagnostics::SubtreeSelector {
688 node_path: vec![fdiagnostics::StringSelector::ExactMatch("root".to_string())],
689 },
690 )),
691 ..Default::default()
692 };
693
694 let selectors = Some(vec![selector_with_no_tree_names]);
695
696 let iac = inspect_artifacts_container_with_one_dir();
697 let container = iac
698 .create_unpopulated(&O_K_IDENTITY, ComponentAllowlist::new_disabled(), &selectors)
699 .unwrap();
700 assert_eq!(1, container.inspect_handles.len());
701 }
702
703 fn inspect_artifacts_container_with_n_trees<'a>(
704 tree_names: impl IntoIterator<Item = &'a str>,
705 ) -> InspectArtifactsContainer {
706 let mut container = InspectArtifactsContainer::default();
707 for name in tree_names {
708 let (proxy, _) = fidl::endpoints::create_proxy::<finspect::TreeMarker>();
709 let handle = InspectHandle::tree(proxy, Some(name));
710 container.push_handle(handle, |_| {});
711 }
712 container
713 }
714
715 #[fuchsia::test]
716 fn name_filter_with_no_matches() {
717 let _executor = fuchsia_async::LocalExecutor::new();
718
719 let b_only = fdiagnostics::Selector {
720 tree_names: Some(fdiagnostics::TreeNames::Some(vec!["b".to_string()])),
721 component_selector: Some(fdiagnostics::ComponentSelector {
722 moniker_segments: Some(vec![
723 fdiagnostics::StringSelector::ExactMatch("o".to_string()),
724 fdiagnostics::StringSelector::ExactMatch("k".to_string()),
725 ]),
726 ..Default::default()
727 }),
728 tree_selector: Some(fdiagnostics::TreeSelector::SubtreeSelector(
729 fdiagnostics::SubtreeSelector {
730 node_path: vec![fdiagnostics::StringSelector::ExactMatch("root".to_string())],
731 },
732 )),
733 ..Default::default()
734 };
735 let selectors = Some(vec![b_only]);
736
737 let iac = inspect_artifacts_container_with_n_trees(["a"]);
738 let container = iac
739 .create_unpopulated(&O_K_IDENTITY, ComponentAllowlist::new_disabled(), &selectors)
740 .unwrap();
741 assert_eq!(0, container.inspect_handles.len());
742 }
743
744 #[fuchsia::test]
745 fn all_name_filter_matches_everything() {
746 let _executor = fuchsia_async::LocalExecutor::new();
747
748 let iac = inspect_artifacts_container_with_n_trees(["a", "b"]);
749 let container = iac
750 .create_unpopulated(
751 &O_K_IDENTITY,
752 ComponentAllowlist::new_disabled(),
753 &None::<&[fdiagnostics::Selector]>,
754 )
755 .unwrap();
756 assert_eq!(2, container.inspect_handles.len());
757
758 let all_selector = fdiagnostics::Selector {
759 tree_names: Some(fdiagnostics::TreeNames::All(fdiagnostics::All {})),
760 component_selector: Some(fdiagnostics::ComponentSelector {
761 moniker_segments: Some(vec![
762 fdiagnostics::StringSelector::ExactMatch("o".to_string()),
763 fdiagnostics::StringSelector::ExactMatch("k".to_string()),
764 ]),
765 ..Default::default()
766 }),
767 tree_selector: Some(fdiagnostics::TreeSelector::SubtreeSelector(
768 fdiagnostics::SubtreeSelector {
769 node_path: vec![fdiagnostics::StringSelector::ExactMatch("root".to_string())],
770 },
771 )),
772 ..Default::default()
773 };
774 let selectors = Some(vec![all_selector]);
775
776 let iac = inspect_artifacts_container_with_n_trees(["a", "b"]);
777 let container = iac
778 .create_unpopulated(&O_K_IDENTITY, ComponentAllowlist::new_disabled(), &selectors)
779 .unwrap();
780 assert_eq!(2, container.inspect_handles.len());
781 }
782
783 #[fuchsia::test]
784 fn none_name_filter_matches_root_only() {
785 let _executor = fuchsia_async::LocalExecutor::new();
786
787 let lists_each_name = fdiagnostics::Selector {
788 tree_names: None,
789 component_selector: Some(fdiagnostics::ComponentSelector {
790 moniker_segments: Some(vec![
791 fdiagnostics::StringSelector::ExactMatch("o".to_string()),
792 fdiagnostics::StringSelector::ExactMatch("k".to_string()),
793 ]),
794 ..Default::default()
795 }),
796 tree_selector: Some(fdiagnostics::TreeSelector::SubtreeSelector(
797 fdiagnostics::SubtreeSelector {
798 node_path: vec![fdiagnostics::StringSelector::ExactMatch("root".to_string())],
799 },
800 )),
801 ..Default::default()
802 };
803 let selectors = Some(vec![lists_each_name]);
804
805 let iac = inspect_artifacts_container_with_n_trees(["a", "b", "root"]);
806 let container = iac
807 .create_unpopulated(&O_K_IDENTITY, ComponentAllowlist::new_disabled(), &selectors)
808 .unwrap();
809 assert_eq!(1, container.inspect_handles.len());
810 }
811
812 #[fuchsia::test]
813 fn select_subset_of_names() {
814 let _executor = fuchsia_async::LocalExecutor::new();
815
816 let a_only = fdiagnostics::Selector {
817 tree_names: Some(fdiagnostics::TreeNames::Some(vec!["a".to_string()])),
818 component_selector: Some(fdiagnostics::ComponentSelector {
819 moniker_segments: Some(vec![
820 fdiagnostics::StringSelector::ExactMatch("o".to_string()),
821 fdiagnostics::StringSelector::ExactMatch("k".to_string()),
822 ]),
823 ..Default::default()
824 }),
825 tree_selector: Some(fdiagnostics::TreeSelector::SubtreeSelector(
826 fdiagnostics::SubtreeSelector {
827 node_path: vec![fdiagnostics::StringSelector::ExactMatch("root".to_string())],
828 },
829 )),
830 ..Default::default()
831 };
832 let selectors = Some(vec![a_only]);
833
834 let iac = inspect_artifacts_container_with_n_trees(["a", "b"]);
835 let container = iac
836 .create_unpopulated(&O_K_IDENTITY, ComponentAllowlist::new_disabled(), &selectors)
837 .unwrap();
838 assert_eq!(1, container.inspect_handles.len());
839
840 match container.inspect_handles[0].upgrade().unwrap().as_ref() {
841 InspectHandle::Tree { name: Some(n), .. } => assert_eq!(n.as_str(), "a"),
842 bad_handle => {
843 panic!("filtering failed, got {bad_handle:?}");
844 }
845 };
846 }
847
848 #[fuchsia::test]
849 fn names_are_case_sensitive() {
850 let _executor = fuchsia_async::LocalExecutor::new();
851
852 let a_but_wrong_case = fdiagnostics::Selector {
853 tree_names: Some(fdiagnostics::TreeNames::Some(vec!["A".to_string()])),
854 component_selector: Some(fdiagnostics::ComponentSelector {
855 moniker_segments: Some(vec![
856 fdiagnostics::StringSelector::ExactMatch("o".to_string()),
857 fdiagnostics::StringSelector::ExactMatch("k".to_string()),
858 ]),
859 ..Default::default()
860 }),
861 tree_selector: Some(fdiagnostics::TreeSelector::SubtreeSelector(
862 fdiagnostics::SubtreeSelector {
863 node_path: vec![fdiagnostics::StringSelector::ExactMatch("root".to_string())],
864 },
865 )),
866 ..Default::default()
867 };
868 let selectors = Some(vec![a_but_wrong_case]);
869
870 let iac = inspect_artifacts_container_with_n_trees(["a", "b"]);
871 let container = iac
872 .create_unpopulated(&O_K_IDENTITY, ComponentAllowlist::new_disabled(), &selectors)
873 .unwrap();
874 assert_eq!(0, container.inspect_handles.len());
875 }
876}