Module: SimpleCov::CLI::Affected
- Extended by:
- Affected, CommandHelpers
- Included in:
- Affected
- Defined in:
- lib/simplecov/cli/affected.rb,
lib/simplecov/cli/affected/selection.rb,
lib/simplecov/cli/affected/changed_files.rb
Overview
simplecov affected: select the test files whose recorded tests touch the
code changed since a git base ref, from a coverage.json written under
track_tests. The change is everything between the merge base of --base
and the working tree, plus untracked files.
The set intersection is the easy half; the hard half is knowing when to distrust the map, because a test map is stale the moment something changes that no test mentions by name. Any changed file outside the tracked set fails open to the full suite, out loud: each trigger is named on stderr and stdout stays empty, which a bare runner reads as "run everything". Test files always select themselves, recorded or not.
Defined Under Namespace
Modules: ChangedFiles, Selection
Constant Summary
Constants included from CommandHelpers
CommandHelpers::STATS_ROW_FORMAT
Instance Method Summary collapse
- #deliver(selection, opts, stdout, stderr) ⇒ Object
- #emit_json(selection, full, stdout) ⇒ Object
-
#emit_text(selection, full, stdout, stderr) ⇒ Object
Stdout carries the selected files and nothing else, so the list can feed a runner directly, and a full-suite fallback prints nothing, which
rspec $(simplecov affected)reads as "run everything". - #execute(selection, full, opts, stderr) ⇒ Object
- #no_changes(opts, stdout, stderr) ⇒ Object
- #note_untouched(stderr) ⇒ Object
- #parse(args) ⇒ Object
-
#precheck(opts) ⇒ Object
A stray positional looks exactly like a forgotten
--baseref, and silently dropping it would select tests for the wrong change. - #respond(diffed, document, contexts, opts, stdout:, stderr:) ⇒ Object
- #run(args, stdout:, stderr:) ⇒ Object
-
#run_command(command, root, stderr) ⇒ Object
The selection's paths are relative to the repository root, so the runner starts there.
-
#split_runner(args) ⇒ Object
Everything after
--runis the runner command, verbatim, so the command's own flags never collide with ours.
Methods included from CommandHelpers
build_parser, command_name, common_options, error, error_nil, on_help, one?, parse_common, quiet_option, recorded_contexts, stats_row
Instance Method Details
#deliver(selection, opts, stdout, stderr) ⇒ Object
90 91 92 93 94 95 96 97 98 |
# File 'lib/simplecov/cli/affected.rb', line 90 def deliver(selection, opts, stdout, stderr) full = !selection.fetch(:triggers).empty? selection.fetch(:triggers).each { |trigger| stderr.puts("simplecov affected: #{trigger}") } stderr.puts("simplecov affected: falling back to the full suite") if full return emit_json(selection, full, stdout) if opts.fetch(:json) return execute(selection, full, opts, stderr) if opts.fetch(:run) emit_text(selection, full, stdout, stderr) end |
#emit_json(selection, full, stdout) ⇒ Object
100 101 102 103 104 105 |
# File 'lib/simplecov/cli/affected.rb', line 100 def emit_json(selection, full, stdout) none = [] #: Array[String] stdout.puts(JSON.pretty_generate("full_suite" => full, "triggers" => selection.fetch(:triggers), "tests" => full ? none : selection.fetch(:tests))) 0 end |
#emit_text(selection, full, stdout, stderr) ⇒ Object
Stdout carries the selected files and nothing else, so the list can feed a
runner directly, and a full-suite fallback prints nothing, which
rspec $(simplecov affected) reads as "run everything".
110 111 112 113 114 115 116 117 118 119 120 |
# File 'lib/simplecov/cli/affected.rb', line 110 def emit_text(selection, full, stdout, stderr) return 0 if full tests = selection.fetch(:tests) if tests.empty? note_untouched(stderr) else tests.each { |file| stdout.puts(file) } 0 end end |
#execute(selection, full, opts, stderr) ⇒ Object
127 128 129 130 131 132 133 134 |
# File 'lib/simplecov/cli/affected.rb', line 127 def execute(selection, full, opts, stderr) return run_command(opts.fetch(:run), opts.fetch(:root), stderr) if full return note_untouched(stderr) if selection.fetch(:tests).empty? count = selection.fetch(:tests).size stderr.puts("simplecov affected: running #{count} test file#{"s" unless count.eql?(1)}") run_command(opts.fetch(:run) + selection.fetch(:tests), opts.fetch(:root), stderr) end |
#no_changes(opts, stdout, stderr) ⇒ Object
83 84 85 86 87 88 |
# File 'lib/simplecov/cli/affected.rb', line 83 def no_changes(opts, stdout, stderr) stderr.puts("simplecov affected: no changes against #{opts.fetch(:base)}") empty = {tests: [], triggers: []} #: Hash[Symbol, Array[String]] emit_json(empty, false, stdout) if opts.fetch(:json) 0 end |
#note_untouched(stderr) ⇒ Object
122 123 124 125 |
# File 'lib/simplecov/cli/affected.rb', line 122 def note_untouched(stderr) stderr.puts("simplecov affected: no recorded test touches the changed code") 0 end |
#parse(args) ⇒ Object
45 46 47 48 49 50 51 52 |
# File 'lib/simplecov/cli/affected.rb', line 45 def parse(args) head, runner = split_runner(args) opts, rest = parse_common(head, base: nil, run: runner) do |parser, into| parser.on("--base REF") { |v| into[:base] = v } end opts[:rest] = rest opts end |
#precheck(opts) ⇒ Object
A stray positional looks exactly like a forgotten --base ref, and
silently dropping it would select tests for the wrong change.
65 66 67 68 69 70 71 |
# File 'lib/simplecov/cli/affected.rb', line 65 def precheck(opts) stray = opts.fetch(:rest).first return "unexpected argument #{stray.inspect} (did you mean `--base #{stray}`?)" if stray return "missing command after --run" if opts.fetch(:run) && opts.fetch(:run).empty? "--run and --json cannot be combined" if opts.fetch(:run) && opts.fetch(:json) end |
#respond(diffed, document, contexts, opts, stdout:, stderr:) ⇒ Object
73 74 75 76 77 78 79 80 81 |
# File 'lib/simplecov/cli/affected.rb', line 73 def respond(diffed, document, contexts, opts, stdout:, stderr:) opts[:root] = diffed.fetch(:root) return no_changes(opts, stdout, stderr) if diffed.fetch(:changed).empty? selection = Selection.build(diffed.fetch(:changed), document, contexts, opts, stderr, root: diffed.fetch(:root)) return 1 unless selection deliver(selection, opts, stdout, stderr) end |
#run(args, stdout:, stderr:) ⇒ Object
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
# File 'lib/simplecov/cli/affected.rb', line 28 def run(args, stdout:, stderr:) opts = parse(args) issue = precheck(opts) return error(stderr, issue) if issue document = CoverageFile.load_document(opts.fetch(:input), command: "affected", stderr: stderr) return 1 unless document contexts = recorded_contexts(document, opts, stderr) return 1 unless contexts diffed = ChangedFiles.call(opts[:base] ||= Git.default_base, stderr) return 1 unless diffed respond(diffed, document, contexts, opts, stdout: stdout, stderr: stderr) end |
#run_command(command, root, stderr) ⇒ Object
The selection's paths are relative to the repository root, so the runner starts there. The command is named explicitly in the failure because not every engine's exception message carries it.
139 140 141 142 143 144 145 |
# File 'lib/simplecov/cli/affected.rb', line 139 def run_command(command, root, stderr) _, status = Process.wait2(spawn(*command, chdir: root)) status.exitstatus || 1 rescue SystemCallError => e stderr.puts("simplecov affected: cannot run #{command.first.inspect} (#{e})") 127 end |
#split_runner(args) ⇒ Object
Everything after --run is the runner command, verbatim, so the command's
own flags never collide with ours.
56 57 58 59 60 61 |
# File 'lib/simplecov/cli/affected.rb', line 56 def split_runner(args) index = args.index("--run") return [args, nil] unless index [args.take(index), args.drop(index + 1)] end |