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
@@ -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)
@@ -6,42 +6,35 @@ module SimpleCov
6
6
  module Formatter
7
7
  # @api private
8
8
  #
9
- # Shared scaffolding for formatters that write a coverage report to
10
- # an output directory and emit a "Coverage report generated for X
11
- # to Y" summary on stderr (it's a status message, not data).
12
- # Subclasses override `format` to do their actual writing, and may
13
- # override `message_prefix` (e.g. JSON prepends "JSON ").
9
+ # Shared scaffolding for formatters that write a coverage report to an
10
+ # output directory and emit a "Coverage report generated for X to Y"
11
+ # summary on stderr. Subclasses override `format`, and may override
12
+ # `message_prefix`.
14
13
  class Base
15
- # `output_dir` defaults to `SimpleCov.coverage_path` so the at_exit
16
- # pipeline keeps working unchanged. Pass it explicitly to write
17
- # somewhere else (handy for tests that don't want to clobber the
18
- # project's `coverage/` directory).
14
+ # `output_dir` defaults to `SimpleCov.coverage_path`. Pass it explicitly
15
+ # to write somewhere else.
19
16
  def initialize(silent: false, output_dir: nil)
20
17
  @silent = silent
21
18
  @output_dir = output_dir
22
19
  end
23
20
 
24
- private
21
+ private
25
22
 
26
23
  # The one home of the "status lines go to stderr, not through warn"
27
- # decision (see #1225). stderr rather than stdout because this is a
28
- # status message, not the program's output, so it stays out of
29
- # pipelines like `rspec -f json`. And `$stderr.puts` rather than
30
- # `warn` so the line neither reaches `Warning.warn` hooks (warning
31
- # trackers, raise-on-warning test setups) nor vanishes under `-W0`.
32
- # Subclasses call this at the end of their `format`.
24
+ # decision (#1225). stderr rather than stdout because this is a status
25
+ # message, not the program's output, so it stays out of pipelines like
26
+ # `rspec -f json`. And `$stderr.puts` rather than `warn` so the line
27
+ # neither reaches `Warning.warn` hooks nor vanishes under `-W0`.
33
28
  def emit_status(result)
34
29
  $stderr.puts output_message(result) unless @silent # rubocop:disable Style/StderrPuts
35
30
  rescue IOError
36
- # A parallel runner can close a worker's stderr before its at_exit
37
- # hooks run (rspec-conductor does). Losing the status line must not
38
- # abort the exit tasks that follow report generation, i.e. the
39
- # threshold checks and the .last_run.json write.
31
+ # A parallel runner can close a worker's stderr before its at_exit hooks
32
+ # run (rspec-conductor does). Losing the status line must not abort the
33
+ # exit tasks that follow report generation.
40
34
  end
41
35
 
42
- # Subclasses override to prepend a marker (e.g. "JSON ") to the
43
- # summary line. Default empty for the HTML formatter, which has
44
- # historically been the unmarked default.
36
+ # Subclasses override to prepend a marker (e.g. "JSON ") to the summary
37
+ # line. Empty for the HTML formatter, historically the unmarked default.
45
38
  def message_prefix
46
39
  ""
47
40
  end
@@ -50,15 +43,11 @@ module SimpleCov
50
43
  @output_dir || SimpleCov.coverage_path
51
44
  end
52
45
 
53
- # The path shown in the "Coverage report generated for X to Y"
54
- # status line. Renders relative to cwd when `output_path` lives
55
- # inside cwd (e.g. `coverage` instead of `/Users/me/proj/coverage`)
56
- # and appends the formatter's `entry_point_filename` so the line
57
- # points at a concrete file the user (or a terminal that
58
- # hyperlinks paths) can act on — e.g. `coverage/index.html`
59
- # instead of the bare directory `coverage`. Paths outside cwd
60
- # stay absolute; a `../../../tmp/cov` display would be more
61
- # confusing than the absolute form. See issue #197.
46
+ # The path shown in the "Coverage report generated for X to Y" status line.
47
+ # Rendered relative to cwd when `output_path` lives inside cwd, with the
48
+ # formatter's `entry_point_filename` appended so the line points at a
49
+ # concrete file a terminal can hyperlink. Paths outside cwd stay
50
+ # absolute; a `../../../tmp/cov` display would be more confusing (#197).
62
51
  def displayable_output_path
63
52
  directory = relative_or_absolute_output_path
64
53
  entry_point = entry_point_filename
@@ -70,47 +59,40 @@ module SimpleCov
70
59
  relative = Pathname.new(absolute).relative_path_from(Pathname.pwd).to_s
71
60
  relative.start_with?("..") ? absolute : relative
72
61
  rescue ArgumentError
73
- # Pathname#relative_path_from raises across mixed absolute/
74
- # relative inputs (and across Windows drives) — keep the
75
- # absolute form on any unresolvable case.
62
+ # Pathname#relative_path_from raises across mixed absolute/relative
63
+ # inputs, and across Windows drives.
76
64
  output_path
77
65
  end
78
66
 
79
- # Subclasses override to name the report's entry-point file
80
- # (e.g. `index.html` for HTML, `coverage.json` for JSON), which
81
- # gets appended to the directory in the status line. Default nil
82
- # leaves the bare directory in place for any third-party formatter
83
- # that has no single canonical entry point.
67
+ # Subclasses override to name the report's entry-point file, which gets
68
+ # appended to the directory in the status line. The empty body answers
69
+ # nil, which leaves the bare directory in place for any third-party
70
+ # formatter that has no single canonical entry point.
84
71
  def entry_point_filename
85
- nil
86
72
  end
87
73
 
88
- # Emit one summary line per criterion that the run actually
89
- # measured. The header line ("Coverage report generated for X
90
- # to Y") is always first; per-criterion lines follow in the
91
- # order of `result.coverage_statistics` (which is the same
92
- # insertion order as `SourceFile#coverage_statistics`, which in
93
- # turn reflects what the user enabled).
74
+ # One summary line per criterion the run actually measured, in the order
75
+ # of `result.coverage_statistics`, which reflects what the user enabled.
94
76
  def output_message(result)
95
77
  header = "#{message_prefix}Coverage report generated for #{result.command_name} to #{displayable_output_path}"
96
- body = result.coverage_statistics.filter_map { |criterion, stat| stats_line(criterion, stat) }
78
+ body = result.coverage_statistics.filter_map { |criterion, stat| stats_line(criterion, stat) }
97
79
  [header, *body].join("\n")
98
80
  end
99
81
 
100
- # Returns nil for branch/method criteria that have nothing to
101
- # measure (e.g. a file with no branches under branch coverage).
102
- # Showing "Branch coverage: 0 / 0 (100.00%)" is noise; the older
103
- # output specifically suppressed it.
82
+ # Returns nil for branch/method criteria that have nothing to measure.
83
+ # Showing "Branch coverage: 0 / 0 (100.00%)" is noise.
104
84
  def stats_line(criterion, stat)
105
- return if criterion != :line && !stat.total.positive?
85
+ return if !criterion.equal?(:line) && !stat.total.positive?
106
86
 
107
87
  percent = SimpleCov.round_coverage(stat.percent)
88
+ # `Symbol#capitalize` answers a Symbol, which `%<label>s` renders as its
89
+ # capitalized name, so the label needs no `to_s` step of its own.
108
90
  Kernel.format(
109
91
  "%<label>s coverage: %<covered>d / %<total>d (%<percent>s)",
110
- label: criterion.to_s.capitalize,
92
+ label: criterion.capitalize,
111
93
  covered: stat.covered,
112
94
  total: stat.total,
113
- percent: SimpleCov::Color.colorize_percent(percent)
95
+ percent: Color.colorize_percent(percent)
114
96
  )
115
97
  end
116
98
  end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+ require_relative "../atomic_file"
5
+
6
+ module SimpleCov
7
+ module Formatter
8
+ # Auto-ratchets the per-file coverage baseline as a formatter, for teams
9
+ # that want floors to tighten on every run instead of by deliberate
10
+ # `simplecov ratchet` invocations. The semantics are exactly the CLI's, and
11
+ # the file is rewritten only when a floor actually moved.
12
+ #
13
+ # The exit checks still judge the run against the floors as they were when
14
+ # it started (the baseline is read once per path), and ratcheting never
15
+ # loosens a floor, so tightening before the checks cannot flip a failing
16
+ # file to passing.
17
+ class BaselineFormatter < Base
18
+ def format(result)
19
+ emit_status(ratchet(result))
20
+ end
21
+
22
+ private
23
+
24
+ def ratchet(result)
25
+ current = current_floors(result)
26
+ existing = SimpleCov.baseline
27
+ return generate(current) unless existing
28
+
29
+ outcome = existing.ratchet(current)
30
+ changed = outcome.tightened.any? || outcome.pruned.any?
31
+ write(outcome.baseline) if changed
32
+ note = summary(outcome, changed)
33
+ regressed = outcome.regressed
34
+ regressed.empty? ? note : "#{note}#{below_floors(regressed)}"
35
+ end
36
+
37
+ def generate(current)
38
+ baseline = Baseline.generate(current)
39
+ write(baseline)
40
+ files = baseline.entries.size
41
+ "Coverage baseline generated to #{displayable_output_path} (#{files} #{files.eql?(1) ? "file" : "files"})"
42
+ end
43
+
44
+ def summary(outcome, changed)
45
+ return "Coverage baseline unchanged at #{displayable_output_path}" unless changed
46
+
47
+ "Coverage baseline ratcheted to #{displayable_output_path} " \
48
+ "(#{outcome.tightened.size} tightened, #{outcome.pruned.size} pruned)"
49
+ end
50
+
51
+ def below_floors(regressed)
52
+ noun = regressed.size.eql?(1) ? "1 file below its floor" : "#{regressed.size} files below their floors"
53
+ ", #{noun}: #{regressed.join(", ")}"
54
+ end
55
+
56
+ # `SourceFile#coverage_statistics` answers for disabled criteria too, as
57
+ # empty statistics, so the criteria are gated on the configuration rather
58
+ # than on the stats' presence.
59
+ def current_floors(result)
60
+ criteria = measured_criteria
61
+ floors = {} #: Baseline::current_floors
62
+ result.files.each do |file|
63
+ floors[file.project_filename] = criteria.to_h { |criterion| [criterion, floor_of(file, criterion)] }
64
+ end
65
+ floors
66
+ end
67
+
68
+ def measured_criteria
69
+ criteria = [] #: Array[Symbol]
70
+ criteria << :line if SimpleCov.line_coverage?
71
+ criteria << :branch if SimpleCov.branch_coverage?
72
+ criteria << :method if SimpleCov.method_coverage?
73
+ criteria
74
+ end
75
+
76
+ def floor_of(file, criterion)
77
+ stats = file.coverage_statistics(criterion)
78
+ {percent: SimpleCov.round_coverage(stats.percent), missed: stats.missed}
79
+ end
80
+
81
+ def write(baseline)
82
+ AtomicFile.write(output_path, baseline.to_yaml)
83
+ end
84
+
85
+ # The baseline lives where the exit check reads it, not under the coverage
86
+ # directory: it is checked-in policy, not a report artifact.
87
+ def output_path
88
+ File.expand_path(SimpleCov.baseline_file, SimpleCov.root)
89
+ end
90
+
91
+ # `format` hands the ratchet's summary to `emit_status`, which passes it
92
+ # through to here in the result's place.
93
+ def output_message(message)
94
+ message
95
+ end
96
+ end
97
+ end
98
+ end