mutineer 1.5.0 → 1.7.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +473 -0
- data/README.md +352 -26
- data/lib/mutineer/child_coverage.rb +53 -0
- data/lib/mutineer/cli.rb +184 -24
- data/lib/mutineer/concern_blocks.rb +80 -0
- data/lib/mutineer/config.rb +148 -8
- data/lib/mutineer/coverage_map.rb +541 -92
- data/lib/mutineer/daemon_backend.rb +227 -86
- data/lib/mutineer/daemon_client.rb +72 -9
- data/lib/mutineer/daemon_server.rb +229 -51
- data/lib/mutineer/external_backend.rb +12 -1
- data/lib/mutineer/file_swap.rb +1 -1
- data/lib/mutineer/init.rb +124 -0
- data/lib/mutineer/isolation.rb +15 -2
- data/lib/mutineer/job_plan.rb +458 -0
- data/lib/mutineer/migrate.rb +705 -0
- data/lib/mutineer/minitest_integration.rb +48 -1
- data/lib/mutineer/mutator_registry.rb +6 -2
- data/lib/mutineer/mutators/base.rb +14 -7
- data/lib/mutineer/mutators/call_to_receiver.rb +135 -0
- data/lib/mutineer/mutators/chain_link.rb +1 -1
- data/lib/mutineer/orphan_guard.rb +100 -0
- data/lib/mutineer/project.rb +411 -21
- data/lib/mutineer/rails_run_env.rb +73 -0
- data/lib/mutineer/rails_worker_db.rb +1477 -48
- data/lib/mutineer/redaction.rb +98 -0
- data/lib/mutineer/reporter.rb +83 -12
- data/lib/mutineer/result.rb +49 -8
- data/lib/mutineer/runner.rb +66 -290
- data/lib/mutineer/statement_lines.rb +54 -7
- data/lib/mutineer/subject.rb +24 -2
- data/lib/mutineer/test_runners/rspec.rb +54 -3
- data/lib/mutineer/test_runners.rb +3 -2
- data/lib/mutineer/triage.rb +111 -0
- data/lib/mutineer/version.rb +1 -1
- data/lib/mutineer.rb +5 -0
- metadata +11 -1
|
@@ -12,7 +12,8 @@ module Mutineer
|
|
|
12
12
|
# Child-process-only RSpec runner.
|
|
13
13
|
#
|
|
14
14
|
# Mirrors MinitestIntegration's contract: run the given spec files and
|
|
15
|
-
# return 0 (all passed) or
|
|
15
|
+
# return 0 (all passed), 1 (an example failed) or 2 (only an error
|
|
16
|
+
# outside examples, such as a spec file that raised while loading).
|
|
16
17
|
module RSpec
|
|
17
18
|
# Runs the given RSpec files.
|
|
18
19
|
#
|
|
@@ -21,7 +22,7 @@ module Mutineer
|
|
|
21
22
|
# first failing example (RSpec `--fail-fast`).
|
|
22
23
|
# @param record_to [IO, nil] a `--matrix` run: send each example's outcome
|
|
23
24
|
# to this channel (see {KillFormatter}) and run every example.
|
|
24
|
-
# @return [Integer] 0 on success,
|
|
25
|
+
# @return [Integer] 0 on success, else {.failure_code}.
|
|
25
26
|
# @raise [ArgumentError] when both options are given: a matrix run never stops.
|
|
26
27
|
def self.run(spec_files, stop_at_first_failure: false, record_to: nil)
|
|
27
28
|
if stop_at_first_failure && record_to
|
|
@@ -44,6 +45,7 @@ module Mutineer
|
|
|
44
45
|
# silenced here: the fork boundary that calls this method has already
|
|
45
46
|
# pointed stdout at File::NULL (see ChildStdout).
|
|
46
47
|
sink = StringIO.new
|
|
48
|
+
keep_file_order(spec_files) if stop_at_first_failure || record_to
|
|
47
49
|
args = ["--no-color"]
|
|
48
50
|
args << "--fail-fast" if stop_at_first_failure
|
|
49
51
|
status = with_fail_fast_off(record_to) do
|
|
@@ -55,7 +57,56 @@ module Mutineer
|
|
|
55
57
|
# early, so `end` needs a reported outcome for every planned example.
|
|
56
58
|
KillChannel.write_end(record_to) if record_to && KillFormatter.last&.saw_every_example?
|
|
57
59
|
|
|
58
|
-
status.zero? ? 0 :
|
|
60
|
+
status.zero? ? 0 : failure_code
|
|
61
|
+
ensure
|
|
62
|
+
FileOrder.rank = nil
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# The exit status of a failed run: 2 when no example failed and RSpec
|
|
66
|
+
# recorded an error outside examples, else 1.
|
|
67
|
+
#
|
|
68
|
+
# @api private
|
|
69
|
+
# @return [Integer] 1 or 2.
|
|
70
|
+
def self.failure_code
|
|
71
|
+
world = ::RSpec.world
|
|
72
|
+
outside_examples = world.respond_to?(:non_example_failure) && world.non_example_failure
|
|
73
|
+
outside_examples && ::RSpec.configuration.reporter.failed_examples.empty? ? 2 : 1
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Runs the top-level example groups in the order of `spec_files` (#203),
|
|
77
|
+
# after RSpec's own ordering, which then decides the order within a file.
|
|
78
|
+
#
|
|
79
|
+
# @api private
|
|
80
|
+
# @param spec_files [String, Array<String>] the files, in run order.
|
|
81
|
+
# @return [void]
|
|
82
|
+
def self.keep_file_order(spec_files)
|
|
83
|
+
FileOrder.rank = Array(spec_files).each_with_index.to_h { |f, i| [File.expand_path(f), i] }
|
|
84
|
+
::RSpec::Core::World.prepend(FileOrder) unless ::RSpec::Core::World <= FileOrder
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Prepended on `RSpec::Core::World`: a stable sort of the ordered
|
|
88
|
+
# top-level groups by their file's position. Does nothing without a rank.
|
|
89
|
+
module FileOrder
|
|
90
|
+
class << self
|
|
91
|
+
# Each spec file's position, keyed by absolute path, or nil.
|
|
92
|
+
#
|
|
93
|
+
# @return [Hash{String => Integer}, nil]
|
|
94
|
+
attr_accessor :rank
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# The groups RSpec ordered, sorted by file position.
|
|
98
|
+
#
|
|
99
|
+
# @return [Array<RSpec::Core::ExampleGroup>]
|
|
100
|
+
def ordered_example_groups
|
|
101
|
+
groups = super
|
|
102
|
+
rank = FileOrder.rank
|
|
103
|
+
return groups unless rank
|
|
104
|
+
|
|
105
|
+
groups.sort_by.with_index do |group, i|
|
|
106
|
+
file = group.metadata[:absolute_file_path] || File.expand_path(group.file_path)
|
|
107
|
+
[rank.fetch(file, rank.size), i]
|
|
108
|
+
end
|
|
109
|
+
end
|
|
59
110
|
end
|
|
60
111
|
|
|
61
112
|
# Runs the block with fail-fast forced off when `record_to` is set: a
|
|
@@ -7,8 +7,9 @@ module Mutineer
|
|
|
7
7
|
# Picks the test-framework runner.
|
|
8
8
|
#
|
|
9
9
|
# Each runner responds to
|
|
10
|
-
# `.run(files, stop_at_first_failure: false, record_to: nil) -> 0/1`
|
|
11
|
-
# (0 = all passed, 1 = any failure
|
|
10
|
+
# `.run(files, stop_at_first_failure: false, record_to: nil) -> 0/1/2`
|
|
11
|
+
# (0 = all passed, 1 = any failure, 2 = only an error outside tests, which
|
|
12
|
+
# the RSpec runner reports) and is called only inside a forked child.
|
|
12
13
|
# Only the mutant path passes `stop_at_first_failure: true`; a `--matrix`
|
|
13
14
|
# mutant run passes `record_to:` instead (a {KillChannel} pipe) and runs every
|
|
14
15
|
# test. The fork boundary silences stdout (see ChildStdout); the runners do not.
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "optparse"
|
|
5
|
+
require "yaml"
|
|
6
|
+
|
|
7
|
+
module Mutineer
|
|
8
|
+
# Prints `.mutineer.yml` `ignore:` entries for survivors in a JSON report.
|
|
9
|
+
# The command writes to stdout and does not edit files. The user pastes the
|
|
10
|
+
# lines under `ignore:`.
|
|
11
|
+
module Triage
|
|
12
|
+
# Usage line for a bad invocation.
|
|
13
|
+
USAGE = "Usage: mutineer triage REPORT.json --reason TEXT (--id ID ... | --all)"
|
|
14
|
+
|
|
15
|
+
# Reads argv, prints the entries, and returns the process exit code.
|
|
16
|
+
# 0 is success. 2 is a usage error.
|
|
17
|
+
#
|
|
18
|
+
# @param argv [Array<String>] arguments after the `triage` command.
|
|
19
|
+
# @param out [IO] where the entries go.
|
|
20
|
+
# @param err [IO] where usage errors go.
|
|
21
|
+
# @return [Integer] 0 or 2.
|
|
22
|
+
def self.run(argv, out: $stdout, err: $stderr)
|
|
23
|
+
reason = nil
|
|
24
|
+
ids = []
|
|
25
|
+
all = false
|
|
26
|
+
parser = OptionParser.new do |o|
|
|
27
|
+
o.banner = USAGE
|
|
28
|
+
o.on("--reason TEXT") { |text| reason = text }
|
|
29
|
+
o.on("--id ID") { |id| ids << id }
|
|
30
|
+
o.on("--all") { all = true }
|
|
31
|
+
end
|
|
32
|
+
begin
|
|
33
|
+
parser.parse!(argv)
|
|
34
|
+
rescue OptionParser::InvalidOption, OptionParser::MissingArgument => e
|
|
35
|
+
err.puts "mutineer: #{e.message}"
|
|
36
|
+
return 2
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
report = argv.shift
|
|
40
|
+
if argv.any?
|
|
41
|
+
err.puts "mutineer: triage takes one report file"
|
|
42
|
+
return 2
|
|
43
|
+
end
|
|
44
|
+
if report.nil? || report.empty?
|
|
45
|
+
err.puts "mutineer: triage requires a JSON report file"
|
|
46
|
+
return 2
|
|
47
|
+
end
|
|
48
|
+
text = reason.to_s.strip
|
|
49
|
+
if reason.nil? || text.empty?
|
|
50
|
+
err.puts "mutineer: triage requires --reason TEXT"
|
|
51
|
+
return 2
|
|
52
|
+
end
|
|
53
|
+
if all && !ids.empty?
|
|
54
|
+
err.puts "mutineer: triage takes --id or --all, not both"
|
|
55
|
+
return 2
|
|
56
|
+
end
|
|
57
|
+
if !all && ids.empty?
|
|
58
|
+
err.puts "mutineer: triage requires --id ID or --all"
|
|
59
|
+
return 2
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
survivors = survivor_ids(report, err)
|
|
63
|
+
return 2 if survivors.nil?
|
|
64
|
+
|
|
65
|
+
chosen = all ? survivors : ids
|
|
66
|
+
missing = chosen.reject { |id| survivors.include?(id) }
|
|
67
|
+
unless missing.empty?
|
|
68
|
+
err.puts "mutineer: triage id is not a survivor: #{missing.join(', ')}"
|
|
69
|
+
return 2
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
entries = chosen.uniq.map { |id| { "id" => id, "reason" => text } }
|
|
73
|
+
if entries.empty?
|
|
74
|
+
err.puts "mutineer: triage has nothing to paste under ignore:"
|
|
75
|
+
return 0
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
body = YAML.dump(entries).sub(/\A---\n/, "")
|
|
79
|
+
out.write(body.end_with?("\n") ? body : "#{body}\n")
|
|
80
|
+
0
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Survivor ids from a JSON report, in report order. Nil when the file
|
|
84
|
+
# cannot be used, after a message on `err`.
|
|
85
|
+
#
|
|
86
|
+
# @param path [String] report path.
|
|
87
|
+
# @param err [IO] usage errors.
|
|
88
|
+
# @return [Array<String>, nil]
|
|
89
|
+
def self.survivor_ids(path, err)
|
|
90
|
+
unless File.file?(path)
|
|
91
|
+
err.puts "mutineer: triage report not found: #{path}"
|
|
92
|
+
return nil
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
doc = JSON.parse(File.read(path))
|
|
96
|
+
rows = doc.is_a?(Hash) ? doc["survivors"] : nil
|
|
97
|
+
unless rows.is_a?(Array)
|
|
98
|
+
err.puts "mutineer: triage report has no survivors list"
|
|
99
|
+
return nil
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
rows.filter_map { |row| row["id"] if row.is_a?(Hash) && row["id"].is_a?(String) }
|
|
103
|
+
rescue JSON::ParserError => e
|
|
104
|
+
err.puts "mutineer: triage report is not JSON: #{e.message}"
|
|
105
|
+
nil
|
|
106
|
+
rescue SystemCallError
|
|
107
|
+
err.puts "mutineer: triage report cannot be read: #{path}"
|
|
108
|
+
nil
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
data/lib/mutineer/version.rb
CHANGED
data/lib/mutineer.rb
CHANGED
|
@@ -29,6 +29,7 @@ require_relative "mutineer/mutators/safe_navigation"
|
|
|
29
29
|
require_relative "mutineer/mutators/range_literal"
|
|
30
30
|
require_relative "mutineer/mutators/negation_removal"
|
|
31
31
|
require_relative "mutineer/mutators/chain_link"
|
|
32
|
+
require_relative "mutineer/mutators/call_to_receiver"
|
|
32
33
|
require_relative "mutineer/mutators/operand_removal"
|
|
33
34
|
require_relative "mutineer/mutators/array_literal"
|
|
34
35
|
require_relative "mutineer/mutators/condition_forcing"
|
|
@@ -36,10 +37,14 @@ require_relative "mutineer/mutators/operator_assignment"
|
|
|
36
37
|
require_relative "mutineer/mutator_registry"
|
|
37
38
|
require_relative "mutineer/worker_pool"
|
|
38
39
|
require_relative "mutineer/progress"
|
|
40
|
+
require_relative "mutineer/job_plan"
|
|
39
41
|
require_relative "mutineer/runner"
|
|
40
42
|
require_relative "mutineer/kill_matrix"
|
|
41
43
|
require_relative "mutineer/reporter"
|
|
42
44
|
require_relative "mutineer/baseline"
|
|
45
|
+
require_relative "mutineer/triage"
|
|
46
|
+
require_relative "mutineer/migrate"
|
|
47
|
+
require_relative "mutineer/init"
|
|
43
48
|
require_relative "mutineer/cli"
|
|
44
49
|
|
|
45
50
|
# Mutineer is the top-level namespace for the mutation-testing library.
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: mutineer
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.7.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- David Teren
|
|
@@ -54,8 +54,10 @@ files:
|
|
|
54
54
|
- lib/mutineer.rb
|
|
55
55
|
- lib/mutineer/baseline.rb
|
|
56
56
|
- lib/mutineer/changed_lines.rb
|
|
57
|
+
- lib/mutineer/child_coverage.rb
|
|
57
58
|
- lib/mutineer/child_stdout.rb
|
|
58
59
|
- lib/mutineer/cli.rb
|
|
60
|
+
- lib/mutineer/concern_blocks.rb
|
|
59
61
|
- lib/mutineer/config.rb
|
|
60
62
|
- lib/mutineer/coverage_map.rb
|
|
61
63
|
- lib/mutineer/daemon_backend.rb
|
|
@@ -63,9 +65,12 @@ files:
|
|
|
63
65
|
- lib/mutineer/daemon_server.rb
|
|
64
66
|
- lib/mutineer/external_backend.rb
|
|
65
67
|
- lib/mutineer/file_swap.rb
|
|
68
|
+
- lib/mutineer/init.rb
|
|
66
69
|
- lib/mutineer/isolation.rb
|
|
70
|
+
- lib/mutineer/job_plan.rb
|
|
67
71
|
- lib/mutineer/kill_channel.rb
|
|
68
72
|
- lib/mutineer/kill_matrix.rb
|
|
73
|
+
- lib/mutineer/migrate.rb
|
|
69
74
|
- lib/mutineer/minitest_integration.rb
|
|
70
75
|
- lib/mutineer/minitest_integration/kill_recorder.rb
|
|
71
76
|
- lib/mutineer/minitest_integration/outer_reporter.rb
|
|
@@ -78,6 +83,7 @@ files:
|
|
|
78
83
|
- lib/mutineer/mutators/base.rb
|
|
79
84
|
- lib/mutineer/mutators/boolean_connector.rb
|
|
80
85
|
- lib/mutineer/mutators/boolean_literal.rb
|
|
86
|
+
- lib/mutineer/mutators/call_to_receiver.rb
|
|
81
87
|
- lib/mutineer/mutators/chain_link.rb
|
|
82
88
|
- lib/mutineer/mutators/collection_method.rb
|
|
83
89
|
- lib/mutineer/mutators/comparison.rb
|
|
@@ -93,12 +99,15 @@ files:
|
|
|
93
99
|
- lib/mutineer/mutators/safe_navigation.rb
|
|
94
100
|
- lib/mutineer/mutators/statement_removal.rb
|
|
95
101
|
- lib/mutineer/mutators/string_literal.rb
|
|
102
|
+
- lib/mutineer/orphan_guard.rb
|
|
96
103
|
- lib/mutineer/pairing.rb
|
|
97
104
|
- lib/mutineer/parser.rb
|
|
98
105
|
- lib/mutineer/progress.rb
|
|
99
106
|
- lib/mutineer/project.rb
|
|
100
107
|
- lib/mutineer/project_path.rb
|
|
108
|
+
- lib/mutineer/rails_run_env.rb
|
|
101
109
|
- lib/mutineer/rails_worker_db.rb
|
|
110
|
+
- lib/mutineer/redaction.rb
|
|
102
111
|
- lib/mutineer/reporter.rb
|
|
103
112
|
- lib/mutineer/result.rb
|
|
104
113
|
- lib/mutineer/runner.rb
|
|
@@ -107,6 +116,7 @@ files:
|
|
|
107
116
|
- lib/mutineer/test_runners.rb
|
|
108
117
|
- lib/mutineer/test_runners/minitest.rb
|
|
109
118
|
- lib/mutineer/test_runners/rspec.rb
|
|
119
|
+
- lib/mutineer/triage.rb
|
|
110
120
|
- lib/mutineer/version.rb
|
|
111
121
|
- lib/mutineer/worker_pool.rb
|
|
112
122
|
homepage: https://github.com/davidteren/mutineer
|