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