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::{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
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 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
382// returns (pkg_dir, lib_proxy)
383fn get_pkg_and_lib_proxy<'a>(
384    ns: &'a Namespace,
385    url: &String,
386) -> Result<(&'a ClientEnd<fio::DirectoryMarker>, fio::DirectoryProxy), ComponentError> {
387    // Locate the '/pkg' directory proxy previously added to the new component's namespace.
388    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
426/// Information about all the test instances running for this component.
427struct ComponentRuntime {
428    /// handle to abort component's outgoing services.
429    outgoing_abortable_handle: Option<futures::future::AbortHandle>,
430
431    /// handle to abort running test suite servers.
432    suite_service_abortable_handles: Option<Arc<Mutex<Vec<futures::future::AbortHandle>>>>,
433
434    /// job containing all processes in this component.
435    job: Option<zx::Job>,
436
437    /// component object which is stored here for safe keeping. It would be dropped when test is
438    /// stopped/killed.
439    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        // drop component.
459        if let Some(component) = self.component.take() {
460            info!("killing component: {}", component.url);
461        }
462
463        // kill outgoing server.
464        if let Some(h) = self.outgoing_abortable_handle.take() {
465            h.abort();
466        }
467
468        // kill all suite servers.
469        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        // kill all test processes if running.
477        if let Some(job) = self.job.take() {
478            let _ = job.kill();
479        }
480    }
481}
482
483/// Setup and run test component in background.
484///
485/// * `F`: Function which returns new instance of `SuiteServer`.
486pub 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        // Take ownership of `server_end`.
502        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    // Debugger support:
530    // 1. Serve the runtime directory providing the "elf/job_id" entry.
531    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    // 2. Wait on `break_on_start` before spawning any processes.
549    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        // as error on abortable will always return Aborted,
596        // no need to check that, as it is a valid usecase.
597        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        // Just return 'OK' here. Any actual errors will be handled through
608        // the test protocol.
609        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(), //reference self binary
674                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}