run_test_suite_lib/output/
directory_with_stdout.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 crate::output::directory::{DirectoryReporter, SchemaVersion};
6use crate::output::mux::{MultiplexedDirectoryWriter, MultiplexedWriter};
7use crate::output::shell::ShellReporter;
8use crate::output::{
9    ArtifactType, DirectoryArtifactType, DynArtifact, DynDirectoryArtifact, EntityId, EntityInfo,
10    ReportedOutcome, Reporter, Timestamp,
11};
12use fuchsia_sync::Mutex;
13use std::fs::File;
14use std::io::{BufWriter, Error};
15use std::path::PathBuf;
16
17/// A reporter implementation that saves output to the structured directory output format, and also
18/// saves human-readable reports to the directory. The reports are generated per-suite, and are
19/// generated using |ShellReporter|.
20// In the future, we may want to make the type of stdout output configurable.
21pub struct DirectoryWithStdoutReporter {
22    directory_reporter: DirectoryReporter,
23    /// Shell reporter for suite. |ShellReporter| is routed events for a suite, and produces
24    /// a report.
25    shell_reporter: Mutex<Option<ShellReporter<BufWriter<File>>>>,
26}
27
28impl DirectoryWithStdoutReporter {
29    pub fn new(root: PathBuf, version: SchemaVersion) -> Result<Self, Error> {
30        Ok(Self {
31            directory_reporter: DirectoryReporter::new(root, version)?,
32            shell_reporter: Mutex::<Option<ShellReporter<BufWriter<File>>>>::new(None),
33        })
34    }
35}
36
37impl Reporter for DirectoryWithStdoutReporter {
38    fn new_entity(&self, entity: &EntityId, name: &str) -> Result<(), Error> {
39        self.directory_reporter.new_entity(entity, name)?;
40
41        match entity {
42            EntityId::Suite => {
43                let human_readable_artifact = self.directory_reporter.add_report(entity)?;
44                let shell_reporter = ShellReporter::new(human_readable_artifact);
45                shell_reporter.new_entity(entity, name)?;
46                let _ = self.shell_reporter.lock().insert(shell_reporter);
47                Ok(())
48            }
49            EntityId::Case { .. } => {
50                self.shell_reporter.lock().as_ref().unwrap().new_entity(entity, name)
51            }
52            EntityId::TestRun => Ok(()),
53        }
54    }
55
56    fn set_entity_info(&self, entity: &EntityId, info: &EntityInfo) {
57        self.directory_reporter.set_entity_info(entity, info);
58
59        match entity {
60            EntityId::Suite | EntityId::Case { .. } => {
61                self.shell_reporter.lock().as_ref().unwrap().set_entity_info(entity, info);
62            }
63            _ => (),
64        };
65    }
66
67    fn entity_started(&self, entity: &EntityId, timestamp: Timestamp) -> Result<(), Error> {
68        self.directory_reporter.entity_started(entity, timestamp)?;
69
70        match entity {
71            EntityId::Suite => {
72                let reporter_guard = self.shell_reporter.lock();
73                // Since we create one reporter per suite, we should start the run at the
74                // same time as the suite.
75                let _ = (*reporter_guard)
76                    .as_ref()
77                    .unwrap()
78                    .entity_started(&EntityId::TestRun, timestamp);
79                (*reporter_guard).as_ref().unwrap().entity_started(entity, timestamp)
80            }
81            EntityId::Case { .. } => {
82                self.shell_reporter.lock().as_ref().unwrap().entity_started(entity, timestamp)
83            }
84            EntityId::TestRun => Ok(()),
85        }
86    }
87
88    fn entity_stopped(
89        &self,
90        entity: &EntityId,
91        outcome: &ReportedOutcome,
92        timestamp: Timestamp,
93    ) -> Result<(), Error> {
94        self.directory_reporter.entity_stopped(entity, outcome, timestamp)?;
95
96        match entity {
97            EntityId::Suite => {
98                let reporter_guard = self.shell_reporter.lock();
99                // Since we create one reporter per suite, we should stop the run at the
100                // same time as the suite.
101                (*reporter_guard).as_ref().unwrap().entity_stopped(entity, outcome, timestamp)?;
102                (*reporter_guard).as_ref().unwrap().entity_stopped(
103                    &EntityId::TestRun,
104                    outcome,
105                    timestamp,
106                )
107            }
108            EntityId::Case { .. } => self
109                .shell_reporter
110                .lock()
111                .as_ref()
112                .unwrap()
113                .entity_stopped(entity, outcome, timestamp),
114            EntityId::TestRun => Ok(()),
115        }
116    }
117
118    fn entity_finished(&self, entity: &EntityId) -> Result<(), Error> {
119        self.directory_reporter.entity_finished(entity)?;
120
121        match entity {
122            EntityId::Suite => {
123                let reporter_guard = self.shell_reporter.lock();
124                // Since we create one reporter per suite, we should finish the run at the
125                // same time as the suite.
126                (*reporter_guard).as_ref().unwrap().entity_finished(entity)?;
127                (*reporter_guard).as_ref().unwrap().entity_finished(&EntityId::TestRun)
128            }
129            EntityId::Case { .. } => {
130                self.shell_reporter.lock().as_ref().unwrap().entity_finished(entity)
131            }
132            EntityId::TestRun => Ok(()),
133        }
134    }
135
136    fn new_artifact(
137        &self,
138        entity: &EntityId,
139        artifact_type: &ArtifactType,
140    ) -> Result<Box<DynArtifact>, Error> {
141        let shell_reporter_artifact = match entity {
142            EntityId::Suite | EntityId::Case { .. } => Some(
143                self.shell_reporter.lock().as_ref().unwrap().new_artifact(entity, artifact_type)?,
144            ),
145            EntityId::TestRun => None,
146        };
147        let directory_reporter_artifact =
148            self.directory_reporter.new_artifact(entity, artifact_type)?;
149        match shell_reporter_artifact {
150            Some(artifact) => {
151                Ok(Box::new(MultiplexedWriter::new(artifact, directory_reporter_artifact)))
152            }
153            None => Ok(directory_reporter_artifact),
154        }
155    }
156
157    fn new_directory_artifact(
158        &self,
159        entity: &EntityId,
160        artifact_type: &DirectoryArtifactType,
161        component_moniker: Option<String>,
162    ) -> Result<Box<DynDirectoryArtifact>, Error> {
163        let component_moniker_clone = component_moniker.clone();
164        let shell_reporter_artifact = match entity {
165            EntityId::Suite | EntityId::Case { .. } => {
166                Some(self.shell_reporter.lock().as_ref().unwrap().new_directory_artifact(
167                    entity,
168                    artifact_type,
169                    component_moniker_clone,
170                )?)
171            }
172            EntityId::TestRun => None,
173        };
174        let directory_reporter_artifact = self.directory_reporter.new_directory_artifact(
175            entity,
176            artifact_type,
177            component_moniker,
178        )?;
179        match shell_reporter_artifact {
180            Some(artifact) => {
181                Ok(Box::new(MultiplexedDirectoryWriter::new(artifact, directory_reporter_artifact)))
182            }
183            None => Ok(directory_reporter_artifact),
184        }
185    }
186}
187
188#[cfg(test)]
189mod test {
190    use super::*;
191    use crate::output::{CaseId, RunReporter};
192    use tempfile::tempdir;
193    use test_output_directory as directory;
194    use test_output_directory::testing::{
195        assert_run_result, ExpectedSuite, ExpectedTestCase, ExpectedTestRun,
196    };
197
198    // these tests are intended to verify that events are routed correctly. The actual contents
199    // are verified more thoroughly in tests for DirectoryReporter and ShellReporter.
200    // TODO(satsukiu): consider adding a reporter that outputs something more structured to stdout
201    // so that we can write more thorough tests.
202
203    #[fuchsia::test]
204    async fn directory_with_stdout() {
205        let dir = tempdir().expect("create temp directory");
206        let run_reporter = RunReporter::new(
207            DirectoryWithStdoutReporter::new(dir.path().to_path_buf(), SchemaVersion::V1).unwrap(),
208        );
209
210        let suite_reporter = run_reporter.new_suite(&format!("test-suite")).expect("create suite");
211        suite_reporter.started(Timestamp::Unknown).expect("start suite");
212        let case_reporter =
213            suite_reporter.new_case("test-case", &CaseId(0)).expect("create test case");
214        case_reporter.started(Timestamp::Unknown).expect("start case");
215        let mut case_stdout =
216            case_reporter.new_artifact(&ArtifactType::Stdout).expect("create stdout");
217        writeln!(case_stdout, "Stdout for test case").expect("write to stdout");
218        case_stdout.flush().expect("flush stdout");
219        case_reporter.stopped(&ReportedOutcome::Passed, Timestamp::Unknown).expect("stop case");
220        case_reporter.finished().expect("finish case");
221        suite_reporter.stopped(&ReportedOutcome::Passed, Timestamp::Unknown).expect("stop suite");
222        suite_reporter.finished().expect("finish suite");
223
224        run_reporter.stopped(&ReportedOutcome::Passed, Timestamp::Unknown).expect("stop run");
225        run_reporter.finished().expect("finish run");
226
227        let mut expected_test = ExpectedTestRun::new(directory::Outcome::Passed);
228
229        let expected_report = format!(
230            "Running test 'test-suite'\n\
231            [RUNNING]\ttest-case\n\
232            [stdout - test-case]\n\
233            Stdout for test case\n\
234            [PASSED]\ttest-case\n\
235            \n\
236            1 out of 1 tests passed...\n\
237            test-suite completed with result: PASSED\n",
238        );
239        let suite = ExpectedSuite::new(format!("test-suite"), directory::Outcome::Passed)
240            .with_artifact(directory::ArtifactType::Report, "report.txt".into(), &expected_report)
241            .with_case(
242                ExpectedTestCase::new("test-case", directory::Outcome::Passed).with_artifact(
243                    directory::ArtifactType::Stdout,
244                    "stdout.txt".into(),
245                    "Stdout for test case\n",
246                ),
247            );
248        expected_test = expected_test.with_suite(suite);
249
250        assert_run_result(dir.path(), &expected_test);
251    }
252}