simplecov 0.18.0.beta3 → 0.18.5

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: a5ac4654ad44859d2e7b68cc5375314766127720e33d51080cc02120d35cc9cb
4
- data.tar.gz: 89ceeaf8b9b1a76bd05a4f078c50a80050bd247ef067164201f9721ce7378b5b
3
+ metadata.gz: 3d8b7307fd45fc7c4079efec764ee740fd7c0d302b8b78e969b11a08a2c94027
4
+ data.tar.gz: 4a39c982bdc16b038c8d4ff88e25498d34b3bf0a1eec6ed99ed2e444a0e5ada7
5
5
  SHA512:
6
- metadata.gz: 8afee26e98b841b002d097d589afbbc8b892452897423c927b817b51696bb7063e3f2e849f462434b765fb09970fbb6759d31d5362ea5c6e90eb65e82824eb78
7
- data.tar.gz: ed2f4ffab150fd1da883604eccec94a087184aeaadb4b4d6f1f7273efa6020d1034b9e2eebbc8b88a9c63c54d545ccd6db4f5db58ee29f8182703ed40281342e
6
+ metadata.gz: e7ce525f98807869dcfafa47e154aafa8047fe8e41685cab5d051400e615b056ee5629a0e18e0203f01af675761be3a57a413d9d4f8eb6a57b4260f4a6db6986
7
+ data.tar.gz: 3a0cc93b7ade626e432238d9ebc18514cbad27d6ea67fbd995c19a4cd40afe6c86486396531110f5f11e29cf65fad6dc81479ef9fdd68304f982257b05abf103
data/CHANGELOG.md CHANGED
@@ -1,3 +1,66 @@
1
+ 0.18.5 (2020-02-25)
2
+ ===================
3
+
4
+ Can you guess? Another bugfix release!
5
+
6
+ ## Bugfixes
7
+ * minitest won't crash if SimpleCov isn't loaded - aka don't execute SimpleCov code in the minitest plugin if SimpleCov isn't loaded. Thanks to [@edariedl](https://github.com/edariedl) for the report of the peculiar problem in [#877](https://github.com/colszowka/simplecov/issues/877).
8
+
9
+ 0.18.4 (2020-02-24)
10
+ ===================
11
+
12
+ Another small bugfix release 🙈 Fixes SimpleCov running with rspec-rails, which was broken due to our fixed minitest integration.
13
+
14
+ ## Bugfixes
15
+ * SimpleCov will run again correctly when used with rspec-rails. The excellent bug report [#873](https://github.com/colszowka/simplecov/issues/873) by [@odlp](https://github.com/odlp) perfectly details what went wrong. Thanks to [@adam12](https://github.com/adam12) for the fix [#874](https://github.com/colszowka/simplecov/pull/874).
16
+
17
+
18
+ 0.18.3 (2020-02-23)
19
+ ===========
20
+
21
+ Small bugfix release. It's especially recommended to upgrade simplecov-html as well because of bugs in the 0.12.0 release.
22
+
23
+ ## Bugfixes
24
+ * Fix a regression related to file encodings as special characters were missing. Furthermore we now respect the magic `# encoding: ...` comment and read files in the right encoding. Thanks ([@Tietew](https://github.com/Tietew)) - see [#866](https://github.com/colszowka/simplecov/pull/866)
25
+ * Use `Minitest.after_run` hook to trigger post-run hooks if `Minitest` is present. See [#756](https://github.com/colszowka/simplecov/pull/756) and [#855](https://github.com/colszowka/simplecov/pull/855) thanks ([@adam12](https://github.com/adam12))
26
+
27
+ 0.18.2 (2020-02-12)
28
+ ===================
29
+
30
+ Small release just to allow you to use the new simplecov-html.
31
+
32
+ ## Enhancements
33
+ * Relax simplecov-html requirement so that you're able to use [0.12.0](https://github.com/colszowka/simplecov-html/blob/master/CHANGELOG.md#0120-2020-02-12)
34
+
35
+ 0.18.1 (2020-01-31)
36
+ ===================
37
+
38
+ Small Bugfix release.
39
+
40
+ ## Bugfixes
41
+ * Just putting `# :nocov:` on top of a file or having an uneven number of them in general works again and acts as if ignoring until the end of the file. See [#846](https://github.com/colszowka/simplecov/issues/846) and thanks [@DannyBen](https://github.com/DannyBen) for the report.
42
+
43
+ 0.18.0 (2020-01-28)
44
+ ===================
45
+
46
+ Huge release! Highlights are support for branch coverage (Ruby 2.5+) and dropping support for EOL'ed Ruby versions (< 2.4).
47
+ Please also read the other beta patch notes.
48
+
49
+ You can run with branch coverage by putting `enable_coverage :branch` into your SimpleCov configuration (like the `SimpleCov.start do .. end` block)
50
+
51
+ ## Enhancements
52
+ * You can now define the minimum expected coverage by criterion like `minimum_coverage line: 90, branch: 80`
53
+ * Memoized some internal data structures that didn't change to reduce SimpleCov overhead
54
+ * 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
55
+
56
+ ## Bugfixes
57
+ * 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.
58
+ * 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
59
+
60
+ ## Noteworthy
61
+ * `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
62
+ * We needed to change an internal file format, which we use for merging across processes, to accommodate branch coverage. Sadly CodeClimate chose to use this file to report test coverage. Until a resolution is found the code climate test reporter won't work with SimpleCov for 0.18+, see [this issue on the test reporter](https://github.com/codeclimate/test-reporter/issues/413).
63
+
1
64
  0.18.0.beta3 (2020-01-20)
2
65
  ========================
3
66
 
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
 
@@ -725,6 +726,10 @@ You can define the minimum coverage percentage expected. SimpleCov will return n
725
726
 
726
727
  ```ruby
727
728
  SimpleCov.minimum_coverage 90
729
+ # same as above (the default is to check line coverage)
730
+ SimpleCov.minimum_coverage line: 90
731
+ # check for a minimum line coverage of 90% and minimum 80% branch coverage
732
+ SimpleCov.minimum_coverage line: 90, branch: 80
728
733
  ```
729
734
 
730
735
  ### Minimum coverage by file
@@ -736,6 +741,8 @@ to help ensure coverage is relatively consistent, rather than being skewed by pa
736
741
  SimpleCov.minimum_coverage_by_file 80
737
742
  ```
738
743
 
744
+ (not yet supported for branch coverage)
745
+
739
746
  ### Maximum coverage drop
740
747
 
741
748
  You can define the maximum coverage drop percentage at once. SimpleCov will return non-zero if exceeded.
@@ -744,6 +751,8 @@ You can define the maximum coverage drop percentage at once. SimpleCov will retu
744
751
  SimpleCov.maximum_coverage_drop 5
745
752
  ```
746
753
 
754
+ (not yet supported for branch coverage)
755
+
747
756
  ### Refuse dropping coverage
748
757
 
749
758
  You can also entirely refuse dropping coverage between test runs:
@@ -752,6 +761,8 @@ You can also entirely refuse dropping coverage between test runs:
752
761
  SimpleCov.refuse_coverage_drop
753
762
  ```
754
763
 
764
+ (not yet supported for branch coverage)
765
+
755
766
  ## Using your own formatter
756
767
 
757
768
  You can use your own formatter with:
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ # How minitest plugins. See https://github.com/colszowka/simplecov/pull/756 for why we need this.
4
+ # https://github.com/seattlerb/minitest#writing-extensions
5
+ module Minitest
6
+ def self.plugin_simplecov_init(_options)
7
+ if defined?(SimpleCov)
8
+ SimpleCov.external_at_exit = true
9
+
10
+ Minitest.after_run do
11
+ SimpleCov.at_exit_behavior
12
+ end
13
+ end
14
+ end
15
+ end
@@ -10,9 +10,9 @@ module SimpleCov
10
10
  module_function
11
11
 
12
12
  #
13
- # Return merged branches or the existed branche if other is missing.
13
+ # Return merged branches or the existed brach if other is missing.
14
14
  #
15
- # Branches inside files are always same if they exists, the difference only in coverage count.
15
+ # Branches inside files are always same if they exist, the difference only in coverage count.
16
16
  # Branch coverage report for any conditional case is built from hash, it's key is a condition and
17
17
  # it's body is a hash << keys from condition and value is coverage rate >>.
18
18
  # ex: branches =>{ [:if, 3, 8, 6, 8, 36] => {[:then, 4, 8, 6, 8, 12] => 1, [:else, 5, 8, 6, 8, 36]=>2}, other conditions...}
@@ -9,8 +9,8 @@ module SimpleCov
9
9
  #
10
10
  # Combine two coverage based on the given combiner_module.
11
11
  #
12
- # Combiners should always be called throught his interface,
13
- # as it takes care of short circuting of one of the coverages is nil.
12
+ # Combiners should always be called through this interface,
13
+ # as it takes care of short-circuiting of one of the coverages is nil.
14
14
  #
15
15
  # @return [Hash]
16
16
  def combine(combiner_module, coverage_a, coverage_b)
@@ -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 strength 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, missed, total)
39
+ @strength = compute_strength(total_strength, total)
40
+ end
41
+
42
+ private
43
+
44
+ def compute_percent(covered, missed, total)
45
+ return 100.0 if missed.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
@@ -22,13 +22,9 @@ end
22
22
  SimpleCov::CommandGuesser.original_run_command = "#{$PROGRAM_NAME} #{ARGV.join(' ')}"
23
23
 
24
24
  at_exit do
25
- # If we are in a different process than called start, don't interfere.
26
- next if SimpleCov.pid != Process.pid
25
+ next if SimpleCov.external_at_exit?
27
26
 
28
- # If SimpleCov is no longer running then don't run exit tasks
29
- next unless SimpleCov.running
30
-
31
- SimpleCov.run_exit_tasks!
27
+ SimpleCov.at_exit_behavior
32
28
  end
33
29
 
34
30
  # Autoload config from ~/.simplecov if present
@@ -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,7 +20,7 @@ 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
@@ -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
@@ -25,10 +25,18 @@ module SimpleCov
25
25
  def src
26
26
  # We intentionally read source code lazily to
27
27
  # suppress reading unused source code.
28
- @src ||= File.open(filename, "rb", &:readlines)
28
+ @src ||= load_source
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,153 @@ 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_src, _index| LinesClassifier.no_cov_line?(line_src) }
166
+
167
+ # if we have an uneven number of nocovs we assume they go to the
168
+ # end of the file, the source doesn't really matter
169
+ # Can't deal with this within the each_slice due to differing
170
+ # behavior in JRuby: jruby/jruby#6048
171
+ no_cov_lines << ["", src.size] if no_cov_lines.size.odd?
172
+
173
+ no_cov_lines.each_slice(2).map do |(_line_src_start, index_start), (_line_src_end, index_end)|
174
+ index_start..index_end
175
+ end
176
+ end
177
+
178
+ def load_source
179
+ lines = []
180
+ # The default encoding is UTF-8
181
+ File.open(filename, "rb:UTF-8") do |file|
182
+ current_line = file.gets
183
+
184
+ if shebang?(current_line)
185
+ lines << current_line
186
+ current_line = file.gets
187
+ end
188
+
189
+ read_lines(file, lines, current_line)
190
+ end
191
+ end
192
+
193
+ SHEBANG_REGEX = /\A#!/.freeze
194
+ def shebang?(line)
195
+ SHEBANG_REGEX.match?(line)
196
+ end
197
+
198
+ def read_lines(file, lines, current_line)
199
+ return lines unless current_line
200
+
201
+ set_encoding_based_on_magic_comment(file, current_line)
202
+ lines.concat([current_line], ensure_remove_undefs(file.readlines))
203
+ end
204
+
205
+ RUBY_FILE_ENCODING_MAGIC_COMMENT_REGEX = /\A#\s*(?:-\*-)?\s*(?:en)?coding:\s*(\S+)\s*(?:-\*-)?\s*\z/.freeze
206
+ def set_encoding_based_on_magic_comment(file, line)
207
+ # Check for encoding magic comment
208
+ # Encoding magic comment must be placed at first line except for shebang
209
+ if (match = RUBY_FILE_ENCODING_MAGIC_COMMENT_REGEX.match(line))
210
+ file.set_encoding(match[1], "UTF-8")
211
+ end
212
+ end
213
+
214
+ def ensure_remove_undefs(file_lines)
215
+ # invalid/undef replace are technically not really necessary but nice to
216
+ # have and work around a JRuby incompatibility. Also moved here from
217
+ # simplecov-html to have encoding shenaningans in one place. See #866
218
+ # also setting these option on `file.set_encoding` doesn't seem to work
219
+ # properly so it has to be done here.
220
+ file_lines.each { |line| line.encode!("UTF-8", invalid: :replace, undef: :replace) }
221
+ end
222
+
223
+ def build_lines
224
+ coverage_exceeding_source_warn if coverage_data["lines"].size > src.size
225
+ lines = src.map.with_index(1) do |src, i|
226
+ SimpleCov::SourceFile::Line.new(src, i, coverage_data["lines"][i - 1])
227
+ end
228
+ process_skipped_lines(lines)
229
+ end
230
+
231
+ def process_skipped_lines(lines)
232
+ # the array the lines are kept in is 0-based whereas the line numbers in the nocov
233
+ # chunks are 1-based and are expected to be like this in other parts (and it's also
234
+ # arguably more understandable)
235
+ no_cov_chunks.each { |chunk| lines[(chunk.begin - 1)..(chunk.end - 1)].each(&:skipped!) }
236
+
237
+ lines
238
+ end
239
+
240
+ def lines_strength
241
+ lines.map(&:coverage).compact.reduce(:+)
242
+ end
243
+
244
+ # Warning to identify condition from Issue #56
245
+ def coverage_exceeding_source_warn
246
+ warn "Warning: coverage data provided by Coverage [#{coverage_data['lines'].size}] exceeds number of lines in #{filename} [#{src.size}]"
247
+ end
248
+
249
+ #
250
+ # Build full branches report
251
+ # Root branches represent the wrapper of all condition state that
252
+ # have inside the branches
253
+ #
254
+ # @return [Hash]
255
+ #
256
+ def build_branches_report
257
+ branches.reject(&:skipped?).each_with_object({}) do |branch, coverage_statistics|
258
+ coverage_statistics[branch.report_line] ||= []
259
+ coverage_statistics[branch.report_line] << branch.report
260
+ end
261
+ end
165
262
 
166
263
  #
167
264
  # Call recursive method that transform our static hash to array of objects
168
265
  # @return [Array]
169
266
  #
170
267
  def build_branches
171
- coverage_branch_data = coverage.fetch("branches", {})
268
+ coverage_branch_data = coverage_data.fetch("branches", {})
172
269
  branches = coverage_branch_data.flat_map do |condition, coverage_branches|
173
270
  build_branches_from(condition, coverage_branches)
174
271
  end
@@ -230,53 +327,23 @@ module SimpleCov
230
327
  )
231
328
  end
232
329
 
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?
330
+ def line_coverage_statistics
331
+ {
332
+ line: CoverageStatistics.new(
333
+ total_strength: lines_strength,
334
+ covered: covered_lines.size,
335
+ missed: missed_lines.size
336
+ )
337
+ }
266
338
  end
267
339
 
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
340
+ def branch_coverage_statistics
341
+ {
342
+ branch: CoverageStatistics.new(
343
+ covered: covered_branches.size,
344
+ missed: missed_branches.size
345
+ )
346
+ }
280
347
  end
281
348
  end
282
349
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SimpleCov
4
- VERSION = "0.18.0.beta3"
4
+ VERSION = "0.18.5"
5
5
  end
data/lib/simplecov.rb CHANGED
@@ -27,6 +27,11 @@ module SimpleCov
27
27
  attr_accessor :pid
28
28
  attr_reader :exit_exception
29
29
 
30
+ # Basically, should we take care of at_exit behavior or something else?
31
+ # Used by the minitest plugin. See lib/minitest/simplecov_plugin.rb
32
+ attr_accessor :external_at_exit
33
+ alias external_at_exit? external_at_exit
34
+
30
35
  #
31
36
  # Sets up SimpleCov to run against your project.
32
37
  # You can optionally specify a profile to use as well as configuration with a block:
@@ -189,6 +194,14 @@ module SimpleCov
189
194
  end
190
195
  end
191
196
 
197
+ def at_exit_behavior
198
+ # If we are in a different process than called start, don't interfere.
199
+ return if SimpleCov.pid != Process.pid
200
+
201
+ # If SimpleCov is no longer running then don't run exit tasks
202
+ SimpleCov.run_exit_tasks! if SimpleCov.running
203
+ end
204
+
192
205
  # @api private
193
206
  #
194
207
  # Called from at_exit block
@@ -231,12 +244,8 @@ module SimpleCov
231
244
  # rubocop:disable Metrics/MethodLength
232
245
  def result_exit_status(result, covered_percent)
233
246
  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
- )
247
+ if (minimum_violations = minimum_coverage_violated(result)).any?
248
+ report_minimum_violated(minimum_violations)
240
249
  SimpleCov::ExitCodes::MINIMUM_COVERAGE
241
250
  elsif covered_percentages.any? { |p| p < SimpleCov.minimum_coverage_by_file }
242
251
  $stderr.printf(
@@ -409,13 +418,40 @@ module SimpleCov
409
418
  def result_with_not_loaded_files
410
419
  @result = SimpleCov::Result.new(add_not_loaded_files(@result))
411
420
  end
421
+
422
+ def minimum_coverage_violated(result)
423
+ coverage_achieved = minimum_coverage.map do |criterion, percent|
424
+ {
425
+ criterion: criterion,
426
+ minimum_expected: percent,
427
+ actual: result.coverage_statistics[criterion].percent
428
+ }
429
+ end
430
+
431
+ coverage_achieved.select do |achieved|
432
+ achieved.fetch(:actual) < achieved.fetch(:minimum_expected)
433
+ end
434
+ end
435
+
436
+ def report_minimum_violated(violations)
437
+ violations.each do |violation|
438
+ $stderr.printf(
439
+ "%<criterion>s coverage (%<covered>.2f%%) is below the expected minimum coverage (%<minimum_coverage>.2f%%).\n",
440
+ covered: violation.fetch(:actual).floor(2),
441
+ minimum_coverage: violation.fetch(:minimum_expected),
442
+ criterion: violation.fetch(:criterion).capitalize
443
+ )
444
+ end
445
+ end
412
446
  end
413
447
  end
414
448
 
415
449
  $LOAD_PATH.unshift(File.join(File.dirname(__FILE__)))
416
450
  require "set"
451
+ require "forwardable"
417
452
  require "simplecov/configuration"
418
453
  SimpleCov.extend SimpleCov::Configuration
454
+ require "simplecov/coverage_statistics"
419
455
  require "simplecov/exit_codes"
420
456
  require "simplecov/profiles"
421
457
  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.beta3
4
+ version: 0.18.5
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-20 00:00:00.000000000 Z
11
+ date: 2020-02-25 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'
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'
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:
@@ -55,6 +55,7 @@ files:
55
55
  - doc/alternate-formatters.md
56
56
  - doc/commercial-services.md
57
57
  - doc/editor-integration.md
58
+ - lib/minitest/simplecov_plugin.rb
58
59
  - lib/simplecov.rb
59
60
  - lib/simplecov/combine.rb
60
61
  - lib/simplecov/combine/branches_combiner.rb
@@ -63,6 +64,7 @@ files:
63
64
  - lib/simplecov/combine/results_combiner.rb
64
65
  - lib/simplecov/command_guesser.rb
65
66
  - lib/simplecov/configuration.rb
67
+ - lib/simplecov/coverage_statistics.rb
66
68
  - lib/simplecov/defaults.rb
67
69
  - lib/simplecov/exit_codes.rb
68
70
  - lib/simplecov/file_list.rb
@@ -95,9 +97,9 @@ licenses:
95
97
  metadata:
96
98
  bug_tracker_uri: https://github.com/colszowka/simplecov/issues
97
99
  changelog_uri: https://github.com/colszowka/simplecov/blob/master/CHANGELOG.md
98
- documentation_uri: https://www.rubydoc.info/gems/simplecov/0.18.0.beta3
100
+ documentation_uri: https://www.rubydoc.info/gems/simplecov/0.18.5
99
101
  mailing_list_uri: https://groups.google.com/forum/#!forum/simplecov
100
- source_code_uri: https://github.com/colszowka/simplecov/tree/v0.18.0.beta3
102
+ source_code_uri: https://github.com/colszowka/simplecov/tree/v0.18.5
101
103
  post_install_message:
102
104
  rdoc_options: []
103
105
  require_paths:
@@ -109,9 +111,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
109
111
  version: 2.4.0
110
112
  required_rubygems_version: !ruby/object:Gem::Requirement
111
113
  requirements:
112
- - - ">"
114
+ - - ">="
113
115
  - !ruby/object:Gem::Version
114
- version: 1.3.1
116
+ version: '0'
115
117
  requirements: []
116
118
  rubygems_version: 3.1.2
117
119
  signing_key: