simplecov 1.1.1 → 1.3.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 (200) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +148 -5
  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/annotations.rb +166 -0
  13. data/lib/simplecov/cli/badge/svg.rb +70 -0
  14. data/lib/simplecov/cli/badge.rb +62 -0
  15. data/lib/simplecov/cli/clean.rb +23 -24
  16. data/lib/simplecov/cli/command_helpers.rb +55 -19
  17. data/lib/simplecov/cli/completions/scripts.rb +114 -0
  18. data/lib/simplecov/cli/completions.rb +83 -0
  19. data/lib/simplecov/cli/coverage.rb +25 -28
  20. data/lib/simplecov/cli/coverage_file.rb +58 -14
  21. data/lib/simplecov/cli/dead_code/output.rb +100 -0
  22. data/lib/simplecov/cli/dead_code.rb +139 -0
  23. data/lib/simplecov/cli/diff/output.rb +45 -0
  24. data/lib/simplecov/cli/diff.rb +35 -67
  25. data/lib/simplecov/cli/dotfile.rb +83 -38
  26. data/lib/simplecov/cli/git.rb +41 -0
  27. data/lib/simplecov/cli/history/output.rb +135 -0
  28. data/lib/simplecov/cli/history.rb +67 -0
  29. data/lib/simplecov/cli/merge.rb +34 -39
  30. data/lib/simplecov/cli/open.rb +12 -15
  31. data/lib/simplecov/cli/patch/changed_lines.rb +149 -0
  32. data/lib/simplecov/cli/patch/output.rb +151 -0
  33. data/lib/simplecov/cli/patch.rb +175 -0
  34. data/lib/simplecov/cli/ratchet/output.rb +44 -0
  35. data/lib/simplecov/cli/ratchet.rb +83 -0
  36. data/lib/simplecov/cli/report.rb +18 -24
  37. data/lib/simplecov/cli/run.rb +8 -9
  38. data/lib/simplecov/cli/serve/report_preparer.rb +7 -6
  39. data/lib/simplecov/cli/serve/static_file_handler.rb +62 -56
  40. data/lib/simplecov/cli/serve.rb +27 -24
  41. data/lib/simplecov/cli/show/annotator.rb +87 -0
  42. data/lib/simplecov/cli/show/sweep.rb +36 -0
  43. data/lib/simplecov/cli/show.rb +133 -0
  44. data/lib/simplecov/cli/status/facts.rb +87 -0
  45. data/lib/simplecov/cli/status.rb +93 -0
  46. data/lib/simplecov/cli/tests/redundancy.rb +78 -0
  47. data/lib/simplecov/cli/tests.rb +148 -0
  48. data/lib/simplecov/cli/uncovered/misses.rb +33 -0
  49. data/lib/simplecov/cli/uncovered.rb +58 -34
  50. data/lib/simplecov/cli/usage.rb +153 -0
  51. data/lib/simplecov/cli/watch/live_report.rb +73 -0
  52. data/lib/simplecov/cli/watch/narrator.rb +55 -0
  53. data/lib/simplecov/cli/watch/poller.rb +45 -0
  54. data/lib/simplecov/cli/watch/session.rb +134 -0
  55. data/lib/simplecov/cli/watch/test_plan.rb +43 -0
  56. data/lib/simplecov/cli/watch.rb +85 -0
  57. data/lib/simplecov/cli.rb +43 -92
  58. data/lib/simplecov/color.rb +19 -37
  59. data/lib/simplecov/combine/branches_combiner.rb +7 -31
  60. data/lib/simplecov/combine/coverage_accumulator.rb +49 -142
  61. data/lib/simplecov/combine/identity_interner.rb +10 -13
  62. data/lib/simplecov/combine/interned_counts.rb +7 -9
  63. data/lib/simplecov/combine/lines_combiner.rb +20 -44
  64. data/lib/simplecov/combine/methods_combiner.rb +9 -35
  65. data/lib/simplecov/combine/results_combiner.rb +4 -21
  66. data/lib/simplecov/combine.rb +0 -6
  67. data/lib/simplecov/command_guesser.rb +20 -34
  68. data/lib/simplecov/configuration/baseline.rb +31 -0
  69. data/lib/simplecov/configuration/coverage.rb +91 -79
  70. data/lib/simplecov/configuration/coverage_criteria.rb +43 -54
  71. data/lib/simplecov/configuration/deprecations.rb +21 -0
  72. data/lib/simplecov/configuration/eval_coverage.rb +7 -11
  73. data/lib/simplecov/configuration/filters.rb +40 -73
  74. data/lib/simplecov/configuration/formatting.rb +74 -83
  75. data/lib/simplecov/configuration/groups.rb +19 -13
  76. data/lib/simplecov/configuration/history.rb +44 -0
  77. data/lib/simplecov/configuration/ignored_entries.rb +31 -22
  78. data/lib/simplecov/configuration/merging.rb +94 -62
  79. data/lib/simplecov/configuration/missed_caps.rb +51 -0
  80. data/lib/simplecov/configuration/production.rb +23 -0
  81. data/lib/simplecov/configuration/test_tracking.rb +48 -0
  82. data/lib/simplecov/configuration/thresholds.rb +33 -62
  83. data/lib/simplecov/configuration/view_coverage.rb +37 -0
  84. data/lib/simplecov/configuration.rb +70 -76
  85. data/lib/simplecov/configuration_error.rb +5 -0
  86. data/lib/simplecov/context_map/union.rb +81 -0
  87. data/lib/simplecov/context_map.rb +187 -0
  88. data/lib/simplecov/coverage_json.rb +2 -5
  89. data/lib/simplecov/coverage_statistics.rb +14 -24
  90. data/lib/simplecov/coverage_violations.rb +160 -65
  91. data/lib/simplecov/current_run.rb +61 -0
  92. data/lib/simplecov/defaults.rb +4 -23
  93. data/lib/simplecov/deprecation.rb +45 -27
  94. data/lib/simplecov/directive/erb.rb +44 -0
  95. data/lib/simplecov/directive/haml.rb +33 -0
  96. data/lib/simplecov/directive/indented_template.rb +37 -0
  97. data/lib/simplecov/directive/slim.rb +35 -0
  98. data/lib/simplecov/directive/template.rb +29 -0
  99. data/lib/simplecov/directive.rb +47 -62
  100. data/lib/simplecov/exit_codes/baseline_check.rb +46 -0
  101. data/lib/simplecov/exit_codes/check.rb +17 -11
  102. data/lib/simplecov/exit_codes/exit_code_handling.rb +21 -8
  103. data/lib/simplecov/exit_codes/maximum_coverage_drop_check.rb +7 -10
  104. data/lib/simplecov/exit_codes/maximum_missed_check.rb +26 -0
  105. data/lib/simplecov/exit_codes/maximum_missed_per_file_check.rb +34 -0
  106. data/lib/simplecov/exit_codes/maximum_overall_coverage_check.rb +7 -12
  107. data/lib/simplecov/exit_codes/minimum_coverage_by_file_check.rb +11 -10
  108. data/lib/simplecov/exit_codes/minimum_coverage_by_group_check.rb +7 -9
  109. data/lib/simplecov/exit_codes/minimum_overall_coverage_check.rb +16 -17
  110. data/lib/simplecov/exit_codes.rb +16 -10
  111. data/lib/simplecov/exit_handling.rb +94 -59
  112. data/lib/simplecov/file_list.rb +20 -51
  113. data/lib/simplecov/filter.rb +43 -66
  114. data/lib/simplecov/formatter/base.rb +37 -55
  115. data/lib/simplecov/formatter/baseline_formatter.rb +98 -0
  116. data/lib/simplecov/formatter/coverage_json_writer.rb +29 -31
  117. data/lib/simplecov/formatter/html_formatter/public/index.html +15 -9
  118. data/lib/simplecov/formatter/html_formatter/viewer_data_validator.rb +74 -17
  119. data/lib/simplecov/formatter/html_formatter.rb +24 -41
  120. data/lib/simplecov/formatter/json_formatter/errors_formatter.rb +50 -16
  121. data/lib/simplecov/formatter/json_formatter/production_section_formatter.rb +48 -0
  122. data/lib/simplecov/formatter/json_formatter/result_hash_formatter.rb +43 -29
  123. data/lib/simplecov/formatter/json_formatter/source_file_formatter.rb +14 -10
  124. data/lib/simplecov/formatter/json_formatter.rb +7 -13
  125. data/lib/simplecov/formatter/multi_formatter.rb +5 -11
  126. data/lib/simplecov/formatter/simple_formatter.rb +2 -8
  127. data/lib/simplecov/formatter.rb +14 -10
  128. data/lib/simplecov/group_names.rb +13 -12
  129. data/lib/simplecov/history.rb +115 -0
  130. data/lib/simplecov/last_run.rb +6 -9
  131. data/lib/simplecov/lines_classifier.rb +31 -16
  132. data/lib/simplecov/load_global_config.rb +7 -7
  133. data/lib/simplecov/parallel_adapters/base.rb +13 -41
  134. data/lib/simplecov/parallel_adapters/generic.rb +7 -19
  135. data/lib/simplecov/parallel_adapters/parallel_tests.rb +21 -42
  136. data/lib/simplecov/parallel_adapters.rb +24 -55
  137. data/lib/simplecov/parallel_coordination.rb +39 -49
  138. data/lib/simplecov/parallel_result_merger/worker_payload.rb +33 -0
  139. data/lib/simplecov/parallel_result_merger.rb +67 -126
  140. data/lib/simplecov/process.rb +14 -32
  141. data/lib/simplecov/production/error.rb +11 -0
  142. data/lib/simplecov/production/file_sink.rb +130 -0
  143. data/lib/simplecov/production.rb +249 -0
  144. data/lib/simplecov/profiles/rails.rb +4 -15
  145. data/lib/simplecov/profiles/root_filter.rb +0 -4
  146. data/lib/simplecov/profiles/strict.rb +11 -21
  147. data/lib/simplecov/profiles.rb +11 -33
  148. data/lib/simplecov/report_deferral.rb +11 -15
  149. data/lib/simplecov/report_stamp.rb +6 -9
  150. data/lib/simplecov/result/filter_config.rb +19 -0
  151. data/lib/simplecov/result/missing_source_files_reporter.rb +8 -9
  152. data/lib/simplecov/result/serialization.rb +43 -0
  153. data/lib/simplecov/result/source_file_builder.rb +7 -12
  154. data/lib/simplecov/result.rb +74 -117
  155. data/lib/simplecov/result_adapter.rb +56 -68
  156. data/lib/simplecov/result_merger/contexts.rb +22 -0
  157. data/lib/simplecov/result_merger/legacy_format_adapter.rb +4 -8
  158. data/lib/simplecov/result_merger/resultset_file.rb +20 -27
  159. data/lib/simplecov/result_merger/resultset_run_identity.rb +23 -25
  160. data/lib/simplecov/result_merger/resultset_store.rb +23 -14
  161. data/lib/simplecov/result_merger/unloaded_files.rb +24 -32
  162. data/lib/simplecov/result_merger.rb +59 -58
  163. data/lib/simplecov/result_processing.rb +92 -122
  164. data/lib/simplecov/run_identity.rb +16 -18
  165. data/lib/simplecov/simulate_coverage.rb +34 -50
  166. data/lib/simplecov/source_file/branch.rb +8 -33
  167. data/lib/simplecov/source_file/branch_builder.rb +28 -47
  168. data/lib/simplecov/source_file/builder_context.rb +13 -13
  169. data/lib/simplecov/source_file/line.rb +13 -33
  170. data/lib/simplecov/source_file/line_builder.rb +9 -15
  171. data/lib/simplecov/source_file/method.rb +3 -7
  172. data/lib/simplecov/source_file/method_builder.rb +6 -15
  173. data/lib/simplecov/source_file/ruby_data_parser.rb +59 -66
  174. data/lib/simplecov/source_file/skip_chunks.rb +29 -29
  175. data/lib/simplecov/source_file/source_loader.rb +45 -35
  176. data/lib/simplecov/source_file/statistics.rb +20 -9
  177. data/lib/simplecov/source_file.rb +21 -51
  178. data/lib/simplecov/static_coverage_extractor/condition_folding.rb +55 -139
  179. data/lib/simplecov/static_coverage_extractor/location_conventions.rb +76 -121
  180. data/lib/simplecov/static_coverage_extractor/method_collector.rb +9 -26
  181. data/lib/simplecov/static_coverage_extractor/prism_compat.rb +15 -24
  182. data/lib/simplecov/static_coverage_extractor/value_position.rb +25 -38
  183. data/lib/simplecov/static_coverage_extractor/visitor.rb +45 -65
  184. data/lib/simplecov/static_coverage_extractor.rb +43 -75
  185. data/lib/simplecov/test_tracker/accessors.rb +34 -0
  186. data/lib/simplecov/test_tracker/constant_watch.rb +49 -0
  187. data/lib/simplecov/test_tracker/delta.rb +69 -0
  188. data/lib/simplecov/test_tracker/framework_hooks.rb +130 -0
  189. data/lib/simplecov/test_tracker.rb +162 -0
  190. data/lib/simplecov/unloaded_file_injector.rb +18 -31
  191. data/lib/simplecov/useless_results_remover.rb +7 -6
  192. data/lib/simplecov/version.rb +1 -1
  193. data/lib/simplecov/view_coverage/template_compiler.rb +83 -0
  194. data/lib/simplecov/view_coverage.rb +55 -0
  195. data/lib/simplecov-html.rb +1 -0
  196. data/lib/simplecov.rb +57 -113
  197. data/lib/simplecov_json_formatter.rb +1 -0
  198. data/man/simplecov.1 +354 -0
  199. data/sig/simplecov.rbs +528 -595
  200. metadata +79 -6
@@ -2,10 +2,8 @@
2
2
 
3
3
  module SimpleCov
4
4
  module ExitCodes
5
- # Runs every coverage check against the result and returns the exit
6
- # code from the first failing one (or SUCCESS if all pass).
7
5
  module ExitCodeHandling
8
- module_function
6
+ extend self
9
7
 
10
8
  def call(result, coverage_limits:)
11
9
  checks = coverage_checks(result, coverage_limits)
@@ -15,21 +13,36 @@ module SimpleCov
15
13
  failing_check.report if SimpleCov.print_errors
16
14
  failing_check.exit_code
17
15
  else
18
- SimpleCov::ExitCodes::SUCCESS
16
+ SUCCESS
19
17
  end
20
18
  end
21
19
 
22
20
  def coverage_checks(result, coverage_limits)
23
21
  [
24
22
  MinimumOverallCoverageCheck.new(result, coverage_limits.minimum_coverage),
25
- MinimumCoverageByFileCheck.new(
26
- result, coverage_limits.minimum_coverage_by_file, coverage_limits.minimum_coverage_by_file_overrides
27
- ),
23
+ minimum_by_file_check(result, coverage_limits),
24
+ BaselineCheck.new(result, coverage_limits.baseline),
28
25
  MinimumCoverageByGroupCheck.new(result, coverage_limits.minimum_coverage_by_group),
29
26
  MaximumOverallCoverageCheck.new(result, coverage_limits.maximum_coverage),
30
- MaximumCoverageDropCheck.new(result, coverage_limits.maximum_coverage_drop)
27
+ MaximumCoverageDropCheck.new(result, coverage_limits.maximum_coverage_drop),
28
+ MaximumMissedCheck.new(result, coverage_limits.maximum_missed),
29
+ maximum_missed_per_file_check(result, coverage_limits)
31
30
  ]
32
31
  end
32
+
33
+ def minimum_by_file_check(result, coverage_limits)
34
+ MinimumCoverageByFileCheck.new(
35
+ result, coverage_limits.minimum_coverage_by_file, coverage_limits.minimum_coverage_by_file_overrides,
36
+ baseline: coverage_limits.baseline
37
+ )
38
+ end
39
+
40
+ def maximum_missed_per_file_check(result, coverage_limits)
41
+ MaximumMissedPerFileCheck.new(
42
+ result, coverage_limits.maximum_missed_per_file, coverage_limits.maximum_missed_per_file_overrides,
43
+ baseline: coverage_limits.baseline
44
+ )
45
+ end
33
46
  end
34
47
  end
35
48
  end
@@ -2,24 +2,21 @@
2
2
 
3
3
  module SimpleCov
4
4
  module ExitCodes
5
- # Fails when any coverage criterion has dropped by more than the
6
- # configured maximum since the last recorded run.
7
5
  class MaximumCoverageDropCheck < Check
8
6
  def exit_code
9
- SimpleCov::ExitCodes::MAXIMUM_COVERAGE_DROP
7
+ MAXIMUM_COVERAGE_DROP
10
8
  end
11
9
 
12
- private
10
+ private
13
11
 
14
12
  def compute_violations
15
- SimpleCov::CoverageViolations.maximum_drop(result, thresholds)
13
+ CoverageViolations.maximum_drop(result, thresholds)
16
14
  end
17
15
 
18
- # The "drop percent" is a delta, not a coverage level it has no
19
- # natural green/yellow/red mapping, so the whole line goes red to
20
- # keep the failure visible at a glance.
21
- def report_violation(violation)
22
- ExitCodes.print_error SimpleCov::Color.colorize(message_for(violation), :red)
16
+ # The drop percent is a delta, not a coverage level, so it has no natural
17
+ # green/yellow/red mapping and the whole line goes red.
18
+ def violation_lines(violation)
19
+ [Color.colorize(message_for(violation), :red)]
23
20
  end
24
21
 
25
22
  def message_for(violation)
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleCov
4
+ module ExitCodes
5
+ class MaximumMissedCheck < Check
6
+ def exit_code
7
+ MINIMUM_COVERAGE
8
+ end
9
+
10
+ private
11
+
12
+ def compute_violations
13
+ CoverageViolations.maximum_missed(result, thresholds)
14
+ end
15
+
16
+ def violation_lines(violation)
17
+ [format(
18
+ "Missed %<units>s (%<actual>d) exceed the configured maximum_missed (%<maximum>d).",
19
+ units: UNITS.fetch(SimpleCov.coverage_statistics_key(violation.fetch(:criterion))),
20
+ actual: violation.fetch(:actual),
21
+ maximum: violation.fetch(:maximum)
22
+ )]
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleCov
4
+ module ExitCodes
5
+ class MaximumMissedPerFileCheck < Check
6
+ def initialize(result, maximum_missed_per_file, overrides = {}, baseline: nil)
7
+ super(result, maximum_missed_per_file)
8
+ @overrides = overrides
9
+ @baseline = baseline
10
+ end
11
+
12
+ def exit_code
13
+ MINIMUM_COVERAGE
14
+ end
15
+
16
+ private
17
+
18
+ def compute_violations
19
+ CoverageViolations.maximum_missed_by_file(result, thresholds, @overrides, baseline: @baseline)
20
+ end
21
+
22
+ def violation_lines(violation)
23
+ [format(
24
+ "Missed %<units>s (%<actual>d) exceed the configured maximum_missed_per_file (%<maximum>d) " \
25
+ "in %<filename>s.",
26
+ units: UNITS.fetch(SimpleCov.coverage_statistics_key(violation.fetch(:criterion))),
27
+ actual: violation.fetch(:actual),
28
+ maximum: violation.fetch(:maximum),
29
+ filename: violation.fetch(:project_filename)
30
+ )]
31
+ end
32
+ end
33
+ end
34
+ end
@@ -2,30 +2,25 @@
2
2
 
3
3
  module SimpleCov
4
4
  module ExitCodes
5
- # Fails when the overall (project-wide) coverage for any criterion is
6
- # above the configured maximum. Pair with
7
- # `SimpleCov::ExitCodes::MinimumOverallCoverageCheck` (or use
8
- # `SimpleCov.expected_coverage`) to pin coverage to an exact value
9
- # and surface unexpected increases instead of silently absorbing them.
10
5
  class MaximumOverallCoverageCheck < Check
11
6
  def exit_code
12
- SimpleCov::ExitCodes::MAXIMUM_COVERAGE
7
+ MAXIMUM_COVERAGE
13
8
  end
14
9
 
15
- private
10
+ private
16
11
 
17
12
  def compute_violations
18
- SimpleCov::CoverageViolations.maximum_overall(result, thresholds)
13
+ CoverageViolations.maximum_overall(result, thresholds)
19
14
  end
20
15
 
21
- def report_violation(violation)
22
- ExitCodes.print_error format(
16
+ def violation_lines(violation)
17
+ [format(
23
18
  "%<criterion>s coverage (%<actual>s) is above the expected maximum coverage (%<expected>.2f%%). " \
24
19
  "Time to bump the threshold!",
25
20
  criterion: violation.fetch(:criterion).capitalize,
26
- actual: SimpleCov::Color.colorize_percent(violation.fetch(:actual)),
21
+ actual: Color.colorize_percent(violation.fetch(:actual)),
27
22
  expected: violation.fetch(:expected)
28
- )
23
+ )]
29
24
  end
30
25
  end
31
26
  end
@@ -2,33 +2,34 @@
2
2
 
3
3
  module SimpleCov
4
4
  module ExitCodes
5
- # Fails when any individual file falls below the configured minimum
6
- # coverage for any criterion.
7
5
  class MinimumCoverageByFileCheck < Check
8
- def initialize(result, minimum_coverage_by_file, overrides = {})
6
+ # `baseline:` exempts the file-and-criterion pairs it covers: those answer to
7
+ # their own floor via `BaselineCheck` instead.
8
+ def initialize(result, minimum_coverage_by_file, overrides = {}, baseline: nil)
9
9
  super(result, minimum_coverage_by_file)
10
10
  @overrides = overrides
11
+ @baseline = baseline
11
12
  end
12
13
 
13
14
  def exit_code
14
- SimpleCov::ExitCodes::MINIMUM_COVERAGE
15
+ MINIMUM_COVERAGE
15
16
  end
16
17
 
17
- private
18
+ private
18
19
 
19
20
  def compute_violations
20
- SimpleCov::CoverageViolations.minimum_by_file(result, thresholds, @overrides)
21
+ CoverageViolations.minimum_by_file(result, thresholds, @overrides, baseline: @baseline)
21
22
  end
22
23
 
23
- def report_violation(violation)
24
- ExitCodes.print_error format(
24
+ def violation_lines(violation)
25
+ [format(
25
26
  "%<criterion>s coverage by file (%<actual>s) is below the expected minimum coverage " \
26
27
  "(%<expected>.2f%%) in %<filename>s.",
27
28
  criterion: violation.fetch(:criterion).capitalize,
28
- actual: SimpleCov::Color.colorize_percent(violation.fetch(:actual)),
29
+ actual: Color.colorize_percent(violation.fetch(:actual)),
29
30
  expected: violation.fetch(:expected),
30
31
  filename: violation.fetch(:project_filename)
31
- )
32
+ )]
32
33
  end
33
34
  end
34
35
  end
@@ -2,28 +2,26 @@
2
2
 
3
3
  module SimpleCov
4
4
  module ExitCodes
5
- # Fails when any configured group falls below its minimum coverage
6
- # threshold for any criterion.
7
5
  class MinimumCoverageByGroupCheck < Check
8
6
  def exit_code
9
- SimpleCov::ExitCodes::MINIMUM_COVERAGE
7
+ MINIMUM_COVERAGE
10
8
  end
11
9
 
12
- private
10
+ private
13
11
 
14
12
  def compute_violations
15
- SimpleCov::CoverageViolations.minimum_by_group(result, thresholds)
13
+ CoverageViolations.minimum_by_group(result, thresholds)
16
14
  end
17
15
 
18
- def report_violation(violation)
19
- ExitCodes.print_error format(
16
+ def violation_lines(violation)
17
+ [format(
20
18
  "%<criterion>s coverage by group (%<actual>s) is below the expected minimum coverage " \
21
19
  "(%<expected>.2f%%) in %<group_name>s.",
22
20
  criterion: violation.fetch(:criterion).capitalize,
23
- actual: SimpleCov::Color.colorize_percent(violation.fetch(:actual)),
21
+ actual: Color.colorize_percent(violation.fetch(:actual)),
24
22
  expected: violation.fetch(:expected),
25
23
  group_name: violation.fetch(:group_name)
26
- )
24
+ )]
27
25
  end
28
26
  end
29
27
  end
@@ -2,53 +2,52 @@
2
2
 
3
3
  module SimpleCov
4
4
  module ExitCodes
5
- # Fails when the overall (project-wide) coverage for any criterion is
6
- # below the configured minimum.
7
5
  class MinimumOverallCoverageCheck < Check
8
6
  def exit_code
9
- SimpleCov::ExitCodes::MINIMUM_COVERAGE
7
+ MINIMUM_COVERAGE
10
8
  end
11
9
 
12
- private
10
+ private
13
11
 
14
12
  WORST_FILES_LIMIT = 5
15
13
  private_constant :WORST_FILES_LIMIT
16
14
 
17
15
  def compute_violations
18
- SimpleCov::CoverageViolations.minimum_overall(result, thresholds)
16
+ CoverageViolations.minimum_overall(result, thresholds)
19
17
  end
20
18
 
21
- def report_violation(violation)
19
+ def violation_lines(violation)
22
20
  criterion = violation.fetch(:criterion)
23
- actual = violation.fetch(:actual)
24
- ExitCodes.print_error format(
21
+ headline = format(
25
22
  "%<criterion>s coverage (%<actual>s) is below the expected minimum coverage (%<expected>.2f%%).",
26
23
  criterion: criterion.capitalize,
27
- actual: SimpleCov::Color.colorize_percent(actual),
24
+ actual: Color.colorize_percent(violation.fetch(:actual)),
28
25
  expected: violation.fetch(:expected)
29
26
  )
30
- report_worst_files(criterion)
27
+ [headline, *worst_files_lines(criterion)]
31
28
  end
32
29
 
33
- def report_worst_files(criterion)
30
+ def worst_files_lines(criterion)
34
31
  worst = worst_files_for(criterion)
35
- return if worst.empty?
32
+ return [] if worst.empty?
36
33
 
37
- ExitCodes.print_error " Lowest-coverage files (#{criterion}):"
38
- worst.each do |path, percent|
39
- ExitCodes.print_error format(
34
+ [" Lowest-coverage files (#{criterion}):"] + worst.map do |path, percent|
35
+ format(
40
36
  " %<percent>s %<path>s",
41
- percent: SimpleCov::Color.colorize_percent(percent, format("%6.2f%%", percent)),
37
+ percent: Color.colorize_percent(percent, format("%6.2f%%", percent)),
42
38
  path: path
43
39
  )
44
40
  end
45
41
  end
46
42
 
43
+ # The list is a hint at where to add tests, so a fully covered file has no
44
+ # business on it: without the filter, files at 100% padded the list out to
45
+ # its five entries (#1286).
47
46
  def worst_files_for(criterion)
48
47
  stats_key = SimpleCov.coverage_statistics_key(criterion)
49
48
  with_stats = result.files.filter_map do |source_file|
50
49
  stats = source_file.coverage_statistics[stats_key]
51
- [source_file.project_filename, stats.percent] if stats
50
+ [source_file.project_filename, stats.percent] if stats && stats.percent < 100
52
51
  end
53
52
  with_stats.sort_by { |_path, percent| percent }.first(WORST_FILES_LIMIT)
54
53
  end
@@ -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,25 +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
21
25
  rescue IOError
22
- # A parallel runner can close a worker's stderr before its at_exit
23
- # hooks run (rspec-conductor does). The violation still has to set
24
- # the exit status even when its explanation cannot be printed.
26
+ # The violation still has to set the exit status even when its
27
+ # explanation cannot be printed.
25
28
  end
26
29
  end
27
30
  end
28
31
 
29
32
  require_relative "exit_codes/check"
33
+ require_relative "exit_codes/baseline_check"
30
34
  require_relative "exit_codes/exit_code_handling"
31
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"
32
38
  require_relative "exit_codes/maximum_overall_coverage_check"
33
39
  require_relative "exit_codes/minimum_coverage_by_file_check"
34
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