1use 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
60pub(crate) struct RunningSuite {
62 instance: RealmInstance,
63 mock_ready_task: Option<fasync::Task<()>>,
64 logs_iterator_task: Option<fasync::Task<Result<(), Error>>>,
65 log_settings: Option<fdiagnostics::LogSettingsProxy>,
68 custom_artifact_tokens: Vec<zx::EventPair>,
72
73 test_realm_proxy: fcomponent::RealmProxy,
75 exposed_dir: fio::DirectoryProxy,
77
78 debug_agent: Option<DebugAgent>,
80}
81
82impl RunningSuite {
83 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 async fn report_custom_artifacts(
398 &mut self,
399 sender: &mut mpsc::Sender<Result<SuiteEvents, LaunchError>>,
400 ) -> Result<(), Error> {
401 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 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 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 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 const TEARDOWN_TIMEOUT: zx::MonotonicDuration = zx::MonotonicDuration::from_seconds(32);
486
487 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 .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 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 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 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 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
555fn log_warning(error: DebugAgentError) -> DebugAgentError {
557 warn!("{}", error);
558 error
559}
560
561pub(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 includes: Vec<glob::Pattern>,
596 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 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
649async 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 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 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 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 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 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 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 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 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 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 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
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
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 .capability(Capability::protocol::<fdiagnostics::ArchiveAccessorMarker>())
972 .capability(Capability::protocol::<
973 fidl_fuchsia_diagnostics_host::ArchiveAccessorMarker,
974 >())
975 .capability(Capability::protocol::<fdiagnostics::LogSettingsMarker>())
976 .capability(Capability::protocol::<ftest::SuiteMarker>())
978 .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
1006async 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 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
1210async 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 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 assert_eq!(AllowedPackages::zero_allowed_pkgs(), get_allowed_package_value(&suite_facet));
1365
1366 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 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 suite_facet.deprecated_allowed_packages = None;
1382 assert_eq!(AllowedPackages::zero_allowed_pkgs(), get_allowed_package_value(&suite_facet));
1383 }
1384}