mutant 0.9.14 → 0.10.6
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/bin/mutant +14 -11
- data/lib/mutant.rb +9 -4
- data/lib/mutant/cli.rb +8 -167
- data/lib/mutant/cli/command.rb +198 -0
- data/lib/mutant/cli/command/root.rb +13 -0
- data/lib/mutant/cli/command/run.rb +161 -0
- data/lib/mutant/cli/command/subscription.rb +54 -0
- data/lib/mutant/config.rb +28 -52
- 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 +13 -0
- data/lib/mutant/mutator/node/named_value/variable_assignment.rb +1 -1
- data/lib/mutant/reporter/cli/printer/config.rb +2 -2
- data/lib/mutant/selector/expression.rb +3 -1
- data/lib/mutant/test.rb +1 -1
- data/lib/mutant/version.rb +1 -1
- data/lib/mutant/world.rb +52 -0
- metadata +18 -14
- data/lib/mutant/minitest/coverage.rb +0 -53
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1b5a7d1620b6792fa1cef90ead01124cf50287a165a0bdd415fbc9aab9ecfde8
|
4
|
+
data.tar.gz: 758609938988d4d8de30a6f95a47c603fc4d5365ba98ad1a113a6de11f8e074f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3ecd6aea62e2d01cb68cc9c9c651190305459b7f1b796f4360ce872ce0dbb1871cb40821744d1c09a029a80f39a93cbda9a242591ceae405e47ed971d3d861f9
|
7
|
+
data.tar.gz: '09e132e435163308309c0185abdd39ea9b6be0d2bd0efa0956049c366f0b521df866a3b7fd04d2e13550bd4f2b09993c659530f86b00142adff69ebc14af3a16'
|
data/bin/mutant
CHANGED
@@ -8,8 +8,13 @@ end
|
|
8
8
|
|
9
9
|
require 'mutant'
|
10
10
|
|
11
|
-
|
12
|
-
|
11
|
+
command = Mutant::CLI.parse(
|
12
|
+
arguments: ARGV,
|
13
|
+
world: Mutant::WORLD
|
14
|
+
)
|
15
|
+
|
16
|
+
status =
|
17
|
+
if command.zombie?
|
13
18
|
$stderr.puts('Running mutant zombified!')
|
14
19
|
Mutant::Zombifier.call(
|
15
20
|
namespace: :Zombie,
|
@@ -31,15 +36,13 @@ namespace =
|
|
31
36
|
concord
|
32
37
|
]
|
33
38
|
)
|
34
|
-
|
39
|
+
|
40
|
+
Zombie::Mutant::CLI.parse(
|
41
|
+
arguments: ARGV,
|
42
|
+
world: Zombie::Mutant::WORLD
|
43
|
+
).call
|
35
44
|
else
|
36
|
-
|
45
|
+
command.call
|
37
46
|
end
|
38
47
|
|
39
|
-
Kernel.exit(
|
40
|
-
namespace::CLI.run(
|
41
|
-
namespace::WORLD,
|
42
|
-
namespace::Config::DEFAULT,
|
43
|
-
ARGV
|
44
|
-
)
|
45
|
-
)
|
48
|
+
Kernel.exit(status)
|
data/lib/mutant.rb
CHANGED
@@ -163,8 +163,13 @@ require 'mutant/integration/null'
|
|
163
163
|
require 'mutant/selector'
|
164
164
|
require 'mutant/selector/expression'
|
165
165
|
require 'mutant/selector/null'
|
166
|
+
require 'mutant/world'
|
166
167
|
require 'mutant/config'
|
167
168
|
require 'mutant/cli'
|
169
|
+
require 'mutant/cli/command'
|
170
|
+
require 'mutant/cli/command/run'
|
171
|
+
require 'mutant/cli/command/subscription'
|
172
|
+
require 'mutant/cli/command/root'
|
168
173
|
require 'mutant/runner'
|
169
174
|
require 'mutant/runner/sink'
|
170
175
|
require 'mutant/result'
|
@@ -211,8 +216,8 @@ module Mutant
|
|
211
216
|
open3: Open3,
|
212
217
|
pathname: Pathname,
|
213
218
|
process: Process,
|
214
|
-
stderr:
|
215
|
-
stdout:
|
219
|
+
stderr: $stderr,
|
220
|
+
stdout: $stdout,
|
216
221
|
thread: Thread,
|
217
222
|
warnings: Warnings.new(Warning)
|
218
223
|
)
|
@@ -228,9 +233,9 @@ module Mutant
|
|
228
233
|
]),
|
229
234
|
fail_fast: false,
|
230
235
|
includes: EMPTY_ARRAY,
|
231
|
-
integration:
|
236
|
+
integration: nil,
|
232
237
|
isolation: Mutant::Isolation::Fork.new(WORLD),
|
233
|
-
jobs:
|
238
|
+
jobs: nil,
|
234
239
|
matcher: Matcher::Config::DEFAULT,
|
235
240
|
reporter: Reporter::CLI.build(WORLD.stdout),
|
236
241
|
requires: EMPTY_ARRAY,
|
data/lib/mutant/cli.rb
CHANGED
@@ -1,174 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Mutant
|
4
|
-
# Commandline
|
5
|
-
|
6
|
-
|
7
|
-
class CLI
|
8
|
-
include Concord.new(:world, :config)
|
9
|
-
|
10
|
-
private_class_method :new
|
11
|
-
|
12
|
-
OPTIONS =
|
13
|
-
%i[
|
14
|
-
add_environment_options
|
15
|
-
add_mutation_options
|
16
|
-
add_filter_options
|
17
|
-
add_debug_options
|
18
|
-
].freeze
|
19
|
-
|
20
|
-
private_constant(*constants(false))
|
21
|
-
|
22
|
-
# Run cli with arguments
|
23
|
-
#
|
24
|
-
# @param [World] world
|
25
|
-
# the outside world
|
26
|
-
#
|
27
|
-
# @param [Config] default_config
|
28
|
-
# the default config
|
29
|
-
#
|
30
|
-
# @param [Array<String>]
|
31
|
-
# the user provided arguments
|
32
|
-
#
|
33
|
-
# @return [Boolean]
|
34
|
-
#
|
35
|
-
# rubocop:disable Style/Semicolon
|
36
|
-
#
|
37
|
-
# ignore :reek:LongParameterList
|
38
|
-
def self.run(world, default_config, arguments)
|
39
|
-
License
|
40
|
-
.apply(world)
|
41
|
-
.bind { Config.load_config_file(world, default_config) }
|
42
|
-
.bind { |file_config| apply(world, file_config, arguments) }
|
43
|
-
.bind { |cli_config| Bootstrap.apply(world, cli_config) }
|
44
|
-
.bind(&Runner.method(:apply))
|
45
|
-
.from_right { |error| world.stderr.puts(error); return false }
|
46
|
-
.success?
|
47
|
-
end
|
48
|
-
# rubocop:enable Style/Semicolon
|
49
|
-
|
50
|
-
# Parse arguments into config
|
51
|
-
#
|
52
|
-
# @param [World] world
|
53
|
-
# @param [Config] config
|
54
|
-
# @param [Array<String>] arguments
|
55
|
-
#
|
56
|
-
# @return [Either<OptionParser::ParseError, Config>]
|
57
|
-
#
|
58
|
-
# ignore :reek:LongParameterList
|
59
|
-
def self.apply(world, config, arguments)
|
60
|
-
Either
|
61
|
-
.wrap_error(OptionParser::ParseError) { new(world, config).parse(arguments) }
|
62
|
-
.lmap(&:message)
|
63
|
-
end
|
64
|
-
|
65
|
-
# Local opt out of option parser defaults
|
66
|
-
class OptionParser < ::OptionParser
|
67
|
-
# Kill defaults added by option parser that
|
68
|
-
# inference with ours under mutation testing.
|
69
|
-
define_method(:add_officious) {}
|
70
|
-
end # OptionParser
|
71
|
-
|
72
|
-
# Parse the command-line options
|
4
|
+
# Commandline interface
|
5
|
+
module CLI
|
6
|
+
# Parse command
|
73
7
|
#
|
74
|
-
# @
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
opts = OptionParser.new do |builder|
|
80
|
-
builder.banner = 'usage: mutant [options] MATCH_EXPRESSION ...'
|
81
|
-
OPTIONS.each do |name|
|
82
|
-
__send__(name, builder)
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
parse_match_expressions(opts.parse!(arguments.dup))
|
87
|
-
|
88
|
-
config
|
89
|
-
end
|
90
|
-
|
91
|
-
private
|
92
|
-
|
93
|
-
def parse_match_expressions(expressions)
|
94
|
-
expressions.each do |expression|
|
95
|
-
add_matcher(:match_expressions, config.expression_parser.apply(expression).from_right)
|
96
|
-
end
|
97
|
-
end
|
98
|
-
|
99
|
-
# rubocop:disable Metrics/MethodLength
|
100
|
-
def add_environment_options(opts)
|
101
|
-
opts.separator('Environment:')
|
102
|
-
opts.on('--zombie', 'Run mutant zombified') do
|
103
|
-
with(zombie: true)
|
104
|
-
end
|
105
|
-
opts.on('-I', '--include DIRECTORY', 'Add DIRECTORY to $LOAD_PATH') do |directory|
|
106
|
-
add(:includes, directory)
|
107
|
-
end
|
108
|
-
opts.on('-r', '--require NAME', 'Require file with NAME') do |name|
|
109
|
-
add(:requires, name)
|
110
|
-
end
|
111
|
-
opts.on('-j', '--jobs NUMBER', 'Number of kill jobs. Defaults to number of processors.') do |number|
|
112
|
-
with(jobs: Integer(number))
|
113
|
-
end
|
114
|
-
end
|
115
|
-
# rubocop:enable Metrics/MethodLength
|
116
|
-
|
117
|
-
def add_mutation_options(opts)
|
118
|
-
opts.separator(nil)
|
119
|
-
opts.separator('Options:')
|
120
|
-
|
121
|
-
opts.on('--use INTEGRATION', 'Use INTEGRATION to kill mutations') do |name|
|
122
|
-
with(integration: name)
|
123
|
-
end
|
124
|
-
end
|
125
|
-
|
126
|
-
# rubocop:disable Metrics/MethodLength
|
127
|
-
def add_filter_options(opts)
|
128
|
-
opts.on('--ignore-subject EXPRESSION', 'Ignore subjects that match EXPRESSION as prefix') do |pattern|
|
129
|
-
add_matcher(:ignore_expressions, config.expression_parser.apply(pattern).from_right)
|
130
|
-
end
|
131
|
-
opts.on('--start-subject EXPRESSION', 'Start mutation testing at a specific subject') do |pattern|
|
132
|
-
add_matcher(:start_expressions, config.expression_parser.apply(pattern).from_right)
|
133
|
-
end
|
134
|
-
opts.on('--since REVISION', 'Only select subjects touched since REVISION') do |revision|
|
135
|
-
add_matcher(
|
136
|
-
:subject_filters,
|
137
|
-
Repository::SubjectFilter.new(
|
138
|
-
Repository::Diff.new(to: revision, world: world)
|
139
|
-
)
|
140
|
-
)
|
141
|
-
end
|
142
|
-
end
|
143
|
-
# rubocop:enable Metrics/MethodLength
|
144
|
-
|
145
|
-
# rubocop:disable Metrics/MethodLength
|
146
|
-
def add_debug_options(opts)
|
147
|
-
opts.on('--fail-fast', 'Fail fast') do
|
148
|
-
with(fail_fast: true)
|
149
|
-
end
|
150
|
-
opts.on('--version', 'Print mutants version') do
|
151
|
-
world.stdout.puts("mutant-#{VERSION}")
|
152
|
-
world.kernel.exit
|
153
|
-
end
|
154
|
-
opts.on_tail('-h', '--help', 'Show this message') do
|
155
|
-
world.stdout.puts(opts.to_s)
|
156
|
-
world.kernel.exit
|
157
|
-
end
|
158
|
-
end
|
159
|
-
# rubocop:enable Metrics/MethodLength
|
160
|
-
|
161
|
-
def with(attributes)
|
162
|
-
@config = config.with(attributes)
|
163
|
-
end
|
164
|
-
|
165
|
-
def add(attribute, value)
|
166
|
-
with(attribute => config.public_send(attribute) + [value])
|
167
|
-
end
|
168
|
-
|
169
|
-
def add_matcher(attribute, value)
|
170
|
-
with(matcher: config.matcher.add(attribute, value))
|
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) }
|
171
13
|
end
|
172
14
|
end # CLI
|
173
|
-
# rubocop:enable Metrics/ClassLength
|
174
15
|
end # Mutant
|
@@ -0,0 +1,198 @@
|
|
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, :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(&method(:with_help))
|
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(with_help('Missing required subcommand!'))
|
163
|
+
else
|
164
|
+
find_command(command_name).bind do |command|
|
165
|
+
command.parse(**to_h, parent: self, arguments: arguments)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def format_subcommands
|
171
|
+
commands = subcommands.map do |subcommand|
|
172
|
+
[subcommand.command_name, subcommand.short_description]
|
173
|
+
end.to_h
|
174
|
+
|
175
|
+
width = commands.each_key.map(&:length).max
|
176
|
+
|
177
|
+
commands.each_key.map do |name|
|
178
|
+
'%-*s - %s' % [width, name, commands.fetch(name)] # rubocop:disable Style/FormatStringToken
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def find_command(name)
|
183
|
+
subcommand = subcommands.detect { |command| command.command_name.eql?(name) }
|
184
|
+
|
185
|
+
if subcommand
|
186
|
+
Either::Right.new(subcommand)
|
187
|
+
else
|
188
|
+
Either::Left.new(with_help("Cannot find subcommand #{name.inspect}"))
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
def with_help(message)
|
193
|
+
"#{full_name}: #{message}\n\n#{parser}"
|
194
|
+
end
|
195
|
+
end # Command
|
196
|
+
# rubocop:enable Metrics/ClassLength
|
197
|
+
end # CLI
|
198
|
+
end # Mutant
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mutant
|
4
|
+
module CLI
|
5
|
+
class Command
|
6
|
+
class Root < self
|
7
|
+
SUBCOMMANDS = [Run, Subscription].freeze
|
8
|
+
SHORT_DESCRIPTION = 'mutation testing engine main command'
|
9
|
+
NAME = 'mutant'
|
10
|
+
end
|
11
|
+
end # Command
|
12
|
+
end # CLI
|
13
|
+
end # Mutant
|
@@ -0,0 +1,161 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mutant
|
4
|
+
module CLI
|
5
|
+
class Command
|
6
|
+
# rubocop:disable Metrics/ClassLength
|
7
|
+
class Run < self
|
8
|
+
NAME = 'run'
|
9
|
+
SHORT_DESCRIPTION = 'Run code analysis'
|
10
|
+
|
11
|
+
OPTIONS =
|
12
|
+
%i[
|
13
|
+
add_environment_options
|
14
|
+
add_runner_options
|
15
|
+
add_integration_options
|
16
|
+
add_matcher_options
|
17
|
+
].freeze
|
18
|
+
|
19
|
+
SLEEP = 40
|
20
|
+
|
21
|
+
UNLICENSED = <<~MESSAGE.lines.freeze
|
22
|
+
Soft fail, continuing in #{SLEEP} seconds
|
23
|
+
Next major version will enforce the license
|
24
|
+
See https://github.com/mbj/mutant#licensing
|
25
|
+
MESSAGE
|
26
|
+
|
27
|
+
# Test if command needs to be executed in zombie environment
|
28
|
+
#
|
29
|
+
# @return [Bool]
|
30
|
+
def zombie?
|
31
|
+
@config.zombie
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def initialize(attributes)
|
37
|
+
super(attributes)
|
38
|
+
@config = Config::DEFAULT
|
39
|
+
end
|
40
|
+
|
41
|
+
def execute
|
42
|
+
soft_fail(License.apply(world))
|
43
|
+
.bind { Config.load_config_file(world) }
|
44
|
+
.fmap(&method(:expand))
|
45
|
+
.bind { Bootstrap.apply(world, @config) }
|
46
|
+
.bind(&Runner.public_method(:apply))
|
47
|
+
.from_right { |error| world.stderr.puts(error); return false }
|
48
|
+
.success?
|
49
|
+
end
|
50
|
+
|
51
|
+
def expand(file_config)
|
52
|
+
@config = Config.env.merge(file_config).merge(@config)
|
53
|
+
end
|
54
|
+
|
55
|
+
def soft_fail(result)
|
56
|
+
result.either(
|
57
|
+
lambda do |message|
|
58
|
+
stderr = world.stderr
|
59
|
+
stderr.puts(message)
|
60
|
+
UNLICENSED.each { |line| stderr.puts(unlicensed(line)) }
|
61
|
+
world.kernel.sleep(SLEEP)
|
62
|
+
Either::Right.new(nil)
|
63
|
+
end,
|
64
|
+
->(_subscription) { Either::Right.new(nil) }
|
65
|
+
)
|
66
|
+
end
|
67
|
+
|
68
|
+
def unlicensed(message)
|
69
|
+
"[Mutant-License-Error]: #{message}"
|
70
|
+
end
|
71
|
+
|
72
|
+
def parse_remaining_arguments(arguments)
|
73
|
+
traverse(@config.expression_parser.public_method(:apply), arguments)
|
74
|
+
.fmap do |match_expressions|
|
75
|
+
matcher(match_expressions: match_expressions)
|
76
|
+
self
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def traverse(action, values)
|
81
|
+
Either::Right.new(
|
82
|
+
values.map do |value|
|
83
|
+
action.call(value).from_right do |error|
|
84
|
+
return Either::Left.new(error)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
)
|
88
|
+
end
|
89
|
+
|
90
|
+
def set(**attributes)
|
91
|
+
@config = @config.with(attributes)
|
92
|
+
end
|
93
|
+
|
94
|
+
def matcher(**attributes)
|
95
|
+
set(matcher: @config.matcher.with(attributes))
|
96
|
+
end
|
97
|
+
|
98
|
+
def add(attribute, value)
|
99
|
+
set(attribute => @config.public_send(attribute) + [value])
|
100
|
+
end
|
101
|
+
|
102
|
+
def add_matcher(attribute, value)
|
103
|
+
set(matcher: @config.matcher.add(attribute, value))
|
104
|
+
end
|
105
|
+
|
106
|
+
def add_environment_options(parser)
|
107
|
+
parser.separator('Environment:')
|
108
|
+
parser.on('--zombie', 'Run mutant zombified') do
|
109
|
+
set(zombie: true)
|
110
|
+
end
|
111
|
+
parser.on('-I', '--include DIRECTORY', 'Add DIRECTORY to $LOAD_PATH') do |directory|
|
112
|
+
add(:includes, directory)
|
113
|
+
end
|
114
|
+
parser.on('-r', '--require NAME', 'Require file with NAME') do |name|
|
115
|
+
add(:requires, name)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def add_integration_options(parser)
|
120
|
+
parser.separator('Integration:')
|
121
|
+
|
122
|
+
parser.on('--use INTEGRATION', 'Use INTEGRATION to kill mutations') do |name|
|
123
|
+
set(integration: name)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
# rubocop:disable Metrics/MethodLength
|
128
|
+
def add_matcher_options(parser)
|
129
|
+
parser.separator('Matcher:')
|
130
|
+
|
131
|
+
parser.on('--ignore-subject EXPRESSION', 'Ignore subjects that match EXPRESSION as prefix') do |pattern|
|
132
|
+
add_matcher(:ignore_expressions, @config.expression_parser.apply(pattern).from_right)
|
133
|
+
end
|
134
|
+
parser.on('--start-subject EXPRESSION', 'Start mutation testing at a specific subject') do |pattern|
|
135
|
+
add_matcher(:start_expressions, @config.expression_parser.apply(pattern).from_right)
|
136
|
+
end
|
137
|
+
parser.on('--since REVISION', 'Only select subjects touched since REVISION') do |revision|
|
138
|
+
add_matcher(
|
139
|
+
:subject_filters,
|
140
|
+
Repository::SubjectFilter.new(
|
141
|
+
Repository::Diff.new(to: revision, world: world)
|
142
|
+
)
|
143
|
+
)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def add_runner_options(parser)
|
148
|
+
parser.separator('Runner:')
|
149
|
+
|
150
|
+
parser.on('--fail-fast', 'Fail fast') do
|
151
|
+
set(fail_fast: true)
|
152
|
+
end
|
153
|
+
parser.on('-j', '--jobs NUMBER', 'Number of kill jobs. Defaults to number of processors.') do |number|
|
154
|
+
set(jobs: Integer(number))
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end # Run
|
158
|
+
# rubocop:enable Metrics/ClassLength
|
159
|
+
end # Command
|
160
|
+
end # CLI
|
161
|
+
end # Mutant
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mutant
|
4
|
+
module CLI
|
5
|
+
class Command
|
6
|
+
class Subscription < self
|
7
|
+
NAME = 'subscription'
|
8
|
+
SHORT_DESCRIPTION = 'Subscription subcommands'
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def license
|
13
|
+
License.apply(world)
|
14
|
+
end
|
15
|
+
|
16
|
+
class Test < self
|
17
|
+
NAME = 'test'
|
18
|
+
SUBCOMMANDS = [].freeze
|
19
|
+
SHORT_DESCRIPTION = 'Silently validates subscription, exits accordingly'
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def execute
|
24
|
+
license.right?
|
25
|
+
end
|
26
|
+
end # Test
|
27
|
+
|
28
|
+
class Show < self
|
29
|
+
NAME = 'show'
|
30
|
+
SUBCOMMANDS = [].freeze
|
31
|
+
SHORT_DESCRIPTION = 'Show subscription status'
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def execute
|
36
|
+
license.either(method(:unlicensed), method(:licensed))
|
37
|
+
end
|
38
|
+
|
39
|
+
def licensed(subscription)
|
40
|
+
world.stdout.puts(subscription.description)
|
41
|
+
true
|
42
|
+
end
|
43
|
+
|
44
|
+
def unlicensed(message)
|
45
|
+
world.stderr.puts(message)
|
46
|
+
false
|
47
|
+
end
|
48
|
+
end # Show
|
49
|
+
|
50
|
+
SUBCOMMANDS = [Show, Test].freeze
|
51
|
+
end # Subscription
|
52
|
+
end # Command
|
53
|
+
end # CLI
|
54
|
+
end # Mutant
|
data/lib/mutant/config.rb
CHANGED
@@ -1,55 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Mutant
|
4
|
-
# The outer world IO objects mutant does interact with
|
5
|
-
class World
|
6
|
-
include Adamantium::Flat, Anima.new(
|
7
|
-
:condition_variable,
|
8
|
-
:gem,
|
9
|
-
:gem_method,
|
10
|
-
:io,
|
11
|
-
:json,
|
12
|
-
:kernel,
|
13
|
-
:load_path,
|
14
|
-
:marshal,
|
15
|
-
:mutex,
|
16
|
-
:object_space,
|
17
|
-
:open3,
|
18
|
-
:pathname,
|
19
|
-
:process,
|
20
|
-
:stderr,
|
21
|
-
:stdout,
|
22
|
-
:thread,
|
23
|
-
:warnings
|
24
|
-
)
|
25
|
-
|
26
|
-
INSPECT = '#<Mutant::World>'
|
27
|
-
|
28
|
-
private_constant(*constants(false))
|
29
|
-
|
30
|
-
# Object inspection
|
31
|
-
#
|
32
|
-
# @return [String]
|
33
|
-
def inspect
|
34
|
-
INSPECT
|
35
|
-
end
|
36
|
-
|
37
|
-
# Capture stdout of a command
|
38
|
-
#
|
39
|
-
# @param [Array<String>] command
|
40
|
-
#
|
41
|
-
# @return [Either<String,String>]
|
42
|
-
def capture_stdout(command)
|
43
|
-
stdout, status = open3.capture2(*command, binmode: true)
|
44
|
-
|
45
|
-
if status.success?
|
46
|
-
Either::Right.new(stdout)
|
47
|
-
else
|
48
|
-
Either::Left.new("Command #{command} failed!")
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end # World
|
52
|
-
|
53
4
|
# Standalone configuration of a mutant execution.
|
54
5
|
#
|
55
6
|
# Does not reference any "external" volatile state. The configuration applied
|
@@ -106,6 +57,23 @@ module Mutant
|
|
106
57
|
mutant.yml
|
107
58
|
].freeze
|
108
59
|
|
60
|
+
# Merge with other config
|
61
|
+
#
|
62
|
+
# @param [Config] other
|
63
|
+
#
|
64
|
+
# @return [Config]
|
65
|
+
def merge(other)
|
66
|
+
other.with(
|
67
|
+
fail_fast: fail_fast || other.fail_fast,
|
68
|
+
includes: includes + other.includes,
|
69
|
+
jobs: other.jobs || jobs,
|
70
|
+
integration: other.integration || integration,
|
71
|
+
matcher: matcher.merge(other.matcher),
|
72
|
+
requires: requires + other.requires,
|
73
|
+
zombie: zombie || other.zombie
|
74
|
+
)
|
75
|
+
end
|
76
|
+
|
109
77
|
private_constant(*constants(false))
|
110
78
|
|
111
79
|
# Load config file
|
@@ -114,11 +82,12 @@ module Mutant
|
|
114
82
|
# @param [Config] config
|
115
83
|
#
|
116
84
|
# @return [Either<String,Config>]
|
117
|
-
def self.load_config_file(world
|
118
|
-
|
85
|
+
def self.load_config_file(world)
|
86
|
+
config = DEFAULT
|
87
|
+
files = CANDIDATES.map(&world.pathname.public_method(:new)).select(&:readable?)
|
119
88
|
|
120
89
|
if files.one?
|
121
|
-
load_contents(files.first).fmap(&config.
|
90
|
+
load_contents(files.first).fmap(&config.public_method(:with))
|
122
91
|
elsif files.empty?
|
123
92
|
Either::Right.new(config)
|
124
93
|
else
|
@@ -133,5 +102,12 @@ module Mutant
|
|
133
102
|
.lmap(&:compact_message)
|
134
103
|
end
|
135
104
|
private_class_method :load_contents
|
105
|
+
|
106
|
+
# The configuration from the environment
|
107
|
+
#
|
108
|
+
# @return [Config]
|
109
|
+
def self.env
|
110
|
+
DEFAULT.with(jobs: Etc.nprocessors)
|
111
|
+
end
|
136
112
|
end # Config
|
137
113
|
end # Mutant
|
data/lib/mutant/expression.rb
CHANGED
data/lib/mutant/license.rb
CHANGED
@@ -4,53 +4,27 @@ module Mutant
|
|
4
4
|
module License
|
5
5
|
NAME = 'mutant-license'
|
6
6
|
VERSION = '~> 0.1.0'
|
7
|
-
SLEEP = 40
|
8
|
-
|
9
|
-
UNLICENSED =
|
10
|
-
IceNine.deep_freeze(
|
11
|
-
[
|
12
|
-
"Soft fail, continuing in #{SLEEP} seconds",
|
13
|
-
'Next major version will enforce the license',
|
14
|
-
'See https://github.com/mbj/mutant#licensing'
|
15
|
-
]
|
16
|
-
)
|
17
7
|
|
8
|
+
# Load license
|
9
|
+
#
|
10
|
+
# @param [World] world
|
11
|
+
#
|
12
|
+
# @return [Either<String,Subscription>]
|
13
|
+
#
|
14
|
+
# @api private
|
18
15
|
def self.apply(world)
|
19
|
-
soft_fail(world, license_result(world))
|
20
|
-
end
|
21
|
-
|
22
|
-
def self.license_result(world)
|
23
16
|
load_mutant_license(world)
|
24
17
|
.fmap { license_path(world) }
|
25
|
-
.
|
26
|
-
.bind { |sub| sub.apply(world) }
|
18
|
+
.bind { |path| Subscription.load(world, world.json.load(path)) }
|
27
19
|
end
|
28
|
-
private_class_method :license_result
|
29
|
-
|
30
|
-
# ignore :reek:NestedIterators
|
31
|
-
def self.soft_fail(world, result)
|
32
|
-
result.lmap do |message|
|
33
|
-
stderr = world.stderr
|
34
|
-
stderr.puts(message)
|
35
|
-
UNLICENSED.each { |line| stderr.puts(unlicensed(line)) }
|
36
|
-
world.kernel.sleep(SLEEP)
|
37
|
-
end
|
38
|
-
|
39
|
-
Either::Right.new(true)
|
40
|
-
end
|
41
|
-
private_class_method :soft_fail
|
42
20
|
|
43
21
|
def self.load_mutant_license(world)
|
44
22
|
Either
|
45
23
|
.wrap_error(LoadError) { world.gem_method.call(NAME, VERSION) }
|
46
24
|
.lmap(&:message)
|
47
25
|
.lmap(&method(:check_for_rubygems_mutant_license))
|
48
|
-
.lmap(&method(:unlicensed))
|
49
|
-
end
|
50
|
-
|
51
|
-
def self.unlicensed(message)
|
52
|
-
"[Mutant-License-Error]: #{message}"
|
53
26
|
end
|
27
|
+
private_class_method :load_mutant_license
|
54
28
|
|
55
29
|
def self.check_for_rubygems_mutant_license(message)
|
56
30
|
if message.include?('already activated mutant-license-0.0.0')
|
@@ -3,8 +3,15 @@
|
|
3
3
|
module Mutant
|
4
4
|
module License
|
5
5
|
class Subscription
|
6
|
+
include Concord.new(:licensed)
|
6
7
|
|
7
|
-
|
8
|
+
FORMAT = <<~'MESSAGE'
|
9
|
+
%<subscription_name>s subscription:
|
10
|
+
Licensed:
|
11
|
+
%<licensed>s
|
12
|
+
MESSAGE
|
13
|
+
|
14
|
+
FAILURE_FORMAT = <<~'MESSAGE'
|
8
15
|
Can not validate %<subscription_name>s license.
|
9
16
|
Licensed:
|
10
17
|
%<expected>s
|
@@ -12,31 +19,46 @@ module Mutant
|
|
12
19
|
%<actual>s
|
13
20
|
MESSAGE
|
14
21
|
|
15
|
-
|
22
|
+
# Load value into subscription
|
23
|
+
#
|
24
|
+
# @param [Object] value
|
25
|
+
#
|
26
|
+
# @return [Subscription]
|
27
|
+
def self.load(world, value)
|
16
28
|
{
|
17
29
|
'com' => Commercial,
|
18
30
|
'oss' => Opensource
|
19
|
-
}.fetch(value.fetch('type'))
|
31
|
+
}.fetch(value.fetch('type'))
|
32
|
+
.from_json(value.fetch('contents'))
|
33
|
+
.apply(world)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Subscription self description
|
37
|
+
#
|
38
|
+
# @return [String]
|
39
|
+
def description
|
40
|
+
FORMAT % {
|
41
|
+
licensed: licensed.to_a.join("\n"),
|
42
|
+
subscription_name: subscription_name
|
43
|
+
}
|
20
44
|
end
|
21
45
|
|
22
46
|
private
|
23
47
|
|
24
48
|
def failure(expected, actual)
|
25
|
-
Either::Left.new(
|
49
|
+
Either::Left.new(failure_message(expected, actual))
|
26
50
|
end
|
27
51
|
|
28
|
-
# ignore :reek:UtilityFunction
|
29
52
|
def success
|
30
|
-
|
31
|
-
Either::Right.new(nil)
|
53
|
+
Either::Right.new(self)
|
32
54
|
end
|
33
55
|
|
34
56
|
def subscription_name
|
35
57
|
self.class.name.split('::').last.downcase
|
36
58
|
end
|
37
59
|
|
38
|
-
def
|
39
|
-
|
60
|
+
def failure_message(expected, actual)
|
61
|
+
FAILURE_FORMAT % {
|
40
62
|
actual: actual.any? ? actual.map(&:to_s).join("\n") : '[none]',
|
41
63
|
expected: expected.map(&:to_s).join("\n"),
|
42
64
|
subscription_name: subscription_name
|
@@ -4,8 +4,6 @@ module Mutant
|
|
4
4
|
module License
|
5
5
|
class Subscription
|
6
6
|
class Commercial < self
|
7
|
-
include Concord.new(:authors)
|
8
|
-
|
9
7
|
class Author
|
10
8
|
include Concord.new(:email)
|
11
9
|
|
@@ -20,10 +18,10 @@ module Mutant
|
|
20
18
|
def apply(world)
|
21
19
|
candidates = candidates(world)
|
22
20
|
|
23
|
-
if (
|
21
|
+
if (licensed & candidates).any?
|
24
22
|
success
|
25
23
|
else
|
26
|
-
failure(
|
24
|
+
failure(licensed, candidates)
|
27
25
|
end
|
28
26
|
end
|
29
27
|
|
@@ -4,8 +4,6 @@ module Mutant
|
|
4
4
|
module License
|
5
5
|
class Subscription
|
6
6
|
class Opensource < self
|
7
|
-
include Concord.new(:repositories)
|
8
|
-
|
9
7
|
class Repository
|
10
8
|
include Concord.new(:host, :path)
|
11
9
|
|
@@ -43,10 +41,13 @@ module Mutant
|
|
43
41
|
private_class_method :parse_url
|
44
42
|
end
|
45
43
|
|
46
|
-
private_constant(*constants(false))
|
47
|
-
|
48
44
|
def self.from_json(value)
|
49
|
-
new(
|
45
|
+
new(
|
46
|
+
value
|
47
|
+
.fetch('repositories')
|
48
|
+
.map(&Repository.public_method(:parse))
|
49
|
+
.to_set
|
50
|
+
)
|
50
51
|
end
|
51
52
|
|
52
53
|
def apply(world)
|
@@ -59,10 +60,10 @@ module Mutant
|
|
59
60
|
private
|
60
61
|
|
61
62
|
def check_subscription(actual)
|
62
|
-
if (
|
63
|
+
if (licensed & actual).any?
|
63
64
|
success
|
64
65
|
else
|
65
|
-
failure(
|
66
|
+
failure(licensed, actual)
|
66
67
|
end
|
67
68
|
end
|
68
69
|
|
@@ -44,6 +44,19 @@ module Mutant
|
|
44
44
|
with(attribute => public_send(attribute) + [value])
|
45
45
|
end
|
46
46
|
|
47
|
+
# Merge with other config
|
48
|
+
#
|
49
|
+
# @param [Config] other
|
50
|
+
#
|
51
|
+
# @return [Config]
|
52
|
+
def merge(other)
|
53
|
+
self.class.new(
|
54
|
+
to_h
|
55
|
+
.map { |name, value| [name, value + other.public_send(name)] }
|
56
|
+
.to_h
|
57
|
+
)
|
58
|
+
end
|
59
|
+
|
47
60
|
private
|
48
61
|
|
49
62
|
def present_attributes
|
@@ -14,8 +14,8 @@ module Mutant
|
|
14
14
|
# @return [undefined]
|
15
15
|
def run
|
16
16
|
info 'Matcher: %s', object.matcher.inspect
|
17
|
-
info 'Integration: %s', object.integration
|
18
|
-
info 'Jobs: %
|
17
|
+
info 'Integration: %s', object.integration || 'null'
|
18
|
+
info 'Jobs: %s', object.jobs || 'auto'
|
19
19
|
info 'Includes: %s', object.includes
|
20
20
|
info 'Requires: %s', object.requires
|
21
21
|
end
|
@@ -14,7 +14,9 @@ module Mutant
|
|
14
14
|
def call(subject)
|
15
15
|
subject.match_expressions.each do |match_expression|
|
16
16
|
subject_tests = integration.all_tests.select do |test|
|
17
|
-
|
17
|
+
test.expressions.any? do |test_expression|
|
18
|
+
match_expression.prefix?(test_expression)
|
19
|
+
end
|
18
20
|
end
|
19
21
|
return subject_tests if subject_tests.any?
|
20
22
|
end
|
data/lib/mutant/test.rb
CHANGED
data/lib/mutant/version.rb
CHANGED
data/lib/mutant/world.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mutant
|
4
|
+
# The outer world IO objects mutant does interact with
|
5
|
+
class World
|
6
|
+
include Adamantium::Flat, Anima.new(
|
7
|
+
:condition_variable,
|
8
|
+
:gem,
|
9
|
+
:gem_method,
|
10
|
+
:io,
|
11
|
+
:json,
|
12
|
+
:kernel,
|
13
|
+
:load_path,
|
14
|
+
:marshal,
|
15
|
+
:mutex,
|
16
|
+
:object_space,
|
17
|
+
:open3,
|
18
|
+
:pathname,
|
19
|
+
:process,
|
20
|
+
:stderr,
|
21
|
+
:stdout,
|
22
|
+
:thread,
|
23
|
+
:warnings
|
24
|
+
)
|
25
|
+
|
26
|
+
INSPECT = '#<Mutant::World>'
|
27
|
+
|
28
|
+
private_constant(*constants(false))
|
29
|
+
|
30
|
+
# Object inspection
|
31
|
+
#
|
32
|
+
# @return [String]
|
33
|
+
def inspect
|
34
|
+
INSPECT
|
35
|
+
end
|
36
|
+
|
37
|
+
# Capture stdout of a command
|
38
|
+
#
|
39
|
+
# @param [Array<String>] command
|
40
|
+
#
|
41
|
+
# @return [Either<String,String>]
|
42
|
+
def capture_stdout(command)
|
43
|
+
stdout, status = open3.capture2(*command, binmode: true)
|
44
|
+
|
45
|
+
if status.success?
|
46
|
+
Either::Right.new(stdout)
|
47
|
+
else
|
48
|
+
Either::Left.new("Command #{command} failed!")
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end # World
|
52
|
+
end # Mutant
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mutant
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.10.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Markus Schirp
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-11-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: abstract_type
|
@@ -184,14 +184,14 @@ dependencies:
|
|
184
184
|
requirements:
|
185
185
|
- - "~>"
|
186
186
|
- !ruby/object:Gem::Version
|
187
|
-
version: 0.5.
|
187
|
+
version: 0.5.4
|
188
188
|
type: :runtime
|
189
189
|
prerelease: false
|
190
190
|
version_requirements: !ruby/object:Gem::Requirement
|
191
191
|
requirements:
|
192
192
|
- - "~>"
|
193
193
|
- !ruby/object:Gem::Version
|
194
|
-
version: 0.5.
|
194
|
+
version: 0.5.4
|
195
195
|
- !ruby/object:Gem::Dependency
|
196
196
|
name: variable
|
197
197
|
requirement: !ruby/object:Gem::Requirement
|
@@ -226,56 +226,56 @@ dependencies:
|
|
226
226
|
requirements:
|
227
227
|
- - "~>"
|
228
228
|
- !ruby/object:Gem::Version
|
229
|
-
version: '3.
|
229
|
+
version: '3.10'
|
230
230
|
type: :development
|
231
231
|
prerelease: false
|
232
232
|
version_requirements: !ruby/object:Gem::Requirement
|
233
233
|
requirements:
|
234
234
|
- - "~>"
|
235
235
|
- !ruby/object:Gem::Version
|
236
|
-
version: '3.
|
236
|
+
version: '3.10'
|
237
237
|
- !ruby/object:Gem::Dependency
|
238
238
|
name: rspec-core
|
239
239
|
requirement: !ruby/object:Gem::Requirement
|
240
240
|
requirements:
|
241
241
|
- - "~>"
|
242
242
|
- !ruby/object:Gem::Version
|
243
|
-
version: '3.
|
243
|
+
version: '3.10'
|
244
244
|
type: :development
|
245
245
|
prerelease: false
|
246
246
|
version_requirements: !ruby/object:Gem::Requirement
|
247
247
|
requirements:
|
248
248
|
- - "~>"
|
249
249
|
- !ruby/object:Gem::Version
|
250
|
-
version: '3.
|
250
|
+
version: '3.10'
|
251
251
|
- !ruby/object:Gem::Dependency
|
252
252
|
name: rspec-its
|
253
253
|
requirement: !ruby/object:Gem::Requirement
|
254
254
|
requirements:
|
255
255
|
- - "~>"
|
256
256
|
- !ruby/object:Gem::Version
|
257
|
-
version: 1.
|
257
|
+
version: 1.3.0
|
258
258
|
type: :development
|
259
259
|
prerelease: false
|
260
260
|
version_requirements: !ruby/object:Gem::Requirement
|
261
261
|
requirements:
|
262
262
|
- - "~>"
|
263
263
|
- !ruby/object:Gem::Version
|
264
|
-
version: 1.
|
264
|
+
version: 1.3.0
|
265
265
|
- !ruby/object:Gem::Dependency
|
266
266
|
name: rubocop
|
267
267
|
requirement: !ruby/object:Gem::Requirement
|
268
268
|
requirements:
|
269
269
|
- - "~>"
|
270
270
|
- !ruby/object:Gem::Version
|
271
|
-
version:
|
271
|
+
version: '1.2'
|
272
272
|
type: :development
|
273
273
|
prerelease: false
|
274
274
|
version_requirements: !ruby/object:Gem::Requirement
|
275
275
|
requirements:
|
276
276
|
- - "~>"
|
277
277
|
- !ruby/object:Gem::Version
|
278
|
-
version:
|
278
|
+
version: '1.2'
|
279
279
|
description: Mutation Testing for Ruby.
|
280
280
|
email:
|
281
281
|
- mbj@schirp-dso.com
|
@@ -303,6 +303,10 @@ files:
|
|
303
303
|
- lib/mutant/ast/types.rb
|
304
304
|
- lib/mutant/bootstrap.rb
|
305
305
|
- lib/mutant/cli.rb
|
306
|
+
- lib/mutant/cli/command.rb
|
307
|
+
- lib/mutant/cli/command/root.rb
|
308
|
+
- lib/mutant/cli/command/run.rb
|
309
|
+
- lib/mutant/cli/command/subscription.rb
|
306
310
|
- lib/mutant/config.rb
|
307
311
|
- lib/mutant/context.rb
|
308
312
|
- lib/mutant/env.rb
|
@@ -338,7 +342,6 @@ files:
|
|
338
342
|
- lib/mutant/meta/example.rb
|
339
343
|
- lib/mutant/meta/example/dsl.rb
|
340
344
|
- lib/mutant/meta/example/verification.rb
|
341
|
-
- lib/mutant/minitest/coverage.rb
|
342
345
|
- lib/mutant/mutation.rb
|
343
346
|
- lib/mutant/mutator.rb
|
344
347
|
- lib/mutant/mutator/node.rb
|
@@ -447,6 +450,7 @@ files:
|
|
447
450
|
- lib/mutant/util.rb
|
448
451
|
- lib/mutant/version.rb
|
449
452
|
- lib/mutant/warnings.rb
|
453
|
+
- lib/mutant/world.rb
|
450
454
|
- lib/mutant/zombifier.rb
|
451
455
|
homepage: https://github.com/mbj/mutant
|
452
456
|
licenses:
|
@@ -460,7 +464,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
460
464
|
requirements:
|
461
465
|
- - ">="
|
462
466
|
- !ruby/object:Gem::Version
|
463
|
-
version: '
|
467
|
+
version: '2.5'
|
464
468
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
465
469
|
requirements:
|
466
470
|
- - ">="
|
@@ -1,53 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'minitest'
|
4
|
-
|
5
|
-
module Mutant
|
6
|
-
module Minitest
|
7
|
-
module Coverage
|
8
|
-
# Setup coverage declaration for current class
|
9
|
-
#
|
10
|
-
# @param [String]
|
11
|
-
#
|
12
|
-
# @example
|
13
|
-
#
|
14
|
-
# class MyTest < MiniTest::Test
|
15
|
-
# cover 'MyCode*'
|
16
|
-
#
|
17
|
-
# def test_some_stuff
|
18
|
-
# end
|
19
|
-
# end
|
20
|
-
#
|
21
|
-
# @api public
|
22
|
-
def cover(expression)
|
23
|
-
if defined?(@cover_expression)
|
24
|
-
fail "#{self} already declares to cover: #{@cover_expression.inspect}"
|
25
|
-
end
|
26
|
-
|
27
|
-
@cover_expression = expression
|
28
|
-
end
|
29
|
-
|
30
|
-
# Effective coverage expression
|
31
|
-
#
|
32
|
-
# @return [String, nil]
|
33
|
-
#
|
34
|
-
# @api private
|
35
|
-
def resolve_cover_expression
|
36
|
-
return @cover_expression if defined?(@cover_expression)
|
37
|
-
|
38
|
-
try_superclass_cover_expression
|
39
|
-
end
|
40
|
-
|
41
|
-
private
|
42
|
-
|
43
|
-
def try_superclass_cover_expression
|
44
|
-
return if superclass.equal?(::Minitest::Runnable)
|
45
|
-
|
46
|
-
superclass.resolve_cover_expression
|
47
|
-
end
|
48
|
-
|
49
|
-
end # Coverage
|
50
|
-
end # Minitest
|
51
|
-
end # Mutant
|
52
|
-
|
53
|
-
Minitest::Test.extend(Mutant::Minitest::Coverage)
|