cargo_tarpaulin/statemachine/
mod.rs1use crate::config::{Config, TraceEngine};
2use crate::errors::RunError;
3use crate::event_log::*;
4use crate::traces::*;
5use crate::LineAnalysis;
6use crate::TestHandle;
7use std::collections::HashMap;
8use std::path::PathBuf;
9use std::time::Instant;
10use tracing::error;
11
12pub mod instrumented;
13cfg_if::cfg_if! {
14 if #[cfg(ptrace_supported)] {
15 pub mod linux;
16 pub use linux::ProcessInfo;
17 }
18}
19pub fn create_state_machine<'a>(
20 test: impl Into<TestHandle>,
21 traces: &'a mut TraceMap,
22 source_analysis: &'a HashMap<PathBuf, LineAnalysis>,
23 config: &'a Config,
24 event_log: &'a Option<EventLog>,
25) -> (TestState, Box<dyn StateData + 'a>) {
26 match config.engine() {
27 TraceEngine::Ptrace => {
28 cfg_if::cfg_if! {
29 if #[cfg(ptrace_supported)] {
30 let (state, machine) = linux::create_state_machine(test, traces, source_analysis, config, event_log);
31 (state, Box::new(machine))
32 } else {
33 error!("The ptrace backend is not supported on this system");
34 panic!()
35 }
36 }
37 }
38 TraceEngine::Llvm | TraceEngine::Auto => {
40 let (state, machine) = instrumented::create_state_machine(
41 test,
42 traces,
43 source_analysis,
44 config,
45 event_log,
46 );
47 (state, Box::new(machine))
48 }
49 }
50}
51
52#[derive(Debug, Copy, Clone, Eq, PartialEq)]
53pub enum TestState {
54 Start { start_time: Instant },
56 Initialise,
58 Waiting { start_time: Instant },
60 Stopped,
62 End(i32),
64}
65
66#[derive(Debug, Clone, Eq, PartialEq)]
70pub enum TracerAction<T> {
71 TryContinue(T),
74 Continue(T),
75 Step(T),
76 Detach(T),
77 Nothing,
78}
79
80impl<T> TracerAction<T> {
81 pub fn get_data(&self) -> Option<&T> {
82 match self {
83 TracerAction::Continue(d) => Some(d),
84 TracerAction::Step(d) => Some(d),
85 TracerAction::Detach(d) => Some(d),
86 TracerAction::TryContinue(d) => Some(d),
87 _ => None,
88 }
89 }
90}
91
92pub trait StateData {
97 fn start(&mut self) -> Result<Option<TestState>, RunError>;
100 fn init(&mut self) -> Result<TestState, RunError>;
102 fn wait(&mut self) -> Result<Option<TestState>, RunError>;
106 fn last_wait_attempt(&mut self) -> Result<Option<TestState>, RunError>;
110 fn stop(&mut self) -> Result<TestState, RunError>;
113}
114
115impl<'a> StateData for Box<dyn StateData + 'a> {
116 fn start(&mut self) -> Result<Option<TestState>, RunError> {
117 self.as_mut().start()
118 }
119
120 fn init(&mut self) -> Result<TestState, RunError> {
121 self.as_mut().init()
122 }
123
124 fn wait(&mut self) -> Result<Option<TestState>, RunError> {
125 self.as_mut().wait()
126 }
127
128 fn last_wait_attempt(&mut self) -> Result<Option<TestState>, RunError> {
129 self.as_mut().last_wait_attempt()
130 }
131
132 fn stop(&mut self) -> Result<TestState, RunError> {
133 self.as_mut().stop()
134 }
135}
136
137impl TestState {
138 pub fn is_finished(self) -> bool {
140 matches!(self, TestState::End(_))
141 }
142
143 fn start_state() -> TestState {
145 TestState::Start {
146 start_time: Instant::now(),
147 }
148 }
149
150 fn wait_state() -> TestState {
152 TestState::Waiting {
153 start_time: Instant::now(),
154 }
155 }
156
157 pub fn step<T: StateData>(self, data: &mut T, config: &Config) -> Result<TestState, RunError> {
159 match self {
160 TestState::Start { start_time } => {
161 if let Some(s) = data.start()? {
162 Ok(s)
163 } else if start_time.elapsed() >= config.test_timeout {
164 Err(RunError::TestRuntime(
165 "Error: Timed out when starting test".to_string(),
166 ))
167 } else {
168 Ok(TestState::Start { start_time })
169 }
170 }
171 TestState::Initialise => data.init(),
172 TestState::Waiting { start_time } => {
173 if let Some(s) = data.wait()? {
174 Ok(s)
175 } else if start_time.elapsed() >= config.test_timeout {
176 if let Some(s) = data.last_wait_attempt()? {
177 Ok(s)
178 } else {
179 Err(RunError::TestRuntime(
180 "Error: Timed out waiting for test response".to_string(),
181 ))
182 }
183 } else {
184 Ok(TestState::Waiting { start_time })
185 }
186 }
187 TestState::Stopped => data.stop(),
188 TestState::End(e) => Ok(TestState::End(e)),
189 }
190 }
191}
192
193#[cfg(test)]
194mod tests {
195 use super::*;
196 use std::time::Duration;
197
198 impl StateData for () {
199 fn start(&mut self) -> Result<Option<TestState>, RunError> {
200 Ok(None)
201 }
202
203 fn init(&mut self) -> Result<TestState, RunError> {
204 Err(RunError::StateMachine(
205 "No valid coverage collector".to_string(),
206 ))
207 }
208
209 fn wait(&mut self) -> Result<Option<TestState>, RunError> {
210 Ok(None)
211 }
212
213 fn last_wait_attempt(&mut self) -> Result<Option<TestState>, RunError> {
214 Err(RunError::StateMachine(
215 "No valid coverage collector".to_string(),
216 ))
217 }
218 fn stop(&mut self) -> Result<TestState, RunError> {
219 Err(RunError::StateMachine(
220 "No valid coverage collector".to_string(),
221 ))
222 }
223 }
224
225 #[test]
226 fn hits_timeouts() {
227 let mut config = Config::default();
228 config.test_timeout = Duration::from_secs(5);
229
230 let start_time = Instant::now() - Duration::from_secs(6);
231
232 let state = TestState::Start { start_time };
233
234 assert!(state.step(&mut (), &config).is_err());
235
236 let state = TestState::Waiting { start_time };
237
238 assert!(state.step(&mut (), &config).is_err());
239 }
240}