use crate::config::{Config, TraceEngine};
use crate::errors::RunError;
use crate::event_log::*;
use crate::traces::*;
use crate::LineAnalysis;
use crate::TestHandle;
use std::collections::HashMap;
use std::path::PathBuf;
use std::time::Instant;
use tracing::error;
pub mod instrumented;
cfg_if::cfg_if! {
if #[cfg(ptrace_supported)] {
pub mod linux;
pub use linux::ProcessInfo;
}
}
pub fn create_state_machine<'a>(
test: impl Into<TestHandle>,
traces: &'a mut TraceMap,
source_analysis: &'a HashMap<PathBuf, LineAnalysis>,
config: &'a Config,
event_log: &'a Option<EventLog>,
) -> (TestState, Box<dyn StateData + 'a>) {
match config.engine() {
TraceEngine::Ptrace => {
cfg_if::cfg_if! {
if #[cfg(ptrace_supported)] {
let (state, machine) = linux::create_state_machine(test, traces, source_analysis, config, event_log);
(state, Box::new(machine))
} else {
error!("The ptrace backend is not supported on this system");
panic!()
}
}
}
TraceEngine::Llvm | TraceEngine::Auto => {
let (state, machine) = instrumented::create_state_machine(
test,
traces,
source_analysis,
config,
event_log,
);
(state, Box::new(machine))
}
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum TestState {
Start { start_time: Instant },
Initialise,
Waiting { start_time: Instant },
Stopped,
End(i32),
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum TracerAction<T> {
TryContinue(T),
Continue(T),
Step(T),
Detach(T),
Nothing,
}
impl<T> TracerAction<T> {
pub fn get_data(&self) -> Option<&T> {
match self {
TracerAction::Continue(d) => Some(d),
TracerAction::Step(d) => Some(d),
TracerAction::Detach(d) => Some(d),
TracerAction::TryContinue(d) => Some(d),
_ => None,
}
}
}
pub trait StateData {
fn start(&mut self) -> Result<Option<TestState>, RunError>;
fn init(&mut self) -> Result<TestState, RunError>;
fn wait(&mut self) -> Result<Option<TestState>, RunError>;
fn last_wait_attempt(&mut self) -> Result<Option<TestState>, RunError>;
fn stop(&mut self) -> Result<TestState, RunError>;
}
impl<'a> StateData for Box<dyn StateData + 'a> {
fn start(&mut self) -> Result<Option<TestState>, RunError> {
self.as_mut().start()
}
fn init(&mut self) -> Result<TestState, RunError> {
self.as_mut().init()
}
fn wait(&mut self) -> Result<Option<TestState>, RunError> {
self.as_mut().wait()
}
fn last_wait_attempt(&mut self) -> Result<Option<TestState>, RunError> {
self.as_mut().last_wait_attempt()
}
fn stop(&mut self) -> Result<TestState, RunError> {
self.as_mut().stop()
}
}
impl TestState {
pub fn is_finished(self) -> bool {
matches!(self, TestState::End(_))
}
fn start_state() -> TestState {
TestState::Start {
start_time: Instant::now(),
}
}
fn wait_state() -> TestState {
TestState::Waiting {
start_time: Instant::now(),
}
}
pub fn step<T: StateData>(self, data: &mut T, config: &Config) -> Result<TestState, RunError> {
match self {
TestState::Start { start_time } => {
if let Some(s) = data.start()? {
Ok(s)
} else if start_time.elapsed() >= config.test_timeout {
Err(RunError::TestRuntime(
"Error: Timed out when starting test".to_string(),
))
} else {
Ok(TestState::Start { start_time })
}
}
TestState::Initialise => data.init(),
TestState::Waiting { start_time } => {
if let Some(s) = data.wait()? {
Ok(s)
} else if start_time.elapsed() >= config.test_timeout {
if let Some(s) = data.last_wait_attempt()? {
Ok(s)
} else {
Err(RunError::TestRuntime(
"Error: Timed out waiting for test response".to_string(),
))
}
} else {
Ok(TestState::Waiting { start_time })
}
}
TestState::Stopped => data.stop(),
TestState::End(e) => Ok(TestState::End(e)),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
impl StateData for () {
fn start(&mut self) -> Result<Option<TestState>, RunError> {
Ok(None)
}
fn init(&mut self) -> Result<TestState, RunError> {
Err(RunError::StateMachine(
"No valid coverage collector".to_string(),
))
}
fn wait(&mut self) -> Result<Option<TestState>, RunError> {
Ok(None)
}
fn last_wait_attempt(&mut self) -> Result<Option<TestState>, RunError> {
Err(RunError::StateMachine(
"No valid coverage collector".to_string(),
))
}
fn stop(&mut self) -> Result<TestState, RunError> {
Err(RunError::StateMachine(
"No valid coverage collector".to_string(),
))
}
}
#[test]
fn hits_timeouts() {
let mut config = Config::default();
config.test_timeout = Duration::from_secs(5);
let start_time = Instant::now() - Duration::from_secs(6);
let state = TestState::Start { start_time };
assert!(state.step(&mut (), &config).is_err());
let state = TestState::Waiting { start_time };
assert!(state.step(&mut (), &config).is_err());
}
}