1use 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
19static 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 pub moniker: Moniker,
193
194 pub url: String,
196
197 pub environment: Option<String>,
199
200 pub instance_id: Option<String>,
202
203 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#[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#[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#[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 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 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}