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 flex_client::ProxyHasDomain;
9use fuchsia_async::TimeoutExt;
10use futures::TryFutureExt;
11use moniker::{Moniker, MonikerError};
12use std::convert::Infallible;
13use thiserror::Error;
14use {flex_fuchsia_component_decl as fcdecl, flex_fuchsia_io as fio, flex_fuchsia_sys2 as fsys};
15
16#[cfg(feature = "fdomain")]
17use fuchsia_fs_fdomain as fuchsia_fs;
18
19/// This value is somewhat arbitrarily chosen based on how long we expect a component to take to
20/// respond to a directory request. There is no clear answer for how long it should take a
21/// component to respond. A request may take unnaturally long if the host is connected to the
22/// target over a weak network connection. The target may be busy doing other work, resulting in a
23/// delayed response here. A request may never return a response, if the component is simply holding
24/// onto the directory handle without serving or dropping it. We should choose a value that balances
25/// a reasonable expectation from the component without making the user wait for too long.
26// TODO(https://fxbug.dev/42182421): Get network latency info from ffx to choose a better timeout.
27static DIR_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(1);
28
29#[cfg(feature = "serde")]
30use {schemars::JsonSchema, serde::Serialize};
31
32#[derive(Debug, Error)]
33pub enum ParseError {
34    #[error("{struct_name} FIDL is missing a field: {field_name}")]
35    MissingField { struct_name: &'static str, field_name: &'static str },
36
37    #[error("moniker could not be parsed successfully: {0}")]
38    BadMoniker(#[from] MonikerError),
39
40    #[error("{struct_name} FIDL enum is set to an unknown value")]
41    UnknownEnumValue { struct_name: &'static str },
42}
43
44#[derive(Debug, Error)]
45pub enum GetInstanceError {
46    #[error("instance {0} could not be found")]
47    InstanceNotFound(Moniker),
48
49    #[error("component manager could not parse {0}")]
50    BadMoniker(Moniker),
51
52    #[error(transparent)]
53    ParseError(#[from] ParseError),
54
55    #[error("component manager responded with an unknown error code")]
56    UnknownError,
57
58    #[error(transparent)]
59    Fidl(#[from] fidl::Error),
60}
61
62#[derive(Debug, Error)]
63pub enum GetAllInstancesError {
64    #[error("scoped root instance could not be found")]
65    InstanceNotFound,
66
67    #[error(transparent)]
68    ParseError(#[from] ParseError),
69
70    #[error("component manager responded with an unknown error code")]
71    UnknownError,
72
73    #[error("FIDL error: {0}")]
74    Fidl(#[from] fidl::Error),
75}
76
77#[derive(Debug, Error)]
78pub enum GetRuntimeError {
79    #[error(transparent)]
80    Fidl(#[from] fidl::Error),
81
82    #[error("Component manager could not open runtime dir: {0:?}")]
83    OpenError(fsys::OpenError),
84
85    #[error("timed out parsing dir")]
86    Timeout,
87
88    #[error("error parsing dir: {0}")]
89    ParseError(#[source] anyhow::Error),
90}
91
92impl From<Infallible> for GetRuntimeError {
93    fn from(_value: Infallible) -> Self {
94        unreachable!()
95    }
96}
97
98#[derive(Debug, Error)]
99pub enum GetOutgoingCapabilitiesError {
100    #[error(transparent)]
101    Fidl(#[from] fidl::Error),
102
103    #[error("Component manager could not open outgoing dir: {0:?}")]
104    OpenError(fsys::OpenError),
105
106    #[error("timed out parsing dir")]
107    Timeout,
108
109    #[error("error parsing dir: {0}")]
110    ParseError(#[source] anyhow::Error),
111}
112
113impl From<Infallible> for GetOutgoingCapabilitiesError {
114    fn from(_value: Infallible) -> Self {
115        unreachable!()
116    }
117}
118
119#[derive(Debug, Error)]
120pub enum GetMerkleRootError {
121    #[error(transparent)]
122    Fidl(#[from] fidl::Error),
123
124    #[error("Component manager could not open pacakage dir: {0:?}")]
125    OpenError(fsys::OpenError),
126
127    #[error("error reading meta file: {0}")]
128    ReadError(#[from] fuchsia_fs::file::ReadError),
129}
130
131impl From<Infallible> for GetMerkleRootError {
132    fn from(_value: Infallible) -> Self {
133        unreachable!()
134    }
135}
136
137#[derive(Debug, Error)]
138pub enum GetDeclarationError {
139    #[error(transparent)]
140    Fidl(#[from] fidl::Error),
141
142    #[error("instance {0} could not be found")]
143    InstanceNotFound(Moniker),
144
145    #[error("instance {0} is not resolved")]
146    InstanceNotResolved(Moniker),
147
148    #[error("component manager could not parse {0}")]
149    BadMoniker(Moniker),
150
151    #[error("component manager failed to encode the manifest")]
152    EncodeFailed,
153
154    #[error("component does not have {_0:?} as a valid location for a child")]
155    BadChildLocation(fsys::ChildLocation),
156
157    #[error("could not resolve component from URL {_0}")]
158    BadUrl(String),
159
160    #[error("component manifest could not be validated")]
161    InvalidManifest(#[from] cm_fidl_validator::error::ErrorList),
162
163    #[error("component manager responded with an unknown error code")]
164    UnknownError,
165}
166
167#[derive(Debug, Error)]
168pub enum GetStructuredConfigError {
169    #[error(transparent)]
170    Fidl(#[from] fidl::Error),
171
172    #[error(transparent)]
173    ParseError(#[from] ParseError),
174
175    #[error("instance {0} could not be found")]
176    InstanceNotFound(Moniker),
177
178    #[error("instance {0} is not resolved")]
179    InstanceNotResolved(Moniker),
180
181    #[error("component manager could not parse {0}")]
182    BadMoniker(Moniker),
183
184    #[error("component manager responded with an unknown error code")]
185    UnknownError,
186}
187
188#[cfg_attr(feature = "serde", derive(JsonSchema, Serialize))]
189#[derive(Debug, Clone)]
190pub struct Instance {
191    /// Moniker of the component.
192    pub moniker: Moniker,
193
194    /// URL of the component.
195    pub url: String,
196
197    /// Environment of the component from its parent.
198    pub environment: Option<String>,
199
200    /// Unique identifier of component.
201    pub instance_id: Option<String>,
202
203    /// Information about resolved state of instance.
204    pub resolved_info: Option<ResolvedInfo>,
205}
206
207impl TryFrom<fsys::Instance> for Instance {
208    type Error = ParseError;
209
210    fn try_from(instance: fsys::Instance) -> Result<Self, Self::Error> {
211        let moniker = instance
212            .moniker
213            .ok_or(ParseError::MissingField { struct_name: "Instance", field_name: "moniker" })?;
214        let moniker = Moniker::parse_str(&moniker)?;
215        let url = instance
216            .url
217            .ok_or(ParseError::MissingField { struct_name: "Instance", field_name: "url" })?;
218        let resolved_info = instance.resolved_info.map(|i| i.try_into()).transpose()?;
219
220        Ok(Self {
221            moniker,
222            url,
223            environment: instance.environment,
224            instance_id: instance.instance_id,
225            resolved_info,
226        })
227    }
228}
229
230/// Additional information about components that are resolved.
231#[cfg_attr(feature = "serde", derive(JsonSchema, Serialize))]
232#[derive(Debug, Clone)]
233pub struct ResolvedInfo {
234    pub resolved_url: String,
235    pub execution_info: Option<ExecutionInfo>,
236}
237
238impl TryFrom<fsys::ResolvedInfo> for ResolvedInfo {
239    type Error = ParseError;
240
241    fn try_from(resolved: fsys::ResolvedInfo) -> Result<Self, Self::Error> {
242        let resolved_url = resolved.resolved_url.ok_or(ParseError::MissingField {
243            struct_name: "ResolvedInfo",
244            field_name: "resolved_url",
245        })?;
246        let execution_info = resolved.execution_info.map(|i| i.try_into()).transpose()?;
247
248        Ok(Self { resolved_url, execution_info })
249    }
250}
251
252/// Additional information about components that are running.
253#[cfg_attr(feature = "serde", derive(JsonSchema, Serialize))]
254#[derive(Debug, Clone)]
255pub struct ExecutionInfo {
256    pub start_reason: String,
257}
258
259impl TryFrom<fsys::ExecutionInfo> for ExecutionInfo {
260    type Error = ParseError;
261
262    fn try_from(info: fsys::ExecutionInfo) -> Result<Self, Self::Error> {
263        let start_reason = info.start_reason.ok_or(ParseError::MissingField {
264            struct_name: "ExecutionInfo",
265            field_name: "start_reason",
266        })?;
267        Ok(Self { start_reason })
268    }
269}
270
271/// A single structured configuration key-value pair.
272#[cfg_attr(feature = "serde", derive(Serialize, JsonSchema))]
273#[derive(Debug, PartialEq)]
274pub struct ConfigField {
275    pub key: String,
276    pub value: String,
277}
278
279impl TryFrom<fcdecl::ResolvedConfigField> for ConfigField {
280    type Error = ParseError;
281
282    fn try_from(field: fcdecl::ResolvedConfigField) -> Result<Self, Self::Error> {
283        let value = match &field.value {
284            fcdecl::ConfigValue::Vector(value) => format!("{:#?}", value),
285            fcdecl::ConfigValue::Single(value) => format!("{:?}", value),
286            _ => {
287                return Err(ParseError::UnknownEnumValue {
288                    struct_name: "fuchsia.component.config.Value",
289                })
290            }
291        };
292        Ok(ConfigField { key: field.key, value })
293    }
294}
295
296#[cfg_attr(feature = "serde", derive(Serialize, JsonSchema))]
297#[derive(Debug)]
298pub enum Runtime {
299    Elf {
300        job_id: u64,
301        process_id: Option<u64>,
302        process_start_time: Option<i64>,
303        process_start_time_utc_estimate: Option<String>,
304    },
305    Unknown,
306}
307
308#[cfg_attr(feature = "serde", derive(Serialize))]
309#[derive(Debug, PartialEq)]
310pub enum Durability {
311    Transient,
312    SingleRun,
313}
314
315impl std::fmt::Display for Durability {
316    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
317        match self {
318            Self::Transient => write!(f, "Transient"),
319            Self::SingleRun => write!(f, "Single-run"),
320        }
321    }
322}
323
324impl From<fcdecl::Durability> for Durability {
325    fn from(value: fcdecl::Durability) -> Self {
326        match value {
327            fcdecl::Durability::SingleRun => Durability::SingleRun,
328            fcdecl::Durability::Transient => Durability::Transient,
329        }
330    }
331}
332
333pub async fn get_all_instances(
334    query: &fsys::RealmQueryProxy,
335) -> Result<Vec<Instance>, GetAllInstancesError> {
336    let result = query.get_all_instances().await?;
337
338    let iterator = match result {
339        Ok(iterator) => iterator,
340        Err(fsys::GetAllInstancesError::InstanceNotFound) => {
341            return Err(GetAllInstancesError::InstanceNotFound)
342        }
343        Err(_) => return Err(GetAllInstancesError::UnknownError),
344    };
345
346    let iterator = iterator.into_proxy();
347    let mut instances = vec![];
348
349    loop {
350        let mut batch = iterator.next().await?;
351        if batch.is_empty() {
352            break;
353        }
354        instances.append(&mut batch);
355    }
356
357    let instances: Result<Vec<Instance>, ParseError> =
358        instances.into_iter().map(|i| Instance::try_from(i)).collect();
359    Ok(instances?)
360}
361
362pub async fn get_resolved_declaration(
363    moniker: &Moniker,
364    realm_query: &fsys::RealmQueryProxy,
365) -> Result<ComponentDecl, GetDeclarationError> {
366    let moniker_str = moniker.to_string();
367    // TODO(https://fxbug.dev/42077935) switch to get_resolved_declaration once OK for ffx compat
368    let iterator = match realm_query.get_manifest(&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)?;
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}