simplecov 0.22.0 → 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 (210) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +1 -1
  3. data/README.md +173 -875
  4. data/exe/simplecov +8 -0
  5. data/lib/minitest/simplecov_plugin.rb +18 -7
  6. data/lib/simplecov/atomic_file.rb +67 -0
  7. data/lib/simplecov/autostart.rb +9 -0
  8. data/lib/simplecov/baseline/parser.rb +58 -0
  9. data/lib/simplecov/baseline.rb +170 -0
  10. data/lib/simplecov/cli/affected/changed_files.rb +57 -0
  11. data/lib/simplecov/cli/affected/selection.rb +117 -0
  12. data/lib/simplecov/cli/affected.rb +148 -0
  13. data/lib/simplecov/cli/annotations.rb +166 -0
  14. data/lib/simplecov/cli/badge/svg.rb +70 -0
  15. data/lib/simplecov/cli/badge.rb +62 -0
  16. data/lib/simplecov/cli/clean.rb +86 -0
  17. data/lib/simplecov/cli/command_helpers.rb +91 -0
  18. data/lib/simplecov/cli/completions/scripts.rb +114 -0
  19. data/lib/simplecov/cli/completions.rb +83 -0
  20. data/lib/simplecov/cli/coverage.rb +83 -0
  21. data/lib/simplecov/cli/coverage_file.rb +109 -0
  22. data/lib/simplecov/cli/dead_code/output.rb +100 -0
  23. data/lib/simplecov/cli/dead_code.rb +139 -0
  24. data/lib/simplecov/cli/diff/output.rb +45 -0
  25. data/lib/simplecov/cli/diff.rb +115 -0
  26. data/lib/simplecov/cli/dotfile.rb +156 -0
  27. data/lib/simplecov/cli/git.rb +41 -0
  28. data/lib/simplecov/cli/history/output.rb +135 -0
  29. data/lib/simplecov/cli/history.rb +67 -0
  30. data/lib/simplecov/cli/merge.rb +115 -0
  31. data/lib/simplecov/cli/open.rb +45 -0
  32. data/lib/simplecov/cli/patch/changed_lines.rb +149 -0
  33. data/lib/simplecov/cli/patch/output.rb +151 -0
  34. data/lib/simplecov/cli/patch.rb +175 -0
  35. data/lib/simplecov/cli/ratchet/output.rb +44 -0
  36. data/lib/simplecov/cli/ratchet.rb +83 -0
  37. data/lib/simplecov/cli/report.rb +91 -0
  38. data/lib/simplecov/cli/run.rb +35 -0
  39. data/lib/simplecov/cli/serve/report_preparer.rb +30 -0
  40. data/lib/simplecov/cli/serve/static_file_handler.rb +128 -0
  41. data/lib/simplecov/cli/serve.rb +88 -0
  42. data/lib/simplecov/cli/show/annotator.rb +87 -0
  43. data/lib/simplecov/cli/show/sweep.rb +36 -0
  44. data/lib/simplecov/cli/show.rb +133 -0
  45. data/lib/simplecov/cli/status/facts.rb +87 -0
  46. data/lib/simplecov/cli/status.rb +93 -0
  47. data/lib/simplecov/cli/tests/redundancy.rb +78 -0
  48. data/lib/simplecov/cli/tests.rb +148 -0
  49. data/lib/simplecov/cli/uncovered/misses.rb +33 -0
  50. data/lib/simplecov/cli/uncovered.rb +127 -0
  51. data/lib/simplecov/cli/usage.rb +153 -0
  52. data/lib/simplecov/cli/watch/live_report.rb +73 -0
  53. data/lib/simplecov/cli/watch/narrator.rb +55 -0
  54. data/lib/simplecov/cli/watch/poller.rb +45 -0
  55. data/lib/simplecov/cli/watch/session.rb +134 -0
  56. data/lib/simplecov/cli/watch/test_plan.rb +43 -0
  57. data/lib/simplecov/cli/watch.rb +85 -0
  58. data/lib/simplecov/cli.rb +114 -0
  59. data/lib/simplecov/color.rb +65 -0
  60. data/lib/simplecov/combine/branches_combiner.rb +44 -21
  61. data/lib/simplecov/combine/coverage_accumulator.rb +175 -0
  62. data/lib/simplecov/combine/identity_interner.rb +27 -0
  63. data/lib/simplecov/combine/interned_counts.rb +28 -0
  64. data/lib/simplecov/combine/lines_combiner.rb +35 -29
  65. data/lib/simplecov/combine/methods_combiner.rb +48 -0
  66. data/lib/simplecov/combine/results_combiner.rb +9 -49
  67. data/lib/simplecov/combine.rb +0 -24
  68. data/lib/simplecov/command_guesser.rb +99 -42
  69. data/lib/simplecov/configuration/baseline.rb +31 -0
  70. data/lib/simplecov/configuration/coverage.rb +184 -0
  71. data/lib/simplecov/configuration/coverage_criteria.rb +143 -0
  72. data/lib/simplecov/configuration/deprecations.rb +21 -0
  73. data/lib/simplecov/configuration/eval_coverage.rb +37 -0
  74. data/lib/simplecov/configuration/filters.rb +131 -0
  75. data/lib/simplecov/configuration/formatting.rb +129 -0
  76. data/lib/simplecov/configuration/groups.rb +48 -0
  77. data/lib/simplecov/configuration/history.rb +44 -0
  78. data/lib/simplecov/configuration/ignored_entries.rb +72 -0
  79. data/lib/simplecov/configuration/merging.rb +197 -0
  80. data/lib/simplecov/configuration/missed_caps.rb +51 -0
  81. data/lib/simplecov/configuration/production.rb +23 -0
  82. data/lib/simplecov/configuration/test_tracking.rb +48 -0
  83. data/lib/simplecov/configuration/thresholds.rb +150 -0
  84. data/lib/simplecov/configuration/view_coverage.rb +37 -0
  85. data/lib/simplecov/configuration.rb +100 -443
  86. data/lib/simplecov/configuration_error.rb +5 -0
  87. data/lib/simplecov/context_map/union.rb +81 -0
  88. data/lib/simplecov/context_map.rb +187 -0
  89. data/lib/simplecov/coverage_json.rb +21 -0
  90. data/lib/simplecov/coverage_statistics.rb +24 -28
  91. data/lib/simplecov/coverage_violations.rb +254 -0
  92. data/lib/simplecov/current_run.rb +61 -0
  93. data/lib/simplecov/defaults.rb +20 -28
  94. data/lib/simplecov/deprecation.rb +63 -0
  95. data/lib/simplecov/directive/erb.rb +44 -0
  96. data/lib/simplecov/directive/haml.rb +33 -0
  97. data/lib/simplecov/directive/indented_template.rb +37 -0
  98. data/lib/simplecov/directive/slim.rb +35 -0
  99. data/lib/simplecov/directive/template.rb +29 -0
  100. data/lib/simplecov/directive.rb +152 -0
  101. data/lib/simplecov/exit_codes/baseline_check.rb +46 -0
  102. data/lib/simplecov/exit_codes/check.rb +39 -0
  103. data/lib/simplecov/exit_codes/exit_code_handling.rb +24 -5
  104. data/lib/simplecov/exit_codes/maximum_coverage_drop_check.rb +17 -67
  105. data/lib/simplecov/exit_codes/maximum_missed_check.rb +26 -0
  106. data/lib/simplecov/exit_codes/maximum_missed_per_file_check.rb +34 -0
  107. data/lib/simplecov/exit_codes/maximum_overall_coverage_check.rb +27 -0
  108. data/lib/simplecov/exit_codes/minimum_coverage_by_file_check.rb +20 -38
  109. data/lib/simplecov/exit_codes/minimum_coverage_by_group_check.rb +28 -0
  110. data/lib/simplecov/exit_codes/minimum_overall_coverage_check.rb +37 -34
  111. data/lib/simplecov/exit_codes.rb +26 -0
  112. data/lib/simplecov/exit_handling.rb +163 -0
  113. data/lib/simplecov/file_list.rb +63 -48
  114. data/lib/simplecov/filter.rb +98 -42
  115. data/lib/simplecov/formatter/base.rb +100 -0
  116. data/lib/simplecov/formatter/baseline_formatter.rb +98 -0
  117. data/lib/simplecov/formatter/coverage_json_writer.rb +95 -0
  118. data/lib/simplecov/formatter/html_formatter/public/index.html +93 -0
  119. data/lib/simplecov/formatter/html_formatter/viewer_data_validator.rb +153 -0
  120. data/lib/simplecov/formatter/html_formatter.rb +88 -0
  121. data/lib/simplecov/formatter/json_formatter/errors_formatter.rb +122 -0
  122. data/lib/simplecov/formatter/json_formatter/production_section_formatter.rb +48 -0
  123. data/lib/simplecov/formatter/json_formatter/result_hash_formatter.rb +132 -0
  124. data/lib/simplecov/formatter/json_formatter/source_file_formatter.rb +97 -0
  125. data/lib/simplecov/formatter/json_formatter.rb +40 -0
  126. data/lib/simplecov/formatter/multi_formatter.rb +8 -10
  127. data/lib/simplecov/formatter/simple_formatter.rb +12 -15
  128. data/lib/simplecov/formatter.rb +21 -1
  129. data/lib/simplecov/group_names.rb +33 -0
  130. data/lib/simplecov/history.rb +115 -0
  131. data/lib/simplecov/last_run.rb +16 -5
  132. data/lib/simplecov/lines_classifier.rb +67 -19
  133. data/lib/simplecov/load_global_config.rb +12 -4
  134. data/lib/simplecov/parallel_adapters/base.rb +50 -0
  135. data/lib/simplecov/parallel_adapters/generic.rb +33 -0
  136. data/lib/simplecov/parallel_adapters/parallel_tests.rb +72 -0
  137. data/lib/simplecov/parallel_adapters.rb +54 -0
  138. data/lib/simplecov/parallel_coordination.rb +124 -0
  139. data/lib/simplecov/parallel_result_merger/worker_payload.rb +33 -0
  140. data/lib/simplecov/parallel_result_merger.rb +171 -0
  141. data/lib/simplecov/process.rb +34 -12
  142. data/lib/simplecov/production/error.rb +11 -0
  143. data/lib/simplecov/production/file_sink.rb +130 -0
  144. data/lib/simplecov/production.rb +249 -0
  145. data/lib/simplecov/profiles/bundler_filter.rb +1 -1
  146. data/lib/simplecov/profiles/hidden_filter.rb +1 -1
  147. data/lib/simplecov/profiles/rails.rb +13 -10
  148. data/lib/simplecov/profiles/root_filter.rb +2 -5
  149. data/lib/simplecov/profiles/strict.rb +22 -0
  150. data/lib/simplecov/profiles/test_frameworks.rb +1 -4
  151. data/lib/simplecov/profiles.rb +26 -19
  152. data/lib/simplecov/report_deferral.rb +45 -0
  153. data/lib/simplecov/report_stamp.rb +25 -0
  154. data/lib/simplecov/result/filter_config.rb +19 -0
  155. data/lib/simplecov/result/missing_source_files_reporter.rb +48 -0
  156. data/lib/simplecov/result/serialization.rb +43 -0
  157. data/lib/simplecov/result/source_file_builder.rb +46 -0
  158. data/lib/simplecov/result.rb +123 -47
  159. data/lib/simplecov/result_adapter.rb +130 -11
  160. data/lib/simplecov/result_merger/contexts.rb +22 -0
  161. data/lib/simplecov/result_merger/legacy_format_adapter.rb +24 -0
  162. data/lib/simplecov/result_merger/resultset_file.rb +67 -0
  163. data/lib/simplecov/result_merger/resultset_run_identity.rb +65 -0
  164. data/lib/simplecov/result_merger/resultset_store.rb +62 -0
  165. data/lib/simplecov/result_merger/unloaded_files.rb +95 -0
  166. data/lib/simplecov/result_merger.rb +120 -124
  167. data/lib/simplecov/result_processing.rb +188 -0
  168. data/lib/simplecov/run_identity.rb +75 -0
  169. data/lib/simplecov/simulate_coverage.rb +72 -17
  170. data/lib/simplecov/source_file/branch.rb +8 -35
  171. data/lib/simplecov/source_file/branch_builder.rb +95 -0
  172. data/lib/simplecov/source_file/builder_context.rb +28 -0
  173. data/lib/simplecov/source_file/line.rb +17 -32
  174. data/lib/simplecov/source_file/line_builder.rb +37 -0
  175. data/lib/simplecov/source_file/method.rb +54 -0
  176. data/lib/simplecov/source_file/method_builder.rb +51 -0
  177. data/lib/simplecov/source_file/ruby_data_parser.rb +103 -0
  178. data/lib/simplecov/source_file/skip_chunks.rb +72 -0
  179. data/lib/simplecov/source_file/source_loader.rb +89 -0
  180. data/lib/simplecov/source_file/statistics.rb +76 -0
  181. data/lib/simplecov/source_file.rb +64 -260
  182. data/lib/simplecov/static_coverage_extractor/condition_folding.rb +155 -0
  183. data/lib/simplecov/static_coverage_extractor/location_conventions.rb +191 -0
  184. data/lib/simplecov/static_coverage_extractor/method_collector.rb +45 -0
  185. data/lib/simplecov/static_coverage_extractor/prism_compat.rb +46 -0
  186. data/lib/simplecov/static_coverage_extractor/value_position.rb +61 -0
  187. data/lib/simplecov/static_coverage_extractor/visitor.rb +163 -0
  188. data/lib/simplecov/static_coverage_extractor.rb +78 -0
  189. data/lib/simplecov/test_tracker/accessors.rb +34 -0
  190. data/lib/simplecov/test_tracker/constant_watch.rb +49 -0
  191. data/lib/simplecov/test_tracker/delta.rb +69 -0
  192. data/lib/simplecov/test_tracker/framework_hooks.rb +130 -0
  193. data/lib/simplecov/test_tracker.rb +162 -0
  194. data/lib/simplecov/unloaded_file_injector.rb +62 -0
  195. data/lib/simplecov/useless_results_remover.rb +17 -7
  196. data/lib/simplecov/version.rb +1 -1
  197. data/lib/simplecov/view_coverage/template_compiler.rb +83 -0
  198. data/lib/simplecov/view_coverage.rb +55 -0
  199. data/lib/simplecov-html.rb +5 -0
  200. data/lib/simplecov.rb +143 -394
  201. data/lib/simplecov_json_formatter.rb +5 -0
  202. data/man/simplecov.1 +354 -0
  203. data/sig/simplecov.rbs +1710 -0
  204. metadata +183 -61
  205. data/CHANGELOG.md +0 -191
  206. data/doc/alternate-formatters.md +0 -71
  207. data/doc/commercial-services.md +0 -25
  208. data/doc/editor-integration.md +0 -18
  209. data/lib/simplecov/combine/files_combiner.rb +0 -24
  210. data/lib/simplecov/default_formatter.rb +0 -20
@@ -0,0 +1,131 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleCov
4
+ module Configuration
5
+ attr_writer :filters
6
+
7
+ #
8
+ # Restrict the universe of files in the coverage report to those matching
9
+ # one or more globs, regexps, or block predicates. Multiple calls union;
10
+ # when any `cover` matcher is configured the report drops every file that
11
+ # doesn't match at least one of them.
12
+ #
13
+ # Strings are interpreted as shell globs, not substring matches, a
14
+ # deliberate departure from the legacy `add_filter` semantics.
15
+ #
16
+ # A string glob is also expanded on disk, so files that exist but were
17
+ # never required during the run still appear in the report at 0%. This is
18
+ # the "include unloaded files" half of the legacy `track_files` behavior.
19
+ #
20
+ def cover(*args, &block)
21
+ args.each { |arg| cover_filters << build_cover_filter(arg) }
22
+ cover_filters << BlockFilter.new(block) if block
23
+ cover_filters
24
+ end
25
+
26
+ def cover_filters
27
+ @cover_filters ||= []
28
+ end
29
+
30
+ # The string globs passed to `cover`, used by the disk-discovery pass in
31
+ # `SimpleCov.tracked_file_paths` so files matching a `cover` glob appear in
32
+ # the report even when they were never required during the suite.
33
+ def cover_globs
34
+ collect_cover_globs(cover_filters)
35
+ end
36
+
37
+ def track_files(glob)
38
+ Deprecation.warn("`SimpleCov.track_files` is deprecated. " \
39
+ "#{track_files_replacement_hint(glob)}")
40
+ @tracked_files = glob
41
+ end
42
+
43
+ # `track_files(nil)` is the documented way to clear a previously-set glob,
44
+ # but `cover(nil)` raises `ConfigurationError`, so don't point users at it.
45
+ def track_files_replacement_hint(glob)
46
+ if glob.nil?
47
+ "Replace with `SimpleCov.cover_filters.clear` — clearing the inclusion list."
48
+ else
49
+ "Replace with `SimpleCov.cover #{glob.inspect}` — `cover` includes unloaded files on disk " \
50
+ "(the historical `track_files` behavior) and also restricts the report to the matching set. " \
51
+ "If you want to keep additional files outside #{glob.inspect} in the report, pass every " \
52
+ "directory you care about, e.g. `cover #{glob.inspect}, \"app/**/*.rb\"`."
53
+ end
54
+ end
55
+
56
+ # Nil until `cover` names a glob: an unset ivar reads as nil, so the absence
57
+ # needs no guard of its own.
58
+ def tracked_files
59
+ @tracked_files
60
+ end
61
+
62
+ def filters
63
+ @filters ||= []
64
+ end
65
+
66
+ #
67
+ # Drop matching files from the coverage report. The inverse of `cover`.
68
+ # Accepts a String (path-segment substring), Regexp, block predicate, or
69
+ # Array of any of those.
70
+ #
71
+ def skip(filter_argument = nil, &)
72
+ filters << parse_filter(filter_argument, &)
73
+ end
74
+
75
+ def add_filter(filter_argument = nil, &block)
76
+ example = block ? "`SimpleCov.skip { ... }`" : "`SimpleCov.skip #{filter_argument.inspect}`"
77
+ Deprecation.warn("`SimpleCov.add_filter` is deprecated. " \
78
+ "Replace with `SimpleCov.skip` (same arguments, same behavior). Example: #{example}.")
79
+ skip(filter_argument, &block)
80
+ end
81
+
82
+ # `reject!` answers nil when it rejected nothing, which is the whole of
83
+ # "was anything removed".
84
+ def remove_filter(filter_argument)
85
+ rejected = filters.reject! do |filter|
86
+ filter.respond_to?(:filter_argument) && filter.filter_argument.eql?(filter_argument)
87
+ end
88
+ !rejected.nil?
89
+ end
90
+
91
+ def clear_filters
92
+ @filters = []
93
+ end
94
+
95
+ # Order matters: call this before your own `skip` invocations.
96
+ def no_default_skips
97
+ clear_filters
98
+ end
99
+
100
+ private
101
+
102
+ def parse_filter(filter_argument = nil, &filter_proc)
103
+ filter = filter_argument || filter_proc
104
+
105
+ raise ArgumentError, "Please specify either a filter or a block to filter with" unless filter
106
+
107
+ Filter.build_filter(filter)
108
+ end
109
+
110
+ # Strings are treated as globs, not substrings (that's `skip`'s
111
+ # semantics); everything else dispatches exactly like `add_filter`.
112
+ def build_cover_filter(arg)
113
+ Filter.build_filter(arg, string_filter: GlobFilter)
114
+ rescue ConfigurationError
115
+ raise ConfigurationError, "Unsupported `cover` argument #{arg.inspect}; " \
116
+ "expected a String glob, Regexp, Proc, " \
117
+ "SimpleCov::Filter, or Array of those."
118
+ end
119
+
120
+ # Descends into `ArrayFilter` wrappers built by `cover(["a", "b"])`.
121
+ def collect_cover_globs(filter_list)
122
+ filter_list.flat_map do |filter|
123
+ case filter
124
+ when GlobFilter then filter.filter_argument
125
+ when ArrayFilter then collect_cover_globs(filter.filter_argument)
126
+ else []
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,129 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../formatter/multi_formatter"
4
+
5
+ module SimpleCov
6
+ module Configuration
7
+ attr_writer :formatter, :print_error_status
8
+
9
+ BUILT_IN_FORMATS = {
10
+ html: :HTMLFormatter, json: :JSONFormatter, simple: :SimpleFormatter, baseline: :BaselineFormatter
11
+ }.freeze
12
+ private_constant :BUILT_IN_FORMATS
13
+
14
+ LAZY_FORMAT_REQUIRES = {html: "../../simplecov-html"}.freeze
15
+ private_constant :LAZY_FORMAT_REQUIRES
16
+
17
+ def formats(*names)
18
+ return formatters if names.empty?
19
+
20
+ self.formatters = names.map { |name| resolve_format(name) }
21
+ formatters
22
+ end
23
+
24
+ def formatter(formatter = :__no_arg__)
25
+ case formatter
26
+ when :__no_arg__
27
+ @formatter
28
+ else
29
+ @formatter = formatter || nil
30
+ end
31
+ end
32
+
33
+ def formatters(formatters = :__no_arg__)
34
+ case formatters
35
+ when :__no_arg__
36
+ configured = formatter
37
+ configured ? [configured] : []
38
+ else
39
+ self.formatters = formatters
40
+ end
41
+ end
42
+
43
+ # `nil`, `false`, and `[]` all opt out of formatting entirely. `false` is
44
+ # normalized first, since `Array(false)` would otherwise smuggle it in as a
45
+ # "formatter" that can only fail.
46
+ def formatters=(formatters)
47
+ @formatter = combined_formatter(Array(formatters || nil))
48
+ end
49
+
50
+ def color(value = :__no_arg__)
51
+ return instance_variable_defined?(:@color) ? @color : :auto if value.eql?(:__no_arg__)
52
+
53
+ self.color = _ = value
54
+ end
55
+
56
+ def color=(value)
57
+ @color = value
58
+ end
59
+
60
+ def print_errors(value = :__no_arg__)
61
+ return instance_variable_defined?(:@print_error_status) ? @print_error_status : true if value.eql?(:__no_arg__)
62
+
63
+ self.print_errors = _ = value
64
+ end
65
+
66
+ def print_errors=(value)
67
+ @print_error_status = value
68
+ end
69
+
70
+ def source_in_json(value = :__no_arg__)
71
+ return instance_variable_defined?(:@source_in_json) ? @source_in_json : true if value.eql?(:__no_arg__)
72
+
73
+ self.source_in_json = _ = value
74
+ end
75
+
76
+ def source_in_json=(value)
77
+ @source_in_json = value
78
+ end
79
+
80
+ def print_error_status
81
+ Deprecation.warn("`SimpleCov.print_error_status` is deprecated. " \
82
+ "Replace with `SimpleCov.print_errors` (same value).")
83
+ instance_variable_defined?(:@print_error_status) ? @print_error_status : true
84
+ end
85
+
86
+ def nocov_token(nocov_token = nil)
87
+ Deprecation.warn("`SimpleCov.nocov_token` and `SimpleCov.skip_token` are deprecated. " \
88
+ "Replace with `# simplecov:disable` / `# simplecov:enable` block comments.")
89
+ current_nocov_token(nocov_token)
90
+ end
91
+ alias_method :skip_token, :nocov_token
92
+
93
+ def current_nocov_token(value = nil)
94
+ return @nocov_token if instance_variable_defined?(:@nocov_token) && value.nil?
95
+
96
+ @nocov_token = value || "nocov"
97
+ end
98
+
99
+ private
100
+
101
+ # An empty list is how a project opts out of formatting.
102
+ def combined_formatter(formatters)
103
+ return if formatters.empty?
104
+
105
+ Formatter::MultiFormatter.new(formatters)
106
+ end
107
+
108
+ def resolve_format(name)
109
+ return name unless name.instance_of?(Symbol)
110
+
111
+ constant = BUILT_IN_FORMATS[name]
112
+ unless constant
113
+ raise ConfigurationError,
114
+ "Unknown format #{name.inspect}. Built-in formats are :html, :json, :simple, and :baseline; " \
115
+ "pass a formatter class or instance for anything else."
116
+ end
117
+
118
+ require_html_formatter(name)
119
+ Formatter.const_get(constant)
120
+ end
121
+
122
+ # `formats :html` has to work in a process that never required the HTML
123
+ # formatter, which under SIMPLECOV_NO_DEFAULTS nothing else has.
124
+ def require_html_formatter(name)
125
+ path = LAZY_FORMAT_REQUIRES[name]
126
+ require_relative(path) if path
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleCov
4
+ module Configuration
5
+ def groups
6
+ @groups ||= {}
7
+ end
8
+
9
+ # The group set `SimpleCov.grouped` bins against when its caller names none.
10
+ # It reads through a method of its own because the keyword it defaults
11
+ # shadows `groups`, so the default has to name a receiver, and every
12
+ # spelling of that receiver is the same call.
13
+ #
14
+ # mutant:disable — for that reason: `SimpleCov.groups`, `self.groups` and
15
+ # `groups()` cannot be told apart.
16
+ def default_groups
17
+ groups
18
+ end
19
+
20
+ def groups=(new_groups)
21
+ new_groups = new_groups.to_h { |name, filter| [GroupNames.normalize(name), filter] }
22
+ GroupNames.validate!(new_groups.keys)
23
+ @groups = new_groups
24
+ end
25
+
26
+ # Same matcher grammar as `skip`, but instead of dropping the matching files
27
+ # it bins them under `group_name` for the formatter. Files matched by no
28
+ # group fall into the implicit "Ungrouped" bucket.
29
+ def group(group_name, filter_argument = nil, &)
30
+ group_name = GroupNames.normalize(group_name)
31
+ GroupNames.validate!([group_name])
32
+ groups[group_name] = parse_filter(filter_argument, &)
33
+ end
34
+
35
+ def add_group(group_name, filter_argument = nil, &block)
36
+ example = if block
37
+ "`SimpleCov.group #{group_name.inspect} { ... }`"
38
+ else
39
+ "`SimpleCov.group #{group_name.inspect}, #{filter_argument.inspect}`"
40
+ end
41
+ Deprecation.warn(
42
+ "`SimpleCov.add_group` is deprecated. " \
43
+ "Replace with `SimpleCov.group` (same arguments, same behavior). Example: #{example}."
44
+ )
45
+ group(group_name, filter_argument, &block)
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleCov
4
+ module Configuration
5
+ DROP_BASELINES = %i[last_run median branch].freeze
6
+
7
+ # How many runs coverage/.history.json keeps, newest kept. 0 disables
8
+ # recording entirely, and anything but a count of runs is refused.
9
+ def history_limit(limit = nil)
10
+ return @history_limit ||= 100 if limit.nil?
11
+
12
+ self.history_limit = limit
13
+ _ = @history_limit
14
+ end
15
+
16
+ def history_limit=(limit)
17
+ unless limit.instance_of?(Integer) && limit >= 0
18
+ raise ConfigurationError, "history_limit takes a non-negative integer, got #{limit.inspect}"
19
+ end
20
+
21
+ @history_limit = limit
22
+ end
23
+
24
+ # What `maximum_coverage_drop` measures the drop against: `:last_run` (the
25
+ # default) the previous run whatever it was, `:median` the median of the
26
+ # recorded history so one run that dipped for an unrelated reason cannot
27
+ # quietly become the baseline, and `:branch` the newest recorded run on the
28
+ # current git branch so a feature branch is compared with itself.
29
+ #
30
+ # The latter two read coverage/.history.json and find nothing to compare
31
+ # against while it is empty, which counts as "no previous run" the way a
32
+ # missing .last_run.json does.
33
+ def drop_baseline(mode = nil)
34
+ return @drop_baseline ||= :last_run if mode.nil?
35
+
36
+ unless DROP_BASELINES.include?(mode)
37
+ raise ConfigurationError,
38
+ "drop_baseline takes one of #{DROP_BASELINES}, got #{mode.inspect}"
39
+ end
40
+
41
+ @drop_baseline = mode
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleCov
4
+ module Configuration
5
+ IGNORABLE_BRANCH_TYPES = %i[implicit_else eval_generated].freeze
6
+ IGNORABLE_METHOD_TYPES = %i[eval_generated].freeze
7
+
8
+ # Deprecated in favour of `coverage(:branch) { ignore :implicit_else }`,
9
+ # which fixes the criterion by context the way the threshold verbs do. One
10
+ # difference rides out the deprecation period: the coverage block enables the
11
+ # criterion it names, while this legacy setter records without enabling.
12
+ def ignore_branches(*types)
13
+ Deprecation.warn("`SimpleCov.ignore_branches` is deprecated. " \
14
+ "Replace with `coverage(:branch) { ignore #{types.map(&:inspect).join(", ")} }`.")
15
+ store_ignored_branches(types)
16
+ end
17
+
18
+ def ignored_branches
19
+ @ignored_branches ||= []
20
+ end
21
+
22
+ def ignored_branch?(type)
23
+ ignored_branches.include?(type)
24
+ end
25
+
26
+ def ignore_methods(*types)
27
+ Deprecation.warn("`SimpleCov.ignore_methods` is deprecated. " \
28
+ "Replace with `coverage(:method) { ignore #{types.map(&:inspect).join(", ")} }`.")
29
+ store_ignored_methods(types)
30
+ end
31
+
32
+ def ignored_methods
33
+ @ignored_methods ||= []
34
+ end
35
+
36
+ def ignored_method?(type)
37
+ ignored_methods.include?(type)
38
+ end
39
+
40
+ private
41
+
42
+ # Variadic semantics: multiple calls union, duplicates are no-ops, unknown
43
+ # tokens raise.
44
+ def store_ignored_branches(types)
45
+ types.each { |type| raise_if_branch_type_unsupported(type) }
46
+ ignored_branches.concat(types).uniq!
47
+ ignored_branches
48
+ end
49
+
50
+ def store_ignored_methods(types)
51
+ types.each { |type| raise_if_method_type_unsupported(type) }
52
+ ignored_methods.concat(types).uniq!
53
+ ignored_methods
54
+ end
55
+
56
+ def raise_if_branch_type_unsupported(type)
57
+ return if IGNORABLE_BRANCH_TYPES.member?(type)
58
+
59
+ raise ConfigurationError,
60
+ "Unsupported branch type #{type.inspect} for `ignore_branches`. " \
61
+ "Supported values are #{IGNORABLE_BRANCH_TYPES.inspect}"
62
+ end
63
+
64
+ def raise_if_method_type_unsupported(type)
65
+ return if IGNORABLE_METHOD_TYPES.member?(type)
66
+
67
+ raise ConfigurationError,
68
+ "Unsupported method type #{type.inspect} for `ignore_methods`. " \
69
+ "Supported values are #{IGNORABLE_METHOD_TYPES.inspect}"
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,197 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleCov
4
+ module Configuration
5
+ #
6
+ # Whether SimpleCov should hook `Process._fork` to attach itself to
7
+ # subprocesses. Required when the suite uses parallel test workers (Rails'
8
+ # `parallelize(workers:)`). Defaults to false.
9
+ #
10
+ def merge_subprocesses(value = nil)
11
+ return @enable_for_subprocesses if instance_variable_defined?(:@enable_for_subprocesses) && value.nil?
12
+
13
+ self.merge_subprocesses = value
14
+ @enable_for_subprocesses
15
+ end
16
+
17
+ # false stands in for nothing, so the answer is always a real opt-in or
18
+ # opt-out.
19
+ def merge_subprocesses=(value)
20
+ @enable_for_subprocesses = value || false
21
+ end
22
+
23
+ # @api private
24
+ def enabled_for_subprocesses?
25
+ !!@enable_for_subprocesses
26
+ end
27
+
28
+ #
29
+ # Whether SimpleCov should auto-require the `parallel_tests` gem when it
30
+ # sees `TEST_ENV_NUMBER` / `PARALLEL_TEST_GROUPS` in the environment.
31
+ # Defaults to auto-detect (nil). See #1018.
32
+ #
33
+ def parallel_tests(value = :__no_arg__)
34
+ return @parallel_tests if value.eql?(:__no_arg__)
35
+
36
+ self.parallel_tests = _ = value
37
+ end
38
+
39
+ def parallel_tests=(value)
40
+ @parallel_tests = value
41
+ end
42
+
43
+ def enable_for_subprocesses(value = nil)
44
+ Deprecation.warn("`SimpleCov.enable_for_subprocesses` is deprecated. " \
45
+ "Replace with `SimpleCov.merge_subprocesses` (same value, same behavior).")
46
+ merge_subprocesses(value)
47
+ end
48
+
49
+ def merging(use = nil)
50
+ self.merging = use unless use.nil?
51
+ # Unset reads as nil, and only an explicit `merging false` turns it off.
52
+ @use_merging = true if @use_merging.nil?
53
+ @use_merging
54
+ end
55
+
56
+ def merging=(use)
57
+ @use_merging = use
58
+ end
59
+
60
+ #
61
+ # Whether SimpleCov's selected final process owns merge processing: waiting
62
+ # for sibling workers, building the merged result, formatting, enforcing
63
+ # thresholds, and writing `.last_run.json`.
64
+ #
65
+ # Defaults to true, except for recognized multi-worker parallel runs that
66
+ # explicitly write to a custom coverage destination while merging is
67
+ # enabled. Those runs are likely using an external `SimpleCov.collate` step
68
+ # to finalize the merge.
69
+ #
70
+ # Splatted rather than defaulted: false is a value someone sets on purpose,
71
+ # so "given nothing" cannot be told from it by looking at the argument.
72
+ def finalize_merge(*value)
73
+ unless value.empty?
74
+ explicit, = value
75
+ self.finalize_merge = _ = explicit
76
+ end
77
+
78
+ return @finalize_merge if @finalize_merge_explicit
79
+
80
+ inferred = inferred_finalize_merge?
81
+ warn_about_inferred_finalize_merge unless inferred
82
+ inferred
83
+ end
84
+
85
+ # Writing is what makes the answer explicit rather than inferred.
86
+ def finalize_merge=(value)
87
+ @finalize_merge = value
88
+ @finalize_merge_explicit = true
89
+ end
90
+
91
+ def finalize_merge?
92
+ finalize_merge
93
+ end
94
+
95
+ # @api private
96
+ def merge_finalization_owner?
97
+ collating_result? || (finalize_merge? && final_result_process?)
98
+ end
99
+
100
+ def use_merging(use = nil)
101
+ Deprecation.warn("`SimpleCov.use_merging` is deprecated. " \
102
+ "Replace with `SimpleCov.merging` (same value, same behavior).")
103
+ merging(use)
104
+ end
105
+
106
+ #
107
+ # The maximum age (in seconds) of a resultset to still be included in
108
+ # merged results. Default is 600 seconds.
109
+ #
110
+ # A numeric SIMPLECOV_MERGE_TIMEOUT in the environment takes precedence
111
+ # over the configured value: `simplecov watch` re-runs test subsets across
112
+ # a long session and extends its child runs' merge window this way, where
113
+ # editing every watched project's configuration is not an option.
114
+ #
115
+ def merge_timeout(seconds = nil)
116
+ self.merge_timeout = seconds
117
+ # Memoized through a local rather than `|| (@merge_timeout ||= 600)`:
118
+ # steep 2.0's logic-type interpreter crashes on an or-assignment nested in
119
+ # a logical operand, and ivar narrowing doesn't carry across statements
120
+ # the way a local's does.
121
+ configured = @merge_timeout || 600
122
+ @merge_timeout = configured
123
+ env_merge_timeout || configured
124
+ end
125
+
126
+ # Anything but an Integer is ignored, the way the dual method always did.
127
+ def merge_timeout=(seconds)
128
+ @merge_timeout = seconds if seconds.instance_of?(Integer)
129
+ end
130
+
131
+ def env_merge_timeout
132
+ value = ENV.fetch("SIMPLECOV_MERGE_TIMEOUT", nil)
133
+ Integer(value, 10) if value&.match?(/\A\d+\z/)
134
+ end
135
+
136
+ #
137
+ # How long (in seconds) the reporting process waits for the remaining
138
+ # parallel-test workers to write their resultsets before it proceeds with a
139
+ # partial merge. Default is 60 seconds. Raise it when a slow worker
140
+ # routinely finishes well after the others, so its coverage is still
141
+ # included and the threshold checks aren't skipped against a partial total.
142
+ #
143
+ def parallel_wait_timeout(seconds = nil)
144
+ self.parallel_wait_timeout = seconds
145
+ @parallel_wait_timeout ||= 60
146
+ end
147
+
148
+ # Anything but an Integer is ignored, the way the dual method always did.
149
+ def parallel_wait_timeout=(seconds)
150
+ @parallel_wait_timeout = seconds if seconds.instance_of?(Integer)
151
+ end
152
+
153
+ private
154
+
155
+ def inferred_finalize_merge?
156
+ return true unless merging
157
+
158
+ adapter = ParallelAdapters.current
159
+ return true unless adapter
160
+ return true unless adapter.expected_worker_count > 1
161
+ return true unless parallel_worker_environment?
162
+ return true unless explicit_custom_coverage_destination?
163
+
164
+ false
165
+ end
166
+
167
+ def parallel_worker_environment?
168
+ ENV.key?("TEST_ENV_NUMBER") || ENV.key?("PARALLEL_TEST_GROUPS")
169
+ end
170
+
171
+ def explicit_custom_coverage_destination?
172
+ return false unless explicit_coverage_destination?
173
+
174
+ !coverage_path.eql?(File.expand_path("coverage", root))
175
+ end
176
+
177
+ def explicit_coverage_destination?
178
+ @coverage_path_explicit || @coverage_dir_explicit
179
+ end
180
+
181
+ def warn_about_inferred_finalize_merge
182
+ return if @finalize_merge_inference_warned
183
+ return unless print_errors
184
+
185
+ @finalize_merge_inference_warned = true
186
+ warn Color.colorize(inferred_finalize_merge_warning, :yellow)
187
+ end
188
+
189
+ def inferred_finalize_merge_warning
190
+ "SimpleCov inferred `finalize_merge false` because this parallel worker is merging " \
191
+ "into a custom coverage destination. Set `SimpleCov.finalize_merge false` to keep " \
192
+ "external collation ownership, or `SimpleCov.finalize_merge true` if this worker " \
193
+ "should wait, merge, format, enforce thresholds, and write `.last_run.json`. " \
194
+ "See https://github.com/simplecov-ruby/simplecov#merge-finalization-ownership."
195
+ end
196
+ end
197
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleCov
4
+ module Configuration
5
+ # Caps the total number of misses per criterion. Where a percent minimum
6
+ # asks for a ratio, this asks for an absolute burn-down number, which stays
7
+ # meaningful as the codebase grows and shrinks.
8
+ def maximum_missed(counts = nil)
9
+ return @maximum_missed ||= {} unless counts
10
+
11
+ @maximum_missed = normalized_missed_caps(counts, "maximum_missed")
12
+ end
13
+
14
+ # Unlike a percent minimum, which systematically flatters big files, this
15
+ # holds every file to the same absolute budget. Files with a baseline entry
16
+ # are exempt per covered criterion, as they are from the per-file minimum.
17
+ def maximum_missed_per_file(counts = nil)
18
+ return @maximum_missed_per_file ||= {} unless counts
19
+
20
+ Deprecation.warn("`SimpleCov.maximum_missed_per_file` is deprecated. " \
21
+ "Replace it with:\n#{missed_per_file_replacement(counts)}")
22
+ @maximum_missed_per_file = normalized_missed_caps(counts, "maximum_missed_per_file")
23
+ end
24
+
25
+ def maximum_missed_per_file_overrides
26
+ @maximum_missed_per_file_overrides ||= {}
27
+ end
28
+
29
+ private
30
+
31
+ def normalized_missed_caps(counts, setting)
32
+ counts = {primary_coverage => counts} if counts.is_a?(Numeric)
33
+ counts.each_key { |criterion| raise_if_criterion_disabled(criterion) }
34
+ counts.each_value { |cap| raise_on_invalid_missed_cap(cap, setting) }
35
+ _ = counts
36
+ end
37
+
38
+ def raise_on_invalid_missed_cap(cap, setting)
39
+ return if cap.instance_of?(Integer) && cap >= 0
40
+
41
+ raise ConfigurationError,
42
+ "#{setting} takes a non-negative integer count of misses, got #{cap.inspect}"
43
+ end
44
+
45
+ def missed_per_file_replacement(counts)
46
+ counts = {primary_coverage => counts} if counts.is_a?(Numeric)
47
+ counts.map { |criterion, cap| " coverage(#{criterion.inspect}) { maximum_missed #{cap}, per: :file }" }
48
+ .join("\n")
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleCov
4
+ module Configuration
5
+ # The path to a production coverage store, the file a `SimpleCov::Production`
6
+ # sink wrote. Relative paths resolve against `SimpleCov.root`. The store is
7
+ # read at report time, and an unreadable or invalid file warns and leaves the
8
+ # section out, because a missing night's data should not fail the suite that
9
+ # measured the tests.
10
+ def production_coverage(path = nil)
11
+ return @production_coverage if path.nil?
12
+
13
+ self.production_coverage = path
14
+ @production_coverage
15
+ end
16
+
17
+ def production_coverage=(path)
18
+ raise ConfigurationError, "production_coverage takes a path, got #{path.inspect}" unless path.is_a?(String)
19
+
20
+ @production_coverage = File.expand_path(path, root)
21
+ end
22
+ end
23
+ end