Module: SimpleCov::CLI::Merge
Overview
simplecov merge <files...> — wrap SimpleCov::ResultMerger so a
CI matrix that produces one .resultset.json per worker can stitch
them together from the shell instead of dropping a Rake task into
every project. Requires the full simplecov library to be on the
load path; lazy-required so the read-only subcommands above don't
pay for ResultMerger (and its Coverage runtime guard).
Constant Summary
CommandHelpers::STATS_ROW_FORMAT
Class Method Summary
collapse
-
.commit(opts, result, stdout) ⇒ Object
-
.duplicate_warning(command_name, paths) ⇒ Object
-
.parse(args) ⇒ Object
-
.parse_input(path, stderr) ⇒ Object
Read first, classify by the exception: an exist?-then-read pair is racy, and rescuing only ENOENT crashed with a backtrace on a directory (EISDIR) or an unreadable file (EACCES).
-
.parse_input_error(stderr, path, reason) ⇒ Object
-
.parse_inputs(files, stderr) ⇒ Object
Validate every input file up-front and return a => parsed hash.
-
.run(args, stdout:, stderr:) ⇒ Object
-
.valid_inputs?(files, stderr) ⇒ Boolean
-
.warn_about_duplicate_command_names(parsed, stderr) ⇒ Object
When two input files share a command_name, ResultMerger folds them together with last-write-wins on the timestamp — easy to mistake for "no merge happened." Surface the overlap so the operator can rename the workers or accept the merge knowingly.
-
.write(path, result) ⇒ Object
command_name, common_options, error, parse_common, stats_row
Class Method Details
.commit(opts, result, stdout) ⇒ Object
34
35
36
37
38
|
# File 'lib/simplecov/cli/merge.rb', line 34
def commit(opts, result, stdout)
verb = opts[:dry_run] ? "would write" : "wrote"
write(opts[:output], result) unless opts[:dry_run]
stdout.puts("simplecov merge: #{verb} #{opts[:output]}") unless opts[:quiet]
end
|
.duplicate_warning(command_name, paths) ⇒ Object
109
110
111
112
113
|
# File 'lib/simplecov/cli/merge.rb', line 109
def duplicate_warning(command_name, paths)
"simplecov merge: warning — command_name #{command_name.inspect} " \
"appears in #{paths.size} input files (#{paths.join(', ')}); " \
"entries will be merged"
end
|
.parse(args) ⇒ Object
47
48
49
50
51
52
53
54
55
56
57
|
# File 'lib/simplecov/cli/merge.rb', line 47
def parse(args)
opts = {output: SimpleCov::CLI.default_resultset, honor_timeout: false, dry_run: false, quiet: false}
files =
OptionParser.new do |o|
o.on("--output PATH") { |v| opts[:output] = v }
o.on("--honor-timeout") { opts[:honor_timeout] = true }
o.on("--dry-run") { opts[:dry_run] = true }
o.on("-q", "--quiet") { opts[:quiet] = true }
end.parse(args)
opts.merge(files: files)
end
|
Read first, classify by the exception: an exist?-then-read pair
is racy, and rescuing only ENOENT crashed with a backtrace on a
directory (EISDIR) or an unreadable file (EACCES).
75
76
77
78
79
80
81
82
83
84
85
86
|
# File 'lib/simplecov/cli/merge.rb', line 75
def parse_input(path, stderr)
data = JSON.parse(File.read(path))
return data if data.is_a?(Hash) && !data.empty?
parse_input_error(stderr, path, "has no resultset entries")
rescue Errno::ENOENT
parse_input_error(stderr, path, "not found")
rescue JSON::ParserError => e
parse_input_error(stderr, path, "isn't valid JSON (#{e.message})")
rescue SystemCallError => e
parse_input_error(stderr, path, "cannot be read (#{e.message.lines.first.to_s.strip})")
end
|
88
89
90
91
|
# File 'lib/simplecov/cli/merge.rb', line 88
def parse_input_error(stderr, path, reason)
stderr.puts("simplecov merge: input file #{path.inspect} #{reason}")
nil
end
|
Validate every input file up-front and return a => parsed
hash. Surfacing per-file errors here turns ResultMerger's
generic "no mergeable results" into a message that points at
the specific input causing the failure.
63
64
65
66
67
68
69
70
|
# File 'lib/simplecov/cli/merge.rb', line 63
def parse_inputs(files, stderr)
parsed = {} files.each_with_object(parsed) do |path, memo|
data = parse_input(path, stderr) or return nil
memo[path] = data
end
end
|
.run(args, stdout:, stderr:) ⇒ Object
21
22
23
24
25
26
27
28
29
30
31
32
|
# File 'lib/simplecov/cli/merge.rb', line 21
def run(args, stdout:, stderr:, **)
opts = parse(args)
return error(stderr, "missing input files") if opts[:files].empty?
return 1 unless valid_inputs?(opts[:files], stderr)
require "simplecov"
result = SimpleCov::ResultMerger.merge_results(*opts[:files], ignore_timeout: !opts[:honor_timeout])
return error(stderr, "no mergeable results in input files") unless result
commit(opts, result, stdout)
0
end
|
40
41
42
43
44
45
|
# File 'lib/simplecov/cli/merge.rb', line 40
def valid_inputs?(files, stderr)
parsed = parse_inputs(files, stderr) or return false
warn_about_duplicate_command_names(parsed, stderr)
true
end
|
.warn_about_duplicate_command_names(parsed, stderr) ⇒ Object
When two input files share a command_name, ResultMerger folds
them together with last-write-wins on the timestamp — easy to
mistake for "no merge happened." Surface the overlap so the
operator can rename the workers or accept the merge knowingly.
97
98
99
100
101
102
103
104
105
106
107
|
# File 'lib/simplecov/cli/merge.rb', line 97
def warn_about_duplicate_command_names(parsed, stderr)
files_per_command = {} parsed.each do |path, data|
data.each_key { |command_name| (files_per_command[command_name] ||= []) << path }
end
files_per_command.each do |command_name, paths|
next if paths.size < 2
stderr.puts(duplicate_warning(command_name, paths))
end
end
|
.write(path, result) ⇒ Object
115
116
117
|
# File 'lib/simplecov/cli/merge.rb', line 115
def write(path, result)
AtomicFile.write(path, JSON.pretty_generate(result.to_hash))
end
|