undercover 0.7.4 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fb439eb426932d432434081cf9e17cce9b19b90a85c71b6a4c173d20cceb9392
4
- data.tar.gz: a2703dd7d19a18f943dd3fdaebc34faf23cc995b1cc94b673756f1b5142cf046
3
+ metadata.gz: 522b27800c01ddd2ed70108b080f709d4c4ec12d338c4c8ba204b54cb4c464bc
4
+ data.tar.gz: 8208f484eb86247a02b0c58d5007ce6a80e29ce125a2a39516e5ee4b8acdd43b
5
5
  SHA512:
6
- metadata.gz: 034ff6bd2826c4826b06780bd874e28c04c8fbcb0fcde85a5c0a1dd8901e4f5d2ebfe6c149b828c0fcf28fd4064f14ef122f69c763f59170b62d99941281d44b
7
- data.tar.gz: 114c9a9873fb75c17a3bfe79612bfe8d2891cd2d2b2dd68f8253d49f3529def86bca607d4774d1f113ade2fd8eb2d6d5d55e53d4b47b4a5464c3a6075eb2da3b
6
+ metadata.gz: a65331bb82a3a31845fea782519a4254449264b914c26d8c54277a00a9ae4653bb183aa5bdc1f66982e991ccadea1b8a066553d616bbcc2c033bcc2c0550f0c0
7
+ data.tar.gz: d60a04cbd89cda50b486f850e68a0843aae0db0da5acc93acab565ba93e9eaf7090c14898140b0c120cd25ddd0164a3fbcd544f794859e3c466eccaa62a3afb7
@@ -7,7 +7,7 @@ jobs:
7
7
  matrix:
8
8
  ruby: ['3.4', '3.0']
9
9
  steps:
10
- - uses: actions/checkout@v4
10
+ - uses: actions/checkout@v5
11
11
  with:
12
12
  fetch-depth: 0 # fetch all since test fixtures depend on history
13
13
  - name: Set up Ruby ${{ matrix.ruby }}
@@ -31,7 +31,7 @@ jobs:
31
31
  runs-on: ubuntu-latest
32
32
  needs: build
33
33
  steps:
34
- - uses: actions/download-artifact@v4
34
+ - uses: actions/download-artifact@v5
35
35
  with:
36
36
  name: undercover-3.4-coverage
37
37
  - name: Upload coverage
data/.gitignore CHANGED
@@ -10,3 +10,6 @@ Gemfile.lock
10
10
 
11
11
  # rspec failure tracking
12
12
  .rspec_status
13
+
14
+ # branch ignore fixture coverage metadata
15
+ spec/fixtures/branch_ignored_coverage/.*
data/.rubocop.yml CHANGED
@@ -25,10 +25,10 @@ Metrics/BlockLength:
25
25
  - spec/**/*
26
26
 
27
27
  Metrics/CyclomaticComplexity:
28
- Max: 10
28
+ Max: 12
29
29
 
30
30
  Metrics/PerceivedComplexity:
31
- Max: 10
31
+ Max: 12
32
32
 
33
33
  Style/HashEachMethods:
34
34
  Enabled: true
data/CHANGELOG.md CHANGED
@@ -6,6 +6,21 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ # [0.8.1] - 2025-09-27
10
+
11
+ ### Fixed
12
+ - Fix missing require in the simplecov formatter causing potential load errors if SimpleCov wasn't required first
13
+ - Fix SimpleCov filters ([#239](https://github.com/grodowski/undercover/pull/239) by [@loadkpi](https://github.com/loadkpi))
14
+
15
+ # [0.8.0] - 2025-08-28
16
+
17
+ ### Added
18
+ - Filter out files ignored by simplecov using `add_filter` ([#234](https://github.com/grodowski/undercover/pull/234))
19
+ - Add LF and LH parsing to LcovParser for simplecov-lcov 0.9 compatibility
20
+
21
+ ### Fixed
22
+ - Fix an `Undercover::Result` edge case causing errors with ignored branches on uningnored lines
23
+
9
24
  # [0.7.4] - 2025-07-13
10
25
 
11
26
  ### Fixed
@@ -185,7 +200,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
185
200
  ### Added
186
201
  - First release of `undercover` 🎉
187
202
 
188
- [Unreleased]: https://github.com/grodowski/undercover/compare/v0.7.4...HEAD
203
+ [Unreleased]: https://github.com/grodowski/undercover/compare/v0.8.0...HEAD
204
+ [0.8.0]: https://github.com/grodowski/undercover/compare/v0.7.4...v0.8.0
189
205
  [0.7.4]: https://github.com/grodowski/undercover/compare/v0.7.3...v0.7.4
190
206
  [0.7.3]: https://github.com/grodowski/undercover/compare/v0.7.2...v0.7.3
191
207
  [0.7.2]: https://github.com/grodowski/undercover/compare/v0.7.1...v0.7.2
@@ -49,6 +49,10 @@ module Undercover
49
49
  :stale_coverage if last_modified > File.mtime(lcov_report_path)
50
50
  end
51
51
 
52
+ def filter_with(filter_set)
53
+ @filter_set = filter_set
54
+ end
55
+
52
56
  private
53
57
 
54
58
  # Diffs `head` or `head` + `compare_base` (if exists),
@@ -5,9 +5,6 @@ require 'rainbow'
5
5
 
6
6
  module Undercover
7
7
  module CLI
8
- # TODO: Report calls >parser< for each file instead of
9
- # traversing the whole project at first!
10
-
11
8
  WARNINGS_TO_S = {
12
9
  stale_coverage: Rainbow('🚨 WARNING: Coverage data is older than your ' \
13
10
  'latest changes and results might be incomplete. ' \
@@ -21,12 +18,20 @@ module Undercover
21
18
  run_report(opts)
22
19
  end
23
20
 
24
- def self.run_report(opts)
21
+ def self.run_report(opts) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
25
22
  coverage_path = opts.simplecov_resultset || opts.lcov
26
23
  return handle_missing_coverage_path(opts) if coverage_path.nil?
27
24
  return handle_missing_file(coverage_path) unless File.exist?(coverage_path)
28
25
 
29
- report = Undercover::Report.new(changeset(opts), opts).build
26
+ simplecov_adapter = if opts.simplecov_resultset
27
+ SimplecovResultAdapter.parse(File.open(opts.simplecov_resultset), opts)
28
+ else
29
+ # TODO: lcov will be deprecated end of 2025 and we'll be able to refactor harder
30
+ LcovParser.parse(File.open(opts.lcov), opts)
31
+ end
32
+
33
+ changeset_obj = changeset(opts)
34
+ report = Undercover::Report.new(changeset_obj, opts, simplecov_adapter).build
30
35
  handle_report_validation(report, coverage_path)
31
36
  end
32
37
 
@@ -63,8 +68,7 @@ module Undercover
63
68
 
64
69
  def self.changeset(opts)
65
70
  git_dir = File.join(opts.path, opts.git_dir)
66
- filter_set = Undercover::FilterSet.new(opts.glob_allow_filters, opts.glob_reject_filters)
67
- Undercover::Changeset.new(git_dir, opts.compare, filter_set)
71
+ Undercover::Changeset.new(git_dir, opts.compare)
68
72
  end
69
73
  end
70
74
  end
@@ -2,16 +2,37 @@
2
2
 
3
3
  module Undercover
4
4
  class FilterSet
5
- attr_reader :allow_filters, :reject_filters
5
+ attr_reader :allow_filters, :reject_filters, :simplecov_filters
6
6
 
7
- def initialize(allow_filters, reject_filters)
7
+ def initialize(allow_filters, reject_filters, simplecov_filters)
8
8
  @allow_filters = allow_filters || []
9
9
  @reject_filters = reject_filters || []
10
+ @simplecov_filters = simplecov_filters || []
10
11
  end
11
12
 
12
13
  def include?(filepath)
13
14
  fnmatch = proc { |glob| File.fnmatch(glob, filepath, File::FNM_EXTGLOB) }
15
+
16
+ # Check if file was ignored by SimpleCov filters
17
+ return false if ignored_by_simplecov?(filepath)
18
+
19
+ # Apply Undercover's own filters
14
20
  allow_filters.any?(fnmatch) && reject_filters.none?(fnmatch)
15
21
  end
22
+
23
+ private
24
+
25
+ def ignored_by_simplecov?(filepath)
26
+ simplecov_filters.any? do |filter|
27
+ filter = filter.transform_keys(&:to_sym)
28
+ if filter[:string]
29
+ filepath.include?(filter[:string])
30
+ elsif filter[:regex]
31
+ filepath.match?(Regexp.new(filter[:regex]))
32
+ elsif filter[:file]
33
+ filepath == filter[:file]
34
+ end
35
+ end
36
+ end
16
37
  end
17
38
  end
@@ -57,6 +57,11 @@ module Undercover
57
57
  false
58
58
  end
59
59
 
60
+ def ignored_files
61
+ # supported by SimplecovResultAdapter only
62
+ []
63
+ end
64
+
60
65
  private
61
66
 
62
67
  # rubocop:disable Metrics/MethodLength, Style/SpecialGlobalVars, Metrics/AbcSize
@@ -79,6 +84,7 @@ module Undercover
79
84
  source_files[@current_filename] << [line_no.to_i, block_no.to_i, branch_no.to_i, covered.to_i]
80
85
  when /^end_of_record$/, /^$/
81
86
  @current_filename = nil
87
+ when /^LF:(\d+)|LH:(\d+)/ # lines found, lines hit; no-op
82
88
  else
83
89
  raise LcovParseError, "could not recognise '#{line}' as valid LCOV"
84
90
  end
@@ -43,7 +43,7 @@ module Undercover
43
43
 
44
44
  # check branch coverage for line_no
45
45
  coverage.each do |ln, _block, _branch, cov|
46
- return true if ln == line_no && cov && cov.zero?
46
+ return true if ln == line_no && cov && cov != 'ignored' && cov.zero?
47
47
  end
48
48
 
49
49
  # check line coverage for line_no
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'simplecov'
3
4
  require 'simplecov_json_formatter'
4
5
 
5
6
  # Patch ResultExporter to allow setting a custom export_path
@@ -13,12 +14,60 @@ module SimpleCovJSONFormatter
13
14
  end
14
15
  end
15
16
 
17
+ module SimpleCov
18
+ class << self
19
+ attr_accessor :filter_definitions
20
+
21
+ alias filtered_uncached filtered
22
+
23
+ def filtered(files)
24
+ @filter_definitions ||= extract_filter_definitions
25
+ original_files = files.dup
26
+ filtered_uncached(files).tap do |filtered_files|
27
+ filtered_file_paths = (original_files.map(&:filename) - filtered_files.map(&:filename))
28
+ filtered_file_paths.each do |file|
29
+ relative_path = file.delete_prefix("#{SimpleCov.root}/")
30
+ @filter_definitions << {file: relative_path} unless covered_by_serializable_filters?(relative_path)
31
+ end
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ def extract_filter_definitions
38
+ filter_array = []
39
+
40
+ filters.each do |filter|
41
+ case filter
42
+ when SimpleCov::StringFilter
43
+ filter_array << {string: filter.filter_argument}
44
+ when SimpleCov::RegexFilter
45
+ filter_array << {regex: filter.filter_argument.source}
46
+ end
47
+ end
48
+
49
+ filter_array
50
+ end
51
+
52
+ def covered_by_serializable_filters?(relative_path)
53
+ @filter_definitions.any? do |filter_def|
54
+ if filter_def[:string]
55
+ relative_path.include?(filter_def[:string])
56
+ elsif filter_def[:regex]
57
+ relative_path.match?(Regexp.new(filter_def[:regex]))
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+
16
64
  module Undercover
17
65
  class ResultHashFormatterWithRoot < SimpleCovJSONFormatter::ResultHashFormatter
18
66
  def format
19
67
  formatted_result[:meta] = {timestamp: @result.created_at.to_i}
20
68
  format_files
21
69
  add_undercover_meta_fields
70
+ add_ignored_files
22
71
  formatted_result
23
72
  end
24
73
 
@@ -30,6 +79,10 @@ module Undercover
30
79
  end
31
80
  end
32
81
 
82
+ def add_ignored_files
83
+ formatted_result[:meta][:ignored_files] = SimpleCov.filter_definitions || []
84
+ end
85
+
33
86
  # format_files uses relative path as keys, as opposed to the superclass method
34
87
  def format_files
35
88
  formatted_result[:coverage] ||= {}
@@ -56,6 +56,10 @@ module Undercover
56
56
  def total_coverage; end
57
57
  def total_branch_coverage; end
58
58
 
59
+ def ignored_files
60
+ @ignored_files ||= simplecov_result.dig('meta', 'ignored_files') || []
61
+ end
62
+
59
63
  private
60
64
 
61
65
  def find_file(filepath)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Undercover
4
- VERSION = '0.7.4'
4
+ VERSION = '0.8.1'
5
5
  end
data/lib/undercover.rb CHANGED
@@ -21,11 +21,12 @@ require 'undercover/version'
21
21
  module Undercover
22
22
  class Report
23
23
  extend Forwardable
24
+
24
25
  def_delegators :changeset, :validate
25
26
 
26
27
  attr_reader :changeset,
27
28
  :lcov,
28
- :simplecov_resultset,
29
+ :coverage_adapter,
29
30
  :results,
30
31
  :code_dir,
31
32
  :filter_set,
@@ -35,15 +36,16 @@ module Undercover
35
36
  #
36
37
  # @param changeset [Undercover::Changeset]
37
38
  # @param opts [Undercover::Options]
38
- def initialize(changeset, opts)
39
- if opts.simplecov_resultset
40
- @simplecov_resultset = SimplecovResultAdapter.parse(File.open(opts.simplecov_resultset), opts)
41
- end
42
- @lcov = LcovParser.parse(File.open(opts.lcov), opts) if opts.lcov
39
+ # @param coverage_adapter [Undercover::SimplecovResultAdapter|Undercover::LcovParser] pre-parsed coverage adapter
40
+ def initialize(changeset, opts, coverage_adapter)
41
+ @coverage_adapter = coverage_adapter
43
42
 
44
43
  @code_dir = opts.path
45
44
  @changeset = changeset
46
- @filter_set = FilterSet.new(opts.glob_allow_filters, opts.glob_reject_filters)
45
+
46
+ ignored_files = coverage_adapter.ignored_files || []
47
+ @filter_set = FilterSet.new(opts.glob_allow_filters, opts.glob_reject_filters, ignored_files)
48
+ changeset.filter_with(filter_set)
47
49
  @max_warnings_limit = opts.max_warnings_limit
48
50
  @loaded_files = {}
49
51
  @results = {}
@@ -106,29 +108,21 @@ module Undercover
106
108
 
107
109
  attr_reader :loaded_files
108
110
 
109
- # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
111
+ # rubocop:disable Metrics/AbcSize
110
112
  def load_and_parse_file(filepath)
111
113
  key = filepath.gsub(/^\.\//, '')
112
114
  return if loaded_files[key]
113
- return unless include_file?(filepath)
114
115
 
115
116
  root_ast = Imagen::Node::Root.new.build_from_file(
116
117
  File.join(code_dir, filepath)
117
118
  )
118
119
  return if root_ast.children.empty?
119
120
 
120
- # lcov will be deprecated at some point and we'll be able to refactor harder
121
- coverage = simplecov_resultset || lcov
122
-
123
121
  loaded_files[key] = []
124
122
  root_ast.find_all(->(node) { !node.is_a?(Imagen::Node::Root) }).each do |imagen_node|
125
- loaded_files[key] << Result.new(imagen_node, coverage, filepath)
123
+ loaded_files[key] << Result.new(imagen_node, coverage_adapter, filepath)
126
124
  end
127
125
  end
128
- # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
129
-
130
- def include_file?(filepath)
131
- filter_set.include?(filepath)
132
- end
126
+ # rubocop:enable Metrics/AbcSize
133
127
  end
134
128
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: undercover
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.4
4
+ version: 0.8.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jan Grodowski
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-07-13 00:00:00.000000000 Z
10
+ date: 2025-09-27 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: base64