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