1use super::SuiteServer;
6use crate::errors::ArgumentError;
7use anyhow::{Context, anyhow};
8use async_trait::async_trait;
9use fidl::endpoints::{ClientEnd, ProtocolMarker, Proxy, ServerEnd, create_proxy};
10use fidl_fuchsia_component as fcomponent;
11use fidl_fuchsia_component_runner as fcrunner;
12use fidl_fuchsia_io as fio;
13use fidl_fuchsia_ldsvc::LoaderMarker;
14use fidl_fuchsia_test_runner::{
15 LibraryLoaderCacheBuilderMarker, LibraryLoaderCacheMarker, LibraryLoaderCacheProxy,
16};
17use fuchsia_async::{self as fasync, TimeoutExt};
18use fuchsia_component::client::connect_to_protocol;
19use fuchsia_component::server::ServiceFs;
20use fuchsia_runtime::job_default;
21use futures::future::{BoxFuture, abortable};
22use futures::prelude::*;
23use log::{error, info, warn};
24use namespace::Namespace;
25use runner::component::StopInfo;
26use std::mem;
27use std::ops::Deref;
28use std::path::Path;
29use std::sync::{Arc, Mutex};
30use thiserror::Error;
31use vfs::execution_scope::ExecutionScope;
32use vfs::file::vmo::read_only;
33use vfs::tree_builder::TreeBuilder;
34use zx::Task;
35
36static PKG_PATH: &'static str = "/pkg";
37
38const MAX_WAIT_BREAK_ON_START: zx::MonotonicDuration = zx::MonotonicDuration::from_millis(300);
42
43#[derive(Debug, Error)]
45pub enum ComponentError {
46 #[error("start info is missing resolved url")]
47 MissingResolvedUrl,
48
49 #[error("error for test {}: {:?}", _0, _1)]
50 InvalidArgs(String, anyhow::Error),
51
52 #[error("Cannot run test {}, no namespace was supplied.", _0)]
53 MissingNamespace(String),
54
55 #[error("Cannot run test {}, as no outgoing directory was supplied.", _0)]
56 MissingOutDir(String),
57
58 #[error("Cannot run test {}, as no runtime directory was supplied.", _0)]
59 MissingRuntimeDir(String),
60
61 #[error("Cannot run test {}, as no /pkg directory was supplied.", _0)]
62 MissingPkg(String),
63
64 #[error("Cannot load library for {}: {}.", _0, _1)]
65 LibraryLoadError(String, anyhow::Error),
66
67 #[error("Cannot load executable binary '{}': {}", _0, _1)]
68 LoadingExecutable(String, anyhow::Error),
69
70 #[error("Cannot create vmo child for test {}: {}", _0, _1)]
71 VmoChild(String, anyhow::Error),
72
73 #[error("Cannot run suite server: {:?}", _0)]
74 ServeSuite(anyhow::Error),
75
76 #[error("Cannot serve runtime directory: {:?}", _0)]
77 ServeRuntimeDir(anyhow::Error),
78
79 #[error("{}: {:?}", _0, _1)]
80 Fidl(String, fidl::Error),
81
82 #[error("cannot create job: {:?}", _0)]
83 CreateJob(zx::Status),
84
85 #[error("Cannot set config vmo: {:?}", _0)]
86 ConfigVmo(anyhow::Error),
87
88 #[error("cannot create channel: {:?}", _0)]
89 CreateChannel(zx::Status),
90
91 #[error("cannot duplicate job: {:?}", _0)]
92 DuplicateJob(zx::Status),
93
94 #[error("invalid url")]
95 InvalidUrl,
96}
97
98impl ComponentError {
99 pub fn as_zx_status(&self) -> zx::Status {
101 let status = match self {
102 Self::MissingResolvedUrl => fcomponent::Error::InvalidArguments,
103 Self::InvalidArgs(_, _) => fcomponent::Error::InvalidArguments,
104 Self::MissingNamespace(_) => fcomponent::Error::InvalidArguments,
105 Self::MissingOutDir(_) => fcomponent::Error::InvalidArguments,
106 Self::MissingRuntimeDir(_) => fcomponent::Error::InvalidArguments,
107 Self::MissingPkg(_) => fcomponent::Error::InvalidArguments,
108 Self::LibraryLoadError(_, _) => fcomponent::Error::Internal,
109 Self::LoadingExecutable(_, _) => fcomponent::Error::InstanceCannotStart,
110 Self::VmoChild(_, _) => fcomponent::Error::Internal,
111 Self::ServeSuite(_) => fcomponent::Error::Internal,
112 Self::ServeRuntimeDir(_) => fcomponent::Error::Internal,
113 Self::Fidl(_, _) => fcomponent::Error::Internal,
114 Self::CreateJob(_) => fcomponent::Error::ResourceUnavailable,
115 Self::CreateChannel(_) => fcomponent::Error::ResourceUnavailable,
116 Self::DuplicateJob(_) => fcomponent::Error::Internal,
117 Self::InvalidUrl => fcomponent::Error::InvalidArguments,
118 Self::ConfigVmo(_) => fcomponent::Error::Internal,
119 };
120 zx::Status::from_raw(status.into_primitive().try_into().unwrap())
121 }
122}
123
124#[derive(Debug)]
126pub struct Component {
127 pub url: String,
129
130 pub name: String,
132
133 pub binary: String,
135
136 pub args: Vec<String>,
138
139 pub environ: Option<Vec<String>>,
141
142 pub ns: Namespace,
144
145 pub job: zx::Job,
147
148 pub options: zx::ProcessOptions,
150
151 lib_loader_cache: LibraryLoaderCacheProxy,
153
154 executable_vmo: zx::Vmo,
156
157 pub config_vmo: Option<zx::Vmo>,
159
160 pub component_instance: Option<fidl::Event>,
162}
163
164pub struct BuilderArgs {
165 pub url: String,
167
168 pub name: String,
170
171 pub binary: String,
173
174 pub args: Vec<String>,
176
177 pub environ: Option<Vec<String>>,
179
180 pub ns: Namespace,
182
183 pub job: zx::Job,
185
186 pub options: zx::ProcessOptions,
188
189 pub config: Option<zx::Vmo>,
191}
192
193impl Component {
194 pub async fn new<F>(
197 start_info: fcrunner::ComponentStartInfo,
198 validate_args: F,
199 ) -> Result<
200 (Self, ServerEnd<fio::DirectoryMarker>, ServerEnd<fio::DirectoryMarker>),
201 ComponentError,
202 >
203 where
204 F: 'static + Fn(&Vec<String>) -> Result<(), ArgumentError>,
205 {
206 let url =
207 runner::get_resolved_url(&start_info).ok_or(ComponentError::MissingResolvedUrl)?;
208 let name = Path::new(&url)
209 .file_name()
210 .ok_or_else(|| ComponentError::InvalidUrl)?
211 .to_str()
212 .ok_or_else(|| ComponentError::InvalidUrl)?
213 .to_string();
214
215 let args = runner::get_program_args(&start_info)
216 .map_err(|e| ComponentError::InvalidArgs(url.clone(), e.into()))?;
217 validate_args(&args).map_err(|e| ComponentError::InvalidArgs(url.clone(), e.into()))?;
218
219 let binary = runner::get_program_binary(&start_info)
220 .map_err(|e| ComponentError::InvalidArgs(url.clone(), e.into()))?;
221
222 let program = start_info.program.as_ref().unwrap();
225 let environ = runner::get_environ(program)
226 .map_err(|e| ComponentError::InvalidArgs(url.clone(), e.into()))?;
227 let is_shared_process = runner::get_bool(program, "is_shared_process").unwrap_or(false);
228
229 let job = job_default().create_child_job().map_err(ComponentError::CreateJob)?;
230 match runner::get_enum(
231 program,
232 "job_policy_bad_handles",
233 &["deny_exception", "allow_exception"],
234 )
235 .unwrap_or(None)
236 {
237 Some("deny_exception") => {
238 let _ = job
239 .set_policy(zx::JobPolicy::Basic(
240 zx::JobPolicyOption::Absolute,
241 vec![(zx::JobCondition::BadHandle, zx::JobAction::DenyException)],
242 ))
243 .map_err(|e| ComponentError::CreateJob(e))?;
244 }
245 Some("allow_exception") => {
246 let _ = job
247 .set_policy(zx::JobPolicy::Basic(
248 zx::JobPolicyOption::Absolute,
249 vec![(zx::JobCondition::BadHandle, zx::JobAction::AllowException)],
250 ))
251 .map_err(|e| ComponentError::CreateJob(e))?;
252 }
253 _ => {}
254 };
255 let ns = start_info.ns.ok_or_else(|| ComponentError::MissingNamespace(url.clone()))?;
256 let ns = Namespace::try_from(ns)
257 .map_err(|e| ComponentError::InvalidArgs(url.clone(), e.into()))?;
258
259 let outgoing_dir =
260 start_info.outgoing_dir.ok_or_else(|| ComponentError::MissingOutDir(url.clone()))?;
261
262 let runtime_dir =
263 start_info.runtime_dir.ok_or_else(|| ComponentError::MissingRuntimeDir(url.clone()))?;
264
265 let (pkg_dir, lib_proxy) = get_pkg_and_lib_proxy(&ns, &url)?;
266
267 let executable_vmo = library_loader::load_vmo(pkg_dir, &binary)
268 .await
269 .map_err(|e| ComponentError::LoadingExecutable(binary.clone(), e))?;
270 let lib_loader_cache_builder = connect_to_protocol::<LibraryLoaderCacheBuilderMarker>()
271 .map_err(|e| ComponentError::LibraryLoadError(url.clone(), e))?;
272
273 let (lib_loader_cache, server_end) = create_proxy::<LibraryLoaderCacheMarker>();
274 lib_loader_cache_builder
275 .create(lib_proxy.into_channel().unwrap().into_zx_channel().into(), server_end)
276 .map_err(|e| {
277 ComponentError::Fidl("cannot communicate with lib loader cache".into(), e)
278 })?;
279
280 let config_vmo = match start_info.encoded_config {
281 None => None,
282 Some(config) => Some(runner::get_config_vmo(config).map_err(|e| {
283 ComponentError::ConfigVmo(anyhow!("Failed to get config vmo: {}", e))
284 })?),
285 };
286
287 Ok((
288 Self {
289 url: url,
290 name: name,
291 binary: binary,
292 args: args,
293 environ,
294 ns: ns,
295 job: job,
296 executable_vmo,
297 lib_loader_cache,
298 options: if is_shared_process {
299 zx::ProcessOptions::SHARED
300 } else {
301 zx::ProcessOptions::empty()
302 },
303 config_vmo,
304 component_instance: start_info.component_instance,
305 },
306 outgoing_dir,
307 runtime_dir,
308 ))
309 }
310
311 pub fn config_vmo(&self) -> Result<Option<zx::Vmo>, ComponentError> {
312 match &self.config_vmo {
313 None => Ok(None),
314 Some(vmo) => Ok(Some(
315 vmo.as_handle_ref()
316 .duplicate_handle(zx::Rights::SAME_RIGHTS)
317 .map_err(|_| {
318 ComponentError::VmoChild(
319 self.url.clone(),
320 anyhow!("Failed to clone config_vmo"),
321 )
322 })?
323 .into(),
324 )),
325 }
326 }
327
328 pub fn executable_vmo(&self) -> Result<zx::Vmo, ComponentError> {
329 vmo_create_child(&self.executable_vmo)
330 .map_err(|e| ComponentError::VmoChild(self.url.clone(), e))
331 }
332
333 pub fn loader_service(&self, loader: ServerEnd<LoaderMarker>) {
334 if let Err(e) = self.lib_loader_cache.serve(loader) {
335 error!("Cannot serve lib loader: {:?}", e);
336 }
337 }
338
339 pub async fn create_for_tests(args: BuilderArgs) -> Result<Self, ComponentError> {
340 let (pkg_dir, lib_proxy) = get_pkg_and_lib_proxy(&args.ns, &args.url)?;
341 let executable_vmo = library_loader::load_vmo(pkg_dir, &args.binary)
342 .await
343 .map_err(|e| ComponentError::LoadingExecutable(args.url.clone(), e))?;
344 let lib_loader_cache_builder = connect_to_protocol::<LibraryLoaderCacheBuilderMarker>()
345 .map_err(|e| ComponentError::LibraryLoadError(args.url.clone(), e))?;
346
347 let (lib_loader_cache, server_end) = create_proxy::<LibraryLoaderCacheMarker>();
348 lib_loader_cache_builder
349 .create(lib_proxy.into_channel().unwrap().into_zx_channel().into(), server_end)
350 .map_err(|e| {
351 ComponentError::Fidl("cannot communicate with lib loader cache".into(), e)
352 })?;
353
354 Ok(Self {
355 url: args.url,
356 name: args.name,
357 binary: args.binary,
358 args: args.args,
359 environ: args.environ,
360 ns: args.ns,
361 job: args.job,
362 lib_loader_cache,
363 executable_vmo,
364 options: args.options,
365 config_vmo: None,
366 component_instance: None,
367 })
368 }
369}
370
371fn vmo_create_child(vmo: &zx::Vmo) -> Result<zx::Vmo, anyhow::Error> {
372 let size = vmo.get_size().context("Cannot get vmo size.")?;
373 vmo.create_child(
374 zx::VmoChildOptions::SNAPSHOT_AT_LEAST_ON_WRITE | zx::VmoChildOptions::NO_WRITE,
375 0,
376 size,
377 )
378 .context("cannot create child vmo")
379}
380
381fn get_pkg_and_lib_proxy<'a>(
383 ns: &'a Namespace,
384 url: &String,
385) -> Result<(&'a ClientEnd<fio::DirectoryMarker>, fio::DirectoryProxy), ComponentError> {
386 let pkg_dir = ns
388 .get(&PKG_PATH.parse().unwrap())
389 .ok_or_else(|| ComponentError::MissingPkg(url.clone()))?;
390
391 let lib_proxy =
392 fuchsia_component::directory::open_directory_async(pkg_dir, "lib", fio::RX_STAR_DIR)
393 .map_err(Into::into)
394 .map_err(|e| ComponentError::LibraryLoadError(url.clone(), e))?;
395 Ok((pkg_dir, lib_proxy))
396}
397
398#[async_trait]
399impl runner::component::Controllable for ComponentRuntime {
400 async fn kill(&mut self) {
401 if let Some(component) = &self.component {
402 info!("kill request component: {}", component.url);
403 }
404 self.kill_self();
405 }
406
407 fn stop<'a>(&mut self) -> BoxFuture<'a, ()> {
408 if let Some(component) = &self.component {
409 info!("stop request component: {}", component.url);
410 }
411 self.kill_self();
412 async move {}.boxed()
413 }
414}
415
416impl Drop for ComponentRuntime {
417 fn drop(&mut self) {
418 if let Some(component) = &self.component {
419 info!("drop component: {}", component.url);
420 }
421 self.kill_self();
422 }
423}
424
425struct ComponentRuntime {
427 outgoing_abortable_handle: Option<futures::future::AbortHandle>,
429
430 suite_service_abortable_handles: Option<Arc<Mutex<Vec<futures::future::AbortHandle>>>>,
432
433 job: Option<zx::Job>,
435
436 component: Option<Arc<Component>>,
439}
440
441impl ComponentRuntime {
442 fn new(
443 outgoing_abortable_handle: futures::future::AbortHandle,
444 suite_service_abortable_handles: Arc<Mutex<Vec<futures::future::AbortHandle>>>,
445 job: zx::Job,
446 component: Arc<Component>,
447 ) -> Self {
448 Self {
449 outgoing_abortable_handle: Some(outgoing_abortable_handle),
450 suite_service_abortable_handles: Some(suite_service_abortable_handles),
451 job: Some(job),
452 component: Some(component),
453 }
454 }
455
456 fn kill_self(&mut self) {
457 if let Some(component) = self.component.take() {
459 info!("killing component: {}", component.url);
460 }
461
462 if let Some(h) = self.outgoing_abortable_handle.take() {
464 h.abort();
465 }
466
467 if let Some(handles) = self.suite_service_abortable_handles.take() {
469 let handles = handles.lock().unwrap();
470 for h in handles.deref() {
471 h.abort();
472 }
473 }
474
475 if let Some(job) = self.job.take() {
477 let _ = job.kill();
478 }
479 }
480}
481
482pub async fn start_component<F, U, S>(
486 start_info: fcrunner::ComponentStartInfo,
487 mut server_end: ServerEnd<fcrunner::ComponentControllerMarker>,
488 get_test_server: F,
489 validate_args: U,
490) -> Result<(), ComponentError>
491where
492 F: 'static + Fn() -> S + Send,
493 U: 'static + Fn(&Vec<String>) -> Result<(), ArgumentError>,
494 S: SuiteServer,
495{
496 let resolved_url = runner::get_resolved_url(&start_info).unwrap_or(String::new());
497 if let Err(e) =
498 start_component_inner(start_info, &mut server_end, get_test_server, validate_args).await
499 {
500 let server_end = take_server_end(&mut server_end);
502 runner::component::report_start_error(
503 e.as_zx_status(),
504 format!("{}", e),
505 &resolved_url,
506 server_end,
507 );
508 return Err(e);
509 }
510 Ok(())
511}
512
513async fn start_component_inner<F, U, S>(
514 mut start_info: fcrunner::ComponentStartInfo,
515 server_end: &mut ServerEnd<fcrunner::ComponentControllerMarker>,
516 get_test_server: F,
517 validate_args: U,
518) -> Result<(), ComponentError>
519where
520 F: 'static + Fn() -> S + Send,
521 U: 'static + Fn(&Vec<String>) -> Result<(), ArgumentError>,
522 S: SuiteServer,
523{
524 let break_on_start = start_info.break_on_start.take();
525 let (component, outgoing_dir, runtime_dir) = Component::new(start_info, validate_args).await?;
526 let component = Arc::new(component);
527
528 let mut runtime_dir_builder = TreeBuilder::empty_dir();
531 let job_id = component
532 .job
533 .koid()
534 .map_err(|s| ComponentError::ServeRuntimeDir(anyhow!("cannot get job koid: {}", s)))?
535 .raw_koid();
536 runtime_dir_builder
537 .add_entry(&["elf", "job_id"], read_only(job_id.to_string()))
538 .map_err(|e| ComponentError::ServeRuntimeDir(anyhow!("cannot add elf/job_id: {}", e)))?;
539
540 vfs::directory::serve_on(
541 runtime_dir_builder.build(),
542 fio::PERM_READABLE,
543 ExecutionScope::new(),
544 runtime_dir,
545 );
546
547 if let Some(break_on_start) = break_on_start {
549 fasync::OnSignals::new(&break_on_start, zx::Signals::OBJECT_PEER_CLOSED)
550 .on_timeout(MAX_WAIT_BREAK_ON_START, || Err(zx::Status::TIMED_OUT))
551 .await
552 .err()
553 .map(|e| warn!("Failed to wait break_on_start on {}: {}", component.name, e));
554 }
555
556 let job_runtime_dup = component
557 .job
558 .duplicate_handle(zx::Rights::SAME_RIGHTS)
559 .map_err(ComponentError::DuplicateJob)?;
560
561 let job_watch_dup = component
562 .job
563 .duplicate_handle(zx::Rights::SAME_RIGHTS)
564 .map_err(ComponentError::DuplicateJob)?;
565 let mut fs = ServiceFs::new();
566
567 let suite_server_abortable_handles = Arc::new(Mutex::new(vec![]));
568 let weak_test_suite_abortable_handles = Arc::downgrade(&suite_server_abortable_handles);
569 let weak_component = Arc::downgrade(&component);
570
571 let url = component.url.clone();
572 fs.dir("svc").add_fidl_service(move |stream| {
573 let abortable_handles = weak_test_suite_abortable_handles.upgrade();
574 if abortable_handles.is_none() {
575 return;
576 }
577 let abortable_handles = abortable_handles.unwrap();
578 let mut abortable_handles = abortable_handles.lock().unwrap();
579 let abortable_handle = get_test_server().run(weak_component.clone(), &url, stream);
580 abortable_handles.push(abortable_handle);
581 });
582
583 fs.serve_connection(outgoing_dir).map_err(ComponentError::ServeSuite)?;
584 let (fut, abortable_handle) = abortable(fs.collect::<()>());
585
586 let component_runtime = ComponentRuntime::new(
587 abortable_handle,
588 suite_server_abortable_handles,
589 job_runtime_dup,
590 component,
591 );
592
593 fasync::Task::spawn(async move {
594 fut.await.ok();
597 })
598 .detach();
599
600 let server_end = take_server_end(server_end);
601 let (controller_stream, control) = server_end.into_stream_and_control_handle();
602 let controller =
603 runner::component::Controller::new(component_runtime, controller_stream, control);
604
605 let termination_fut = Box::pin(async move {
606 let _ =
609 fasync::OnSignals::new(&job_watch_dup.as_handle_ref(), zx::Signals::TASK_TERMINATED)
610 .await;
611 StopInfo::from_ok(None)
612 });
613
614 fasync::Task::spawn(controller.serve(termination_fut)).detach();
615
616 Ok(())
617}
618
619fn take_server_end<P: ProtocolMarker>(end: &mut ServerEnd<P>) -> ServerEnd<P> {
620 let invalid_end: ServerEnd<P> = zx::NullableHandle::invalid().into();
621 mem::replace(end, invalid_end)
622}
623
624#[cfg(test)]
625mod tests {
626 use super::*;
627 use crate::elf::EnumeratedTestCases;
628 use crate::errors::{EnumerationError, RunTestError};
629 use anyhow::Error;
630 use assert_matches::assert_matches;
631 use fidl::endpoints::{self};
632 use fidl_fuchsia_test::{Invocation, RunListenerProxy};
633 use futures::future::{AbortHandle, Aborted};
634 use namespace::NamespaceError;
635 use std::sync::Weak;
636
637 fn create_ns_from_current_ns(
638 dir_paths: Vec<(&str, fio::Flags)>,
639 ) -> Result<Namespace, NamespaceError> {
640 let mut ns = vec![];
641 for (path, permission) in dir_paths {
642 let chan = fuchsia_fs::directory::open_in_namespace(path, permission)
643 .unwrap()
644 .into_channel()
645 .unwrap()
646 .into_zx_channel();
647 let handle = ClientEnd::new(chan);
648
649 ns.push(fcrunner::ComponentNamespaceEntry {
650 path: Some(path.to_string()),
651 directory: Some(handle),
652 ..Default::default()
653 });
654 }
655 Namespace::try_from(ns)
656 }
657
658 macro_rules! child_job {
659 () => {
660 job_default().create_child_job().unwrap()
661 };
662 }
663
664 async fn sample_test_component() -> Result<Arc<Component>, Error> {
665 let ns =
666 create_ns_from_current_ns(vec![("/pkg", fio::PERM_READABLE | fio::PERM_EXECUTABLE)])?;
667
668 Ok(Arc::new(
669 Component::create_for_tests(BuilderArgs {
670 url: "fuchsia-pkg://fuchsia.com/sample_test#test.cm".to_owned(),
671 name: "test.cm".to_owned(),
672 binary: "bin/test_runners_lib_lib_test".to_owned(), args: vec![],
674 environ: None,
675 ns: ns,
676 job: child_job!(),
677 options: zx::ProcessOptions::empty(),
678 config: None,
679 })
680 .await?,
681 ))
682 }
683
684 async fn dummy_func() -> u32 {
685 2
686 }
687
688 struct DummyServer {}
689
690 #[async_trait]
691 impl SuiteServer for DummyServer {
692 fn run(
693 self,
694 _component: Weak<Component>,
695 _test_url: &str,
696 _stream: fidl_fuchsia_test::SuiteRequestStream,
697 ) -> AbortHandle {
698 let (_, handle) = abortable(async {});
699 handle
700 }
701
702 async fn enumerate_tests(
703 &self,
704 _test_component: Arc<Component>,
705 ) -> Result<EnumeratedTestCases, EnumerationError> {
706 Ok(Arc::new(vec![]))
707 }
708
709 async fn run_tests(
710 &self,
711 _invocations: Vec<Invocation>,
712 _run_options: fidl_fuchsia_test::RunOptions,
713 _component: Arc<Component>,
714 _run_listener: &RunListenerProxy,
715 ) -> Result<(), RunTestError> {
716 Ok(())
717 }
718 }
719
720 #[fuchsia_async::run_singlethreaded(test)]
721 async fn start_component_error() {
722 let start_info = fcrunner::ComponentStartInfo {
723 resolved_url: None,
724 program: None,
725 ns: None,
726 outgoing_dir: None,
727 runtime_dir: None,
728 ..Default::default()
729 };
730 let (client_controller, server_controller) = endpoints::create_proxy();
731 let get_test_server = || DummyServer {};
732 let err = start_component(start_info, server_controller, get_test_server, |_| Ok(())).await;
733 assert_matches!(err, Err(ComponentError::MissingResolvedUrl));
734 let expected_status = zx::Status::from_raw(
735 fcomponent::Error::InvalidArguments.into_primitive().try_into().unwrap(),
736 );
737 let s = assert_matches!(
738 client_controller.take_event_stream().next().await,
739 Some(Err(fidl::Error::ClientChannelClosed { status: s, .. })) => s
740 );
741 assert_eq!(s, expected_status);
742 }
743
744 #[fuchsia_async::run_singlethreaded(test)]
745 async fn start_component_works() {
746 let _ = sample_test_component().await.unwrap();
747 }
748
749 #[fuchsia_async::run_singlethreaded(test)]
750 async fn component_runtime_kill_job_works() {
751 let component = sample_test_component().await.unwrap();
752
753 let mut futs = vec![];
754 let mut handles = vec![];
755 for _i in 0..10 {
756 let (fut, handle) = abortable(dummy_func());
757 futs.push(fut);
758 handles.push(handle);
759 }
760
761 let (out_fut, out_handle) = abortable(dummy_func());
762 let mut runtime = ComponentRuntime::new(
763 out_handle,
764 Arc::new(Mutex::new(handles)),
765 child_job!(),
766 component.clone(),
767 );
768
769 assert_eq!(Arc::strong_count(&component), 2);
770 runtime.kill_self();
771
772 for fut in futs {
773 assert_eq!(fut.await, Err(Aborted));
774 }
775
776 assert_eq!(out_fut.await, Err(Aborted));
777
778 assert_eq!(Arc::strong_count(&component), 1);
779 }
780}