simplecov 1.1.0 → 1.2.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.
Files changed (196) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +165 -57
  3. data/exe/simplecov +0 -3
  4. data/lib/minitest/simplecov_plugin.rb +12 -9
  5. data/lib/simplecov/atomic_file.rb +16 -19
  6. data/lib/simplecov/autostart.rb +5 -7
  7. data/lib/simplecov/baseline/parser.rb +58 -0
  8. data/lib/simplecov/baseline.rb +170 -0
  9. data/lib/simplecov/cli/affected/changed_files.rb +57 -0
  10. data/lib/simplecov/cli/affected/selection.rb +117 -0
  11. data/lib/simplecov/cli/affected.rb +148 -0
  12. data/lib/simplecov/cli/badge/svg.rb +70 -0
  13. data/lib/simplecov/cli/badge.rb +62 -0
  14. data/lib/simplecov/cli/clean.rb +23 -24
  15. data/lib/simplecov/cli/command_helpers.rb +55 -19
  16. data/lib/simplecov/cli/completions/scripts.rb +114 -0
  17. data/lib/simplecov/cli/completions.rb +83 -0
  18. data/lib/simplecov/cli/coverage.rb +25 -28
  19. data/lib/simplecov/cli/coverage_file.rb +58 -14
  20. data/lib/simplecov/cli/dead_code/output.rb +100 -0
  21. data/lib/simplecov/cli/dead_code.rb +139 -0
  22. data/lib/simplecov/cli/diff/output.rb +45 -0
  23. data/lib/simplecov/cli/diff.rb +35 -67
  24. data/lib/simplecov/cli/dotfile.rb +83 -38
  25. data/lib/simplecov/cli/git.rb +41 -0
  26. data/lib/simplecov/cli/history/output.rb +135 -0
  27. data/lib/simplecov/cli/history.rb +67 -0
  28. data/lib/simplecov/cli/merge.rb +34 -39
  29. data/lib/simplecov/cli/open.rb +12 -15
  30. data/lib/simplecov/cli/patch/changed_lines.rb +149 -0
  31. data/lib/simplecov/cli/patch/output.rb +135 -0
  32. data/lib/simplecov/cli/patch.rb +169 -0
  33. data/lib/simplecov/cli/ratchet/output.rb +44 -0
  34. data/lib/simplecov/cli/ratchet.rb +83 -0
  35. data/lib/simplecov/cli/report.rb +18 -24
  36. data/lib/simplecov/cli/run.rb +8 -9
  37. data/lib/simplecov/cli/serve/report_preparer.rb +7 -6
  38. data/lib/simplecov/cli/serve/static_file_handler.rb +62 -56
  39. data/lib/simplecov/cli/serve.rb +27 -24
  40. data/lib/simplecov/cli/show/annotator.rb +87 -0
  41. data/lib/simplecov/cli/show/sweep.rb +36 -0
  42. data/lib/simplecov/cli/show.rb +133 -0
  43. data/lib/simplecov/cli/status/facts.rb +87 -0
  44. data/lib/simplecov/cli/status.rb +93 -0
  45. data/lib/simplecov/cli/tests/redundancy.rb +78 -0
  46. data/lib/simplecov/cli/tests.rb +148 -0
  47. data/lib/simplecov/cli/uncovered/misses.rb +37 -0
  48. data/lib/simplecov/cli/uncovered.rb +70 -32
  49. data/lib/simplecov/cli/usage.rb +152 -0
  50. data/lib/simplecov/cli/watch/live_report.rb +73 -0
  51. data/lib/simplecov/cli/watch/narrator.rb +55 -0
  52. data/lib/simplecov/cli/watch/poller.rb +45 -0
  53. data/lib/simplecov/cli/watch/session.rb +134 -0
  54. data/lib/simplecov/cli/watch/test_plan.rb +43 -0
  55. data/lib/simplecov/cli/watch.rb +85 -0
  56. data/lib/simplecov/cli.rb +43 -92
  57. data/lib/simplecov/color.rb +21 -35
  58. data/lib/simplecov/combine/branches_combiner.rb +7 -31
  59. data/lib/simplecov/combine/coverage_accumulator.rb +49 -142
  60. data/lib/simplecov/combine/identity_interner.rb +10 -13
  61. data/lib/simplecov/combine/interned_counts.rb +7 -9
  62. data/lib/simplecov/combine/lines_combiner.rb +20 -44
  63. data/lib/simplecov/combine/methods_combiner.rb +9 -35
  64. data/lib/simplecov/combine/results_combiner.rb +4 -21
  65. data/lib/simplecov/combine.rb +0 -6
  66. data/lib/simplecov/command_guesser.rb +20 -34
  67. data/lib/simplecov/configuration/baseline.rb +31 -0
  68. data/lib/simplecov/configuration/coverage.rb +91 -79
  69. data/lib/simplecov/configuration/coverage_criteria.rb +43 -54
  70. data/lib/simplecov/configuration/deprecations.rb +21 -0
  71. data/lib/simplecov/configuration/eval_coverage.rb +6 -10
  72. data/lib/simplecov/configuration/filters.rb +40 -73
  73. data/lib/simplecov/configuration/formatting.rb +74 -83
  74. data/lib/simplecov/configuration/groups.rb +19 -13
  75. data/lib/simplecov/configuration/history.rb +44 -0
  76. data/lib/simplecov/configuration/ignored_entries.rb +31 -22
  77. data/lib/simplecov/configuration/merging.rb +94 -62
  78. data/lib/simplecov/configuration/missed_caps.rb +51 -0
  79. data/lib/simplecov/configuration/production.rb +23 -0
  80. data/lib/simplecov/configuration/test_tracking.rb +48 -0
  81. data/lib/simplecov/configuration/thresholds.rb +33 -62
  82. data/lib/simplecov/configuration/view_coverage.rb +38 -0
  83. data/lib/simplecov/configuration.rb +70 -76
  84. data/lib/simplecov/configuration_error.rb +5 -0
  85. data/lib/simplecov/context_map/union.rb +81 -0
  86. data/lib/simplecov/context_map.rb +187 -0
  87. data/lib/simplecov/coverage_json.rb +2 -5
  88. data/lib/simplecov/coverage_statistics.rb +14 -24
  89. data/lib/simplecov/coverage_violations.rb +157 -64
  90. data/lib/simplecov/current_run.rb +61 -0
  91. data/lib/simplecov/defaults.rb +4 -23
  92. data/lib/simplecov/deprecation.rb +45 -27
  93. data/lib/simplecov/directive.rb +46 -62
  94. data/lib/simplecov/exit_codes/baseline_check.rb +46 -0
  95. data/lib/simplecov/exit_codes/check.rb +17 -11
  96. data/lib/simplecov/exit_codes/exit_code_handling.rb +21 -8
  97. data/lib/simplecov/exit_codes/maximum_coverage_drop_check.rb +7 -10
  98. data/lib/simplecov/exit_codes/maximum_missed_check.rb +26 -0
  99. data/lib/simplecov/exit_codes/maximum_missed_per_file_check.rb +34 -0
  100. data/lib/simplecov/exit_codes/maximum_overall_coverage_check.rb +7 -12
  101. data/lib/simplecov/exit_codes/minimum_coverage_by_file_check.rb +11 -10
  102. data/lib/simplecov/exit_codes/minimum_coverage_by_group_check.rb +7 -9
  103. data/lib/simplecov/exit_codes/minimum_overall_coverage_check.rb +16 -17
  104. data/lib/simplecov/exit_codes.rb +17 -7
  105. data/lib/simplecov/exit_handling.rb +94 -59
  106. data/lib/simplecov/file_list.rb +20 -51
  107. data/lib/simplecov/filter.rb +43 -66
  108. data/lib/simplecov/formatter/base.rb +38 -51
  109. data/lib/simplecov/formatter/baseline_formatter.rb +98 -0
  110. data/lib/simplecov/formatter/coverage_json_writer.rb +29 -31
  111. data/lib/simplecov/formatter/html_formatter/public/index.html +17 -10
  112. data/lib/simplecov/formatter/html_formatter/viewer_data_validator.rb +74 -17
  113. data/lib/simplecov/formatter/html_formatter.rb +24 -41
  114. data/lib/simplecov/formatter/json_formatter/errors_formatter.rb +50 -16
  115. data/lib/simplecov/formatter/json_formatter/production_section_formatter.rb +48 -0
  116. data/lib/simplecov/formatter/json_formatter/result_hash_formatter.rb +43 -29
  117. data/lib/simplecov/formatter/json_formatter/source_file_formatter.rb +14 -10
  118. data/lib/simplecov/formatter/json_formatter.rb +7 -13
  119. data/lib/simplecov/formatter/multi_formatter.rb +5 -11
  120. data/lib/simplecov/formatter/simple_formatter.rb +2 -8
  121. data/lib/simplecov/formatter.rb +14 -10
  122. data/lib/simplecov/group_names.rb +13 -12
  123. data/lib/simplecov/history.rb +115 -0
  124. data/lib/simplecov/last_run.rb +6 -9
  125. data/lib/simplecov/lines_classifier.rb +31 -16
  126. data/lib/simplecov/load_global_config.rb +7 -7
  127. data/lib/simplecov/parallel_adapters/base.rb +13 -41
  128. data/lib/simplecov/parallel_adapters/generic.rb +7 -18
  129. data/lib/simplecov/parallel_adapters/parallel_tests.rb +21 -42
  130. data/lib/simplecov/parallel_adapters.rb +24 -53
  131. data/lib/simplecov/parallel_coordination.rb +39 -49
  132. data/lib/simplecov/parallel_result_merger/worker_payload.rb +33 -0
  133. data/lib/simplecov/parallel_result_merger.rb +67 -126
  134. data/lib/simplecov/process.rb +14 -32
  135. data/lib/simplecov/production/error.rb +11 -0
  136. data/lib/simplecov/production/file_sink.rb +130 -0
  137. data/lib/simplecov/production.rb +249 -0
  138. data/lib/simplecov/profiles/rails.rb +1 -12
  139. data/lib/simplecov/profiles/root_filter.rb +0 -4
  140. data/lib/simplecov/profiles/strict.rb +9 -19
  141. data/lib/simplecov/profiles.rb +11 -33
  142. data/lib/simplecov/report_deferral.rb +11 -15
  143. data/lib/simplecov/report_stamp.rb +6 -9
  144. data/lib/simplecov/result/filter_config.rb +19 -0
  145. data/lib/simplecov/result/missing_source_files_reporter.rb +8 -9
  146. data/lib/simplecov/result/serialization.rb +43 -0
  147. data/lib/simplecov/result/source_file_builder.rb +7 -12
  148. data/lib/simplecov/result.rb +68 -117
  149. data/lib/simplecov/result_adapter.rb +58 -68
  150. data/lib/simplecov/result_merger/contexts.rb +22 -0
  151. data/lib/simplecov/result_merger/legacy_format_adapter.rb +4 -8
  152. data/lib/simplecov/result_merger/resultset_file.rb +20 -27
  153. data/lib/simplecov/result_merger/resultset_run_identity.rb +23 -25
  154. data/lib/simplecov/result_merger/resultset_store.rb +23 -14
  155. data/lib/simplecov/result_merger/unloaded_files.rb +24 -32
  156. data/lib/simplecov/result_merger.rb +59 -58
  157. data/lib/simplecov/result_processing.rb +74 -119
  158. data/lib/simplecov/run_identity.rb +16 -18
  159. data/lib/simplecov/simulate_coverage.rb +35 -50
  160. data/lib/simplecov/source_file/branch.rb +8 -33
  161. data/lib/simplecov/source_file/branch_builder.rb +28 -47
  162. data/lib/simplecov/source_file/builder_context.rb +6 -13
  163. data/lib/simplecov/source_file/line.rb +13 -33
  164. data/lib/simplecov/source_file/line_builder.rb +9 -15
  165. data/lib/simplecov/source_file/method.rb +3 -7
  166. data/lib/simplecov/source_file/method_builder.rb +6 -15
  167. data/lib/simplecov/source_file/ruby_data_parser.rb +59 -66
  168. data/lib/simplecov/source_file/skip_chunks.rb +19 -28
  169. data/lib/simplecov/source_file/source_loader.rb +45 -35
  170. data/lib/simplecov/source_file/statistics.rb +10 -9
  171. data/lib/simplecov/source_file.rb +21 -51
  172. data/lib/simplecov/static_coverage_extractor/condition_folding.rb +63 -114
  173. data/lib/simplecov/static_coverage_extractor/location_conventions.rb +76 -121
  174. data/lib/simplecov/static_coverage_extractor/method_collector.rb +11 -24
  175. data/lib/simplecov/static_coverage_extractor/prism_compat.rb +15 -24
  176. data/lib/simplecov/static_coverage_extractor/value_position.rb +25 -38
  177. data/lib/simplecov/static_coverage_extractor/visitor.rb +47 -64
  178. data/lib/simplecov/static_coverage_extractor.rb +46 -62
  179. data/lib/simplecov/test_tracker/accessors.rb +34 -0
  180. data/lib/simplecov/test_tracker/constant_watch.rb +49 -0
  181. data/lib/simplecov/test_tracker/delta.rb +69 -0
  182. data/lib/simplecov/test_tracker/framework_hooks.rb +130 -0
  183. data/lib/simplecov/test_tracker.rb +162 -0
  184. data/lib/simplecov/unloaded_file_injector.rb +18 -31
  185. data/lib/simplecov/useless_results_remover.rb +7 -6
  186. data/lib/simplecov/version.rb +1 -1
  187. data/lib/simplecov/view_coverage/template_compiler.rb +83 -0
  188. data/lib/simplecov/view_coverage.rb +55 -0
  189. data/lib/simplecov-html.rb +1 -0
  190. data/lib/simplecov.rb +59 -114
  191. data/lib/simplecov_json_formatter.rb +1 -0
  192. data/man/simplecov.1 +349 -0
  193. data/sig/simplecov.rbs +523 -595
  194. metadata +72 -7
  195. data/schemas/coverage-v1.0.schema.json +0 -306
  196. data/schemas/coverage.schema.json +0 -306
@@ -1,8 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SimpleCov
4
- # Exit statuses SimpleCov sets when coverage checks fail, and the output
5
- # helper the enforcement machinery reports through.
6
4
  module ExitCodes
7
5
  SUCCESS = 0
8
6
  EXCEPTION = 1
@@ -10,21 +8,33 @@ module SimpleCov
10
8
  MAXIMUM_COVERAGE_DROP = 3
11
9
  MAXIMUM_COVERAGE = 4
12
10
 
13
- # Threshold-violation reports and exit-status notices are the output of
14
- # the enforcement feature, not Ruby warnings: routing them through
15
- # `Kernel#warn` made `-W0` swallow the explanation for a failing exit
16
- # code, let `Warning.warn` hooks (warning trackers, raise-on-warning
17
- # test setups) intercept them mid-`at_exit`, and fed colorized text to
11
+ # The plural unit each criterion's misses count in, for the checks that report
12
+ # counts rather than percents.
13
+ UNITS = {line: "lines", branch: "branches", method: "methods"}.freeze
14
+
15
+ # Threshold-violation reports and exit-status notices are the output of the
16
+ # enforcement feature, not Ruby warnings: routing them through `Kernel#warn`
17
+ # made `-W0` swallow the explanation for a failing exit code, let
18
+ # `Warning.warn` hooks intercept them mid-at_exit, and fed colorized text to
18
19
  # warning logs. `print_errors false` remains the intended opt-out.
20
+ #
21
+ # A parallel runner can close a worker's stderr before its at_exit hooks
22
+ # run, hence the rescue.
19
23
  def self.print_error(message)
20
24
  $stderr.puts message # rubocop:disable Style/StderrPuts
25
+ rescue IOError
26
+ # The violation still has to set the exit status even when its
27
+ # explanation cannot be printed.
21
28
  end
22
29
  end
23
30
  end
24
31
 
25
32
  require_relative "exit_codes/check"
33
+ require_relative "exit_codes/baseline_check"
26
34
  require_relative "exit_codes/exit_code_handling"
27
35
  require_relative "exit_codes/maximum_coverage_drop_check"
36
+ require_relative "exit_codes/maximum_missed_check"
37
+ require_relative "exit_codes/maximum_missed_per_file_check"
28
38
  require_relative "exit_codes/maximum_overall_coverage_check"
29
39
  require_relative "exit_codes/minimum_coverage_by_file_check"
30
40
  require_relative "exit_codes/minimum_coverage_by_group_check"
@@ -2,9 +2,11 @@
2
2
 
3
3
  require "English"
4
4
 
5
- # `at_exit` orchestration: post-suite report generation, threshold
6
- # checks, deferral when a sibling subprocess already wrote a fresher
7
- # report, and exit-status propagation.
5
+ #
6
+ # `at_exit` orchestration: post-suite report generation, threshold checks,
7
+ # deferral when a sibling subprocess already wrote a fresher report, and
8
+ # exit-status propagation.
9
+ #
8
10
  module SimpleCov
9
11
  class << self
10
12
  # @api private
@@ -14,38 +16,59 @@ module SimpleCov
14
16
  :minimum_coverage_by_file_overrides,
15
17
  :minimum_coverage_by_group,
16
18
  :maximum_coverage,
17
- :maximum_coverage_drop
19
+ :maximum_coverage_drop,
20
+ :maximum_missed,
21
+ :maximum_missed_per_file,
22
+ :maximum_missed_per_file_overrides,
23
+ :baseline
18
24
  )
19
25
 
20
26
  def at_exit_behavior
21
- # If we are in a different process than called start, don't interfere.
22
- return if SimpleCov.pid != Process.pid
27
+ # A different process than the one that called start; don't interfere.
28
+ return unless started_in_this_process?
23
29
 
24
- # If Coverage is no longer running (e.g. someone manually stopped it
25
- # or a test consumed the result) then don't run exit tasks.
30
+ # Coverage is no longer running (someone stopped it manually, or a test
31
+ # consumed the result), so don't run exit tasks.
26
32
  return unless Coverage.running?
27
33
 
28
- # Capture the suite's exit status BEFORE the deferral probe: its
29
- # freshness check rescues filesystem errors, and completing any
30
- # rescue inside an at_exit handler resets $ERROR_INFO to nil.
34
+ # Captured BEFORE the deferral probe: its freshness check rescues
35
+ # filesystem errors, and completing any rescue inside an at_exit handler
36
+ # resets $ERROR_INFO to nil.
31
37
  error_exit_status = exit_status_from_exception
32
38
 
33
39
  # Stand down when we'd only clobber a fresher report (#581).
34
40
  return if defer_to_existing_report?
35
41
 
36
- SimpleCov.run_exit_tasks!(error_exit_status)
42
+ run_exit_tasks!(error_exit_status)
37
43
  end
38
44
 
39
- # @api private called from the at_exit block (which pre-captures
40
- # the status, see there) and by `collate` (the default argument).
41
- def run_exit_tasks!(error_exit_status = exit_status_from_exception)
45
+ # @api private -- everything the end of a measured run does, answered as the
46
+ # exit status the process should end with rather than performed as an exit.
47
+ # The one side effect left to the caller is ending the process, which is
48
+ # what makes this whole path answerable from inside a test.
49
+ def run_exit_tasks(error_exit_status = exit_status_from_exception)
42
50
  at_exit.call
43
51
 
44
- exit_and_report_previous_error(error_exit_status) if previous_error?(error_exit_status)
45
- process_results_and_report_error if ready_to_process_results?
52
+ if previous_error?(error_exit_status)
53
+ report_previous_error
54
+ error_exit_status
55
+ elsif ready_to_process_results?
56
+ report_processing_failure(process_result(result))
57
+ else
58
+ ExitCodes::SUCCESS
59
+ end
46
60
  end
47
61
 
48
- # @api private returns the exit status from the exit exception.
62
+ # @api private -- the exiting adapter over `run_exit_tasks`, called from the
63
+ # at_exit block (which pre-captures the status, see there) and by `collate`.
64
+ def run_exit_tasks!(error_exit_status = exit_status_from_exception)
65
+ status = run_exit_tasks(error_exit_status)
66
+
67
+ # Force exit with stored status (#5).
68
+ Kernel.exit(status) unless successful?(status)
69
+ end
70
+
71
+ # @api private
49
72
  def exit_status_from_exception
50
73
  @exit_exception = $ERROR_INFO
51
74
  return nil unless @exit_exception
@@ -53,76 +76,88 @@ module SimpleCov
53
76
  if @exit_exception.is_a?(SystemExit)
54
77
  @exit_exception.status
55
78
  else
56
- SimpleCov::ExitCodes::EXCEPTION
79
+ ExitCodes::EXCEPTION
57
80
  end
58
81
  end
59
82
 
60
- # @api private strict boolean so rspec-mocks 4's predicate matcher
61
- # accepts it. test_unit sets status 0 on success, so SUCCESS must
62
- # also be treated as "not a previous error".
83
+ # @api private -- strict boolean so rspec-mocks 4's predicate matcher
84
+ # accepts it. test_unit sets status 0 on success, so SUCCESS must also be
85
+ # treated as "not a previous error".
63
86
  def previous_error?(error_exit_status)
64
- !!(error_exit_status && error_exit_status != SimpleCov::ExitCodes::SUCCESS)
87
+ return false unless error_exit_status
88
+
89
+ !successful?(error_exit_status)
65
90
  end
66
91
 
67
- # @api private
68
- def exit_and_report_previous_error(exit_status)
69
- if print_errors
70
- ExitCodes.print_error SimpleCov::Color.colorize(
71
- "Stopped processing SimpleCov as a previous error not related to SimpleCov has been detected",
72
- :yellow
73
- )
74
- end
75
- Kernel.exit(exit_status)
92
+ # @api private -- the notice alone; the status it accompanies is the
93
+ # caller's to answer with.
94
+ def report_previous_error
95
+ return unless print_errors
96
+
97
+ ExitCodes.print_error Color.colorize(
98
+ "Stopped processing SimpleCov as a previous error not related to SimpleCov has been detected",
99
+ :yellow
100
+ )
76
101
  end
77
102
 
78
- # @api private the process that owns final merge processing is the
79
- # only one that reports against thresholds, and only when its
80
- # `wait_for_other_processes` confirmed every sibling reported.
81
- # When the wait times out, the merged total is partial and
82
- # comparing it against `minimum_coverage` / `maximum_coverage`
83
- # would surface a spurious "below minimum" violation about the
84
- # missing slice rather than a real shortfall.
103
+ # @api private. The process that owns final merge processing is the only one
104
+ # that reports against thresholds, and only when its
105
+ # `wait_for_other_processes` confirmed every sibling reported. When the wait
106
+ # times out the merged total is partial, and comparing it against the
107
+ # thresholds would surface a spurious violation about the missing slice.
85
108
  def ready_to_process_results?
86
109
  merge_finalization_owner? && result? &&
87
110
  (collating_result? || parallel_results_complete?)
88
111
  end
89
112
 
90
- def process_results_and_report_error
91
- exit_status = process_result(result)
92
-
93
- # Force exit with stored status (see github issue #5)
94
- return unless exit_status.positive?
95
-
96
- if print_errors
97
- ExitCodes.print_error SimpleCov::Color.colorize(
113
+ # @api private -- answers the status it was handed, explaining it first when
114
+ # it is a coverage failure worth explaining.
115
+ def report_processing_failure(exit_status)
116
+ if exit_status.positive? && print_errors
117
+ ExitCodes.print_error Color.colorize(
98
118
  "SimpleCov failed with exit #{exit_status} due to a coverage related error", :red
99
119
  )
100
120
  end
101
- Kernel.exit exit_status
121
+ exit_status
102
122
  end
103
123
 
104
- # @api private `exit_status = SimpleCov.process_result(SimpleCov.result)`.
124
+ # @api private. The history entry is recorded on the same successful-run
125
+ # condition `.last_run.json` uses, so a run that failed its thresholds
126
+ # becomes neither the drop baseline nor a data point in the trend.
105
127
  def process_result(result)
106
128
  result_exit_status = result_exit_status(result)
107
- write_last_run(result) if result_exit_status == SimpleCov::ExitCodes::SUCCESS
129
+ if successful?(result_exit_status)
130
+ write_last_run(result)
131
+ History.record(result)
132
+ end
108
133
  result_exit_status
109
134
  end
110
135
 
136
+ # mutant:disable -- a pid is an Integer, and Integers answer ==, eql? and
137
+ # equal? alike for the same value.
138
+ def started_in_this_process?
139
+ SimpleCov.pid == Process.pid
140
+ end
141
+
142
+ # Asked as a predicate rather than compared against `ExitCodes::SUCCESS`,
143
+ # because two equal Integers are equal through every spelling of the
144
+ # comparison.
145
+ def successful?(status)
146
+ status.zero?
147
+ end
148
+
111
149
  def result_exit_status(result)
112
150
  ExitCodes::ExitCodeHandling.call(result, coverage_limits: build_coverage_limits)
113
151
  end
114
152
 
115
- private
153
+ private
116
154
 
155
+ # Every CoverageLimits member is named after the configuration reader that
156
+ # supplies it, so a new limit only has to be added to the Data definition
157
+ # and the checks.
117
158
  def build_coverage_limits
118
- CoverageLimits.new(
119
- minimum_coverage: minimum_coverage,
120
- minimum_coverage_by_file: minimum_coverage_by_file,
121
- minimum_coverage_by_file_overrides: minimum_coverage_by_file_overrides,
122
- minimum_coverage_by_group: minimum_coverage_by_group,
123
- maximum_coverage: maximum_coverage,
124
- maximum_coverage_drop: maximum_coverage_drop
125
- )
159
+ limits = CoverageLimits.members.to_h { |name| [name, public_send(name)] } #: Hash[Symbol, untyped]
160
+ CoverageLimits.new(**limits)
126
161
  end
127
162
  end
128
163
  end
@@ -1,31 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SimpleCov
4
- # An array of SimpleCov SourceFile instances with additional collection helper
5
- # methods for calculating coverage across them etc.
6
4
  class FileList
7
5
  include Enumerable
8
6
  extend Forwardable
9
7
 
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
8
+ # Enumerable's own implementations are skipped where Array has a better
9
+ # one, and `to_a` / `to_ary` keep this acting like the array it wraps.
10
+ def_delegators :@files, :each, :size, :map, :count, :empty?, :length, :to_a, :to_ary
21
11
 
22
12
  def initialize(files)
23
13
  @files = files
24
14
  end
25
15
 
26
- # The per-criterion coverage statistics across all files. With no argument
27
- # returns the `{line:, branch:, method:}` Hash; pass a criterion symbol
28
- # (`:line` / `:branch` / `:method`) to get that one CoverageStatistics.
16
+ # With no argument answers the `{line:, branch:, method:}` Hash, and with a
17
+ # criterion symbol that one CoverageStatistics.
29
18
  def coverage_statistics(criterion = nil)
30
19
  stats = (@coverage_statistics ||= compute_coverage_statistics)
31
20
  criterion ? stats[criterion] : stats
@@ -35,34 +24,27 @@ module SimpleCov
35
24
  @coverage_statistics_by_file ||= compute_coverage_statistics_by_file
36
25
  end
37
26
 
38
- # Returns the count of lines that have coverage
39
27
  def covered_lines
40
28
  coverage_statistics[:line]&.covered
41
29
  end
42
30
 
43
- # Returns the count of lines that have been missed
44
31
  def missed_lines
45
32
  coverage_statistics[:line]&.missed
46
33
  end
47
34
 
48
- # Returns the count of lines that are not relevant for coverage
49
35
  def never_lines
50
36
  sum { |f| f.never_lines.size }
51
37
  end
52
38
 
53
- # Returns the count of skipped lines
54
39
  def skipped_lines
55
40
  sum { |f| f.skipped_lines.size }
56
41
  end
57
42
 
58
- # Computes the coverage based upon lines covered and lines missed for each file
59
- # Returns an array with all coverage percentages
60
43
  def covered_percentages
61
44
  map(&:covered_percent)
62
45
  end
63
46
 
64
- # Finds the least covered file and returns that file's name, or nil
65
- # for an empty list (e.g. a fully filtered result).
47
+ # Nil for an empty list, such as a fully filtered result.
66
48
  def least_covered_file
67
49
  # `covered_percent` is nil only for an unmeasured criterion, and :line
68
50
  # is always measured, so the `|| 0.0` arm never fires at runtime; it
@@ -70,36 +52,28 @@ module SimpleCov
70
52
  min_by { |file| file.covered_percent || 0.0 }&.filename
71
53
  end
72
54
 
73
- # Returns the overall amount of relevant lines of code across all files in this list
74
55
  def lines_of_code
75
56
  coverage_statistics[:line]&.total
76
57
  end
77
58
 
78
- # The coverage across all files in percent, for the given criterion (line
79
- # by default). Returns nil if the criterion was not measured.
80
- # @return [Float, nil]
59
+ # Nil if the criterion was not measured.
81
60
  def covered_percent(criterion = :line)
82
61
  coverage_statistics(criterion)&.percent
83
62
  end
84
63
 
85
- # The strength (average hits per relevant unit) for the given criterion
86
- # (line by default).
87
- # @return [Float, nil]
64
+ # The strength (average hits per relevant unit) for the given criterion.
88
65
  def covered_strength(criterion = :line)
89
66
  coverage_statistics(criterion)&.strength
90
67
  end
91
68
 
92
- # Return total count of branches in all files
93
69
  def total_branches
94
70
  coverage_statistics[:branch]&.total
95
71
  end
96
72
 
97
- # Return total count of covered branches
98
73
  def covered_branches
99
74
  coverage_statistics[:branch]&.covered
100
75
  end
101
76
 
102
- # Return total count of covered branches
103
77
  def missed_branches
104
78
  coverage_statistics[:branch]&.missed
105
79
  end
@@ -108,17 +82,14 @@ module SimpleCov
108
82
  coverage_statistics[:branch]&.percent
109
83
  end
110
84
 
111
- # Return total count of methods in all files
112
85
  def total_methods
113
86
  coverage_statistics[:method]&.total
114
87
  end
115
88
 
116
- # Return total count of covered methods
117
89
  def covered_methods
118
90
  coverage_statistics[:method]&.covered
119
91
  end
120
92
 
121
- # Return total count of missed methods
122
93
  def missed_methods
123
94
  coverage_statistics[:method]&.missed
124
95
  end
@@ -127,23 +98,21 @@ module SimpleCov
127
98
  coverage_statistics[:method]&.percent
128
99
  end
129
100
 
130
- private
101
+ private
131
102
 
132
- # Seed the result hash with one entry per criterion the user
133
- # enabled so an empty FileList (e.g. a group with no files) still
134
- # yields the right shape then fold each file's stats into the
135
- # matching bucket. `SourceFile#coverage_statistics` always reports
136
- # all three criteria; FileList is the layer that filters to the
137
- # enabled set so disabled criteria don't surface in totals, JSON,
138
- # or the HTML report.
103
+ # Seeded with one entry per criterion the user enabled, so an empty FileList
104
+ # still yields the right shape. `SourceFile#coverage_statistics` always
105
+ # reports all three criteria; FileList is the layer that filters to the
106
+ # enabled set so disabled criteria don't surface in totals, JSON, or the
107
+ # HTML report.
139
108
  def compute_coverage_statistics_by_file
140
109
  seed = enabled_criteria_for_reporting.to_h do |criterion|
141
110
  bucket = [] #: Array[CoverageStatistics]
142
111
  [criterion, bucket]
143
112
  end
144
- @files.each_with_object(seed) do |file, together|
113
+ each_with_object(seed) do |file, together|
145
114
  file.coverage_statistics.each do |criterion, stats|
146
- together[criterion] << stats if together.key?(criterion)
115
+ together.fetch(criterion) << stats if together.key?(criterion)
147
116
  end
148
117
  end
149
118
  end
@@ -152,12 +121,12 @@ module SimpleCov
152
121
  coverage_statistics_by_file.transform_values { |stats| CoverageStatistics.from(stats) }
153
122
  end
154
123
 
155
- # `:line` (or its `:oneshot_line` synonym) is reported when either
156
- # criterion is enabled; the JRuby-gated branch/method criteria are
157
- # reported when they pass their own engine-support check.
124
+ # `:line` (or its `:oneshot_line` synonym) is reported when either criterion
125
+ # is enabled; the JRuby-gated branch/method criteria are reported when they
126
+ # pass their own engine-support check.
158
127
  def enabled_criteria_for_reporting
159
128
  criteria = [] #: Array[SimpleCov::criterion]
160
- criteria << :line if SimpleCov.line_coverage?
129
+ criteria << :line if SimpleCov.line_coverage?
161
130
  criteria << :branch if SimpleCov.branch_coverage?
162
131
  criteria << :method if SimpleCov.method_coverage?
163
132
  criteria
@@ -2,15 +2,8 @@
2
2
 
3
3
  module SimpleCov
4
4
  #
5
- # Base filter class. Inherit from this to create custom filters,
6
- # and overwrite the matches?(source_file) instance method
7
- #
8
- # # A sample class that rejects all source files.
9
- # class StupidFilter < SimpleCov::Filter
10
- # def matches?(source_file)
11
- # false
12
- # end
13
- # end
5
+ # Base filter class. Inherit from this to create custom filters, and
6
+ # override `matches?(source_file)`.
14
7
  #
15
8
  class Filter
16
9
  attr_reader :filter_argument
@@ -25,50 +18,46 @@ module SimpleCov
25
18
 
26
19
  # Whether this filter's verdict depends only on the file's path, so it can
27
20
  # be decided before that file has any coverage. Used when recording which
28
- # tracked files a process did not load, where no coverage exists yet.
29
- # Defaults to false so a custom filter is never guessed at. See #1250.
21
+ # tracked files a process did not load. Defaults to false so a custom
22
+ # filter is never guessed at (#1250).
30
23
  def path_only?
31
24
  false
32
25
  end
33
26
 
34
- # `string_filter` selects the semantics of bare String arguments
35
- # StringFilter's segment-substring match for `add_filter`/`skip`,
36
- # GlobFilter for `cover` and threads through Array elements so a
37
- # list gets the same treatment as its members.
38
- def self.build_filter(filter_argument, string_filter: SimpleCov::StringFilter)
27
+ # `string_filter` selects the semantics of bare String arguments:
28
+ # StringFilter's segment-substring match for `skip`, GlobFilter for
29
+ # `cover`. It threads through Array elements so a list gets the same
30
+ # treatment as its members.
31
+ def self.build_filter(filter_argument, string_filter: StringFilter)
39
32
  case filter_argument
40
- when SimpleCov::Filter then filter_argument
41
- when String then string_filter.new(filter_argument)
33
+ when Filter then filter_argument
34
+ when String then string_filter.new(filter_argument)
42
35
  when Array
43
- SimpleCov::ArrayFilter.new(filter_argument.map { |arg| build_filter(arg, string_filter: string_filter) })
36
+ ArrayFilter.new(filter_argument.map { |arg| build_filter(arg, string_filter: string_filter) })
44
37
  else class_for_argument(filter_argument).new(filter_argument)
45
38
  end
46
39
  end
47
40
 
48
41
  def self.class_for_argument(filter_argument)
49
42
  filter_classes_by_argument_type.find { |type, _| filter_argument.is_a?(type) }&.last ||
50
- raise(SimpleCov::ConfigurationError, "You have provided an unrecognized filter type")
43
+ raise(ConfigurationError, "You have provided an unrecognized filter type")
51
44
  end
52
45
 
53
46
  def self.filter_classes_by_argument_type
54
47
  @filter_classes_by_argument_type ||= {
55
- String => SimpleCov::StringFilter,
56
- Regexp => SimpleCov::RegexFilter,
57
- Array => SimpleCov::ArrayFilter,
58
- Proc => SimpleCov::BlockFilter
48
+ String => StringFilter,
49
+ Regexp => RegexFilter,
50
+ Array => ArrayFilter,
51
+ Proc => BlockFilter
59
52
  }.freeze
60
53
  end
61
54
  private_class_method :filter_classes_by_argument_type
62
55
  end
63
56
 
64
- # Filter that matches when the source file's project path contains the
65
- # configured string at a path-segment boundary.
66
- class StringFilter < SimpleCov::Filter
67
- # Returns true when the given source file's filename matches the
68
- # string configured when initializing this Filter with StringFilter.new('somestring').
69
- # Matching is path-segment-aware: the argument must appear immediately after a "/"
70
- # and be followed by "/" or end-of-string, so "lib" matches "/lib/foo.rb" but not
71
- # "/app/models/library.rb".
57
+ class StringFilter < Filter
58
+ # Matching is path-segment-aware: the argument must appear immediately
59
+ # after a "/" and be followed by "/" or end-of-string, so "lib" matches
60
+ # "/lib/foo.rb" but not "/app/models/library.rb".
72
61
  def matches?(source_file)
73
62
  source_file.project_filename.match?(segment_pattern)
74
63
  end
@@ -77,7 +66,7 @@ module SimpleCov
77
66
  true
78
67
  end
79
68
 
80
- private
69
+ private
81
70
 
82
71
  def segment_pattern
83
72
  @segment_pattern ||= compute_segment_pattern
@@ -85,34 +74,32 @@ module SimpleCov
85
74
 
86
75
  def compute_segment_pattern
87
76
  normalized = filter_argument.delete_prefix("/")
88
- escaped = Regexp.escape(normalized)
89
- boundary = '(?:\A|/)'
77
+ escaped = Regexp.escape(normalized)
78
+ boundary = '(?:\A|/)'
90
79
 
91
80
  if normalized.end_with?("/")
92
81
  # Trailing slash signals directory-only matching.
93
82
  /#{boundary}#{escaped}/
94
83
  elsif normalized.include?(".") && !normalized.include?("/")
95
- # Bare filename pattern (e.g. "test.rb" matches "faked_test.rb"):
96
- # allow a substring match within a single path segment. Multi-segment
97
- # arguments must not get this relaxation, or "app/models/user.rb"
98
- # would also match "webapp/models/user.rb".
99
- %r{#{boundary}[^/]*#{escaped}(?=[/.]|\z)}
84
+ # Bare filename pattern ("test.rb" matches "faked_test.rb"): allow a
85
+ # substring match within a single path segment. Multi-segment arguments
86
+ # must not get this relaxation, or "app/models/user.rb" would also
87
+ # match "webapp/models/user.rb". No boundary here: `[^/]*` already
88
+ # reaches back to the segment start, so prefixing one matches the same
89
+ # paths.
90
+ %r{[^/]*#{escaped}(?=[/.]|\z)}
100
91
  else
101
- # Directory or path: require a segment-boundary match so "lib"
102
- # matches "lib/" but not "library/".
92
+ # Require a segment-boundary match so "lib" matches "lib/" but not
93
+ # "library/".
103
94
  %r{#{boundary}#{escaped}(?=[/.]|\z)}
104
95
  end
105
96
  end
106
97
  end
107
98
 
108
- # Filter that matches when the source file's project path matches the
109
- # configured Regexp.
110
- class RegexFilter < SimpleCov::Filter
111
- # Returns true when the given source file's filename matches the
112
- # regex configured when initializing this Filter with RegexFilter.new(/someregex/).
113
- # Uses `Regexp#match?` so the predicate returns a real boolean — `=~`
114
- # would return the match position (an Integer or nil), which trips
115
- # rspec-mocks 4's stricter predicate-matcher type check.
99
+ class RegexFilter < Filter
100
+ # `Regexp#match?` rather than `=~` so the predicate returns a real boolean:
101
+ # `=~` would return the match position, which trips rspec-mocks 4's
102
+ # stricter predicate-matcher type check.
116
103
  def matches?(source_file)
117
104
  filter_argument.match?(source_file.project_filename)
118
105
  end
@@ -122,21 +109,16 @@ module SimpleCov
122
109
  end
123
110
  end
124
111
 
125
- # Filter that matches when the configured block returns truthy for the
126
- # source file.
127
- class BlockFilter < SimpleCov::Filter
128
- # Returns true if the block given when initializing this filter with BlockFilter.new {|src_file| ... }
129
- # returns true for the given source file.
112
+ class BlockFilter < Filter
130
113
  def matches?(source_file)
131
114
  filter_argument.call(source_file)
132
115
  end
133
116
  end
134
117
 
135
- # Filter that matches when the source file's project path matches the
136
- # configured shell glob (e.g. "lib/**/*.rb"). Used by `cover` and
137
- # `skip` when callers want glob semantics instead of the substring
138
- # match of `StringFilter`.
139
- class GlobFilter < SimpleCov::Filter
118
+ # Matches when the source file's project path matches the configured shell
119
+ # glob. Used by `cover`, and by `skip` when callers want glob semantics
120
+ # instead of `StringFilter`'s substring match.
121
+ class GlobFilter < Filter
140
122
  def matches?(source_file)
141
123
  File.fnmatch?(filter_argument, source_file.project_filename, File::FNM_PATHNAME | File::FNM_EXTGLOB)
142
124
  end
@@ -146,9 +128,7 @@ module SimpleCov
146
128
  end
147
129
  end
148
130
 
149
- # Filter that matches when any of its component filters (built from the
150
- # array's elements) match the source file.
151
- class ArrayFilter < SimpleCov::Filter
131
+ class ArrayFilter < Filter
152
132
  def initialize(filter_argument)
153
133
  filter_objects = filter_argument.map do |arg|
154
134
  Filter.build_filter(arg)
@@ -157,13 +137,10 @@ module SimpleCov
157
137
  super(filter_objects)
158
138
  end
159
139
 
160
- # Path-decidable only when every component filter is.
161
140
  def path_only?
162
141
  filter_argument.all?(&:path_only?)
163
142
  end
164
143
 
165
- # Returns true if any of the filters in the array match the given source file.
166
- # Configure this Filter like StringFilter.new(['some/path', /^some_regex/, Proc.new {|src_file| ... }])
167
144
  def matches?(source_files_list)
168
145
  filter_argument.any? do |arg|
169
146
  arg.matches?(source_files_list)