fix(cli/coverage): display mapped instrumentation line counts (#9310)

This commit is contained in:
Casper Beyer 2021-01-30 03:45:22 +08:00 committed by GitHub
parent 013b8fe606
commit 9965fc8cc3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 180 additions and 108 deletions

View file

@ -3478,6 +3478,12 @@ itest!(deno_test_coverage {
exit_code: 0, exit_code: 0,
}); });
itest!(deno_test_complex_coverage {
args: "test --coverage --unstable test_complex_coverage.ts",
output: "test_complex_coverage.out",
exit_code: 0,
});
itest!(deno_test_comment_coverage { itest!(deno_test_comment_coverage {
args: "test --coverage --unstable test_comment_coverage.ts", args: "test --coverage --unstable test_comment_coverage.ts",
output: "test_comment_coverage.out", output: "test_comment_coverage.out",

View file

@ -0,0 +1,35 @@
// This entire interface should be completely ignored by the coverage tool.
export interface Complex {
// These are comments.
foo: string;
// But this is a stub, so this isn't really documentation.
bar: string;
// Really all these are doing is padding the line count.
baz: string;
}
export function complex(
foo: string,
bar: string,
baz: string,
): Complex {
return {
foo,
bar,
baz,
};
}
export function unused(
foo: string,
bar: string,
baz: string,
): Complex {
return complex(
foo,
bar,
baz,
);
}

View file

@ -4,7 +4,7 @@ test branch ... ok ([WILDCARD])
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out ([WILDCARD]) test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out ([WILDCARD])
cover [WILDCARD]/tests/subdir/branch.ts ... 66.667% (6/9) cover [WILDCARD]/tests/subdir/branch.ts ... 57.143% (4/7)
4 | } else { 4 | } else {
5 | return false; 5 | return false;
6 | } 6 | }

View file

@ -4,4 +4,4 @@ test comment ... ok ([WILDCARD])
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out ([WILDCARD]) test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out ([WILDCARD])
[WILDCARD]/tests/subdir/comment.ts ... 100.000% (4/4) [WILDCARD]/tests/subdir/comment.ts ... 100.000% (3/3)

View file

@ -0,0 +1,18 @@
Check [WILDCARD]/tests/$deno$test.ts
running 1 tests
test complex ... ok ([WILDCARD])
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out ([WILDCARD])
cover [WILDCARD]/tests/subdir/complex.ts ... 50.000% (10/20)
25 | export function unused(
26 | foo: string,
27 | bar: string,
28 | baz: string,
-----|-----
30 | return complex(
31 | foo,
32 | bar,
33 | baz,
34 | );
35 | }

View file

@ -0,0 +1,5 @@
import { complex } from "./subdir/complex.ts";
Deno.test("complex", function () {
complex("foo", "bar", "baz");
});

View file

@ -4,7 +4,7 @@ test returnsFooSuccess ... ok ([WILDCARD])
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out ([WILDCARD]) test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out ([WILDCARD])
cover [WILDCARD]/tests/subdir/mod1.ts ... 35.714% (5/14) cover [WILDCARD]/tests/subdir/mod1.ts ... 30.769% (4/13)
3 | export function returnsHi(): string { 3 | export function returnsHi(): string {
4 | return "Hi"; 4 | return "Hi";
5 | } 5 | }
@ -16,11 +16,11 @@ cover [WILDCARD]/tests/subdir/mod1.ts ... 35.714% (5/14)
15 | export function throwsError(): void { 15 | export function throwsError(): void {
16 | throw Error("exception from mod1"); 16 | throw Error("exception from mod1");
17 | } 17 | }
cover [WILDCARD]/tests/subdir/print_hello.ts ... 25.000% (1/4) cover [WILDCARD]/tests/subdir/print_hello.ts ... 0.000% (0/3)
1 | export function printHello(): void { 1 | export function printHello(): void {
2 | console.log("Hello"); 2 | console.log("Hello");
3 | } 3 | }
cover [WILDCARD]/tests/subdir/subdir2/mod2.ts ... 62.500% (5/8) cover [WILDCARD]/tests/subdir/subdir2/mod2.ts ... 57.143% (4/7)
7 | export function printHello2(): void { 7 | export function printHello2(): void {
8 | printHello(); 8 | printHello();
9 | } 9 | }

View file

@ -12,8 +12,8 @@ ok ([WILDCARD])
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out ([WILDCARD]) test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out ([WILDCARD])
cover [WILDCARD]/tests/run_coverage.ts ... 100.000% (3/3) cover [WILDCARD]/tests/run_coverage.ts ... 100.000% (2/2)
cover [WILDCARD]/tests/subdir/mod1.ts ... 57.143% (8/14) cover [WILDCARD]/tests/subdir/mod1.ts ... 53.846% (7/13)
11 | export function printHello3(): void { 11 | export function printHello3(): void {
12 | printHello2(); 12 | printHello2();
13 | } 13 | }
@ -21,12 +21,12 @@ cover [WILDCARD]/tests/subdir/mod1.ts ... 57.143% (8/14)
15 | export function throwsError(): void { 15 | export function throwsError(): void {
16 | throw Error("exception from mod1"); 16 | throw Error("exception from mod1");
17 | } 17 | }
cover [WILDCARD]/tests/subdir/print_hello.ts ... 25.000% (1/4) cover [WILDCARD]/tests/subdir/print_hello.ts ... 0.000% (0/3)
1 | export function printHello(): void { 1 | export function printHello(): void {
2 | console.log("Hello"); 2 | console.log("Hello");
3 | } 3 | }
cover [WILDCARD]/tests/subdir/subdir2/mod2.ts ... 62.500% (5/8) cover [WILDCARD]/tests/subdir/subdir2/mod2.ts ... 57.143% (4/7)
7 | export function printHello2(): void { 7 | export function printHello2(): void {
8 | printHello(); 8 | printHello();
9 | } 9 | }
cover [WILDCARD]/tests/test_coverage.ts ... 100.000% (5/5) cover [WILDCARD]/tests/test_coverage.ts ... 100.000% (4/4)

View file

@ -5,8 +5,8 @@ ok ([WILDCARD])
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out ([WILDCARD]) test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out ([WILDCARD])
cover [WILDCARD]/tests/run_coverage.ts ... 100.000% (3/3) cover [WILDCARD]/tests/run_coverage.ts ... 100.000% (2/2)
cover [WILDCARD]/tests/subdir/mod1.ts ... 35.714% (5/14) cover [WILDCARD]/tests/subdir/mod1.ts ... 30.769% (4/13)
7 | export function returnsFoo2(): string { 7 | export function returnsFoo2(): string {
8 | return returnsFoo(); 8 | return returnsFoo();
9 | } 9 | }
@ -18,11 +18,11 @@ cover [WILDCARD]/tests/subdir/mod1.ts ... 35.714% (5/14)
15 | export function throwsError(): void { 15 | export function throwsError(): void {
16 | throw Error("exception from mod1"); 16 | throw Error("exception from mod1");
17 | } 17 | }
cover [WILDCARD]/tests/subdir/print_hello.ts ... 25.000% (1/4) cover [WILDCARD]/tests/subdir/print_hello.ts ... 0.000% (0/3)
1 | export function printHello(): void { 1 | export function printHello(): void {
2 | console.log("Hello"); 2 | console.log("Hello");
3 | } 3 | }
cover [WILDCARD]/tests/subdir/subdir2/mod2.ts ... 25.000% (2/8) cover [WILDCARD]/tests/subdir/subdir2/mod2.ts ... 14.286% (1/7)
3 | export function returnsFoo(): string { 3 | export function returnsFoo(): string {
4 | return "Foo"; 4 | return "Foo";
5 | } 5 | }

View file

@ -10,7 +10,7 @@ ok ([WILDCARD])
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out ([WILDCARD]) test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out ([WILDCARD])
cover [WILDCARD]/tests/subdir/mod1.ts ... 35.714% (5/14) cover [WILDCARD]/tests/subdir/mod1.ts ... 30.769% (4/13)
3 | export function returnsHi(): string { 3 | export function returnsHi(): string {
4 | return "Hi"; 4 | return "Hi";
5 | } 5 | }
@ -22,12 +22,12 @@ cover [WILDCARD]/tests/subdir/mod1.ts ... 35.714% (5/14)
15 | export function throwsError(): void { 15 | export function throwsError(): void {
16 | throw Error("exception from mod1"); 16 | throw Error("exception from mod1");
17 | } 17 | }
cover [WILDCARD]/tests/subdir/print_hello.ts ... 25.000% (1/4) cover [WILDCARD]/tests/subdir/print_hello.ts ... 0.000% (0/3)
1 | export function printHello(): void { 1 | export function printHello(): void {
2 | console.log("Hello"); 2 | console.log("Hello");
3 | } 3 | }
cover [WILDCARD]/tests/subdir/subdir2/mod2.ts ... 62.500% (5/8) cover [WILDCARD]/tests/subdir/subdir2/mod2.ts ... 57.143% (4/7)
7 | export function printHello2(): void { 7 | export function printHello2(): void {
8 | printHello(); 8 | printHello();
9 | } 9 | }
cover [WILDCARD]/tests/test_coverage.ts ... 100.000% (5/5) cover [WILDCARD]/tests/test_coverage.ts ... 100.000% (4/4)

View file

@ -151,119 +151,127 @@ impl PrettyCoverageReporter {
} }
let lines = script_source.split('\n').collect::<Vec<_>>(); let lines = script_source.split('\n').collect::<Vec<_>>();
let mut covered_lines: Vec<usize> = Vec::new();
let mut uncovered_lines: Vec<usize> = Vec::new();
let mut line_start_offset = 0; let line_offsets = {
for (index, line) in lines.iter().enumerate() { let mut offsets: Vec<(usize, usize)> = Vec::new();
let line_end_offset = line_start_offset + line.len(); let mut index = 0;
let mut count = 0; for line in &lines {
offsets.push((index, index + line.len() + 1));
let ignore = ignored_spans.iter().any(|span| { index += line.len() + 1;
(span.lo.0 as usize) <= line_start_offset
&& (span.hi.0 as usize) >= line_end_offset
});
if ignore {
covered_lines.push(index);
continue;
} }
// Count the hits of ranges that include the entire line which will always be at-least one offsets
// as long as the code has been evaluated. };
for function in &script_coverage.functions {
for range in &function.ranges { let line_counts = line_offsets
if range.start_offset <= line_start_offset .iter()
&& range.end_offset >= line_end_offset .enumerate()
{ .map(|(index, (line_start_offset, line_end_offset))| {
count += range.count; let ignore = ignored_spans.iter().any(|span| {
(span.lo.0 as usize) <= *line_start_offset
&& (span.hi.0 as usize) >= *line_end_offset
});
if ignore {
return (index, 1);
}
let mut count = 0;
// Count the hits of ranges that include the entire line which will always be at-least one
// as long as the code has been evaluated.
for function in &script_coverage.functions {
for range in &function.ranges {
if range.start_offset <= *line_start_offset
&& range.end_offset >= *line_end_offset
{
count += range.count;
}
} }
} }
}
// Reset the count if any block intersects with the current line has a count of // Reset the count if any block intersects with the current line has a count of
// zero. // zero.
// //
// We check for intersection instead of inclusion here because a block may be anywhere // We check for intersection instead of inclusion here because a block may be anywhere
// inside a line. // inside a line.
for function in &script_coverage.functions { for function in &script_coverage.functions {
for range in &function.ranges { for range in &function.ranges {
if range.count > 0 { if range.count > 0 {
continue; continue;
} }
if (range.start_offset < line_start_offset if (range.start_offset < *line_start_offset
&& range.end_offset > line_start_offset) && range.end_offset > *line_start_offset)
|| (range.start_offset < line_end_offset || (range.start_offset < *line_end_offset
&& range.end_offset > line_end_offset) && range.end_offset > *line_end_offset)
{ {
count = 0; count = 0;
}
} }
} }
}
if count > 0 { (index, count)
covered_lines.push(index); })
} else { .collect::<Vec<(usize, usize)>>();
uncovered_lines.push(index);
}
line_start_offset += line.len() + 1; let lines = if let Some(original_source) = maybe_original_source.as_ref() {
} original_source.split('\n').collect::<Vec<_>>()
} else {
lines
};
let line_counts = if let Some(source_map) = maybe_source_map.as_ref() {
let mut line_counts = line_counts
.iter()
.map(|(index, count)| {
source_map
.tokens()
.filter(move |token| token.get_dst_line() as usize == *index)
.map(move |token| (token.get_src_line() as usize, *count))
})
.flatten()
.collect::<Vec<(usize, usize)>>();
line_counts.sort_unstable_by_key(|(index, _)| *index);
line_counts.dedup_by_key(|(index, _)| *index);
line_counts
} else {
line_counts
};
if !self.quiet { if !self.quiet {
print!("cover {} ... ", script_coverage.url); print!("cover {} ... ", script_coverage.url);
let line_coverage_ratio = covered_lines.len() as f32 / lines.len() as f32; let hit_lines = line_counts
let line_coverage = format!( .iter()
"{:.3}% ({}/{})", .filter(|(_, count)| *count != 0)
line_coverage_ratio * 100.0, .map(|(index, _)| *index);
covered_lines.len(),
lines.len()
);
if line_coverage_ratio >= 0.9 { let missed_lines = line_counts
.iter()
.filter(|(_, count)| *count == 0)
.map(|(index, _)| *index);
let lines_found = line_counts.len();
let lines_hit = hit_lines.count();
let line_ratio = lines_hit as f32 / lines_found as f32;
let line_coverage =
format!("{:.3}% ({}/{})", line_ratio * 100.0, lines_hit, lines_found,);
if line_ratio >= 0.9 {
println!("{}", colors::green(&line_coverage)); println!("{}", colors::green(&line_coverage));
} else if line_coverage_ratio >= 0.75 { } else if line_ratio >= 0.75 {
println!("{}", colors::yellow(&line_coverage)); println!("{}", colors::yellow(&line_coverage));
} else { } else {
println!("{}", colors::red(&line_coverage)); println!("{}", colors::red(&line_coverage));
} }
let output_lines =
if let Some(original_source) = maybe_original_source.as_ref() {
original_source.split('\n').collect::<Vec<_>>()
} else {
lines
};
let output_indices = if let Some(source_map) = maybe_source_map.as_ref() {
// The compiled executable source lines have to be mapped to all the original source lines that they
// came from; this happens in a couple of emit scenarios, the most common example being function
// declarations where the compiled JavaScript code only takes a line but the original
// TypeScript source spans 10 lines.
let mut indices = uncovered_lines
.iter()
.map(|i| {
source_map
.tokens()
.filter(move |token| token.get_dst_line() as usize == *i)
.map(|token| token.get_src_line() as usize)
})
.flatten()
.collect::<Vec<usize>>();
indices.sort_unstable();
indices.dedup();
indices
} else {
uncovered_lines
};
let mut last_line = None; let mut last_line = None;
for line_index in output_indices { for line_index in missed_lines {
const WIDTH: usize = 4; const WIDTH: usize = 4;
const SEPERATOR: &str = "|"; const SEPERATOR: &str = "|";
@ -279,7 +287,7 @@ impl PrettyCoverageReporter {
"{:width$} {} {}", "{:width$} {} {}",
line_index + 1, line_index + 1,
colors::gray(SEPERATOR), colors::gray(SEPERATOR),
colors::red(&output_lines[line_index]), colors::red(&lines[line_index]),
width = WIDTH width = WIDTH
); );