module SimpleCov::CLI::Merge
def commit(opts, result, stdout)
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
def duplicate_warning(command_name, paths)
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
def error(stderr, message)
def error(stderr, message) stderr.puts("simplecov merge: #{message}") 1 end
def parse(args)
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
def parse_input(path, stderr)
def parse_input(path, stderr) return parse_input_error(stderr, path, "not found") unless File.exist?(path) 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 JSON::ParserError => e parse_input_error(stderr, path, "isn't valid JSON (#{e.message})") end
def parse_input_error(stderr, path, reason)
def parse_input_error(stderr, path, reason) stderr.puts("simplecov merge: input file #{path.inspect} #{reason}") nil end
def parse_inputs(files, stderr)
generic "no mergeable results" into a message that points at
hash. Surfacing per-file errors here turns ResultMerger's
Validate every input file up-front and return a {path => parsed}
def parse_inputs(files, stderr) files.each_with_object({}) do |path, memo| data = parse_input(path, stderr) or return nil memo[path] = data end end
def run(args, stdout:, stderr:, **)
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
def valid_inputs?(files, stderr)
def valid_inputs?(files, stderr) parsed = parse_inputs(files, stderr) or return false warn_about_duplicate_command_names(parsed, stderr) true end
def warn_about_duplicate_command_names(parsed, stderr)
mistake for "no merge happened." Surface the overlap so the
them together with last-write-wins on the timestamp — easy to
When two input files share a command_name, ResultMerger folds
def warn_about_duplicate_command_names(parsed, stderr) files_per_command = parsed.each_with_object({}) do |(path, data), memo| data.each_key { |command_name| (memo[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
def write(path, result)
def write(path, result) require "fileutils" FileUtils.mkdir_p(File.dirname(path)) File.write(path, JSON.pretty_generate(result.to_hash)) end