1use crate::output::{
6 ArtifactType, DirectoryArtifactType, DirectoryWrite, DynArtifact, DynDirectoryArtifact,
7 EntityId, EntityInfo, ReportedOutcome, Reporter, Timestamp, ZxTime,
8};
9use anyhow::format_err;
10use fuchsia_sync::Mutex;
11use std::borrow::Cow;
12use std::collections::HashMap;
13use std::fs::{DirBuilder, File};
14use std::io::{BufWriter, Error, ErrorKind};
15use std::path::{Path, PathBuf};
16use std::sync::atomic::{AtomicU32, Ordering};
17use test_list::TestTag;
18use test_output_directory as directory;
19
20pub use directory::SchemaVersion;
21
22const STDOUT_FILE: &str = "stdout.txt";
23const STDERR_FILE: &str = "stderr.txt";
24const SYSLOG_FILE: &str = "syslog.txt";
25const REPORT_FILE: &str = "report.txt";
26const RESTRICTED_LOG_FILE: &str = "restricted_logs.txt";
27const CUSTOM_ARTIFACT_DIRECTORY: &str = "custom";
28const DEBUG_ARTIFACT_DIRECTORY: &str = "debug";
29
30const SAVE_AFTER_SUITE_COUNT: u32 = 10;
31
32pub struct DirectoryReporter {
34 output_directory: directory::OutputDirectoryBuilder,
36 entries: Mutex<HashMap<EntityId, EntityEntry>>,
40 name_counter: AtomicU32,
42 suites_finished_counter: AtomicU32,
45}
46
47struct EntityEntry {
49 common: directory::CommonResult,
50 children: Vec<EntityId>,
52 timer: MonotonicTimer,
55 tags: Option<Vec<TestTag>>,
56}
57
58enum MonotonicTimer {
59 Unknown,
60 Started {
62 mono_start_time: ZxTime,
64 },
65 Stopped,
67}
68
69impl DirectoryReporter {
70 pub fn new(root: PathBuf, schema_version: SchemaVersion) -> Result<Self, Error> {
72 let output_directory = directory::OutputDirectoryBuilder::new(root, schema_version)?;
73
74 let mut entries = HashMap::new();
75 entries.insert(
76 EntityId::TestRun,
77 EntityEntry {
78 common: directory::CommonResult {
79 name: "".to_string(),
80 artifact_dir: output_directory.new_artifact_dir()?,
81 outcome: directory::Outcome::NotStarted.into(),
82 start_time: None,
83 duration_milliseconds: None,
84 },
85 children: vec![],
86 timer: MonotonicTimer::Unknown,
87 tags: None,
88 },
89 );
90 let new_self = Self {
91 output_directory,
92 entries: Mutex::new(entries),
93 name_counter: AtomicU32::new(0),
94 suites_finished_counter: AtomicU32::new(1),
95 };
96 new_self.persist_summary()?;
97 Ok(new_self)
98 }
99
100 pub(super) fn add_report(&self, entity: &EntityId) -> Result<BufWriter<File>, Error> {
101 self.new_artifact_inner(entity, directory::ArtifactType::Report)
102 }
103
104 fn persist_summary(&self) -> Result<(), Error> {
105 let entry_lock = self.entries.lock();
106 let run_entry =
107 entry_lock.get(&EntityId::TestRun).expect("Run entry should always be present");
108
109 let mut run_result =
110 directory::TestRunResult { common: Cow::Borrowed(&run_entry.common), suites: vec![] };
111
112 for suite_entity_id in run_entry.children.iter() {
113 let suite_entry =
114 entry_lock.get(suite_entity_id).expect("Nonexistant suite referenced");
115 let mut suite_result = directory::SuiteResult {
116 common: Cow::Borrowed(&suite_entry.common),
117 cases: vec![],
118 tags: suite_entry.tags.as_ref().map(Cow::Borrowed).unwrap_or(Cow::Owned(vec![])),
119 };
120 for case_entity_id in suite_entry.children.iter() {
121 suite_result.cases.push(directory::TestCaseResult {
122 common: Cow::Borrowed(
123 &entry_lock
124 .get(case_entity_id)
125 .expect("Nonexistant case referenced")
126 .common,
127 ),
128 })
129 }
130
131 run_result.suites.push(suite_result);
132 }
133
134 self.output_directory.save_summary(&run_result)
135 }
136
137 fn new_artifact_inner(
138 &self,
139 entity: &EntityId,
140 artifact_type: directory::ArtifactType,
141 ) -> Result<BufWriter<File>, Error> {
142 let mut lock = self.entries.lock();
143 let entry = lock
144 .get_mut(entity)
145 .expect("Attempting to create an artifact for an entity that does not exist");
146 let file = entry
147 .common
148 .artifact_dir
149 .new_artifact(artifact_type, filename_for_type(&artifact_type))?;
150 Ok(BufWriter::new(file))
151 }
152}
153
154impl Reporter for DirectoryReporter {
155 fn new_entity(&self, entity: &EntityId, name: &str) -> Result<(), Error> {
156 let mut entries = self.entries.lock();
157 let parent_id = match entity {
158 EntityId::TestRun => panic!("Cannot create new test run"),
159 EntityId::Suite(_) => EntityId::TestRun,
160 EntityId::Case { suite, .. } => EntityId::Suite(*suite),
161 };
162 let parent = entries
163 .get_mut(&parent_id)
164 .expect("Attempting to create a child for an entity that does not exist");
165 parent.children.push(*entity);
166 entries.insert(
167 *entity,
168 EntityEntry {
169 common: directory::CommonResult {
170 name: name.to_string(),
171 artifact_dir: self.output_directory.new_artifact_dir()?,
172 outcome: directory::Outcome::NotStarted.into(),
173 start_time: None,
174 duration_milliseconds: None,
175 },
176 children: vec![],
177 timer: MonotonicTimer::Unknown,
178 tags: None,
179 },
180 );
181
182 Ok(())
183 }
184
185 fn set_entity_info(&self, entity: &EntityId, info: &EntityInfo) {
186 let mut entries = self.entries.lock();
187 let entry = entries.get_mut(entity).expect("Setting info for entity that does not exist");
188 entry.tags = info.tags.clone();
189 }
190
191 fn entity_started(&self, entity: &EntityId, timestamp: Timestamp) -> Result<(), Error> {
192 let mut entries = self.entries.lock();
193 let entry =
194 entries.get_mut(entity).expect("Outcome reported for an entity that does not exist");
195 entry.common.start_time = Some(
196 std::time::SystemTime::now()
197 .duration_since(std::time::SystemTime::UNIX_EPOCH)
198 .unwrap()
199 .as_millis() as u64,
200 );
201 entry.common.outcome = directory::Outcome::Inconclusive.into();
202 match (&entry.timer, timestamp) {
203 (MonotonicTimer::Unknown, Timestamp::Given(mono_start_time)) => {
204 entry.timer = MonotonicTimer::Started { mono_start_time };
205 }
206 _ => (),
207 }
208 Ok(())
209 }
210
211 fn entity_stopped(
212 &self,
213 entity: &EntityId,
214 outcome: &ReportedOutcome,
215 timestamp: Timestamp,
216 ) -> Result<(), Error> {
217 let mut entries = self.entries.lock();
218 let entry =
219 entries.get_mut(entity).expect("Outcome reported for an entity that does not exist");
220 entry.common.outcome = into_serializable_outcome(*outcome).into();
221
222 if let (MonotonicTimer::Started { mono_start_time }, Timestamp::Given(mono_end_time)) =
223 (&entry.timer, timestamp)
224 {
225 entry.common.duration_milliseconds = Some(
226 mono_end_time
227 .checked_sub(*mono_start_time)
228 .expect("end time must be after start time")
229 .as_millis() as u64,
230 );
231 entry.timer = MonotonicTimer::Stopped;
232 }
233 Ok(())
234 }
235
236 fn entity_finished(&self, entity: &EntityId) -> Result<(), Error> {
239 match entity {
240 EntityId::TestRun => self.persist_summary()?,
241 EntityId::Suite(_) => {
242 let num_saved_suites = self.suites_finished_counter.fetch_add(1, Ordering::Relaxed);
243 if num_saved_suites % SAVE_AFTER_SUITE_COUNT == 0 {
244 self.persist_summary()?;
245 }
246 }
247 EntityId::Case { .. } => (),
249 }
250 Ok(())
251 }
252
253 fn new_artifact(
254 &self,
255 entity: &EntityId,
256 artifact_type: &ArtifactType,
257 ) -> Result<Box<DynArtifact>, Error> {
258 let file = self.new_artifact_inner(entity, (*artifact_type).into())?;
259 Ok(Box::new(file))
260 }
261
262 fn new_directory_artifact(
263 &self,
264 entity: &EntityId,
265 artifact_type: &DirectoryArtifactType,
266 component_moniker: Option<String>,
267 ) -> Result<Box<DynDirectoryArtifact>, Error> {
268 let mut lock = self.entries.lock();
269 let entry = lock
270 .get_mut(entity)
271 .expect("Attempting to create an artifact for an entity that does not exist");
272 let name = format!(
273 "{}-{}",
274 prefix_for_directory_type(artifact_type),
275 self.name_counter.fetch_add(1, Ordering::Relaxed),
276 );
277 let subdir = entry.common.artifact_dir.new_directory_artifact(
278 directory::ArtifactMetadata {
279 artifact_type: directory::MaybeUnknown::Known((*artifact_type).into()),
280 component_moniker,
281 },
282 name,
283 )?;
284
285 Ok(Box::new(DirectoryDirectoryWriter { path: subdir }))
286 }
287}
288
289struct DirectoryDirectoryWriter {
291 path: PathBuf,
292}
293
294impl DirectoryWrite for DirectoryDirectoryWriter {
295 fn new_file(&self, path: &Path) -> Result<Box<DynArtifact>, Error> {
296 let new_path = self.path.join(path);
297 if !new_path.starts_with(&self.path)
300 || new_path.components().any(|c| match c {
301 std::path::Component::ParentDir => true,
302 _ => false,
303 })
304 {
305 return Err(Error::new(
306 ErrorKind::Other,
307 format_err!(
308 "Path {:?} results in destination {:?} that may be outside of {:?}",
309 path,
310 new_path,
311 self.path
312 ),
313 ));
314 }
315 if let Some(parent) = new_path.parent() {
316 if !parent.exists() {
317 DirBuilder::new().recursive(true).create(&parent)?;
318 }
319 }
320
321 let file = BufWriter::new(File::create(new_path)?);
322 Ok(Box::new(file))
323 }
324}
325
326fn prefix_for_directory_type(artifact_type: &DirectoryArtifactType) -> &'static str {
327 match artifact_type {
328 DirectoryArtifactType::Custom => CUSTOM_ARTIFACT_DIRECTORY,
329 DirectoryArtifactType::Debug => DEBUG_ARTIFACT_DIRECTORY,
330 }
331}
332
333fn filename_for_type(artifact_type: &directory::ArtifactType) -> &'static str {
334 match artifact_type {
335 directory::ArtifactType::Stdout => STDOUT_FILE,
336 directory::ArtifactType::Stderr => STDERR_FILE,
337 directory::ArtifactType::Syslog => SYSLOG_FILE,
338 directory::ArtifactType::RestrictedLog => RESTRICTED_LOG_FILE,
339 directory::ArtifactType::Report => REPORT_FILE,
340 directory::ArtifactType::Custom => unreachable!("Custom artifact is not a file"),
341 directory::ArtifactType::Debug => {
342 unreachable!("Debug artifacts must be placed in a directory")
343 }
344 }
345}
346fn into_serializable_outcome(outcome: ReportedOutcome) -> directory::Outcome {
347 match outcome {
348 ReportedOutcome::Passed => directory::Outcome::Passed,
349 ReportedOutcome::Failed => directory::Outcome::Failed,
350 ReportedOutcome::Inconclusive => directory::Outcome::Inconclusive,
351 ReportedOutcome::Timedout => directory::Outcome::Timedout,
352 ReportedOutcome::Error => directory::Outcome::Error,
353 ReportedOutcome::Skipped => directory::Outcome::Skipped,
354 ReportedOutcome::Cancelled => directory::Outcome::Inconclusive,
355 ReportedOutcome::DidNotFinish => directory::Outcome::Inconclusive,
356 }
357}
358
359#[cfg(test)]
360mod test {
361 use super::*;
362 use crate::output::{CaseId, RunReporter, SuiteId};
363 use fixture::fixture;
364 use std::ops::Deref;
365 use tempfile::tempdir;
366 use test_output_directory::testing::{
367 assert_run_result, assert_suite_result, ExpectedDirectory, ExpectedSuite, ExpectedTestCase,
368 ExpectedTestRun,
369 };
370
371 fn version_variants<F>(_name: &str, test_fn: F)
372 where
373 F: Fn(SchemaVersion),
374 {
375 for schema in SchemaVersion::all_variants() {
376 test_fn(schema);
377 }
378 }
379
380 #[fixture(version_variants)]
381 #[fuchsia::test]
382 fn no_artifacts(version: SchemaVersion) {
383 let dir = tempdir().expect("create temp directory");
384 const CASE_TIMES: [(ZxTime, ZxTime); 3] = [
385 (ZxTime::from_nanos(0x1100000), ZxTime::from_nanos(0x2100000)),
386 (ZxTime::from_nanos(0x1200000), ZxTime::from_nanos(0x2200000)),
387 (ZxTime::from_nanos(0x1300000), ZxTime::from_nanos(0x2300000)),
388 ];
389 const SUITE_TIMES: (ZxTime, ZxTime) =
390 (ZxTime::from_nanos(0x1000000), ZxTime::from_nanos(0x2400000));
391
392 let run_reporter = RunReporter::new(
393 DirectoryReporter::new(dir.path().to_path_buf(), version).expect("create run reporter"),
394 );
395 for suite_no in 0..3 {
396 let suite_reporter = run_reporter
397 .new_suite(&format!("suite-{:?}", suite_no), &SuiteId(suite_no))
398 .expect("create suite reporter");
399 suite_reporter.started(Timestamp::Given(SUITE_TIMES.0)).expect("start suite");
400 for case_no in 0..3 {
401 let case_reporter = suite_reporter
402 .new_case(&format!("case-{:?}-{:?}", suite_no, case_no), &CaseId(case_no))
403 .expect("create suite reporter");
404 case_reporter
405 .started(Timestamp::Given(CASE_TIMES[case_no as usize].0))
406 .expect("start case");
407 case_reporter
408 .stopped(&ReportedOutcome::Passed, Timestamp::Unknown)
409 .expect("stop case");
410 }
411 suite_reporter
412 .stopped(&ReportedOutcome::Failed, Timestamp::Unknown)
413 .expect("set suite outcome");
414 suite_reporter.finished().expect("record suite");
415 }
416 run_reporter
417 .stopped(&ReportedOutcome::Timedout, Timestamp::Unknown)
418 .expect("set run outcome");
419 run_reporter.finished().expect("record run");
420
421 assert_run_result(
422 dir.path(),
423 &ExpectedTestRun::new(directory::Outcome::Timedout)
424 .with_suite(
425 ExpectedSuite::new("suite-0", directory::Outcome::Failed)
426 .with_case(ExpectedTestCase::new("case-0-0", directory::Outcome::Passed))
427 .with_case(ExpectedTestCase::new("case-0-1", directory::Outcome::Passed))
428 .with_case(ExpectedTestCase::new("case-0-2", directory::Outcome::Passed)),
429 )
430 .with_suite(
431 ExpectedSuite::new("suite-1", directory::Outcome::Failed)
432 .with_case(ExpectedTestCase::new("case-1-0", directory::Outcome::Passed))
433 .with_case(ExpectedTestCase::new("case-1-1", directory::Outcome::Passed))
434 .with_case(ExpectedTestCase::new("case-1-2", directory::Outcome::Passed)),
435 )
436 .with_suite(
437 ExpectedSuite::new("suite-2", directory::Outcome::Failed)
438 .with_case(ExpectedTestCase::new("case-2-0", directory::Outcome::Passed))
439 .with_case(ExpectedTestCase::new("case-2-1", directory::Outcome::Passed))
440 .with_case(ExpectedTestCase::new("case-2-2", directory::Outcome::Passed)),
441 ),
442 );
443 }
444
445 #[fixture(version_variants)]
446 #[fuchsia::test]
447 fn artifacts_per_entity(version: SchemaVersion) {
448 let dir = tempdir().expect("create temp directory");
449 let run_reporter = RunReporter::new(
450 DirectoryReporter::new(dir.path().to_path_buf(), version).expect("create run reporter"),
451 );
452 let suite_reporter =
453 run_reporter.new_suite("suite-1", &SuiteId(0)).expect("create new suite");
454 run_reporter.started(Timestamp::Unknown).expect("start run");
455 for case_no in 0..3 {
456 let case_reporter = suite_reporter
457 .new_case(&format!("case-1-{:?}", case_no), &CaseId(case_no))
458 .expect("create new case");
459 case_reporter.started(Timestamp::Unknown).expect("start case");
460 let mut artifact =
461 case_reporter.new_artifact(&ArtifactType::Stdout).expect("create case artifact");
462 writeln!(artifact, "stdout from case {:?}", case_no).expect("write to artifact");
463 case_reporter
464 .stopped(&ReportedOutcome::Passed, Timestamp::Unknown)
465 .expect("report case outcome");
466 }
467
468 let mut suite_artifact =
469 suite_reporter.new_artifact(&ArtifactType::Stdout).expect("create suite artifact");
470 writeln!(suite_artifact, "stdout from suite").expect("write to artifact");
471 suite_reporter.started(Timestamp::Unknown).expect("start suite");
472 suite_reporter
473 .stopped(&ReportedOutcome::Passed, Timestamp::Unknown)
474 .expect("report suite outcome");
475 suite_reporter.finished().expect("record suite");
476 drop(suite_artifact); let mut run_artifact =
479 run_reporter.new_artifact(&ArtifactType::Stdout).expect("create run artifact");
480 writeln!(run_artifact, "stdout from run").expect("write to artifact");
481 run_reporter
482 .stopped(&ReportedOutcome::Passed, Timestamp::Unknown)
483 .expect("record run outcome");
484 run_reporter.finished().expect("record run");
485 drop(run_artifact); assert_run_result(
488 dir.path(),
489 &ExpectedTestRun::new(directory::Outcome::Passed)
490 .with_artifact(
491 directory::ArtifactType::Stdout,
492 STDOUT_FILE.into(),
493 "stdout from run\n",
494 )
495 .with_suite(
496 ExpectedSuite::new("suite-1", directory::Outcome::Passed)
497 .with_case(
498 ExpectedTestCase::new("case-1-0", directory::Outcome::Passed)
499 .with_artifact(
500 directory::ArtifactType::Stdout,
501 STDOUT_FILE.into(),
502 "stdout from case 0\n",
503 ),
504 )
505 .with_case(
506 ExpectedTestCase::new("case-1-1", directory::Outcome::Passed)
507 .with_artifact(
508 directory::ArtifactType::Stdout,
509 STDOUT_FILE.into(),
510 "stdout from case 1\n",
511 ),
512 )
513 .with_case(
514 ExpectedTestCase::new("case-1-2", directory::Outcome::Passed)
515 .with_artifact(
516 directory::ArtifactType::Stdout,
517 STDOUT_FILE.into(),
518 "stdout from case 2\n",
519 ),
520 )
521 .with_artifact(
522 directory::ArtifactType::Stdout,
523 STDOUT_FILE.into(),
524 "stdout from suite\n",
525 ),
526 ),
527 );
528 }
529
530 #[fixture(version_variants)]
531 #[fuchsia::test]
532 fn empty_directory_artifacts(version: SchemaVersion) {
533 let dir = tempdir().expect("create temp directory");
534
535 let run_reporter = RunReporter::new(
536 DirectoryReporter::new(dir.path().to_path_buf(), version).expect("create run reporter"),
537 );
538 run_reporter.started(Timestamp::Unknown).expect("start run");
539 let _run_directory_artifact = run_reporter
540 .new_directory_artifact(&DirectoryArtifactType::Custom, None)
541 .expect("Create run directory artifact");
542
543 let suite_reporter =
544 run_reporter.new_suite("suite-1", &SuiteId(0)).expect("create new suite");
545 suite_reporter.started(Timestamp::Unknown).expect("start suite");
546 let _suite_directory_artifact = suite_reporter
547 .new_directory_artifact(&DirectoryArtifactType::Custom, Some("suite-moniker".into()))
548 .expect("create suite directory artifact");
549
550 let case_reporter =
551 suite_reporter.new_case("case-1-1", &CaseId(1)).expect("create new case");
552 case_reporter.started(Timestamp::Unknown).expect("start case");
553 let _case_directory_artifact = case_reporter
554 .new_directory_artifact(&DirectoryArtifactType::Custom, None)
555 .expect("create suite directory artifact");
556 case_reporter
557 .stopped(&ReportedOutcome::Passed, Timestamp::Unknown)
558 .expect("report case outcome");
559 case_reporter.finished().expect("Case finished");
560
561 suite_reporter
562 .stopped(&ReportedOutcome::Passed, Timestamp::Unknown)
563 .expect("report suite outcome");
564 suite_reporter.finished().expect("record suite");
565
566 run_reporter
567 .stopped(&ReportedOutcome::Passed, Timestamp::Unknown)
568 .expect("record run outcome");
569 run_reporter.finished().expect("record run");
570
571 assert_run_result(
572 dir.path(),
573 &ExpectedTestRun::new(directory::Outcome::Passed)
574 .with_directory_artifact(
575 directory::ArtifactType::Custom,
576 Option::<&str>::None,
577 ExpectedDirectory::new(),
578 )
579 .with_suite(
580 ExpectedSuite::new("suite-1", directory::Outcome::Passed)
581 .with_directory_artifact(
582 directory::ArtifactMetadata {
583 artifact_type: directory::ArtifactType::Custom.into(),
584 component_moniker: Some("suite-moniker".into()),
585 },
586 Option::<&str>::None,
587 ExpectedDirectory::new(),
588 )
589 .with_case(
590 ExpectedTestCase::new("case-1-1", directory::Outcome::Passed)
591 .with_directory_artifact(
592 directory::ArtifactType::Custom,
593 Option::<&str>::None,
594 ExpectedDirectory::new(),
595 ),
596 ),
597 ),
598 );
599 }
600
601 #[fixture(version_variants)]
602 #[fuchsia::test]
603 fn directory_artifacts(version: SchemaVersion) {
604 let dir = tempdir().expect("create temp directory");
605
606 let run_reporter = RunReporter::new(
607 DirectoryReporter::new(dir.path().to_path_buf(), version).expect("create run reporter"),
608 );
609 run_reporter.started(Timestamp::Unknown).expect("start run");
610 let run_directory_artifact = run_reporter
611 .new_directory_artifact(&DirectoryArtifactType::Custom, None)
612 .expect("Create run directory artifact");
613 let mut run_artifact_file = run_directory_artifact
614 .new_file("run-artifact".as_ref())
615 .expect("Create file in run directory artifact");
616 writeln!(run_artifact_file, "run artifact content").expect("write to run artifact");
617 drop(run_artifact_file); let suite_reporter =
620 run_reporter.new_suite("suite-1", &SuiteId(0)).expect("create new suite");
621 suite_reporter.started(Timestamp::Unknown).expect("start suite");
622 let suite_directory_artifact = suite_reporter
623 .new_directory_artifact(&DirectoryArtifactType::Custom, Some("suite-moniker".into()))
624 .expect("create suite directory artifact");
625 let mut suite_artifact_file = suite_directory_artifact
626 .new_file("suite-artifact".as_ref())
627 .expect("Create file in suite directory artifact");
628 writeln!(suite_artifact_file, "suite artifact content").expect("write to suite artifact");
629 drop(suite_artifact_file); let case_reporter =
632 suite_reporter.new_case("case-1-1", &CaseId(1)).expect("create new case");
633 case_reporter.started(Timestamp::Unknown).expect("start case");
634 let case_directory_artifact = case_reporter
635 .new_directory_artifact(&DirectoryArtifactType::Custom, None)
636 .expect("create suite directory artifact");
637 let mut case_artifact_file = case_directory_artifact
638 .new_file("case-artifact".as_ref())
639 .expect("Create file in case directory artifact");
640 writeln!(case_artifact_file, "case artifact content").expect("write to case artifact");
641 drop(case_artifact_file); case_reporter
643 .stopped(&ReportedOutcome::Passed, Timestamp::Unknown)
644 .expect("report case outcome");
645 case_reporter.finished().expect("Case finished");
646
647 suite_reporter
648 .stopped(&ReportedOutcome::Passed, Timestamp::Unknown)
649 .expect("report suite outcome");
650 suite_reporter.finished().expect("record suite");
651
652 run_reporter
653 .stopped(&ReportedOutcome::Passed, Timestamp::Unknown)
654 .expect("record run outcome");
655 run_reporter.finished().expect("record run");
656
657 assert_run_result(
658 dir.path(),
659 &ExpectedTestRun::new(directory::Outcome::Passed)
660 .with_directory_artifact(
661 directory::ArtifactType::Custom,
662 Option::<&str>::None,
663 ExpectedDirectory::new().with_file("run-artifact", "run artifact content\n"),
664 )
665 .with_suite(
666 ExpectedSuite::new("suite-1", directory::Outcome::Passed)
667 .with_directory_artifact(
668 directory::ArtifactMetadata {
669 artifact_type: directory::ArtifactType::Custom.into(),
670 component_moniker: Some("suite-moniker".into()),
671 },
672 Option::<&str>::None,
673 ExpectedDirectory::new()
674 .with_file("suite-artifact", "suite artifact content\n"),
675 )
676 .with_case(
677 ExpectedTestCase::new("case-1-1", directory::Outcome::Passed)
678 .with_directory_artifact(
679 directory::ArtifactType::Custom,
680 Option::<&str>::None,
681 ExpectedDirectory::new()
682 .with_file("case-artifact", "case artifact content\n"),
683 ),
684 ),
685 ),
686 );
687 }
688
689 #[fixture(version_variants)]
690 #[fuchsia::test]
691 fn ensure_paths_cannot_escape_directory(version: SchemaVersion) {
692 let dir = tempdir().expect("create temp directory");
693
694 let run_reporter = RunReporter::new(
695 DirectoryReporter::new(dir.path().to_path_buf(), version).expect("create run reporter"),
696 );
697
698 let directory = run_reporter
699 .new_directory_artifact(&DirectoryArtifactType::Custom, None)
700 .expect("make custom directory");
701 assert!(directory.new_file("file.txt".as_ref()).is_ok());
702
703 assert!(directory.new_file("this/is/a/file/path.txt".as_ref()).is_ok());
704
705 assert!(directory.new_file("../file.txt".as_ref()).is_err());
706 assert!(directory.new_file("/file.txt".as_ref()).is_err());
707 assert!(directory.new_file("../this/is/a/file/path.txt".as_ref()).is_err());
708 assert!(directory.new_file("/this/is/a/file/path.txt".as_ref()).is_err());
709 assert!(directory.new_file("/../file.txt".as_ref()).is_err());
710 assert!(directory.new_file("fail/../../file.txt".as_ref()).is_err());
711 }
712
713 #[fixture(version_variants)]
714 #[fuchsia::test]
715 fn duplicate_suite_names_ok(version: SchemaVersion) {
716 let dir = tempdir().expect("create temp directory");
717 let run_reporter = RunReporter::new(
718 DirectoryReporter::new(dir.path().to_path_buf(), version).expect("create run reporter"),
719 );
720
721 let success_suite_reporter =
722 run_reporter.new_suite("suite", &SuiteId(0)).expect("create new suite");
723 success_suite_reporter
724 .stopped(&ReportedOutcome::Passed, Timestamp::Unknown)
725 .expect("report suite outcome");
726 success_suite_reporter
727 .new_artifact(&ArtifactType::Stdout)
728 .expect("create new artifact")
729 .write_all(b"stdout from passed suite\n")
730 .expect("write to artifact");
731 success_suite_reporter.finished().expect("record suite");
732
733 let failed_suite_reporter =
734 run_reporter.new_suite("suite", &SuiteId(1)).expect("create new suite");
735 failed_suite_reporter
736 .stopped(&ReportedOutcome::Failed, Timestamp::Unknown)
737 .expect("report suite outcome");
738 failed_suite_reporter.finished().expect("record suite");
739
740 run_reporter
741 .stopped(&ReportedOutcome::Failed, Timestamp::Unknown)
742 .expect("report run outcome");
743 run_reporter.finished().expect("record run");
744
745 let saved_run_result = directory::TestRunResult::from_dir(dir.path()).expect("parse dir");
746 assert_eq!(
747 saved_run_result.common.deref().outcome,
748 directory::MaybeUnknown::Known(directory::Outcome::Failed)
749 );
750
751 assert_eq!(saved_run_result.suites.len(), 2);
752 let expected_success_suite = ExpectedSuite::new("suite", directory::Outcome::Passed)
754 .with_artifact(
755 directory::ArtifactType::Stdout,
756 STDOUT_FILE.into(),
757 "stdout from passed suite\n",
758 );
759 let expected_failed_suite = ExpectedSuite::new("suite", directory::Outcome::Failed);
760
761 let suite_results = saved_run_result.suites;
762
763 if suite_results[0].common.deref().outcome
764 == directory::MaybeUnknown::Known(directory::Outcome::Passed)
765 {
766 assert_suite_result(dir.path(), &suite_results[0], &expected_success_suite);
767 assert_suite_result(dir.path(), &suite_results[1], &expected_failed_suite);
768 } else {
769 assert_suite_result(dir.path(), &suite_results[0], &expected_failed_suite);
770 assert_suite_result(dir.path(), &suite_results[1], &expected_success_suite);
771 }
772 }
773
774 #[fixture(version_variants)]
775 #[fuchsia::test]
776 fn intermediate_results_persisted(version: SchemaVersion) {
777 let dir = tempdir().expect("create temp directory");
781 let run_reporter = RunReporter::new(
782 DirectoryReporter::new(dir.path().to_path_buf(), version).expect("create run reporter"),
783 );
784
785 assert_run_result(dir.path(), &ExpectedTestRun::new(directory::Outcome::NotStarted));
786
787 run_reporter.started(Timestamp::Unknown).expect("start test run");
788
789 let incomplete_suite_reporter =
791 run_reporter.new_suite("incomplete", &SuiteId(99)).expect("create new suite");
792
793 for i in 0..SAVE_AFTER_SUITE_COUNT {
794 let suite_reporter = run_reporter
795 .new_suite(&format!("suite-{:?}", i), &SuiteId(i))
796 .expect("create new suite");
797 suite_reporter.started(Timestamp::Unknown).expect("start suite");
798 suite_reporter
799 .stopped(&ReportedOutcome::Passed, Timestamp::Unknown)
800 .expect("stop suite");
801 suite_reporter.finished().expect("finish suite");
802 }
803
804 let mut intermediate_run = ExpectedTestRun::new(directory::Outcome::Inconclusive)
805 .with_suite(ExpectedSuite::new("incomplete", directory::Outcome::NotStarted));
806 for i in 0..SAVE_AFTER_SUITE_COUNT {
807 intermediate_run = intermediate_run.with_suite(ExpectedSuite::new(
808 &format!("suite-{:?}", i),
809 directory::Outcome::Passed,
810 ));
811 }
812
813 assert_run_result(dir.path(), &intermediate_run);
814
815 incomplete_suite_reporter.finished().expect("finish suite");
816
817 run_reporter.stopped(&ReportedOutcome::Passed, Timestamp::Unknown).expect("stop test run");
818 run_reporter.finished().expect("finish test run");
819
820 let mut final_run = ExpectedTestRun::new(directory::Outcome::Passed)
821 .with_suite(ExpectedSuite::new("incomplete", directory::Outcome::NotStarted));
822 for i in 0..SAVE_AFTER_SUITE_COUNT {
823 final_run = final_run.with_suite(ExpectedSuite::new(
824 &format!("suite-{:?}", i),
825 directory::Outcome::Passed,
826 ));
827 }
828
829 assert_run_result(dir.path(), &final_run);
830 }
831
832 #[fixture(version_variants)]
833 #[fuchsia::test]
834 fn early_finish_ok(version: SchemaVersion) {
835 let dir = tempdir().expect("create temp directory");
838 let run_reporter = RunReporter::new(
839 DirectoryReporter::new(dir.path().to_path_buf(), version).expect("create run reporter"),
840 );
841
842 run_reporter.started(Timestamp::Unknown).expect("start test run");
843
844 let suite_reporter =
846 run_reporter.new_suite("suite", &SuiteId(0)).expect("create new suite");
847 suite_reporter.started(Timestamp::Unknown).expect("start suite");
848 let case_reporter = suite_reporter.new_case("case", &CaseId(0)).expect("create case");
849 case_reporter.started(Timestamp::Unknown).expect("start case");
850 case_reporter.finished().expect("finish case");
852 suite_reporter.finished().expect("finish suite");
853
854 let no_start_suite_reporter =
856 run_reporter.new_suite("no-start-suite", &SuiteId(1)).expect("create new suite");
857 no_start_suite_reporter.finished().expect("finish suite");
858
859 run_reporter.finished().expect("finish test run");
860
861 assert_run_result(
862 dir.path(),
863 &ExpectedTestRun::new(directory::Outcome::Inconclusive)
864 .with_suite(
865 ExpectedSuite::new("suite", directory::Outcome::Inconclusive)
866 .with_case(ExpectedTestCase::new("case", directory::Outcome::Inconclusive)),
867 )
868 .with_suite(ExpectedSuite::new("no-start-suite", directory::Outcome::NotStarted)),
869 );
870 }
871}