1use crate::cargo::TestBinary;
2use crate::config::*;
3use crate::errors::*;
4use crate::event_log::*;
5use crate::path_utils::*;
6use crate::process_handling::*;
7use crate::report::report_coverage;
8use crate::source_analysis::{LineAnalysis, SourceAnalysis};
9use crate::test_loader::*;
10use crate::traces::*;
11use std::ffi::OsString;
12use std::fs::{create_dir_all, remove_dir_all};
13use std::io;
14use tracing::{debug, error, info, warn};
15use tracing_subscriber::{filter::LevelFilter, EnvFilter};
16
17pub mod args;
18pub mod cargo;
19pub mod config;
20pub mod errors;
21pub mod event_log;
22pub mod path_utils;
23mod process_handling;
24pub mod report;
25pub mod source_analysis;
26pub mod statemachine;
27pub mod test_loader;
28pub mod traces;
29
30const RUST_LOG_ENV: &str = "RUST_LOG";
31
32#[cfg(not(tarpaulin_include))]
33pub fn setup_logging(color: Color, debug: bool, verbose: bool, stderr: bool) {
34 let base_exceptions = |env: EnvFilter| {
36 if debug {
37 env.add_directive("cargo_tarpaulin=trace".parse().unwrap())
38 .add_directive("llvm_profparser=trace".parse().unwrap())
39 } else if verbose {
40 env.add_directive("cargo_tarpaulin=debug".parse().unwrap())
41 .add_directive("llvm_profparser=warn".parse().unwrap())
42 } else {
43 env.add_directive("cargo_tarpaulin=info".parse().unwrap())
44 .add_directive("llvm_profparser=error".parse().unwrap())
45 }
46 .add_directive(LevelFilter::INFO.into())
47 };
48
49 let filter = match std::env::var_os(RUST_LOG_ENV).map(OsString::into_string) {
55 Some(Ok(env)) => {
56 let mut filter = base_exceptions(EnvFilter::new(""));
57 for s in env.split(',') {
58 match s.parse() {
59 Ok(d) => filter = filter.add_directive(d),
60 Err(err) => println!("WARN ignoring log directive: `{s}`: {err}"),
61 };
62 }
63 filter
64 }
65 _ => base_exceptions(EnvFilter::from_env(RUST_LOG_ENV)),
66 };
67
68 let with_ansi = color != Color::Never;
69
70 let builder = tracing_subscriber::FmtSubscriber::builder()
71 .with_max_level(tracing::Level::ERROR)
72 .with_env_filter(filter)
73 .with_ansi(with_ansi);
74
75 let res = if stderr {
76 builder.with_writer(io::stderr).try_init()
77 } else {
78 builder.try_init()
79 };
80
81 if let Err(e) = res {
82 eprintln!("Logging may be misconfigured: {e}");
83 }
84
85 debug!("set up logging");
86}
87
88pub fn trace(configs: &[Config]) -> Result<(TraceMap, i32), RunError> {
89 let logger = create_logger(configs);
90 let mut tracemap = TraceMap::new();
91 let mut ret = 0;
92 let mut fail_fast_ret = 0;
93 let mut tarpaulin_result = Ok(());
94 let mut bad_threshold = Ok(());
95
96 for config in configs.iter() {
97 if config.name == "report" {
98 continue;
99 }
100
101 if let Some(log) = logger.as_ref() {
102 let name = config_name(config);
103 log.push_config(name);
104 }
105
106 create_target_dir(config);
107
108 match launch_tarpaulin(config, &logger) {
109 Ok((t, r)) => {
110 if config.no_fail_fast {
111 fail_fast_ret |= r;
112 } else {
113 ret |= r;
114 }
115 if configs.len() > 1 {
116 bad_threshold = check_fail_threshold(&t, config);
118 }
119 tracemap.merge(&t);
120 }
121 Err(e) => {
122 error!("{e}");
123 tarpaulin_result = tarpaulin_result.and(Err(e));
124 }
125 }
126 }
127
128 tracemap.dedup();
129
130 if let Err(bad_limit) = bad_threshold {
132 let _ = report_coverage(&configs[0], &tracemap);
134 Err(bad_limit)
135 } else if ret == 0 {
136 tarpaulin_result.map(|_| (tracemap, fail_fast_ret))
137 } else {
138 Err(RunError::TestFailed)
139 }
140}
141
142fn create_logger(configs: &[Config]) -> Option<EventLog> {
143 if configs.iter().any(|c| c.dump_traces) {
144 let config = if let Some(c) = configs.iter().find(|c| c.output_directory.is_some()) {
145 c
146 } else {
147 &configs[0]
148 };
149
150 Some(EventLog::new(
151 configs.iter().map(|x| x.root()).collect(),
152 config,
153 ))
154 } else {
155 None
156 }
157}
158
159fn create_target_dir(config: &Config) {
160 let path = config.target_dir();
161 if !path.exists() {
162 if let Err(e) = create_dir_all(&path) {
163 warn!("Failed to create target-dir {}", e);
164 }
165 }
166}
167
168fn config_name(config: &Config) -> String {
169 if config.name.is_empty() {
170 "<anonymous>".to_string()
171 } else {
172 config.name.clone()
173 }
174}
175
176fn check_fail_threshold(traces: &TraceMap, config: &Config) -> Result<(), RunError> {
177 let percent = traces.coverage_percentage() * 100.0;
178 match config.fail_under.as_ref() {
179 Some(limit) if percent < *limit => {
180 let error = RunError::BelowThreshold(percent, *limit);
181 error!("{}", error);
182 Err(error)
183 }
184 _ => Ok(()),
185 }
186}
187
188pub fn run(configs: &[Config]) -> Result<(), RunError> {
189 if configs.iter().any(|x| x.engine() == TraceEngine::Llvm) {
190 let profraw_dir = configs[0].profraw_dir();
191 let _ = remove_dir_all(&profraw_dir);
192 if let Err(e) = create_dir_all(&profraw_dir) {
193 warn!(
194 "Unable to create profraw directory in tarpaulin's target folder: {}",
195 e
196 );
197 }
198 }
199 let (tracemap, ret) = collect_tracemap(configs)?;
200 report_tracemap(configs, tracemap)?;
201 if ret != 0 {
202 Err(RunError::TestFailed)
205 } else {
206 Ok(())
207 }
208}
209
210fn collect_tracemap(configs: &[Config]) -> Result<(TraceMap, i32), RunError> {
211 let (mut tracemap, ret) = trace(configs)?;
212 if !configs.is_empty() {
213 for dir in get_source_walker(&configs[0]) {
215 tracemap.add_file(dir.path());
216 }
217 }
218
219 Ok((tracemap, ret))
220}
221
222pub fn report_tracemap(configs: &[Config], tracemap: TraceMap) -> Result<(), RunError> {
223 let mut reported = false;
224 for c in configs.iter() {
225 if c.no_run || c.name != "report" {
226 continue;
227 }
228
229 report_coverage_with_check(c, &tracemap)?;
230 reported = true;
231 }
232
233 if !reported && !configs.is_empty() && !configs[0].no_run {
234 report_coverage_with_check(&configs[0], &tracemap)?;
235 }
236
237 Ok(())
238}
239
240fn report_coverage_with_check(c: &Config, tracemap: &TraceMap) -> Result<(), RunError> {
241 report_coverage(c, tracemap)?;
242 check_fail_threshold(tracemap, c)
243}
244
245pub fn launch_tarpaulin(
247 config: &Config,
248 logger: &Option<EventLog>,
249) -> Result<(TraceMap, i32), RunError> {
250 if !config.name.is_empty() {
251 info!("Running config {}", config.name);
252 }
253
254 info!("Running Tarpaulin");
255
256 let mut result = TraceMap::new();
257 let mut return_code = 0i32;
258 info!("Building project");
259 let executables = cargo::get_tests(config)?;
260 if !config.no_run {
261 let project_analysis = SourceAnalysis::get_analysis(config);
262 result.set_functions(project_analysis.create_function_map());
263 let project_analysis = project_analysis.lines;
264 let mut other_bins = config.objects().to_vec();
265 other_bins.extend(executables.binaries.iter().cloned());
266 for exe in &executables.test_binaries {
267 if exe.should_panic() {
268 info!("Running a test executable that is expected to panic");
269 }
270 let coverage =
271 get_test_coverage(exe, &other_bins, &project_analysis, config, false, logger);
272
273 let coverage = match coverage {
274 Ok(coverage) => coverage,
275 Err(run_error) => {
276 if config.no_fail_fast {
277 info!("No failing fast!");
278 return_code = 101;
279 None
280 } else {
281 return Err(run_error);
282 }
283 }
284 };
285 if let Some(res) = coverage {
286 result.merge(&res.0);
287 return_code |= if exe.should_panic() {
288 (res.1 == 0).into()
289 } else {
290 res.1
291 };
292 }
293 if config.run_ignored {
294 let coverage =
295 get_test_coverage(exe, &other_bins, &project_analysis, config, true, logger);
296 let coverage = match coverage {
297 Ok(coverage) => coverage,
298 Err(run_error) => {
299 if config.no_fail_fast {
300 return_code = 101;
301 None
302 } else {
303 return Err(run_error);
304 }
305 }
306 };
307 if let Some(res) = coverage {
308 result.merge(&res.0);
309 return_code |= res.1;
310 }
311 }
312
313 if config.fail_immediately && return_code != 0 {
314 return Err(RunError::TestFailed);
315 }
316 }
317 result.dedup();
318 }
319 Ok((result, return_code))
320}