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

xd009642 / tarpaulin / #799

29 Jun 2026 09:37AM UTC coverage: 88.955% (+2.9%) from 86.061%
#799

push

xd009642
Release 0.36.0

459 of 500 new or added lines in 22 files covered. (91.8%)

6 existing lines in 2 files now uncovered.

6975 of 7841 relevant lines covered (88.96%)

203233.13 hits per line

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

92.31
/src/source_analysis/attributes.rs
1
use crate::source_analysis::prelude::*;
2
use std::collections::HashSet;
3
use std::path::{Path, PathBuf};
4
use syn::punctuated::Punctuated;
5
use syn::*;
6

7
pub mod predicates {
8
    pub fn is_test_attribute(id: &syn::Path) -> bool {
512✔
9
        id.segments
1,024✔
10
            .last()
512✔
11
            .unwrap()
512✔
12
            .ident
512✔
13
            .to_string()
14
            .ends_with("test")
15
    }
16
}
17

18
impl SourceAnalysis {
19
    pub(crate) fn check_attr_list(&mut self, attrs: &[Attribute], ctx: &Context) -> bool {
1,929✔
20
        let analysis = self.get_line_analysis(ctx.file.to_path_buf());
9,645✔
21
        let mut check_cover = true;
3,858✔
22
        for attr in attrs {
2,211✔
23
            analysis.ignore_tokens(attr);
846✔
24
            if check_cfg_attr(&attr.meta) {
579✔
25
                check_cover = false;
15✔
26
            } else if attr.meta.path().is_ident("cfg") {
816✔
27
                if eval_cfg_attr(&attr.meta, ctx) == Some(false) {
391✔
28
                    check_cover = false;
53✔
29
                }
30
                let mut skip = false;
338✔
31
                let _ = attr.parse_nested_meta(|meta| {
507✔
32
                    skip |=
33
                        predicates::is_test_attribute(&meta.path) && !ctx.config.include_tests();
453✔
34
                    Ok(())
169✔
35
                });
36
                if skip {
191✔
37
                    check_cover = false;
22✔
38
                }
39
            }
40
            if !check_cover {
282✔
41
                break;
68✔
42
            }
43
        }
44
        check_cover
1,929✔
45
    }
46
}
47

48
fn eval_cfg_attr(attr: &Meta, ctx: &Context<'_>) -> Option<bool> {
169✔
49
    let Meta::List(list) = attr else {
338✔
NEW
50
        return None;
×
51
    };
52
    let predicates = list
338✔
53
        .parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)
169✔
54
        .ok()?;
55
    match predicates.len() {
169✔
NEW
56
        0 => None,
×
57
        1 => predicates.first().and_then(|meta| eval_cfg(meta, ctx)),
1,014✔
NEW
58
        _ => eval_all(predicates.iter(), ctx),
×
59
    }
60
}
61

62
fn eval_cfg(meta: &Meta, ctx: &Context<'_>) -> Option<bool> {
200✔
63
    if meta.path().is_ident("all") {
600✔
64
        let Meta::List(list) = meta else {
14✔
NEW
65
            return None;
×
66
        };
67
        let predicates = list
14✔
68
            .parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)
7✔
69
            .ok()?;
70
        eval_all(predicates.iter(), ctx)
28✔
71
    } else if meta.path().is_ident("any") {
579✔
72
        let Meta::List(list) = meta else {
14✔
NEW
73
            return None;
×
74
        };
75
        let predicates = list
14✔
76
            .parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)
7✔
77
            .ok()?;
78
        eval_any(predicates.iter(), ctx)
28✔
79
    } else if meta.path().is_ident("not") {
558✔
80
        let Meta::List(list) = meta else {
14✔
NEW
81
            return None;
×
82
        };
83
        let predicates = list
14✔
84
            .parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)
7✔
85
            .ok()?;
86
        if predicates.len() != 1 {
7✔
NEW
87
            return None;
×
88
        }
89
        predicates
7✔
90
            .first()
91
            .and_then(|predicate| eval_cfg(predicate, ctx))
28✔
92
            .map(|result| !result)
14✔
93
    } else if meta.path().is_ident("feature") {
537✔
94
        let Meta::NameValue(name_value) = meta else {
122✔
NEW
95
            return None;
×
96
        };
97
        let Expr::Lit(ExprLit {
98
            lit: Lit::Str(feature),
61✔
99
            ..
100
        }) = &name_value.value
61✔
101
        else {
NEW
102
            return None;
×
103
        };
104
        Some(active_features(ctx).contains(&feature.value()))
183✔
105
    } else if meta.path().is_ident("test") {
354✔
106
        Some(ctx.config.include_tests())
115✔
107
    } else if meta.path().is_ident("tarpaulin") {
9✔
NEW
108
        Some(!ctx.config.avoid_cfg_tarpaulin)
×
109
    } else {
110
        None
3✔
111
    }
112
}
113

114
fn eval_all<'a>(predicates: impl Iterator<Item = &'a Meta>, ctx: &Context<'_>) -> Option<bool> {
7✔
115
    let mut result = Some(true);
14✔
116
    for predicate in predicates {
17✔
117
        match eval_cfg(predicate, ctx) {
20✔
118
            Some(false) => return Some(false),
4✔
119
            Some(true) => {}
3✔
120
            None => result = None,
3✔
121
        }
122
    }
123
    result
3✔
124
}
125

126
fn eval_any<'a>(predicates: impl Iterator<Item = &'a Meta>, ctx: &Context<'_>) -> Option<bool> {
7✔
127
    let mut result = Some(false);
14✔
128
    for predicate in predicates {
21✔
129
        match eval_cfg(predicate, ctx) {
28✔
130
            Some(true) => return Some(true),
3✔
131
            Some(false) => {}
11✔
NEW
132
            None => result = None,
×
133
        }
134
    }
135
    result
4✔
136
}
137

138
fn active_features(ctx: &Context<'_>) -> HashSet<String> {
61✔
139
    let mut active = HashSet::new();
122✔
140
    let metadata = ctx.config.get_metadata();
183✔
141
    let package = metadata
122✔
142
        .as_ref()
143
        .and_then(|metadata| package_for_file(ctx.file, metadata.packages.iter()));
244✔
144

145
    let Some(package) = package else {
122✔
NEW
146
        return active;
×
147
    };
148

149
    if ctx.config.all_features {
61✔
150
        active.extend(package.features.keys().cloned());
35✔
151
        return active;
7✔
152
    }
153

154
    if !ctx.config.no_default_features {
80✔
155
        add_feature("default", &package.features, &mut active);
78✔
156
    }
157

158
    if let Some(features) = ctx.config.features.as_ref() {
76✔
159
        for feature in features.split_ascii_whitespace() {
44✔
160
            let feature = feature
44✔
161
                .rsplit_once('/')
162
                .map_or(feature, |(_, feature)| feature);
44✔
163
            add_feature(feature, &package.features, &mut active);
88✔
164
        }
165
    }
166

167
    active
54✔
168
}
169

170
fn package_for_file<'a>(
61✔
171
    file: &Path,
172
    packages: impl Iterator<Item = &'a cargo_metadata::Package>,
173
) -> Option<&'a cargo_metadata::Package> {
174
    packages
61✔
175
        .filter_map(|package| {
122✔
176
            let manifest = PathBuf::from(package.manifest_path.as_std_path());
183✔
177
            let package_root = manifest.parent()?;
122✔
178
            file.starts_with(package_root)
183✔
179
                .then_some((package, package_root.components().count()))
244✔
180
        })
181
        .max_by_key(|(_, package_root_len)| *package_root_len)
61✔
182
        .map(|(package, _)| package)
61✔
183
}
184

185
fn add_feature(
110✔
186
    feature: &str,
187
    features: &std::collections::BTreeMap<String, Vec<String>>,
188
    active: &mut HashSet<String>,
189
) {
190
    if !features.contains_key(feature) || !active.insert(feature.to_string()) {
550✔
NEW
191
        return;
×
192
    }
193

194
    for dependency in &features[feature] {
172✔
195
        let dependency_feature = dependency
186✔
196
            .strip_prefix("dep:")
197
            .unwrap_or(dependency)
62✔
198
            .split_once('?')
199
            .map_or(dependency.as_str(), |(_, feature)| feature)
186✔
200
            .split_once('/')
201
            .map_or(dependency.as_str(), |(_, feature)| feature);
186✔
202

203
        if features.contains_key(dependency_feature) {
248✔
204
            add_feature(dependency_feature, features, active);
186✔
205
        }
206
    }
207
}
208

209
pub(crate) fn check_cfg_attr(attr: &Meta) -> bool {
573✔
210
    tracing::trace!("cfg attr: {}", attr.to_token_stream());
573✔
211
    let mut ignore_span = false;
1,146✔
212
    let id = attr.path();
1,719✔
213

214
    // no coverage is now deprecated in the compiler, so in future we can remove this just to
215
    // minimise some of this code
216
    if id.is_ident("no_coverage") {
1,720✔
217
        ignore_span = true;
1✔
218
    } else if id.is_ident("coverage") {
1,717✔
219
        if let Meta::List(ml) = attr {
2✔
220
            let _ = ml.parse_nested_meta(|nested| {
3✔
221
                ignore_span |= nested.path.is_ident("off");
2✔
222
                Ok(())
1✔
223
            });
224
        }
225
    } else if id.is_ident("cfg") {
1,713✔
226
        if let Meta::List(ml) = attr {
446✔
227
            let _ = ml.parse_nested_meta(|nested| {
669✔
228
                if nested.path.is_ident("not") {
669✔
229
                    nested.parse_nested_meta(|meta| {
72✔
230
                        ignore_span |= meta.path.is_ident("tarpaulin_include")
72✔
231
                            || meta.path.is_ident("tarpaulin");
30✔
232
                        Ok(())
24✔
233
                    })
234
                } else {
235
                    Ok(())
199✔
236
                }
237
            });
238
        }
239
    } else if id.is_ident("cfg_attr") {
1,044✔
240
        if let Meta::List(ml) = attr {
10✔
241
            let mut first = true;
10✔
242
            let mut is_tarpaulin = false;
10✔
243
            let _ = ml.parse_nested_meta(|nested| {
20✔
244
                if first && nested.path.is_ident("tarpaulin") {
30✔
245
                    first = false;
5✔
246
                    is_tarpaulin = true;
5✔
247
                } else if !first && is_tarpaulin {
15✔
248
                    if nested.path.is_ident("no_coverage") {
16✔
249
                        ignore_span = true;
1✔
250
                    } else if nested.path.is_ident("coverage") {
13✔
251
                        let _ = nested.parse_nested_meta(|nested| {
3✔
252
                            ignore_span |= nested.path.is_ident("off");
2✔
253
                            Ok(())
1✔
254
                        });
255
                    }
256
                }
257
                Ok(())
10✔
258
            });
259
        }
260
    } else if predicates::is_test_attribute(id) {
688✔
261
        ignore_span = true;
2✔
262
    } else {
263
        let skip_attrs = &["tarpaulin", "skip"];
682✔
264
        let mut n = 0;
682✔
265
        ignore_span = true;
341✔
266
        for (segment, attr) in id.segments.iter().zip(skip_attrs.iter()) {
2,401✔
267
            n += 1;
348✔
268
            if segment.ident != attr {
682✔
269
                ignore_span = false;
334✔
270
            }
271
        }
272
        if n < skip_attrs.len() {
1,016✔
273
            ignore_span = false;
334✔
274
        }
275
    }
276
    ignore_span
573✔
277
}
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

© 2026 Coveralls, Inc