use std::sync::Mutex;
use std::sync::Arc;
use std::cell::RefCell;
use std::collections::HashMap;
use std::path::{ Path, PathBuf };
use std::os::unix::fs::PermissionsExt;
use super::formatters::{ PlanFormatter, PlanFormatterBuilder };
#[cfg(feature = "use-static-destructors")]
mod g {
use static_init::{dynamic};
use super::*;
#[allow(clippy::redundant_closure_call)]
#[dynamic(drop)]
pub(super) static mut OUTPUT_FILES: Mutex<RefCell<HashMap<&'static str, Arc<GlobalFile>>>> = Mutex::default();
}
#[cfg(not(feature = "use-static-destructors"))]
mod g {
use lazy_static::lazy_static;
use super::*;
lazy_static! {
pub(super) static ref OUTPUT_ID: Mutex<RefCell<HashMap<&'static str, u32>>> = Mutex::default();
}
}
#[derive(Clone)]
struct TempFile(Arc<Mutex<RefCell<tempfile::NamedTempFile>>>);
impl TempFile {
fn new(f: tempfile::NamedTempFile) -> Self {
Self(Arc::new(Mutex::new(RefCell::new(f))))
}
fn into_inner(self) -> Option<tempfile::NamedTempFile> {
let res = Arc::try_unwrap(self.0).ok()?;
Some(res.into_inner().unwrap().into_inner())
}
}
impl std::io::Write for TempFile {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize>
{
let lock = self.0.lock().unwrap();
let mut w = lock.borrow_mut();
w.write(buf)
}
fn flush(&mut self) -> std::io::Result<()>
{
let lock = self.0.lock().unwrap();
let mut w = lock.borrow_mut();
w.flush()
}
}
#[derive(Default)]
struct GlobalFile {
inner: Mutex<Option<(Box<dyn PlanFormatter + Send>,
TempFile)>>,
filename: PathBuf,
}
impl GlobalFile {
pub fn new<B>(builder: &B, basename: PathBuf) -> std::io::Result<Self>
where
B: PlanFormatterBuilder<TempFile>,
B::Target: PlanFormatter + Send + 'static,
{
let res = {
let tmpfile = TempFile::new(tempfile::NamedTempFile::new_in(
basename.parent().unwrap_or_else(|| Path::new(".")))?);
let formatter = Box::new(builder.from_writer(tmpfile.clone()));
Self {
inner: Mutex::new(Some((formatter, tmpfile))),
filename: basename,
}
};
Ok(res)
}
pub fn persist(&mut self) {
let output = {
let mut lock = self.inner.lock().unwrap();
lock.take().map(|v| v.1)
};
if let Some(o_ref) = output {
let o = o_ref.into_inner().unwrap();
if let Ok(meta) = std::fs::metadata(o.path()) {
let mut permissions = meta.permissions();
permissions.set_mode(0o644);
std::fs::set_permissions(o.path(), permissions).unwrap();
}
o.persist(&self.filename).unwrap();
}
}
pub fn serialize(&self, plan: &super::ReportPlan) -> Result<(), ()>
{
let lock = self.inner.lock().unwrap();
let fmt = &lock.as_ref().unwrap().0;
fmt.serialize(plan)
}
}
impl Drop for GlobalFile {
fn drop(&mut self) {
self.persist();
}
}
#[cfg(feature = "use-static-destructors")]
fn create_file<B>(builder: B, basename: &str, suffix: &'static str) -> std::io::Result<Arc<GlobalFile>>
where
B: PlanFormatterBuilder<TempFile>,
B::Target: PlanFormatter + Send + 'static,
{
let files_lock = unsafe { g::OUTPUT_FILES.lock().unwrap() };
let mut is_first = true;
loop {
if let Some(v) = (*files_lock).borrow().get(suffix) {
break Ok(v.clone())
}
let mut path = PathBuf::new();
path.push(basename);
path.set_extension(suffix);
let f = Arc::new(GlobalFile::new::<B>(&builder, path)?);
assert!(is_first);
is_first = false;
(*files_lock).borrow_mut().insert(suffix, f);
}
}
#[cfg(not(feature = "use-static-destructors"))]
fn create_file<B>(builder: B, basename: &str, suffix: &'static str) -> std::io::Result<Arc<GlobalFile>>
where
B: PlanFormatterBuilder<TempFile> + Send + 'static
{
let output_id_lock = g::OUTPUT_ID.lock().unwrap();
let output_id = *(*output_id_lock).borrow().get(suffix).unwrap_or(&0);
let mut path = PathBuf::new();
path.push(basename);
path.set_extension(format!("{}.{}", output_id, suffix));
let f = Arc::new(GlobalFile::new::<B>(path)?);
(*output_id_lock).borrow_mut().insert(suffix, output_id + 1);
Ok(f)
}
fn output_tap(basename: &str, plan: &super::ReportPlan) -> Result<(), ()>
{
use crate::report::formatters::tap::TapFormatterBuilder as Builder;
let f = create_file(Builder::new(), basename, "tap").unwrap();
f.serialize(plan)
}
fn output_json(basename: &str, plan: &super::ReportPlan) -> Result<(), ()>
{
use crate::report::formatters::json::JsonFormatterBuilder as Builder;
let f = create_file(Builder::new(), basename, "json").unwrap();
f.serialize(plan)
}
fn output_junit(basename: &str, plan: &super::ReportPlan, nested: bool) -> Result<(), ()>
{
use crate::report::formatters::junit::JUnitFormatterBuilder as Builder;
let f = create_file(Builder::new(nested), basename,
match nested {
true => "nested.junit",
false => "flat.junit",
}).unwrap();
f.serialize(plan)
}
pub fn emit(plan: &super::ReportPlan) -> Result<(),()>
{
let basename = std::env::var("TESTSUITE_OUTPUT").unwrap_or_else(|_| "test-result".to_string());
for o in std::env::var("TESTSUITE_FORMAT").unwrap_or_else(|_| "tap".to_string()).split(',') {
if o.is_empty() {
continue;
}
match o {
"tap" => output_tap(&basename, plan)?,
"json" => output_json(&basename, plan)?,
"junit" |
"junit-flat" => output_junit(&basename, plan, false)?,
"junit-nested" => output_junit(&basename, plan, true)?,
_ => panic!("unsupported format {}", o),
};
}
Ok(())
}