test_manager_lib/
above_root_capabilities.rs

1// Copyright 2022 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::constants::{
6    HERMETIC_RESOLVER_REALM_NAME, TEST_ROOT_COLLECTION, TEST_TYPE_REALM_MAP, WRAPPER_REALM_NAME,
7};
8use anyhow::{format_err, Error};
9use fuchsia_component_test::error::Error as RealmBuilderError;
10use fuchsia_component_test::{Capability, RealmBuilder, Ref, RefContext, Route, SubRealmBuilder};
11use std::collections::HashMap;
12use {fidl_fuchsia_component_decl as fdecl, fidl_fuchsia_component_test as ftest};
13
14#[derive(Default)]
15struct CollectionData {
16    capabilities: Vec<ftest::Capability>,
17    required_event_streams: RequiredEventStreams,
18}
19
20#[derive(Default)]
21struct RequiredEventStreams {
22    capability_requested: bool,
23}
24
25impl RequiredEventStreams {
26    fn validate(&self) -> bool {
27        self.capability_requested
28    }
29}
30
31pub struct AboveRootCapabilitiesForTest {
32    collection_data: HashMap<&'static str, CollectionData>,
33}
34
35impl AboveRootCapabilitiesForTest {
36    pub async fn new(manifest_name: &str) -> Result<Self, Error> {
37        let path = format!("/pkg/meta/{}", manifest_name);
38        let file_proxy = fuchsia_fs::file::open_in_namespace(&path, fuchsia_fs::PERM_READABLE)?;
39        let component_decl = fuchsia_fs::file::read_fidl::<fdecl::Component>(&file_proxy).await?;
40        let collection_data = Self::load(component_decl);
41        Ok(Self { collection_data })
42    }
43
44    #[cfg(test)]
45    pub fn new_empty_for_tests() -> Self {
46        let empty_collection_data = HashMap::new();
47        Self { collection_data: empty_collection_data }
48    }
49
50    pub fn validate(&self, collection: &str) -> Result<(), Error> {
51        match self.collection_data.get(collection) {
52            Some(c) if !c.required_event_streams.validate() => {
53                return Err(format_err!(
54                    "The collection `{collection}` must be routed the events \
55                `capability_requested` from `parent` scoped \
56                to it"
57                ));
58            }
59            _ => Ok(()),
60        }
61    }
62
63    pub async fn apply(
64        &self,
65        collection: &str,
66        builder: &RealmBuilder,
67        wrapper_realm: &SubRealmBuilder,
68    ) -> Result<(), RealmBuilderError> {
69        if !self.collection_data.contains_key(collection) {
70            return Ok(());
71        }
72        for capability in &self.collection_data[collection].capabilities {
73            let (capability_for_test_wrapper, capability_for_test_root) =
74                if let ftest::Capability::EventStream(event_stream) = &capability {
75                    let mut test_wrapper_event_stream = event_stream.clone();
76                    let (wrapper_realm, _) =
77                        Ref::child(WRAPPER_REALM_NAME).into_fidl(RefContext::Source);
78                    let (hermetic_resolver_realm, _) =
79                        Ref::child(HERMETIC_RESOLVER_REALM_NAME).into_fidl(RefContext::Source);
80                    let (test_root_collection, _) =
81                        Ref::collection(TEST_ROOT_COLLECTION).into_fidl(RefContext::Source);
82                    test_wrapper_event_stream.scope = Some(vec![wrapper_realm]);
83                    let mut test_root_event_stream = event_stream.clone();
84                    test_root_event_stream.scope =
85                        Some(vec![test_root_collection, hermetic_resolver_realm]);
86                    (
87                        ftest::Capability::EventStream(test_wrapper_event_stream),
88                        ftest::Capability::EventStream(test_root_event_stream),
89                    )
90                } else {
91                    (capability.clone(), capability.clone())
92                };
93            builder
94                .add_route(
95                    Route::new()
96                        .capability(capability_for_test_wrapper.clone())
97                        .from(Ref::parent())
98                        .to(wrapper_realm),
99                )
100                .await?;
101            wrapper_realm
102                .add_route(
103                    Route::new()
104                        .capability(capability_for_test_root.clone())
105                        .from(Ref::parent())
106                        .to(Ref::collection(TEST_ROOT_COLLECTION)),
107                )
108                .await?;
109        }
110        Ok(())
111    }
112
113    fn load(decl: fdecl::Component) -> HashMap<&'static str, CollectionData> {
114        let mut collection_data: HashMap<_, _> =
115            TEST_TYPE_REALM_MAP.values().map(|v| (*v, CollectionData::default())).collect();
116        for offer_decl in decl.offers.unwrap_or(vec![]) {
117            match offer_decl {
118                fdecl::Offer::Protocol(fdecl::OfferProtocol {
119                    target: Some(fdecl::Ref::Collection(fdecl::CollectionRef { name })),
120                    target_name: Some(target_name),
121                    ..
122                }) if collection_data.contains_key(name.as_str())
123                    && target_name != "fuchsia.logger.LogSink"
124                    && target_name != "fuchsia.inspect.InspectSink" =>
125                {
126                    collection_data.get_mut(name.as_str()).unwrap().capabilities.push(
127                        Capability::protocol_by_name(target_name)
128                            .availability_same_as_target()
129                            .into(),
130                    );
131                }
132                fdecl::Offer::Directory(fdecl::OfferDirectory {
133                    target: Some(fdecl::Ref::Collection(fdecl::CollectionRef { name })),
134                    target_name: Some(target_name),
135                    ..
136                }) if collection_data.contains_key(name.as_str()) => {
137                    collection_data.get_mut(name.as_str()).unwrap().capabilities.push(
138                        Capability::directory(target_name).availability_same_as_target().into(),
139                    );
140                }
141                fdecl::Offer::Storage(fdecl::OfferStorage {
142                    target: Some(fdecl::Ref::Collection(fdecl::CollectionRef { name })),
143                    target_name: Some(target_name),
144                    ..
145                }) if collection_data.contains_key(name.as_str()) => {
146                    let use_path = format!("/{}", target_name);
147                    collection_data.get_mut(name.as_str()).unwrap().capabilities.push(
148                        Capability::storage(target_name)
149                            .path(use_path)
150                            .availability_same_as_target()
151                            .into(),
152                    );
153                }
154                fdecl::Offer::EventStream(fdecl::OfferEventStream {
155                    target: Some(fdecl::Ref::Collection(fdecl::CollectionRef { name })),
156                    target_name: Some(target_name),
157                    source: Some(source),
158                    scope,
159                    ..
160                }) if collection_data.contains_key(name.as_str()) => {
161                    collection_data
162                        .get_mut(name.as_str())
163                        .unwrap()
164                        .capabilities
165                        .push(Capability::event_stream(target_name.clone()).into());
166
167                    // Keep track of relevant event streams being offered from parent to the
168                    // collection scoped to it.
169                    let coll_ref =
170                        fdecl::Ref::Collection(fdecl::CollectionRef { name: name.clone() });
171                    if target_name == "capability_requested"
172                        && matches!(source, fdecl::Ref::Parent(_))
173                        && scope.map(|s| s.contains(&coll_ref)).unwrap_or(false)
174                    {
175                        let entry = collection_data.get_mut(name.as_str()).unwrap();
176                        entry.required_event_streams.capability_requested =
177                            entry.required_event_streams.capability_requested
178                                || target_name == "capability_requested";
179                    }
180                }
181                fdecl::Offer::Service(fdecl::OfferService {
182                    target: Some(fdecl::Ref::Collection(fdecl::CollectionRef { name })),
183                    target_name: Some(target_name),
184                    ..
185                }) if collection_data.contains_key(name.as_str()) => {
186                    collection_data.get_mut(name.as_str()).unwrap().capabilities.push(
187                        Capability::service_by_name(target_name)
188                            .availability_same_as_target()
189                            .into(),
190                    );
191                }
192                fdecl::Offer::Runner(fdecl::OfferRunner {
193                    target: Some(fdecl::Ref::Collection(fdecl::CollectionRef { name })),
194                    ..
195                })
196                | fdecl::Offer::Resolver(fdecl::OfferResolver {
197                    target: Some(fdecl::Ref::Collection(fdecl::CollectionRef { name })),
198                    ..
199                }) if collection_data.contains_key(name.as_str()) => {
200                    unimplemented!("Runners and resolvers are not supported by realm builder");
201                }
202                _ => {
203                    // Ignore anything else that is not routed to test collections
204                }
205            }
206        }
207        collection_data
208    }
209}