test_runners_lib/elf/
elf_component.rs

1// Copyright 2020 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use 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::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
39// Maximum time that the runner will wait for break_on_start eventpair to signal.
40// This is set to prevent debuggers from blocking us for too long, either intentionally
41// or unintentionally.
42const MAX_WAIT_BREAK_ON_START: zx::MonotonicDuration = zx::MonotonicDuration::from_millis(300);
43
44/// Error encountered running test component
45#[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    /// Convert this error into its approximate `fuchsia.component.Error` equivalent.
101    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/// All information about this test ELF component.
126#[derive(Debug)]
127pub struct Component {
128    /// Component URL
129    pub url: String,
130
131    /// Component name
132    pub name: String,
133
134    /// Binary path for this component relative to /pkg in 'ns'
135    pub binary: String,
136
137    /// Arguments for this test.
138    pub args: Vec<String>,
139
140    /// Environment variables for this test.
141    pub environ: Option<Vec<String>>,
142
143    /// Namespace to pass to test process.
144    pub ns: Namespace,
145
146    /// Parent job in which all test processes should be executed.
147    pub job: zx::Job,
148
149    /// Options to create process with.
150    pub options: zx::ProcessOptions,
151
152    /// Handle to library loader cache.
153    lib_loader_cache: LibraryLoaderCacheProxy,
154
155    /// cached executable vmo.
156    executable_vmo: zx::Vmo,
157
158    /// The structured config vmo.
159    pub config_vmo: Option<zx::Vmo>,
160
161    /// Component instance token, used only in tracing
162    pub component_instance: Option<fidl::Event>,
163}
164
165pub struct BuilderArgs {
166    /// Component URL
167    pub url: String,
168
169    /// Component name
170    pub name: String,
171
172    /// Binary path for this component relative to /pkg in 'ns'
173    pub binary: String,
174
175    /// Arguments for this test.
176    pub args: Vec<String>,
177
178    /// Environment variables for this test.
179    pub environ: Option<Vec<String>>,
180
181    /// Namespace to pass to test process.
182    pub ns: Namespace,
183
184    /// Parent job in which all test processes should be executed.
185    pub job: zx::Job,
186
187    /// The options to create the process with.
188    pub options: zx::ProcessOptions,
189
190    /// The structured config vmo.
191    pub config: Option<zx::Vmo>,
192}
193
194impl Component {
195    /// Create new object using `ComponentStartInfo`.
196    /// On success returns self and outgoing_dir from `ComponentStartInfo`.
197    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        // It's safe to unwrap `start_info.program` below because if the field
224        // were empty, this func would have a returned an error by now.
225        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 ns = start_info.ns.ok_or_else(|| ComponentError::MissingNamespace(url.clone()))?;
231        let ns = Namespace::try_from(ns)
232            .map_err(|e| ComponentError::InvalidArgs(url.clone(), e.into()))?;
233
234        let outgoing_dir =
235            start_info.outgoing_dir.ok_or_else(|| ComponentError::MissingOutDir(url.clone()))?;
236
237        let runtime_dir =
238            start_info.runtime_dir.ok_or_else(|| ComponentError::MissingRuntimeDir(url.clone()))?;
239
240        let (pkg_dir, lib_proxy) = get_pkg_and_lib_proxy(&ns, &url)?;
241
242        let executable_vmo = library_loader::load_vmo(pkg_dir, &binary)
243            .await
244            .map_err(|e| ComponentError::LoadingExecutable(binary.clone(), e))?;
245        let lib_loader_cache_builder = connect_to_protocol::<LibraryLoaderCacheBuilderMarker>()
246            .map_err(|e| ComponentError::LibraryLoadError(url.clone(), e))?;
247
248        let (lib_loader_cache, server_end) = create_proxy::<LibraryLoaderCacheMarker>();
249        lib_loader_cache_builder
250            .create(lib_proxy.into_channel().unwrap().into_zx_channel().into(), server_end)
251            .map_err(|e| {
252                ComponentError::Fidl("cannot communicate with lib loader cache".into(), e)
253            })?;
254
255        let config_vmo = match start_info.encoded_config {
256            None => None,
257            Some(config) => Some(runner::get_config_vmo(config).map_err(|e| {
258                ComponentError::ConfigVmo(anyhow!("Failed to get config vmo: {}", e))
259            })?),
260        };
261
262        Ok((
263            Self {
264                url: url,
265                name: name,
266                binary: binary,
267                args: args,
268                environ,
269                ns: ns,
270                job: job_default().create_child_job().map_err(ComponentError::CreateJob)?,
271                executable_vmo,
272                lib_loader_cache,
273                options: if is_shared_process {
274                    zx::ProcessOptions::SHARED
275                } else {
276                    zx::ProcessOptions::empty()
277                },
278                config_vmo,
279                component_instance: start_info.component_instance,
280            },
281            outgoing_dir,
282            runtime_dir,
283        ))
284    }
285
286    pub fn config_vmo(&self) -> Result<Option<zx::Vmo>, ComponentError> {
287        match &self.config_vmo {
288            None => Ok(None),
289            Some(vmo) => Ok(Some(
290                vmo.as_handle_ref()
291                    .duplicate(zx::Rights::SAME_RIGHTS)
292                    .map_err(|_| {
293                        ComponentError::VmoChild(
294                            self.url.clone(),
295                            anyhow!("Failed to clone config_vmo"),
296                        )
297                    })?
298                    .into(),
299            )),
300        }
301    }
302
303    pub fn executable_vmo(&self) -> Result<zx::Vmo, ComponentError> {
304        vmo_create_child(&self.executable_vmo)
305            .map_err(|e| ComponentError::VmoChild(self.url.clone(), e))
306    }
307
308    pub fn loader_service(&self, loader: ServerEnd<LoaderMarker>) {
309        if let Err(e) = self.lib_loader_cache.serve(loader) {
310            error!("Cannot serve lib loader: {:?}", e);
311        }
312    }
313
314    pub async fn create_for_tests(args: BuilderArgs) -> Result<Self, ComponentError> {
315        let (pkg_dir, lib_proxy) = get_pkg_and_lib_proxy(&args.ns, &args.url)?;
316        let executable_vmo = library_loader::load_vmo(pkg_dir, &args.binary)
317            .await
318            .map_err(|e| ComponentError::LoadingExecutable(args.url.clone(), e))?;
319        let lib_loader_cache_builder = connect_to_protocol::<LibraryLoaderCacheBuilderMarker>()
320            .map_err(|e| ComponentError::LibraryLoadError(args.url.clone(), e))?;
321
322        let (lib_loader_cache, server_end) = create_proxy::<LibraryLoaderCacheMarker>();
323        lib_loader_cache_builder
324            .create(lib_proxy.into_channel().unwrap().into_zx_channel().into(), server_end)
325            .map_err(|e| {
326                ComponentError::Fidl("cannot communicate with lib loader cache".into(), e)
327            })?;
328
329        Ok(Self {
330            url: args.url,
331            name: args.name,
332            binary: args.binary,
333            args: args.args,
334            environ: args.environ,
335            ns: args.ns,
336            job: args.job,
337            lib_loader_cache,
338            executable_vmo,
339            options: args.options,
340            config_vmo: None,
341            component_instance: None,
342        })
343    }
344}
345
346fn vmo_create_child(vmo: &zx::Vmo) -> Result<zx::Vmo, anyhow::Error> {
347    let size = vmo.get_size().context("Cannot get vmo size.")?;
348    vmo.create_child(
349        zx::VmoChildOptions::SNAPSHOT_AT_LEAST_ON_WRITE | zx::VmoChildOptions::NO_WRITE,
350        0,
351        size,
352    )
353    .context("cannot create child vmo")
354}
355
356// returns (pkg_dir, lib_proxy)
357fn get_pkg_and_lib_proxy<'a>(
358    ns: &'a Namespace,
359    url: &String,
360) -> Result<(&'a ClientEnd<fio::DirectoryMarker>, fio::DirectoryProxy), ComponentError> {
361    // Locate the '/pkg' directory proxy previously added to the new component's namespace.
362    let pkg_dir = ns
363        .get(&PKG_PATH.parse().unwrap())
364        .ok_or_else(|| ComponentError::MissingPkg(url.clone()))?;
365
366    let lib_proxy =
367        fuchsia_component::directory::open_directory_async(pkg_dir, "lib", fio::RX_STAR_DIR)
368            .map_err(Into::into)
369            .map_err(|e| ComponentError::LibraryLoadError(url.clone(), e))?;
370    Ok((pkg_dir, lib_proxy))
371}
372
373#[async_trait]
374impl runner::component::Controllable for ComponentRuntime {
375    async fn kill(&mut self) {
376        if let Some(component) = &self.component {
377            info!("kill request component: {}", component.url);
378        }
379        self.kill_self();
380    }
381
382    fn stop<'a>(&mut self) -> BoxFuture<'a, ()> {
383        if let Some(component) = &self.component {
384            info!("stop request component: {}", component.url);
385        }
386        self.kill_self();
387        async move {}.boxed()
388    }
389}
390
391impl Drop for ComponentRuntime {
392    fn drop(&mut self) {
393        if let Some(component) = &self.component {
394            info!("drop component: {}", component.url);
395        }
396        self.kill_self();
397    }
398}
399
400/// Information about all the test instances running for this component.
401struct ComponentRuntime {
402    /// handle to abort component's outgoing services.
403    outgoing_abortable_handle: Option<futures::future::AbortHandle>,
404
405    /// handle to abort running test suite servers.
406    suite_service_abortable_handles: Option<Arc<Mutex<Vec<futures::future::AbortHandle>>>>,
407
408    /// job containing all processes in this component.
409    job: Option<zx::Job>,
410
411    /// component object which is stored here for safe keeping. It would be dropped when test is
412    /// stopped/killed.
413    component: Option<Arc<Component>>,
414}
415
416impl ComponentRuntime {
417    fn new(
418        outgoing_abortable_handle: futures::future::AbortHandle,
419        suite_service_abortable_handles: Arc<Mutex<Vec<futures::future::AbortHandle>>>,
420        job: zx::Job,
421        component: Arc<Component>,
422    ) -> Self {
423        Self {
424            outgoing_abortable_handle: Some(outgoing_abortable_handle),
425            suite_service_abortable_handles: Some(suite_service_abortable_handles),
426            job: Some(job),
427            component: Some(component),
428        }
429    }
430
431    fn kill_self(&mut self) {
432        // drop component.
433        if let Some(component) = self.component.take() {
434            info!("killing component: {}", component.url);
435        }
436
437        // kill outgoing server.
438        if let Some(h) = self.outgoing_abortable_handle.take() {
439            h.abort();
440        }
441
442        // kill all suite servers.
443        if let Some(handles) = self.suite_service_abortable_handles.take() {
444            let handles = handles.lock().unwrap();
445            for h in handles.deref() {
446                h.abort();
447            }
448        }
449
450        // kill all test processes if running.
451        if let Some(job) = self.job.take() {
452            let _ = job.kill();
453        }
454    }
455}
456
457/// Setup and run test component in background.
458///
459/// * `F`: Function which returns new instance of `SuiteServer`.
460pub async fn start_component<F, U, S>(
461    start_info: fcrunner::ComponentStartInfo,
462    mut server_end: ServerEnd<fcrunner::ComponentControllerMarker>,
463    get_test_server: F,
464    validate_args: U,
465) -> Result<(), ComponentError>
466where
467    F: 'static + Fn() -> S + Send,
468    U: 'static + Fn(&Vec<String>) -> Result<(), ArgumentError>,
469    S: SuiteServer,
470{
471    let resolved_url = runner::get_resolved_url(&start_info).unwrap_or(String::new());
472    if let Err(e) =
473        start_component_inner(start_info, &mut server_end, get_test_server, validate_args).await
474    {
475        // Take ownership of `server_end`.
476        let server_end = take_server_end(&mut server_end);
477        runner::component::report_start_error(
478            e.as_zx_status(),
479            format!("{}", e),
480            &resolved_url,
481            server_end,
482        );
483        return Err(e);
484    }
485    Ok(())
486}
487
488async fn start_component_inner<F, U, S>(
489    mut start_info: fcrunner::ComponentStartInfo,
490    server_end: &mut ServerEnd<fcrunner::ComponentControllerMarker>,
491    get_test_server: F,
492    validate_args: U,
493) -> Result<(), ComponentError>
494where
495    F: 'static + Fn() -> S + Send,
496    U: 'static + Fn(&Vec<String>) -> Result<(), ArgumentError>,
497    S: SuiteServer,
498{
499    let break_on_start = start_info.break_on_start.take();
500    let (component, outgoing_dir, runtime_dir) = Component::new(start_info, validate_args).await?;
501    let component = Arc::new(component);
502
503    // Debugger support:
504    // 1. Serve the runtime directory providing the "elf/job_id" entry.
505    let mut runtime_dir_builder = TreeBuilder::empty_dir();
506    let job_id = component
507        .job
508        .get_koid()
509        .map_err(|s| ComponentError::ServeRuntimeDir(anyhow!("cannot get job koid: {}", s)))?
510        .raw_koid();
511    runtime_dir_builder
512        .add_entry(&["elf", "job_id"], read_only(job_id.to_string()))
513        .map_err(|e| ComponentError::ServeRuntimeDir(anyhow!("cannot add elf/job_id: {}", e)))?;
514
515    vfs::directory::serve_on(
516        runtime_dir_builder.build(),
517        fio::PERM_READABLE,
518        ExecutionScope::new(),
519        runtime_dir,
520    );
521
522    // 2. Wait on `break_on_start` before spawning any processes.
523    if let Some(break_on_start) = break_on_start {
524        fasync::OnSignals::new(&break_on_start, zx::Signals::OBJECT_PEER_CLOSED)
525            .on_timeout(MAX_WAIT_BREAK_ON_START, || Err(zx::Status::TIMED_OUT))
526            .await
527            .err()
528            .map(|e| warn!("Failed to wait break_on_start on {}: {}", component.name, e));
529    }
530
531    let job_runtime_dup = component
532        .job
533        .duplicate_handle(zx::Rights::SAME_RIGHTS)
534        .map_err(ComponentError::DuplicateJob)?;
535
536    let job_watch_dup = component
537        .job
538        .duplicate_handle(zx::Rights::SAME_RIGHTS)
539        .map_err(ComponentError::DuplicateJob)?;
540    let mut fs = ServiceFs::new();
541
542    let suite_server_abortable_handles = Arc::new(Mutex::new(vec![]));
543    let weak_test_suite_abortable_handles = Arc::downgrade(&suite_server_abortable_handles);
544    let weak_component = Arc::downgrade(&component);
545
546    let url = component.url.clone();
547    fs.dir("svc").add_fidl_service(move |stream| {
548        let abortable_handles = weak_test_suite_abortable_handles.upgrade();
549        if abortable_handles.is_none() {
550            return;
551        }
552        let abortable_handles = abortable_handles.unwrap();
553        let mut abortable_handles = abortable_handles.lock().unwrap();
554        let abortable_handle = get_test_server().run(weak_component.clone(), &url, stream);
555        abortable_handles.push(abortable_handle);
556    });
557
558    fs.serve_connection(outgoing_dir).map_err(ComponentError::ServeSuite)?;
559    let (fut, abortable_handle) = abortable(fs.collect::<()>());
560
561    let component_runtime = ComponentRuntime::new(
562        abortable_handle,
563        suite_server_abortable_handles,
564        job_runtime_dup,
565        component,
566    );
567
568    fasync::Task::spawn(async move {
569        // as error on abortable will always return Aborted,
570        // no need to check that, as it is a valid usecase.
571        fut.await.ok();
572    })
573    .detach();
574
575    let server_end = take_server_end(server_end);
576    let (controller_stream, control) = server_end.into_stream_and_control_handle();
577    let controller =
578        runner::component::Controller::new(component_runtime, controller_stream, control);
579
580    let termination_fut = Box::pin(async move {
581        // Just return 'OK' here. Any actual errors will be handled through
582        // the test protocol.
583        let _ =
584            fasync::OnSignals::new(&job_watch_dup.as_handle_ref(), zx::Signals::TASK_TERMINATED)
585                .await;
586        StopInfo::from_ok(None)
587    });
588
589    fasync::Task::spawn(controller.serve(termination_fut)).detach();
590
591    Ok(())
592}
593
594fn take_server_end<P: ProtocolMarker>(end: &mut ServerEnd<P>) -> ServerEnd<P> {
595    let invalid_end: ServerEnd<P> = zx::Handle::invalid().into();
596    mem::replace(end, invalid_end)
597}
598
599#[cfg(test)]
600mod tests {
601    use super::*;
602    use crate::elf::EnumeratedTestCases;
603    use crate::errors::{EnumerationError, RunTestError};
604    use anyhow::Error;
605    use assert_matches::assert_matches;
606    use fidl::endpoints::{self};
607    use fidl_fuchsia_test::{Invocation, RunListenerProxy};
608    use futures::future::{AbortHandle, Aborted};
609    use namespace::NamespaceError;
610    use std::sync::Weak;
611
612    fn create_ns_from_current_ns(
613        dir_paths: Vec<(&str, fio::Flags)>,
614    ) -> Result<Namespace, NamespaceError> {
615        let mut ns = vec![];
616        for (path, permission) in dir_paths {
617            let chan = fuchsia_fs::directory::open_in_namespace(path, permission)
618                .unwrap()
619                .into_channel()
620                .unwrap()
621                .into_zx_channel();
622            let handle = ClientEnd::new(chan);
623
624            ns.push(fcrunner::ComponentNamespaceEntry {
625                path: Some(path.to_string()),
626                directory: Some(handle),
627                ..Default::default()
628            });
629        }
630        Namespace::try_from(ns)
631    }
632
633    macro_rules! child_job {
634        () => {
635            job_default().create_child_job().unwrap()
636        };
637    }
638
639    async fn sample_test_component() -> Result<Arc<Component>, Error> {
640        let ns =
641            create_ns_from_current_ns(vec![("/pkg", fio::PERM_READABLE | fio::PERM_EXECUTABLE)])?;
642
643        Ok(Arc::new(
644            Component::create_for_tests(BuilderArgs {
645                url: "fuchsia-pkg://fuchsia.com/sample_test#test.cm".to_owned(),
646                name: "test.cm".to_owned(),
647                binary: "bin/test_runners_lib_lib_test".to_owned(), //reference self binary
648                args: vec![],
649                environ: None,
650                ns: ns,
651                job: child_job!(),
652                options: zx::ProcessOptions::empty(),
653                config: None,
654            })
655            .await?,
656        ))
657    }
658
659    async fn dummy_func() -> u32 {
660        2
661    }
662
663    struct DummyServer {}
664
665    #[async_trait]
666    impl SuiteServer for DummyServer {
667        fn run(
668            self,
669            _component: Weak<Component>,
670            _test_url: &str,
671            _stream: fidl_fuchsia_test::SuiteRequestStream,
672        ) -> AbortHandle {
673            let (_, handle) = abortable(async {});
674            handle
675        }
676
677        async fn enumerate_tests(
678            &self,
679            _test_component: Arc<Component>,
680        ) -> Result<EnumeratedTestCases, EnumerationError> {
681            Ok(Arc::new(vec![]))
682        }
683
684        async fn run_tests(
685            &self,
686            _invocations: Vec<Invocation>,
687            _run_options: fidl_fuchsia_test::RunOptions,
688            _component: Arc<Component>,
689            _run_listener: &RunListenerProxy,
690        ) -> Result<(), RunTestError> {
691            Ok(())
692        }
693    }
694
695    #[fuchsia_async::run_singlethreaded(test)]
696    async fn start_component_error() {
697        let start_info = fcrunner::ComponentStartInfo {
698            resolved_url: None,
699            program: None,
700            ns: None,
701            outgoing_dir: None,
702            runtime_dir: None,
703            ..Default::default()
704        };
705        let (client_controller, server_controller) = endpoints::create_proxy();
706        let get_test_server = || DummyServer {};
707        let err = start_component(start_info, server_controller, get_test_server, |_| Ok(())).await;
708        assert_matches!(err, Err(ComponentError::MissingResolvedUrl));
709        let expected_status = zx::Status::from_raw(
710            fcomponent::Error::InvalidArguments.into_primitive().try_into().unwrap(),
711        );
712        let s = assert_matches!(
713            client_controller.take_event_stream().next().await,
714            Some(Err(fidl::Error::ClientChannelClosed { status: s, .. })) => s
715        );
716        assert_eq!(s, expected_status);
717    }
718
719    #[fuchsia_async::run_singlethreaded(test)]
720    async fn start_component_works() {
721        let _ = sample_test_component().await.unwrap();
722    }
723
724    #[fuchsia_async::run_singlethreaded(test)]
725    async fn component_runtime_kill_job_works() {
726        let component = sample_test_component().await.unwrap();
727
728        let mut futs = vec![];
729        let mut handles = vec![];
730        for _i in 0..10 {
731            let (fut, handle) = abortable(dummy_func());
732            futs.push(fut);
733            handles.push(handle);
734        }
735
736        let (out_fut, out_handle) = abortable(dummy_func());
737        let mut runtime = ComponentRuntime::new(
738            out_handle,
739            Arc::new(Mutex::new(handles)),
740            child_job!(),
741            component.clone(),
742        );
743
744        assert_eq!(Arc::strong_count(&component), 2);
745        runtime.kill_self();
746
747        for fut in futs {
748            assert_eq!(fut.await, Err(Aborted));
749        }
750
751        assert_eq!(out_fut.await, Err(Aborted));
752
753        assert_eq!(Arc::strong_count(&component), 1);
754    }
755}