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::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
41// Maximum time that the runner will wait for break_on_start eventpair to signal.
42// This is set to prevent debuggers from blocking us for too long, either intentionally
43// or unintentionally.
44const MAX_WAIT_BREAK_ON_START: zx::MonotonicDuration = zx::MonotonicDuration::from_millis(300);
45
46/// Error encountered running test component
47#[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    /// Convert this error into its approximate `fuchsia.component.Error` equivalent.
103    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/// All information about this test ELF component.
128#[derive(Debug)]
129pub struct Component {
130    /// Component URL
131    pub url: String,
132
133    /// Component name
134    pub name: String,
135
136    /// Binary path for this component relative to /pkg in 'ns'
137    pub binary: String,
138
139    /// Arguments for this test.
140    pub args: Vec<String>,
141
142    /// Environment variables for this test.
143    pub environ: Option<Vec<String>>,
144
145    /// Namespace to pass to test process.
146    pub ns: Namespace,
147
148    /// Parent job in which all test processes should be executed.
149    pub job: zx::Job,
150
151    /// Options to create process with.
152    pub options: zx::ProcessOptions,
153
154    /// Handle to library loader cache.
155    lib_loader_cache: LibraryLoaderCacheProxy,
156
157    /// cached executable vmo.
158    executable_vmo: zx::Vmo,
159
160    /// The structured config vmo.
161    pub config_vmo: Option<zx::Vmo>,
162
163    /// Component instance token, used only in tracing
164    pub component_instance: Option<fidl::Event>,
165}
166
167pub struct BuilderArgs {
168    /// Component URL
169    pub url: String,
170
171    /// Component name
172    pub name: String,
173
174    /// Binary path for this component relative to /pkg in 'ns'
175    pub binary: String,
176
177    /// Arguments for this test.
178    pub args: Vec<String>,
179
180    /// Environment variables for this test.
181    pub environ: Option<Vec<String>>,
182
183    /// Namespace to pass to test process.
184    pub ns: Namespace,
185
186    /// Parent job in which all test processes should be executed.
187    pub job: zx::Job,
188
189    /// The options to create the process with.
190    pub options: zx::ProcessOptions,
191
192    /// The structured config vmo.
193    pub config: Option<zx::Vmo>,
194}
195
196impl Component {
197    /// Create new object using `ComponentStartInfo`.
198    /// On success returns self and outgoing_dir from `ComponentStartInfo`.
199    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        // It's safe to unwrap `start_info.program` below because if the field
226        // were empty, this func would have a returned an error by now.
227        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
358// returns (pkg_dir, lib_proxy)
359fn get_pkg_and_lib_proxy<'a>(
360    ns: &'a Namespace,
361    url: &String,
362) -> Result<(&'a ClientEnd<fio::DirectoryMarker>, fio::DirectoryProxy), ComponentError> {
363    // Locate the '/pkg' directory proxy previously added to the new component's namespace.
364    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
402/// Information about all the test instances running for this component.
403struct ComponentRuntime {
404    /// handle to abort component's outgoing services.
405    outgoing_abortable_handle: Option<futures::future::AbortHandle>,
406
407    /// handle to abort running test suite servers.
408    suite_service_abortable_handles: Option<Arc<Mutex<Vec<futures::future::AbortHandle>>>>,
409
410    /// job containing all processes in this component.
411    job: Option<zx::Job>,
412
413    /// component object which is stored here for safe keeping. It would be dropped when test is
414    /// stopped/killed.
415    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        // drop component.
435        if let Some(component) = self.component.take() {
436            info!("killing component: {}", component.url);
437        }
438
439        // kill outgoing server.
440        if let Some(h) = self.outgoing_abortable_handle.take() {
441            h.abort();
442        }
443
444        // kill all suite servers.
445        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        // kill all test processes if running.
453        if let Some(job) = self.job.take() {
454            let _ = job.kill();
455        }
456    }
457}
458
459/// Setup and run test component in background.
460///
461/// * `F`: Function which returns new instance of `SuiteServer`.
462pub 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        // Take ownership of `server_end`.
478        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    // Debugger support:
506    // 1. Serve the runtime directory providing the "elf/job_id" entry.
507    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    // 2. Wait on `break_on_start` before spawning any processes.
527    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        // as error on abortable will always return Aborted,
574        // no need to check that, as it is a valid usecase.
575        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        // Just return 'OK' here. Any actual errors will be handled through
586        // the test protocol.
587        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(), //reference self binary
652                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}