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