Module: SimpleCov::Formatter::CoverageJSONWriter
- Extended by:
- CoverageJSONWriter
- Included in:
- CoverageJSONWriter
- Defined in:
- lib/simplecov/formatter/coverage_json_writer.rb,
sig/simplecov.rbs
Overview
Shared writer for the coverage.json artifact, used by JSONFormatter as its
report and by HTMLFormatter as the side file feeding simplecov serve and
external tools. Centralizing the write keeps the two copies byte-identical
and gives both formatters the concurrent-overwrite warning of #1171.
Constant Summary collapse
- FILENAME =
"coverage.json"- META_SCAN_BYTES =
64 * 1024
Instance Method Summary collapse
- #existing_meta(path) ⇒ Object
- #parse_meta(path) ⇒ Object
- #parse_meta_full(path) ⇒ Object
-
#parse_meta_head(path) ⇒ Object
The meta object is flat and sits at the head of every file this module writes, so the common case parses just that slice instead of a multi-megabyte report.
-
#parse_time(value) ⇒ Object
Our own writer emits an ISO 8601 string, but coverage.json is whatever wrote it last: third-party formatters rewrite it in their own shape with an epoch integer, and
Time.iso8601raised TypeError on that, taking the whole report down on the next run. - #self?.existing_meta ⇒ { timestamp: untyped, command_name: untyped }?
- #self?.parse_meta ⇒ Hash[Symbol, untyped]?
- #self?.parse_meta_full ⇒ Hash[Symbol, untyped]?
- #self?.parse_meta_head ⇒ Hash[Symbol, untyped]?
- #self?.parse_time ⇒ Object
- #self?.warn_if_concurrent_overwrite ⇒ void
- #self?.write ⇒ String
-
#warn_if_concurrent_overwrite(path, result) ⇒ Object
Warns when the existing coverage.json has a timestamp newer than this process's start time, a strong signal that a sibling test process wrote it while we were running.
- #write(output_path, hash, result) ⇒ Object
Instance Method Details
#existing_meta(path) ⇒ Object
47 48 49 50 51 52 53 54 |
# File 'lib/simplecov/formatter/coverage_json_writer.rb', line 47 def (path) return nil unless File.exist?(path) = (path) or return nil = parse_time([:timestamp]) or return nil {timestamp: , command_name: [:command_name]} end |
#parse_meta(path) ⇒ Object
70 71 72 |
# File 'lib/simplecov/formatter/coverage_json_writer.rb', line 70 def (path) (path) || (path) end |
#parse_meta_full(path) ⇒ Object
87 88 89 90 91 92 |
# File 'lib/simplecov/formatter/coverage_json_writer.rb', line 87 def (path) parsed = JSON.parse(File.read(path), symbolize_names: true) parsed[:meta] if parsed.is_a?(Hash) rescue JSON::ParserError nil end |
#parse_meta_head(path) ⇒ Object
The meta object is flat and sits at the head of every file this module writes, so the common case parses just that slice instead of a multi-megabyte report. A miss falls back to the full parse.
77 78 79 80 81 82 83 84 85 |
# File 'lib/simplecov/formatter/coverage_json_writer.rb', line 77 def (path) head = File.read(path, META_SCAN_BYTES).to_s slice = head[/"meta"\s*:\s*(\{.*?\})/m, 1] return nil unless slice JSON.parse(slice, symbolize_names: true) rescue JSON::ParserError nil end |
#parse_time(value) ⇒ Object
Our own writer emits an ISO 8601 string, but coverage.json is whatever
wrote it last: third-party formatters rewrite it in their own shape with
an epoch integer, and Time.iso8601 raised TypeError on that, taking the
whole report down on the next run. Anything but the two spellings disables
the concurrency check for this file instead of failing the write (#1285).
61 62 63 64 65 66 67 68 |
# File 'lib/simplecov/formatter/coverage_json_writer.rb', line 61 def parse_time(value) case value when String then Time.iso8601(value) when Numeric then Time.at(value) end rescue ArgumentError nil end |
#self?.existing_meta ⇒ { timestamp: untyped, command_name: untyped }?
1225 |
# File 'sig/simplecov.rbs', line 1225 def self?.existing_meta: (String path) -> ({ timestamp: untyped, command_name: untyped })? |
#self?.parse_meta ⇒ Hash[Symbol, untyped]?
1229 |
# File 'sig/simplecov.rbs', line 1229 def self?.parse_meta: (String path) -> Hash[Symbol, untyped]? |
#self?.parse_meta_full ⇒ Hash[Symbol, untyped]?
1233 |
# File 'sig/simplecov.rbs', line 1233 def self?.parse_meta_full: (String path) -> Hash[Symbol, untyped]? |
#self?.parse_meta_head ⇒ Hash[Symbol, untyped]?
1231 |
# File 'sig/simplecov.rbs', line 1231 def self?.parse_meta_head: (String path) -> Hash[Symbol, untyped]? |
#self?.parse_time ⇒ Object
1227 |
# File 'sig/simplecov.rbs', line 1227 def self?.parse_time: (untyped value) -> untyped |
#self?.warn_if_concurrent_overwrite ⇒ void
This method returns an undefined value.
1223 |
# File 'sig/simplecov.rbs', line 1223 def self?.warn_if_concurrent_overwrite: (String path, SimpleCov::Result result) -> void |
#self?.write ⇒ String
1221 |
# File 'sig/simplecov.rbs', line 1221 def self?.write: (untyped output_path, Hash[Symbol, untyped] hash, SimpleCov::Result result) -> String |
#warn_if_concurrent_overwrite(path, result) ⇒ Object
Warns when the existing coverage.json has a timestamp newer than this process's start time, a strong signal that a sibling test process wrote it while we were running. A matching command_name means the same merged result, which is what both formatters configured together produce, so there is nothing to lose by overwriting (#1171).
33 34 35 36 37 38 39 40 41 42 43 44 45 |
# File 'lib/simplecov/formatter/coverage_json_writer.rb', line 33 def warn_if_concurrent_overwrite(path, result) start_time = SimpleCov.process_start_time or return existing = (path) or return return unless existing.fetch(:timestamp) > start_time return if existing.fetch(:command_name).eql?(result.command_name) warn "simplecov: #{path} was written at #{existing[:timestamp].iso8601} — after " \ "this process started at #{start_time.iso8601}. Overwriting " \ "likely loses coverage data from a concurrent test run. For " \ "parallel test setups, use SimpleCov::ResultMerger or run a single " \ "collation step after all workers finish." end |
#write(output_path, hash, result) ⇒ Object
21 22 23 24 25 26 |
# File 'lib/simplecov/formatter/coverage_json_writer.rb', line 21 def write(output_path, hash, result) path = File.join(output_path, FILENAME) warn_if_concurrent_overwrite(path, result) AtomicFile.write(path, JSON.pretty_generate(hash), binary: true) path end |