cargo_tarpaulin/statemachine/
instrumented.rs

1#![allow(dead_code)]
2use crate::path_utils::{get_profile_walker, get_source_walker};
3use crate::process_handling::RunningProcessHandle;
4use crate::statemachine::*;
5use llvm_profparser::*;
6use std::thread::sleep;
7use tracing::{info, warn};
8
9pub fn create_state_machine<'a>(
10    test: impl Into<TestHandle>,
11    traces: &'a mut TraceMap,
12    analysis: &'a HashMap<PathBuf, LineAnalysis>,
13    config: &'a Config,
14    event_log: &'a Option<EventLog>,
15) -> (TestState, LlvmInstrumentedData<'a>) {
16    let handle = test.into();
17    if let TestHandle::Process(process) = handle {
18        let llvm = LlvmInstrumentedData {
19            process: Some(process),
20            event_log,
21            config,
22            traces,
23            analysis,
24        };
25        (TestState::start_state(), llvm)
26    } else {
27        error!("The llvm cov statemachine requires a process::Child");
28        let invalid = LlvmInstrumentedData {
29            process: None,
30            config,
31            event_log,
32            traces,
33            analysis,
34        };
35        (TestState::End(1), invalid)
36    }
37}
38
39/// Handle to the process for an instrumented binary. This will simply
40pub struct LlvmInstrumentedData<'a> {
41    /// Parent pid of the test
42    process: Option<RunningProcessHandle>,
43    /// Program config
44    config: &'a Config,
45    /// Optional event log to update as the test progresses
46    event_log: &'a Option<EventLog>,
47    /// Instrumentation points in code with associated coverage data
48    traces: &'a mut TraceMap,
49    /// Source analysis, needed in case we need to follow any executables
50    analysis: &'a HashMap<PathBuf, LineAnalysis>,
51}
52
53impl<'a> LlvmInstrumentedData<'a> {
54    fn should_panic(&self) -> bool {
55        match &self.process {
56            Some(hnd) => hnd.should_panic,
57            None => false,
58        }
59    }
60}
61
62impl<'a> StateData for LlvmInstrumentedData<'a> {
63    fn start(&mut self) -> Result<Option<TestState>, RunError> {
64        // Nothing needs to be done at startup as this runs like a normal process
65        Ok(Some(TestState::wait_state()))
66    }
67
68    fn init(&mut self) -> Result<TestState, RunError> {
69        // Nothing needs to be done at init as this runs like a normal process
70        unreachable!();
71    }
72
73    fn last_wait_attempt(&mut self) -> Result<Option<TestState>, RunError> {
74        unreachable!();
75    }
76
77    fn wait(&mut self) -> Result<Option<TestState>, RunError> {
78        let should_panic = self.should_panic();
79        if let Some(parent) = self.process.as_mut() {
80            match parent.child.wait() {
81                Ok(exit) => {
82                    if !exit.success() && !should_panic {
83                        return Err(RunError::TestFailed);
84                    }
85                    if let Some(delay) = self.config.post_test_delay {
86                        sleep(delay);
87                    }
88                    let profraws = get_profile_walker(self.config)
89                        .map(|x| x.path().to_path_buf())
90                        .filter(|x| !parent.existing_profraws.contains(x))
91                        .collect::<Vec<_>>();
92
93                    info!(
94                        "For binary: {}",
95                        self.config.strip_base_dir(&parent.path).display()
96                    );
97                    for prof in &profraws {
98                        let profraw_name = self.config.strip_base_dir(prof);
99                        info!("Generated: {}", profraw_name.display());
100                    }
101
102                    let binary_path = parent.path.clone();
103                    info!("Merging coverage reports");
104                    let instrumentation = merge_profiles(&profraws)?;
105                    if instrumentation.is_empty() {
106                        warn!("profraw file has no records after merging. If this is unexpected it may be caused by a panic or signal used in a test that prevented the LLVM instrumentation runtime from serialising results");
107                        self.process = None;
108                        let code = exit.code().unwrap_or(1);
109                        return Ok(Some(TestState::End(code)));
110                    }
111
112                    let mut binaries = parent
113                        .extra_binaries
114                        .iter()
115                        .filter(|path| {
116                            // extra binaries might not exist yet and be created
117                            // later by the test suite
118                            if path.exists() {
119                                true
120                            } else {
121                                info!(
122                                    "Skipping additional object '{}' since the file does not exist",
123                                    path.display()
124                                );
125                                false
126                            }
127                        })
128                        .cloned()
129                        .collect::<Vec<_>>();
130
131                    binaries.push(binary_path);
132                    info!("Mapping coverage data to source");
133                    let mapping =
134                        CoverageMapping::new(&binaries, &instrumentation, true).map_err(|e| {
135                            error!("Failed to get coverage: {}", e);
136                            RunError::TestCoverage(e.to_string())
137                        })?;
138
139                    let root = self.config.root();
140                    let report = mapping.generate_subreport(|paths| {
141                        paths.iter().any(|path| path.starts_with(&root))
142                    });
143
144                    if self.traces.is_empty() {
145                        for source_file in get_source_walker(self.config) {
146                            let file = source_file.path();
147                            let analysis = self.analysis.get(file);
148                            if let Some(result) = report.files.get(file) {
149                                for (loc, hits) in result.hits.iter() {
150                                    for line in loc.line_start..(loc.line_end + 1) {
151                                        let include = match analysis.as_ref() {
152                                            Some(analysis) => !analysis.should_ignore(line),
153                                            None => true,
154                                        };
155                                        if include {
156                                            let mut trace = Trace::new_stub(line as u64);
157                                            trace.stats = CoverageStat::Line(*hits as u64);
158                                            self.traces.add_trace(file, trace);
159                                        }
160                                    }
161                                }
162                            }
163                            if let Some(analysis) = analysis {
164                                for line in analysis.cover.iter() {
165                                    if !self.traces.contains_location(file, *line as u64) {
166                                        let mut trace = Trace::new_stub(*line as u64);
167                                        trace.stats = CoverageStat::Line(0);
168                                        self.traces.add_trace(file, trace);
169                                    }
170                                }
171                            }
172                        }
173                    } else {
174                        self.traces.dedup();
175
176                        for (file, result) in report.files.iter() {
177                            if let Some(traces) = self.traces.file_traces_mut(file) {
178                                for trace in traces.iter_mut() {
179                                    if let Some(hits) = result.hits_for_line(trace.line as usize) {
180                                        if let CoverageStat::Line(ref mut x) = trace.stats {
181                                            *x = hits as _;
182                                        }
183                                    }
184                                }
185                            } else {
186                                warn!(
187                                    "Couldn't find {} in {:?}",
188                                    file.display(),
189                                    self.traces.files()
190                                );
191                            }
192                        }
193                    }
194
195                    self.process = None;
196                    let code = exit.code().unwrap_or(1);
197                    Ok(Some(TestState::End(code)))
198                }
199                Err(e) => Err(e.into()),
200            }
201        } else {
202            Err(RunError::TestCoverage("Test was not launched".to_string()))
203        }
204    }
205
206    fn stop(&mut self) -> Result<TestState, RunError> {
207        unreachable!();
208    }
209}