simplecov 0.18.0.beta1 → 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: 8dd6ab6b1c33e8b2503dc00691a8ae500ae0caf885fc2dba52bbe102d696d926
4
- data.tar.gz: 41c70724c0beb18bd66f665cb078a8b9cf95d1dbb770bf1eb0601156c12370e0
3
+ metadata.gz: ce12d99507e5e8bd75399d80855c495907b8dda937fb5e2a33d2895e9f74e370
4
+ data.tar.gz: ae53aefe53c30c3b1d6c1c0b70aa34c1193642d4a879f77a0c3522960fc92773
5
5
  SHA512:
6
- metadata.gz: ba1dcb50ce7ef6765a9f519bda006388d587c67cd103bcf6d0f4bd4d4427ebef240de13772e8925f62d70dd232310bd705b66fbda1c39c25444dcd329b9dedc1
7
- data.tar.gz: cc1160ba31aa5ecdcb46a255e3147e78f649789d542d1363343fec803ec9c5c96759272990ba67ceb75a0a07116325f962a69354d8a01477a905b491bba86275
6
+ metadata.gz: e9a60e36936b9b5018a6998ade1742373bfa67b96688411ab42c90ac82ca6280abe9d407dcef4d45398ed28803ca876d6f82ce0cabc878d6141897cca4dab91e
7
+ data.tar.gz: 7056045c2385516ce0029af14558bd95c9c84bc85319d82bddfcf2011b7db87a5291168bb739c1ff02d8830e133065b3f2fda605e03b7c2e6ed094b61bff9240
data/CHANGELOG.md CHANGED
@@ -1,3 +1,45 @@
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)
29
+ ===================
30
+
31
+ ## Enhancements
32
+ * only turn on the requested coverage criteria (when activating branch coverage before SimpleCov would also instruct Ruby to take Method coverage)
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!
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))
35
+ * Allow manual collation of result sets through the `SimpleCov.collate` entrypoint. See the README for more details (thanks [@ticky](https://github.com/ticky))
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)
37
+ * Stop symbolizing all keys when loading cache (should lead to be faster and consume less memory)
38
+ * Cache whether we can use/are using branch coverage (should be slightly faster)
39
+
40
+ ## Bugfixes
41
+ * Fix a crash that happened when an old version of our internal cache file `.resultset.json` was still present
42
+
1
43
  0.18.0.beta1 (2020-01-05)
2
44
  ===================
3
45
 
@@ -8,7 +50,7 @@ This release is still beta because we'd love for you to test out branch coverage
8
50
  On a personal note from [@PragTob](https://github.com/PragTob/) thanks to [ruby together](https://rubytogether.org/) for sponsoring this work on SimpleCov making it possible to deliver this and subsequent releases.
9
51
 
10
52
  ## Breaking
11
- * Dropped official support for all EOL'ed rubies meaning we only officially support 2.4+ - older rubies might still work but are not guaranteed to do so. Older release should still work. (thanks [@deivid-rodriguez](https://github.com/deivid-rodriguez))
53
+ * Dropped support for all EOL'ed rubies meaning we only support 2.4+. Simplecov can no longer be installed on older rubies, but older simplecov releases should still work. (thanks [@deivid-rodriguez](https://github.com/deivid-rodriguez))
12
54
  * Dropped the `rake simplecov` task that "magically" integreated with rails. It was always undocumented, caused some issues and [had some issues](https://github.com/colszowka/simplecov/issues/689#issuecomment-561572327). Use the integration as described in the README please :)
13
55
 
14
56
  ## Enhancements
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
 
@@ -170,6 +171,17 @@ to use SimpleCov with them. Here's an overview of the known ones:
170
171
  <a href="https://github.com/colszowka/simplecov/pull/185">#185</a>
171
172
  </td>
172
173
  </tr>
174
+ <tr>
175
+ <th>
176
+ knapsack_pro
177
+ </th>
178
+ <td>
179
+ To make SimpleCov work with Knapsack Pro Queue Mode to split tests in parallel on CI jobs you need to provide CI node index number to the <code>SimpleCov.command_name</code> in <code>KnapsackPro::Hooks::Queue.before_queue</code> hook.
180
+ </td>
181
+ <td>
182
+ <a href="https://knapsackpro.com/faq/question/how-to-use-simplecov-in-queue-mode">Tip</a>
183
+ </td>
184
+ </tr>
173
185
  <tr>
174
186
  <th>
175
187
  RubyMine
@@ -308,12 +320,16 @@ return if number.odd?
308
320
  ```
309
321
 
310
322
  If all the code in that method was covered you'd never know if the guard clause was ever
311
- triggered with line coverage as just evaluating the condition marks it as covered.
323
+ triggered! With line coverage as just evaluating the condition marks it as covered.
312
324
 
313
- In the HTML report the lines of code will be annotated like `hit_count, positive_or_negative_branch`:
325
+ In the HTML report the lines of code will be annotated like `branch_type: hit_count`:
314
326
 
315
- * `2, +` - the positive branch was executed twice
316
- * `0, -` - the negative branch was never executed
327
+ * `then: 2` - the then branch (of an `if`) was executed twice
328
+ * `else: 0` - the else branch (of an `if` or `case`) was never executed
329
+
330
+ Not that even if you don't declare an `else` branch it will still show up in the coverage
331
+ reports meaning that the condition of the `if` was not hit or that no `when` of `case`
332
+ was hit during the test runs.
317
333
 
318
334
  **Is branch coverage strictly better?** No. Branch coverage really only concerns itself with
319
335
  conditionals - meaning coverage of sequential code is of no interest to it. A file without
@@ -461,12 +477,11 @@ end
461
477
 
462
478
  You normally want to have your coverage analyzed across ALL of your test suites, right?
463
479
 
464
- Simplecov automatically caches coverage results in your (coverage_path)/.resultset.json. Those results will then
465
- be automatically merged when generating the result, so when coverage is set up properly for Cucumber and your
466
- unit / functional / integration tests, all of those test suites will be taken into account when building the
467
- coverage report.
468
-
469
- There are two things to note here though:
480
+ Simplecov automatically caches coverage results in your
481
+ (coverage_path)/.resultset.json, and will merge or override those with
482
+ subsequent runs, depending on whether simplecov considers those subsequent runs
483
+ as different test suites or as the same test suite as the cached results. To
484
+ make this distinction, simplecov has the concept of "test suite names".
470
485
 
471
486
  ### Test suite names
472
487
 
@@ -520,14 +535,84 @@ SimpleCov.command_name "features" + (ENV['TEST_ENV_NUMBER'] || '')
520
535
 
521
536
  [simplecov-html] prints the used test suites in the footer of the generated coverage report.
522
537
 
523
- ### Timeout for merge
524
538
 
525
- Of course, your cached coverage data is likely to become invalid at some point. Thus, result sets that are older than
526
- `SimpleCov.merge_timeout` will not be used any more. By default, the timeout is 600 seconds (10 minutes), and you can
527
- raise (or lower) it by specifying `SimpleCov.merge_timeout 3600` (1 hour), or, inside a configure/start block, with
528
- just `merge_timeout 3600`.
539
+ ### Merging test runs under the same execution environment
540
+
541
+ Test results are automatically merged with previous runs in the same execution
542
+ environment when generating the result, so when coverage is set up properly for
543
+ Cucumber and your unit / functional / integration tests, all of those test
544
+ suites will be taken into account when building the coverage report.
545
+
546
+ #### Timeout for merge
547
+
548
+ Of course, your cached coverage data is likely to become invalid at some point. Thus, when automatically merging
549
+ subsequent test runs, result sets that are older than `SimpleCov.merge_timeout` will not be used any more. By default,
550
+ the timeout is 600 seconds (10 minutes), and you can raise (or lower) it by specifying `SimpleCov.merge_timeout 3600`
551
+ (1 hour), or, inside a configure/start block, with just `merge_timeout 3600`.
552
+
553
+ You can deactivate this automatic merging altogether with `SimpleCov.use_merging false`.
554
+
555
+ ### Merging test runs under different execution environments
556
+
557
+ If your tests are done in parallel across multiple build machines, you can fetch them all and merge them into a single
558
+ result set using the `SimpleCov.collate` method. This can be added to a Rakefile or script file, having downloaded a set of
559
+ `.resultset.json` files from each parallel test run.
560
+
561
+ ```ruby
562
+ # lib/tasks/coverage_report.rake
563
+ namespace :coverage do
564
+ desc "Collates all result sets generated by the different test runners"
565
+ task :report do
566
+ require 'simplecov'
567
+
568
+ SimpleCov.collate Dir["simplecov-resultset-*/.resultset.json"]
569
+ end
570
+ end
571
+ ```
572
+
573
+ `SimpleCov.collate` also takes an optional simplecov profile and an optional
574
+ block for configuration, just the same as `SimpleCov.start` or
575
+ `SimpleCov.configure`. This means you can configure a separate formatter for
576
+ the collated output. For instance, you can make the formatter in
577
+ `SimpleCov.start` the `SimpleCov::Formatter::SimpleFormatter`, and only use more
578
+ complex formatters in the final `SimpleCov.collate` run.
579
+
580
+ ```ruby
581
+ # spec/spec_helper.rb
582
+ require 'simplecov'
583
+
584
+ SimpleCov.start 'rails' do
585
+ # Disambiguates individual test runs
586
+ command_name "Job #{ENV["TEST_ENV_NUMBER"]}" if ENV["TEST_ENV_NUMBER"]
587
+
588
+ if ENV['CI']
589
+ formatter SimpleCov::Formatter::SimpleFormatter
590
+ else
591
+ formatter SimpleCov::Formatter::MultiFormatter.new([
592
+ SimpleCov::Formatter::SimpleFormatter,
593
+ SimpleCov::Formatter::HTMLFormatter
594
+ ])
595
+ end
529
596
 
530
- You can deactivate merging altogether with `SimpleCov.use_merging false`.
597
+ track_files "**/*.rb"
598
+ end
599
+ ```
600
+
601
+ ```ruby
602
+ # lib/tasks/coverage_report.rake
603
+ namespace :coverage do
604
+ task :report do
605
+ require 'simplecov'
606
+
607
+ SimpleCov.collate Dir["simplecov-resultset-*/.resultset.json"], 'rails' do
608
+ formatter SimpleCov::Formatter::MultiFormatter.new([
609
+ SimpleCov::Formatter::SimpleFormatter,
610
+ SimpleCov::Formatter::HTMLFormatter
611
+ ])
612
+ end
613
+ end
614
+ end
615
+ ```
531
616
 
532
617
  ## Running coverage only on demand
533
618
 
@@ -15,10 +15,9 @@ module SimpleCov
15
15
  # @return [Hash]
16
16
  #
17
17
  def combine(coverage_a, coverage_b)
18
- {
19
- :lines => Combine.combine(LinesCombiner, coverage_a[:lines], coverage_b[:lines]),
20
- :branches => Combine.combine(BranchesCombiner, coverage_a[:branches], coverage_b[:branches])
21
- }
18
+ combination = {"lines" => Combine.combine(LinesCombiner, coverage_a["lines"], coverage_b["lines"])}
19
+ combination["branches"] = Combine.combine(BranchesCombiner, coverage_a["branches"], coverage_b["branches"]) if SimpleCov.branch_coverage?
20
+ combination
22
21
  end
23
22
  end
24
23
  end
@@ -20,26 +20,26 @@ module SimpleCov
20
20
  # @return [Hash]
21
21
  #
22
22
  def combine(*results)
23
- results.reduce({}) do |result, next_result|
24
- combine_result_sets(result, next_result)
23
+ results.reduce({}) do |combined_results, next_result|
24
+ combine_result_sets(combined_results, next_result)
25
25
  end
26
26
  end
27
27
 
28
28
  #
29
29
  # Manage combining results on files level
30
30
  #
31
- # @param [Hash] result_a
32
- # @param [Hash] result_b
31
+ # @param [Hash] combined_results
32
+ # @param [Hash] result
33
33
  #
34
34
  # @return [Hash]
35
35
  #
36
- def combine_result_sets(result_a, result_b)
37
- results_files = result_a.keys | result_b.keys
36
+ def combine_result_sets(combined_results, result)
37
+ results_files = combined_results.keys | result.keys
38
38
 
39
- results_files.each_with_object({}) do |file_name, combined_results|
40
- combined_results[file_name] = combine_file_coverage(
41
- result_a[file_name],
42
- result_b[file_name]
39
+ results_files.each_with_object({}) do |file_name, file_combination|
40
+ file_combination[file_name] = combine_file_coverage(
41
+ combined_results[file_name],
42
+ result[file_name]
43
43
  )
44
44
  end
45
45
  end
@@ -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
  #
@@ -313,7 +320,7 @@ module SimpleCov
313
320
  # * :line - coverage based on lines aka has this line been executed?
314
321
  # * :branch - coverage based on branches aka has this branch (think conditions) been executed?
315
322
  #
316
- # If not set the default is is `:line`
323
+ # If not set the default is `:line`
317
324
  #
318
325
  # @param [Symbol] criterion
319
326
  #
@@ -335,23 +342,48 @@ module SimpleCov
335
342
  @coverage_criteria ||= Set[DEFAULT_COVERAGE_CRITERION]
336
343
  end
337
344
 
345
+ def coverage_criterion_enabled?(criterion)
346
+ coverage_criteria.member?(criterion)
347
+ end
348
+
349
+ def clear_coverage_criteria
350
+ @coverage_criteria = nil
351
+ end
352
+
338
353
  def branch_coverage?
339
- branch_coverage_supported? && coverage_criteria.member?(:branch)
354
+ branch_coverage_supported? && coverage_criterion_enabled?(:branch)
340
355
  end
341
356
 
342
- def branch_coverage_supported?
343
- require "coverage"
344
- !Coverage.method(:start).arity.zero?
357
+ def coverage_start_arguments_supported?
358
+ # safe to cache as within one process this value should never
359
+ # change
360
+ return @coverage_start_arguments_supported if defined?(@coverage_start_arguments_supported)
361
+
362
+ @coverage_start_arguments_supported = begin
363
+ require "coverage"
364
+ !Coverage.method(:start).arity.zero?
365
+ end
345
366
  end
346
367
 
368
+ alias branch_coverage_supported? coverage_start_arguments_supported?
369
+
347
370
  private
348
371
 
349
- def raise_if_criterion_unsupported(criterion)
350
- 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
351
379
  end
352
380
 
353
- def raise_criterion_unsupported(criterion)
354
- 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
355
387
  end
356
388
 
357
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
@@ -25,7 +25,9 @@ at_exit do
25
25
  # If we are in a different process than called start, don't interfere.
26
26
  next if SimpleCov.pid != Process.pid
27
27
 
28
- SimpleCov.set_exit_exception
28
+ # If SimpleCov is no longer running then don't run exit tasks
29
+ next unless SimpleCov.running
30
+
29
31
  SimpleCov.run_exit_tasks!
30
32
  end
31
33
 
@@ -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
@@ -15,7 +15,7 @@ module SimpleCov
15
15
  json = File.read(last_run_path)
16
16
  return nil if json.strip.empty?
17
17
 
18
- JSON.parse(json)
18
+ JSON.parse(json, symbolize_names: true)
19
19
  end
20
20
 
21
21
  def write(json)
@@ -20,15 +20,16 @@ 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|
31
- SimpleCov::SourceFile.new(filename, JSON.parse(JSON.dump(coverage), :symbolize_names => true)) if File.file?(filename.to_s)
29
+ result = adapt_result(original_result)
30
+ @original_result = result.freeze
31
+ @files = SimpleCov::FileList.new(result.map do |filename, coverage|
32
+ SimpleCov::SourceFile.new(filename, JSON.parse(JSON.dump(coverage))) if File.file?(filename)
32
33
  end.compact.sort_by(&:filename))
33
34
  filter!
34
35
  end
@@ -61,36 +62,51 @@ module SimpleCov
61
62
 
62
63
  # Returns a hash representation of this Result that can be used for marshalling it into JSON
63
64
  def to_hash
64
- {command_name => {"coverage" => coverage, "timestamp" => created_at.to_i}}
65
+ {
66
+ command_name => {
67
+ "coverage" => coverage,
68
+ "timestamp" => created_at.to_i
69
+ }
70
+ }
65
71
  end
66
72
 
67
73
  # Loads a SimpleCov::Result#to_hash dump
68
74
  def self.from_hash(hash)
69
75
  command_name, data = hash.first
70
76
 
71
- result = SimpleCov::Result.new(
72
- symbolize_names_of_coverage_results(data["coverage"])
73
- )
77
+ result = SimpleCov::Result.new(data["coverage"])
74
78
 
75
79
  result.command_name = command_name
76
80
  result.created_at = Time.at(data["timestamp"])
77
81
  result
78
82
  end
79
83
 
80
- # Manage symbolize the keys of coverage hash.
81
- # JSON.parse gives coverage hash with stringified keys what breaks some logics
82
- # inside the process that expects them as symboles.
83
- #
84
- # @return [Hash]
85
- def self.symbolize_names_of_coverage_results(coverage_data)
86
- coverage_data.each_with_object({}) do |(file_name, file_coverage_result), coverage_results|
87
- coverage_results[file_name] = file_coverage_result.each_with_object({}) do |(k, v), cov_elem|
88
- cov_elem[k.to_sym] = v
89
- end
84
+ private
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
90
95
  end
91
96
  end
92
97
 
93
- private
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
94
110
 
95
111
  def coverage
96
112
  keys = original_result.keys & filenames
@@ -20,7 +20,7 @@ module SimpleCov
20
20
 
21
21
  result.each_with_object({}) do |(file_name, cover_statistic), adapted_result|
22
22
  if cover_statistic.is_a?(Array)
23
- adapted_result.merge!(file_name => {:lines => cover_statistic})
23
+ adapted_result.merge!(file_name => {"lines" => cover_statistic})
24
24
  else
25
25
  adapted_result.merge!(file_name => cover_statistic)
26
26
  end