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