blob: 3496c51facfa0398f07246c181524815b5a9ea81 [file] [log] [blame]
// Copyright 2025 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use crate::errors::TestRunError;
use std::path::PathBuf;
use std::{fs, io};
/// Writer for command invocation stdout and stderr. `StdWriter` writes to a file it creates
/// and to an additional writer. When dropped, it deletes the created file if nothing was
/// written.
pub struct StdWriter {
path: PathBuf,
file: Option<fs::File>,
wrote: bool,
also: Box<dyn io::Write + Send>,
}
impl StdWriter {
/// Creates a new `StdWriter`, creating the file specified by `path`.
pub fn new(path: PathBuf, also: Box<dyn io::Write + Send>) -> Result<Self, TestRunError> {
fs::create_dir_all(path.parent().expect("stdout/err file path has parent"))
.map_err(|e| TestRunError::FailedToCreateFile { path: path.clone(), source: e })?;
let file = fs::File::create(&path)
.map_err(|e| TestRunError::FailedToCreateFile { path: path.clone(), source: e })?;
Ok(Self { path, file: Some(file), wrote: false, also })
}
/// Returns the path of the created file if a write has occurred, None if not.
pub fn path_if_wrote(&self) -> Option<PathBuf> {
if self.wrote {
Some(self.path.clone())
} else {
None
}
}
}
impl io::Write for StdWriter {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.wrote = true;
if let Some(file) = &mut self.file {
file.write(buf)?;
}
self.also.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
if let Some(file) = &mut self.file {
file.flush()?;
}
self.also.flush()
}
}
impl Drop for StdWriter {
fn drop(&mut self) {
self.file = None;
if !self.wrote {
let _ = fs::remove_file(&self.path);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Write;
use std::sync::mpsc::{sync_channel, TryRecvError};
use tempfile::tempdir;
struct FakeWriter {
sender: std::sync::mpsc::SyncSender<FakeWriterMessage>,
}
#[derive(Debug, PartialEq)]
enum FakeWriterMessage {
Written(usize),
Flushed,
}
impl io::Write for FakeWriter {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.sender.send(FakeWriterMessage::Written(buf.len())).unwrap();
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
self.sender.send(FakeWriterMessage::Flushed).unwrap();
Ok(())
}
}
#[fuchsia::test]
fn test_std_writer_no_write() {
let temp_dir = tempdir().expect("to create temporary directory");
let log_path = temp_dir.path().join("stderr");
let (sender, receiver) = sync_channel(10);
let fake_writer = Box::new(FakeWriter { sender });
let under_test = StdWriter::new(log_path.clone(), fake_writer).expect("new should succeed");
assert_eq!(true, log_path.exists());
assert_eq!(true, under_test.path_if_wrote().is_none());
drop(under_test);
assert_eq!(false, log_path.exists());
assert_eq!(Err(TryRecvError::Disconnected), receiver.try_recv());
temp_dir.close().expect("temp directory successfully closed");
}
#[fuchsia::test]
fn test_std_writer_write() {
let temp_dir = tempdir().expect("to create temporary directory");
let log_path = temp_dir.path().join("stderr");
let (sender, receiver) = sync_channel(10);
let fake_writer = Box::new(FakeWriter { sender });
let mut under_test =
StdWriter::new(log_path.clone(), fake_writer).expect("new should succeed");
assert_eq!(true, log_path.exists());
let _ = under_test.write(b"ostrich").expect("write succeeds");
under_test.flush().expect("flush succeeds");
assert_eq!(log_path, under_test.path_if_wrote().expect("path_if_wrote returns something"));
drop(under_test);
assert_eq!(true, log_path.exists());
assert_eq!(
FakeWriterMessage::Written(b"ostrich".len()),
receiver.try_recv().expect("recv succeeds")
);
assert_eq!(FakeWriterMessage::Flushed, receiver.try_recv().expect("recv succeeds"));
assert_eq!(Err(TryRecvError::Disconnected), receiver.try_recv());
assert_eq!(b"ostrich".to_vec(), fs::read(log_path).expect("read from log succeeds"));
temp_dir.close().expect("temp directory successfully closed");
}
}