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