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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 71f2d39499ffdc3adced885c60b9b22220e409f360fce5ab8ac0aaeb36bdbda3
4
- data.tar.gz: dee6aa26ddce9e06949566dfc75d4daf295ec76ea46903af80b0ac4027f338ac
3
+ metadata.gz: b5375b029dd29a085a6c3e3ba965135c47ed4276e6419d16874eff9b5e511f7f
4
+ data.tar.gz: a63b255ed6823d547eba19718c37ba93f827d11e1f18b7c33020d4ee80d103ee
5
5
  SHA512:
6
- metadata.gz: e6a44765e6fec377e0f4d9e3047832468af386b34f5d7a231fb940802a97977bba6000b016ca6739f402314bf03027dd8852776ff1ee687d6e67fb6168d5416a
7
- data.tar.gz: 9d696a03e4a86c8838fa73ab147a75dac9f8f14b1f20eef653b30a3758c4044f016f4444f94827738d6bd39d05fdd9cddafdcfbd2a6475c2ebbc8674f005738d
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
- namespace =
12
- if ARGV.include?('--zombie')
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
- Zombie::Mutant
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
- Mutant
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)
@@ -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/dstr'
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: STDERR,
216
- stdout: STDOUT,
218
+ stderr: $stderr,
219
+ stdout: $stdout,
217
220
  thread: Thread,
218
221
  warnings: Warnings.new(Warning)
219
222
  )
@@ -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
 
@@ -1,168 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Mutant
4
- # Commandline parser / runner
5
- class CLI
6
- include Concord.new(:world, :config)
7
-
8
- private_class_method :new
9
-
10
- OPTIONS =
11
- %i[
12
- add_environment_options
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