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