component_debug/
realm.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::io::{Directory, RemoteDirectory};
6use anyhow::Context;
7use cm_rust::{ComponentDecl, FidlIntoNative};
8use directed_graph::DirectedGraph;
9use flex_client::ProxyHasDomain;
10use fuchsia_async::TimeoutExt;
11use futures::TryFutureExt;
12use moniker::{Moniker, MonikerError};
13use std::convert::Infallible;
14use thiserror::Error;
15use {flex_fuchsia_component_decl as fcdecl, flex_fuchsia_io as fio, flex_fuchsia_sys2 as fsys};
16
17#[cfg(feature = "fdomain")]
18use fuchsia_fs_fdomain as fuchsia_fs;
19
20/// This value is somewhat arbitrarily chosen based on how long we expect a component to take to
21/// respond to a directory request. There is no clear answer for how long it should take a
22/// component to respond. A request may take unnaturally long if the host is connected to the
23/// target over a weak network connection. The target may be busy doing other work, resulting in a
24/// delayed response here. A request may never return a response, if the component is simply holding
25/// onto the directory handle without serving or dropping it. We should choose a value that balances
26/// a reasonable expectation from the component without making the user wait for too long.
27// TODO(https://fxbug.dev/42182421): Get network latency info from ffx to choose a better timeout.
28static DIR_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(1);
29
30#[cfg(feature = "serde")]
31use {schemars::JsonSchema, serde::Serialize};
32
33#[derive(Debug, Error)]
34pub enum ParseError {
35    #[error("{struct_name} FIDL is missing a field: {field_name}")]
36    MissingField { struct_name: &'static str, field_name: &'static str },
37
38    #[error("moniker could not be parsed successfully: {0}")]
39    BadMoniker(#[from] MonikerError),
40
41    #[error("{struct_name} FIDL enum is set to an unknown value")]
42    UnknownEnumValue { struct_name: &'static str },
43}
44
45#[derive(Debug, Error)]
46pub enum GetInstanceError {
47    #[error("instance {0} could not be found")]
48    InstanceNotFound(Moniker),
49
50    #[error("component manager could not parse {0}")]
51    BadMoniker(Moniker),
52
53    #[error(transparent)]
54    ParseError(#[from] ParseError),
55
56    #[error("component manager responded with an unknown error code")]
57    UnknownError,
58
59    #[error(transparent)]
60    Fidl(#[from] fidl::Error),
61}
62
63#[derive(Debug, Error)]
64pub enum GetAllInstancesError {
65    #[error("scoped root instance could not be found")]
66    InstanceNotFound,
67
68    #[error(transparent)]
69    ParseError(#[from] ParseError),
70
71    #[error("component manager responded with an unknown error code")]
72    UnknownError,
73
74    #[error("FIDL error: {0}")]
75    Fidl(#[from] fidl::Error),
76}
77
78#[derive(Debug, Error)]
79pub enum GetRuntimeError {
80    #[error(transparent)]
81    Fidl(#[from] fidl::Error),
82
83    #[error("Component manager could not open runtime dir: {0:?}")]
84    OpenError(fsys::OpenError),
85
86    #[error("timed out parsing dir")]
87    Timeout,
88
89    #[error("error parsing dir: {0}")]
90    ParseError(#[source] anyhow::Error),
91}
92
93impl From<Infallible> for GetRuntimeError {
94    fn from(_value: Infallible) -> Self {
95        unreachable!()
96    }
97}
98
99#[derive(Debug, Error)]
100pub enum GetOutgoingCapabilitiesError {
101    #[error(transparent)]
102    Fidl(#[from] fidl::Error),
103
104    #[error("Component manager could not open outgoing dir: {0:?}")]
105    OpenError(fsys::OpenError),
106
107    #[error("timed out parsing dir")]
108    Timeout,
109
110    #[error("error parsing dir: {0}")]
111    ParseError(#[source] anyhow::Error),
112}
113
114impl From<Infallible> for GetOutgoingCapabilitiesError {
115    fn from(_value: Infallible) -> Self {
116        unreachable!()
117    }
118}
119
120#[derive(Debug, Error)]
121pub enum GetMerkleRootError {
122    #[error(transparent)]
123    Fidl(#[from] fidl::Error),
124
125    #[error("Component manager could not open pacakage dir: {0:?}")]
126    OpenError(fsys::OpenError),
127
128    #[error("error reading meta file: {0}")]
129    ReadError(#[from] fuchsia_fs::file::ReadError),
130}
131
132impl From<Infallible> for GetMerkleRootError {
133    fn from(_value: Infallible) -> Self {
134        unreachable!()
135    }
136}
137
138#[derive(Debug, Error)]
139pub enum GetDeclarationError {
140    #[error(transparent)]
141    Fidl(#[from] fidl::Error),
142
143    #[error("instance {0} could not be found")]
144    InstanceNotFound(Moniker),
145
146    #[error("instance {0} is not resolved")]
147    InstanceNotResolved(Moniker),
148
149    #[error("component manager could not parse {0}")]
150    BadMoniker(Moniker),
151
152    #[error("component manager failed to encode the manifest")]
153    EncodeFailed,
154
155    #[error("component does not have {_0:?} as a valid location for a child")]
156    BadChildLocation(fsys::ChildLocation),
157
158    #[error("could not resolve component from URL {_0}")]
159    BadUrl(String),
160
161    #[error("component manifest could not be validated")]
162    InvalidManifest(#[from] cm_fidl_validator::error::ErrorList),
163
164    #[error("component manager responded with an unknown error code")]
165    UnknownError,
166}
167
168#[derive(Debug, Error)]
169pub enum GetStructuredConfigError {
170    #[error(transparent)]
171    Fidl(#[from] fidl::Error),
172
173    #[error(transparent)]
174    ParseError(#[from] ParseError),
175
176    #[error("instance {0} could not be found")]
177    InstanceNotFound(Moniker),
178
179    #[error("instance {0} is not resolved")]
180    InstanceNotResolved(Moniker),
181
182    #[error("component manager could not parse {0}")]
183    BadMoniker(Moniker),
184
185    #[error("component manager responded with an unknown error code")]
186    UnknownError,
187}
188
189#[cfg_attr(feature = "serde", derive(JsonSchema, Serialize))]
190#[derive(Debug, Clone)]
191pub struct Instance {
192    /// Moniker of the component.
193    pub moniker: Moniker,
194
195    /// URL of the component.
196    pub url: String,
197
198    /// Environment of the component from its parent.
199    pub environment: Option<String>,
200
201    /// Unique identifier of component.
202    pub instance_id: Option<String>,
203
204    /// Information about resolved state of instance.
205    pub resolved_info: Option<ResolvedInfo>,
206}
207
208impl TryFrom<fsys::Instance> for Instance {
209    type Error = ParseError;
210
211    fn try_from(instance: fsys::Instance) -> Result<Self, Self::Error> {
212        let moniker = instance
213            .moniker
214            .ok_or(ParseError::MissingField { struct_name: "Instance", field_name: "moniker" })?;
215        let moniker = Moniker::parse_str(&moniker)?;
216        let url = instance
217            .url
218            .ok_or(ParseError::MissingField { struct_name: "Instance", field_name: "url" })?;
219        let resolved_info = instance.resolved_info.map(|i| i.try_into()).transpose()?;
220
221        Ok(Self {
222            moniker,
223            url,
224            environment: instance.environment,
225            instance_id: instance.instance_id,
226            resolved_info,
227        })
228    }
229}
230
231/// Additional information about components that are resolved.
232#[cfg_attr(feature = "serde", derive(JsonSchema, Serialize))]
233#[derive(Debug, Clone)]
234pub struct ResolvedInfo {
235    pub resolved_url: String,
236    pub execution_info: Option<ExecutionInfo>,
237}
238
239impl TryFrom<fsys::ResolvedInfo> for ResolvedInfo {
240    type Error = ParseError;
241
242    fn try_from(resolved: fsys::ResolvedInfo) -> Result<Self, Self::Error> {
243        let resolved_url = resolved.resolved_url.ok_or(ParseError::MissingField {
244            struct_name: "ResolvedInfo",
245            field_name: "resolved_url",
246        })?;
247        let execution_info = resolved.execution_info.map(|i| i.try_into()).transpose()?;
248
249        Ok(Self { resolved_url, execution_info })
250    }
251}
252
253/// Additional information about components that are running.
254#[cfg_attr(feature = "serde", derive(JsonSchema, Serialize))]
255#[derive(Debug, Clone)]
256pub struct ExecutionInfo {
257    pub start_reason: String,
258}
259
260impl TryFrom<fsys::ExecutionInfo> for ExecutionInfo {
261    type Error = ParseError;
262
263    fn try_from(info: fsys::ExecutionInfo) -> Result<Self, Self::Error> {
264        let start_reason = info.start_reason.ok_or(ParseError::MissingField {
265            struct_name: "ExecutionInfo",
266            field_name: "start_reason",
267        })?;
268        Ok(Self { start_reason })
269    }
270}
271
272/// A single structured configuration key-value pair.
273#[cfg_attr(feature = "serde", derive(Serialize, JsonSchema))]
274#[derive(Debug, PartialEq)]
275pub struct ConfigField {
276    pub key: String,
277    pub value: String,
278}
279
280impl TryFrom<fcdecl::ResolvedConfigField> for ConfigField {
281    type Error = ParseError;
282
283    fn try_from(field: fcdecl::ResolvedConfigField) -> Result<Self, Self::Error> {
284        let value = match &field.value {
285            fcdecl::ConfigValue::Vector(value) => format!("{:#?}", value),
286            fcdecl::ConfigValue::Single(value) => format!("{:?}", value),
287            _ => {
288                return Err(ParseError::UnknownEnumValue {
289                    struct_name: "fuchsia.component.config.Value",
290                });
291            }
292        };
293        Ok(ConfigField { key: field.key, value })
294    }
295}
296
297#[cfg_attr(feature = "serde", derive(Serialize, JsonSchema))]
298#[derive(Debug)]
299pub enum Runtime {
300    Elf {
301        job_id: u64,
302        process_id: Option<u64>,
303        process_start_time: Option<i64>,
304        process_start_time_utc_estimate: Option<String>,
305    },
306    Unknown,
307}
308
309#[cfg_attr(feature = "serde", derive(Serialize))]
310#[derive(Debug, PartialEq)]
311pub enum Durability {
312    Transient,
313    SingleRun,
314}
315
316impl std::fmt::Display for Durability {
317    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
318        match self {
319            Self::Transient => write!(f, "Transient"),
320            Self::SingleRun => write!(f, "Single-run"),
321        }
322    }
323}
324
325impl From<fcdecl::Durability> for Durability {
326    fn from(value: fcdecl::Durability) -> Self {
327        match value {
328            fcdecl::Durability::SingleRun => Durability::SingleRun,
329            fcdecl::Durability::Transient => Durability::Transient,
330        }
331    }
332}
333
334pub async fn get_all_instances(
335    query: &fsys::RealmQueryProxy,
336) -> Result<Vec<Instance>, GetAllInstancesError> {
337    let result = query.get_all_instances().await?;
338
339    let iterator = match result {
340        Ok(iterator) => iterator,
341        Err(fsys::GetAllInstancesError::InstanceNotFound) => {
342            return Err(GetAllInstancesError::InstanceNotFound);
343        }
344        Err(_) => return Err(GetAllInstancesError::UnknownError),
345    };
346
347    let iterator = iterator.into_proxy();
348    let mut instances = vec![];
349
350    loop {
351        let mut batch = iterator.next().await?;
352        if batch.is_empty() {
353            break;
354        }
355        instances.append(&mut batch);
356    }
357
358    let instances: Result<Vec<Instance>, ParseError> =
359        instances.into_iter().map(|i| Instance::try_from(i)).collect();
360    Ok(instances?)
361}
362
363pub async fn get_resolved_declaration(
364    moniker: &Moniker,
365    realm_query: &fsys::RealmQueryProxy,
366) -> Result<ComponentDecl, GetDeclarationError> {
367    let moniker_str = moniker.to_string();
368    let iterator = match realm_query.get_resolved_declaration(&moniker_str).await? {
369        Ok(iterator) => Ok(iterator),
370        Err(fsys::GetDeclarationError::InstanceNotFound) => {
371            Err(GetDeclarationError::InstanceNotFound(moniker.clone()))
372        }
373        Err(fsys::GetDeclarationError::InstanceNotResolved) => {
374            Err(GetDeclarationError::InstanceNotResolved(moniker.clone()))
375        }
376        Err(fsys::GetDeclarationError::BadMoniker) => {
377            Err(GetDeclarationError::BadMoniker(moniker.clone()))
378        }
379        Err(fsys::GetDeclarationError::EncodeFailed) => Err(GetDeclarationError::EncodeFailed),
380        Err(_) => Err(GetDeclarationError::UnknownError),
381    }?;
382
383    let bytes = drain_manifest_bytes_iterator(iterator.into_proxy()).await?;
384    let manifest = fidl::unpersist::<fcdecl::Component>(&bytes)?;
385    let manifest = manifest.fidl_into_native();
386    Ok(manifest)
387}
388
389async fn drain_manifest_bytes_iterator(
390    iterator: fsys::ManifestBytesIteratorProxy,
391) -> Result<Vec<u8>, fidl::Error> {
392    let mut bytes = vec![];
393
394    loop {
395        let mut batch = iterator.next().await?;
396        if batch.is_empty() {
397            break;
398        }
399        bytes.append(&mut batch);
400    }
401
402    Ok(bytes)
403}
404
405pub async fn resolve_declaration(
406    realm_query: &fsys::RealmQueryProxy,
407    parent: &Moniker,
408    child_location: &fsys::ChildLocation,
409    url: &str,
410) -> Result<ComponentDecl, GetDeclarationError> {
411    let iterator = realm_query
412        .resolve_declaration(&parent.to_string(), child_location, url)
413        .await?
414        .map_err(|e| match e {
415            fsys::GetDeclarationError::InstanceNotFound => {
416                GetDeclarationError::InstanceNotFound(parent.clone())
417            }
418            fsys::GetDeclarationError::InstanceNotResolved => {
419                GetDeclarationError::InstanceNotResolved(parent.clone())
420            }
421            fsys::GetDeclarationError::BadMoniker => {
422                GetDeclarationError::BadMoniker(parent.clone())
423            }
424            fsys::GetDeclarationError::EncodeFailed => GetDeclarationError::EncodeFailed,
425            fsys::GetDeclarationError::BadChildLocation => {
426                GetDeclarationError::BadChildLocation(child_location.to_owned())
427            }
428            fsys::GetDeclarationError::BadUrl => GetDeclarationError::BadUrl(url.to_owned()),
429            _ => GetDeclarationError::UnknownError,
430        })?;
431
432    let bytes = drain_manifest_bytes_iterator(iterator.into_proxy()).await?;
433    let manifest = fidl::unpersist::<fcdecl::Component>(&bytes)?;
434    cm_fidl_validator::validate(&manifest, &mut DirectedGraph::new())?;
435    let manifest = manifest.fidl_into_native();
436    Ok(manifest)
437}
438
439pub async fn get_config_fields(
440    moniker: &Moniker,
441    realm_query: &fsys::RealmQueryProxy,
442) -> Result<Option<Vec<ConfigField>>, GetStructuredConfigError> {
443    // Parse the runtime directory and add it into the State object
444    let moniker_str = moniker.to_string();
445    match realm_query.get_structured_config(&moniker_str).await? {
446        Ok(config) => {
447            let fields: Result<Vec<ConfigField>, ParseError> =
448                config.fields.into_iter().map(|f| f.try_into()).collect();
449            let fields = fields?;
450            Ok(Some(fields))
451        }
452        Err(fsys::GetStructuredConfigError::InstanceNotFound) => {
453            Err(GetStructuredConfigError::InstanceNotFound(moniker.clone()))
454        }
455        Err(fsys::GetStructuredConfigError::InstanceNotResolved) => {
456            Err(GetStructuredConfigError::InstanceNotResolved(moniker.clone()))
457        }
458        Err(fsys::GetStructuredConfigError::NoConfig) => Ok(None),
459        Err(fsys::GetStructuredConfigError::BadMoniker) => {
460            Err(GetStructuredConfigError::BadMoniker(moniker.clone()))
461        }
462        Err(_) => Err(GetStructuredConfigError::UnknownError),
463    }
464}
465
466pub async fn get_runtime(
467    moniker: &Moniker,
468    realm_query: &fsys::RealmQueryProxy,
469) -> Result<Runtime, GetRuntimeError> {
470    // Parse the runtime directory and add it into the State object
471    let moniker_str = moniker.to_string();
472    let (runtime_dir, server_end) = realm_query.domain().create_proxy::<fio::DirectoryMarker>();
473    let runtime_dir = RemoteDirectory::from_proxy(runtime_dir);
474    realm_query
475        .open_directory(&moniker_str, fsys::OpenDirType::RuntimeDir, server_end)
476        .await?
477        .map_err(|e| GetRuntimeError::OpenError(e))?;
478    parse_runtime_from_dir(runtime_dir)
479        .map_err(|e| GetRuntimeError::ParseError(e))
480        .on_timeout(DIR_TIMEOUT, || Err(GetRuntimeError::Timeout))
481        .await
482}
483
484async fn parse_runtime_from_dir(runtime_dir: RemoteDirectory) -> Result<Runtime, anyhow::Error> {
485    // Some runners may not serve the runtime directory, so attempting to get the entries
486    // may fail. This is normal and should be treated as no ELF runtime.
487    if let Ok(true) = runtime_dir.exists("elf").await {
488        let elf_dir = runtime_dir.open_dir_readonly("elf")?;
489
490        let (job_id, process_id, process_start_time, process_start_time_utc_estimate) = futures::join!(
491            elf_dir.read_file("job_id"),
492            elf_dir.read_file("process_id"),
493            elf_dir.read_file("process_start_time"),
494            elf_dir.read_file("process_start_time_utc_estimate"),
495        );
496
497        let job_id = job_id?.parse::<u64>().context("Job ID is not u64")?;
498
499        let process_id = match process_id {
500            Ok(id) => Some(id.parse::<u64>().context("Process ID is not u64")?),
501            Err(_) => None,
502        };
503
504        let process_start_time =
505            process_start_time.ok().map(|time_string| time_string.parse::<i64>().ok()).flatten();
506
507        let process_start_time_utc_estimate = process_start_time_utc_estimate.ok();
508
509        Ok(Runtime::Elf { job_id, process_id, process_start_time, process_start_time_utc_estimate })
510    } else {
511        Ok(Runtime::Unknown)
512    }
513}
514
515pub async fn get_instance(
516    moniker: &Moniker,
517    realm_query: &fsys::RealmQueryProxy,
518) -> Result<Instance, GetInstanceError> {
519    let moniker_str = moniker.to_string();
520    match realm_query.get_instance(&moniker_str).await? {
521        Ok(instance) => {
522            let instance = instance.try_into()?;
523            Ok(instance)
524        }
525        Err(fsys::GetInstanceError::InstanceNotFound) => {
526            Err(GetInstanceError::InstanceNotFound(moniker.clone()))
527        }
528        Err(fsys::GetInstanceError::BadMoniker) => {
529            Err(GetInstanceError::BadMoniker(moniker.clone()))
530        }
531        Err(_) => Err(GetInstanceError::UnknownError),
532    }
533}
534
535pub async fn get_outgoing_capabilities(
536    moniker: &Moniker,
537    realm_query: &fsys::RealmQueryProxy,
538) -> Result<Vec<String>, GetOutgoingCapabilitiesError> {
539    let moniker_str = moniker.to_string();
540    let (out_dir, server_end) = realm_query.domain().create_proxy::<fio::DirectoryMarker>();
541    realm_query
542        .open_directory(&moniker_str, fsys::OpenDirType::OutgoingDir, server_end)
543        .await?
544        .map_err(|e| GetOutgoingCapabilitiesError::OpenError(e))?;
545    let out_dir = RemoteDirectory::from_proxy(out_dir);
546    get_capabilities(out_dir)
547        .map_err(|e| GetOutgoingCapabilitiesError::ParseError(e))
548        .on_timeout(DIR_TIMEOUT, || Err(GetOutgoingCapabilitiesError::Timeout))
549        .await
550}
551
552pub async fn get_merkle_root(
553    moniker: &Moniker,
554    realm_query: &fsys::RealmQueryProxy,
555) -> Result<String, GetMerkleRootError> {
556    let moniker_str = moniker.to_string();
557    let (package_dir, server_end) = realm_query.domain().create_proxy::<fio::DirectoryMarker>();
558    realm_query
559        .open_directory(&moniker_str, fsys::OpenDirType::PackageDir, server_end)
560        .await?
561        .map_err(|e| GetMerkleRootError::OpenError(e))?;
562    let (meta_file, server_end) = realm_query.domain().create_proxy::<fio::FileMarker>();
563    package_dir
564        .open(
565            "meta",
566            fio::PERM_READABLE | fio::Flags::PROTOCOL_FILE,
567            &Default::default(),
568            server_end.into_channel(),
569        )
570        .map_err(|e| GetMerkleRootError::Fidl(e))?;
571    let merkle_root = fuchsia_fs::file::read_to_string(&meta_file).await?;
572    Ok(merkle_root)
573}
574
575// Get all entries in a capabilities directory. If there is a "svc" directory, traverse it and
576// collect all protocol names as well.
577async fn get_capabilities(capability_dir: RemoteDirectory) -> Result<Vec<String>, anyhow::Error> {
578    let mut entries = capability_dir.entry_names().await?;
579
580    for (index, name) in entries.iter().enumerate() {
581        if name == "svc" {
582            entries.remove(index);
583            let svc_dir = capability_dir.open_dir_readonly("svc")?;
584            let mut svc_entries = svc_dir.entry_names().await?;
585            entries.append(&mut svc_entries);
586            break;
587        }
588    }
589
590    entries.sort_unstable();
591    Ok(entries)
592}
593
594#[cfg(test)]
595mod tests {
596    use super::*;
597    use crate::test_utils::*;
598    use fidl_fuchsia_component_decl as fdecl;
599    use std::collections::HashMap;
600    use tempfile::TempDir;
601
602    #[fuchsia::test]
603    async fn test_get_all_instances() {
604        let query = serve_realm_query_instances(vec![fsys::Instance {
605            moniker: Some("./my_foo".to_string()),
606            url: Some("#meta/foo.cm".to_string()),
607            instance_id: Some("1234567890".to_string()),
608            resolved_info: Some(fsys::ResolvedInfo {
609                resolved_url: Some("fuchsia-pkg://fuchsia.com/foo#meta/foo.cm".to_string()),
610                execution_info: Some(fsys::ExecutionInfo {
611                    start_reason: Some("Debugging Workflow".to_string()),
612                    ..Default::default()
613                }),
614                ..Default::default()
615            }),
616            ..Default::default()
617        }]);
618
619        let mut instances = get_all_instances(&query).await.unwrap();
620        assert_eq!(instances.len(), 1);
621        let instance = instances.remove(0);
622
623        let moniker = Moniker::parse_str("/my_foo").unwrap();
624        assert_eq!(instance.moniker, moniker);
625        assert_eq!(instance.url, "#meta/foo.cm");
626        assert_eq!(instance.instance_id.unwrap(), "1234567890");
627        assert!(instance.resolved_info.is_some());
628
629        let resolved = instance.resolved_info.unwrap();
630        assert_eq!(resolved.resolved_url, "fuchsia-pkg://fuchsia.com/foo#meta/foo.cm");
631
632        let execution_info = resolved.execution_info.unwrap();
633        assert_eq!(execution_info.start_reason, "Debugging Workflow".to_string());
634    }
635
636    #[fuchsia::test]
637    async fn test_get_manifest() {
638        let query = serve_realm_query(
639            vec![],
640            HashMap::from([(
641                "./my_foo".to_string(),
642                fdecl::Component {
643                    uses: Some(vec![fdecl::Use::Protocol(fdecl::UseProtocol {
644                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef)),
645                        source_name: Some("fuchsia.foo.bar".to_string()),
646                        target_path: Some("/svc/fuchsia.foo.bar".to_string()),
647                        dependency_type: Some(fdecl::DependencyType::Strong),
648                        availability: Some(fdecl::Availability::Required),
649                        ..Default::default()
650                    })]),
651                    exposes: Some(vec![fdecl::Expose::Protocol(fdecl::ExposeProtocol {
652                        source: Some(fdecl::Ref::Self_(fdecl::SelfRef)),
653                        source_name: Some("fuchsia.bar.baz".to_string()),
654                        target: Some(fdecl::Ref::Parent(fdecl::ParentRef)),
655                        target_name: Some("fuchsia.bar.baz".to_string()),
656                        ..Default::default()
657                    })]),
658                    capabilities: Some(vec![fdecl::Capability::Protocol(fdecl::Protocol {
659                        name: Some("fuchsia.bar.baz".to_string()),
660                        source_path: Some("/svc/fuchsia.bar.baz".to_string()),
661                        ..Default::default()
662                    })]),
663                    ..Default::default()
664                },
665            )]),
666            HashMap::new(),
667            HashMap::new(),
668        );
669
670        let moniker = Moniker::parse_str("/my_foo").unwrap();
671        let manifest = get_resolved_declaration(&moniker, &query).await.unwrap();
672
673        assert_eq!(manifest.uses.len(), 1);
674        assert_eq!(manifest.exposes.len(), 1);
675    }
676
677    pub fn create_pkg_dir() -> TempDir {
678        let temp_dir = TempDir::new_in("/tmp").unwrap();
679        let root = temp_dir.path();
680
681        std::fs::write(root.join("meta"), "1234").unwrap();
682
683        temp_dir
684    }
685
686    #[fuchsia::test]
687    async fn test_get_merkle_root() {
688        let pkg_dir = create_pkg_dir();
689        let query = serve_realm_query(
690            vec![],
691            HashMap::new(),
692            HashMap::new(),
693            HashMap::from([(("./my_foo".to_string(), fsys::OpenDirType::PackageDir), pkg_dir)]),
694        );
695
696        let moniker = Moniker::parse_str("/my_foo").unwrap();
697        let merkle_root = get_merkle_root(&moniker, &query).await.unwrap();
698
699        assert_eq!(merkle_root, "1234");
700    }
701
702    #[fuchsia::test]
703    async fn test_get_instance() {
704        let realm_query = serve_realm_query_instances(vec![fsys::Instance {
705            moniker: Some("./my_foo".to_string()),
706            url: Some("#meta/foo.cm".to_string()),
707            instance_id: Some("1234567890".to_string()),
708            resolved_info: Some(fsys::ResolvedInfo {
709                resolved_url: Some("fuchsia-pkg://fuchsia.com/foo#meta/foo.cm".to_string()),
710                execution_info: Some(fsys::ExecutionInfo {
711                    start_reason: Some("Debugging Workflow".to_string()),
712                    ..Default::default()
713                }),
714                ..Default::default()
715            }),
716            ..Default::default()
717        }]);
718
719        let moniker = Moniker::parse_str("/my_foo").unwrap();
720        let instance = get_instance(&moniker, &realm_query).await.unwrap();
721
722        assert_eq!(instance.moniker, moniker);
723        assert_eq!(instance.url, "#meta/foo.cm");
724        assert_eq!(instance.instance_id.unwrap(), "1234567890");
725        assert!(instance.resolved_info.is_some());
726
727        let resolved = instance.resolved_info.unwrap();
728        assert_eq!(resolved.resolved_url, "fuchsia-pkg://fuchsia.com/foo#meta/foo.cm");
729
730        let execution_info = resolved.execution_info.unwrap();
731        assert_eq!(execution_info.start_reason, "Debugging Workflow".to_string());
732    }
733}