run_test_suite_lib/output/
mod.rs

1// Copyright 2021 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use fidl_fuchsia_test_manager as ftest_manager;
6use std::borrow::Borrow;
7use std::io::{Error, Write};
8use std::marker::PhantomData;
9use std::path::Path;
10use test_list::TestTag;
11
12mod directory;
13mod directory_with_stdout;
14mod line;
15mod memory;
16mod mux;
17mod noop;
18mod shell;
19
20pub use directory::{DirectoryReporter, SchemaVersion};
21pub use directory_with_stdout::DirectoryWithStdoutReporter;
22use line::AnsiFilterReporter;
23pub use memory::{InMemoryArtifact, InMemoryDirectoryWriter, InMemoryReporter};
24pub use mux::MultiplexedReporter;
25pub use noop::NoopReporter;
26pub use shell::{ShellReporter, ShellWriterView};
27
28pub(crate) type DynArtifact = dyn 'static + Write + Send + Sync;
29pub(crate) type DynDirectoryArtifact = dyn 'static + DirectoryWrite + Send + Sync;
30pub(crate) type DynReporter = dyn 'static + Reporter + Send + Sync;
31
32pub struct EntityReporter<E, T: Borrow<DynReporter>> {
33    reporter: T,
34    entity: EntityId,
35    _entity_type: std::marker::PhantomData<E>,
36}
37
38/// A reporter for structured results scoped to a test run.
39/// To report results and artifacts for a test run, a user should create a `RunReporter`,
40/// and create child `SuiteReporter`s and `CaseReporter`s beneath it to report results and
41/// artifacts scoped to suites and cases. When the run is finished, the user should call
42/// `record` to ensure the results are persisted.
43///
44/// `RunReporter`, `SuiteReporter`, and `CaseReporter` are wrappers around `Reporter`
45/// implementations that statically ensure that some assumptions made by `Reporter` implemtnations
46/// are upheld (for example, that an entity marked finished at most once).
47pub type RunReporter = EntityReporter<(), Box<DynReporter>>;
48/// A reporter for structured results scoped to a test suite. Note this may not outlive
49/// the `RunReporter` from which it is created.
50pub type SuiteReporter<'a> = EntityReporter<(), &'a DynReporter>;
51
52/// A reporter for structured results scoped to a single test case. Note this may not outlive
53/// the `SuiteReporter` from which it is created.
54pub type CaseReporter<'a> = EntityReporter<CaseId, &'a DynReporter>;
55
56impl<E, T: Borrow<DynReporter>> EntityReporter<E, T> {
57    /// Create a new artifact scoped to the suite.
58    pub fn new_artifact(&self, artifact_type: &ArtifactType) -> Result<Box<DynArtifact>, Error> {
59        self.reporter.borrow().new_artifact(&self.entity, artifact_type)
60    }
61
62    /// Create a new directory artifact scoped to the suite.
63    pub fn new_directory_artifact(
64        &self,
65        artifact_type: &DirectoryArtifactType,
66        component_moniker: Option<String>,
67    ) -> Result<Box<DynDirectoryArtifact>, Error> {
68        self.reporter.borrow().new_directory_artifact(
69            &self.entity,
70            artifact_type,
71            component_moniker,
72        )
73    }
74
75    /// Record that the suite has started.
76    pub fn started(&self, timestamp: Timestamp) -> Result<(), Error> {
77        self.reporter.borrow().entity_started(&self.entity, timestamp)
78    }
79
80    /// Record the outcome of the suite.
81    pub fn stopped(&self, outcome: &ReportedOutcome, timestamp: Timestamp) -> Result<(), Error> {
82        self.reporter.borrow().entity_stopped(&self.entity, outcome, timestamp)
83    }
84    /// Finalize and persist the test run.
85    pub fn finished(self) -> Result<(), Error> {
86        self.reporter.borrow().entity_finished(&self.entity)
87    }
88}
89
90impl RunReporter {
91    /// Create a `RunReporter`, where stdout, stderr, syslog, and restricted log artifact types
92    /// are sanitized for ANSI escape sequences before being passed along to |reporter|.
93    pub fn new_ansi_filtered<R: 'static + Reporter + Send + Sync>(reporter: R) -> Self {
94        Self::new(AnsiFilterReporter::new(reporter))
95    }
96
97    /// Create a `RunReporter`.
98    pub fn new<R: 'static + Reporter + Send + Sync>(reporter: R) -> Self {
99        let reporter = Box::new(reporter);
100        Self { reporter, entity: EntityId::TestRun, _entity_type: PhantomData }
101    }
102
103    /// Set the number of expected suites for the run.
104    pub fn set_expected_suites(&self, expected_suites: u32) {
105        self.reporter.set_entity_info(
106            &self.entity,
107            &EntityInfo { expected_children: Some(expected_suites), ..EntityInfo::default() },
108        )
109    }
110
111    /// Record a new suite under the test run.
112    pub fn new_suite(&self, url: &str) -> Result<SuiteReporter<'_>, Error> {
113        let entity = EntityId::Suite;
114        self.reporter.new_entity(&entity, url)?;
115        Ok(SuiteReporter { reporter: self.reporter.borrow(), entity, _entity_type: PhantomData })
116    }
117}
118
119impl<'a> SuiteReporter<'a> {
120    /// Record a new suite under the suite.
121    pub fn new_case(&self, name: &str, case_id: &CaseId) -> Result<CaseReporter<'_>, Error> {
122        let entity = EntityId::Case { case: *case_id };
123        self.reporter.new_entity(&entity, name)?;
124        Ok(CaseReporter { reporter: self.reporter, entity, _entity_type: PhantomData })
125    }
126
127    /// Set the tags for this suite.
128    pub fn set_tags(&self, tags: Vec<TestTag>) {
129        self.reporter.set_entity_info(
130            &self.entity,
131            &EntityInfo { tags: Some(tags), ..EntityInfo::default() },
132        );
133    }
134}
135
136/// An enumeration of different known artifact types.
137#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
138pub enum ArtifactType {
139    Stdout,
140    Stderr,
141    Syslog,
142    RestrictedLog,
143}
144
145/// An enumeration of different known artifact types consisting of multiple files.
146#[derive(Clone, Copy, Debug)]
147pub enum DirectoryArtifactType {
148    /// An arbitrary set of custom files stored by the test in a directory.
149    Custom,
150    /// A collection of debug files. For example: coverage and profiling.
151    Debug,
152}
153
154/// Common outcome type for test results, suites, and test cases.
155#[derive(Clone, Copy, PartialEq, Debug)]
156pub enum ReportedOutcome {
157    Passed,
158    Failed,
159    Inconclusive,
160    Timedout,
161    Error,
162    Skipped,
163    Cancelled,
164    DidNotFinish,
165}
166
167impl std::fmt::Display for ReportedOutcome {
168    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
169        let repr = match self {
170            Self::Passed => "PASSED",
171            Self::Failed => "FAILED",
172            Self::Inconclusive => "INCONCLUSIVE",
173            Self::Timedout => "TIMED_OUT",
174            Self::Error => "ERROR",
175            Self::Skipped => "SKIPPED",
176            Self::Cancelled => "CANCELLED",
177            Self::DidNotFinish => "DID_NOT_FINISH",
178        };
179        write!(f, "{}", repr)
180    }
181}
182
183impl From<ftest_manager::TestCaseResult> for ReportedOutcome {
184    fn from(status: ftest_manager::TestCaseResult) -> Self {
185        match status {
186            ftest_manager::TestCaseResult::Passed => Self::Passed,
187            ftest_manager::TestCaseResult::Failed => Self::Failed,
188            ftest_manager::TestCaseResult::TimedOut => Self::Timedout,
189            ftest_manager::TestCaseResult::Skipped => Self::Skipped,
190            // Test case 'Error' indicates the test failed to report a result, not internal error.
191            ftest_manager::TestCaseResult::Error => Self::DidNotFinish,
192            ftest_manager::TestCaseResultUnknown!() => {
193                panic!("unrecognized case status");
194            }
195        }
196    }
197}
198
199impl From<crate::Outcome> for ReportedOutcome {
200    fn from(outcome: crate::Outcome) -> Self {
201        match outcome {
202            crate::Outcome::Passed => Self::Passed,
203            crate::Outcome::Failed => Self::Failed,
204            crate::Outcome::Inconclusive => Self::Inconclusive,
205            crate::Outcome::Cancelled => Self::Cancelled,
206            crate::Outcome::DidNotFinish => Self::DidNotFinish,
207            crate::Outcome::Timedout => Self::Timedout,
208            crate::Outcome::Error { .. } => Self::Error,
209        }
210    }
211}
212
213impl Into<test_output_directory::ArtifactType> for ArtifactType {
214    fn into(self) -> test_output_directory::ArtifactType {
215        match self {
216            Self::Stdout => test_output_directory::ArtifactType::Stdout,
217            Self::Stderr => test_output_directory::ArtifactType::Stderr,
218            Self::Syslog => test_output_directory::ArtifactType::Syslog,
219            Self::RestrictedLog => test_output_directory::ArtifactType::RestrictedLog,
220        }
221    }
222}
223
224impl Into<test_output_directory::ArtifactType> for DirectoryArtifactType {
225    fn into(self) -> test_output_directory::ArtifactType {
226        match self {
227            Self::Custom => test_output_directory::ArtifactType::Custom,
228            Self::Debug => test_output_directory::ArtifactType::Debug,
229        }
230    }
231}
232
233#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
234pub struct CaseId(pub u32);
235
236#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
237pub enum EntityId {
238    TestRun,
239    Suite,
240    Case { case: CaseId },
241}
242
243#[derive(Default)]
244pub struct EntityInfo {
245    pub expected_children: Option<u32>,
246    pub tags: Option<Vec<TestTag>>,
247}
248
249/// A trait for structs that report test results.
250/// Implementations of `Reporter` serve as the backend powering `RunReporter`.
251///
252/// As with `std::io::Write`, `Reporter` implementations are intended to be composable.
253/// An implementation of `Reporter` may contain other `Reporter` implementors and delegate
254/// calls to them.
255pub trait Reporter: Send + Sync {
256    /// Record a new test suite or test case. This should be called once per entity.
257    fn new_entity(&self, entity: &EntityId, name: &str) -> Result<(), Error>;
258
259    /// Add additional info for an entity.
260    fn set_entity_info(&self, entity: &EntityId, info: &EntityInfo);
261
262    /// Record that a test run, suite or case has started.
263    fn entity_started(&self, entity: &EntityId, timestamp: Timestamp) -> Result<(), Error>;
264
265    /// Record that a test run, suite, or case has stopped.
266    fn entity_stopped(
267        &self,
268        entity: &EntityId,
269        outcome: &ReportedOutcome,
270        timestamp: Timestamp,
271    ) -> Result<(), Error>;
272
273    /// Record that a test run, suite, or case has stopped. After this method is called for
274    /// an entity, no additional events or artifacts may be added to the entity.
275    /// Implementations of `Reporter` may assume that `entity_finished` will be called no more
276    /// than once for any entity.
277    fn entity_finished(&self, entity: &EntityId) -> Result<(), Error>;
278
279    /// Create a new artifact scoped to the referenced entity.
280    fn new_artifact(
281        &self,
282        entity: &EntityId,
283        artifact_type: &ArtifactType,
284    ) -> Result<Box<DynArtifact>, Error>;
285
286    /// Create a new artifact consisting of multiple files.
287    fn new_directory_artifact(
288        &self,
289        entity: &EntityId,
290        artifact_type: &DirectoryArtifactType,
291        component_moniker: Option<String>,
292    ) -> Result<Box<DynDirectoryArtifact>, Error>;
293}
294
295/// A trait for writing artifacts that consist of multiple files organized in a directory.
296pub trait DirectoryWrite {
297    /// Create a new file within the directory. `path` must be a relative path with no parent
298    /// segments.
299    fn new_file(&self, path: &Path) -> Result<Box<DynArtifact>, Error>;
300}
301
302/// A wrapper around Fuchsia's representation of time.
303/// This is added as fuchsia-zircon is not available on host.
304#[derive(Clone, Copy)]
305pub struct ZxTime(i64);
306
307impl ZxTime {
308    pub const fn from_nanos(nanos: i64) -> Self {
309        ZxTime(nanos)
310    }
311
312    pub fn checked_sub(&self, rhs: Self) -> Option<std::time::Duration> {
313        let nanos = self.0 - rhs.0;
314        if nanos < 0 {
315            None
316        } else {
317            Some(std::time::Duration::from_nanos(nanos as u64))
318        }
319    }
320}
321
322#[derive(Clone, Copy)]
323pub enum Timestamp {
324    Unknown,
325    Given(ZxTime),
326}
327
328impl Timestamp {
329    pub fn from_nanos(nanos: Option<i64>) -> Self {
330        match nanos {
331            None => Self::Unknown,
332            Some(n) => Self::Given(ZxTime::from_nanos(n)),
333        }
334    }
335}