cargo_tarpaulin/statemachine/
instrumented.rs1#![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
39pub struct LlvmInstrumentedData<'a> {
41 process: Option<RunningProcessHandle>,
43 config: &'a Config,
45 event_log: &'a Option<EventLog>,
47 traces: &'a mut TraceMap,
49 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 Ok(Some(TestState::wait_state()))
66 }
67
68 fn init(&mut self) -> Result<TestState, RunError> {
69 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 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}