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 { .. } => EntityId::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};
363 use fixture::fixture;
364 use tempfile::tempdir;
365 use test_output_directory::testing::{
366 assert_run_result, ExpectedDirectory, ExpectedSuite, ExpectedTestCase, ExpectedTestRun,
367 };
368
369 fn version_variants<F>(_name: &str, test_fn: F)
370 where
371 F: Fn(SchemaVersion),
372 {
373 for schema in SchemaVersion::all_variants() {
374 test_fn(schema);
375 }
376 }
377
378 #[fixture(version_variants)]
379 #[fuchsia::test]
380 fn no_artifacts(version: SchemaVersion) {
381 let dir = tempdir().expect("create temp directory");
382 const CASE_TIMES: [(ZxTime, ZxTime); 3] = [
383 (ZxTime::from_nanos(0x1100000), ZxTime::from_nanos(0x2100000)),
384 (ZxTime::from_nanos(0x1200000), ZxTime::from_nanos(0x2200000)),
385 (ZxTime::from_nanos(0x1300000), ZxTime::from_nanos(0x2300000)),
386 ];
387 const SUITE_TIMES: (ZxTime, ZxTime) =
388 (ZxTime::from_nanos(0x1000000), ZxTime::from_nanos(0x2400000));
389
390 let run_reporter = RunReporter::new(
391 DirectoryReporter::new(dir.path().to_path_buf(), version).expect("create run reporter"),
392 );
393 let suite_reporter = run_reporter.new_suite("suite").expect("create suite reporter");
394 suite_reporter.started(Timestamp::Given(SUITE_TIMES.0)).expect("start suite");
395 for case_no in 0..3 {
396 let case_reporter = suite_reporter
397 .new_case(&format!("case-{:?}", case_no), &CaseId(case_no))
398 .expect("create suite reporter");
399 case_reporter
400 .started(Timestamp::Given(CASE_TIMES[case_no as usize].0))
401 .expect("start case");
402 case_reporter.stopped(&ReportedOutcome::Passed, Timestamp::Unknown).expect("stop case");
403 }
404 suite_reporter
405 .stopped(&ReportedOutcome::Failed, Timestamp::Unknown)
406 .expect("set suite outcome");
407 suite_reporter.finished().expect("record suite");
408 run_reporter
409 .stopped(&ReportedOutcome::Timedout, Timestamp::Unknown)
410 .expect("set run outcome");
411 run_reporter.finished().expect("record run");
412
413 assert_run_result(
414 dir.path(),
415 &ExpectedTestRun::new(directory::Outcome::Timedout).with_suite(
416 ExpectedSuite::new("suite", directory::Outcome::Failed)
417 .with_case(ExpectedTestCase::new("case-0", directory::Outcome::Passed))
418 .with_case(ExpectedTestCase::new("case-1", directory::Outcome::Passed))
419 .with_case(ExpectedTestCase::new("case-2", directory::Outcome::Passed)),
420 ),
421 );
422 }
423
424 #[fixture(version_variants)]
425 #[fuchsia::test]
426 fn artifacts_per_entity(version: SchemaVersion) {
427 let dir = tempdir().expect("create temp directory");
428 let run_reporter = RunReporter::new(
429 DirectoryReporter::new(dir.path().to_path_buf(), version).expect("create run reporter"),
430 );
431 let suite_reporter = run_reporter.new_suite("suite").expect("create new suite");
432 run_reporter.started(Timestamp::Unknown).expect("start run");
433 for case_no in 0..3 {
434 let case_reporter = suite_reporter
435 .new_case(&format!("case-1-{:?}", case_no), &CaseId(case_no))
436 .expect("create new case");
437 case_reporter.started(Timestamp::Unknown).expect("start case");
438 let mut artifact =
439 case_reporter.new_artifact(&ArtifactType::Stdout).expect("create case artifact");
440 writeln!(artifact, "stdout from case {:?}", case_no).expect("write to artifact");
441 case_reporter
442 .stopped(&ReportedOutcome::Passed, Timestamp::Unknown)
443 .expect("report case outcome");
444 }
445
446 let mut suite_artifact =
447 suite_reporter.new_artifact(&ArtifactType::Stdout).expect("create suite artifact");
448 writeln!(suite_artifact, "stdout from suite").expect("write to artifact");
449 suite_reporter.started(Timestamp::Unknown).expect("start suite");
450 suite_reporter
451 .stopped(&ReportedOutcome::Passed, Timestamp::Unknown)
452 .expect("report suite outcome");
453 suite_reporter.finished().expect("record suite");
454 drop(suite_artifact); let mut run_artifact =
457 run_reporter.new_artifact(&ArtifactType::Stdout).expect("create run artifact");
458 writeln!(run_artifact, "stdout from run").expect("write to artifact");
459 run_reporter
460 .stopped(&ReportedOutcome::Passed, Timestamp::Unknown)
461 .expect("record run outcome");
462 run_reporter.finished().expect("record run");
463 drop(run_artifact); assert_run_result(
466 dir.path(),
467 &ExpectedTestRun::new(directory::Outcome::Passed)
468 .with_artifact(
469 directory::ArtifactType::Stdout,
470 STDOUT_FILE.into(),
471 "stdout from run\n",
472 )
473 .with_suite(
474 ExpectedSuite::new("suite", directory::Outcome::Passed)
475 .with_case(
476 ExpectedTestCase::new("case-1-0", directory::Outcome::Passed)
477 .with_artifact(
478 directory::ArtifactType::Stdout,
479 STDOUT_FILE.into(),
480 "stdout from case 0\n",
481 ),
482 )
483 .with_case(
484 ExpectedTestCase::new("case-1-1", directory::Outcome::Passed)
485 .with_artifact(
486 directory::ArtifactType::Stdout,
487 STDOUT_FILE.into(),
488 "stdout from case 1\n",
489 ),
490 )
491 .with_case(
492 ExpectedTestCase::new("case-1-2", directory::Outcome::Passed)
493 .with_artifact(
494 directory::ArtifactType::Stdout,
495 STDOUT_FILE.into(),
496 "stdout from case 2\n",
497 ),
498 )
499 .with_artifact(
500 directory::ArtifactType::Stdout,
501 STDOUT_FILE.into(),
502 "stdout from suite\n",
503 ),
504 ),
505 );
506 }
507
508 #[fixture(version_variants)]
509 #[fuchsia::test]
510 fn empty_directory_artifacts(version: SchemaVersion) {
511 let dir = tempdir().expect("create temp directory");
512
513 let run_reporter = RunReporter::new(
514 DirectoryReporter::new(dir.path().to_path_buf(), version).expect("create run reporter"),
515 );
516 run_reporter.started(Timestamp::Unknown).expect("start run");
517 let _run_directory_artifact = run_reporter
518 .new_directory_artifact(&DirectoryArtifactType::Custom, None)
519 .expect("Create run directory artifact");
520
521 let suite_reporter = run_reporter.new_suite("suite").expect("create new suite");
522 suite_reporter.started(Timestamp::Unknown).expect("start suite");
523 let _suite_directory_artifact = suite_reporter
524 .new_directory_artifact(&DirectoryArtifactType::Custom, Some("suite-moniker".into()))
525 .expect("create suite directory artifact");
526
527 let case_reporter =
528 suite_reporter.new_case("case-1-1", &CaseId(1)).expect("create new case");
529 case_reporter.started(Timestamp::Unknown).expect("start case");
530 let _case_directory_artifact = case_reporter
531 .new_directory_artifact(&DirectoryArtifactType::Custom, None)
532 .expect("create suite directory artifact");
533 case_reporter
534 .stopped(&ReportedOutcome::Passed, Timestamp::Unknown)
535 .expect("report case outcome");
536 case_reporter.finished().expect("Case finished");
537
538 suite_reporter
539 .stopped(&ReportedOutcome::Passed, Timestamp::Unknown)
540 .expect("report suite outcome");
541 suite_reporter.finished().expect("record suite");
542
543 run_reporter
544 .stopped(&ReportedOutcome::Passed, Timestamp::Unknown)
545 .expect("record run outcome");
546 run_reporter.finished().expect("record run");
547
548 assert_run_result(
549 dir.path(),
550 &ExpectedTestRun::new(directory::Outcome::Passed)
551 .with_directory_artifact(
552 directory::ArtifactType::Custom,
553 Option::<&str>::None,
554 ExpectedDirectory::new(),
555 )
556 .with_suite(
557 ExpectedSuite::new("suite", directory::Outcome::Passed)
558 .with_directory_artifact(
559 directory::ArtifactMetadata {
560 artifact_type: directory::ArtifactType::Custom.into(),
561 component_moniker: Some("suite-moniker".into()),
562 },
563 Option::<&str>::None,
564 ExpectedDirectory::new(),
565 )
566 .with_case(
567 ExpectedTestCase::new("case-1-1", directory::Outcome::Passed)
568 .with_directory_artifact(
569 directory::ArtifactType::Custom,
570 Option::<&str>::None,
571 ExpectedDirectory::new(),
572 ),
573 ),
574 ),
575 );
576 }
577
578 #[fixture(version_variants)]
579 #[fuchsia::test]
580 fn directory_artifacts(version: SchemaVersion) {
581 let dir = tempdir().expect("create temp directory");
582
583 let run_reporter = RunReporter::new(
584 DirectoryReporter::new(dir.path().to_path_buf(), version).expect("create run reporter"),
585 );
586 run_reporter.started(Timestamp::Unknown).expect("start run");
587 let run_directory_artifact = run_reporter
588 .new_directory_artifact(&DirectoryArtifactType::Custom, None)
589 .expect("Create run directory artifact");
590 let mut run_artifact_file = run_directory_artifact
591 .new_file("run-artifact".as_ref())
592 .expect("Create file in run directory artifact");
593 writeln!(run_artifact_file, "run artifact content").expect("write to run artifact");
594 drop(run_artifact_file); let suite_reporter = run_reporter.new_suite("suite").expect("create new suite");
597 suite_reporter.started(Timestamp::Unknown).expect("start suite");
598 let suite_directory_artifact = suite_reporter
599 .new_directory_artifact(&DirectoryArtifactType::Custom, Some("suite-moniker".into()))
600 .expect("create suite directory artifact");
601 let mut suite_artifact_file = suite_directory_artifact
602 .new_file("suite-artifact".as_ref())
603 .expect("Create file in suite directory artifact");
604 writeln!(suite_artifact_file, "suite artifact content").expect("write to suite artifact");
605 drop(suite_artifact_file); let case_reporter =
608 suite_reporter.new_case("case-1-1", &CaseId(1)).expect("create new case");
609 case_reporter.started(Timestamp::Unknown).expect("start case");
610 let case_directory_artifact = case_reporter
611 .new_directory_artifact(&DirectoryArtifactType::Custom, None)
612 .expect("create suite directory artifact");
613 let mut case_artifact_file = case_directory_artifact
614 .new_file("case-artifact".as_ref())
615 .expect("Create file in case directory artifact");
616 writeln!(case_artifact_file, "case artifact content").expect("write to case artifact");
617 drop(case_artifact_file); case_reporter
619 .stopped(&ReportedOutcome::Passed, Timestamp::Unknown)
620 .expect("report case outcome");
621 case_reporter.finished().expect("Case finished");
622
623 suite_reporter
624 .stopped(&ReportedOutcome::Passed, Timestamp::Unknown)
625 .expect("report suite outcome");
626 suite_reporter.finished().expect("record suite");
627
628 run_reporter
629 .stopped(&ReportedOutcome::Passed, Timestamp::Unknown)
630 .expect("record run outcome");
631 run_reporter.finished().expect("record run");
632
633 assert_run_result(
634 dir.path(),
635 &ExpectedTestRun::new(directory::Outcome::Passed)
636 .with_directory_artifact(
637 directory::ArtifactType::Custom,
638 Option::<&str>::None,
639 ExpectedDirectory::new().with_file("run-artifact", "run artifact content\n"),
640 )
641 .with_suite(
642 ExpectedSuite::new("suite", directory::Outcome::Passed)
643 .with_directory_artifact(
644 directory::ArtifactMetadata {
645 artifact_type: directory::ArtifactType::Custom.into(),
646 component_moniker: Some("suite-moniker".into()),
647 },
648 Option::<&str>::None,
649 ExpectedDirectory::new()
650 .with_file("suite-artifact", "suite artifact content\n"),
651 )
652 .with_case(
653 ExpectedTestCase::new("case-1-1", directory::Outcome::Passed)
654 .with_directory_artifact(
655 directory::ArtifactType::Custom,
656 Option::<&str>::None,
657 ExpectedDirectory::new()
658 .with_file("case-artifact", "case artifact content\n"),
659 ),
660 ),
661 ),
662 );
663 }
664
665 #[fixture(version_variants)]
666 #[fuchsia::test]
667 fn ensure_paths_cannot_escape_directory(version: SchemaVersion) {
668 let dir = tempdir().expect("create temp directory");
669
670 let run_reporter = RunReporter::new(
671 DirectoryReporter::new(dir.path().to_path_buf(), version).expect("create run reporter"),
672 );
673
674 let directory = run_reporter
675 .new_directory_artifact(&DirectoryArtifactType::Custom, None)
676 .expect("make custom directory");
677 assert!(directory.new_file("file.txt".as_ref()).is_ok());
678
679 assert!(directory.new_file("this/is/a/file/path.txt".as_ref()).is_ok());
680
681 assert!(directory.new_file("../file.txt".as_ref()).is_err());
682 assert!(directory.new_file("/file.txt".as_ref()).is_err());
683 assert!(directory.new_file("../this/is/a/file/path.txt".as_ref()).is_err());
684 assert!(directory.new_file("/this/is/a/file/path.txt".as_ref()).is_err());
685 assert!(directory.new_file("/../file.txt".as_ref()).is_err());
686 assert!(directory.new_file("fail/../../file.txt".as_ref()).is_err());
687 }
688
689 #[fixture(version_variants)]
690 #[fuchsia::test]
691 fn early_finish_ok(version: SchemaVersion) {
692 let dir = tempdir().expect("create temp directory");
695 let run_reporter = RunReporter::new(
696 DirectoryReporter::new(dir.path().to_path_buf(), version).expect("create run reporter"),
697 );
698
699 run_reporter.started(Timestamp::Unknown).expect("start test run");
700
701 let suite_reporter = run_reporter.new_suite("suite").expect("create new suite");
703 suite_reporter.started(Timestamp::Unknown).expect("start suite");
704 let case_reporter = suite_reporter.new_case("case", &CaseId(0)).expect("create case");
705 case_reporter.started(Timestamp::Unknown).expect("start case");
706 case_reporter.finished().expect("finish case");
708 suite_reporter.finished().expect("finish suite");
709
710 let no_start_suite_reporter =
712 run_reporter.new_suite("no-start-suite").expect("create new suite");
713 no_start_suite_reporter.finished().expect("finish suite");
714
715 run_reporter.finished().expect("finish test run");
716
717 assert_run_result(
718 dir.path(),
719 &ExpectedTestRun::new(directory::Outcome::Inconclusive)
720 .with_suite(
721 ExpectedSuite::new("suite", directory::Outcome::Inconclusive)
722 .with_case(ExpectedTestCase::new("case", directory::Outcome::Inconclusive)),
723 )
724 .with_suite(ExpectedSuite::new("no-start-suite", directory::Outcome::NotStarted)),
725 );
726 }
727}