lcov_parser/report/
line.rs

1// Copyright (c) 2015-2016 lcov-parser developers
2//
3// Licensed under the Apache License, Version 2.0
4// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0> or the MIT
5// license <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6// option. All files in the project carrying such notice may not be copied,
7// modified, or distributed except according to those terms.
8
9use std::io;
10use std::cmp::PartialEq;
11use std::collections::btree_map:: { BTreeMap };
12use std::convert::{ AsRef, From };
13use std::fmt:: { Display, Formatter, Result };
14use record:: { LineData, RecordWrite };
15use report::summary:: { Summary };
16use report::attribute:: { LineNumber, CheckSum, ExecutionCount };
17use report::counter:: { Hit, HitFoundCounter, FoundCounter, HitCounter };
18use merger::ops:: { TryMerge, MergeResult, MergeLine, ChecksumError };
19
20#[derive(Debug, Eq, Clone)]
21pub struct Line {
22    line_number: LineNumber,
23    execution_count: ExecutionCount,
24    checksum: Option<CheckSum>
25}
26
27impl Line {
28    pub fn new(
29        line_number: LineNumber,
30        execution_count: ExecutionCount,
31        checksum: Option<CheckSum>
32    ) -> Self {
33        Line {
34            line_number: line_number,
35            execution_count: execution_count,
36            checksum: checksum
37        }
38    }
39    pub fn line_number(&self) -> &LineNumber {
40        &self.line_number
41    }
42    pub fn execution_count(&self) -> &ExecutionCount {
43        &self.execution_count
44    }
45    pub fn checksum(&self) -> Option<&CheckSum> {
46        match self.checksum {
47            Some(ref v) => Some(v),
48            None => None
49        }
50    }
51    pub fn has_checkshum(&self) -> bool {
52        self.checksum.is_some()
53    }
54    pub fn is_hit(&self) -> bool {
55        self.execution_count.is_hit()
56    }
57}
58
59impl<'a> From<&'a LineData> for Line {
60    fn from(line_data: &'a LineData) -> Self {
61        Line::new(
62            line_data.line,
63            line_data.count,
64            line_data.checksum.clone()
65        )
66    }
67}
68
69impl PartialEq for Line {
70    fn eq(&self, other: &Self) -> bool {
71        let has_checkshum = self.has_checkshum() && other.has_checkshum();
72        if has_checkshum {
73            return self.checksum.as_ref() == other.checksum();
74        }
75        return &self.line_number == other.line_number();
76    }
77}
78
79impl<'a> TryMerge<&'a Line> for Line {
80    type Err = ChecksumError;
81
82    fn try_merge(&mut self, other: &'a Line) -> MergeResult<Self::Err> {
83        if let Some(o) = other.checksum() {
84            if let Some(ref s) = self.checksum {
85                if s != o {
86                    return Err(ChecksumError::Mismatch(
87                        MergeLine::from(&self.clone()),
88                        MergeLine::from(other)
89                    ));
90                }
91            }
92            self.checksum = Some(o.clone());
93        }
94        self.execution_count += *other.execution_count();
95        Ok(())
96    }
97}
98
99impl<'a> TryMerge<&'a LineData> for Line {
100    type Err = ChecksumError;
101
102    fn try_merge(&mut self, other: &'a LineData) -> MergeResult<Self::Err> {
103        self.try_merge(&Line::from(other))
104    }
105}
106
107
108#[derive(Debug, Clone)]
109pub struct Lines {
110    lines: BTreeMap<LineNumber, Line>
111}
112
113impl Lines {
114    pub fn new() -> Self {
115        Lines {
116            lines: BTreeMap::new()
117        }
118    }
119}
120
121impl AsRef<BTreeMap<LineNumber, Line>> for Lines {
122    fn as_ref(&self) -> &BTreeMap<LineNumber, Line> {
123        &self.lines
124    }
125}
126
127impl_summary!(Lines, lines<LineNumber, Line>);
128
129
130impl HitCounter for Lines {
131    fn hit_count(&self) -> usize {
132        self.iter()
133            .filter(|&(_, line)| line.is_hit() )
134            .count()
135    }
136}
137
138impl FoundCounter for Lines {
139    fn found_count(&self) -> usize {
140        self.lines.len()
141    }
142}
143
144impl HitFoundCounter for Lines {
145}
146
147
148
149impl RecordWrite for Lines {
150    fn write_records<T: io::Write>(&self, output: &mut T) -> io::Result<()> {
151        write!(output, "{}", self)
152    }
153}
154
155impl Display for Lines {
156    fn fmt(&self, f: &mut Formatter) -> Result {
157        if self.is_empty() {
158            return Ok(());
159        }
160        for (_, line) in self.iter() {
161            match line.checksum() {
162                Some(ref checksum) => writeln!(f, "DA:{},{},{}", line.line_number(), line.execution_count(), checksum)?,
163                None => writeln!(f, "DA:{},{}", line.line_number(), line.execution_count())?
164            }
165        }
166        writeln!(f, "LF:{}", self.found_count())?;
167        writeln!(f, "LH:{}", self.hit_count())?;
168        Ok(())
169    }
170}
171
172
173impl<'a> TryMerge<&'a LineData> for Lines {
174    type Err = ChecksumError;
175
176    fn try_merge(&mut self, line_data: &'a LineData) -> MergeResult<Self::Err> {
177        if !self.lines.contains_key(&line_data.line) {
178            self.lines.insert(line_data.line, Line::from(line_data));
179            return Ok(());
180        }
181        let line = self.lines.get_mut(&line_data.line).unwrap();
182        line.try_merge(line_data)
183    }
184}
185
186
187impl_try_merge_self_summary!(Lines:lines, ChecksumError);
188
189
190#[cfg(test)]
191mod tests {
192    use merger::ops::*;
193    use record:: { LineData };
194    use report::line:: { Line, Lines };
195    use report::summary:: { Summary };
196    use report::counter:: { FoundCounter, HitCounter };
197
198    #[test]
199    fn add_line_data() {
200        let mut lines = Lines::new();
201        lines.try_merge(&LineData { line: 1, count: 1, checksum: Some("abc".to_string()) }).unwrap();
202        lines.try_merge(&LineData { line: 1, count: 1, checksum: Some("abc".to_string()) }).unwrap();
203
204        let result = lines.clone();
205        assert_eq!( result.get(&1), Some(&Line::new(1, 2, Some("abc".to_string()))) );
206    }
207
208    #[test]
209    fn add_lines_data() {
210        let mut lines = Lines::new();
211        lines.try_merge(&LineData { line: 1, count: 1, checksum: Some("abc".to_string()) }).unwrap();
212
213        let ref cloned_lines = lines.clone();
214        lines.try_merge(cloned_lines).unwrap();
215
216        assert_eq!( lines.get(&1), Some(&Line::new(1, 2, Some("abc".to_string()))) );
217    }
218
219    #[test]
220    fn hit_count_and_found_count() {
221        let mut lines = Lines::new();
222
223        lines.try_merge(&LineData { line: 1, count: 1, checksum: Some("abc".to_string()) }).unwrap();
224        lines.try_merge(&LineData { line: 2, count: 0, checksum: Some("def".to_string()) }).unwrap();
225
226        assert_eq!( lines.hit_count(), 1 );
227        assert_eq!( lines.found_count(), 2 );
228    }
229}