undercover 0.3.4 → 0.4.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/dependabot.yml +16 -0
- data/.github/workflows/ruby.yml +1 -1
- data/.rubocop.yml +2 -1
- data/CHANGELOG.md +26 -1
- data/README.md +13 -3
- data/lib/undercover/changeset.rb +2 -1
- data/lib/undercover/cli.rb +2 -2
- data/lib/undercover/formatter.rb +4 -4
- data/lib/undercover/lcov_parser.rb +10 -2
- data/lib/undercover/options.rb +1 -1
- data/lib/undercover/result.rb +51 -21
- data/lib/undercover/version.rb +1 -1
- data/lib/undercover.rb +2 -2
- data/undercover.gemspec +5 -3
- metadata +13 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f6cd3608abfe38ef691799586ac21e0818846ea2b67e7867af9c917762c28de7
|
4
|
+
data.tar.gz: 434a7428414fe21d31e5cf691255f39f5abdc9a0e370f39a362d7ac6f90ae7a4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 18574633620761accff515e582f79949c7bd4deaae9f5e44dfdba03cddfeed39c76d271214be0bbccf6c51da1c0231bd290693679cfd182753b14102bc4dac0b
|
7
|
+
data.tar.gz: 05f18a34ffa46f66a4cb73c9143c0851e295ad60129e7969d2e72a6350af5a874713201797f75ff8bbcaebfe27918a322b75e3b5eb9508eb20140ca5ad82d0bf
|
@@ -0,0 +1,16 @@
|
|
1
|
+
version: 2
|
2
|
+
updates:
|
3
|
+
- package-ecosystem: bundler
|
4
|
+
directory: "/"
|
5
|
+
schedule:
|
6
|
+
interval: daily
|
7
|
+
time: "04:00"
|
8
|
+
open-pull-requests-limit: 10
|
9
|
+
ignore:
|
10
|
+
- dependency-name: rubocop
|
11
|
+
versions:
|
12
|
+
- 1.10.0
|
13
|
+
- 1.12.0
|
14
|
+
- 1.12.1
|
15
|
+
- 1.8.1
|
16
|
+
- 1.9.0
|
data/.github/workflows/ruby.yml
CHANGED
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -6,6 +6,27 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|
6
6
|
|
7
7
|
## [Unreleased]
|
8
8
|
|
9
|
+
# [0.4.4] - 2021-11-29
|
10
|
+
### Changed
|
11
|
+
- Dependency updates
|
12
|
+
|
13
|
+
# [0.4.3] - 2021-03-16
|
14
|
+
### Fixed
|
15
|
+
- Branch coverage without line coverage marked as uncovered - fix by @GCorbel
|
16
|
+
|
17
|
+
# [0.4.1] - 2021-03-11
|
18
|
+
### Fixed
|
19
|
+
- Fix zero-division edge case resulting in NaN from Result#coverage_f
|
20
|
+
|
21
|
+
# [0.4.0] - 2021-02-06
|
22
|
+
### Added
|
23
|
+
- [Minimal implementation of branch coverage in LCOV parser](https://github.com/grodowski/undercover/pull/112) by [@magneland](https://github.com/magneland)
|
24
|
+
- Branch coverage output support in Undercover::Formatter
|
25
|
+
### Changed
|
26
|
+
- Min Ruby requirement bumped to 2.5.0
|
27
|
+
- Dependency updates: Rubocop 1.0 and Rugged 1.1.0
|
28
|
+
|
29
|
+
|
9
30
|
## [0.3.4] - 2020-04-05
|
10
31
|
### Changed
|
11
32
|
- Updated parsing performance by scoping `all_results` to git diff
|
@@ -86,7 +107,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|
86
107
|
### Added
|
87
108
|
- First release of `undercover` 🎉
|
88
109
|
|
89
|
-
[Unreleased]: https://github.com/grodowski/undercover/compare/v0.
|
110
|
+
[Unreleased]: https://github.com/grodowski/undercover/compare/v0.4.4...HEAD
|
111
|
+
[0.4.3]: https://github.com/grodowski/undercover/compare/v0.4.3...v0.4.4
|
112
|
+
[0.4.3]: https://github.com/grodowski/undercover/compare/v0.4.1...v0.4.3
|
113
|
+
[0.4.1]: https://github.com/grodowski/undercover/compare/v0.4.0...v0.4.1
|
114
|
+
[0.4.0]: https://github.com/grodowski/undercover/compare/v0.3.4...v0.4.0
|
90
115
|
[0.3.4]: https://github.com/grodowski/undercover/compare/v0.3.3...v0.3.4
|
91
116
|
[0.3.3]: https://github.com/grodowski/undercover/compare/v0.3.2...v0.3.3
|
92
117
|
[0.3.2]: https://github.com/grodowski/undercover/compare/v0.3.1...v0.3.2
|
data/README.md
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
# undercover 👮♂️
|
2
2
|
|
3
|
-
**Like RuboCop but for code coverage
|
3
|
+
**Like RuboCop but for code coverage**. Inspects files in a git diff and warns on changed methods, classes and blocks which need to be tested. Use it locally or as part of an automated build to shorten your code coverage feedback loop!
|
4
4
|
|
5
|
-
**
|
5
|
+
- Visit **[https://undercover-ci.com](https://undercover-ci.com)** to set up actionable GitHub code review checks, or use one of the [integrations](#code-review-integrations)
|
6
|
+
- Learn how to find untested code changes locally with the [CLI](#usage)
|
6
7
|
|
7
8
|
A sample output of `undercover` ran before a commit may look like this:
|
8
9
|
|
@@ -14,7 +15,6 @@ And like this, given that specs were added:
|
|
14
15
|
|
15
16
|
[![Build Status](https://action-badges.now.sh/grodowski/undercover)](https://github.com/grodowski/undercover/actions)
|
16
17
|
[![Maintainability](https://api.codeclimate.com/v1/badges/b403feed68a18c072ec5/maintainability)](https://codeclimate.com/github/grodowski/undercover/maintainability)
|
17
|
-
[![codebeat badge](https://codebeat.co/badges/be548247-2421-4448-bdab-896d13eb02e9)](https://codebeat.co/projects/github-com-grodowski-undercover-master)
|
18
18
|
|
19
19
|
## Installation
|
20
20
|
|
@@ -51,6 +51,7 @@ SimpleCov.formatter = SimpleCov::Formatter::LcovFormatter
|
|
51
51
|
SimpleCov.start do
|
52
52
|
add_filter(/^\/spec\//) # For RSpec
|
53
53
|
add_filter(/^\/test\//) # For Minitest
|
54
|
+
enable_coverage(:branch) # Report branch coverage to trigger branch-level undercover warnings
|
54
55
|
end
|
55
56
|
|
56
57
|
require 'undercover'
|
@@ -83,6 +84,15 @@ Check out `docs/` for CI configuration examples:
|
|
83
84
|
|
84
85
|
Merging coverage results ([sample gist](https://gist.github.com/grodowski/9744ff91034dce8df20c2a8210409fb0)) is required for parallel tests before processing with `undercover`.
|
85
86
|
|
87
|
+
## Code review integrations
|
88
|
+
|
89
|
+
A few options exist to provide automated comments from `undercover` in Pull Request reviews, which is the most streamlined way to add Undercover to your development workflow.
|
90
|
+
|
91
|
+
- [UndercoverCI](https://undercover-ci.com) - `undercover` Pull Request feedback delivered natively with GitHub Checks
|
92
|
+
- [pronto-undercover](https://github.com/grodowski/pronto-undercover)
|
93
|
+
- [danger-undercover](https://github.com/nimblehq/danger-undercover)
|
94
|
+
- [undercover-checkstyle](https://github.com/aki77/undercover-checkstyle)
|
95
|
+
|
86
96
|
## Configuration
|
87
97
|
|
88
98
|
### CLI Options
|
data/lib/undercover/changeset.rb
CHANGED
data/lib/undercover/cli.rb
CHANGED
@@ -10,8 +10,8 @@ module Undercover
|
|
10
10
|
|
11
11
|
WARNINGS_TO_S = {
|
12
12
|
stale_coverage: Rainbow('🚨 WARNING: Coverage data is older than your ' \
|
13
|
-
|
14
|
-
|
13
|
+
'latest changes and results might be incomplete. ' \
|
14
|
+
'Re-run tests to update').yellow,
|
15
15
|
no_changes: Rainbow('✅ No reportable changes').green
|
16
16
|
}.freeze
|
17
17
|
def self.run(args)
|
data/lib/undercover/formatter.rb
CHANGED
@@ -18,20 +18,20 @@ module Undercover
|
|
18
18
|
@results.map.with_index(1) do |res, idx|
|
19
19
|
"🚨 #{idx}) node `#{res.node.name}` type: #{res.node.human_name},\n" +
|
20
20
|
(' ' * pad_size) + "loc: #{res.file_path_with_lines}," \
|
21
|
-
|
21
|
+
" coverage: #{res.coverage_f * 100}%\n" +
|
22
22
|
res.pretty_print
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
26
26
|
def success
|
27
27
|
"#{Rainbow('undercover').bold.green}: ✅ No coverage" \
|
28
|
-
|
28
|
+
' is missing in latest changes'
|
29
29
|
end
|
30
30
|
|
31
31
|
def warnings_header
|
32
32
|
"#{Rainbow('undercover').bold.red}: " \
|
33
|
-
|
34
|
-
|
33
|
+
'👮♂️ some methods have no test coverage! Please add specs for' \
|
34
|
+
' methods listed below'
|
35
35
|
end
|
36
36
|
|
37
37
|
def pad_size
|
@@ -31,7 +31,7 @@ module Undercover
|
|
31
31
|
|
32
32
|
private
|
33
33
|
|
34
|
-
# rubocop:disable Metrics/MethodLength, Style/SpecialGlobalVars
|
34
|
+
# rubocop:disable Metrics/MethodLength, Style/SpecialGlobalVars, Metrics/AbcSize
|
35
35
|
def parse_line(line)
|
36
36
|
case line
|
37
37
|
when /^SF:(.+)/
|
@@ -41,12 +41,20 @@ module Undercover
|
|
41
41
|
line_no = $~[1]
|
42
42
|
covered = $~[2]
|
43
43
|
source_files[@current_filename] << [line_no.to_i, covered.to_i]
|
44
|
+
when /^(BRF|BRH):(\d+)/
|
45
|
+
# branches found/hit; no-op
|
46
|
+
when /^BRDA:(\d+),(\d+),(\d+),(-|\d+)/
|
47
|
+
line_no = $~[1]
|
48
|
+
block_no = $~[2]
|
49
|
+
branch_no = $~[3]
|
50
|
+
covered = ($~[4] == '-' ? '0' : $~[4])
|
51
|
+
source_files[@current_filename] << [line_no.to_i, block_no.to_i, branch_no.to_i, covered.to_i]
|
44
52
|
when /^end_of_record$/, /^$/
|
45
53
|
@current_filename = nil
|
46
54
|
else
|
47
55
|
raise LcovParseError, "could not recognise '#{line}' as valid LCOV"
|
48
56
|
end
|
49
57
|
end
|
50
|
-
# rubocop:enable Metrics/MethodLength, Style/SpecialGlobalVars
|
58
|
+
# rubocop:enable Metrics/MethodLength, Style/SpecialGlobalVars, Metrics/AbcSize
|
51
59
|
end
|
52
60
|
end
|
data/lib/undercover/options.rb
CHANGED
data/lib/undercover/result.rb
CHANGED
@@ -27,28 +27,38 @@ module Undercover
|
|
27
27
|
@flagged
|
28
28
|
end
|
29
29
|
|
30
|
-
#
|
31
|
-
def non_code?(line_no)
|
32
|
-
line_cov = coverage.find { |ln, _cov| ln == line_no }
|
33
|
-
!line_cov
|
34
|
-
end
|
35
|
-
|
36
|
-
def covered?(line_no)
|
37
|
-
line_cov = coverage.find { |ln, _cov| ln == line_no }
|
38
|
-
line_cov && line_cov[1].positive?
|
39
|
-
end
|
40
|
-
|
30
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
41
31
|
def uncovered?(line_no)
|
42
|
-
|
32
|
+
# check branch coverage for line_no
|
33
|
+
coverage.each do |ln, _block, _branch, cov|
|
34
|
+
return true if ln == line_no && cov && cov.zero?
|
35
|
+
end
|
36
|
+
|
37
|
+
# check line coverage for line_no
|
38
|
+
line_cov = coverage.select { |cov| cov.size == 2 }.find { |ln, _cov| ln == line_no }
|
43
39
|
line_cov && line_cov[1].zero?
|
44
40
|
end
|
41
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
45
42
|
|
43
|
+
# Method `coverage_f` returns the total coverage of this Undercover::Result
|
44
|
+
# as a % value, taking into account missing branches. Line coverage will be counted
|
45
|
+
# as 0 if any branch is untested.
|
46
|
+
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
46
47
|
def coverage_f
|
47
|
-
|
48
|
-
|
48
|
+
lines = {}
|
49
|
+
coverage.each do |ln, block_or_line_cov, _, branch_cov|
|
50
|
+
lines[ln] = 1 unless lines.key?(ln)
|
51
|
+
if branch_cov
|
52
|
+
lines[ln] = 0 if branch_cov.zero?
|
53
|
+
elsif block_or_line_cov.zero?
|
54
|
+
lines[ln] = 0
|
55
|
+
end
|
49
56
|
end
|
50
|
-
|
57
|
+
return 1.0 if lines.keys.size.zero?
|
58
|
+
|
59
|
+
(lines.values.sum.to_f / lines.keys.size).round(4)
|
51
60
|
end
|
61
|
+
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
|
52
62
|
|
53
63
|
# TODO: create a formatter interface instead and add some tests.
|
54
64
|
# TODO: re-enable rubocops
|
@@ -60,10 +70,10 @@ module Undercover
|
|
60
70
|
cov_enum = coverage.each
|
61
71
|
cov_source_lines = (node.first_line..node.last_line).map do |line_no|
|
62
72
|
cov_line_no = begin
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
73
|
+
cov_enum.peek[0]
|
74
|
+
rescue StopIteration
|
75
|
+
-1
|
76
|
+
end
|
67
77
|
cov_enum.next[1] if cov_line_no == line_no
|
68
78
|
end
|
69
79
|
cov_source_lines.zip(node.source_lines_with_numbers)
|
@@ -81,10 +91,12 @@ module Undercover
|
|
81
91
|
Rainbow(' hits: n/a').italic.darkgray.dark
|
82
92
|
elsif covered.positive?
|
83
93
|
Rainbow(formatted_line).green + \
|
84
|
-
Rainbow(" hits: #{covered}").italic.darkgray.dark
|
94
|
+
Rainbow(" hits: #{covered}").italic.darkgray.dark + \
|
95
|
+
count_covered_branches(num)
|
85
96
|
elsif covered.zero?
|
86
97
|
Rainbow(formatted_line).red + \
|
87
|
-
Rainbow(" hits: #{covered}").italic.darkgray.dark
|
98
|
+
Rainbow(" hits: #{covered}").italic.darkgray.dark + \
|
99
|
+
count_covered_branches(num)
|
88
100
|
end
|
89
101
|
end.join("\n")
|
90
102
|
end
|
@@ -99,5 +111,23 @@ module Undercover
|
|
99
111
|
" name: #{node.name}, coverage: #{coverage_f}>"
|
100
112
|
end
|
101
113
|
alias to_s inspect
|
114
|
+
|
115
|
+
private
|
116
|
+
|
117
|
+
# rubocop:disable Metrics/AbcSize
|
118
|
+
def count_covered_branches(line_number)
|
119
|
+
branches = coverage.select { |cov| cov.size == 4 && cov[0] == line_number }
|
120
|
+
count_covered = branches.count { |cov| cov[3].positive? }
|
121
|
+
|
122
|
+
return '' if branches.size.zero?
|
123
|
+
|
124
|
+
if count_covered < branches.size
|
125
|
+
Rainbow(' branches: ').italic.darkgray.dark + \
|
126
|
+
Rainbow("#{count_covered}/#{branches.size}").italic.red
|
127
|
+
else
|
128
|
+
Rainbow(" branches: #{count_covered}/#{branches.size}").italic.darkgray.dark
|
129
|
+
end
|
130
|
+
end
|
131
|
+
# rubocop:enable Metrics/AbcSize
|
102
132
|
end
|
103
133
|
end
|
data/lib/undercover/version.rb
CHANGED
data/lib/undercover.rb
CHANGED
@@ -36,7 +36,7 @@ module Undercover
|
|
36
36
|
@results = {}
|
37
37
|
end
|
38
38
|
|
39
|
-
# rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
39
|
+
# rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity
|
40
40
|
def build
|
41
41
|
changeset.each_changed_line do |filepath, line_no|
|
42
42
|
dist_from_line_no = lambda do |res|
|
@@ -61,7 +61,7 @@ module Undercover
|
|
61
61
|
end
|
62
62
|
self
|
63
63
|
end
|
64
|
-
# rubocop:enable Metrics/MethodLength, Metrics/AbcSize
|
64
|
+
# rubocop:enable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity
|
65
65
|
|
66
66
|
def build_warnings
|
67
67
|
warn('Undercover::Report#build_warnings is deprecated! ' \
|
data/undercover.gemspec
CHANGED
@@ -23,18 +23,20 @@ Gem::Specification.new do |spec|
|
|
23
23
|
spec.executables = spec.files.grep(/^bin\//) { |f| File.basename(f) }
|
24
24
|
spec.require_paths = ['lib']
|
25
25
|
|
26
|
+
spec.required_ruby_version = '>= 2.5.0'
|
27
|
+
|
26
28
|
spec.add_dependency 'imagen', '>= 0.1.8'
|
27
29
|
spec.add_dependency 'rainbow', '>= 2.1', '< 4.0'
|
28
|
-
spec.add_dependency 'rugged', '>= 0.27', '< 1.
|
30
|
+
spec.add_dependency 'rugged', '>= 0.27', '< 1.3'
|
29
31
|
|
30
32
|
spec.add_development_dependency 'bundler'
|
31
33
|
spec.add_development_dependency 'pry'
|
32
34
|
spec.add_development_dependency 'rake', '~> 13.0'
|
33
35
|
spec.add_development_dependency 'rspec', '~> 3.0'
|
34
|
-
spec.add_development_dependency 'rubocop', '~>
|
36
|
+
spec.add_development_dependency 'rubocop', '~> 1.22.1'
|
35
37
|
spec.add_development_dependency 'simplecov'
|
36
38
|
spec.add_development_dependency 'simplecov-html'
|
37
|
-
spec.add_development_dependency 'simplecov-lcov'
|
39
|
+
spec.add_development_dependency 'simplecov-lcov', '~> 0.8'
|
38
40
|
spec.add_development_dependency 'timecop'
|
39
41
|
end
|
40
42
|
# rubocop:enable Metrics/BlockLength
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: undercover
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jan Grodowski
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-11-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: imagen
|
@@ -53,7 +53,7 @@ dependencies:
|
|
53
53
|
version: '0.27'
|
54
54
|
- - "<"
|
55
55
|
- !ruby/object:Gem::Version
|
56
|
-
version: '1.
|
56
|
+
version: '1.3'
|
57
57
|
type: :runtime
|
58
58
|
prerelease: false
|
59
59
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -63,7 +63,7 @@ dependencies:
|
|
63
63
|
version: '0.27'
|
64
64
|
- - "<"
|
65
65
|
- !ruby/object:Gem::Version
|
66
|
-
version: '1.
|
66
|
+
version: '1.3'
|
67
67
|
- !ruby/object:Gem::Dependency
|
68
68
|
name: bundler
|
69
69
|
requirement: !ruby/object:Gem::Requirement
|
@@ -126,14 +126,14 @@ dependencies:
|
|
126
126
|
requirements:
|
127
127
|
- - "~>"
|
128
128
|
- !ruby/object:Gem::Version
|
129
|
-
version:
|
129
|
+
version: 1.22.1
|
130
130
|
type: :development
|
131
131
|
prerelease: false
|
132
132
|
version_requirements: !ruby/object:Gem::Requirement
|
133
133
|
requirements:
|
134
134
|
- - "~>"
|
135
135
|
- !ruby/object:Gem::Version
|
136
|
-
version:
|
136
|
+
version: 1.22.1
|
137
137
|
- !ruby/object:Gem::Dependency
|
138
138
|
name: simplecov
|
139
139
|
requirement: !ruby/object:Gem::Requirement
|
@@ -166,16 +166,16 @@ dependencies:
|
|
166
166
|
name: simplecov-lcov
|
167
167
|
requirement: !ruby/object:Gem::Requirement
|
168
168
|
requirements:
|
169
|
-
- - "
|
169
|
+
- - "~>"
|
170
170
|
- !ruby/object:Gem::Version
|
171
|
-
version: '0'
|
171
|
+
version: '0.8'
|
172
172
|
type: :development
|
173
173
|
prerelease: false
|
174
174
|
version_requirements: !ruby/object:Gem::Requirement
|
175
175
|
requirements:
|
176
|
-
- - "
|
176
|
+
- - "~>"
|
177
177
|
- !ruby/object:Gem::Version
|
178
|
-
version: '0'
|
178
|
+
version: '0.8'
|
179
179
|
- !ruby/object:Gem::Dependency
|
180
180
|
name: timecop
|
181
181
|
requirement: !ruby/object:Gem::Requirement
|
@@ -199,6 +199,7 @@ extensions: []
|
|
199
199
|
extra_rdoc_files: []
|
200
200
|
files:
|
201
201
|
- ".codebeatsettings"
|
202
|
+
- ".github/dependabot.yml"
|
202
203
|
- ".github/workflows/ruby.yml"
|
203
204
|
- ".gitignore"
|
204
205
|
- ".overcommit.yml"
|
@@ -238,14 +239,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
238
239
|
requirements:
|
239
240
|
- - ">="
|
240
241
|
- !ruby/object:Gem::Version
|
241
|
-
version:
|
242
|
+
version: 2.5.0
|
242
243
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
243
244
|
requirements:
|
244
245
|
- - ">="
|
245
246
|
- !ruby/object:Gem::Version
|
246
247
|
version: '0'
|
247
248
|
requirements: []
|
248
|
-
rubygems_version: 3.
|
249
|
+
rubygems_version: 3.2.22
|
249
250
|
signing_key:
|
250
251
|
specification_version: 4
|
251
252
|
summary: Actionable code coverage - detects untested code blocks in recent changes
|