simplecov 0.18.0.beta2 → 0.18.0

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: eeee06c5cbeaf6c89000a43641f7a6163d9b267d145698e742089385eae6e2b8
4
- data.tar.gz: 76e7182adc3c8156e021d780618733f6237a81723656f0123b51abf34a63e891
3
+ metadata.gz: ce12d99507e5e8bd75399d80855c495907b8dda937fb5e2a33d2895e9f74e370
4
+ data.tar.gz: ae53aefe53c30c3b1d6c1c0b70aa34c1193642d4a879f77a0c3522960fc92773
5
5
  SHA512:
6
- metadata.gz: e5e785e01ff4bf53c9cbdbeaeb7cc5925265e2ec9cacecb9865261150eed13b8c59c43d840588eb6baa0565ce510c74891f535f02bb1db42d2eeea4e61b3cd54
7
- data.tar.gz: d8f29ed7d8ecf8a01d9fcd843a0c2a9845f022a690c8c7096c23077f7e3c2488fce290e099e70083ee44e37d1bb05f1535c2ec1a70b6952929ab92857b38a339
6
+ metadata.gz: e9a60e36936b9b5018a6998ade1742373bfa67b96688411ab42c90ac82ca6280abe9d407dcef4d45398ed28803ca876d6f82ce0cabc878d6141897cca4dab91e
7
+ data.tar.gz: 7056045c2385516ce0029af14558bd95c9c84bc85319d82bddfcf2011b7db87a5291168bb739c1ff02d8830e133065b3f2fda605e03b7c2e6ed094b61bff9240
data/CHANGELOG.md CHANGED
@@ -1,10 +1,37 @@
1
- 0.18.0.beta2 (unreleased)
1
+ 0.18.0 (2020-01-28)
2
+ ===================
3
+
4
+ Huge release! Highlights are support for branch coverage (Ruby 2.5+) and dropping support for EOL'ed Ruby versions (< 2.4).
5
+ Please also read the other beta patch notes.
6
+
7
+ ## Enhancements
8
+ * You can now define the minimum expected coverage by criterion like `minimum_coverage line: 90, branch: 80`
9
+ * Memoized some internal data structures that didn't change to reduce SimpleCov overhead
10
+ * Both `FileList` and `SourceFile` now have a `coverage` method that returns a hash that points from a coverage criterion to a `CoverageStatistics` object for uniform access to overall coverage statistics for both line and branch coverage
11
+
12
+ ## Bugfixes
13
+ * we were losing precision by rounding the covered strength early, that has been removed. **For Formatters** this also means that you may need to round it yourself now.
14
+ * Removed an inconsistency in how we treat skipped vs. irrelevant lines (see [#565](https://github.com/colszowka/simplecov/issues/565)) - SimpleCov's definition of 100% is now "You covered everything that you could" so if coverage is 0/0 that's counted as a 100% no matter if the lines were irrelevant or ignored/skipped
15
+
16
+ ## Noteworthy
17
+ * `FileList` stopped inheriting from Array, it includes Enumerable so if you didn't use Array specific methods on it in formatters you should be fine
18
+
19
+ 0.18.0.beta3 (2020-01-20)
20
+ ========================
21
+
22
+ ## Enhancements
23
+ * Instead of ignoring old `.resultset.json`s that are inside the merge timeout, adapt and respect them
24
+
25
+ ## Bugfixes
26
+ * Remove the constant warning printing if you still have a `.resultset.json` in pre 0.18 layout that is within your merge timeout
27
+
28
+ 0.18.0.beta2 (2020-01-19)
2
29
  ===================
3
30
 
4
31
  ## Enhancements
5
32
  * only turn on the requested coverage criteria (when activating branch coverage before SimpleCov would also instruct Ruby to take Method coverage)
6
33
  * Change how branch coverage is displayed, now it's `branch_type: hit_count` which should be more self explanatory. See [#830](https://github.com/colszowka/simplecov/pull/830) for an example and feel free to give feedback!
7
- * Allow early running exit tasks and avoid the `at_exit` hook through the `SimpleCov.run_exit_tasks!` method. (thanks [@macumber]: https://github.com/macumber))
34
+ * Allow early running exit tasks and avoid the `at_exit` hook through the `SimpleCov.run_exit_tasks!` method. (thanks [@macumber](https://github.com/macumber))
8
35
  * Allow manual collation of result sets through the `SimpleCov.collate` entrypoint. See the README for more details (thanks [@ticky](https://github.com/ticky))
9
36
  * Within `case`, even if there is no `else` branch declared show missing coverage for it (aka no branch of it). See [#825](https://github.com/colszowka/simplecov/pull/825)
10
37
  * Stop symbolizing all keys when loading cache (should lead to be faster and consume less memory)
data/README.md CHANGED
@@ -1,5 +1,6 @@
1
- SimpleCov [![Gem Version](https://badge.fury.io/rb/simplecov.svg)](https://badge.fury.io/rb/simplecov) [![Build Status](https://travis-ci.org/colszowka/simplecov.svg?branch=master)][Continuous Integration] [![Code Climate](https://codeclimate.com/github/colszowka/simplecov.svg)](https://codeclimate.com/github/colszowka/simplecov) [![Inline docs](http://inch-ci.org/github/colszowka/simplecov.svg)](http://inch-ci.org/github/colszowka/simplecov)
1
+ SimpleCov [![Gem Version](https://badge.fury.io/rb/simplecov.svg)](https://badge.fury.io/rb/simplecov) [![Build Status](https://github.com/colszowka/simplecov/workflows/stable/badge.svg?branch=master)][Continuous Integration] [![Code Climate](https://codeclimate.com/github/colszowka/simplecov.svg)](https://codeclimate.com/github/colszowka/simplecov) [![Inline docs](http://inch-ci.org/github/colszowka/simplecov.svg)](http://inch-ci.org/github/colszowka/simplecov)
2
2
  =========
3
+
3
4
  **Code coverage for Ruby**
4
5
 
5
6
  * [Source Code]
@@ -14,7 +15,7 @@ SimpleCov [![Gem Version](https://badge.fury.io/rb/simplecov.svg)](https://badge
14
15
  [Configuration]: http://rubydoc.info/gems/simplecov/SimpleCov/Configuration "Configuration options API documentation"
15
16
  [Changelog]: https://github.com/colszowka/simplecov/blob/master/CHANGELOG.md "Project Changelog"
16
17
  [Rubygem]: http://rubygems.org/gems/simplecov "SimpleCov @ rubygems.org"
17
- [Continuous Integration]: http://travis-ci.org/colszowka/simplecov "SimpleCov is built around the clock by travis-ci.org"
18
+ [Continuous Integration]: https://github.com/colszowka/simplecov/actions?query=workflow%3Astable "SimpleCov is built around the clock by github.com"
18
19
  [Dependencies]: https://gemnasium.com/colszowka/simplecov "SimpleCov dependencies on Gemnasium"
19
20
  [simplecov-html]: https://github.com/colszowka/simplecov-html "SimpleCov HTML Formatter Source Code @ GitHub"
20
21
 
@@ -34,11 +34,6 @@ module SimpleCov
34
34
  # @return [Hash]
35
35
  #
36
36
  def combine_result_sets(combined_results, result)
37
- unless correct_format?(result)
38
- warn_wrong_format
39
- return combined_results
40
- end
41
-
42
37
  results_files = combined_results.keys | result.keys
43
38
 
44
39
  results_files.each_with_object({}) do |file_name, file_combination|
@@ -49,41 +44,6 @@ module SimpleCov
49
44
  end
50
45
  end
51
46
 
52
- # We might start a run of a new simplecov version with a new format stored while
53
- # there is still a recent file like this lying around. If it's recent enough (
54
- # see merge_timeout) it will end up here. In order not to crash against this
55
- # we need to do some basic checking of the format of data we expect and
56
- # otherwise ignore it. See #820
57
- #
58
- # Currently correct format is:
59
- # { file_path_string => {coverage_criterion => coverage_date}}
60
- #
61
- # Internal use/reliance only.
62
- def correct_format?(result)
63
- result.empty? || matches_current_format?(result)
64
- end
65
-
66
- def matches_current_format?(result)
67
- # I so wish I could already use pattern matching
68
- key, data = result.first
69
-
70
- key.is_a?(String) && second_level_choice_of_criterion?(data)
71
- end
72
-
73
- SECOND_LEVEL_KEYS = %w[lines branches].freeze
74
- def second_level_choice_of_criterion?(data)
75
- second_level_key, = data.first
76
-
77
- SECOND_LEVEL_KEYS.member?(second_level_key)
78
- end
79
-
80
- def warn_wrong_format
81
- warn "Merging results, encountered an incorrectly formatted value. "\
82
- "This value was ignored.\nIf you just upgraded simplecov this is "\
83
- "likely due to a changed file format. If this happens again please "\
84
- "file a bug. https://github.com/colszowka/simplecov/issues"
85
- end
86
-
87
47
  #
88
48
  # Combine two files coverage results
89
49
  #
@@ -240,8 +240,15 @@ module SimpleCov
240
240
  # Default is 0% (disabled)
241
241
  #
242
242
  def minimum_coverage(coverage = nil)
243
- minimum_possible_coverage_exceeded("minimum_coverage") if coverage && coverage > 100
244
- @minimum_coverage ||= (coverage || 0).to_f.round(2)
243
+ return @minimum_coverage ||= {} unless coverage
244
+
245
+ coverage = {DEFAULT_COVERAGE_CRITERION => coverage} if coverage.is_a?(Numeric)
246
+ coverage.keys.each { |criterion| raise_if_criterion_disabled(criterion) }
247
+ coverage.values.each do |percent|
248
+ minimum_possible_coverage_exceeded("minimum_coverage") if percent && percent > 100
249
+ end
250
+
251
+ @minimum_coverage = coverage
245
252
  end
246
253
 
247
254
  #
@@ -362,12 +369,21 @@ module SimpleCov
362
369
 
363
370
  private
364
371
 
365
- def raise_if_criterion_unsupported(criterion)
366
- raise_criterion_unsupported(criterion) unless SUPPORTED_COVERAGE_CRITERIA.member?(criterion)
372
+ def raise_if_criterion_disabled(criterion)
373
+ raise_if_criterion_unsupported(criterion)
374
+ # rubocop:disable Style/IfUnlessModifier
375
+ unless coverage_criterion_enabled?(criterion)
376
+ raise "Coverage criterion #{criterion}, is disabled! Please enable it first through enable_coverage #{criterion} (if supported)"
377
+ end
378
+ # rubocop:enable Style/IfUnlessModifier
367
379
  end
368
380
 
369
- def raise_criterion_unsupported(criterion)
370
- raise "Unsupported coverage criterion #{criterion}, supported values are #{SUPPORTED_COVERAGE_CRITERIA}"
381
+ def raise_if_criterion_unsupported(criterion)
382
+ # rubocop:disable Style/IfUnlessModifier
383
+ unless SUPPORTED_COVERAGE_CRITERIA.member?(criterion)
384
+ raise "Unsupported coverage criterion #{criterion}, supported values are #{SUPPORTED_COVERAGE_CRITERIA}"
385
+ end
386
+ # rubocop:enable Style/IfUnlessModifier
371
387
  end
372
388
 
373
389
  def minimum_possible_coverage_exceeded(coverage_option)
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleCov
4
+ # Holds the individual data of a coverage result.
5
+ #
6
+ # This is uniform across coverage criteria as they all have:
7
+ #
8
+ # * total - how many things to cover there are (total relevant loc/branches)
9
+ # * covered - how many of the coverables are hit
10
+ # * missed - how many of the coverables are missed
11
+ # * percent - percentage as covered/missed
12
+ # * strength - average hits per/coverable (will not exist for one shot lines format)
13
+ class CoverageStatistics
14
+ attr_reader :total, :covered, :missed, :strength, :percent
15
+
16
+ def self.from(coverage_statistics)
17
+ sum_covered, sum_missed, sum_total_strength =
18
+ coverage_statistics.reduce([0, 0, 0.0]) do |(covered, missed, total_strength), file_coverage_statistics|
19
+ [
20
+ covered + file_coverage_statistics.covered,
21
+ missed + file_coverage_statistics.missed,
22
+ # gotta remultiply with loc because files have different strenght and loc
23
+ # giving them a different "weight" in total
24
+ total_strength + (file_coverage_statistics.strength * file_coverage_statistics.total)
25
+ ]
26
+ end
27
+
28
+ new(covered: sum_covered, missed: sum_missed, total_strength: sum_total_strength)
29
+ end
30
+
31
+ # Requires only covered, missed and strength to be initialized.
32
+ #
33
+ # Other values are computed by this class.
34
+ def initialize(covered:, missed:, total_strength: 0.0)
35
+ @covered = covered
36
+ @missed = missed
37
+ @total = covered + missed
38
+ @percent = compute_percent(covered, total)
39
+ @strength = compute_strength(total_strength, @total)
40
+ end
41
+
42
+ private
43
+
44
+ def compute_percent(covered, total)
45
+ return 100.0 if total.zero?
46
+
47
+ covered * 100.0 / total
48
+ end
49
+
50
+ def compute_strength(total_strength, total)
51
+ return 0.0 if total.zero?
52
+
53
+ total_strength.to_f / total
54
+ end
55
+ end
56
+ end
@@ -3,19 +3,38 @@
3
3
  module SimpleCov
4
4
  # An array of SimpleCov SourceFile instances with additional collection helper
5
5
  # methods for calculating coverage across them etc.
6
- class FileList < Array
6
+ class FileList
7
+ include Enumerable
8
+ extend Forwardable
9
+
10
+ def_delegators :@files,
11
+ # For Enumerable
12
+ :each,
13
+ # also delegating methods implemented in Enumerable as they have
14
+ # custom Array implementations which are presumably better/more
15
+ # resource efficient
16
+ :size, :map, :count,
17
+ # surprisingly not in Enumerable
18
+ :empty?, :length,
19
+ # still act like we're kinda an array
20
+ :to_a, :to_ary
21
+
22
+ def initialize(files)
23
+ @files = files
24
+ end
25
+
26
+ def coverage_statistics
27
+ @coverage_statistics ||= compute_coverage_statistics
28
+ end
29
+
7
30
  # Returns the count of lines that have coverage
8
31
  def covered_lines
9
- return 0.0 if empty?
10
-
11
- map { |f| f.covered_lines.count }.inject(:+)
32
+ coverage_statistics[:line]&.covered
12
33
  end
13
34
 
14
35
  # Returns the count of lines that have been missed
15
36
  def missed_lines
16
- return 0.0 if empty?
17
-
18
- map { |f| f.missed_lines.count }.inject(:+)
37
+ coverage_statistics[:line]&.missed
19
38
  end
20
39
 
21
40
  # Returns the count of lines that are not relevant for coverage
@@ -45,44 +64,51 @@ module SimpleCov
45
64
 
46
65
  # Returns the overall amount of relevant lines of code across all files in this list
47
66
  def lines_of_code
48
- covered_lines + missed_lines
67
+ coverage_statistics[:line]&.total
49
68
  end
50
69
 
51
70
  # Computes the coverage based upon lines covered and lines missed
52
71
  # @return [Float]
53
72
  def covered_percent
54
- return 100.0 if empty? || lines_of_code.zero?
55
-
56
- Float(covered_lines * 100.0 / lines_of_code)
73
+ coverage_statistics[:line]&.percent
57
74
  end
58
75
 
59
76
  # Computes the strength (hits / line) based upon lines covered and lines missed
60
77
  # @return [Float]
61
78
  def covered_strength
62
- return 0.0 if empty? || lines_of_code.zero?
63
-
64
- Float(map { |f| f.covered_strength * f.lines_of_code }.inject(:+) / lines_of_code)
79
+ coverage_statistics[:line]&.strength
65
80
  end
66
81
 
67
82
  # Return total count of branches in all files
68
83
  def total_branches
69
- return 0 if empty?
70
-
71
- map { |file| file.total_branches.count }.inject(:+)
84
+ coverage_statistics[:branch]&.total
72
85
  end
73
86
 
74
87
  # Return total count of covered branches
75
88
  def covered_branches
76
- return 0 if empty?
77
-
78
- map { |file| file.covered_branches.count }.inject(:+)
89
+ coverage_statistics[:branch]&.covered
79
90
  end
80
91
 
81
92
  # Return total count of covered branches
82
93
  def missed_branches
83
- return 0 if empty?
94
+ coverage_statistics[:branch]&.missed
95
+ end
96
+
97
+ def branch_covered_percent
98
+ coverage_statistics[:branch]&.percent
99
+ end
100
+
101
+ private
102
+
103
+ def compute_coverage_statistics
104
+ total_coverage_statistics = @files.each_with_object(line: [], branch: []) do |file, together|
105
+ together[:line] << file.coverage_statistics[:line]
106
+ together[:branch] << file.coverage_statistics[:branch] if SimpleCov.branch_coverage?
107
+ end
84
108
 
85
- map { |file| file.missed_branches.count }.inject(:+)
109
+ coverage_statistics = {line: CoverageStatistics.from(total_coverage_statistics[:line])}
110
+ coverage_statistics[:branch] = CoverageStatistics.from(total_coverage_statistics[:branch]) if SimpleCov.branch_coverage?
111
+ coverage_statistics
86
112
  end
87
113
  end
88
114
  end
@@ -20,14 +20,15 @@ module SimpleCov
20
20
  # Explicitly set the command name that was used for this coverage result. Defaults to SimpleCov.command_name
21
21
  attr_writer :command_name
22
22
 
23
- def_delegators :files, :covered_percent, :covered_percentages, :least_covered_file, :covered_strength, :covered_lines, :missed_lines, :total_branches, :covered_branches, :missed_branches
23
+ def_delegators :files, :covered_percent, :covered_percentages, :least_covered_file, :covered_strength, :covered_lines, :missed_lines, :total_branches, :covered_branches, :missed_branches, :coverage_statistics
24
24
  def_delegator :files, :lines_of_code, :total_lines
25
25
 
26
26
  # Initialize a new SimpleCov::Result from given Coverage.result (a Hash of filenames each containing an array of
27
27
  # coverage data)
28
28
  def initialize(original_result)
29
- @original_result = original_result.freeze
30
- @files = SimpleCov::FileList.new(original_result.map do |filename, coverage|
29
+ result = adapt_result(original_result)
30
+ @original_result = result.freeze
31
+ @files = SimpleCov::FileList.new(result.map do |filename, coverage|
31
32
  SimpleCov::SourceFile.new(filename, JSON.parse(JSON.dump(coverage))) if File.file?(filename)
32
33
  end.compact.sort_by(&:filename))
33
34
  filter!
@@ -82,6 +83,31 @@ module SimpleCov
82
83
 
83
84
  private
84
85
 
86
+ # We changed the format of the raw result data in simplecov, as people are likely
87
+ # to have "old" resultsets lying around (but not too old so that they're still
88
+ # considered we can adapt them).
89
+ # See https://github.com/colszowka/simplecov/pull/824#issuecomment-576049747
90
+ def adapt_result(result)
91
+ if pre_simplecov_0_18_result?(result)
92
+ adapt_pre_simplecov_0_18_result(result)
93
+ else
94
+ result
95
+ end
96
+ end
97
+
98
+ # pre 0.18 coverage data pointed from file directly to an array of line coverage
99
+ def pre_simplecov_0_18_result?(result)
100
+ _key, data = result.first
101
+
102
+ data.is_a?(Array)
103
+ end
104
+
105
+ def adapt_pre_simplecov_0_18_result(result)
106
+ result.map do |file_path, line_coverage_data|
107
+ [file_path, {"lines" => line_coverage_data}]
108
+ end.to_h
109
+ end
110
+
85
111
  def coverage
86
112
  keys = original_result.keys & filenames
87
113
  Hash[keys.zip(original_result.values_at(*keys))]
@@ -9,11 +9,11 @@ module SimpleCov
9
9
  # The full path to this source file (e.g. /User/colszowka/projects/simplecov/lib/simplecov/source_file.rb)
10
10
  attr_reader :filename
11
11
  # The array of coverage data received from the Coverage.result
12
- attr_reader :coverage
12
+ attr_reader :coverage_data
13
13
 
14
- def initialize(filename, coverage)
14
+ def initialize(filename, coverage_data)
15
15
  @filename = filename
16
- @coverage = coverage
16
+ @coverage_data = coverage_data
17
17
  end
18
18
 
19
19
  # The path to this source file relative to the projects directory
@@ -29,6 +29,14 @@ module SimpleCov
29
29
  end
30
30
  alias source src
31
31
 
32
+ def coverage_statistics
33
+ @coverage_statistics ||=
34
+ {
35
+ **line_coverage_statistics,
36
+ **branch_coverage_statistics
37
+ }
38
+ end
39
+
32
40
  # Returns all source lines for this file as instances of SimpleCov::SourceFile::Line,
33
41
  # and thus including coverage data. Aliased as :source_lines
34
42
  def lines
@@ -60,44 +68,7 @@ module SimpleCov
60
68
 
61
69
  # Returns the number of relevant lines (covered + missed)
62
70
  def lines_of_code
63
- covered_lines.size + missed_lines.size
64
- end
65
-
66
- def build_lines
67
- coverage_exceeding_source_warn if coverage["lines"].size > src.size
68
- lines = src.map.with_index(1) do |src, i|
69
- SimpleCov::SourceFile::Line.new(src, i, coverage["lines"][i - 1])
70
- end
71
- process_skipped_lines(lines)
72
- end
73
-
74
- # no_cov_chunks is zero indexed to work directly with the array holding the lines
75
- def no_cov_chunks
76
- @no_cov_chunks ||= build_no_cov_chunks
77
- end
78
-
79
- def build_no_cov_chunks
80
- no_cov_lines = src.map.with_index(1).select { |line, _index| LinesClassifier.no_cov_line?(line) }
81
-
82
- warn "uneven number of nocov comments detected" if no_cov_lines.size.odd?
83
-
84
- no_cov_lines.each_slice(2).map do |(_line_start, index_start), (_line_end, index_end)|
85
- index_start..index_end
86
- end
87
- end
88
-
89
- def process_skipped_lines(lines)
90
- # the array the lines are kept in is 0-based whereas the line numbers in the nocov
91
- # chunks are 1-based and are expected to be like this in other parts (and it's also
92
- # arguably more understandable)
93
- no_cov_chunks.each { |chunk| lines[(chunk.begin - 1)..(chunk.end - 1)].each(&:skipped!) }
94
-
95
- lines
96
- end
97
-
98
- # Warning to identify condition from Issue #56
99
- def coverage_exceeding_source_warn
100
- warn "Warning: coverage data provided by Coverage [#{coverage['lines'].size}] exceeds number of lines in #{filename} [#{src.size}]"
71
+ coverage_statistics[:line]&.total
101
72
  end
102
73
 
103
74
  # Access SimpleCov::SourceFile::Line source lines by line number
@@ -107,27 +78,17 @@ module SimpleCov
107
78
 
108
79
  # The coverage for this file in percent. 0 if the file has no coverage lines
109
80
  def covered_percent
110
- return 100.0 if no_lines?
111
-
112
- return 0.0 if relevant_lines.zero?
113
-
114
- Float(covered_lines.size * 100.0 / relevant_lines.to_f)
81
+ coverage_statistics[:line]&.percent
115
82
  end
116
83
 
117
84
  def covered_strength
118
- return 0.0 if relevant_lines.zero?
119
-
120
- (lines_strength / relevant_lines.to_f).round(1)
85
+ coverage_statistics[:line]&.strength
121
86
  end
122
87
 
123
88
  def no_lines?
124
89
  lines.length.zero? || (lines.length == never_lines.size)
125
90
  end
126
91
 
127
- def lines_strength
128
- lines.map(&:coverage).compact.reduce(:+)
129
- end
130
-
131
92
  def relevant_lines
132
93
  lines.size - never_lines.size - skipped_lines.size
133
94
  end
@@ -143,16 +104,13 @@ module SimpleCov
143
104
  end
144
105
 
145
106
  def branches_coverage_percent
146
- return 100.0 if no_branches?
147
- return 0.0 if covered_branches.empty?
148
-
149
- Float(covered_branches.size * 100.0 / total_branches.size.to_f)
107
+ coverage_statistics[:branch]&.percent
150
108
  end
151
109
 
152
110
  #
153
111
  # Return the relevant branches to source file
154
112
  def total_branches
155
- covered_branches + missed_branches
113
+ @total_branches ||= covered_branches + missed_branches
156
114
  end
157
115
 
158
116
  #
@@ -161,14 +119,104 @@ module SimpleCov
161
119
  @branches_report ||= build_branches_report
162
120
  end
163
121
 
164
- ## Related to source file branches statistics
122
+ #
123
+ # Select the covered branches
124
+ # Here we user tree schema because some conditions like case may have additional
125
+ # else that is not in declared inside the code but given by default by coverage report
126
+ #
127
+ # @return [Array]
128
+ #
129
+ def covered_branches
130
+ @covered_branches ||= branches.select(&:covered?)
131
+ end
132
+
133
+ #
134
+ # Select the missed branches with coverage equal to zero
135
+ #
136
+ # @return [Array]
137
+ #
138
+ def missed_branches
139
+ @missed_branches ||= branches.select(&:missed?)
140
+ end
141
+
142
+ def branches_for_line(line_number)
143
+ branches_report.fetch(line_number, [])
144
+ end
145
+
146
+ #
147
+ # Check if any branches missing on given line number
148
+ #
149
+ # @param [Integer] line_number
150
+ #
151
+ # @return [Boolean]
152
+ #
153
+ def line_with_missed_branch?(line_number)
154
+ branches_for_line(line_number).select { |_type, count| count.zero? }.any?
155
+ end
156
+
157
+ private
158
+
159
+ # no_cov_chunks is zero indexed to work directly with the array holding the lines
160
+ def no_cov_chunks
161
+ @no_cov_chunks ||= build_no_cov_chunks
162
+ end
163
+
164
+ def build_no_cov_chunks
165
+ no_cov_lines = src.map.with_index(1).select { |line, _index| LinesClassifier.no_cov_line?(line) }
166
+
167
+ warn "uneven number of nocov comments detected" if no_cov_lines.size.odd?
168
+
169
+ no_cov_lines.each_slice(2).map do |(_line_start, index_start), (_line_end, index_end)|
170
+ index_start..index_end
171
+ end
172
+ end
173
+
174
+ def build_lines
175
+ coverage_exceeding_source_warn if coverage_data["lines"].size > src.size
176
+ lines = src.map.with_index(1) do |src, i|
177
+ SimpleCov::SourceFile::Line.new(src, i, coverage_data["lines"][i - 1])
178
+ end
179
+ process_skipped_lines(lines)
180
+ end
181
+
182
+ def process_skipped_lines(lines)
183
+ # the array the lines are kept in is 0-based whereas the line numbers in the nocov
184
+ # chunks are 1-based and are expected to be like this in other parts (and it's also
185
+ # arguably more understandable)
186
+ no_cov_chunks.each { |chunk| lines[(chunk.begin - 1)..(chunk.end - 1)].each(&:skipped!) }
187
+
188
+ lines
189
+ end
190
+
191
+ def lines_strength
192
+ lines.map(&:coverage).compact.reduce(:+)
193
+ end
194
+
195
+ # Warning to identify condition from Issue #56
196
+ def coverage_exceeding_source_warn
197
+ warn "Warning: coverage data provided by Coverage [#{coverage_data['lines'].size}] exceeds number of lines in #{filename} [#{src.size}]"
198
+ end
199
+
200
+ #
201
+ # Build full branches report
202
+ # Root branches represent the wrapper of all condition state that
203
+ # have inside the branches
204
+ #
205
+ # @return [Hash]
206
+ #
207
+ def build_branches_report
208
+ branches.reject(&:skipped?).each_with_object({}) do |branch, coverage_statistics|
209
+ coverage_statistics[branch.report_line] ||= []
210
+ coverage_statistics[branch.report_line] << branch.report
211
+ end
212
+ end
165
213
 
166
214
  #
167
215
  # Call recursive method that transform our static hash to array of objects
168
216
  # @return [Array]
169
217
  #
170
218
  def build_branches
171
- coverage_branch_data = coverage.fetch("branches", {})
219
+ coverage_branch_data = coverage_data.fetch("branches", {})
172
220
  branches = coverage_branch_data.flat_map do |condition, coverage_branches|
173
221
  build_branches_from(condition, coverage_branches)
174
222
  end
@@ -230,53 +278,23 @@ module SimpleCov
230
278
  )
231
279
  end
232
280
 
233
- #
234
- # Select the covered branches
235
- # Here we user tree schema because some conditions like case may have additional
236
- # else that is not in declared inside the code but given by default by coverage report
237
- #
238
- # @return [Array]
239
- #
240
- def covered_branches
241
- @covered_branches ||= branches.select(&:covered?)
242
- end
243
-
244
- #
245
- # Select the missed branches with coverage equal to zero
246
- #
247
- # @return [Array]
248
- #
249
- def missed_branches
250
- @missed_branches ||= branches.select(&:missed?)
251
- end
252
-
253
- def branches_for_line(line_number)
254
- branches_report.fetch(line_number, [])
255
- end
256
-
257
- #
258
- # Check if any branches missing on given line number
259
- #
260
- # @param [Integer] line_number
261
- #
262
- # @return [Boolean]
263
- #
264
- def line_with_missed_branch?(line_number)
265
- branches_for_line(line_number).select { |_type, count| count.zero? }.any?
281
+ def line_coverage_statistics
282
+ {
283
+ line: CoverageStatistics.new(
284
+ total_strength: lines_strength,
285
+ covered: covered_lines.size,
286
+ missed: missed_lines.size
287
+ )
288
+ }
266
289
  end
267
290
 
268
- #
269
- # Build full branches report
270
- # Root branches represent the wrapper of all condition state that
271
- # have inside the branches
272
- #
273
- # @return [Hash]
274
- #
275
- def build_branches_report
276
- branches.reject(&:skipped?).each_with_object({}) do |branch, coverage_statistics|
277
- coverage_statistics[branch.report_line] ||= []
278
- coverage_statistics[branch.report_line] << branch.report
279
- end
291
+ def branch_coverage_statistics
292
+ {
293
+ branch: CoverageStatistics.new(
294
+ covered: covered_branches.size,
295
+ missed: missed_branches.size
296
+ )
297
+ }
280
298
  end
281
299
  end
282
300
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SimpleCov
4
- VERSION = "0.18.0.beta2"
4
+ VERSION = "0.18.0"
5
5
  end
data/lib/simplecov.rb CHANGED
@@ -231,12 +231,8 @@ module SimpleCov
231
231
  # rubocop:disable Metrics/MethodLength
232
232
  def result_exit_status(result, covered_percent)
233
233
  covered_percentages = result.covered_percentages.map { |percentage| percentage.floor(2) }
234
- if covered_percent < SimpleCov.minimum_coverage
235
- $stderr.printf(
236
- "Coverage (%<covered>.2f%%) is below the expected minimum coverage (%<minimum_coverage>.2f%%).\n",
237
- covered: covered_percent,
238
- minimum_coverage: SimpleCov.minimum_coverage
239
- )
234
+ if (minimum_violations = minimum_coverage_violated(result)).any?
235
+ report_minimum_violated(minimum_violations)
240
236
  SimpleCov::ExitCodes::MINIMUM_COVERAGE
241
237
  elsif covered_percentages.any? { |p| p < SimpleCov.minimum_coverage_by_file }
242
238
  $stderr.printf(
@@ -409,13 +405,40 @@ module SimpleCov
409
405
  def result_with_not_loaded_files
410
406
  @result = SimpleCov::Result.new(add_not_loaded_files(@result))
411
407
  end
408
+
409
+ def minimum_coverage_violated(result)
410
+ coverage_achieved = minimum_coverage.map do |criterion, percent|
411
+ {
412
+ criterion: criterion,
413
+ minimum_expected: percent,
414
+ actual: result.coverage_statistics[criterion].percent
415
+ }
416
+ end
417
+
418
+ coverage_achieved.select do |achieved|
419
+ achieved.fetch(:actual) < achieved.fetch(:minimum_expected)
420
+ end
421
+ end
422
+
423
+ def report_minimum_violated(violations)
424
+ violations.each do |violation|
425
+ $stderr.printf(
426
+ "%<criterion>s coverage (%<covered>.2f%%) is below the expected minimum coverage (%<minimum_coverage>.2f%%).\n",
427
+ covered: violation.fetch(:actual).floor(2),
428
+ minimum_coverage: violation.fetch(:minimum_expected),
429
+ criterion: violation.fetch(:criterion).capitalize
430
+ )
431
+ end
432
+ end
412
433
  end
413
434
  end
414
435
 
415
436
  $LOAD_PATH.unshift(File.join(File.dirname(__FILE__)))
416
437
  require "set"
438
+ require "forwardable"
417
439
  require "simplecov/configuration"
418
440
  SimpleCov.extend SimpleCov::Configuration
441
+ require "simplecov/coverage_statistics"
419
442
  require "simplecov/exit_codes"
420
443
  require "simplecov/profiles"
421
444
  require "simplecov/source_file/line"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: simplecov
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.18.0.beta2
4
+ version: 0.18.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Christoph Olszowka
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-01-19 00:00:00.000000000 Z
11
+ date: 2020-01-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: docile
@@ -30,14 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: 0.11.0.beta2
33
+ version: 0.11.0
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: 0.11.0.beta2
40
+ version: 0.11.0
41
41
  description: Code coverage for Ruby with a powerful configuration library and automatic
42
42
  merging of coverage across test suites
43
43
  email:
@@ -63,6 +63,7 @@ files:
63
63
  - lib/simplecov/combine/results_combiner.rb
64
64
  - lib/simplecov/command_guesser.rb
65
65
  - lib/simplecov/configuration.rb
66
+ - lib/simplecov/coverage_statistics.rb
66
67
  - lib/simplecov/defaults.rb
67
68
  - lib/simplecov/exit_codes.rb
68
69
  - lib/simplecov/file_list.rb
@@ -95,9 +96,9 @@ licenses:
95
96
  metadata:
96
97
  bug_tracker_uri: https://github.com/colszowka/simplecov/issues
97
98
  changelog_uri: https://github.com/colszowka/simplecov/blob/master/CHANGELOG.md
98
- documentation_uri: https://www.rubydoc.info/gems/simplecov/0.18.0.beta2
99
+ documentation_uri: https://www.rubydoc.info/gems/simplecov/0.18.0
99
100
  mailing_list_uri: https://groups.google.com/forum/#!forum/simplecov
100
- source_code_uri: https://github.com/colszowka/simplecov/tree/v0.18.0.beta2
101
+ source_code_uri: https://github.com/colszowka/simplecov/tree/v0.18.0
101
102
  post_install_message:
102
103
  rdoc_options: []
103
104
  require_paths:
@@ -109,9 +110,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
109
110
  version: 2.4.0
110
111
  required_rubygems_version: !ruby/object:Gem::Requirement
111
112
  requirements:
112
- - - ">"
113
+ - - ">="
113
114
  - !ruby/object:Gem::Version
114
- version: 1.3.1
115
+ version: '0'
115
116
  requirements: []
116
117
  rubygems_version: 3.1.2
117
118
  signing_key: