use crate::TestStatus;
use super::generic::Generic;
const MAX_CONTENT_DATA_SIZE: u64 = 512 * 1_024;
#[derive(Debug)]
enum IoFileContent {
File(std::fs::File),
Data(Vec<u8>),
}
#[derive(Debug)]
pub struct IoFile {
pub key: &'static str,
pub ftype: IoFileType,
content: IoFileContent,
}
impl IoFile {
pub fn new(ftype: IoFileType, key: &'static str) -> std::io::Result<Self>
{
Ok(Self {
key: key,
ftype: ftype,
content: IoFileContent::File(tempfile::tempfile()?),
})
}
fn flush(&mut self) -> std::io::Result<()>
{
use std::io::Write;
match &mut self.content {
IoFileContent::File(f) => f.flush(),
_ => Ok(()),
}
}
fn try_clone(&self) -> std::io::Result<std::fs::File>
{
match &self.content {
IoFileContent::File(f) => f.try_clone(),
_ => Err(std::io::ErrorKind::BrokenPipe.into()),
}
}
fn finish(&mut self) -> std::io::Result<()> {
if let IoFileContent::File(f) = &self.content {
if let Ok(m) = f.metadata() {
if m.len() > MAX_CONTENT_DATA_SIZE {
return Ok(());
}
}
let data = Self::read_internal(f)?;
self.content = IoFileContent::Data(data);
}
Ok(())
}
fn read_internal(file: &std::fs::File) -> std::io::Result<Vec<u8>>
{
use std::io::Read;
use std::io::Seek;
use std::io::SeekFrom;
let mut buf = Vec::new();
let mut file = file.try_clone()?;
file.seek(SeekFrom::Start(0))?;
file.read_to_end(&mut buf)?;
let mut pos_s = 0;
while pos_s < buf.len() && buf[pos_s] == b'\n' {
pos_s += 1;
}
let mut pos_e = buf.len();
while pos_e > 0 && buf[pos_e - 1] == b'\n' {
pos_e -= 1;
}
Ok(buf[pos_s..pos_e].to_vec())
}
pub fn read(&self) -> std::io::Result<Vec<u8>>
{
match &self.content {
IoFileContent::File(f) => Self::read_internal(f),
IoFileContent::Data(d) => Ok(d.clone()),
}
}
pub fn read_string(&self) -> std::io::Result<Vec<String>>
{
let mut data: Vec<_> = self.read()?
.split(|c| *c == b'\n')
.map(|v| String::from_utf8_lossy(v).to_string())
.collect();
while matches!(data.last(), Some(data) if data.is_empty()) {
data.pop();
}
Ok(data)
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum IoFileType {
Stderr,
Stdout,
}
trait IoFileAccessor {
fn find_or_insert<'a>(&'a mut self, key: &'static str, ftype: IoFileType) -> std::io::Result<&'a mut IoFile>;
}
impl IoFileAccessor for Vec<IoFile>
{
fn find_or_insert<'a>(&'a mut self, key: &'static str, ftype: IoFileType) -> std::io::Result<&'a mut IoFile>
{
let idx = match self.iter().position(|e| e.ftype == ftype && e.key == key) {
Some(p) => p,
None => {
self.push(IoFile::new(ftype, key)?);
self.len() - 1
}
};
Ok(&mut self[idx])
}
}
#[derive(Debug, Default)]
pub struct Case {
pub(crate) status: TestStatus,
pub(crate) generic: Generic,
pub(crate) backtrace: Option<String>,
pub(crate) reason: Option<String>,
pub(crate) val_expect: Option<String>,
pub(crate) val_got: Option<String>,
pub(crate) stdio: Vec<IoFile>,
}
impl Case {
pub fn new() -> Self
{
Self::default()
}
pub fn record_duration(&mut self, duration: std::time::Duration)
{
self.generic.record_duration(duration)
}
pub fn record_expect(&mut self, s: &str)
{
assert!(self.val_expect.is_none());
self.val_expect = Some(s.to_string());
}
pub fn record_got(&mut self, s: &str)
{
assert!(self.val_got.is_none());
self.val_got = Some(s.to_string());
}
#[allow(dead_code)]
fn read_file(f: &Option<std::fs::File>) -> std::io::Result<Option<String>>
{
fn read(mut f: std::fs::File) -> std::io::Result<String>
{
use std::io::Read;
use std::io::Seek;
use std::io::SeekFrom;
let mut buf = String::new();
f.seek(SeekFrom::Start(0))?;
f.read_to_string(&mut buf)?;
Ok(buf)
}
Ok(if let Some(f) = f {
Some(read(f.try_clone()?)?)
} else {
None
})
}
pub(crate) fn stderr_iter(&self) -> Vec<&IoFile>
{
self.stdio
.iter()
.filter(|v| v.ftype == IoFileType::Stderr)
.collect()
}
pub(crate) fn stdout_iter(&self) -> Vec<&IoFile>
{
self.stdio
.iter()
.filter(|v| v.ftype == IoFileType::Stdout)
.collect()
}
pub fn get_stderr(&mut self) -> std::io::Result<std::fs::File>
{
self.get_stderr_named("default")
}
pub fn get_stderr_named(&mut self, key: &'static str)
-> std::io::Result<std::fs::File>
{
let stdio = self.stdio.find_or_insert(key, IoFileType::Stderr)?;
stdio.flush()?;
stdio.try_clone()
}
pub fn get_stdout(&mut self) -> std::io::Result<std::fs::File>
{
self.get_stdout_named("default")
}
pub fn get_stdout_named(&mut self, key: &'static str)
-> std::io::Result<std::fs::File>
{
let stdio = self.stdio.find_or_insert(key, IoFileType::Stdout)?;
stdio.flush()?;
stdio.try_clone()
}
#[allow(dead_code)]
pub fn set_backtrace(&mut self, s: &str)
{
self.backtrace = Some(s.to_string());
}
pub fn set_reason(&mut self, s: &str)
{
self.reason = Some(s.to_string());
}
pub fn is_ok(&self) -> bool {
self.status.is_ok()
}
pub(crate) fn finish_io(&mut self) -> std::io::Result<()> {
for f in &mut self.stdio {
f.finish()?;
}
Ok(())
}
}