simplecov 1.1.1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (194) 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/badge/svg.rb +70 -0
  13. data/lib/simplecov/cli/badge.rb +62 -0
  14. data/lib/simplecov/cli/clean.rb +23 -24
  15. data/lib/simplecov/cli/command_helpers.rb +55 -19
  16. data/lib/simplecov/cli/completions/scripts.rb +114 -0
  17. data/lib/simplecov/cli/completions.rb +83 -0
  18. data/lib/simplecov/cli/coverage.rb +25 -28
  19. data/lib/simplecov/cli/coverage_file.rb +58 -14
  20. data/lib/simplecov/cli/dead_code/output.rb +100 -0
  21. data/lib/simplecov/cli/dead_code.rb +139 -0
  22. data/lib/simplecov/cli/diff/output.rb +45 -0
  23. data/lib/simplecov/cli/diff.rb +35 -67
  24. data/lib/simplecov/cli/dotfile.rb +83 -38
  25. data/lib/simplecov/cli/git.rb +41 -0
  26. data/lib/simplecov/cli/history/output.rb +135 -0
  27. data/lib/simplecov/cli/history.rb +67 -0
  28. data/lib/simplecov/cli/merge.rb +34 -39
  29. data/lib/simplecov/cli/open.rb +12 -15
  30. data/lib/simplecov/cli/patch/changed_lines.rb +149 -0
  31. data/lib/simplecov/cli/patch/output.rb +135 -0
  32. data/lib/simplecov/cli/patch.rb +169 -0
  33. data/lib/simplecov/cli/ratchet/output.rb +44 -0
  34. data/lib/simplecov/cli/ratchet.rb +83 -0
  35. data/lib/simplecov/cli/report.rb +18 -24
  36. data/lib/simplecov/cli/run.rb +8 -9
  37. data/lib/simplecov/cli/serve/report_preparer.rb +7 -6
  38. data/lib/simplecov/cli/serve/static_file_handler.rb +62 -56
  39. data/lib/simplecov/cli/serve.rb +27 -24
  40. data/lib/simplecov/cli/show/annotator.rb +87 -0
  41. data/lib/simplecov/cli/show/sweep.rb +36 -0
  42. data/lib/simplecov/cli/show.rb +133 -0
  43. data/lib/simplecov/cli/status/facts.rb +87 -0
  44. data/lib/simplecov/cli/status.rb +93 -0
  45. data/lib/simplecov/cli/tests/redundancy.rb +78 -0
  46. data/lib/simplecov/cli/tests.rb +148 -0
  47. data/lib/simplecov/cli/uncovered/misses.rb +37 -0
  48. data/lib/simplecov/cli/uncovered.rb +70 -32
  49. data/lib/simplecov/cli/usage.rb +152 -0
  50. data/lib/simplecov/cli/watch/live_report.rb +73 -0
  51. data/lib/simplecov/cli/watch/narrator.rb +55 -0
  52. data/lib/simplecov/cli/watch/poller.rb +45 -0
  53. data/lib/simplecov/cli/watch/session.rb +134 -0
  54. data/lib/simplecov/cli/watch/test_plan.rb +43 -0
  55. data/lib/simplecov/cli/watch.rb +85 -0
  56. data/lib/simplecov/cli.rb +43 -92
  57. data/lib/simplecov/color.rb +19 -37
  58. data/lib/simplecov/combine/branches_combiner.rb +7 -31
  59. data/lib/simplecov/combine/coverage_accumulator.rb +49 -142
  60. data/lib/simplecov/combine/identity_interner.rb +10 -13
  61. data/lib/simplecov/combine/interned_counts.rb +7 -9
  62. data/lib/simplecov/combine/lines_combiner.rb +20 -44
  63. data/lib/simplecov/combine/methods_combiner.rb +9 -35
  64. data/lib/simplecov/combine/results_combiner.rb +4 -21
  65. data/lib/simplecov/combine.rb +0 -6
  66. data/lib/simplecov/command_guesser.rb +20 -34
  67. data/lib/simplecov/configuration/baseline.rb +31 -0
  68. data/lib/simplecov/configuration/coverage.rb +91 -79
  69. data/lib/simplecov/configuration/coverage_criteria.rb +43 -54
  70. data/lib/simplecov/configuration/deprecations.rb +21 -0
  71. data/lib/simplecov/configuration/eval_coverage.rb +6 -10
  72. data/lib/simplecov/configuration/filters.rb +40 -73
  73. data/lib/simplecov/configuration/formatting.rb +74 -83
  74. data/lib/simplecov/configuration/groups.rb +19 -13
  75. data/lib/simplecov/configuration/history.rb +44 -0
  76. data/lib/simplecov/configuration/ignored_entries.rb +31 -22
  77. data/lib/simplecov/configuration/merging.rb +94 -62
  78. data/lib/simplecov/configuration/missed_caps.rb +51 -0
  79. data/lib/simplecov/configuration/production.rb +23 -0
  80. data/lib/simplecov/configuration/test_tracking.rb +48 -0
  81. data/lib/simplecov/configuration/thresholds.rb +33 -62
  82. data/lib/simplecov/configuration/view_coverage.rb +38 -0
  83. data/lib/simplecov/configuration.rb +70 -76
  84. data/lib/simplecov/configuration_error.rb +5 -0
  85. data/lib/simplecov/context_map/union.rb +81 -0
  86. data/lib/simplecov/context_map.rb +187 -0
  87. data/lib/simplecov/coverage_json.rb +2 -5
  88. data/lib/simplecov/coverage_statistics.rb +14 -24
  89. data/lib/simplecov/coverage_violations.rb +157 -64
  90. data/lib/simplecov/current_run.rb +61 -0
  91. data/lib/simplecov/defaults.rb +4 -23
  92. data/lib/simplecov/deprecation.rb +45 -27
  93. data/lib/simplecov/directive.rb +46 -62
  94. data/lib/simplecov/exit_codes/baseline_check.rb +46 -0
  95. data/lib/simplecov/exit_codes/check.rb +17 -11
  96. data/lib/simplecov/exit_codes/exit_code_handling.rb +21 -8
  97. data/lib/simplecov/exit_codes/maximum_coverage_drop_check.rb +7 -10
  98. data/lib/simplecov/exit_codes/maximum_missed_check.rb +26 -0
  99. data/lib/simplecov/exit_codes/maximum_missed_per_file_check.rb +34 -0
  100. data/lib/simplecov/exit_codes/maximum_overall_coverage_check.rb +7 -12
  101. data/lib/simplecov/exit_codes/minimum_coverage_by_file_check.rb +11 -10
  102. data/lib/simplecov/exit_codes/minimum_coverage_by_group_check.rb +7 -9
  103. data/lib/simplecov/exit_codes/minimum_overall_coverage_check.rb +16 -17
  104. data/lib/simplecov/exit_codes.rb +16 -10
  105. data/lib/simplecov/exit_handling.rb +94 -59
  106. data/lib/simplecov/file_list.rb +20 -51
  107. data/lib/simplecov/filter.rb +43 -66
  108. data/lib/simplecov/formatter/base.rb +37 -55
  109. data/lib/simplecov/formatter/baseline_formatter.rb +98 -0
  110. data/lib/simplecov/formatter/coverage_json_writer.rb +29 -31
  111. data/lib/simplecov/formatter/html_formatter/public/index.html +15 -9
  112. data/lib/simplecov/formatter/html_formatter/viewer_data_validator.rb +74 -17
  113. data/lib/simplecov/formatter/html_formatter.rb +24 -41
  114. data/lib/simplecov/formatter/json_formatter/errors_formatter.rb +50 -16
  115. data/lib/simplecov/formatter/json_formatter/production_section_formatter.rb +48 -0
  116. data/lib/simplecov/formatter/json_formatter/result_hash_formatter.rb +43 -29
  117. data/lib/simplecov/formatter/json_formatter/source_file_formatter.rb +14 -10
  118. data/lib/simplecov/formatter/json_formatter.rb +7 -13
  119. data/lib/simplecov/formatter/multi_formatter.rb +5 -11
  120. data/lib/simplecov/formatter/simple_formatter.rb +2 -8
  121. data/lib/simplecov/formatter.rb +14 -10
  122. data/lib/simplecov/group_names.rb +13 -12
  123. data/lib/simplecov/history.rb +115 -0
  124. data/lib/simplecov/last_run.rb +6 -9
  125. data/lib/simplecov/lines_classifier.rb +31 -16
  126. data/lib/simplecov/load_global_config.rb +7 -7
  127. data/lib/simplecov/parallel_adapters/base.rb +13 -41
  128. data/lib/simplecov/parallel_adapters/generic.rb +7 -19
  129. data/lib/simplecov/parallel_adapters/parallel_tests.rb +21 -42
  130. data/lib/simplecov/parallel_adapters.rb +24 -55
  131. data/lib/simplecov/parallel_coordination.rb +39 -49
  132. data/lib/simplecov/parallel_result_merger/worker_payload.rb +33 -0
  133. data/lib/simplecov/parallel_result_merger.rb +67 -126
  134. data/lib/simplecov/process.rb +14 -32
  135. data/lib/simplecov/production/error.rb +11 -0
  136. data/lib/simplecov/production/file_sink.rb +130 -0
  137. data/lib/simplecov/production.rb +249 -0
  138. data/lib/simplecov/profiles/rails.rb +1 -12
  139. data/lib/simplecov/profiles/root_filter.rb +0 -4
  140. data/lib/simplecov/profiles/strict.rb +9 -19
  141. data/lib/simplecov/profiles.rb +11 -33
  142. data/lib/simplecov/report_deferral.rb +11 -15
  143. data/lib/simplecov/report_stamp.rb +6 -9
  144. data/lib/simplecov/result/filter_config.rb +19 -0
  145. data/lib/simplecov/result/missing_source_files_reporter.rb +8 -9
  146. data/lib/simplecov/result/serialization.rb +43 -0
  147. data/lib/simplecov/result/source_file_builder.rb +7 -12
  148. data/lib/simplecov/result.rb +68 -117
  149. data/lib/simplecov/result_adapter.rb +56 -68
  150. data/lib/simplecov/result_merger/contexts.rb +22 -0
  151. data/lib/simplecov/result_merger/legacy_format_adapter.rb +4 -8
  152. data/lib/simplecov/result_merger/resultset_file.rb +20 -27
  153. data/lib/simplecov/result_merger/resultset_run_identity.rb +23 -25
  154. data/lib/simplecov/result_merger/resultset_store.rb +23 -14
  155. data/lib/simplecov/result_merger/unloaded_files.rb +24 -32
  156. data/lib/simplecov/result_merger.rb +59 -58
  157. data/lib/simplecov/result_processing.rb +74 -119
  158. data/lib/simplecov/run_identity.rb +16 -18
  159. data/lib/simplecov/simulate_coverage.rb +34 -50
  160. data/lib/simplecov/source_file/branch.rb +8 -33
  161. data/lib/simplecov/source_file/branch_builder.rb +28 -47
  162. data/lib/simplecov/source_file/builder_context.rb +6 -13
  163. data/lib/simplecov/source_file/line.rb +13 -33
  164. data/lib/simplecov/source_file/line_builder.rb +9 -15
  165. data/lib/simplecov/source_file/method.rb +3 -7
  166. data/lib/simplecov/source_file/method_builder.rb +6 -15
  167. data/lib/simplecov/source_file/ruby_data_parser.rb +59 -66
  168. data/lib/simplecov/source_file/skip_chunks.rb +19 -28
  169. data/lib/simplecov/source_file/source_loader.rb +45 -35
  170. data/lib/simplecov/source_file/statistics.rb +10 -9
  171. data/lib/simplecov/source_file.rb +21 -51
  172. data/lib/simplecov/static_coverage_extractor/condition_folding.rb +63 -114
  173. data/lib/simplecov/static_coverage_extractor/location_conventions.rb +76 -121
  174. data/lib/simplecov/static_coverage_extractor/method_collector.rb +11 -24
  175. data/lib/simplecov/static_coverage_extractor/prism_compat.rb +15 -24
  176. data/lib/simplecov/static_coverage_extractor/value_position.rb +25 -38
  177. data/lib/simplecov/static_coverage_extractor/visitor.rb +47 -64
  178. data/lib/simplecov/static_coverage_extractor.rb +46 -62
  179. data/lib/simplecov/test_tracker/accessors.rb +34 -0
  180. data/lib/simplecov/test_tracker/constant_watch.rb +49 -0
  181. data/lib/simplecov/test_tracker/delta.rb +69 -0
  182. data/lib/simplecov/test_tracker/framework_hooks.rb +130 -0
  183. data/lib/simplecov/test_tracker.rb +162 -0
  184. data/lib/simplecov/unloaded_file_injector.rb +18 -31
  185. data/lib/simplecov/useless_results_remover.rb +7 -6
  186. data/lib/simplecov/version.rb +1 -1
  187. data/lib/simplecov/view_coverage/template_compiler.rb +83 -0
  188. data/lib/simplecov/view_coverage.rb +55 -0
  189. data/lib/simplecov-html.rb +1 -0
  190. data/lib/simplecov.rb +57 -113
  191. data/lib/simplecov_json_formatter.rb +1 -0
  192. data/man/simplecov.1 +349 -0
  193. data/sig/simplecov.rbs +523 -595
  194. metadata +72 -5
@@ -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
@@ -6,23 +6,17 @@ require_relative "../atomic_file"
6
6
 
7
7
  module SimpleCov
8
8
  module Formatter
9
- # Shared writer for the coverage.json artifact, used by JSONFormatter
10
- # (as its report) and HTMLFormatter (as the side file feeding
11
- # `simplecov serve` and external tools). Centralizing the write keeps
12
- # the two copies byte-identical (same pretty printing, same binary
13
- # mode, so no CRLF translation on Windows depending on which
14
- # formatter wrote last) and gives both formatters the
15
- # concurrent-overwrite warning of issue #1171.
9
+ # Shared writer for the coverage.json artifact, used by JSONFormatter as its
10
+ # report and by HTMLFormatter as the side file feeding `simplecov serve` and
11
+ # external tools. Centralizing the write keeps the two copies byte-identical
12
+ # and gives both formatters the concurrent-overwrite warning of #1171.
16
13
  module CoverageJSONWriter
17
14
  FILENAME = "coverage.json"
18
15
 
19
- # The previous report can embed the project's entire source text,
20
- # so the overwrite check bounds how much of it is read looking for
21
- # the meta object before falling back to a full parse.
22
16
  META_SCAN_BYTES = 64 * 1024
23
17
  private_constant :META_SCAN_BYTES
24
18
 
25
- module_function
19
+ extend self
26
20
 
27
21
  def write(output_path, hash, result)
28
22
  path = File.join(output_path, FILENAME)
@@ -32,20 +26,16 @@ module SimpleCov
32
26
  end
33
27
 
34
28
  # Warns when the existing coverage.json has a timestamp newer than this
35
- # process's start time a strong signal that a sibling test process
36
- # (e.g., parallel_tests) wrote it while we were running, and that our
37
- # write is about to clobber their data.
29
+ # process's start time, a strong signal that a sibling test process wrote it
30
+ # while we were running. A matching command_name means the same merged
31
+ # result, which is what both formatters configured together produce, so
32
+ # there is nothing to lose by overwriting (#1171).
38
33
  def warn_if_concurrent_overwrite(path, result)
39
34
  start_time = SimpleCov.process_start_time or return
40
35
  existing = existing_meta(path) or return
41
- return unless existing[:timestamp] > start_time
36
+ return unless existing.fetch(:timestamp) > start_time
42
37
 
43
- # Both formatters write coverage.json through this method, so when
44
- # they are configured together the file found here was just written
45
- # by our own run, not a concurrent one. A matching command_name
46
- # means the same merged result, so there's nothing to lose by
47
- # overwriting. See issue #1171.
48
- return if existing[:command_name] == result.command_name
38
+ return if existing.fetch(:command_name).eql?(result.command_name)
49
39
 
50
40
  warn "simplecov: #{path} was written at #{existing[:timestamp].iso8601} — after " \
51
41
  "this process started at #{start_time.iso8601}. Overwriting " \
@@ -58,11 +48,22 @@ module SimpleCov
58
48
  return nil unless File.exist?(path)
59
49
 
60
50
  meta = parse_meta(path) or return nil
61
- timestamp = meta[:timestamp] or return nil
51
+ timestamp = parse_time(meta[:timestamp]) or return nil
62
52
 
63
- {timestamp: Time.iso8601(timestamp), command_name: meta[:command_name]}
53
+ {timestamp: timestamp, command_name: meta[:command_name]}
54
+ end
55
+
56
+ # Our own writer emits an ISO 8601 string, but coverage.json is whatever
57
+ # wrote it last: third-party formatters rewrite it in their own shape with
58
+ # an epoch integer, and `Time.iso8601` raised TypeError on that, taking the
59
+ # whole report down on the next run. Anything but the two spellings disables
60
+ # the concurrency check for this file instead of failing the write (#1285).
61
+ def parse_time(value)
62
+ case value
63
+ when String then Time.iso8601(value)
64
+ when Numeric then Time.at(value)
65
+ end
64
66
  rescue ArgumentError
65
- # An unparseable timestamp disables the check for this file.
66
67
  nil
67
68
  end
68
69
 
@@ -70,17 +71,14 @@ module SimpleCov
70
71
  parse_meta_head(path) || parse_meta_full(path)
71
72
  end
72
73
 
73
- # The meta object is flat and sits at the head of every file this
74
- # module writes, so the common case parses just that slice instead
75
- # of a multi-megabyte report. A miss (foreign key order, a brace
76
- # inside a meta string) falls back to the full parse.
74
+ # The meta object is flat and sits at the head of every file this module
75
+ # writes, so the common case parses just that slice instead of a
76
+ # multi-megabyte report. A miss falls back to the full parse.
77
77
  def parse_meta_head(path)
78
78
  head = File.read(path, META_SCAN_BYTES).to_s
79
79
  slice = head[/"meta"\s*:\s*(\{.*?\})/m, 1]
80
80
  return nil unless slice
81
81
 
82
- # The captured slice is always a JSON object, so a successful parse
83
- # is always a Hash.
84
82
  JSON.parse(slice, symbolize_names: true)
85
83
  rescue JSON::ParserError
86
84
  nil
@@ -88,7 +86,7 @@ module SimpleCov
88
86
 
89
87
  def parse_meta_full(path)
90
88
  parsed = JSON.parse(File.read(path), symbolize_names: true)
91
- parsed.is_a?(Hash) ? parsed[:meta] : nil
89
+ parsed[:meta] if parsed.is_a?(Hash)
92
90
  rescue JSON::ParserError
93
91
  nil
94
92
  end