Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Skipping files and lines. #218

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ HTTP = "cd3eb016-35fb-5094-929b-558a96fad6f3"
JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
LibGit2 = "76f85450-5226-5b5a-8eaa-529ad045b433"
MbedTLS = "739be429-bea8-5141-9913-cc70e7f3736d"
YAML = "ddb6d928-2868-570f-bddf-ab3f9cf99eb6"

[compat]
HTTP = ">=0.8.0"
Expand Down
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,19 @@ For AppVeyor, add this to `.appveyor.yml`:
environment:
DISABLE_AMEND_COVERAGE_FROM_SRC: yes

To help the coverage tracking, one can put a `.coverage.yml` in the source code
folder to exclude particular files, or remove particular lines of code from the
coverage statistic. The file should look like this:
```
# exclude files in the directory with these names exclude_files:
- file_to_be_excluded.jl
- another_file_to_excluded.jl

# exclude any lines that match any of the following regexes exclude_patterns:
- "[NO COVERAGE]"
```
The exclusions are applied to all files in the folder as well as sub folders.

## Some Julia packages using Coverage.jl

*Pull requests to add your package welcome (or open an issue)*
Expand Down
98 changes: 86 additions & 12 deletions src/Coverage.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#######################################################################
module Coverage
using LibGit2
import YAML

export process_folder, process_file
export clean_folder, clean_file
Expand Down Expand Up @@ -136,10 +137,9 @@ module Coverage
@info "Coverage.process_cov: processing $file"
coverage = CovCount[]
for line in eachline(file)
# Columns 1:9 contain the coverage count
cov_segment = line[1:9]
# If coverage is NA, there will be a dash
push!(coverage, cov_segment[9] == '-' ? nothing : parse(Int, cov_segment))
# Columns 1:9 contain the coverage count
push!(coverage, line[9] == '-' ? nothing : parse(Int, line[1:9]))
end
full_coverage = merge_coverage_counts(full_coverage, coverage)
end
Expand All @@ -156,8 +156,8 @@ module Coverage
This function takes an existing result and updates the coverage vector
in-place to mark source lines that may be inside a function.
"""
amend_coverage_from_src!(coverage::Vector{CovCount}, srcname) = amend_coverage_from_src!(FileCoverage(srcname, read(srcname, String), coverage))
function amend_coverage_from_src!(fc::FileCoverage)
amend_coverage_from_src!(coverage::Vector{CovCount}, srcname, excluded_line_patterns::Vector{Regex}=Regex[]) = amend_coverage_from_src!(FileCoverage(srcname, read(srcname, String), coverage), excluded_line_patterns)
function amend_coverage_from_src!(fc::FileCoverage, excluded_line_patterns::Vector{Regex}=Regex[])
# The code coverage results produced by Julia itself report some
# lines as "null" (cannot be run), when they could have been run
# but were never compiled (thus should be 0).
Expand Down Expand Up @@ -201,24 +201,38 @@ module Coverage
end
end
end

# check for excluded lines
l = 1
let io = IOBuffer(content)
while !eof(io)
line = readline(io)
# Verify that there is no line exclusion tag
if any(pat -> occursin(pat, line), excluded_line_patterns)
@debug "removing with regex $excluded_line_patterns line $l: $line"
coverage[l] = nothing
end
l += 1
end
end
nothing
end

"""
process_file(filename[, folder]) -> FileCoverage
process_file(filename[, folder], excluded_line_patterns) -> FileCoverage

Given a .jl file and its containing folder, produce a corresponding
`FileCoverage` instance from the source and matching coverage files. If the
folder is not given it is extracted from the filename.
"""
function process_file end

function process_file(filename, folder)
function process_file(filename, folder, excluded_line_patterns::Vector{Regex}=Regex[])
@info "Coverage.process_file: Detecting coverage for $filename"
coverage = process_cov(filename, folder)
fc = FileCoverage(filename, read(filename, String), coverage)
if get(ENV, "DISABLE_AMEND_COVERAGE_FROM_SRC", "no") != "yes"
amend_coverage_from_src!(fc)
amend_coverage_from_src!(fc, excluded_line_patterns)
end
return fc
end
Expand All @@ -231,28 +245,88 @@ module Coverage
statistics for all the files contained within. Will recursively traverse
child folders. Default folder is "src", which is useful for the primary case
where Coverage is called from the root directory of a package.

In case there is a `.coverage.yml` present within `folder`, the indicated
file and line exclusion tags will be used.
"""
function process_folder(folder="src")
@info "Coverage.process_folder: Searching $folder for .jl files..."
# get coverage file
excl_file = joinpath(folder, ".coverage.yml")
excluded_files, excluded_line_patterns = get_exclusions(excl_file)
return process_folder(folder, excluded_files, excluded_line_patterns)
end
function process_folder(folder, excluded_files, excluded_line_patterns)
@info """Coverage.process_folder: Searching $folder for .jl files..."""
source_files = FileCoverage[]
files = readdir(folder)

for file in files
fullfile = joinpath(folder, file)
if file in excluded_files
@info "Coverage.process_folder: Skipping $file, in exclusion list"
continue
end

fullfile = joinpath(folder,file)
if isfile(fullfile)
# Is it a Julia file?
if splitext(fullfile)[2] == ".jl"
push!(source_files, process_file(fullfile, folder))
push!(source_files, process_file(fullfile, folder, excluded_line_patterns))
else
@debug "Coverage.process_folder: Skipping $file, not a .jl file"
end
elseif isdir(fullfile)
# If it is a folder, recursively traverse
append!(source_files, process_folder(fullfile))
# pass on the same excluded_files & excluded_line_patterns from root
append!(source_files, process_folder(fullfile, excluded_files, excluded_line_patterns))
end
end
return source_files
end

"""
get_exclusions(file::AbstractString) -> (excluded_files, excluded_line_patterns)

Parse the provided file as a coverage exclusions file (named
`.coverage.yml`). This file should have a format like the following:
```
# exclude files in the directory with these names
exclude_files:
- foo.jl
- bar.jl

# exclude any lines that match any of the following regexes
exclude_patterns:
- "[NO COVERAGE]"
zundertj marked this conversation as resolved.
Show resolved Hide resolved
```
Note that both `exclude_files` and `exclude_patterns` should be lists.
"""
function get_exclusions(file)
excluded_files = Set{String}()
excluded_line_patterns = Regex[]
if isfile(file)
# read file
config = YAML.load_file(file)

# if exclude_files entry is present, try to parse
if haskey(config, "exclude_files")
if !isa(config["exclude_files"], Array{String})
error("'exclude_files' in $file must be a list of strings")
end
excluded_files = config["exclude_files"]
end

# if exclude_patterns entry is present, try to parse
if haskey(config, "exclude_patterns")
if !isa(config["exclude_patterns"], Array{String})
error("'exclude_patterns' in $file must be a list of strings")
end
excluded_line_patterns = Regex.(config["exclude_patterns"])
end
end

return excluded_files, excluded_line_patterns
end

# matches julia coverage files with and without the PID
iscovfile(filename) = occursin(r"\.jl\.?[0-9]*\.cov$", filename)
# matches a coverage file for the given sourcefile. They can be full paths
Expand Down
Loading