mutant 0.9.13 → 0.10.3

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: 8a634036241e157b66b8778d1645bca99d67cd81fa26042c42e43f868bafa260
4
- data.tar.gz: ee46dcacd412e10634454dbdd5ec52d5a803c71fa8e9db9cc3fcdc35e35a8e5c
3
+ metadata.gz: a6386182577241439f7352b0756c0951414526a9e4f7dab25507587b9bb18a16
4
+ data.tar.gz: 479cfa3d6a9683a895e56e6d8288acb15f863b0aa3979ab2c731753212726e4d
5
5
  SHA512:
6
- metadata.gz: b0d20d48dc5443ba2347c176425ec372ed2d4e2f11e0bea33c59895b9b52d00b4efcdd07e8f9b47eefbd2ebd8f20d610f3ed3a726d9d59bbed1378b2be2fea1f
7
- data.tar.gz: e4ad2f3d626eaae68f390447ad94cdf1338525f010f684f524873cfab6560d25ffb67062f6f2567750c9468670241d5180ba1a8bcc32fe7e42fc16cd3ff2c4ef
6
+ metadata.gz: e7888d0b9adf6de924d1fec9138e47650c0220e5e3f77d99a94894a2a79cd446db4abc42dd908be8f829191f02d15b001325e89655e9de722962374c3f760635
7
+ data.tar.gz: a837ed520121e31790e4cd079d447dcd85fb5b18078c75f247eeea8270585107480af983f1ee21ba07e4a12d771d793afd2f9ba02ee565703235b20fa3fca816
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
  )
@@ -1,174 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Mutant
4
- # Commandline parser / runner
5
- #
6
- # rubocop:disable Metrics/ClassLength
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
- # @param [Array<String>] arguments
75
- # Command-line options and arguments to be parsed.
76
- #
77
- # @return [Config]
78
- def parse(arguments)
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,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