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