From 9965fc8cc35afc0ff4b338b0d893742b84fe102d Mon Sep 17 00:00:00 2001 From: Casper Beyer Date: Sat, 30 Jan 2021 03:45:22 +0800 Subject: [PATCH] fix(cli/coverage): display mapped instrumentation line counts (#9310) --- cli/tests/integration_tests.rs | 6 + cli/tests/subdir/complex.ts | 35 +++++ cli/tests/test_branch_coverage.out | 2 +- cli/tests/test_comment_coverage.out | 2 +- cli/tests/test_complex_coverage.out | 18 +++ cli/tests/test_complex_coverage.ts | 5 + cli/tests/test_coverage.out | 6 +- cli/tests/test_run_combined_coverage.out | 10 +- cli/tests/test_run_run_coverage.out | 8 +- cli/tests/test_run_test_coverage.out | 8 +- cli/tools/coverage.rs | 188 ++++++++++++----------- 11 files changed, 180 insertions(+), 108 deletions(-) create mode 100644 cli/tests/subdir/complex.ts create mode 100644 cli/tests/test_complex_coverage.out create mode 100644 cli/tests/test_complex_coverage.ts diff --git a/cli/tests/integration_tests.rs b/cli/tests/integration_tests.rs index eab49a3c4a..7eae47859c 100644 --- a/cli/tests/integration_tests.rs +++ b/cli/tests/integration_tests.rs @@ -3478,6 +3478,12 @@ itest!(deno_test_coverage { 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 { args: "test --coverage --unstable test_comment_coverage.ts", output: "test_comment_coverage.out", diff --git a/cli/tests/subdir/complex.ts b/cli/tests/subdir/complex.ts new file mode 100644 index 0000000000..588e6ce591 --- /dev/null +++ b/cli/tests/subdir/complex.ts @@ -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, + ); +} diff --git a/cli/tests/test_branch_coverage.out b/cli/tests/test_branch_coverage.out index e52c4bac0b..375073e364 100644 --- a/cli/tests/test_branch_coverage.out +++ b/cli/tests/test_branch_coverage.out @@ -4,7 +4,7 @@ test branch ... ok ([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 { 5 | return false; 6 | } diff --git a/cli/tests/test_comment_coverage.out b/cli/tests/test_comment_coverage.out index 582152fa18..ce846836c0 100644 --- a/cli/tests/test_comment_coverage.out +++ b/cli/tests/test_comment_coverage.out @@ -4,4 +4,4 @@ test comment ... ok ([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) diff --git a/cli/tests/test_complex_coverage.out b/cli/tests/test_complex_coverage.out new file mode 100644 index 0000000000..1082d098c5 --- /dev/null +++ b/cli/tests/test_complex_coverage.out @@ -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 | } diff --git a/cli/tests/test_complex_coverage.ts b/cli/tests/test_complex_coverage.ts new file mode 100644 index 0000000000..06f17d87d0 --- /dev/null +++ b/cli/tests/test_complex_coverage.ts @@ -0,0 +1,5 @@ +import { complex } from "./subdir/complex.ts"; + +Deno.test("complex", function () { + complex("foo", "bar", "baz"); +}); diff --git a/cli/tests/test_coverage.out b/cli/tests/test_coverage.out index 709d57d3b3..83456bced2 100644 --- a/cli/tests/test_coverage.out +++ b/cli/tests/test_coverage.out @@ -4,7 +4,7 @@ test returnsFooSuccess ... ok ([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 { 4 | return "Hi"; 5 | } @@ -16,11 +16,11 @@ cover [WILDCARD]/tests/subdir/mod1.ts ... 35.714% (5/14) 15 | export function throwsError(): void { 16 | throw Error("exception from mod1"); 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 { 2 | console.log("Hello"); 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 { 8 | printHello(); 9 | } diff --git a/cli/tests/test_run_combined_coverage.out b/cli/tests/test_run_combined_coverage.out index 24ceb9955c..9a638214e4 100644 --- a/cli/tests/test_run_combined_coverage.out +++ b/cli/tests/test_run_combined_coverage.out @@ -12,8 +12,8 @@ ok ([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/subdir/mod1.ts ... 57.143% (8/14) +cover [WILDCARD]/tests/run_coverage.ts ... 100.000% (2/2) +cover [WILDCARD]/tests/subdir/mod1.ts ... 53.846% (7/13) 11 | export function printHello3(): void { 12 | printHello2(); 13 | } @@ -21,12 +21,12 @@ cover [WILDCARD]/tests/subdir/mod1.ts ... 57.143% (8/14) 15 | export function throwsError(): void { 16 | throw Error("exception from mod1"); 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 { 2 | console.log("Hello"); 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 { 8 | printHello(); 9 | } -cover [WILDCARD]/tests/test_coverage.ts ... 100.000% (5/5) +cover [WILDCARD]/tests/test_coverage.ts ... 100.000% (4/4) diff --git a/cli/tests/test_run_run_coverage.out b/cli/tests/test_run_run_coverage.out index 97eb292640..81f86c9bee 100644 --- a/cli/tests/test_run_run_coverage.out +++ b/cli/tests/test_run_run_coverage.out @@ -5,8 +5,8 @@ ok ([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/subdir/mod1.ts ... 35.714% (5/14) +cover [WILDCARD]/tests/run_coverage.ts ... 100.000% (2/2) +cover [WILDCARD]/tests/subdir/mod1.ts ... 30.769% (4/13) 7 | export function returnsFoo2(): string { 8 | return returnsFoo(); 9 | } @@ -18,11 +18,11 @@ cover [WILDCARD]/tests/subdir/mod1.ts ... 35.714% (5/14) 15 | export function throwsError(): void { 16 | throw Error("exception from mod1"); 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 { 2 | console.log("Hello"); 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 { 4 | return "Foo"; 5 | } diff --git a/cli/tests/test_run_test_coverage.out b/cli/tests/test_run_test_coverage.out index 186ecba139..aa524966eb 100644 --- a/cli/tests/test_run_test_coverage.out +++ b/cli/tests/test_run_test_coverage.out @@ -10,7 +10,7 @@ ok ([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 { 4 | return "Hi"; 5 | } @@ -22,12 +22,12 @@ cover [WILDCARD]/tests/subdir/mod1.ts ... 35.714% (5/14) 15 | export function throwsError(): void { 16 | throw Error("exception from mod1"); 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 { 2 | console.log("Hello"); 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 { 8 | printHello(); 9 | } -cover [WILDCARD]/tests/test_coverage.ts ... 100.000% (5/5) +cover [WILDCARD]/tests/test_coverage.ts ... 100.000% (4/4) diff --git a/cli/tools/coverage.rs b/cli/tools/coverage.rs index b7d64e8f26..b06f4d2bec 100644 --- a/cli/tools/coverage.rs +++ b/cli/tools/coverage.rs @@ -151,119 +151,127 @@ impl PrettyCoverageReporter { } let lines = script_source.split('\n').collect::>(); - let mut covered_lines: Vec = Vec::new(); - let mut uncovered_lines: Vec = Vec::new(); - let mut line_start_offset = 0; - for (index, line) in lines.iter().enumerate() { - let line_end_offset = line_start_offset + line.len(); + let line_offsets = { + let mut offsets: Vec<(usize, usize)> = Vec::new(); + let mut index = 0; - let mut count = 0; - - 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 { - covered_lines.push(index); - continue; + for line in &lines { + offsets.push((index, index + line.len() + 1)); + index += line.len() + 1; } - // 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; + offsets + }; + + let line_counts = line_offsets + .iter() + .enumerate() + .map(|(index, (line_start_offset, line_end_offset))| { + 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 - // zero. - // - // We check for intersection instead of inclusion here because a block may be anywhere - // inside a line. - for function in &script_coverage.functions { - for range in &function.ranges { - if range.count > 0 { - continue; - } + // Reset the count if any block intersects with the current line has a count of + // zero. + // + // We check for intersection instead of inclusion here because a block may be anywhere + // inside a line. + for function in &script_coverage.functions { + for range in &function.ranges { + if range.count > 0 { + continue; + } - if (range.start_offset < line_start_offset - && range.end_offset > line_start_offset) - || (range.start_offset < line_end_offset - && range.end_offset > line_end_offset) - { - count = 0; + if (range.start_offset < *line_start_offset + && range.end_offset > *line_start_offset) + || (range.start_offset < *line_end_offset + && range.end_offset > *line_end_offset) + { + count = 0; + } } } - } - if count > 0 { - covered_lines.push(index); - } else { - uncovered_lines.push(index); - } + (index, count) + }) + .collect::>(); - line_start_offset += line.len() + 1; - } + let lines = if let Some(original_source) = maybe_original_source.as_ref() { + original_source.split('\n').collect::>() + } 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::>(); + + line_counts.sort_unstable_by_key(|(index, _)| *index); + line_counts.dedup_by_key(|(index, _)| *index); + + line_counts + } else { + line_counts + }; if !self.quiet { print!("cover {} ... ", script_coverage.url); - let line_coverage_ratio = covered_lines.len() as f32 / lines.len() as f32; - let line_coverage = format!( - "{:.3}% ({}/{})", - line_coverage_ratio * 100.0, - covered_lines.len(), - lines.len() - ); + let hit_lines = line_counts + .iter() + .filter(|(_, count)| *count != 0) + .map(|(index, _)| *index); - 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)); - } else if line_coverage_ratio >= 0.75 { + } else if line_ratio >= 0.75 { println!("{}", colors::yellow(&line_coverage)); } else { println!("{}", colors::red(&line_coverage)); } - let output_lines = - if let Some(original_source) = maybe_original_source.as_ref() { - original_source.split('\n').collect::>() - } 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::>(); - - indices.sort_unstable(); - indices.dedup(); - - indices - } else { - uncovered_lines - }; - let mut last_line = None; - for line_index in output_indices { + for line_index in missed_lines { const WIDTH: usize = 4; const SEPERATOR: &str = "|"; @@ -279,7 +287,7 @@ impl PrettyCoverageReporter { "{:width$} {} {}", line_index + 1, colors::gray(SEPERATOR), - colors::red(&output_lines[line_index]), + colors::red(&lines[line_index]), width = WIDTH );