mutant 0.9.12 → 0.10.2
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of mutant might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/bin/mutant +16 -11
- data/lib/mutant.rb +7 -4
- data/lib/mutant/bootstrap.rb +14 -1
- data/lib/mutant/cli.rb +9 -162
- data/lib/mutant/cli/command.rb +196 -0
- data/lib/mutant/cli/command/root.rb +13 -0
- data/lib/mutant/cli/command/run.rb +151 -0
- data/lib/mutant/cli/command/subscription.rb +54 -0
- data/lib/mutant/expression.rb +0 -1
- data/lib/mutant/license.rb +9 -35
- data/lib/mutant/license/subscription.rb +31 -9
- data/lib/mutant/license/subscription/commercial.rb +2 -4
- data/lib/mutant/license/subscription/opensource.rb +8 -7
- data/lib/mutant/matcher/config.rb +2 -0
- data/lib/mutant/meta/example.rb +16 -4
- data/lib/mutant/meta/example/dsl.rb +33 -16
- data/lib/mutant/meta/example/verification.rb +70 -28
- data/lib/mutant/mutator/node.rb +2 -2
- data/lib/mutant/mutator/node/{dstr.rb → dynamic_literal.rb} +7 -5
- data/lib/mutant/mutator/node/index.rb +4 -4
- data/lib/mutant/mutator/node/named_value/variable_assignment.rb +1 -1
- data/lib/mutant/mutator/node/op_asgn.rb +15 -1
- data/lib/mutant/mutator/node/send.rb +1 -1
- data/lib/mutant/mutator/node/send/attribute_assignment.rb +1 -0
- data/lib/mutant/reporter/cli/printer/isolation_result.rb +9 -3
- data/lib/mutant/selector/expression.rb +3 -1
- data/lib/mutant/subject/method/instance.rb +1 -1
- data/lib/mutant/test.rb +1 -1
- data/lib/mutant/version.rb +1 -1
- metadata +15 -13
- data/lib/mutant/minitest/coverage.rb +0 -53
- data/lib/mutant/mutator/node/dsym.rb +0 -22
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b5375b029dd29a085a6c3e3ba965135c47ed4276e6419d16874eff9b5e511f7f
|
4
|
+
data.tar.gz: a63b255ed6823d547eba19718c37ba93f827d11e1f18b7c33020d4ee80d103ee
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8da639cf0fe812ef14e9b07952f8456735b668a44e90a71793b514e41885134dd7368e17762bdbe981f3513e2fd54d70223d181c119fdbc2a579ee18b572d50f
|
7
|
+
data.tar.gz: 020a702b6b235fc9b5cef0e1d5dd6b6831cd003d34bdd4c6e92ca249cce661cf2ad4df16778ff0b8c7cc2212006c9b465ff30465ad71abe5cb6daff2ca33d6a6
|
data/bin/mutant
CHANGED
@@ -8,8 +8,14 @@ end
|
|
8
8
|
|
9
9
|
require 'mutant'
|
10
10
|
|
11
|
-
|
12
|
-
|
11
|
+
command = Mutant::CLI.parse(
|
12
|
+
arguments: ARGV,
|
13
|
+
config: Mutant::Config::DEFAULT,
|
14
|
+
world: Mutant::WORLD
|
15
|
+
)
|
16
|
+
|
17
|
+
status =
|
18
|
+
if command.zombie?
|
13
19
|
$stderr.puts('Running mutant zombified!')
|
14
20
|
Mutant::Zombifier.call(
|
15
21
|
namespace: :Zombie,
|
@@ -31,15 +37,14 @@ namespace =
|
|
31
37
|
concord
|
32
38
|
]
|
33
39
|
)
|
34
|
-
|
40
|
+
|
41
|
+
Zombie::Mutant::CLI.parse(
|
42
|
+
arguments: ARGV,
|
43
|
+
config: Zombie::Mutant::Config::DEFAULT,
|
44
|
+
world: Zombie::Mutant::WORLD
|
45
|
+
).call
|
35
46
|
else
|
36
|
-
|
47
|
+
command.call
|
37
48
|
end
|
38
49
|
|
39
|
-
Kernel.exit(
|
40
|
-
namespace::CLI.run(
|
41
|
-
namespace::WORLD,
|
42
|
-
namespace::Config::DEFAULT,
|
43
|
-
ARGV
|
44
|
-
)
|
45
|
-
)
|
50
|
+
Kernel.exit(status)
|
data/lib/mutant.rb
CHANGED
@@ -90,8 +90,7 @@ require 'mutant/mutator/node/arguments'
|
|
90
90
|
require 'mutant/mutator/node/begin'
|
91
91
|
require 'mutant/mutator/node/binary'
|
92
92
|
require 'mutant/mutator/node/const'
|
93
|
-
require 'mutant/mutator/node/
|
94
|
-
require 'mutant/mutator/node/dsym'
|
93
|
+
require 'mutant/mutator/node/dynamic_literal'
|
95
94
|
require 'mutant/mutator/node/kwbegin'
|
96
95
|
require 'mutant/mutator/node/named_value/access'
|
97
96
|
require 'mutant/mutator/node/named_value/constant_assignment'
|
@@ -166,6 +165,10 @@ require 'mutant/selector/expression'
|
|
166
165
|
require 'mutant/selector/null'
|
167
166
|
require 'mutant/config'
|
168
167
|
require 'mutant/cli'
|
168
|
+
require 'mutant/cli/command'
|
169
|
+
require 'mutant/cli/command/run'
|
170
|
+
require 'mutant/cli/command/subscription'
|
171
|
+
require 'mutant/cli/command/root'
|
169
172
|
require 'mutant/runner'
|
170
173
|
require 'mutant/runner/sink'
|
171
174
|
require 'mutant/result'
|
@@ -212,8 +215,8 @@ module Mutant
|
|
212
215
|
open3: Open3,
|
213
216
|
pathname: Pathname,
|
214
217
|
process: Process,
|
215
|
-
stderr:
|
216
|
-
stdout:
|
218
|
+
stderr: $stderr,
|
219
|
+
stdout: $stdout,
|
217
220
|
thread: Thread,
|
218
221
|
warnings: Warnings.new(Warning)
|
219
222
|
)
|
data/lib/mutant/bootstrap.rb
CHANGED
@@ -36,7 +36,7 @@ module Mutant
|
|
36
36
|
.tap(&method(:infect))
|
37
37
|
.with(matchable_scopes: matchable_scopes(world, config))
|
38
38
|
|
39
|
-
subjects = Matcher.from_config(env.config.matcher).call(env)
|
39
|
+
subjects = start_subject(env, Matcher.from_config(env.config.matcher).call(env))
|
40
40
|
|
41
41
|
Integration.setup(env).fmap do |integration|
|
42
42
|
env.with(
|
@@ -49,6 +49,19 @@ module Mutant
|
|
49
49
|
end
|
50
50
|
# rubocop:enable Metrics/MethodLength
|
51
51
|
|
52
|
+
def self.start_subject(env, subjects)
|
53
|
+
start_expressions = env.config.matcher.start_expressions
|
54
|
+
|
55
|
+
return subjects if start_expressions.empty?
|
56
|
+
|
57
|
+
subjects.drop_while do |subject|
|
58
|
+
start_expressions.none? do |expression|
|
59
|
+
expression.prefix?(subject.expression)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
private_class_method :start_subject
|
64
|
+
|
52
65
|
def self.infect(env)
|
53
66
|
config, world = env.config, env.world
|
54
67
|
|
data/lib/mutant/cli.rb
CHANGED
@@ -1,168 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Mutant
|
4
|
-
# Commandline
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
add_mutation_options
|
14
|
-
add_filter_options
|
15
|
-
add_debug_options
|
16
|
-
].freeze
|
17
|
-
|
18
|
-
private_constant(*constants(false))
|
19
|
-
|
20
|
-
# Run cli with arguments
|
21
|
-
#
|
22
|
-
# @param [World] world
|
23
|
-
# the outside world
|
24
|
-
#
|
25
|
-
# @param [Config] default_config
|
26
|
-
# the default config
|
27
|
-
#
|
28
|
-
# @param [Array<String>]
|
29
|
-
# the user provided arguments
|
30
|
-
#
|
31
|
-
# @return [Boolean]
|
32
|
-
#
|
33
|
-
# rubocop:disable Style/Semicolon
|
34
|
-
#
|
35
|
-
# ignore :reek:LongParameterList
|
36
|
-
def self.run(world, default_config, arguments)
|
37
|
-
License
|
38
|
-
.apply(world)
|
39
|
-
.bind { Config.load_config_file(world, default_config) }
|
40
|
-
.bind { |file_config| apply(world, file_config, arguments) }
|
41
|
-
.bind { |cli_config| Bootstrap.apply(world, cli_config) }
|
42
|
-
.bind(&Runner.method(:apply))
|
43
|
-
.from_right { |error| world.stderr.puts(error); return false }
|
44
|
-
.success?
|
45
|
-
end
|
46
|
-
# rubocop:enable Style/Semicolon
|
47
|
-
|
48
|
-
# Parse arguments into config
|
49
|
-
#
|
50
|
-
# @param [World] world
|
51
|
-
# @param [Config] config
|
52
|
-
# @param [Array<String>] arguments
|
53
|
-
#
|
54
|
-
# @return [Either<OptionParser::ParseError, Config>]
|
55
|
-
#
|
56
|
-
# ignore :reek:LongParameterList
|
57
|
-
def self.apply(world, config, arguments)
|
58
|
-
Either
|
59
|
-
.wrap_error(OptionParser::ParseError) { new(world, config).parse(arguments) }
|
60
|
-
.lmap(&:message)
|
61
|
-
end
|
62
|
-
|
63
|
-
# Local opt out of option parser defaults
|
64
|
-
class OptionParser < ::OptionParser
|
65
|
-
# Kill defaults added by option parser that
|
66
|
-
# inference with ours under mutation testing.
|
67
|
-
define_method(:add_officious) {}
|
68
|
-
end # OptionParser
|
69
|
-
|
70
|
-
# Parse the command-line options
|
71
|
-
#
|
72
|
-
# @param [Array<String>] arguments
|
73
|
-
# Command-line options and arguments to be parsed.
|
74
|
-
#
|
75
|
-
# @return [Config]
|
76
|
-
def parse(arguments)
|
77
|
-
opts = OptionParser.new do |builder|
|
78
|
-
builder.banner = 'usage: mutant [options] MATCH_EXPRESSION ...'
|
79
|
-
OPTIONS.each do |name|
|
80
|
-
__send__(name, builder)
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
84
|
-
parse_match_expressions(opts.parse!(arguments.dup))
|
85
|
-
|
86
|
-
config
|
87
|
-
end
|
88
|
-
|
89
|
-
private
|
90
|
-
|
91
|
-
def parse_match_expressions(expressions)
|
92
|
-
expressions.each do |expression|
|
93
|
-
add_matcher(:match_expressions, config.expression_parser.apply(expression).from_right)
|
94
|
-
end
|
95
|
-
end
|
96
|
-
|
97
|
-
# rubocop:disable Metrics/MethodLength
|
98
|
-
def add_environment_options(opts)
|
99
|
-
opts.separator('Environment:')
|
100
|
-
opts.on('--zombie', 'Run mutant zombified') do
|
101
|
-
with(zombie: true)
|
102
|
-
end
|
103
|
-
opts.on('-I', '--include DIRECTORY', 'Add DIRECTORY to $LOAD_PATH') do |directory|
|
104
|
-
add(:includes, directory)
|
105
|
-
end
|
106
|
-
opts.on('-r', '--require NAME', 'Require file with NAME') do |name|
|
107
|
-
add(:requires, name)
|
108
|
-
end
|
109
|
-
opts.on('-j', '--jobs NUMBER', 'Number of kill jobs. Defaults to number of processors.') do |number|
|
110
|
-
with(jobs: Integer(number))
|
111
|
-
end
|
112
|
-
end
|
113
|
-
# rubocop:enable Metrics/MethodLength
|
114
|
-
|
115
|
-
def add_mutation_options(opts)
|
116
|
-
opts.separator(nil)
|
117
|
-
opts.separator('Options:')
|
118
|
-
|
119
|
-
opts.on('--use INTEGRATION', 'Use INTEGRATION to kill mutations') do |name|
|
120
|
-
with(integration: name)
|
121
|
-
end
|
122
|
-
end
|
123
|
-
|
124
|
-
# rubocop:disable Metrics/MethodLength
|
125
|
-
def add_filter_options(opts)
|
126
|
-
opts.on('--ignore-subject EXPRESSION', 'Ignore subjects that match EXPRESSION as prefix') do |pattern|
|
127
|
-
add_matcher(:ignore_expressions, config.expression_parser.apply(pattern).from_right)
|
128
|
-
end
|
129
|
-
opts.on('--since REVISION', 'Only select subjects touched since REVISION') do |revision|
|
130
|
-
add_matcher(
|
131
|
-
:subject_filters,
|
132
|
-
Repository::SubjectFilter.new(
|
133
|
-
Repository::Diff.new(to: revision, world: world)
|
134
|
-
)
|
135
|
-
)
|
136
|
-
end
|
137
|
-
end
|
138
|
-
# rubocop:enable Metrics/MethodLength
|
139
|
-
|
140
|
-
# rubocop:disable Metrics/MethodLength
|
141
|
-
def add_debug_options(opts)
|
142
|
-
opts.on('--fail-fast', 'Fail fast') do
|
143
|
-
with(fail_fast: true)
|
144
|
-
end
|
145
|
-
opts.on('--version', 'Print mutants version') do
|
146
|
-
world.stdout.puts("mutant-#{VERSION}")
|
147
|
-
world.kernel.exit
|
148
|
-
end
|
149
|
-
opts.on_tail('-h', '--help', 'Show this message') do
|
150
|
-
world.stdout.puts(opts.to_s)
|
151
|
-
world.kernel.exit
|
152
|
-
end
|
153
|
-
end
|
154
|
-
# rubocop:enable Metrics/MethodLength
|
155
|
-
|
156
|
-
def with(attributes)
|
157
|
-
@config = config.with(attributes)
|
158
|
-
end
|
159
|
-
|
160
|
-
def add(attribute, value)
|
161
|
-
with(attribute => config.public_send(attribute) + [value])
|
162
|
-
end
|
163
|
-
|
164
|
-
def add_matcher(attribute, value)
|
165
|
-
with(matcher: config.matcher.add(attribute, value))
|
4
|
+
# Commandline interface
|
5
|
+
module CLI
|
6
|
+
# Parse command
|
7
|
+
#
|
8
|
+
# @return [Command]
|
9
|
+
def self.parse(world:, **attributes)
|
10
|
+
Command::Root
|
11
|
+
.parse(world: world, **attributes)
|
12
|
+
.from_right { |message| Command::FailParse.new(world, message) }
|
166
13
|
end
|
167
14
|
end # CLI
|
168
15
|
end # Mutant
|
@@ -0,0 +1,196 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mutant
|
4
|
+
module CLI
|
5
|
+
# rubocop:disable Metrics/ClassLength
|
6
|
+
class Command
|
7
|
+
include AbstractType, Anima.new(:world, :config, :main, :parent, :arguments)
|
8
|
+
|
9
|
+
include Equalizer.new(:parent, :arguments)
|
10
|
+
|
11
|
+
OPTIONS = [].freeze
|
12
|
+
SUBCOMMANDS = [].freeze
|
13
|
+
|
14
|
+
# Local opt out of option parser defaults
|
15
|
+
class OptionParser < ::OptionParser
|
16
|
+
# Kill defaults added by option parser that
|
17
|
+
# inference with ours under mutation testing.
|
18
|
+
define_method(:add_officious) {}
|
19
|
+
end # OptionParser
|
20
|
+
|
21
|
+
class FailParse < self
|
22
|
+
include Concord.new(:world, :message)
|
23
|
+
|
24
|
+
def call
|
25
|
+
world.stderr.puts(message)
|
26
|
+
false
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Parse command
|
31
|
+
#
|
32
|
+
# @return [Command]
|
33
|
+
def self.parse(**attributes)
|
34
|
+
new(main: nil, parent: nil, **attributes).__send__(:parse)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Command name
|
38
|
+
#
|
39
|
+
# @return [String]
|
40
|
+
def self.command_name
|
41
|
+
self::NAME
|
42
|
+
end
|
43
|
+
|
44
|
+
# Command short description
|
45
|
+
#
|
46
|
+
# @return [String]
|
47
|
+
def self.short_description
|
48
|
+
self::SHORT_DESCRIPTION
|
49
|
+
end
|
50
|
+
|
51
|
+
# Execute the command, invoke its side effects
|
52
|
+
#
|
53
|
+
# @return [Bool]
|
54
|
+
def call
|
55
|
+
main ? main.call : execute
|
56
|
+
end
|
57
|
+
|
58
|
+
# Commands full name
|
59
|
+
#
|
60
|
+
# @return [String]
|
61
|
+
def full_name
|
62
|
+
[*parent&.full_name, self.class.command_name].join(' ')
|
63
|
+
end
|
64
|
+
|
65
|
+
# Test if command needs to be executed in zombie environment
|
66
|
+
#
|
67
|
+
# @return [Bool]
|
68
|
+
def zombie?
|
69
|
+
instance_of?(Run)
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def subcommands
|
75
|
+
self.class::SUBCOMMANDS
|
76
|
+
end
|
77
|
+
|
78
|
+
def parser
|
79
|
+
OptionParser.new do |parser|
|
80
|
+
parser.banner = "usage: #{banner}"
|
81
|
+
|
82
|
+
add_summary(parser)
|
83
|
+
add_global_options(parser)
|
84
|
+
add_subcommands(parser)
|
85
|
+
|
86
|
+
self.class::OPTIONS.each do |method_name|
|
87
|
+
2.times { parser.separator(nil) }
|
88
|
+
__send__(method_name, parser)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def capture_main(&block)
|
94
|
+
@main = block
|
95
|
+
end
|
96
|
+
|
97
|
+
def banner
|
98
|
+
if subcommands.any?
|
99
|
+
"#{full_name} <#{subcommands.map(&:command_name).join('|')}> [options]"
|
100
|
+
else
|
101
|
+
"#{full_name} [options]"
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def parse
|
106
|
+
Either
|
107
|
+
.wrap_error(OptionParser::InvalidOption) { parser.order(arguments) }
|
108
|
+
.lmap { |error| "#{full_name}: #{error}" }
|
109
|
+
.bind(&method(:parse_remaining))
|
110
|
+
end
|
111
|
+
|
112
|
+
def add_summary(parser)
|
113
|
+
parser.separator(nil)
|
114
|
+
parser.separator("Summary: #{self.class.short_description}")
|
115
|
+
parser.separator(nil)
|
116
|
+
end
|
117
|
+
|
118
|
+
def add_global_options(parser)
|
119
|
+
parser.separator('Global Options:')
|
120
|
+
parser.separator(nil)
|
121
|
+
|
122
|
+
parser.on('--help', 'Print help') do
|
123
|
+
capture_main { world.stdout.puts(parser.help); true }
|
124
|
+
end
|
125
|
+
|
126
|
+
parser.on('--version', 'Print mutants version') do
|
127
|
+
capture_main { world.stdout.puts("mutant-#{VERSION}"); true }
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def add_subcommands(parser)
|
132
|
+
return unless subcommands.any?
|
133
|
+
|
134
|
+
parser.separator(nil)
|
135
|
+
parser.separator('Available subcommands:')
|
136
|
+
parser.separator(nil)
|
137
|
+
parser.separator(format_subcommands)
|
138
|
+
end
|
139
|
+
|
140
|
+
def parse_remaining(remaining)
|
141
|
+
return Either::Right.new(self) if main
|
142
|
+
|
143
|
+
if subcommands.any?
|
144
|
+
parse_subcommand(remaining)
|
145
|
+
else
|
146
|
+
parse_remaining_arguments(remaining)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def parse_remaining_arguments(remaining)
|
151
|
+
if remaining.any?
|
152
|
+
Either::Left.new("#{full_name}: Does not expect extra arguments")
|
153
|
+
else
|
154
|
+
Either::Right.new(self)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def parse_subcommand(arguments)
|
159
|
+
command_name, *arguments = arguments
|
160
|
+
|
161
|
+
if command_name.nil?
|
162
|
+
Either::Left.new(
|
163
|
+
"Missing required subcommand!\n\n#{parser}"
|
164
|
+
)
|
165
|
+
else
|
166
|
+
find_command(command_name).bind do |command|
|
167
|
+
command.parse(**to_h, parent: self, arguments: arguments)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def format_subcommands
|
173
|
+
commands = subcommands.map do |subcommand|
|
174
|
+
[subcommand.command_name, subcommand.short_description]
|
175
|
+
end.to_h
|
176
|
+
|
177
|
+
width = commands.each_key.map(&:length).max
|
178
|
+
|
179
|
+
commands.each_key.map do |name|
|
180
|
+
'%-*s - %s' % [width, name, commands.fetch(name)] # rubocop:disable Style/FormatStringToken
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
def find_command(name)
|
185
|
+
subcommand = subcommands.detect { |command| command.command_name.eql?(name) }
|
186
|
+
|
187
|
+
if subcommand
|
188
|
+
Either::Right.new(subcommand)
|
189
|
+
else
|
190
|
+
Either::Left.new("#{full_name}: Cannot find subcommand #{name.inspect}")
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end # Command
|
194
|
+
# rubocop:enable Metrics/ClassLength
|
195
|
+
end # CLI
|
196
|
+
end # Mutant
|