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