• 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

68.42
/src/report/lcov.rs
1
use crate::config::Config;
2
use crate::errors::RunError;
3
use crate::traces::{CoverageStat, TraceMap};
4
use std::fs::File;
5
use std::io::Write;
6

7
pub fn export(coverage_data: &TraceMap, config: &Config) -> Result<(), RunError> {
2✔
8
    let file_path = config.output_dir().join("lcov.info");
2✔
9
    let file = match File::create(file_path) {
4✔
10
        Ok(k) => k,
11
        Err(e) => return Err(RunError::Lcov(format!("File is not writeable: {e}"))),
×
12
    };
13

14
    write_lcov(file, coverage_data)
15
}
16

17
fn write_lcov(mut file: impl Write, coverage_data: &TraceMap) -> Result<(), RunError> {
3✔
18
    for (path, traces) in coverage_data.iter() {
9✔
19
        if traces.is_empty() {
6✔
20
            continue;
×
21
        }
22
        writeln!(file, "TN:")?;
6✔
23
        writeln!(file, "SF:{}", path.to_str().unwrap())?;
6✔
24

25
        let mut fns: Vec<String> = vec![];
6✔
26
        let mut fnda: Vec<String> = vec![];
6✔
27
        let mut da: Vec<(u64, u64)> = vec![];
6✔
28

29
        for trace in traces {
28✔
UNCOV
30
            if trace.fn_name.is_some() {
×
31
                let fn_name = trace.fn_name.clone().unwrap();
1✔
32
                let fn_hits = match trace.stats {
2✔
UNCOV
33
                    CoverageStat::Line(hits) => hits,
×
UNCOV
34
                    _ => {
×
35
                        return Err(RunError::Lcov(
×
36
                            "Function doesn't have hits number".to_string(),
×
37
                        ))
38
                    }
39
                };
40

UNCOV
41
                fns.push(format!("FN:{},{}", trace.line, fn_name));
×
UNCOV
42
                fnda.push(format!("FNDA:{fn_hits},{fn_name}"));
×
43
            }
44

45
            if let CoverageStat::Line(hits) = trace.stats {
33✔
46
                da.push((trace.line, hits));
11✔
47
            }
48
        }
49

50
        for fn_line in &fns {
8✔
51
            writeln!(file, "{fn_line}",)?;
×
52
        }
53

54
        writeln!(file, "FNF:{}", fns.len())?;
6✔
55

56
        for fnda_line in fnda {
8✔
57
            writeln!(file, "{fnda_line}")?;
×
58
        }
59

60
        for (line, hits) in &da {
28✔
61
            writeln!(file, "DA:{line},{hits}")?;
×
62
        }
63

64
        writeln!(file, "LF:{}", da.len())?;
6✔
65
        writeln!(
6✔
66
            file,
6✔
67
            "LH:{}",
68
            da.iter().filter(|(_, hits)| *hits != 0).count()
17✔
69
        )?;
70

71
        // TODO: add support for branching
72
        // BRDA (BRDA:<line number>,<block number>,<branch number>,<hits>)
73
        // BRF (branches found)
74
        // BRH (branches hit)
75
        // More at http://ltp.sourceforge.net/coverage/lcov/geninfo.1.php
76

77
        writeln!(file, "end_of_record")?;
6✔
78
    }
79
    Ok(())
3✔
80
}
81

82
#[cfg(test)]
83
mod tests {
84
    use super::*;
85
    use crate::traces::*;
86
    use lcov::{record::Record, Reader};
87
    use std::io::Cursor;
88
    use std::path::{Path, PathBuf};
89

90
    #[test]
91
    fn generate_valid_lcov() {
92
        let mut traces = TraceMap::new();
93
        traces.add_trace(
94
            Path::new("foo.rs"),
95
            Trace {
96
                line: 4,
97
                stats: CoverageStat::Line(1),
98
                address: Default::default(),
99
                length: 0,
100
                fn_name: None,
101
            },
102
        );
103
        traces.add_trace(
104
            Path::new("foo.rs"),
105
            Trace {
106
                line: 5,
107
                stats: CoverageStat::Line(0),
108
                address: Default::default(),
109
                length: 0,
110
                fn_name: None,
111
            },
112
        );
113

114
        traces.add_trace(
115
            Path::new("bar.rs"),
116
            Trace {
117
                line: 14,
118
                stats: CoverageStat::Line(9),
119
                address: Default::default(),
120
                length: 0,
121
                fn_name: Some("baz".to_string()),
122
            },
123
        );
124

125
        let mut data = vec![];
126
        let cursor = Cursor::new(&mut data);
127

128
        write_lcov(cursor, &traces).unwrap();
129

130
        let reader = Reader::new(data.as_slice());
131
        let mut items = 0;
132
        let mut files_seen = 0;
133

134
        let mut current_source = PathBuf::new();
135
        for item in reader {
136
            let record = item.unwrap();
137

138
            match record {
139
                Record::SourceFile { path } => {
140
                    current_source = path.clone();
141
                    // We know files are presented sorted
142
                    if files_seen == 0 {
143
                        assert_eq!(path, Path::new("bar.rs"));
144
                    } else if files_seen == 1 {
145
                        assert_eq!(path, Path::new("foo.rs"));
146
                    } else {
147
                        panic!("Too many files");
148
                    }
149

150
                    files_seen += 1;
151
                }
152
                Record::EndOfRecord => {
153
                    current_source = PathBuf::new();
154
                }
155
                Record::FunctionName { name, start_line } => {
156
                    assert_eq!(name, "baz");
157
                    assert_eq!(start_line, 14);
158
                }
159
                Record::LineData {
160
                    line,
161
                    count,
162
                    checksum: _,
163
                } => {
164
                    if current_source == Path::new("bar.rs") {
165
                        assert_eq!(line, 14);
166
                        assert_eq!(count, 9);
167
                    } else if current_source == Path::new("foo.rs") {
168
                        assert!((line == 4 && count == 1) || (line == 5 && count == 0));
169
                    } else {
170
                        panic!("Line data not attached to file");
171
                    }
172
                }
173
                Record::LinesFound { found } => {
174
                    if current_source == Path::new("bar.rs") {
175
                        assert_eq!(found, 1);
176
                    } else if current_source == Path::new("foo.rs") {
177
                        assert_eq!(found, 2);
178
                    } else {
179
                        panic!("Lines found not attached to file");
180
                    }
181
                }
182
                Record::LinesHit { hit } => {
183
                    if current_source == Path::new("bar.rs") {
184
                        assert_eq!(hit, 1);
185
                    } else if current_source == Path::new("foo.rs") {
186
                        assert_eq!(hit, 1);
187
                    } else {
188
                        panic!("Lines found not attached to file");
189
                    }
190
                }
191
                _ => {}
192
            }
193

194
            items += 1;
195
        }
196
        assert!(items > 0);
197
    }
198
}
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