diff --git a/.travis.yml b/.travis.yml index 9238a7b95..204d6fb3f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -126,7 +126,7 @@ script: - | if [ "$TRAVIS_RUST_VERSION" = "nightly" ] && [ -z "$TRAVIS_TAG" ]; then zip -0 ccov.zip `find . \( -name "grcov*.gc*" -o -name "llvmgcov.gc*" \) -print`; - ./target/debug/grcov ccov.zip -s . -t lcov --llvm --branch --ignore-not-existing --ignore "/*" -o lcov.info; + ./target/debug/grcov ccov.zip -s . -t lcov --llvm --branch --ignore-not-existing --ignore "/*" -o lcov.info --excl-line "#\[derive\(" --excl-br-line "#\[derive\("; bash <(curl -s https://codecov.io/bash) -f lcov.info; fi - | diff --git a/Cargo.lock b/Cargo.lock index cc7149a67..0b41fcc9f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -199,7 +199,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", "crossbeam-channel 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-deque 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-deque 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", "crossbeam-epoch 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "crossbeam-queue 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -215,24 +215,12 @@ dependencies = [ [[package]] name = "crossbeam-deque" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "crossbeam-epoch 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "arrayvec 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", - "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "memoffset 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-epoch 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -303,6 +291,11 @@ dependencies = [ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "either" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "failure" version = "0.1.5" @@ -392,6 +385,7 @@ dependencies = [ "md-5 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "num_cpus 1.13.0 (registry+https://github.com/rust-lang/crates.io-index)", "quick-xml 0.18.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rayon 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "regex 1.3.7 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-hash 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -447,6 +441,11 @@ dependencies = [ "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "maybe-uninit" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "md-5" version = "0.8.0" @@ -625,6 +624,28 @@ dependencies = [ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "rayon" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crossbeam-deque 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", + "either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)", + "rayon-core 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rayon-core" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crossbeam-deque 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-queue 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 1.13.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "rdrand" version = "0.4.0" @@ -955,8 +976,7 @@ dependencies = [ "checksum crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1" "checksum crossbeam 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "69323bff1fb41c635347b8ead484a5ca6c3f11914d784170b158d8449ab07f8e" "checksum crossbeam-channel 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "acec9a3b0b3559f15aee4f90746c4e5e293b701c0f7d3925d24e01645267b68c" -"checksum crossbeam-deque 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b18cd2e169ad86297e6bc0ad9aa679aee9daa4f19e8163860faf7c164e4f5a71" -"checksum crossbeam-epoch 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "fedcd6772e37f3da2a9af9bf12ebe046c0dfe657992377b4df982a2b54cd37a9" +"checksum crossbeam-deque 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "9f02af974daeee82218205558e51ec8768b48cf524bd01d550abe5573a608285" "checksum crossbeam-epoch 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5064ebdbf05ce3cb95e45c8b086f72263f4166b29b97f6baff7ef7fe047b55ac" "checksum crossbeam-queue 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "dfd6515864a82d2f877b42813d4553292c6659498c9a2aa31bab5a15243c2700" "checksum crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "04973fa96e96579258a5091af6003abde64af786b860f18622b82e026cca60e6" @@ -964,6 +984,7 @@ dependencies = [ "checksum digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" "checksum dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3" "checksum dirs-sys 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "afa0b23de8fd801745c471deffa6e12d248f962c9fd4b4c33787b055599bde7b" +"checksum either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" "checksum failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "795bd83d3abeb9220f257e597aa0080a508b27533824adf336529648f6abf7e2" "checksum failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ea1063915fd7ef4309e222a5a07cf9c319fb9c7836b1f89b85458672dbb127e1" "checksum flate2 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)" = "2adaffba6388640136149e18ed080b77a78611c1e1d6de75aedcdf78df5d4682" @@ -979,6 +1000,7 @@ dependencies = [ "checksum libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)" = "34fcd2c08d2f832f376f4173a231990fa5aef4e99fb569867318a227ef4c06ba" "checksum libz-sys 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)" = "2eb5e43362e38e2bca2fd5f5134c4d4564a23a5c28e9b95411652021a8675ebe" "checksum log 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "1b9ad466a945c9c40f6f9a449c55675547e59bc75a2722d4689042ab3ae80c9c" +"checksum maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" "checksum md-5 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a18af3dcaf2b0219366cdb4e2af65a6101457b415c3d1a5c71dd9c2b7c77b9c8" "checksum memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" "checksum memoffset 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ce6075db033bbbb7ee5a0bbd3a3186bbae616f57fb001c485c7ff77955f8177f" @@ -1002,6 +1024,8 @@ dependencies = [ "checksum rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" "checksum rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" "checksum rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" +"checksum rayon 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "db6ce3297f9c85e16621bb8cca38a06779ffc31bb8184e1be4bed2be4678a098" +"checksum rayon-core 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "08a89b46efaf957e52b18062fb2f4660f8b8a4dde1807ca002690868ef2c85a9" "checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" "checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" "checksum redox_users 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4ecedbca3bf205f8d8f5c2b44d83cd0690e39ee84b951ed649e9f1841132b66d" diff --git a/Cargo.toml b/Cargo.toml index 9840f54e0..70bcd8901 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,10 +51,9 @@ fomat-macros = "^0.3" chrono = "^0.4" log = "^0.4" simplelog = "^0.7" +regex = "^1.3" +rayon = "^1.3" [target.'cfg(unix)'.dependencies] #tcmalloc = { version = "^0.3", features = ["bundled"] } tcmalloc = { version = "^0.3", optional = true } - -[dev-dependencies] -regex = "^1.3" diff --git a/README.md b/README.md index 12c1d9680..db2ae84bc 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,18 @@ FLAGS: OPTIONS: --commit-sha Sets the hash of the commit used to generate the code coverage data + --excl-br-line + Lines in covered files containing this marker will be excluded from branch coverage. + --excl-br-start + Marks the end of a section excluded from branch coverage. The current line is part of this section. + --excl-br-stop + Marks the end of a section excluded from branch coverage. The current line is part of this section. + --excl-line + Lines in covered files containing this marker will be excluded. + --excl-start + Marks the beginning of an excluded section. The current line is part of this section. + --excl-stop + Marks the end of an excluded section. The current line is part of this section. --filter Filters out covered/uncovered files. Use 'covered' to only return covered files, 'uncovered' to only return uncovered files [possible values: covered, uncovered] diff --git a/appveyor.yml b/appveyor.yml index 2665c0b39..60abfab38 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -56,7 +56,7 @@ test_script: If ($env:CHANNEL -eq "nightly" -And $env:APPVEYOR_REPO_TAG -eq "false") { mkdir ccov_dir Get-ChildItem -Path *\grcov*.gc* -Recurse | Copy-Item -Destination ccov_dir - .\target\debug\grcov ccov_dir -s . -t lcov --llvm --branch --ignore-not-existing --ignore "C:*" -o lcov.info + .\target\debug\grcov ccov_dir -s . -t lcov --llvm --branch --ignore-not-existing --ignore "C:*" -o lcov.info --excl-line "#\[derive\(" --excl-br-line "#\[derive\(" (Get-Content lcov.info) | Foreach-Object {$_ -replace "\xEF\xBB\xBF", ""} | Set-Content lcov.info ((Get-Content lcov.info) -join "`n") + "`n" | Set-Content -NoNewline lcov.info $env:PATH = "C:\msys64\usr\bin;" + $env:PATH diff --git a/benches/parser.rs b/benches/parser.rs index 63b625bd9..b90ad68bf 100644 --- a/benches/parser.rs +++ b/benches/parser.rs @@ -10,9 +10,8 @@ use test::{black_box, Bencher}; #[bench] fn bench_parser_lcov(b: &mut Bencher) { b.iter(|| { - let f = File::open("./test/prova.info").expect("Failed to open lcov file"); - let file = BufReader::new(&f); - black_box(grcov::parse_lcov(file, true)); + let file = std::fs::read("./test/prova.info").expect("Failed to open lcov file"); + black_box(grcov::parse_lcov(file, true).unwrap()); }); } diff --git a/src/file_filter.rs b/src/file_filter.rs new file mode 100644 index 000000000..44239f29b --- /dev/null +++ b/src/file_filter.rs @@ -0,0 +1,134 @@ +use regex::Regex; +use std::path::PathBuf; + +pub enum FilterType { + Line(u32), + Branch(u32), + Both(u32), +} + +#[derive(Default)] +pub struct FileFilter { + excl_line: Option, + excl_start: Option, + excl_stop: Option, + excl_br_line: Option, + excl_br_start: Option, + excl_br_stop: Option, +} + +impl FileFilter { + pub fn new( + excl_line: Option, + excl_start: Option, + excl_stop: Option, + excl_br_line: Option, + excl_br_start: Option, + excl_br_stop: Option, + ) -> Self { + Self { + excl_line, + excl_start, + excl_stop, + excl_br_line, + excl_br_start, + excl_br_stop, + } + } + + pub fn create(&self, file: &PathBuf) -> Vec { + if self.excl_line.is_none() + && self.excl_start.is_none() + && self.excl_br_line.is_none() + && self.excl_br_start.is_none() + { + return Vec::new(); + } + + let file = std::fs::read_to_string(file); + let file = if let Ok(file) = file { + file + } else { + return Vec::new(); + }; + + let mut ignore_br = false; + let mut ignore = false; + + file.split('\n') + .enumerate() + .filter_map(move |(number, line)| { + // Line numbers are 1-based. + let number = (number + 1) as u32; + + // The file is split on \n, which may result in a trailing \r + // on Windows. Remove it. + let line = if line.ends_with('\r') { + &line[..(line.len() - 1)] + } else { + line + }; + + // End a branch ignore region. Region endings are exclusive. + if ignore_br + && self + .excl_br_stop + .as_ref() + .map_or(false, |f| f.is_match(line)) + { + ignore_br = false + } + + // End a line ignore region. Region endings are exclusive. + if ignore && self.excl_stop.as_ref().map_or(false, |f| f.is_match(line)) { + ignore = false + } + + // Start a branch ignore region. Region starts are inclusive. + if !ignore_br + && self + .excl_br_start + .as_ref() + .map_or(false, |f| f.is_match(line)) + { + ignore_br = true; + } + + // Start a line ignore region. Region starts are inclusive. + if !ignore && self.excl_start.as_ref().map_or(false, |f| f.is_match(line)) { + ignore = true; + } + + if ignore_br { + // Consuming code has to eliminate each of these + // individually, so it has to know when both are ignored vs. + // either. + if ignore { + Some(FilterType::Both(number)) + } else { + Some(FilterType::Branch(number)) + } + } else if ignore { + Some(FilterType::Line(number)) + } else if self + .excl_br_line + .as_ref() + .map_or(false, |f| f.is_match(line)) + { + // Single line exclusion. If single line exclusions occur + // inside a region they are meaningless (would be applied + // anway), so they are lower priority. + if self.excl_line.as_ref().map_or(false, |f| f.is_match(line)) { + Some(FilterType::Both(number)) + } else { + Some(FilterType::Branch(number)) + } + } else if self.excl_line.as_ref().map_or(false, |f| f.is_match(line)) { + Some(FilterType::Line(number)) + } else { + None + } + }) + .collect() + } +} diff --git a/src/lib.rs b/src/lib.rs index 796499356..56f06dd20 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -43,6 +43,9 @@ pub use crate::covdir::*; pub mod html; +mod file_filter; +pub use crate::file_filter::*; + use log::error; use std::collections::{btree_map, hash_map}; use std::fs; diff --git a/src/main.rs b/src/main.rs index da8af3d36..ccf91230c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -183,6 +183,42 @@ fn main() { .value_name("LOG") .takes_value(true)) + .arg(Arg::with_name("excl-line") + .help("Lines in covered files containing this marker will be excluded.") + .long("excl-line") + .value_name("regex") + .takes_value(true)) + + .arg(Arg::with_name("excl-start") + .help("Marks the beginning of an excluded section. The current line is part of this section.") + .long("excl-start") + .value_name("regex") + .takes_value(true)) + + .arg(Arg::with_name("excl-stop") + .help("Marks the end of an excluded section. The current line is part of this section.") + .long("excl-stop") + .value_name("regex") + .takes_value(true)) + + .arg(Arg::with_name("excl-br-line") + .help("Lines in covered files containing this marker will be excluded from branch coverage.") + .long("excl-br-line") + .value_name("regex") + .takes_value(true)) + + .arg(Arg::with_name("excl-br-start") + .help("Marks the end of a section excluded from branch coverage. The current line is part of this section.") + .long("excl-br-start") + .value_name("regex") + .takes_value(true)) + + .arg(Arg::with_name("excl-br-stop") + .help("Marks the end of a section excluded from branch coverage. The current line is part of this section.") + .long("excl-br-stop") + .value_name("regex") + .takes_value(true)) + // This group requires that at least one of --token and --service-job-id // be present. --service-job-id requires --service-name, so this // effectively means we accept the following combinations: @@ -244,6 +280,33 @@ fn main() { } }; + let excl_line = matches + .value_of("excl-line") + .and_then(|f| Some(regex::Regex::new(f).expect("invalid regex for excl-line."))); + let excl_start = matches + .value_of("excl-start") + .and_then(|f| Some(regex::Regex::new(f).expect("invalid regex for excl-start."))); + let excl_stop = matches + .value_of("excl-stop") + .and_then(|f| Some(regex::Regex::new(f).expect("invalid regex for excl-stop."))); + let excl_br_line = matches + .value_of("excl-br-line") + .and_then(|f| Some(regex::Regex::new(f).expect("invalid regex for excl-br-line."))); + let excl_br_start = matches + .value_of("excl-br-start") + .and_then(|f| Some(regex::Regex::new(f).expect("invalid regex for excl-br-start."))); + let excl_br_stop = matches + .value_of("excl-br-stop") + .and_then(|f| Some(regex::Regex::new(f).expect("invalid regex for excl-br-stop."))); + let file_filter = FileFilter::new( + excl_line, + excl_start, + excl_stop, + excl_br_line, + excl_br_start, + excl_br_stop, + ); + panic::set_hook(Box::new(|panic_info| { let (filename, line) = panic_info .location() @@ -377,6 +440,7 @@ fn main() { ignore_not_existing, &mut to_ignore_dirs, filter_option, + file_filter, ); if output_type == "ade" { diff --git a/src/path_rewriting.rs b/src/path_rewriting.rs index bad59c55b..e902832fd 100644 --- a/src/path_rewriting.rs +++ b/src/path_rewriting.rs @@ -1,10 +1,10 @@ use globset::{Glob, GlobSetBuilder}; +use rayon::prelude::*; use rustc_hash::FxHashMap; use serde_json::Value; use std::collections::hash_map; use std::fs; use std::io; -use std::mem; use std::path::{Component, Path, PathBuf}; use walkdir::{DirEntry, WalkDir}; @@ -92,15 +92,9 @@ fn apply_mapping(mapping: &Option, path: &str) -> PathBuf { } // Remove common part between the prefix's end and the path's start -fn guess_abs_path(prefix_dir: &PathBuf, path: &PathBuf, cache: &mut Option) -> PathBuf { - if let Some(cache) = cache { - if path.starts_with(&cache) { - return prefix_dir.join(path.strip_prefix(cache).unwrap().to_path_buf()); - } - } +fn guess_abs_path(prefix_dir: &PathBuf, path: &PathBuf) -> PathBuf { for ancestor in path.ancestors() { if prefix_dir.ends_with(ancestor) && !ancestor.as_os_str().is_empty() { - mem::replace(cache, Some(ancestor.to_path_buf())); return prefix_dir.join(path.strip_prefix(ancestor).unwrap().to_path_buf()); } } @@ -131,21 +125,16 @@ fn fixup_rel_path(source_dir: &Option, abs_path: &PathBuf, rel_path: Pa } // Get the absolute path for the source file's path, resolving symlinks. -fn get_abs_path( - source_dir: &Option, - rel_path: PathBuf, - cache: &mut Option, -) -> Option<(PathBuf, PathBuf)> { +fn get_abs_path(source_dir: &Option, rel_path: PathBuf) -> Option<(PathBuf, PathBuf)> { let mut abs_path = if !rel_path.is_relative() { rel_path.clone() } else if let Some(ref source_dir) = source_dir { if !cfg!(windows) { - guess_abs_path(&source_dir, &rel_path, cache) + guess_abs_path(&source_dir, &rel_path) } else { guess_abs_path( &source_dir, &PathBuf::from(&rel_path.to_str().unwrap().replace("/", "\\")), - cache, ) } } else { @@ -237,6 +226,7 @@ pub fn rewrite_paths( ignore_not_existing: bool, to_ignore_dirs: &mut [&str], filter_option: Option, + file_filter: crate::FileFilter, ) -> CovResultIter { let mut glob_builder = GlobSetBuilder::new(); @@ -279,59 +269,80 @@ pub fn rewrite_paths( } } - let mut cache: Option = None; + let results = result_map + .into_par_iter() + .filter_map(move |(path, mut result)| { + let path = path.replace("\\", "/"); - Box::new(result_map.into_iter().filter_map(move |(path, result)| { - let path = path.replace("\\", "/"); + // Get path from the mapping. + let rel_path = apply_mapping(&path_mapping, &path); - // Get path from the mapping. - let rel_path = apply_mapping(&path_mapping, &path); + // Remove prefix from the path. + let rel_path = remove_prefix(&prefix_dir, rel_path); - // Remove prefix from the path. - let rel_path = remove_prefix(&prefix_dir, rel_path); - - // Try mapping a partial path to a full path. - let rel_path = if check_extension(&rel_path, "java") { - map_partial_path(&file_to_paths, rel_path) - } else { - rel_path - }; - - // Get absolute path to the source file. - let paths = get_abs_path(&source_dir, rel_path, &mut cache); - if paths.is_none() { - return None; - } + // Try mapping a partial path to a full path. + let rel_path = if check_extension(&rel_path, "java") { + map_partial_path(&file_to_paths, rel_path) + } else { + rel_path + }; - let (abs_path, rel_path) = paths.unwrap(); + // Get absolute path to the source file. + let paths = get_abs_path(&source_dir, rel_path); + if paths.is_none() { + return None; + } - if to_ignore_globset.is_match(&rel_path) { - return None; - } + let (abs_path, rel_path) = paths.unwrap(); - if ignore_not_existing && !abs_path.exists() { - return None; - } + if to_ignore_globset.is_match(&rel_path) { + return None; + } - // Always return results with '/'. - let rel_path = PathBuf::from(rel_path.to_str().unwrap().replace("\\", "/")); + if ignore_not_existing && !abs_path.exists() { + return None; + } - match filter_option { - Some(true) => { - if !is_covered(&result) { - return None; + // Always return results with '/'. + let rel_path = PathBuf::from(rel_path.to_str().unwrap().replace("\\", "/")); + + for filter in file_filter.create(&abs_path) { + match filter { + crate::FilterType::Both(number) => { + result.branches.remove(&number); + result.lines.remove(&number); + } + crate::FilterType::Line(number) => { + result.lines.remove(&number); + } + crate::FilterType::Branch(number) => { + result.branches.remove(&number); + } } } - Some(false) => { - if is_covered(&result) { - return None; + + match filter_option { + Some(true) => { + if !is_covered(&result) { + return None; + } } - } - None => (), - }; + Some(false) => { + if is_covered(&result) { + return None; + } + } + None => (), + }; + + Some((abs_path, rel_path, result)) + }); - Some((abs_path, rel_path, result)) - })) + Box::new( + results + .collect::>() + .into_iter(), + ) } #[cfg(test)] @@ -394,11 +405,31 @@ mod tests { }}; } + macro_rules! skipping_result { + () => {{ + let mut result = empty_result!(); + for i in 1..20 { + result.lines.insert(i, 1); + result.branches.insert(i, vec![true]); + } + result + }}; + } + #[test] fn test_rewrite_paths_basic() { let mut result_map: CovResultMap = FxHashMap::default(); result_map.insert("main.cpp".to_string(), empty_result!()); - let results = rewrite_paths(result_map, None, None, None, false, &mut Vec::new(), None); + let results = rewrite_paths( + result_map, + None, + None, + None, + false, + &mut Vec::new(), + None, + Default::default(), + ); let mut count = 0; for (abs_path, rel_path, result) in results { count += 1; @@ -425,6 +456,7 @@ mod tests { false, &mut Vec::new(), None, + Default::default(), ); let mut count = 0; for (abs_path, rel_path, result) in results { @@ -452,6 +484,7 @@ mod tests { false, &mut Vec::new(), None, + Default::default(), ); let mut count = 0; for (abs_path, rel_path, result) in results { @@ -479,6 +512,7 @@ mod tests { false, &mut Vec::new(), None, + Default::default(), ); let mut count = 0; for (abs_path, rel_path, result) in results { @@ -506,6 +540,7 @@ mod tests { false, &mut Vec::new(), None, + Default::default(), ); let mut count = 0; for (abs_path, rel_path, result) in results { @@ -523,7 +558,16 @@ mod tests { let mut result_map: CovResultMap = FxHashMap::default(); result_map.insert("tests/class/main.cpp".to_string(), empty_result!()); result_map.insert("tests/class/doesntexist.cpp".to_string(), empty_result!()); - let results = rewrite_paths(result_map, None, None, None, true, &mut Vec::new(), None); + let results = rewrite_paths( + result_map, + None, + None, + None, + true, + &mut Vec::new(), + None, + Default::default(), + ); let mut count = 0; for (abs_path, rel_path, result) in results { count += 1; @@ -545,7 +589,16 @@ mod tests { let mut result_map: CovResultMap = FxHashMap::default(); result_map.insert("tests\\class\\main.cpp".to_string(), empty_result!()); result_map.insert("tests\\class\\doesntexist.cpp".to_string(), empty_result!()); - let results = rewrite_paths(result_map, None, None, None, true, &mut Vec::new(), None); + let results = rewrite_paths( + result_map, + None, + None, + None, + true, + &mut Vec::new(), + None, + Default::default(), + ); let mut count = 0; for (abs_path, rel_path, result) in results { count += 1; @@ -571,6 +624,7 @@ mod tests { false, &mut vec!["mydir/*"], None, + Default::default(), ); let mut count = 0; for (abs_path, rel_path, result) in results { @@ -596,6 +650,7 @@ mod tests { false, &mut vec!["mydir/*"], None, + Default::default(), ); let mut count = 0; for (abs_path, rel_path, result) in results { @@ -625,6 +680,7 @@ mod tests { false, &mut ignore_dirs.clone(), None, + Default::default(), ); let mut count = 0; for (abs_path, rel_path, result) in results { @@ -656,6 +712,7 @@ mod tests { false, &mut ignore_dirs.clone(), None, + Default::default(), ); let mut count = 0; for (abs_path, rel_path, result) in results { @@ -681,7 +738,9 @@ mod tests { true, &mut Vec::new(), None, - ); + Default::default(), + ) + .any(|_| false); } #[cfg(unix)] @@ -698,6 +757,7 @@ mod tests { true, &mut Vec::new(), None, + Default::default(), ); let mut count = 0; for (abs_path, rel_path, result) in results { @@ -724,6 +784,7 @@ mod tests { true, &mut Vec::new(), None, + Default::default(), ); let mut count = 0; for (abs_path, rel_path, result) in results { @@ -750,6 +811,7 @@ mod tests { true, &mut Vec::new(), None, + Default::default(), ); let mut results: Vec<(PathBuf, PathBuf, CovResult)> = results.collect(); assert!(results.len() == 1); @@ -775,6 +837,7 @@ mod tests { true, &mut Vec::new(), None, + Default::default(), ); let mut results: Vec<(PathBuf, PathBuf, CovResult)> = results.collect(); assert!(results.len() == 1); @@ -799,6 +862,7 @@ mod tests { true, &mut Vec::new(), None, + Default::default(), ); let mut count = 0; for (abs_path, rel_path, result) in results { @@ -824,6 +888,7 @@ mod tests { true, &mut Vec::new(), None, + Default::default(), ); let mut count = 0; for (abs_path, rel_path, result) in results { @@ -852,6 +917,7 @@ mod tests { true, &mut Vec::new(), None, + Default::default(), ); let mut count = 0; for (abs_path, rel_path, result) in results { @@ -881,6 +947,7 @@ mod tests { true, &mut Vec::new(), None, + Default::default(), ); let mut count = 0; for (abs_path, rel_path, result) in results { @@ -906,6 +973,7 @@ mod tests { false, &mut Vec::new(), None, + Default::default(), ); let mut count = 0; for (abs_path, rel_path, result) in results { @@ -930,6 +998,7 @@ mod tests { false, &mut Vec::new(), None, + Default::default(), ); let mut count = 0; for (abs_path, rel_path, result) in results { @@ -957,6 +1026,7 @@ mod tests { true, &mut Vec::new(), None, + Default::default(), ); let mut count = 0; for (abs_path, rel_path, result) in results { @@ -985,6 +1055,7 @@ mod tests { true, &mut Vec::new(), None, + Default::default(), ); let mut count = 0; for (abs_path, rel_path, result) in results { @@ -1013,6 +1084,7 @@ mod tests { true, &mut Vec::new(), None, + Default::default(), ); let mut count = 0; for (abs_path, rel_path, result) in results { @@ -1044,6 +1116,7 @@ mod tests { true, &mut Vec::new(), None, + Default::default(), ); let mut count = 0; for (abs_path, rel_path, result) in results { @@ -1071,6 +1144,7 @@ mod tests { true, &mut Vec::new(), None, + Default::default(), ); let mut count = 0; for (abs_path, rel_path, result) in results { @@ -1098,6 +1172,7 @@ mod tests { true, &mut Vec::new(), None, + Default::default(), ); let mut count = 0; for (abs_path, rel_path, result) in results { @@ -1125,6 +1200,7 @@ mod tests { true, &mut Vec::new(), None, + Default::default(), ); let mut count = 0; for (abs_path, rel_path, result) in results { @@ -1153,6 +1229,7 @@ mod tests { true, &mut Vec::new(), None, + Default::default(), ); let mut count = 0; for (abs_path, rel_path, result) in results { @@ -1181,6 +1258,7 @@ mod tests { true, &mut Vec::new(), None, + Default::default(), ); let mut count = 0; for (abs_path, rel_path, result) in results { @@ -1206,6 +1284,7 @@ mod tests { false, &mut Vec::new(), Some(true), + Default::default(), ); let mut count = 0; for (abs_path, rel_path, result) in results { @@ -1230,6 +1309,7 @@ mod tests { false, &mut Vec::new(), Some(false), + Default::default(), ); let mut count = 0; for (abs_path, rel_path, result) in results { @@ -1275,4 +1355,88 @@ mod tests { assert!(!has_no_parent("/")); assert!(!has_no_parent("/foo/bar.oof")); } + + #[cfg(unix)] + #[test] + fn test_rewrite_paths_filter_lines_and_branches() { + let mut result_map: CovResultMap = FxHashMap::default(); + result_map.insert("test/java/skip.java".to_string(), skipping_result!()); + let results = rewrite_paths( + result_map, + None, + Some(canonicalize_path("test").unwrap()), + None, + true, + &mut Vec::new(), + None, + crate::FileFilter::new( + Some(regex::Regex::new("excluded line").unwrap()), + Some(regex::Regex::new("skip line start").unwrap()), + Some(regex::Regex::new("skip line end").unwrap()), + Some(regex::Regex::new("excluded branch").unwrap()), + Some(regex::Regex::new("skip branch start").unwrap()), + Some(regex::Regex::new("skip branch end").unwrap()), + ), + ); + let mut count = 0; + for (_, _, result) in results { + count += 1; + for inc in [1, 2, 3, 5, 8, 9, 10, 11, 12, 13, 14, 15, 16].iter() { + assert!(result.lines.contains_key(&inc)); + } + for inc in [4, 6, 7, 17, 18, 19, 20].iter() { + assert!(!result.lines.contains_key(&inc)); + } + + for inc in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 15, 16, 17].iter() { + assert!(result.branches.contains_key(&inc)); + } + for inc in [11, 13, 14, 18, 19, 20].iter() { + assert!(!result.branches.contains_key(&inc)); + } + } + assert_eq!(count, 1); + } + + #[cfg(windows)] + #[test] + fn test_rewrite_paths_filter_lines_and_branches() { + let mut result_map: CovResultMap = FxHashMap::default(); + result_map.insert("test\\java\\skip.java".to_string(), skipping_result!()); + let results = rewrite_paths( + result_map, + None, + Some(canonicalize_path("test").unwrap()), + None, + true, + &mut Vec::new(), + None, + crate::FileFilter::new( + Some(regex::Regex::new("excluded line").unwrap()), + Some(regex::Regex::new("skip line start").unwrap()), + Some(regex::Regex::new("skip line end").unwrap()), + Some(regex::Regex::new("excluded branch").unwrap()), + Some(regex::Regex::new("skip branch start").unwrap()), + Some(regex::Regex::new("skip branch end").unwrap()), + ), + ); + let mut count = 0; + for (_, _, result) in results { + count += 1; + for inc in [1, 2, 3, 5, 8, 9, 10, 11, 12, 13, 14, 15, 16].iter() { + assert!(result.lines.contains_key(&inc)); + } + for inc in [4, 6, 7, 17, 18, 19, 20].iter() { + assert!(!result.lines.contains_key(&inc)); + } + + for inc in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 15, 16, 17].iter() { + assert!(result.branches.contains_key(&inc)); + } + for inc in [11, 13, 14, 18, 19, 20].iter() { + assert!(!result.branches.contains_key(&inc)); + } + } + assert_eq!(count, 1); + } } diff --git a/test/.gitattributes b/test/.gitattributes new file mode 100644 index 000000000..3ea6f9bf0 --- /dev/null +++ b/test/.gitattributes @@ -0,0 +1 @@ +* -crlf -text binary diff --git a/test/java/skip.java b/test/java/skip.java new file mode 100644 index 000000000..deadac3ab --- /dev/null +++ b/test/java/skip.java @@ -0,0 +1,20 @@ +class Test { + void Foo() { + // included line + // excluded line + + // skip line start + // skipped line + // skip line end + + // included branch + // excluded branch + + // skip branch start + // skipped branch + // skip branch end + + // skip line start + // skip branch start + } +} \ No newline at end of file