Skip to content

Commit

Permalink
[cli] Improve span merging in the coverage tool
Browse files Browse the repository at this point in the history
- Improve merging spans in the coverage tool:
  - The old solution failed to merge neighbouring spans
    e.g. `[3, 5]` and `[5, 7]` into `[3, 7]`
- Also, make the `merge_spans()` function public so it could be used by the
  `move-mutation-test` tool which also parses the coverage output file.
  • Loading branch information
Rqnsom committed Nov 6, 2024
1 parent 13a9587 commit 41c7a06
Showing 1 changed file with 85 additions and 12 deletions.
97 changes: 85 additions & 12 deletions third_party/move/tools/move-coverage/src/source_coverage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -398,34 +398,107 @@ impl SourceCoverage {
}
}

fn merge_spans(file_hash: FileHash, cov: FunctionSourceCoverage) -> Vec<Span> {
/// Merge overlapping spans.
pub fn merge_spans(file_hash: FileHash, cov: FunctionSourceCoverage) -> Vec<Span> {
if cov.uncovered_locations.is_empty() {
return vec![];
}

let mut covs: Vec<_> = cov
let mut spans = cov
.uncovered_locations
.iter()
.filter(|loc| loc.file_hash() == file_hash)
.map(|loc| Span::new(loc.start(), loc.end()))
.collect();
if covs.is_empty() {
.collect::<Vec<_>>();
if spans.is_empty() {
return vec![];
}
covs.sort();
spans.sort();

let mut unioned = Vec::new();
let mut curr = covs.remove(0);
let mut unioned = Vec::with_capacity(spans.len());
let mut curr = spans.remove(0);

for interval in covs {
if curr.disjoint(interval) {
unioned.push(curr);
curr = interval;
for span in spans {
if curr.end() >= span.start() {
curr = curr.merge(span);
} else {
curr = curr.merge(interval);
unioned.push(curr);
curr = span;
}
}

unioned.push(curr);
unioned
}

#[cfg(test)]
mod tests {
use super::FunctionSourceCoverage;
use crate::source_coverage::merge_spans;
use codespan::Span;
use move_command_line_common::files::FileHash;
use move_ir_types::location::Loc;

/// Ensure merging spans works as expected.
#[test]
fn test_merge_spans_works() {
let test_hash = FileHash([0; 32]);
let other_hash = FileHash([1; 32]);

// Let's use unsorted array on purpose.
let uncovered_locations = vec![
// Should be merged:
Loc::new(test_hash, 50, 51),
Loc::new(test_hash, 51, 52),
// Should be merged:
Loc::new(test_hash, 3, 5),
Loc::new(test_hash, 2, 10),
Loc::new(test_hash, 5, 11),
// Should stay the same:
Loc::new(test_hash, 15, 16),
// Should be merged:
Loc::new(test_hash, 20, 25),
Loc::new(test_hash, 24, 25),
Loc::new(test_hash, 21, 25),
// Should stay the same:
Loc::new(test_hash, 26, 29),
// Shouldn't intefere with other hashes.
Loc::new(other_hash, 2, 20),
];
let expected_spans = vec![
Span::new(2, 11),
Span::new(15, 16),
Span::new(20, 25),
Span::new(26, 29),
Span::new(50, 52),
];

let cov = FunctionSourceCoverage {
fn_is_native: false,
uncovered_locations,
};
assert_eq!(expected_spans, merge_spans(test_hash, cov));
}

/// Ensure merging spans works fine when there is no spans to be merged.
#[test]
fn test_merge_spans_when_spans_are_empty() {
let test_hash = FileHash([0; 32]);
let other_hash = FileHash([1; 32]);

// Check that it works when are files have full coverage.
let cov = FunctionSourceCoverage {
fn_is_native: false,
uncovered_locations: vec![],
};
assert!(merge_spans(test_hash, cov).is_empty());

// Check that it works when the hash under test has the full coverage.
let uncovered_locations = vec![Loc::new(other_hash, 2, 20)];
let cov = FunctionSourceCoverage {
fn_is_native: false,
uncovered_locations,
};
assert!(merge_spans(test_hash, cov).is_empty());
}
}

0 comments on commit 41c7a06

Please sign in to comment.