• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

xd009642 / tarpaulin / #467

01 May 2024 06:45PM UTC coverage: 74.573% (+0.3%) from 74.24%
#467

push

xd009642
Release 0.29.0

26 of 28 new or added lines in 6 files covered. (92.86%)

6 existing lines in 2 files now uncovered.

2619 of 3512 relevant lines covered (74.57%)

145566.32 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

74.48
/src/lib.rs
1
use crate::cargo::TestBinary;
2
use crate::config::*;
3
use crate::errors::*;
4
use crate::event_log::*;
5
use crate::path_utils::*;
6
use crate::process_handling::*;
7
use crate::report::report_coverage;
8
use crate::source_analysis::{LineAnalysis, SourceAnalysis};
9
use crate::test_loader::*;
10
use crate::traces::*;
11
use std::ffi::OsString;
12
use std::fs::{create_dir_all, remove_dir_all};
13
use tracing::{debug, error, info, warn};
14
use tracing_subscriber::{filter::LevelFilter, EnvFilter};
15

16
pub mod args;
17
pub mod branching;
18
pub mod cargo;
19
pub mod config;
20
pub mod errors;
21
pub mod event_log;
22
pub mod path_utils;
23
mod process_handling;
24
pub mod report;
25
pub mod source_analysis;
26
pub mod statemachine;
27
pub mod test_loader;
28
pub mod traces;
29

30
const RUST_LOG_ENV: &str = "RUST_LOG";
31

32
pub fn setup_logging(color: Color, debug: bool, verbose: bool) {
122✔
33
    //By default, we set tarpaulin to info,debug,trace while all dependencies stay at INFO
34
    let base_exceptions = |env: EnvFilter| {
244✔
35
        if debug {
122✔
36
            env.add_directive("cargo_tarpaulin=trace".parse().unwrap())
82✔
37
                .add_directive("llvm_profparser=trace".parse().unwrap())
82✔
38
        } else if verbose {
40✔
39
            env.add_directive("cargo_tarpaulin=debug".parse().unwrap())
×
NEW
40
                .add_directive("llvm_profparser=warn".parse().unwrap())
×
41
        } else {
42
            env.add_directive("cargo_tarpaulin=info".parse().unwrap())
40✔
43
                .add_directive("llvm_profparser=error".parse().unwrap())
40✔
44
        }
45
        .add_directive(LevelFilter::INFO.into())
122✔
46
    };
47

48
    //If RUST_LOG is set, then first apply our default directives (which are controlled by debug an verbose).
49
    // Then RUST_LOG will overwrite those default directives.
50
    // e.g. `RUST_LOG="trace" cargo-tarpaulin` will end up printing TRACE for everything
51
    // `cargo-tarpaulin -v` will print DEBUG for tarpaulin and INFO for everything else.
52
    // `RUST_LOG="error" cargo-tarpaulin -v` will print ERROR for everything.
53
    let filter = match std::env::var_os(RUST_LOG_ENV).map(OsString::into_string) {
244✔
54
        Some(Ok(env)) => {
×
55
            let mut filter = base_exceptions(EnvFilter::new(""));
×
56
            for s in env.split(',') {
×
57
                match s.parse() {
×
58
                    Ok(d) => filter = filter.add_directive(d),
×
59
                    Err(err) => println!("WARN ignoring log directive: `{s}`: {err}"),
×
60
                };
61
            }
62
            filter
×
63
        }
64
        _ => base_exceptions(EnvFilter::from_env(RUST_LOG_ENV)),
122✔
65
    };
66

67
    let with_ansi = color != Color::Never;
68

69
    let res = tracing_subscriber::FmtSubscriber::builder()
70
        .with_max_level(tracing::Level::ERROR)
71
        .with_env_filter(filter)
72
        .with_ansi(with_ansi)
73
        .try_init();
74

75
    if let Err(e) = res {
18✔
76
        eprintln!("Logging may be misconfigured: {e}");
77
    }
78

79
    debug!("set up logging");
204✔
80
}
81

82
pub fn trace(configs: &[Config]) -> Result<(TraceMap, i32), RunError> {
14✔
83
    let logger = create_logger(configs);
14✔
84
    let mut tracemap = TraceMap::new();
14✔
85
    let mut ret = 0;
14✔
86
    let mut fail_fast_ret = 0;
14✔
87
    let mut tarpaulin_result = Ok(());
14✔
88
    let mut bad_threshold = Ok(());
14✔
89

90
    for config in configs.iter() {
30✔
91
        if config.name == "report" {
16✔
92
            continue;
2✔
93
        }
94

95
        if let Some(log) = logger.as_ref() {
2✔
96
            let name = config_name(config);
97
            log.push_config(name);
98
        }
99

100
        create_target_dir(config);
14✔
101

102
        match launch_tarpaulin(config, &logger) {
14✔
103
            Ok((t, r)) => {
14✔
104
                if config.no_fail_fast {
14✔
105
                    fail_fast_ret |= r;
×
106
                } else {
107
                    ret |= r;
14✔
108
                }
109
                if configs.len() > 1 {
16✔
110
                    // Otherwise threshold is a global one and we'll let the caller handle it
111
                    bad_threshold = check_fail_threshold(&t, config);
2✔
112
                }
113
                tracemap.merge(&t);
14✔
114
            }
115
            Err(e) => {
×
116
                error!("{e}");
×
117
                tarpaulin_result = tarpaulin_result.and(Err(e));
×
118
            }
119
        }
120
    }
121

122
    tracemap.dedup();
14✔
123

124
    // It's OK that bad_threshold, tarpaulin_result may be overwritten in a loop
125
    if let Err(bad_limit) = bad_threshold {
14✔
126
        // Failure threshold probably more important than reporting failing
127
        let _ = report_coverage(&configs[0], &tracemap);
128
        Err(bad_limit)
129
    } else if ret == 0 {
14✔
130
        tarpaulin_result.map(|_| (tracemap, fail_fast_ret))
30✔
131
    } else {
132
        Err(RunError::TestFailed)
4✔
133
    }
134
}
135

136
fn create_logger(configs: &[Config]) -> Option<EventLog> {
14✔
137
    if configs.iter().any(|c| c.dump_traces) {
44✔
138
        let config = if let Some(c) = configs.iter().find(|c| c.output_directory.is_some()) {
10✔
139
            c
2✔
140
        } else {
141
            &configs[0]
×
142
        };
143

144
        Some(EventLog::new(
145
            configs.iter().map(|x| x.root()).collect(),
2✔
146
            config,
147
        ))
148
    } else {
149
        None
12✔
150
    }
151
}
152

153
fn create_target_dir(config: &Config) {
14✔
154
    let path = config.target_dir();
14✔
155
    if !path.exists() {
14✔
156
        if let Err(e) = create_dir_all(&path) {
6✔
157
            warn!("Failed to create target-dir {}", e);
×
158
        }
159
    }
160
}
161

162
fn config_name(config: &Config) -> String {
2✔
163
    if config.name.is_empty() {
2✔
164
        "<anonymous>".to_string()
2✔
165
    } else {
166
        config.name.clone()
×
167
    }
168
}
169

170
fn check_fail_threshold(traces: &TraceMap, config: &Config) -> Result<(), RunError> {
12✔
171
    let percent = traces.coverage_percentage() * 100.0;
12✔
172
    match config.fail_under.as_ref() {
12✔
173
        Some(limit) if percent < *limit => {
12✔
174
            let error = RunError::BelowThreshold(percent, *limit);
175
            error!("{}", error);
4✔
176
            Err(error)
4✔
177
        }
178
        _ => Ok(()),
8✔
179
    }
180
}
181

182
pub fn run(configs: &[Config]) -> Result<(), RunError> {
14✔
183
    if configs.iter().any(|x| x.engine() == TraceEngine::Llvm) {
44✔
184
        let profraw_dir = configs[0].profraw_dir();
2✔
185
        let _ = remove_dir_all(&profraw_dir);
2✔
186
        if let Err(e) = create_dir_all(&profraw_dir) {
2✔
187
            warn!(
188
                "Unable to create profraw directory in tarpaulin's target folder: {}",
189
                e
190
            );
191
        }
192
    }
193
    let (tracemap, ret) = collect_tracemap(configs)?;
28✔
194
    report_tracemap(configs, tracemap)?;
4✔
195
    if ret != 0 {
6✔
196
        // So we had a test fail in a way where we still want to report coverage so since we've now
197
        // done that we can return the test failed error.
198
        Err(RunError::TestFailed)
×
199
    } else {
200
        Ok(())
6✔
201
    }
202
}
203

204
fn collect_tracemap(configs: &[Config]) -> Result<(TraceMap, i32), RunError> {
14✔
205
    let (mut tracemap, ret) = trace(configs)?;
28✔
206
    if !configs.is_empty() {
207
        // Assumption: all configs are for the same project
208
        for dir in get_source_walker(&configs[0]) {
50✔
209
            tracemap.add_file(dir.path());
20✔
210
        }
211
    }
212

213
    Ok((tracemap, ret))
10✔
214
}
215

216
pub fn report_tracemap(configs: &[Config], tracemap: TraceMap) -> Result<(), RunError> {
10✔
217
    let mut reported = false;
10✔
218
    for c in configs.iter() {
22✔
219
        if c.no_run || c.name != "report" {
24✔
220
            continue;
10✔
221
        }
222

223
        report_coverage_with_check(c, &tracemap)?;
4✔
224
        reported = true;
×
225
    }
226

227
    if !reported && !configs.is_empty() && !configs[0].no_run {
24✔
228
        report_coverage_with_check(&configs[0], &tracemap)?;
10✔
229
    }
230

231
    Ok(())
6✔
232
}
233

234
fn report_coverage_with_check(c: &Config, tracemap: &TraceMap) -> Result<(), RunError> {
10✔
235
    report_coverage(c, tracemap)?;
10✔
236
    check_fail_threshold(tracemap, c)
10✔
237
}
238

239
/// Launches tarpaulin with the given configuration.
240
pub fn launch_tarpaulin(
164✔
241
    config: &Config,
242
    logger: &Option<EventLog>,
243
) -> Result<(TraceMap, i32), RunError> {
244
    if !config.name.is_empty() {
164✔
245
        info!("Running config {}", config.name);
20✔
246
    }
247

248
    info!("Running Tarpaulin");
300✔
249

250
    let mut result = TraceMap::new();
164✔
251
    let mut return_code = 0i32;
164✔
252
    info!("Building project");
300✔
253
    let executables = cargo::get_tests(config)?;
328✔
254
    if !config.no_run {
255
        let project_analysis = SourceAnalysis::get_analysis(config);
154✔
256
        let project_analysis = project_analysis.lines;
154✔
257
        let mut other_bins = config.objects().to_vec();
154✔
258
        other_bins.extend(executables.binaries.iter().cloned());
154✔
259
        for exe in &executables.test_binaries {
530✔
260
            if exe.should_panic() {
261
                info!("Running a test executable that is expected to panic");
12✔
262
            }
263
            let coverage =
188✔
264
                get_test_coverage(exe, &other_bins, &project_analysis, config, false, logger);
188✔
265

266
            let coverage = match coverage {
188✔
267
                Ok(coverage) => coverage,
188✔
268
                Err(run_error) => {
×
269
                    if config.no_fail_fast {
×
270
                        info!("No failing fast!");
×
271
                        return_code = 101;
×
272
                        None
×
273
                    } else {
274
                        return Err(run_error);
×
275
                    }
276
                }
277
            };
278
            if let Some(res) = coverage {
188✔
279
                result.merge(&res.0);
280
                return_code |= if exe.should_panic() {
281
                    (res.1 == 0).into()
6✔
282
                } else {
283
                    res.1
182✔
284
                };
285
            }
286
            if config.run_ignored {
188✔
287
                let coverage =
×
288
                    get_test_coverage(exe, &other_bins, &project_analysis, config, true, logger);
×
289
                let coverage = match coverage {
×
290
                    Ok(coverage) => coverage,
×
291
                    Err(run_error) => {
×
292
                        if config.no_fail_fast {
×
293
                            return_code = 101;
×
294
                            None
×
295
                        } else {
296
                            return Err(run_error);
×
297
                        }
298
                    }
299
                };
300
                if let Some(res) = coverage {
×
301
                    result.merge(&res.0);
×
302
                    return_code |= res.1;
×
303
                }
304
            }
305

306
            if config.fail_immediately && return_code != 0 {
188✔
307
                return Err(RunError::TestFailed);
×
308
            }
309
        }
310
        result.dedup();
154✔
311
    }
312
    Ok((result, return_code))
154✔
313
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc