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