use std::sync::atomic::AtomicU64;
use super::plan_rc::PlanRc;
use super::opts::Opts;
use super::info_ref::InfoRef;
use crate::TestStatus;
use crate::PlanRunner;
static ID: AtomicU64 = AtomicU64::new(0);
fn next_id() -> u64 {
ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
}
#[derive(Debug)]
pub struct Plan {
_id: u64,
parent: Option<PlanRc>,
pub(super) info: InfoRef,
ignore_abort: bool,
}
impl Plan {
#[allow(clippy::new_ret_no_self)]
pub fn new() -> PlanRc {
PlanRc::new_root()
}
pub fn from_parent(parent: PlanRc, opts: Opts) -> PlanRc
{
PlanRc::new(Self {
_id: next_id(),
parent: Some(parent.clone()),
info: InfoRef::create(&parent, &opts),
ignore_abort: opts.ignore_abort,
})
}
pub fn with_opts(name: &str) -> Opts
{
Opts::new(name)
}
pub(super) fn new_root() -> Self {
Self {
_id: next_id(),
parent: None,
info: InfoRef::default(),
ignore_abort: false,
}
}
fn finish(&self, duration: std::time::Duration)
{
self.info.with_mut(|i| i.record_duration(duration));
}
fn close(&self, mut report: Box<crate::ReportPlan>)
{
report.close();
match self.parent.as_ref() {
Some(p) => {
p.record_result(report.status);
p.info.with_mut(|i| i.append_report(report));
},
None => panic!("no parent"),
}
}
pub(crate) fn set_status(&self, status: TestStatus)
{
let info = self.info.lock().unwrap();
info.borrow_mut().report.status.update(status);
}
pub(crate) fn set_reason(&self, reason: &str)
{
let info = self.info.lock().unwrap();
if info.borrow().report.reason.is_none() {
info.borrow_mut().report.reason = Some(reason.to_string());
}
}
pub fn get_status(&self) -> TestStatus {
self.info.with(|i| i.report.status)
}
pub fn is_aborted(&self) -> bool {
self.get_status() == TestStatus::Abort && !self.ignore_abort
}
pub fn override_status(&self, status: TestStatus) -> TestStatus
{
let info = self.info.lock().unwrap();
let old = info.borrow().report.status;
info.borrow_mut().report.status = status;
old
}
pub fn record_result(&self, status: TestStatus)
{
let info = self.info.lock().unwrap();
info.borrow_mut().report.status.update(status);
info.borrow_mut().report.counter.inc_from_status(status);
}
fn take_report(&self) -> Box<crate::ReportPlan>
{
let info_lock = self.info.lock().unwrap();
let mut info = info_lock.borrow_mut();
info.report.take()
}
pub(crate) fn append_report(&self, mut report: Box<crate::ReportCase>)
{
let info_lock = self.info.lock().unwrap();
let mut info = info_lock.borrow_mut();
report.finish_io()
.map_err(|e| eprintln!("failed to finish io: {:?}", e))
.ok();
info.report.append_case(report)
}
pub(crate) fn append_comment(&self, s: &str)
{
let info_lock = self.info.lock().unwrap();
let mut info = info_lock.borrow_mut();
info.report.append_comment(s)
}
pub(super) fn run_internal<F>(parent: PlanRc, opts: Opts, f: F) -> TestStatus
where
F: FnOnce(&PlanRunner),
{
let parent_status = parent.get_status();
let new_plan = Self::from_parent(parent, opts);
if new_plan.get_status() != TestStatus::Init {
} else if parent_status == TestStatus::Abort {
new_plan.set_status(TestStatus::Skip);
new_plan.set_reason("parent plan has been aborted");
} else {
new_plan.set_status(TestStatus::Pass);
let stime = std::time::Instant::now();
f(&PlanRunner::from_plan(&new_plan));
let etime = std::time::Instant::now();
new_plan.finish(etime - stime);
}
let mut report = new_plan.take_report();
if let Some(v) = &report.num_planned {
if *v != report.counter.get_total() {
report.status.update(TestStatus::Fail);
if report.reason.is_none() {
report.reason = Some("testcase number mismatch".to_string());
}
}
}
let status = report.status;
new_plan.close(report);
status
}
fn emit(&self)
{
self.info.with(|i| i.emit_report());
}
pub fn destruct(self)
{
}
}
impl Drop for Plan {
fn drop(&mut self) {
if self.parent.is_none() {
self.emit();
assert!(self.get_status().is_ok());
}
}
}