Skip to content
Merged
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
30 changes: 12 additions & 18 deletions lib/undercover.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,12 @@
module Undercover
class Report
extend Forwardable

def_delegators :changeset, :validate

attr_reader :changeset,
:lcov,
:simplecov_resultset,
:coverage_adapter,
:results,
:code_dir,
:filter_set,
Expand All @@ -35,15 +36,16 @@ class Report
#
# @param changeset [Undercover::Changeset]
# @param opts [Undercover::Options]
def initialize(changeset, opts)
if opts.simplecov_resultset
@simplecov_resultset = SimplecovResultAdapter.parse(File.open(opts.simplecov_resultset), opts)
end
@lcov = LcovParser.parse(File.open(opts.lcov), opts) if opts.lcov
# @param coverage_adapter [Undercover::SimplecovResultAdapter|Undercover::LcovParser] pre-parsed coverage adapter
def initialize(changeset, opts, coverage_adapter)
@coverage_adapter = coverage_adapter

@code_dir = opts.path
@changeset = changeset
@filter_set = FilterSet.new(opts.glob_allow_filters, opts.glob_reject_filters)

ignored_files = coverage_adapter.ignored_files || []
@filter_set = FilterSet.new(opts.glob_allow_filters, opts.glob_reject_filters, ignored_files)
changeset.filter_with(filter_set)
@max_warnings_limit = opts.max_warnings_limit
@loaded_files = {}
@results = {}
Expand Down Expand Up @@ -106,29 +108,21 @@ def inspect

attr_reader :loaded_files

# rubocop:disable Metrics/MethodLength, Metrics/AbcSize
# rubocop:disable Metrics/AbcSize
def load_and_parse_file(filepath)
key = filepath.gsub(/^\.\//, '')
return if loaded_files[key]
return unless include_file?(filepath)

root_ast = Imagen::Node::Root.new.build_from_file(
File.join(code_dir, filepath)
)
return if root_ast.children.empty?

# lcov will be deprecated at some point and we'll be able to refactor harder
coverage = simplecov_resultset || lcov

loaded_files[key] = []
root_ast.find_all(->(node) { !node.is_a?(Imagen::Node::Root) }).each do |imagen_node|
loaded_files[key] << Result.new(imagen_node, coverage, filepath)
loaded_files[key] << Result.new(imagen_node, coverage_adapter, filepath)
end
end
# rubocop:enable Metrics/MethodLength, Metrics/AbcSize

def include_file?(filepath)
filter_set.include?(filepath)
end
# rubocop:enable Metrics/AbcSize
end
end
4 changes: 4 additions & 0 deletions lib/undercover/changeset.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ def validate(lcov_report_path)
:stale_coverage if last_modified > File.mtime(lcov_report_path)
end

def filter_with(filter_set)
@filter_set = filter_set
end

private

# Diffs `head` or `head` + `compare_base` (if exists),
Expand Down
18 changes: 11 additions & 7 deletions lib/undercover/cli.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@

module Undercover
module CLI
# TODO: Report calls >parser< for each file instead of
# traversing the whole project at first!

WARNINGS_TO_S = {
stale_coverage: Rainbow('🚨 WARNING: Coverage data is older than your ' \
'latest changes and results might be incomplete. ' \
Expand All @@ -21,12 +18,20 @@ def self.run(args)
run_report(opts)
end

def self.run_report(opts)
def self.run_report(opts) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
coverage_path = opts.simplecov_resultset || opts.lcov
return handle_missing_coverage_path(opts) if coverage_path.nil?
return handle_missing_file(coverage_path) unless File.exist?(coverage_path)

report = Undercover::Report.new(changeset(opts), opts).build
simplecov_adapter = if opts.simplecov_resultset
SimplecovResultAdapter.parse(File.open(opts.simplecov_resultset), opts)
else
# TODO: lcov will be deprecated end of 2025 and we'll be able to refactor harder
LcovParser.parse(File.open(opts.lcov), opts)
end

changeset_obj = changeset(opts)
report = Undercover::Report.new(changeset_obj, opts, simplecov_adapter).build
handle_report_validation(report, coverage_path)
end

Expand Down Expand Up @@ -63,8 +68,7 @@ def self.syntax_version(version)

def self.changeset(opts)
git_dir = File.join(opts.path, opts.git_dir)
filter_set = Undercover::FilterSet.new(opts.glob_allow_filters, opts.glob_reject_filters)
Undercover::Changeset.new(git_dir, opts.compare, filter_set)
Undercover::Changeset.new(git_dir, opts.compare)
end
end
end
24 changes: 22 additions & 2 deletions lib/undercover/filter_set.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,36 @@

module Undercover
class FilterSet
attr_reader :allow_filters, :reject_filters
attr_reader :allow_filters, :reject_filters, :simplecov_filters

def initialize(allow_filters, reject_filters)
def initialize(allow_filters, reject_filters, simplecov_filters)
@allow_filters = allow_filters || []
@reject_filters = reject_filters || []
@simplecov_filters = simplecov_filters || []
end

def include?(filepath)
fnmatch = proc { |glob| File.fnmatch(glob, filepath, File::FNM_EXTGLOB) }

# Check if file was ignored by SimpleCov filters
return false if ignored_by_simplecov?(filepath)

# Apply Undercover's own filters
allow_filters.any?(fnmatch) && reject_filters.none?(fnmatch)
end

private

def ignored_by_simplecov?(filepath)
simplecov_filters.any? do |filter|
if filter[:string]
filepath.include?(filter[:string])
elsif filter[:regex]
filepath.match?(Regexp.new(filter[:regex]))
elsif filter[:file]
filepath == filter[:file]
end
end
end
end
end
5 changes: 5 additions & 0 deletions lib/undercover/lcov_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ def skipped?(_filepath, _line_no)
false
end

def ignored_files
# supported by SimplecovResultAdapter only
[]
end

private

# rubocop:disable Metrics/MethodLength, Style/SpecialGlobalVars, Metrics/AbcSize
Expand Down
52 changes: 52 additions & 0 deletions lib/undercover/simplecov_formatter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,60 @@ def export_path
end
end

module SimpleCov
class << self
attr_accessor :filter_definitions

alias filtered_uncached filtered

def filtered(files)
@filter_definitions ||= extract_filter_definitions
original_files = files.dup
filtered_uncached(files).tap do |filtered_files|
filtered_file_paths = (original_files.map(&:filename) - filtered_files.map(&:filename))
filtered_file_paths.each do |file|
relative_path = file.delete_prefix("#{SimpleCov.root}/")
@filter_definitions << {file: relative_path} unless covered_by_serializable_filters?(relative_path)
end
end
end

private

def extract_filter_definitions
filter_array = []

filters.each do |filter|
case filter
when SimpleCov::StringFilter
filter_array << {string: filter.filter_argument}
when SimpleCov::RegexFilter
filter_array << {regex: filter.filter_argument.source}
end
end

filter_array
end

def covered_by_serializable_filters?(relative_path)
@filter_definitions.any? do |filter_def|
if filter_def[:string]
relative_path.include?(filter_def[:string])
elsif filter_def[:regex]
relative_path.match?(Regexp.new(filter_def[:regex]))
end
end
end
end
end

module Undercover
class ResultHashFormatterWithRoot < SimpleCovJSONFormatter::ResultHashFormatter
def format
formatted_result[:meta] = {timestamp: @result.created_at.to_i}
format_files
add_undercover_meta_fields
add_ignored_files
formatted_result
end

Expand All @@ -30,6 +78,10 @@ def add_undercover_meta_fields
end
end

def add_ignored_files
formatted_result[:meta][:ignored_files] = SimpleCov.filter_definitions || []
end

# format_files uses relative path as keys, as opposed to the superclass method
def format_files
formatted_result[:coverage] ||= {}
Expand Down
4 changes: 4 additions & 0 deletions lib/undercover/simplecov_result_adapter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ def skipped?(filepath, line_no)
def total_coverage; end
def total_branch_coverage; end

def ignored_files
@ignored_files ||= simplecov_result.dig('meta', 'ignored_files') || []
end

private

def find_file(filepath)
Expand Down
4 changes: 2 additions & 2 deletions spec/changeset_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@

describe 'filtering' do
it 'filters files using FilterSet in each_changed_line' do
filter_set = Undercover::FilterSet.new(['*.rb'], ['*_spec.rb'])
filter_set = Undercover::FilterSet.new(['*.rb'], ['*_spec.rb'], [])
changeset = Undercover::Changeset.new('spec/fixtures/test.git', 'master', filter_set)

yielded_files = []
Expand All @@ -75,7 +75,7 @@
end

it 'filters files using FilterSet with brace expansion' do
filter_set = Undercover::FilterSet.new(['*.{rb,js}'], ['*_spec.rb'])
filter_set = Undercover::FilterSet.new(['*.{rb,js}'], ['*_spec.rb'], [])
changeset = Undercover::Changeset.new('spec/fixtures/test.git', 'master', filter_set)

yielded_files = []
Expand Down
Loading