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,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Every coverage criterion enabled and held to 100%. An opt-in profile rather
4
+ # than the default (#1061), so a team that has paid down the debt can flip the
5
+ # strict switch without re-typing the threshold trio every time.
6
+ #
7
+ # JRuby gracefully degrades: `enable_coverage :branch` / `:method` are accepted
8
+ # but the runtime cannot measure them, and `CoverageViolations` skips threshold
9
+ # lookups for criteria not in the stats, so only `:line` is enforced there.
10
+ #
11
+ # `:eval` is guarded on `coverage_for_eval_supported?` so the profile stays
12
+ # quiet on engines without it, where enabling it would warn every time it loads.
13
+
14
+ SimpleCov.profiles.define "strict" do
15
+ enable_coverage :branch
16
+ enable_coverage :method
17
+ # simplecov:disable branch — dogfood branch coverage runs on CRuby only,
18
+ # so the else arm (eval coverage not supported) is unreachable from CI.
19
+ enable_coverage :eval if coverage_for_eval_supported?
20
+ # simplecov:enable
21
+ minimum_coverage line: 100, branch: 100, method: 100
22
+ end
@@ -1,8 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  SimpleCov.profiles.define "test_frameworks" do
4
- add_filter "/test/"
5
- add_filter "/features/"
6
- add_filter "/spec/"
7
- add_filter "/autotest/"
4
+ skip %r{\A(test|features|spec|autotest)/}
8
5
  end
@@ -1,35 +1,42 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SimpleCov
4
- #
5
- # Profiles are SimpleCov configuration procs that can be easily
6
- # loaded using SimpleCov.start :rails and defined using
7
- # SimpleCov.profiles.define :foo do
8
- # # SimpleCov configuration here, same as in SimpleCov.configure
9
- # end
10
- #
4
+ # Configuration procs loaded with `SimpleCov.start :rails` and defined with
5
+ # `SimpleCov.profiles.define`.
11
6
  class Profiles < Hash
12
- #
13
- # Define a SimpleCov profile:
14
- # SimpleCov.profiles.define 'rails' do
15
- # # Same as SimpleCov.configure do .. here
16
- # end
17
- #
18
7
  def define(name, &blk)
19
8
  name = name.to_sym
20
- raise "SimpleCov Profile '#{name}' is already defined" unless self[name].nil?
9
+ raise ConfigurationError, "SimpleCov Profile '#{name}' is already defined" unless self[name].nil?
21
10
 
22
11
  self[name] = blk
23
12
  end
24
13
 
25
- #
26
- # Applies the profile of given name on SimpleCov.configure
27
- #
28
14
  def load(name)
15
+ SimpleCov.configure(&fetch_proc(name))
16
+ end
17
+
18
+ # Lookup order: already registered via #define, then
19
+ # `require "simplecov/profiles/<name>"` for the bundled profiles, then
20
+ # `require "simplecov-profile-<name>"` for third-party plugin gems.
21
+ def fetch_proc(name)
29
22
  name = name.to_sym
30
- raise "Could not find SimpleCov Profile called '#{name}'" unless key?(name)
23
+ autoload_profile(name) unless key?(name)
24
+ return fetch(name) if key?(name)
25
+
26
+ raise ConfigurationError, "Could not find SimpleCov Profile called '#{name}'"
27
+ end
28
+
29
+ private
31
30
 
32
- SimpleCov.configure(&self[name])
31
+ def autoload_profile(name)
32
+ require "simplecov/profiles/#{name}"
33
+ rescue LoadError
34
+ begin
35
+ require "simplecov-profile-#{name}"
36
+ rescue LoadError
37
+ # Neither a bundled profile nor a plugin gem; `fetch_proc` raises the
38
+ # user-facing error.
39
+ end
33
40
  end
34
41
  end
35
42
  end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The clobber-prevention backstop: an empty process stands down instead of
4
+ # formatting over a fresher report a sibling produced. Typically fires when
5
+ # `SimpleCov.start` ran in a parent process that shelled out to the test runner
6
+ # (#581).
7
+
8
+ module SimpleCov
9
+ class << self
10
+ def defer_to_existing_report?
11
+ return false unless existing_report_newer_than_us?
12
+
13
+ res = result
14
+ empty = res.nil? || res.files.empty?
15
+ warn_about_deferred_report if empty
16
+ empty
17
+ end
18
+
19
+ # `.last_run.json` only exists after fully successful runs, so alone it left
20
+ # the backstop inert when the child run failed, the case where clobbering its
21
+ # report hurts most. The report stamp is touched by every formatting process
22
+ # regardless of exit status. The rescue covers a file vanishing mid-at_exit.
23
+ def existing_report_newer_than_us?
24
+ return false unless process_start_time
25
+
26
+ [LastRun.last_run_path, ReportStamp.path].any? do |path|
27
+ File.mtime(path) > process_start_time
28
+ rescue SystemCallError
29
+ false
30
+ end
31
+ end
32
+
33
+ def warn_about_deferred_report
34
+ return unless print_errors
35
+
36
+ ExitCodes.print_error Color.colorize(
37
+ "Skipping SimpleCov report — this process tracked no application code and a newer " \
38
+ "report already exists at #{coverage_path}. This usually means SimpleCov.start ran in a " \
39
+ "parent process (e.g. a Rakefile or Rails' Bundler.require) that shelled out to the test " \
40
+ "runner. See https://github.com/simplecov-ruby/simplecov/issues/581.",
41
+ :yellow
42
+ )
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+
5
+ module SimpleCov
6
+ # Touches coverage/.report_stamp whenever a report is formatted, no matter how
7
+ # the run ends. The clobber-prevention backstop needs an on-disk signal that a
8
+ # fresher report exists, and `.last_run.json` can't be it alone: that file is
9
+ # only written after fully successful runs.
10
+ module ReportStamp
11
+ class << self
12
+ def path
13
+ File.join(SimpleCov.coverage_path, ".report_stamp")
14
+ end
15
+
16
+ # A read-only or vanished coverage dir must not crash reporting; the stamp
17
+ # only powers the deferral heuristic.
18
+ def touch
19
+ FileUtils.touch(path)
20
+ rescue SystemCallError
21
+ nil
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleCov
4
+ class Result
5
+ # The filter and grouping configuration a Result applies to its source files
6
+ # after building them. Each field defaults to the SimpleCov singleton's
7
+ # configuration, so ordinary callers never construct one; tests pass a custom
8
+ # instance to opt out of the project's filters or groups.
9
+ class FilterConfig
10
+ attr_reader :filters, :cover_filters, :groups
11
+
12
+ def initialize(filters: SimpleCov.filters, cover_filters: SimpleCov.cover_filters, groups: SimpleCov.groups)
13
+ @filters = filters
14
+ @cover_filters = cover_filters
15
+ @groups = groups
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleCov
4
+ class Result
5
+ # When a resultset references source files that don't exist on the local
6
+ # filesystem they're silently dropped, which produces an empty
7
+ # `0 / 0 (100.00%)` report that looks like success but isn't. This emits a
8
+ # single warning summarizing the drop and, when every entry was lost, points
9
+ # at the typical cause: `SimpleCov.collate` invoked from a machine or path
10
+ # different from where the resultsets were generated (#980).
11
+ class MissingSourceFilesReporter
12
+ def initialize(missing_paths, every_entry_dropped:)
13
+ @missing_paths = missing_paths
14
+ @every_entry_dropped = every_entry_dropped
15
+ end
16
+
17
+ def warn!
18
+ warn Color.colorize(message, :yellow)
19
+ end
20
+
21
+ def message
22
+ @every_entry_dropped ? all_missing_warning : partial_missing_warning
23
+ end
24
+
25
+ private
26
+
27
+ def all_missing_warning
28
+ "SimpleCov dropped all #{@missing_paths.size} source file(s) from the result — " \
29
+ "none of the paths in the resultset exist on this filesystem: " \
30
+ "#{summary}. If you're running `SimpleCov.collate`, the source " \
31
+ "files must be available at the same absolute paths as when the individual resultsets " \
32
+ "were generated."
33
+ end
34
+
35
+ def partial_missing_warning
36
+ "SimpleCov dropped #{@missing_paths.size} source file(s) from the result because " \
37
+ "they don't exist on this filesystem: #{summary}. They were " \
38
+ "tracked in the resultset but have since moved or been removed."
39
+ end
40
+
41
+ def summary
42
+ sample = @missing_paths.first(5).join(", ")
43
+ remaining = @missing_paths.size - 5
44
+ remaining.positive? ? "#{sample} (+#{remaining} more)" : sample
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleCov
4
+ class Result
5
+ module Serialization
6
+ def to_hash
7
+ data = {"coverage" => coverage, "timestamp" => created_at.to_f} #: Hash[String, untyped]
8
+ data["run_id"] = run_id if run_id
9
+ data["worker_id"] = worker_id if worker_id
10
+ data["tracked_files"] = tracked_files unless tracked_files.empty?
11
+ append_contexts(data)
12
+ {command_name => data}
13
+ end
14
+
15
+ private
16
+
17
+ # Serialized whenever a map was recorded, even an empty one: for the merge,
18
+ # the key's presence is the signal that this result tracked tests, which is
19
+ # what lets it tell "tracked and covered nothing" from "never tracked". The
20
+ # map's files are restricted to this result's post-filter universe.
21
+ def append_contexts(data)
22
+ map = contexts
23
+ return unless map
24
+
25
+ data["contexts"] = map.to_h(only: context_filenames)
26
+ end
27
+
28
+ def context_filenames
29
+ Set.new(filenames)
30
+ end
31
+
32
+ # A live result's criterion keys are Symbols while entries parsed back from
33
+ # `.resultset.json` carry Strings, and the combiners read only String keys.
34
+ # Serializing with String keys is what lets a live result merged against a
35
+ # stored entry contribute its counts instead of being dropped.
36
+ def coverage
37
+ original_result.slice(*filenames).transform_values do |file_coverage|
38
+ file_coverage.transform_keys(&:to_s)
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleCov
4
+ class Result
5
+ class SourceFileBuilder
6
+ attr_reader :missing_source_files
7
+
8
+ def initialize(original_result, not_loaded_files:)
9
+ @original_result = original_result
10
+ @not_loaded_files = not_loaded_files
11
+ @missing_source_files = []
12
+ end
13
+
14
+ def call
15
+ FileList.new(
16
+ @original_result
17
+ .filter_map { |filename, coverage| build_source_file(filename, coverage) }
18
+ .sort_by(&:filename)
19
+ )
20
+ end
21
+
22
+ private
23
+
24
+ def build_source_file(filename, coverage)
25
+ unless File.file?(filename)
26
+ @missing_source_files << filename
27
+ return
28
+ end
29
+
30
+ SourceFile.new(
31
+ filename,
32
+ stringify_outer_keys(coverage),
33
+ loaded: !@not_loaded_files.include?(filename)
34
+ )
35
+ end
36
+
37
+ # `Coverage.result` returns symbol keys; resultsets loaded from disk are
38
+ # already string-keyed. SourceFile reads with strings and handles both Array
39
+ # and stringified-Array branch/method keys itself, so only the outer hash
40
+ # needs normalizing.
41
+ def stringify_outer_keys(coverage)
42
+ coverage.transform_keys(&:to_s)
43
+ end
44
+ end
45
+ end
46
+ end
@@ -2,93 +2,169 @@
2
2
 
3
3
  require "digest/sha1"
4
4
  require "forwardable"
5
+ require_relative "result/filter_config"
6
+ require_relative "result/missing_source_files_reporter"
7
+ require_relative "result/serialization"
8
+ require_relative "result/source_file_builder"
5
9
 
6
10
  module SimpleCov
7
11
  #
8
- # A simplecov code coverage result, initialized from the Hash Ruby's built-in coverage
9
- # library generates (Coverage.result).
12
+ # A code coverage result, initialized from the Hash Ruby's built-in coverage
13
+ # library generates.
10
14
  #
11
15
  class Result
12
16
  extend Forwardable
13
- # Returns the original Coverage.result used for this instance of SimpleCov::Result
14
- attr_reader :original_result
15
- # Returns all files that are applicable to this result (sans filters!) as instances of SimpleCov::SourceFile. Aliased as :source_files
16
- attr_reader :files
17
- alias source_files files
18
- # Explicitly set the Time this result has been created
19
- attr_writer :created_at
20
- # Explicitly set the command name that was used for this coverage result. Defaults to SimpleCov.command_name
21
- attr_writer :command_name
22
-
23
- def_delegators :files, :covered_percent, :covered_percentages, :least_covered_file, :covered_strength, :covered_lines, :missed_lines, :total_branches, :covered_branches, :missed_branches, :coverage_statistics, :coverage_statistics_by_file
17
+ include Serialization
18
+
19
+ attr_reader :original_result, :files
20
+ # Every path the producing process was told to track, loaded or not.
21
+ # Carried into the resultset so a merge elsewhere can inject the ones
22
+ # nobody loaded without needing that process's config (#1250).
23
+ attr_reader :tracked_files
24
+ # Used only for parallel-result coordination. They do not change which
25
+ # fresh suites are merged.
26
+ attr_reader :run_id, :worker_id
27
+ alias_method :source_files, :files
28
+ # The `ContextMap` recorded under `track_tests`, or nil when this result
29
+ # carries none: tracking was off, or a merge dropped the map because not
30
+ # every merged result recorded one.
31
+ attr_reader :contexts
32
+
33
+ # The distinct run names behind this result, set by a merge so presentation
34
+ # can say "A and N other runs" instead of reading the joined
35
+ # `command_name` aloud in full. Splitting that string back apart would
36
+ # misread a run name that itself contains a comma.
37
+ attr_writer :created_at, :command_name, :command_names
38
+
39
+ def_delegators :files, :covered_percent, :covered_percentages, :least_covered_file, :covered_strength,
40
+ :covered_lines, :missed_lines,
41
+ :total_branches, :covered_branches, :missed_branches,
42
+ :total_methods, :covered_methods, :missed_methods,
43
+ :coverage_statistics, :coverage_statistics_by_file
24
44
  def_delegator :files, :lines_of_code, :total_lines
25
45
 
26
- # Initialize a new SimpleCov::Result from given Coverage.result (a Hash of filenames each containing an array of
27
- # coverage data)
28
- def initialize(original_result, command_name: nil, created_at: nil)
29
- result = original_result
30
- @original_result = result.freeze
46
+ # `filter_config` defaults to the SimpleCov singleton's filter / group
47
+ # configuration. Pass a custom FilterConfig to opt out, which is useful for
48
+ # tests that build synthetic Results.
49
+ #
50
+ # `tracked_files` accepts any collection that answers `to_a` (the merge
51
+ # passes a Set), and nil for a run that tracked nothing.
52
+ def initialize(original_result, command_name: nil, created_at: nil, not_loaded_files: Set.new,
53
+ tracked_files: nil, run_id: nil, worker_id: nil, contexts: nil, report: false,
54
+ filter_config: FilterConfig.new)
55
+ @original_result = original_result.freeze
31
56
  @command_name = command_name
32
57
  @created_at = created_at
33
- @files = SimpleCov::FileList.new(result.map do |filename, coverage|
34
- SimpleCov::SourceFile.new(filename, JSON.parse(JSON.dump(coverage))) if File.file?(filename)
35
- end.compact.sort_by(&:filename))
36
- filter!
58
+ initialize_resultset_metadata(tracked_files, run_id, worker_id, contexts)
59
+ @groups_config = filter_config.groups
60
+ builder = SourceFileBuilder.new(original_result, not_loaded_files: not_loaded_files)
61
+ @files = builder.call
62
+ warn_about_missing_source_files(builder.missing_source_files) if report
63
+ apply_cover_filters!(filter_config.cover_filters)
64
+ apply_filters!(filter_config.filters)
37
65
  end
38
66
 
39
- # Returns all filenames for source files contained in this result
40
67
  def filenames
41
68
  files.map(&:filename)
42
69
  end
43
70
 
44
- # Returns a Hash of groups for this result. Define groups using SimpleCov.add_group 'Models', 'app/models'
71
+ # The path is resolved against SimpleCov.root, so callers can pass either
72
+ # an absolute path or a project-relative one.
73
+ def source_file_for(path)
74
+ target = File.expand_path(path, SimpleCov.root)
75
+ files.find { |file| file.filename.eql?(target) }
76
+ end
77
+
78
+ # Path resolution as in `source_file_for`.
79
+ def coverage_for(path)
80
+ source_file_for(path)&.coverage_statistics
81
+ end
82
+
45
83
  def groups
46
- @groups ||= SimpleCov.grouped(files)
84
+ @groups ||= SimpleCov.grouped(files, groups: @groups_config)
85
+ end
86
+
87
+ # True for a group the configuration defines, whether or not any file
88
+ # matched it; `groups` omits the ones that matched nothing.
89
+ def configured_group?(group_name)
90
+ @groups_config.key?(group_name)
47
91
  end
48
92
 
49
- # Applies the configured SimpleCov.formatter on this result
93
+ # Returns nil if formatting has been opted out of (`SimpleCov.formatter
94
+ # false` / `SimpleCov.formatters []`), the cheap path for non-final
95
+ # processes in a parallel CI run, which only need their `.resultset.json`
96
+ # on disk (#964).
50
97
  def format!
51
- SimpleCov.formatter.new.format(self)
98
+ formatter = SimpleCov.formatter
99
+ return nil if formatter.nil?
100
+
101
+ formatted = Formatter.format(formatter, self)
102
+ # Recorded regardless of how the run ends, so a parent process's
103
+ # clobber-prevention backstop can tell a report was produced even when
104
+ # this run's checks or tests failed, unlike .last_run.json.
105
+ ReportStamp.touch
106
+ formatted
52
107
  end
53
108
 
54
- # Defines when this result has been created. Defaults to Time.now
55
109
  def created_at
56
110
  @created_at ||= Time.now
57
111
  end
58
112
 
59
- # The command name that launched this result.
60
- # Delegated to SimpleCov.command_name if not set manually
61
113
  def command_name
62
114
  @command_name ||= SimpleCov.command_name
63
115
  end
64
116
 
65
- # Returns a hash representation of this Result that can be used for marshalling it into JSON
66
- def to_hash
67
- {
68
- command_name => {
69
- "coverage" => coverage,
70
- "timestamp" => created_at.to_i
71
- }
72
- }
117
+ def command_names
118
+ @command_names ||= [command_name]
73
119
  end
74
120
 
75
- # Loads a SimpleCov::Result#to_hash dump
76
121
  def self.from_hash(hash)
77
122
  hash.map do |command_name, data|
78
- new(data.fetch("coverage"), command_name: command_name, created_at: Time.at(data["timestamp"]))
123
+ new(data.fetch("coverage"), command_name: command_name, created_at: Time.at(data.fetch("timestamp")),
124
+ tracked_files: data["tracked_files"], run_id: data["run_id"],
125
+ worker_id: data["worker_id"], contexts: ContextMap.from_hash(data["contexts"]))
79
126
  end
80
127
  end
81
128
 
82
- private
129
+ private
130
+
131
+ def initialize_resultset_metadata(tracked_files, run_id, worker_id, contexts)
132
+ @tracked_files = tracked_files.to_a
133
+ @run_id = run_id
134
+ @worker_id = worker_id
135
+ @contexts = contexts
136
+ end
137
+
138
+ def warn_about_missing_source_files(missing)
139
+ return if missing.empty?
140
+
141
+ # Emit only from the process that writes the final report: the merged
142
+ # result is rebuilt in every parallel worker, so without this gate the
143
+ # warning prints once per worker. Intentionally not gated on
144
+ # print_errors, because the default at_fork sets print_errors false on
145
+ # workers and in many parallel runners the final-report process is itself
146
+ # a worker (#980, #1171).
147
+ return unless SimpleCov.final_result_process?
83
148
 
84
- def coverage
85
- keys = original_result.keys & filenames
86
- Hash[keys.zip(original_result.values_at(*keys))]
149
+ # Every built file survives (only the missing ones are dropped), so an
150
+ # empty file list is exactly the "nothing was found" case.
151
+ MissingSourceFilesReporter.new(missing, every_entry_dropped: @files.empty?).warn!
87
152
  end
88
153
 
89
- # Applies all configured SimpleCov filters on this result's source files
90
- def filter!
91
- @files = SimpleCov.filtered(files)
154
+ def apply_filters!(filters)
155
+ filters.each do |filter|
156
+ @files = FileList.new(@files.reject { |source_file| filter.matches?(source_file) })
157
+ end
158
+ end
159
+
160
+ # With no cover matchers configured this is a no-op, preserving the
161
+ # historical "everything required, then filtered" universe.
162
+ def apply_cover_filters!(cover_filters)
163
+ return if cover_filters.empty?
164
+
165
+ @files = FileList.new(
166
+ @files.select { |source_file| cover_filters.any? { |filter| filter.matches?(source_file) } }
167
+ )
92
168
  end
93
169
  end
94
170
  end