Skip to main content

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