undercover 0.6.4 → 0.6.6

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: 2a3601cf9aa4d26cee9c04f12b44273f8a67e0e8584b020e4e9bb763e03bf24e
4
- data.tar.gz: 8198c30de2bc16734a1575b7aa0213c25ccafe120848dad9c24257d688b7bca9
3
+ metadata.gz: 6d4fbea540abf1922afa393179a770aa7468bb030085495c4be1daad89bafaaa
4
+ data.tar.gz: a815eb262c6b848fe21420d1626ba4e1b43dadf025e095380acb7b0fb6fe5e07
5
5
  SHA512:
6
- metadata.gz: 6e6a60fad9a478c37895ae24035022a9acf86f789ce0d798194b6d13b82c7db52c9de6590a9a9d3b8a84d304f220a30ef2c8e5226bab6fa58cea96f09c3856ca
7
- data.tar.gz: fd1ca272d59b29debe708bd2f8df2bf867dccc60484416002dbf004d21ea83370e408e7703098f8d6e2ff0345d2234a7e662f94254a7dcc339f7370bae9f9885
6
+ metadata.gz: 625346ca6e5b042b1658e58e82be45e4c5a5528843f4a7112b079fd244b99282965e96f47f67ff54cd341e2ce7e8d2d16c559bfbbda9d5483ed44a6ed847c814
7
+ data.tar.gz: 307d626ae101ae1d7136f2fa6247f317b9a3bd072c7f0da936f32dc0d7910ada7b9c9f8b04fe66ae2cc79fe0387cc0575f303af06bf67e729fd6e9174f3aacd4
data/CHANGELOG.md CHANGED
@@ -6,6 +6,15 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ # [0.6.6] - 2025-07-01
10
+
11
+ - Bugfix in `max_warnings_limit` following ([#229](https://github.com/grodowski/undercover/pull/229))
12
+
13
+ # [0.6.5] - 2025-07-01
14
+
15
+ ### Fixed
16
+ - Improved performance for large PRs with lazy diff enumeration ([#229](https://github.com/grodowski/undercover/pull/229))
17
+
9
18
  # [0.6.4] - 2025-03-29
10
19
 
11
20
  ### Fixed
@@ -148,9 +157,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
148
157
  ### Added
149
158
  - First release of `undercover` 🎉
150
159
 
151
- [Unreleased]: https://github.com/grodowski/undercover/compare/v0.6.3...HEAD
152
- [0.6.3]:https://github.com/grodowski/undercover/compare/v0.6.3...v0.6.0
153
- [0.6.0]: https://github.com/grodowski/undercover/compare/v0.6.0...v0.5.0
160
+ [Unreleased]: https://github.com/grodowski/undercover/compare/v0.6.6...HEAD
161
+ [0.6.6]: https://github.com/grodowski/undercover/compare/v0.6.5...0.6.6
162
+ [0.6.5]: https://github.com/grodowski/undercover/compare/v0.6.4...0.6.5
163
+ [0.6.4]: https://github.com/grodowski/undercover/compare/v0.6.3...v0.6.4
164
+ [0.6.3]: https://github.com/grodowski/undercover/compare/v0.6.0...v0.6.3
165
+ [0.6.0]: https://github.com/grodowski/undercover/compare/v0.5.0...v0.6.0
154
166
  [0.5.0]: https://github.com/grodowski/undercover/compare/v0.4.7...v0.5.0
155
167
  [0.4.7]: https://github.com/grodowski/undercover/compare/v0.4.6...v0.4.7
156
168
  [0.4.6]: https://github.com/grodowski/undercover/compare/v0.4.5...v0.4.6
data/README.md CHANGED
@@ -9,7 +9,6 @@ Works with any Ruby CI pipeline as well as locally as a CLI.
9
9
 
10
10
 
11
11
  [![Build Status](https://github.com/grodowski/undercover/actions/workflows/ruby.yml/badge.svg)](https://github.com/grodowski/undercover/actions)
12
- [![Maintainability](https://api.codeclimate.com/v1/badges/b403feed68a18c072ec5/maintainability)](https://codeclimate.com/github/grodowski/undercover/maintainability)
13
12
  ![Downloads](https://img.shields.io/gem/dt/undercover)
14
13
 
15
14
  A sample output of `undercover` ran before a commit may look like this:
@@ -110,6 +109,7 @@ Usage: undercover [options]
110
109
  -g, --git-dir dir Override `.git` with a custom directory
111
110
  -c, --compare ref Generate coverage warnings for all changes after `ref`
112
111
  -r, --ruby-syntax ver Ruby syntax version, one of: current, ruby18, ruby19, ruby20, ruby21, ruby22, ruby23, ruby24, ruby25, ruby26, ruby30, ruby31, ruby32, ruby33
112
+ -w, --max-warnings limit Maximum number of warnings to generate before stopping analysis. Useful as a performance improvement for large diffs.
113
113
  -f, --include-files globs Include files matching specified glob patterns (comma separated). Defaults to '*.rb,*.rake,*.ru,Rakefile'
114
114
  -x, --exclude-files globs Skip files matching specified glob patterns (comma separated). Empty by default.
115
115
  -h, --help Prints this help
@@ -8,31 +8,12 @@ module Undercover
8
8
  class Changeset
9
9
  T_ZERO = Time.strptime('0', '%s').freeze
10
10
 
11
- extend Forwardable
12
- include Enumerable
13
-
14
- attr_reader :files
15
-
16
- def_delegators :files, :each, :<=>
17
-
18
- def initialize(dir, compare_base = nil)
11
+ def initialize(dir, compare_base = nil, filter_set = nil)
19
12
  @dir = dir
20
13
  @repo = Rugged::Repository.new(dir)
21
14
  @repo.workdir = Pathname.new(dir).dirname.to_s # TODO: can replace?
22
15
  @compare_base = compare_base
23
- @files = {}
24
- end
25
-
26
- def update
27
- full_diff.each_patch do |patch|
28
- filepath = patch.delta.new_file[:path]
29
- line_nums = patch.each_hunk.map do |hunk|
30
- # TODO: optimise this to use line ranges!
31
- hunk.lines.select(&:addition?).map(&:new_lineno)
32
- end.flatten
33
- @files[filepath] = line_nums if line_nums.any?
34
- end
35
- self
16
+ @filter_set = filter_set
36
17
  end
37
18
 
38
19
  def last_modified
@@ -46,18 +27,25 @@ module Undercover
46
27
  end
47
28
 
48
29
  def file_paths
49
- files.keys.sort
30
+ full_diff.deltas.map { |d| d.new_file[:path] }.sort
50
31
  end
51
32
 
52
33
  def each_changed_line
53
- files.each do |filepath, line_numbers|
54
- line_numbers.each { |ln| yield filepath, ln }
34
+ full_diff.each_patch do |patch|
35
+ filepath = patch.delta.new_file[:path]
36
+ next if filter_set && !filter_set.include?(filepath)
37
+
38
+ patch.each_hunk do |hunk|
39
+ hunk.lines.select(&:addition?).each do |line|
40
+ yield filepath, line.new_lineno
41
+ end
42
+ end
55
43
  end
56
44
  end
57
45
 
58
46
  # TODO: refactor to a standalone validator (depending on changeset AND lcov)
59
47
  def validate(lcov_report_path)
60
- return :no_changes if files.empty?
48
+ return :no_changes if full_diff.deltas.empty?
61
49
 
62
50
  :stale_coverage if last_modified > File.mtime(lcov_report_path)
63
51
  end
@@ -68,7 +56,7 @@ module Undercover
68
56
  # as it makes sense to run Undercover with the most recent file versions
69
57
  def full_diff
70
58
  base = compare_base_obj || head
71
- base.diff(repo.index).merge!(repo.diff_workdir(head))
59
+ @full_diff ||= base.diff(repo.index).merge!(repo.diff_workdir(head))
72
60
  end
73
61
 
74
62
  def compare_base_obj
@@ -83,6 +71,6 @@ module Undercover
83
71
  repo.head.target
84
72
  end
85
73
 
86
- attr_reader :repo, :compare_base
74
+ attr_reader :repo, :compare_base, :filter_set
87
75
  end
88
76
  end
@@ -43,7 +43,8 @@ module Undercover
43
43
 
44
44
  def self.changeset(opts)
45
45
  git_dir = File.join(opts.path, opts.git_dir)
46
- Undercover::Changeset.new(git_dir, opts.compare)
46
+ filter_set = Undercover::FilterSet.new(opts.glob_allow_filters, opts.glob_reject_filters)
47
+ Undercover::Changeset.new(git_dir, opts.compare, filter_set)
47
48
  end
48
49
  end
49
50
  end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Undercover
4
+ class FilterSet
5
+ attr_reader :allow_filters, :reject_filters
6
+
7
+ def initialize(allow_filters, reject_filters)
8
+ @allow_filters = allow_filters || []
9
+ @reject_filters = reject_filters || []
10
+ end
11
+
12
+ def include?(filepath)
13
+ fnmatch = proc { |glob| File.fnmatch(glob, filepath) }
14
+ allow_filters.any?(fnmatch) && reject_filters.none?(fnmatch)
15
+ end
16
+ end
17
+ end
@@ -41,7 +41,8 @@ module Undercover
41
41
  :run_mode,
42
42
  :file_scope,
43
43
  :glob_allow_filters,
44
- :glob_reject_filters
44
+ :glob_reject_filters,
45
+ :max_warnings_limit
45
46
 
46
47
  def initialize
47
48
  @run_mode = DIFF_TRIGGER_LINE
@@ -51,6 +52,7 @@ module Undercover
51
52
  self.git_dir = '.git'
52
53
  self.glob_allow_filters = DEFAULT_FILE_INCLUDE_GLOBS
53
54
  self.glob_reject_filters = DEFAULT_FILE_EXCLUDE_GLOBS
55
+ self.max_warnings_limit = nil
54
56
  end
55
57
 
56
58
  # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
@@ -75,6 +77,7 @@ module Undercover
75
77
  git_dir_option(opts)
76
78
  compare_option(opts)
77
79
  ruby_syntax_option(opts)
80
+ max_warnings_limit_option(opts)
78
81
  file_filters(opts)
79
82
  end.parse(args)
80
83
 
@@ -137,6 +140,13 @@ module Undercover
137
140
  end
138
141
  end
139
142
 
143
+ def max_warnings_limit_option(parser)
144
+ desc = 'Maximum number of warnings to generate before stopping analysis'
145
+ parser.on('-w', '--max-warnings limit', Integer, desc) do |limit|
146
+ self.max_warnings_limit = limit
147
+ end
148
+ end
149
+
140
150
  def guess_lcov_path
141
151
  cwd = Pathname.new(File.expand_path(path))
142
152
  self.lcov = File.join(cwd, 'coverage', 'lcov', "#{cwd.split.last}.lcov")
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Undercover
4
- VERSION = '0.6.4'
4
+ VERSION = '0.6.6'
5
5
  end
data/lib/undercover.rb CHANGED
@@ -12,6 +12,7 @@ require 'undercover/cli'
12
12
  require 'undercover/changeset'
13
13
  require 'undercover/formatter'
14
14
  require 'undercover/options'
15
+ require 'undercover/filter_set'
15
16
  require 'undercover/version'
16
17
 
17
18
  module Undercover
@@ -23,7 +24,8 @@ module Undercover
23
24
  :lcov,
24
25
  :results,
25
26
  :code_dir,
26
- :glob_filters
27
+ :filter_set,
28
+ :max_warnings_limit
27
29
 
28
30
  # Initializes a new Undercover::Report
29
31
  #
@@ -32,18 +34,19 @@ module Undercover
32
34
  def initialize(changeset, opts)
33
35
  @lcov = LcovParser.parse(File.open(opts.lcov))
34
36
  @code_dir = opts.path
35
- @changeset = changeset.update
36
- @glob_filters = {
37
- allow: opts.glob_allow_filters,
38
- reject: opts.glob_reject_filters
39
- }
37
+ @changeset = changeset
38
+ @filter_set = FilterSet.new(opts.glob_allow_filters, opts.glob_reject_filters)
39
+ @max_warnings_limit = opts.max_warnings_limit
40
40
  @loaded_files = {}
41
41
  @results = {}
42
42
  end
43
43
 
44
44
  # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
45
45
  def build
46
+ flag_count = 0
46
47
  changeset.each_changed_line do |filepath, line_no|
48
+ break if max_warnings_limit && flag_count >= max_warnings_limit
49
+
47
50
  dist_from_line_no = lambda do |res|
48
51
  return BigDecimal::INFINITY if line_no < res.first_line
49
52
 
@@ -61,7 +64,10 @@ module Undercover
61
64
  next unless loaded_files[filepath]
62
65
 
63
66
  res = loaded_files[filepath].min(&dist_from_line_no_sorter)
64
- res.flag if res&.uncovered?(line_no)
67
+ if res.uncovered?(line_no) && !res.flagged?
68
+ res.flag
69
+ flag_count += 1
70
+ end
65
71
  results[filepath] ||= Set.new
66
72
  results[filepath] << res
67
73
  end
@@ -114,8 +120,7 @@ module Undercover
114
120
  # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
115
121
 
116
122
  def include_file?(filepath)
117
- fnmatch = proc { |glob| File.fnmatch(glob, filepath) }
118
- glob_filters[:allow].any?(fnmatch) && glob_filters[:reject].none?(fnmatch)
123
+ filter_set.include?(filepath)
119
124
  end
120
125
  end
121
126
  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.6.4
4
+ version: 0.6.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jan Grodowski
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-03-29 00:00:00.000000000 Z
10
+ date: 2025-07-01 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: base64
@@ -122,6 +122,7 @@ files:
122
122
  - lib/undercover.rb
123
123
  - lib/undercover/changeset.rb
124
124
  - lib/undercover/cli.rb
125
+ - lib/undercover/filter_set.rb
125
126
  - lib/undercover/formatter.rb
126
127
  - lib/undercover/lcov_parser.rb
127
128
  - lib/undercover/options.rb