test_manager_lib/
running_suite.rs

1// Copyright 2022 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 crate::above_root_capabilities::AboveRootCapabilitiesForTest;
6use crate::constants::{
7    CUSTOM_ARTIFACTS_CAPABILITY_NAME, HERMETIC_RESOLVER_REALM_NAME, TEST_ENVIRONMENT_NAME,
8    TEST_ROOT_COLLECTION, TEST_ROOT_REALM_NAME, WRAPPER_REALM_NAME,
9};
10use crate::debug_agent::DebugAgent;
11use crate::debug_data_processor::{DebugDataSender, serve_debug_data_publisher};
12use crate::error::{DebugAgentError, LaunchTestError};
13use crate::offers::apply_offers;
14use crate::run_events::SuiteEvents;
15use crate::self_diagnostics::DiagnosticNode;
16use crate::test_suite::SuiteRealm;
17use crate::utilities::stream_fn;
18use crate::{diagnostics, facet, resolver};
19use anyhow::{Context, Error, anyhow, format_err};
20use cm_rust::push_box;
21use fidl::endpoints::{ClientEnd, create_proxy};
22use fidl_fuchsia_component_resolution::ResolverProxy;
23use fidl_fuchsia_pkg::PackageResolverProxy;
24use ftest::Invocation;
25use ftest_manager::{CaseStatus, LaunchError, SuiteStatus};
26use fuchsia_async::{self as fasync, TimeoutExt};
27use fuchsia_component::client::connect_to_protocol_at_dir_root;
28use fuchsia_component_test::error::Error as RealmBuilderError;
29use fuchsia_component_test::{
30    Capability, ChildOptions, RealmBuilder, RealmBuilderParams, RealmInstance, Ref, Route,
31};
32use fuchsia_url::AbsoluteComponentUrl;
33use futures::channel::{mpsc, oneshot};
34use futures::future::join_all;
35use futures::prelude::*;
36use futures::{FutureExt, lock};
37use log::{debug, info, warn};
38use moniker::Moniker;
39use resolver::AllowedPackages;
40use std::collections::HashSet;
41use std::fmt;
42use std::sync::Arc;
43use std::sync::atomic::{AtomicU32, Ordering};
44use thiserror::Error;
45use {
46    fidl_fuchsia_component as fcomponent, fidl_fuchsia_component_decl as fdecl,
47    fidl_fuchsia_diagnostics as fdiagnostics, fidl_fuchsia_io as fio, fidl_fuchsia_sys2 as fsys,
48    fidl_fuchsia_test as ftest, fidl_fuchsia_test_manager as ftest_manager,
49    fidl_fuchsia_tracing_provider as ftracing_provider,
50};
51
52const DEBUG_DATA_REALM_NAME: &'static str = "debug-data";
53const ARCHIVIST_REALM_NAME: &'static str = "archivist";
54const DIAGNOSTICS_DICTIONARY_NAME: &'static str = "diagnostics";
55const ARCHIVIST_FOR_EMBEDDING_URL: &'static str = "#meta/archivist-for-embedding.cm";
56const MEMFS_FOR_EMBEDDING_URL: &'static str = "#meta/memfs.cm";
57const MEMFS_REALM_NAME: &'static str = "memfs";
58
59pub const HERMETIC_RESOLVER_CAPABILITY_NAME: &'static str = "hermetic_resolver";
60
61/// A |RunningSuite| represents a launched test component.
62pub(crate) struct RunningSuite {
63    instance: RealmInstance,
64    mock_ready_task: Option<fasync::Task<()>>,
65    logs_iterator_task: Option<fasync::Task<Result<(), Error>>>,
66    /// A connection to the embedded archivist LogSettings protocol. This connection must be kept
67    /// alive for the duration of the test execution for the configured settings to be persisted.
68    log_settings: Option<fdiagnostics::LogSettingsProxy>,
69    /// Server ends of event pairs used to track if a client is accessing a component's
70    /// custom storage. Used to defer destruction of the realm until clients have completed
71    /// reading the storage.
72    custom_artifact_tokens: Vec<zx::EventPair>,
73
74    /// `Realm` protocol for the test root.
75    test_realm_proxy: fcomponent::RealmProxy,
76    /// exposed directory of the test realm.
77    exposed_dir: fio::DirectoryProxy,
78
79    /// Wrapper for `DebugAgent` service.
80    debug_agent: Option<DebugAgent>,
81}
82
83impl RunningSuite {
84    /// Launch a suite component.
85    pub(crate) async fn launch(
86        test_url: &str,
87        facets: facet::SuiteFacets,
88        resolver: Arc<ResolverProxy>,
89        pkg_resolver: Arc<PackageResolverProxy>,
90        above_root_capabilities_for_test: Arc<AboveRootCapabilitiesForTest>,
91        debug_data_sender: DebugDataSender,
92        diagnostics: &DiagnosticNode,
93        suite_realm: &Option<SuiteRealm>,
94        use_debug_agent: bool,
95    ) -> Result<Self, LaunchTestError> {
96        info!(test_url, diagnostics:?, collection = facets.collection; "Starting test suite.");
97
98        let test_package = match AbsoluteComponentUrl::parse(test_url) {
99            Ok(component_url) => component_url.package_url().name().to_string(),
100            Err(_) => match fuchsia_url::boot_url::BootUrl::parse(test_url) {
101                Ok(boot_url) => boot_url.path().to_string(),
102                Err(_) => {
103                    return Err(LaunchTestError::InvalidResolverData);
104                }
105            },
106        };
107        above_root_capabilities_for_test
108            .validate(facets.collection)
109            .map_err(LaunchTestError::ValidateTestRealm)?;
110        let (builder, mock_ready_event) = get_realm(
111            test_url,
112            test_package.as_ref(),
113            &facets,
114            above_root_capabilities_for_test,
115            resolver,
116            pkg_resolver,
117            debug_data_sender,
118            suite_realm,
119        )
120        .await
121        .map_err(LaunchTestError::InitializeTestRealm)?;
122        let instance = builder.build().await.map_err(LaunchTestError::CreateTestRealm)?;
123        let test_realm_proxy: fcomponent::RealmProxy = instance
124            .root
125            .connect_to_protocol_at_exposed_dir()
126            .map_err(|e| LaunchTestError::ConnectToTestSuite(e))?;
127        let collection_ref = fdecl::CollectionRef { name: TEST_ROOT_COLLECTION.into() };
128        let child_decl = fdecl::Child {
129            name: Some(TEST_ROOT_REALM_NAME.into()),
130            url: Some(test_url.into()),
131            startup: Some(fdecl::StartupMode::Eager),
132            environment: None,
133            ..Default::default()
134        };
135        test_realm_proxy
136            .create_child(&collection_ref, &child_decl, fcomponent::CreateChildArgs::default())
137            .await
138            .map_err(|e| LaunchTestError::CreateTestFidl(e))?
139            .map_err(|e| LaunchTestError::CreateTest(e))?;
140
141        let (exposed_dir, server_end) = fidl::endpoints::create_proxy::<fio::DirectoryMarker>();
142        let child_ref = fdecl::ChildRef {
143            name: TEST_ROOT_REALM_NAME.into(),
144            collection: Some(TEST_ROOT_COLLECTION.into()),
145        };
146        test_realm_proxy
147            .open_exposed_dir(&child_ref, server_end)
148            .await
149            .map_err(|e| LaunchTestError::ConnectToTestSuite(e.into()))?
150            .map_err(|e| {
151                LaunchTestError::ConnectToTestSuite(format_err!(
152                    "failed to open exposed dir: {:?}",
153                    e
154                ))
155            })?;
156
157        Ok(RunningSuite {
158            custom_artifact_tokens: vec![],
159            mock_ready_task: Some(fasync::Task::spawn(
160                mock_ready_event.wait_or_dropped().map(|_| ()),
161            )),
162            logs_iterator_task: None,
163            log_settings: None,
164            instance,
165            test_realm_proxy,
166            exposed_dir,
167            debug_agent: if use_debug_agent {
168                DebugAgent::new().await.map_err(log_warning).ok()
169            } else {
170                warn!(
171                    "Not using debug_agent for test {} because it is marked create_no_exception_channel",
172                    test_url
173                );
174                None
175            },
176        })
177    }
178
179    pub(crate) async fn run_tests(
180        &mut self,
181        test_url: &str,
182        mut options: ftest_manager::RunOptions,
183        mut sender: mpsc::Sender<Result<SuiteEvents, LaunchError>>,
184        mut stop_recv: oneshot::Receiver<()>,
185    ) {
186        debug!("running test suite {}", test_url);
187
188        let (log_iterator, syslog) = match options.log_iterator {
189            Some(ftest_manager::LogsIteratorOption::SocketBatchIterator) => {
190                let (local, remote) = zx::Socket::create_stream();
191                (ftest_manager::LogsIterator::Stream(local), ftest_manager::Syslog::Stream(remote))
192            }
193            _ => {
194                let (proxy, request) = fidl::endpoints::create_endpoints();
195                (ftest_manager::LogsIterator::Batch(request), ftest_manager::Syslog::Batch(proxy))
196            }
197        };
198
199        sender.send(Ok(SuiteEvents::suite_syslog(syslog).into())).await.unwrap();
200
201        if let Some(log_interest) = options.log_interest.take() {
202            let log_settings: fdiagnostics::LogSettingsProxy =
203                match self.instance.root.connect_to_protocol_at_exposed_dir() {
204                    Ok(proxy) => proxy,
205                    Err(e) => {
206                        warn!("Error connecting to LogSettings");
207                        sender
208                            .send(Err(LaunchTestError::ConnectToLogSettings(e.into()).into()))
209                            .await
210                            .unwrap();
211                        return;
212                    }
213                };
214
215            let fut = log_settings.set_component_interest(
216                &fdiagnostics::LogSettingsSetComponentInterestRequest {
217                    selectors: Some(log_interest),
218                    persist: Some(false),
219                    ..Default::default()
220                },
221            );
222            if let Err(e) = fut.await {
223                warn!("Error setting log interest");
224                sender.send(Err(LaunchTestError::SetLogInterest(e.into()).into())).await.unwrap();
225                return;
226            }
227            self.log_settings = Some(log_settings);
228        }
229
230        let archive_accessor = match self
231            .instance
232            .root
233            .connect_to_protocol_at_exposed_dir::<fdiagnostics::ArchiveAccessorProxy>()
234        {
235            Ok(accessor) => match accessor.wait_for_ready().await {
236                Ok(()) => accessor,
237                Err(e) => {
238                    warn!("Error connecting to ArchiveAccessor");
239                    sender
240                        .send(Err(LaunchTestError::ConnectToArchiveAccessor(e.into()).into()))
241                        .await
242                        .unwrap();
243                    return;
244                }
245            },
246            Err(e) => {
247                warn!("Error connecting to ArchiveAccessor");
248                sender
249                    .send(Err(LaunchTestError::ConnectToArchiveAccessor(e.into()).into()))
250                    .await
251                    .unwrap();
252                return;
253            }
254        };
255        let host_archive_accessor = match self.instance.root.connect_to_protocol_at_exposed_dir() {
256            Ok(accessor) => accessor,
257            Err(e) => {
258                warn!("Error connecting to ArchiveAccessor");
259                sender
260                    .send(Err(LaunchTestError::ConnectToArchiveAccessor(e.into()).into()))
261                    .await
262                    .unwrap();
263                return;
264            }
265        };
266
267        match diagnostics::serve_syslog(archive_accessor, host_archive_accessor, log_iterator) {
268            Ok(diagnostics::ServeSyslogOutcome { logs_iterator_task }) => {
269                self.logs_iterator_task = logs_iterator_task;
270            }
271            Err(e) => {
272                warn!("Error spawning iterator server: {:?}", e);
273                sender.send(Err(LaunchTestError::StreamIsolatedLogs(e).into())).await.unwrap();
274                return;
275            }
276        };
277
278        let fut = async {
279            let matcher = match options.case_filters_to_run.as_ref() {
280                Some(filters) => match CaseMatcher::new(filters) {
281                    Ok(p) => Some(p),
282                    Err(e) => {
283                        sender.send(Err(LaunchError::InvalidArgs)).await.unwrap();
284                        return Err(e);
285                    }
286                },
287                None => None,
288            };
289
290            let suite = self.connect_to_suite()?;
291            let invocations = match enumerate_test_cases(&suite, matcher.as_ref()).await {
292                Ok(i) if i.is_empty() && matcher.is_some() => {
293                    sender.send(Err(LaunchError::NoMatchingCases)).await.unwrap();
294                    return Err(format_err!("Found no matching cases using {:?}", matcher));
295                }
296                Ok(i) => i,
297                Err(e) => {
298                    sender.send(Err(LaunchError::CaseEnumeration)).await.unwrap();
299                    return Err(e);
300                }
301            };
302            if let Ok(Some(_)) = stop_recv.try_recv() {
303                sender
304                    .send(Ok(SuiteEvents::suite_stopped(SuiteStatus::Stopped).into()))
305                    .await
306                    .unwrap();
307                return Ok(());
308            }
309
310            let mut suite_status = SuiteStatus::Passed;
311            let mut invocations_iter = invocations.into_iter();
312            let counter = AtomicU32::new(0);
313            let timeout_time = match options.timeout {
314                Some(t) => zx::MonotonicInstant::after(zx::MonotonicDuration::from_nanos(t)),
315                None => zx::MonotonicInstant::INFINITE,
316            };
317            let timeout_fut = fasync::Timer::new(timeout_time).shared();
318
319            let run_options = get_invocation_options(options);
320
321            sender.send(Ok(SuiteEvents::suite_started().into())).await.unwrap();
322
323            loop {
324                const INVOCATIONS_CHUNK: usize = 50;
325                let chunk = invocations_iter.by_ref().take(INVOCATIONS_CHUNK).collect::<Vec<_>>();
326                if chunk.is_empty() {
327                    break;
328                }
329                if let Ok(Some(_)) = stop_recv.try_recv() {
330                    sender
331                        .send(Ok(SuiteEvents::suite_stopped(SuiteStatus::Stopped).into()))
332                        .await
333                        .unwrap();
334                    return self.report_custom_artifacts(&mut sender).await;
335                }
336                let res = match run_invocations(
337                    &suite,
338                    chunk,
339                    run_options.clone(),
340                    &counter,
341                    &mut sender,
342                    timeout_fut.clone(),
343                )
344                .await
345                .context("Error running test cases")
346                {
347                    Ok(success) => success,
348                    Err(e) => {
349                        return Err(e);
350                    }
351                };
352                if res == SuiteStatus::TimedOut {
353                    if let Some(debug_agent) = &self.debug_agent {
354                        debug_agent.report_all_backtraces(Some(test_url), sender.clone()).await;
355                    }
356                    sender
357                        .send(Ok(SuiteEvents::suite_stopped(SuiteStatus::TimedOut).into()))
358                        .await
359                        .unwrap();
360                    return self.report_custom_artifacts(&mut sender).await;
361                }
362                suite_status = concat_suite_status(suite_status, res);
363            }
364            sender.send(Ok(SuiteEvents::suite_stopped(suite_status).into())).await.unwrap();
365            if let Some(debug_agent) = &mut self.debug_agent {
366                let mut fatal_exception_receiver = debug_agent.take_fatal_exception_receiver();
367                debug_agent.stop_sending_fatal_exceptions();
368                loop {
369                    match fatal_exception_receiver.next().await {
370                        Some(backtrace_info) => {
371                            let (client_end, mut server_end) =
372                                fidl::handle::fuchsia_handles::Socket::create_stream();
373                            sender
374                                .send(Ok(SuiteEvents::suite_stderr(client_end).into()))
375                                .await
376                                .unwrap();
377
378                            warn!("fatal exception occurred, thread {:?}", backtrace_info.thread);
379                            let _ = server_end.write(
380                                format!("fatal exception, thread {:?}\n", backtrace_info.thread)
381                                    .as_bytes(),
382                            );
383                            DebugAgent::write_backtrace_info(&backtrace_info, &mut server_end)
384                                .await;
385                        }
386                        None => {
387                            break;
388                        }
389                    }
390                }
391            }
392            self.report_custom_artifacts(&mut sender).await
393        };
394        if let Err(e) = fut.await {
395            warn!("Error running test {}: {:?}", test_url, e);
396        }
397    }
398
399    /// Find any custom artifact users under the test realm and report them via sender.
400    async fn report_custom_artifacts(
401        &mut self,
402        sender: &mut mpsc::Sender<Result<SuiteEvents, LaunchError>>,
403    ) -> Result<(), Error> {
404        // TODO(https://fxbug.dev/42074399): Support custom artifacts when test is running in outside
405        // realm.
406        let artifact_storage_admin = self.connect_to_storage_admin()?;
407
408        let root_moniker = "./";
409        let (iterator, iter_server) = create_proxy::<fsys::StorageIteratorMarker>();
410        artifact_storage_admin
411            .list_storage_in_realm(&root_moniker, iter_server)
412            .await?
413            .map_err(|e| format_err!("Error listing storage users in test realm: {:?}", e))?;
414        let stream = stream_fn(move || iterator.next());
415        futures::pin_mut!(stream);
416        while let Some(storage_moniker) = stream.try_next().await? {
417            let (node, server) = fidl::endpoints::create_endpoints::<fio::NodeMarker>();
418            let directory: ClientEnd<fio::DirectoryMarker> = node.into_channel().into();
419            artifact_storage_admin.open_storage(&storage_moniker, server).await?.map_err(|e| {
420                format_err!("Error opening component storage in test realm: {:?}", e)
421            })?;
422            let (event_client, event_server) = zx::EventPair::create();
423            self.custom_artifact_tokens.push(event_server);
424
425            // Monikers should be reported relative to the test root, so strip away the wrapping
426            // components from the path.
427            let moniker_parsed = Moniker::try_from(storage_moniker.as_str()).unwrap();
428            let moniker_relative_to_test_root = if moniker_parsed.is_root() {
429                moniker_parsed
430            } else {
431                Moniker::new_from_borrowed(&moniker_parsed.path()[1..])
432            };
433            sender
434                .send(Ok(SuiteEvents::suite_custom_artifact(ftest_manager::CustomArtifact {
435                    directory_and_token: Some(ftest_manager::DirectoryAndToken {
436                        directory,
437                        token: event_client,
438                    }),
439                    component_moniker: Some(moniker_relative_to_test_root.to_string()),
440                    ..Default::default()
441                })
442                .into()))
443                .await
444                .unwrap();
445        }
446        Ok(())
447    }
448
449    pub(crate) fn connect_to_suite(&self) -> Result<ftest::SuiteProxy, LaunchTestError> {
450        connect_to_protocol_at_dir_root::<ftest::SuiteMarker>(&self.exposed_dir)
451            .map_err(|e| LaunchTestError::ConnectToTestSuite(e))
452    }
453
454    fn connect_to_storage_admin(&self) -> Result<fsys::StorageAdminProxy, LaunchTestError> {
455        self.instance
456            .root
457            .connect_to_protocol_at_exposed_dir()
458            .map_err(|e| LaunchTestError::ConnectToStorageAdmin(e))
459    }
460
461    /// Mark the resources associated with the suite for destruction, then wait for destruction to
462    /// complete. Returns an error only if destruction fails.
463    pub(crate) async fn destroy(self, diagnostics: DiagnosticNode) -> Result<(), Error> {
464        let exposed_dir_fut = self.exposed_dir.close();
465        let exposed_dir_close_task = fasync::Task::spawn(async move {
466            let _ = exposed_dir_fut.await;
467        });
468
469        // before destroying the realm, wait for any clients to finish accessing storage.
470        // TODO(https://fxbug.dev/42165719): Remove signal for USER_0, this is used while Overnet does not support
471        // signalling ZX_EVENTPAIR_CLOSED when the eventpair is closed.
472        let tokens_closed_signals = self.custom_artifact_tokens.iter().map(|token| {
473            fasync::OnSignals::new(token, zx::Signals::EVENTPAIR_PEER_CLOSED | zx::Signals::USER_0)
474                .unwrap_or_else(|_| zx::Signals::empty())
475        });
476        if let Some(mock_ready_task) = self.mock_ready_task {
477            info!(diagnostics:?; "Waiting on mock_ready_task...");
478            mock_ready_task.await;
479        }
480        info!(diagnostics:?; "Waiting on tokens_closed_signals...");
481        futures::future::join_all(tokens_closed_signals).await;
482
483        info!(diagnostics:?; "Waiting on exposed_dir_close_task...");
484        exposed_dir_close_task.await;
485
486        info!(diagnostics:?; "Start destroying test realm...");
487
488        // TODO(https://fxbug.dev/42174479) Remove timeout once component manager hangs are removed.
489        // This value is set to be slightly longer than the shutdown timeout for tests (30 sec).
490        const TEARDOWN_TIMEOUT: zx::MonotonicDuration = zx::MonotonicDuration::from_seconds(32);
491
492        // Make the call to destroy the test, before destroying the entire realm. Once this
493        // completes, it guarantees that any of its service providers (archivist, storage,
494        // debugdata) have received all outgoing requests from the test such as log connections,
495        // etc.
496        let child_ref = fdecl::ChildRef {
497            name: TEST_ROOT_REALM_NAME.into(),
498            collection: Some(TEST_ROOT_COLLECTION.into()),
499        };
500        self.test_realm_proxy
501            .destroy_child(&child_ref)
502            .map_err(|e| Error::from(e).context("call to destroy test failed"))
503            // This should not hang, but wrap it in a timeout just in case.
504            .on_timeout(TEARDOWN_TIMEOUT, || Err(anyhow!("Timeout waiting for test to destroy")))
505            .await?
506            .map_err(|e| format_err!("call to destroy test failed: {:?}", e))?;
507
508        #[derive(Debug, Error)]
509        enum TeardownError {
510            #[error("timeout")]
511            Timeout,
512            #[error("{}", .0)]
513            Other(Error),
514        }
515        // When serving logs over ArchiveIterator in the host, we should also wait for all logs to
516        // be drained.
517        let logs_iterator_task = self
518            .logs_iterator_task
519            .unwrap_or_else(|| fasync::Task::spawn(futures::future::ready(Ok(()))));
520        let (logs_iterator_res, teardown_res) = futures::future::join(
521            logs_iterator_task.map_err(|e| TeardownError::Other(e)).on_timeout(
522                TEARDOWN_TIMEOUT,
523                || {
524                    // This log is detected in triage. Update the config in
525                    // src/diagnostics/config/triage/test_manager.triage when changing this log.
526                    warn!("Test manager timeout draining logs");
527                    Err(TeardownError::Timeout)
528                },
529            ),
530            self.instance.destroy().map_err(|e| TeardownError::Other(e.into())).on_timeout(
531                TEARDOWN_TIMEOUT,
532                || {
533                    // This log is detected in triage. Update the config in
534                    // src/diagnostics/config/triage/test_manager.triage when changing this log.
535                    warn!("Test manager timeout destroying realm");
536                    Err(TeardownError::Timeout)
537                },
538            ),
539        )
540        .await;
541        let logs_iterator_res = match logs_iterator_res {
542            Err(TeardownError::Other(e)) => {
543                // Allow test teardown to proceed if failed to stream logs
544                warn!("Error streaming logs: {}", e);
545                Ok(())
546            }
547            r => r,
548        };
549        match (logs_iterator_res, teardown_res) {
550            (Err(e1), Err(e2)) => {
551                Err(anyhow!("Draining logs failed: {}. Realm teardown failed: {}", e1, e2))
552            }
553            (Err(e), Ok(())) => Err(anyhow!("Draining logs failed: {}", e)),
554            (Ok(()), Err(e)) => Err(anyhow!("Realm teardown failed: {}", e)),
555            (Ok(()), Ok(())) => Ok(()),
556        }
557    }
558}
559
560/// Logs an error an returns it.
561fn log_warning(error: DebugAgentError) -> DebugAgentError {
562    warn!("{}", error);
563    error
564}
565
566/// Enumerates test cases and return invocations.
567pub(crate) async fn enumerate_test_cases(
568    suite: &ftest::SuiteProxy,
569    matcher: Option<&CaseMatcher>,
570) -> Result<Vec<Invocation>, anyhow::Error> {
571    debug!("enumerating tests");
572    let (case_iterator, server_end) = fidl::endpoints::create_proxy();
573    suite.get_tests(server_end).map_err(enumeration_error)?;
574    let mut invocations = vec![];
575
576    loop {
577        let cases = case_iterator.get_next().await.map_err(enumeration_error)?;
578        if cases.is_empty() {
579            break;
580        }
581        for case in cases {
582            let case_name =
583                case.name.ok_or_else(|| format_err!("invocation should contain a name."))?;
584            if matcher.as_ref().map_or(true, |m| m.matches(&case_name)) {
585                invocations.push(Invocation {
586                    name: Some(case_name),
587                    tag: None,
588                    ..Default::default()
589                });
590            }
591        }
592    }
593
594    debug!("invocations: {:#?}", invocations);
595
596    Ok(invocations)
597}
598
599pub(crate) struct CaseMatcher {
600    /// Patterns specifying cases to include.
601    includes: Vec<glob::Pattern>,
602    /// Patterns specifying cases to exclude.
603    excludes: Vec<glob::Pattern>,
604}
605
606impl fmt::Debug for CaseMatcher {
607    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
608        f.debug_struct("CaseMatcher")
609            .field("includes", &self.includes.iter().map(|p| p.to_string()).collect::<Vec<_>>())
610            .field("excludes", &self.excludes.iter().map(|p| p.to_string()).collect::<Vec<_>>())
611            .finish()
612    }
613}
614
615impl CaseMatcher {
616    fn new<T: AsRef<str>>(filters: &Vec<T>) -> Result<Self, anyhow::Error> {
617        let mut matcher = CaseMatcher { includes: vec![], excludes: vec![] };
618        filters.iter().try_for_each(|filter| {
619            match filter.as_ref().chars().next() {
620                Some('-') => {
621                    let pattern = glob::Pattern::new(&filter.as_ref()[1..])
622                        .map_err(|e| format_err!("Bad negative test filter pattern: {}", e))?;
623                    matcher.excludes.push(pattern);
624                }
625                Some(_) | None => {
626                    let pattern = glob::Pattern::new(&filter.as_ref())
627                        .map_err(|e| format_err!("Bad test filter pattern: {}", e))?;
628                    matcher.includes.push(pattern);
629                }
630            }
631            Ok::<(), anyhow::Error>(())
632        })?;
633        Ok(matcher)
634    }
635
636    /// Returns whether or not a case should be run.
637    fn matches(&self, case: &str) -> bool {
638        let matches_includes = match self.includes.is_empty() {
639            true => true,
640            false => self.includes.iter().any(|p| p.matches(case)),
641        };
642        matches_includes && !self.excludes.iter().any(|p| p.matches(case))
643    }
644}
645
646fn get_allowed_package_value(suite_facet: &facet::SuiteFacets) -> AllowedPackages {
647    match &suite_facet.deprecated_allowed_packages {
648        Some(deprecated_allowed_packages) => {
649            AllowedPackages::from_iter(deprecated_allowed_packages.iter().cloned())
650        }
651        None => AllowedPackages::zero_allowed_pkgs(),
652    }
653}
654
655/// Create a RealmBuilder and populate it with local components and routes needed to run a
656/// test. Returns a populated RealmBuilder instance, and an Event which is signalled when
657/// the debug_data local component is ready to accept connection requests.
658// TODO(https://fxbug.dev/42056523): The returned event is part of a synchronization mechanism to
659// work around cases when a component is destroyed before starting, even though there is a
660// request. In this case debug data can be lost. Remove the synchronization once it is
661// provided by cm.
662async fn get_realm(
663    test_url: &str,
664    test_package: &str,
665    suite_facet: &facet::SuiteFacets,
666    above_root_capabilities_for_test: Arc<AboveRootCapabilitiesForTest>,
667    resolver: Arc<ResolverProxy>,
668    pkg_resolver: Arc<PackageResolverProxy>,
669    debug_data_sender: DebugDataSender,
670    suite_realm: &Option<SuiteRealm>,
671) -> Result<(RealmBuilder, async_utils::event::Event), RealmBuilderError> {
672    let mut realm_param = RealmBuilderParams::new();
673    realm_param = match suite_realm {
674        Some(suite_realm) => realm_param
675            .with_realm_proxy(suite_realm.realm_proxy.clone())
676            .in_collection(suite_realm.test_collection.clone()),
677        None => realm_param.in_collection(suite_facet.collection.to_string()),
678    };
679    let builder = RealmBuilder::with_params(realm_param).await?;
680    let wrapper_realm =
681        builder.add_child_realm(WRAPPER_REALM_NAME, ChildOptions::new().eager()).await?;
682
683    let hermetic_test_package_name = Arc::new(test_package.to_owned());
684    let other_allowed_packages = get_allowed_package_value(&suite_facet);
685
686    let hermetic_test_package_name_clone = hermetic_test_package_name.clone();
687    let other_allowed_packages_clone = other_allowed_packages.clone();
688    let resolver = wrapper_realm
689        .add_local_child(
690            HERMETIC_RESOLVER_REALM_NAME,
691            move |handles| {
692                Box::pin(resolver::serve_hermetic_resolver(
693                    handles,
694                    hermetic_test_package_name_clone.clone(),
695                    other_allowed_packages_clone.clone(),
696                    resolver.clone(),
697                    pkg_resolver.clone(),
698                ))
699            },
700            ChildOptions::new(),
701        )
702        .await?;
703
704    // Provide and expose the debug data capability to the test environment.
705    let owned_url = test_url.to_string();
706    let mock_ready_event = async_utils::event::Event::new();
707    let mock_ready_clone = mock_ready_event.clone();
708    let debug_data = wrapper_realm
709        .add_local_child(
710            DEBUG_DATA_REALM_NAME,
711            move |handles| {
712                Box::pin(serve_debug_data_publisher(
713                    handles,
714                    owned_url.clone(),
715                    debug_data_sender.clone(),
716                    mock_ready_clone.clone(),
717                ))
718            },
719            // This component is launched eagerly so that debug_data always signals ready, even
720            // in absence of a connection request. TODO(https://fxbug.dev/42056523): Remove once
721            // synchronization is provided by cm.
722            ChildOptions::new().eager(),
723        )
724        .await?;
725
726    let mut debug_data_decl = wrapper_realm.get_component_decl(&debug_data).await?;
727    push_box(
728        &mut debug_data_decl.exposes,
729        cm_rust::ExposeDecl::Protocol(cm_rust::ExposeProtocolDecl {
730            source: cm_rust::ExposeSource::Self_,
731            source_name: "fuchsia.debugdata.Publisher".parse().unwrap(),
732            source_dictionary: Default::default(),
733            target: cm_rust::ExposeTarget::Parent,
734            target_name: "fuchsia.debugdata.Publisher".parse().unwrap(),
735            availability: cm_rust::Availability::Required,
736        }),
737    );
738    push_box(
739        &mut debug_data_decl.capabilities,
740        cm_rust::CapabilityDecl::Protocol(cm_rust::ProtocolDecl {
741            name: "fuchsia.debugdata.Publisher".parse().unwrap(),
742            source_path: Some("/svc/fuchsia.debugdata.Publisher".parse().unwrap()),
743            delivery: Default::default(),
744        }),
745    );
746    wrapper_realm.replace_component_decl(&debug_data, debug_data_decl).await?;
747
748    // Provide and expose the resolver capability from the resolver to test_wrapper.
749    let mut hermetic_resolver_decl =
750        wrapper_realm.get_component_decl(HERMETIC_RESOLVER_REALM_NAME).await?;
751    push_box(
752        &mut hermetic_resolver_decl.exposes,
753        cm_rust::ExposeDecl::Resolver(cm_rust::ExposeResolverDecl {
754            source: cm_rust::ExposeSource::Self_,
755            source_name: HERMETIC_RESOLVER_CAPABILITY_NAME.parse().unwrap(),
756            source_dictionary: Default::default(),
757            target: cm_rust::ExposeTarget::Parent,
758            target_name: HERMETIC_RESOLVER_CAPABILITY_NAME.parse().unwrap(),
759        }),
760    );
761    push_box(
762        &mut hermetic_resolver_decl.capabilities,
763        cm_rust::CapabilityDecl::Resolver(cm_rust::ResolverDecl {
764            name: HERMETIC_RESOLVER_CAPABILITY_NAME.parse().unwrap(),
765            source_path: Some("/svc/fuchsia.component.resolution.Resolver".parse().unwrap()),
766        }),
767    );
768
769    wrapper_realm
770        .replace_component_decl(HERMETIC_RESOLVER_REALM_NAME, hermetic_resolver_decl)
771        .await?;
772
773    let memfs = wrapper_realm
774        .add_child(MEMFS_REALM_NAME, MEMFS_FOR_EMBEDDING_URL, ChildOptions::new())
775        .await?;
776
777    // Create the hermetic environment in the test_wrapper.
778    let mut test_wrapper_decl = wrapper_realm.get_realm_decl().await?;
779    push_box(
780        &mut test_wrapper_decl.environments,
781        cm_rust::EnvironmentDecl {
782            name: TEST_ENVIRONMENT_NAME.parse().unwrap(),
783            extends: fdecl::EnvironmentExtends::Realm,
784            resolvers: Box::from([cm_rust::ResolverRegistration {
785                resolver: HERMETIC_RESOLVER_CAPABILITY_NAME.parse().unwrap(),
786                source: cm_rust::RegistrationSource::Child(String::from(
787                    HERMETIC_RESOLVER_CAPABILITY_NAME,
788                )),
789                scheme: String::from("fuchsia-pkg"),
790            }]),
791            runners: Box::from([]),
792            debug_capabilities: Box::from([cm_rust::DebugRegistration::Protocol(
793                cm_rust::DebugProtocolRegistration {
794                    source_name: "fuchsia.debugdata.Publisher".parse().unwrap(),
795                    source: cm_rust::RegistrationSource::Child(DEBUG_DATA_REALM_NAME.to_string()),
796                    target_name: "fuchsia.debugdata.Publisher".parse().unwrap(),
797                },
798            )]),
799            stop_timeout_ms: None,
800        },
801    );
802
803    // Add the collection to hold the test. This lets us individually destroy the test.
804    push_box(
805        &mut test_wrapper_decl.collections,
806        cm_rust::CollectionDecl {
807            name: TEST_ROOT_COLLECTION.parse().unwrap(),
808            durability: fdecl::Durability::Transient,
809            environment: Some(TEST_ENVIRONMENT_NAME.parse().unwrap()),
810            allowed_offers: cm_types::AllowedOffers::StaticOnly,
811            allow_long_names: false,
812            persistent_storage: None,
813        },
814    );
815
816    push_box(
817        &mut test_wrapper_decl.capabilities,
818        cm_rust::CapabilityDecl::Storage(cm_rust::StorageDecl {
819            name: CUSTOM_ARTIFACTS_CAPABILITY_NAME.parse().unwrap(),
820            source: cm_rust::StorageDirectorySource::Child(MEMFS_REALM_NAME.to_string()),
821            backing_dir: "memfs".parse().unwrap(),
822            subdir: "custom_artifacts".parse().unwrap(),
823            storage_id: fdecl::StorageId::StaticInstanceIdOrMoniker,
824        }),
825    );
826
827    push_box(
828        &mut test_wrapper_decl.capabilities,
829        cm_rust::CapabilityDecl::Dictionary(cm_rust::DictionaryDecl {
830            name: DIAGNOSTICS_DICTIONARY_NAME.parse().unwrap(),
831            source_path: None,
832        }),
833    );
834
835    wrapper_realm.replace_realm_decl(test_wrapper_decl).await?;
836
837    let test_root = Ref::collection(TEST_ROOT_COLLECTION);
838    let archivist = wrapper_realm
839        .add_child(ARCHIVIST_REALM_NAME, ARCHIVIST_FOR_EMBEDDING_URL, ChildOptions::new().eager())
840        .await?;
841
842    // Parent to archivist
843    builder
844        .add_route(
845            Route::new()
846                .capability(Capability::dictionary(DIAGNOSTICS_DICTIONARY_NAME))
847                .from(Ref::parent())
848                .to(&wrapper_realm),
849        )
850        .await?;
851
852    wrapper_realm
853        .add_route(
854            Route::new()
855                .capability(Capability::dictionary(DIAGNOSTICS_DICTIONARY_NAME))
856                .from(Ref::parent())
857                .to(&memfs),
858        )
859        .await?;
860
861    // archivist to test root
862    wrapper_realm
863        .add_route(
864            Route::new()
865                .capability(Capability::protocol_by_name("fuchsia.logger.Log"))
866                .from(&archivist)
867                .to(test_root.clone()),
868        )
869        .await?;
870
871    // It does not seem possible to route diagnostics capabilities from Archivist to the resolver,
872    // so we must route them from the parent.
873    wrapper_realm
874        .add_route(
875            Route::new()
876                .capability(Capability::protocol_by_name("fuchsia.logger.LogSink"))
877                .capability(Capability::protocol_by_name("fuchsia.inspect.InspectSink"))
878                .from(Ref::dictionary(Ref::parent(), DIAGNOSTICS_DICTIONARY_NAME))
879                .to(&resolver),
880        )
881        .await?;
882
883    wrapper_realm
884        .add_route(
885            Route::new()
886                .capability(Capability::protocol_by_name("fuchsia.logger.LogSink"))
887                .capability(Capability::protocol_by_name("fuchsia.inspect.InspectSink"))
888                .from(&archivist)
889                .to(test_root.clone())
890                .to(Ref::dictionary(Ref::self_(), DIAGNOSTICS_DICTIONARY_NAME)),
891        )
892        .await?;
893    wrapper_realm
894        .add_route(
895            Route::new()
896                .capability(Capability::protocol_by_name("fuchsia.debugdata.Publisher"))
897                .from(&debug_data)
898                .to(Ref::dictionary(Ref::self_(), DIAGNOSTICS_DICTIONARY_NAME)),
899        )
900        .await?;
901
902    // Diagnostics dictionary to resolver and test_root
903    wrapper_realm
904        .add_route(
905            Route::new()
906                .capability(Capability::dictionary(DIAGNOSTICS_DICTIONARY_NAME))
907                .from(Ref::self_())
908                .to(test_root.clone())
909                .to(&resolver),
910        )
911        .await?;
912
913    // archivist to parent and test root
914    wrapper_realm
915        .add_route(
916            Route::new()
917                .capability(Capability::protocol::<fdiagnostics::ArchiveAccessorMarker>())
918                .capability(Capability::protocol::<
919                    fidl_fuchsia_diagnostics_host::ArchiveAccessorMarker,
920                >())
921                .from(Ref::dictionary(&archivist, "diagnostics-accessors"))
922                .to(Ref::parent())
923                .to(test_root.clone()),
924        )
925        .await?;
926
927    wrapper_realm
928        .add_route(
929            Route::new()
930                .capability(Capability::protocol::<fdiagnostics::LogSettingsMarker>())
931                .from(&archivist)
932                .to(Ref::parent()),
933        )
934        .await?;
935
936    wrapper_realm
937        .add_route(
938            Route::new()
939                .capability(Capability::storage(CUSTOM_ARTIFACTS_CAPABILITY_NAME))
940                .from(Ref::self_())
941                .to(test_root.clone()),
942        )
943        .await?;
944
945    wrapper_realm
946        .add_route(
947            Route::new()
948                .capability(Capability::protocol::<fsys::StorageAdminMarker>())
949                .from(Ref::capability(CUSTOM_ARTIFACTS_CAPABILITY_NAME))
950                .to(Ref::parent()),
951        )
952        .await?;
953
954    // We need to expose the raw protocols so that the driver test realm's driver framework
955    // can use it in order to provide support for subpackaged drivers.
956    wrapper_realm
957        .add_route(
958            Route::new()
959                .capability(
960                    Capability::protocol_by_name("fuchsia.component.resolution.Resolver-hermetic")
961                        .path("/svc/fuchsia.component.resolution.Resolver"),
962                )
963                .from(&resolver)
964                .to(test_root.clone()),
965        )
966        .await?;
967    wrapper_realm
968        .add_route(
969            Route::new()
970                .capability(
971                    Capability::protocol_by_name("fuchsia.pkg.PackageResolver-hermetic")
972                        .path("/svc/fuchsia.pkg.PackageResolver"),
973                )
974                .from(&resolver)
975                .to(test_root.clone()),
976        )
977        .await?;
978
979    // wrapper realm to parent
980    wrapper_realm
981        .add_route(
982            Route::new()
983                .capability(Capability::protocol::<fcomponent::RealmMarker>())
984                .from(Ref::framework())
985                .to(Ref::parent()),
986        )
987        .await?;
988
989    // wrapper realm to archivist
990
991    wrapper_realm
992        .add_route(
993            Route::new()
994                .capability(
995                    Capability::event_stream("capability_requested").with_scope(test_root.clone()),
996                )
997                .from(Ref::parent())
998                .to(&archivist),
999        )
1000        .await?;
1001
1002    wrapper_realm
1003        .add_route(
1004            Route::new()
1005                .capability(Capability::protocol::<ftracing_provider::RegistryMarker>().optional())
1006                .from(Ref::void())
1007                .to(&archivist),
1008        )
1009        .await?;
1010
1011    builder
1012        .add_route(
1013            Route::new()
1014                // from archivist
1015                .capability(Capability::protocol::<fdiagnostics::ArchiveAccessorMarker>())
1016                .capability(Capability::protocol::<
1017                    fidl_fuchsia_diagnostics_host::ArchiveAccessorMarker,
1018                >())
1019                .capability(Capability::protocol::<fdiagnostics::LogSettingsMarker>())
1020                // from test root
1021                .capability(Capability::protocol::<ftest::SuiteMarker>())
1022                // custom_artifact capability
1023                .capability(Capability::protocol::<fsys::StorageAdminMarker>())
1024                .capability(Capability::protocol::<fcomponent::RealmMarker>())
1025                .from(&wrapper_realm)
1026                .to(Ref::parent()),
1027        )
1028        .await?;
1029    if let Some(suite_realm) = suite_realm {
1030        apply_offers(&builder, &wrapper_realm, &suite_realm.offers).await?;
1031    } else {
1032        above_root_capabilities_for_test
1033            .apply(suite_facet.collection, &builder, &wrapper_realm)
1034            .await?;
1035    }
1036
1037    Ok((builder, mock_ready_event))
1038}
1039
1040fn get_invocation_options(options: ftest_manager::RunOptions) -> ftest::RunOptions {
1041    ftest::RunOptions {
1042        include_disabled_tests: options.run_disabled_tests,
1043        parallel: options.parallel,
1044        arguments: options.arguments,
1045        break_on_failure: options.break_on_failure,
1046        ..Default::default()
1047    }
1048}
1049
1050/// Runs the test component using `suite` and collects stdout logs and results.
1051async fn run_invocations(
1052    suite: &ftest::SuiteProxy,
1053    invocations: Vec<Invocation>,
1054    run_options: fidl_fuchsia_test::RunOptions,
1055    counter: &AtomicU32,
1056    sender: &mut mpsc::Sender<Result<SuiteEvents, LaunchError>>,
1057    timeout_fut: futures::future::Shared<fasync::Timer>,
1058) -> Result<SuiteStatus, anyhow::Error> {
1059    let (run_listener_client, mut run_listener) = fidl::endpoints::create_request_stream();
1060    suite.run(&invocations, &run_options, run_listener_client)?;
1061
1062    let tasks = Arc::new(lock::Mutex::new(vec![]));
1063    let running_test_cases = Arc::new(lock::Mutex::new(HashSet::new()));
1064    let tasks_clone = tasks.clone();
1065    let initial_suite_status: SuiteStatus;
1066    let mut sender_clone = sender.clone();
1067    let test_fut = async {
1068        let mut initial_suite_status = SuiteStatus::DidNotFinish;
1069        while let Some(result_event) =
1070            run_listener.try_next().await.context("error waiting for listener")?
1071        {
1072            match result_event {
1073                ftest::RunListenerRequest::OnTestCaseStarted {
1074                    invocation,
1075                    std_handles,
1076                    listener,
1077                    control_handle: _,
1078                } => {
1079                    let name = invocation
1080                        .name
1081                        .ok_or_else(|| format_err!("cannot find name in invocation"))?;
1082                    let identifier = counter.fetch_add(1, Ordering::Relaxed);
1083                    let events = vec![
1084                        Ok(SuiteEvents::case_found(identifier, name).into()),
1085                        Ok(SuiteEvents::case_started(identifier).into()),
1086                    ];
1087                    for event in events {
1088                        sender_clone.send(event).await.unwrap();
1089                    }
1090                    let listener = listener.into_stream();
1091                    running_test_cases.lock().await.insert(identifier);
1092                    let running_test_cases = running_test_cases.clone();
1093                    let mut sender = sender_clone.clone();
1094                    let task = fasync::Task::spawn(async move {
1095                        let _ = &std_handles;
1096                        let mut sender_stdout = sender.clone();
1097                        let mut sender_stderr = sender.clone();
1098                        let completion_fut = async {
1099                            let status = listen_for_completion(listener).await;
1100                            sender
1101                                .send(Ok(
1102                                    SuiteEvents::case_stopped(identifier, status.clone()).into()
1103                                ))
1104                                .await
1105                                .unwrap();
1106                            status
1107                        };
1108                        let ftest::StdHandles { out, err, .. } = std_handles;
1109                        let stdout_fut = async move {
1110                            if let Some(out) = out {
1111                                match fasync::OnSignals::new(
1112                                    &out,
1113                                    zx::Signals::SOCKET_READABLE | zx::Signals::SOCKET_PEER_CLOSED,
1114                                )
1115                                .await
1116                                {
1117                                    Ok(signals)
1118                                        if signals.contains(zx::Signals::SOCKET_READABLE) =>
1119                                    {
1120                                        sender_stdout
1121                                            .send(Ok(
1122                                                SuiteEvents::case_stdout(identifier, out).into()
1123                                            ))
1124                                            .await
1125                                            .unwrap();
1126                                    }
1127                                    Ok(_) | Err(_) => (),
1128                                }
1129                            }
1130                        };
1131                        let stderr_fut = async move {
1132                            if let Some(err) = err {
1133                                match fasync::OnSignals::new(
1134                                    &err,
1135                                    zx::Signals::SOCKET_READABLE | zx::Signals::SOCKET_PEER_CLOSED,
1136                                )
1137                                .await
1138                                {
1139                                    Ok(signals)
1140                                        if signals.contains(zx::Signals::SOCKET_READABLE) =>
1141                                    {
1142                                        sender_stderr
1143                                            .send(Ok(
1144                                                SuiteEvents::case_stderr(identifier, err).into()
1145                                            ))
1146                                            .await
1147                                            .unwrap();
1148                                    }
1149                                    Ok(_) | Err(_) => (),
1150                                }
1151                            }
1152                        };
1153                        let (status, (), ()) =
1154                            futures::future::join3(completion_fut, stdout_fut, stderr_fut).await;
1155
1156                        sender
1157                            .send(Ok(SuiteEvents::case_finished(identifier).into()))
1158                            .await
1159                            .unwrap();
1160                        running_test_cases.lock().await.remove(&identifier);
1161                        status
1162                    });
1163                    tasks_clone.lock().await.push(task);
1164                }
1165                ftest::RunListenerRequest::OnFinished { .. } => {
1166                    initial_suite_status = SuiteStatus::Passed;
1167                    break;
1168                }
1169            }
1170        }
1171        Ok(initial_suite_status)
1172    }
1173    .fuse();
1174
1175    futures::pin_mut!(test_fut);
1176    let timeout_fut = timeout_fut.fuse();
1177    futures::pin_mut!(timeout_fut);
1178
1179    futures::select! {
1180        () = timeout_fut => {
1181            let mut all_tasks = vec![];
1182            let mut tasks = tasks.lock().await;
1183            all_tasks.append(&mut tasks);
1184            drop(tasks);
1185            drop(all_tasks);
1186            let running_test_cases = running_test_cases.lock().await;
1187            for i in &*running_test_cases {
1188                sender
1189                    .send(Ok(SuiteEvents::case_stopped(*i, CaseStatus::TimedOut).into()))
1190                    .await
1191                    .unwrap();
1192                sender
1193                    .send(Ok(SuiteEvents::case_finished(*i).into()))
1194                    .await
1195                    .unwrap();
1196            }
1197            return Ok(SuiteStatus::TimedOut);
1198        }
1199        r = test_fut => {
1200            initial_suite_status = match r {
1201                Err(e) => {
1202                    return Err(e);
1203                }
1204                Ok(s) => s,
1205            };
1206        }
1207    }
1208
1209    let mut tasks = tasks.lock().await;
1210    let mut all_tasks = vec![];
1211    all_tasks.append(&mut tasks);
1212    // await for all invocations to complete for which test case never completed.
1213    let suite_status = join_all(all_tasks)
1214        .await
1215        .into_iter()
1216        .fold(initial_suite_status, get_suite_status_from_case_status);
1217    Ok(suite_status)
1218}
1219
1220fn concat_suite_status(initial: SuiteStatus, new: SuiteStatus) -> SuiteStatus {
1221    if initial.into_primitive() > new.into_primitive() {
1222        return initial;
1223    }
1224    return new;
1225}
1226
1227fn get_suite_status_from_case_status(
1228    initial_suite_status: SuiteStatus,
1229    case_status: CaseStatus,
1230) -> SuiteStatus {
1231    let status = match case_status {
1232        CaseStatus::Passed => SuiteStatus::Passed,
1233        CaseStatus::Failed => SuiteStatus::Failed,
1234        CaseStatus::TimedOut => SuiteStatus::TimedOut,
1235        CaseStatus::Skipped => SuiteStatus::Passed,
1236        CaseStatus::Error => SuiteStatus::DidNotFinish,
1237        _ => {
1238            panic!("this should not happen");
1239        }
1240    };
1241    concat_suite_status(initial_suite_status, status)
1242}
1243
1244fn enumeration_error(err: fidl::Error) -> anyhow::Error {
1245    match err {
1246        fidl::Error::ClientChannelClosed { .. } => anyhow::anyhow!(
1247            "The test protocol was closed during enumeration. This may mean that the test is using
1248            wrong test runner or `fuchsia.test.Suite` was not configured correctly. Refer to: \
1249            https://fuchsia.dev/go/components/test-errors"
1250        ),
1251        err => err.into(),
1252    }
1253}
1254
1255/// Listen for test completion on the given |listener|. Returns None if the channel is closed
1256/// before a test completion event.
1257async fn listen_for_completion(mut listener: ftest::CaseListenerRequestStream) -> CaseStatus {
1258    match listener.try_next().await.context("waiting for listener") {
1259        Ok(Some(request)) => {
1260            let ftest::CaseListenerRequest::Finished { result, control_handle: _ } = request;
1261            let result = match result.status {
1262                Some(status) => match status {
1263                    fidl_fuchsia_test::Status::Passed => CaseStatus::Passed,
1264                    fidl_fuchsia_test::Status::Failed => CaseStatus::Failed,
1265                    fidl_fuchsia_test::Status::Skipped => CaseStatus::Skipped,
1266                },
1267                // This will happen when test protocol is not properly implemented
1268                // by the test and it forgets to set the result.
1269                None => CaseStatus::Error,
1270            };
1271            result
1272        }
1273        Err(e) => {
1274            warn!("listener failed: {:?}", e);
1275            CaseStatus::Error
1276        }
1277        Ok(None) => CaseStatus::Error,
1278    }
1279}
1280
1281#[cfg(test)]
1282mod tests {
1283    use crate::constants::{HERMETIC_TESTS_COLLECTION, SYSTEM_TESTS_COLLECTION};
1284
1285    use super::*;
1286    use maplit::hashset;
1287
1288    #[test]
1289    fn case_matcher_tests() {
1290        let all_test_case_names = hashset! {
1291            "Foo.Test1", "Foo.Test2", "Foo.Test3", "Bar.Test1", "Bar.Test2", "Bar.Test3"
1292        };
1293
1294        let cases = vec![
1295            (vec![], all_test_case_names.clone()),
1296            (vec!["Foo.Test1"], hashset! {"Foo.Test1"}),
1297            (vec!["Foo.*"], hashset! {"Foo.Test1", "Foo.Test2", "Foo.Test3"}),
1298            (vec!["-Foo.*"], hashset! {"Bar.Test1", "Bar.Test2", "Bar.Test3"}),
1299            (vec!["Foo.*", "-*.Test2"], hashset! {"Foo.Test1", "Foo.Test3"}),
1300        ];
1301
1302        for (filters, expected_matching_cases) in cases.into_iter() {
1303            let case_matcher = CaseMatcher::new(&filters).expect("Create case matcher");
1304            for test_case in all_test_case_names.iter() {
1305                match expected_matching_cases.contains(test_case) {
1306                    true => assert!(
1307                        case_matcher.matches(test_case),
1308                        "Expected filters {:?} to match test case name {}",
1309                        filters,
1310                        test_case
1311                    ),
1312                    false => assert!(
1313                        !case_matcher.matches(test_case),
1314                        "Expected filters {:?} to not match test case name {}",
1315                        filters,
1316                        test_case
1317                    ),
1318                }
1319            }
1320        }
1321    }
1322
1323    #[test]
1324    fn suite_status() {
1325        let all_case_status = vec![
1326            CaseStatus::Error,
1327            CaseStatus::TimedOut,
1328            CaseStatus::Failed,
1329            CaseStatus::Skipped,
1330            CaseStatus::Passed,
1331        ];
1332        for status in &all_case_status {
1333            assert_eq!(
1334                get_suite_status_from_case_status(SuiteStatus::InternalError, *status),
1335                SuiteStatus::InternalError
1336            );
1337        }
1338
1339        for status in &all_case_status {
1340            let s = get_suite_status_from_case_status(SuiteStatus::TimedOut, *status);
1341            assert_eq!(s, SuiteStatus::TimedOut);
1342        }
1343
1344        for status in &all_case_status {
1345            let s = get_suite_status_from_case_status(SuiteStatus::DidNotFinish, *status);
1346            let mut expected = SuiteStatus::DidNotFinish;
1347            if status == &CaseStatus::TimedOut {
1348                expected = SuiteStatus::TimedOut;
1349            }
1350            assert_eq!(s, expected);
1351        }
1352
1353        for status in &all_case_status {
1354            let s = get_suite_status_from_case_status(SuiteStatus::Failed, *status);
1355            let expected = match *status {
1356                CaseStatus::TimedOut => SuiteStatus::TimedOut,
1357                CaseStatus::Error => SuiteStatus::DidNotFinish,
1358                _ => SuiteStatus::Failed,
1359            };
1360            assert_eq!(s, expected);
1361        }
1362
1363        for status in &all_case_status {
1364            let s = get_suite_status_from_case_status(SuiteStatus::Passed, *status);
1365            let mut expected = SuiteStatus::Passed;
1366            if status == &CaseStatus::Error {
1367                expected = SuiteStatus::DidNotFinish;
1368            }
1369            if status == &CaseStatus::TimedOut {
1370                expected = SuiteStatus::TimedOut;
1371            }
1372            if status == &CaseStatus::Failed {
1373                expected = SuiteStatus::Failed;
1374            }
1375            assert_eq!(s, expected);
1376        }
1377
1378        let all_suite_status = vec![
1379            SuiteStatus::Passed,
1380            SuiteStatus::Failed,
1381            SuiteStatus::TimedOut,
1382            SuiteStatus::Stopped,
1383            SuiteStatus::InternalError,
1384        ];
1385
1386        for initial_status in &all_suite_status {
1387            for status in &all_suite_status {
1388                let s = concat_suite_status(*initial_status, *status);
1389                let expected: SuiteStatus;
1390                if initial_status.into_primitive() > status.into_primitive() {
1391                    expected = *initial_status;
1392                } else {
1393                    expected = *status;
1394                }
1395
1396                assert_eq!(s, expected);
1397            }
1398        }
1399    }
1400
1401    #[test]
1402    fn test_allowed_packages_value() {
1403        let mut suite_facet = facet::SuiteFacets {
1404            collection: HERMETIC_TESTS_COLLECTION,
1405            deprecated_allowed_packages: None,
1406        };
1407
1408        // default for hermetic realm is no other allowed package.
1409        assert_eq!(AllowedPackages::zero_allowed_pkgs(), get_allowed_package_value(&suite_facet));
1410
1411        // deprecated_allowed_packages overrides HERMETIC_TESTS_COLLECTION
1412        suite_facet.deprecated_allowed_packages = Some(vec!["pkg-one".to_owned()]);
1413        assert_eq!(
1414            AllowedPackages::from_iter(["pkg-one".to_owned()]),
1415            get_allowed_package_value(&suite_facet)
1416        );
1417
1418        // test with other collections
1419        suite_facet.collection = SYSTEM_TESTS_COLLECTION;
1420        assert_eq!(
1421            AllowedPackages::from_iter(["pkg-one".to_owned()]),
1422            get_allowed_package_value(&suite_facet)
1423        );
1424
1425        // test default with other collection
1426        suite_facet.deprecated_allowed_packages = None;
1427        assert_eq!(AllowedPackages::zero_allowed_pkgs(), get_allowed_package_value(&suite_facet));
1428    }
1429}