1use 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
20static 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 pub moniker: Moniker,
194
195 pub url: String,
197
198 pub environment: Option<String>,
200
201 pub instance_id: Option<String>,
203
204 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#[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#[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#[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 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 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 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
575async 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}