use anyhow::Result;
use log::*;
use std::io::{BufRead, BufReader, ErrorKind, Read};
use std::mem;
use std::time::Instant;
use crate::timeout::Timer;
use crate::{CaselistResult, FailCounter, TestResult, TestStatus};
#[derive(Debug)]
pub enum ParserState {
BeginTest(String),
SubTest(String, TestStatus),
EndTest(TestStatus),
FullTest(String, TestStatus),
}
pub trait ResultParser: Sized {
fn initialize(&mut self) -> Option<ParserState> {
None
}
fn parse_line(&mut self, line: &str) -> Result<Option<ParserState>>;
fn parse(self, output: impl Read, fail_counter: Option<FailCounter>) -> Result<CaselistResult> {
self.parse_with_timer(output, None, fail_counter)
}
fn default_test_status(&self) -> TestStatus {
TestStatus::Crash
}
fn parse_with_timer(
mut self,
output: impl Read,
timer: Option<Timer>,
fail_counter: Option<FailCounter>,
) -> Result<CaselistResult> {
let output = BufReader::new(output);
let mut stdout: Vec<String> = Vec::new();
let mut reason = None;
let mut current_test = None;
let mut results = Vec::new();
let mut subtests = Vec::new();
let mut subtest_start = None;
let mut fulltest_start = None;
fn push_result(
results: &mut Vec<TestResult>,
name: String,
status: TestStatus,
time: Instant,
subtests: Vec<TestResult>,
fail_counter: Option<FailCounter>,
) {
if let Some(pos) = results.iter().position(|x: &TestResult| x.name == name) {
error!("Duplicate test found, marking test failed: {}", name);
results[pos].status = TestStatus::Fail;
} else {
if let Some(counter) = fail_counter {
counter.add_test_result(status, &name);
}
results.push(TestResult {
name,
status,
duration: time.elapsed(),
subtests,
});
}
}
if let Some(state) = self.initialize() {
match state {
ParserState::BeginTest(name) => {
current_test = Some((name, Instant::now()));
subtest_start = Some(Instant::now());
}
_ => panic!("Unexpected state while initializing parser: {:?}", state),
}
} else {
fulltest_start = Some(Instant::now())
}
for line in output.lines() {
let line = match line {
Ok(line) => line,
Err(e) => {
if let ErrorKind::TimedOut = e.kind() {
reason = Some(TestStatus::Timeout);
} else {
reason = Some(TestStatus::Crash);
}
break;
}
};
stdout.push(line);
let line = stdout.last().unwrap();
if let Some(state) = self.parse_line(line)? {
match state {
ParserState::BeginTest(name) => {
current_test = Some((name, Instant::now()));
subtest_start = Some(Instant::now());
if let Some(ref timer) = timer {
timer.reset();
}
}
ParserState::SubTest(name, status) => {
let time = subtest_start.replace(Instant::now()).unwrap();
push_result(&mut subtests, name, status, time, vec![], None);
if let Some(ref timer) = timer {
timer.reset();
}
}
ParserState::EndTest(status) => {
if let Some((name, time)) = current_test.take() {
push_result(
&mut results,
name,
status,
time,
mem::take(&mut subtests),
fail_counter.clone(),
);
}
subtest_start = None;
}
ParserState::FullTest(name, status) => {
let time = fulltest_start.replace(Instant::now()).unwrap();
push_result(
&mut results,
name,
status,
time,
vec![],
fail_counter.clone(),
);
if let Some(ref timer) = timer {
timer.reset();
}
}
}
}
if let Some(counter) = &fail_counter {
if counter.max_reached() {
eprintln!("deqp-runner max-fails exceeded");
break;
}
}
}
if let Some((name, time)) = current_test.take() {
let status = reason.unwrap_or_else(|| self.default_test_status());
push_result(
&mut results,
name,
status,
time,
mem::take(&mut subtests),
fail_counter,
);
}
Ok(CaselistResult { results, stdout })
}
}