mutant 0.11.11 → 0.11.12

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6aa7e42cfe0d8ed12281745fc607d4d933fbb03af10b2bb41c41e6df3765ef89
4
- data.tar.gz: 2a4a940e72bf2381816cfc0522f236166df7c2f649bd4da6ee8c2f41a6851c33
3
+ metadata.gz: 3c75928a9c55d93ebfad196c028271b8c82d7d9eb94e48873ac75c9ee4bb212f
4
+ data.tar.gz: 27149ec498db15629bb18c55a5840e3ea8c369542a85bf9d3658cadc78fc8f4a
5
5
  SHA512:
6
- metadata.gz: 9ffa1cf3efbe20ba6ac20d7aee9765cbb8833a2934f2d1b874ec800673582c709e76bcbdb6a026191bd751a9f30726720c5f0f81b09e0999805608e22d775dc7
7
- data.tar.gz: b4a2cf6f0fc16056e8f92b306828c4e5578f0b1408770b0395b4874ff887a3f1cbe5023613694493736bcdede6c906dfaed06c1b73e78f2b8b5821b2fb61457b
6
+ metadata.gz: 44867690e5c1c367df8b22ec3fa5b35941328e12fd10a26a252baa3815c955fbd37f1adaaaadfafbcdc1f72280ce013d37996d001c163930c084b95b07905362
7
+ data.tar.gz: b22e71a0ae7f1ecb96345e7996e61c4ee843c7b82442f21dea3516be132503fa9c0193443f1d7da9196fbb27f23f3ef858ee58f81c03b7cda8d4f4dcced1a9a7
@@ -24,16 +24,15 @@ module Mutant
24
24
 
25
25
  # Run Bootstrap
26
26
  #
27
- # @param [World] world
28
- # @param [Config] config
27
+ # @param [Env] env
29
28
  #
30
29
  # @return [Either<String, Env>]
31
30
  #
32
31
  # rubocop:disable Metrics/MethodLength
33
- def self.call(world, config)
34
- env = load_hooks(Env.empty(world, config))
32
+ def self.call(env)
33
+ env = load_hooks(env)
35
34
  .tap(&method(:infect))
36
- .with(matchable_scopes: matchable_scopes(world, config))
35
+ .with(matchable_scopes: matchable_scopes(env))
37
36
 
38
37
  subjects = start_subject(env, Matcher.from_config(env.config.matcher).call(env))
39
38
 
@@ -82,8 +81,10 @@ module Mutant
82
81
  end
83
82
  private_class_method :infect
84
83
 
85
- def self.matchable_scopes(world, config)
86
- scopes = world.object_space.each_object(Module).each_with_object([]) do |scope, aggregate|
84
+ def self.matchable_scopes(env)
85
+ config = env.config
86
+
87
+ scopes = env.world.object_space.each_object(Module).each_with_object([]) do |scope, aggregate|
87
88
  expression = expression(config.reporter, config.expression_parser, scope) || next
88
89
  aggregate << Scope.new(scope, expression)
89
90
  end
@@ -23,9 +23,11 @@ module Mutant
23
23
  end
24
24
 
25
25
  def bootstrap
26
- Config.load_config_file(world)
26
+ env = Env.empty(world, @config)
27
+
28
+ Config.load_config_file(env)
27
29
  .fmap(&method(:expand))
28
- .bind { Bootstrap.call(world, @config) }
30
+ .bind { Bootstrap.call(env.with(config: @config)) }
29
31
  end
30
32
 
31
33
  def expand(file_config)
@@ -121,7 +123,7 @@ module Mutant
121
123
  set(jobs: Integer(number))
122
124
  end
123
125
  parser.on('-t', '--mutation-timeout NUMBER', 'Per mutation analysis timeout') do |number|
124
- set(mutation_timeout: Float(number))
126
+ set(mutation: @config.mutation.with(timeout: Float(number)))
125
127
  end
126
128
  end
127
129
  end # Run
@@ -14,6 +14,12 @@ module Mutant
14
14
  OPTIONS = %i[add_target_options].freeze
15
15
 
16
16
  def action
17
+ @ignore_patterns.map! do |syntax|
18
+ AST::Pattern.parse(syntax).from_right do |message|
19
+ return Either::Left.new(message)
20
+ end
21
+ end
22
+
17
23
  @targets.each(&method(:print_mutations))
18
24
  Either::Right.new(nil)
19
25
  end
@@ -50,18 +56,27 @@ module Mutant
50
56
  def initialize(_arguments)
51
57
  super
52
58
 
53
- @targets = []
59
+ @targets = []
60
+ @ignore_patterns = []
54
61
  end
55
62
 
56
63
  def add_target_options(parser)
57
64
  parser.on('-e', '--evaluate SOURCE') do |source|
58
65
  @targets << Target::Source.new(source)
59
66
  end
67
+
68
+ parser.on('-i', '--ignore-pattern AST_PATTERN') do |pattern|
69
+ @ignore_patterns << pattern
70
+ end
60
71
  end
61
72
 
62
73
  def print_mutations(target)
63
74
  world.stdout.puts(target.identification)
64
- Mutator.mutate(target.node).each do |mutation|
75
+
76
+ Mutator::Node.mutate(
77
+ config: Mutant::Mutation::Config::DEFAULT.with(ignore_patterns: @ignore_patterns),
78
+ node: target.node
79
+ ).each do |mutation|
65
80
  Reporter::CLI::Printer::Mutation.call(
66
81
  world.stdout,
67
82
  Mutant::Mutation::Evil.new(target, mutation)
data/lib/mutant/config.rb CHANGED
@@ -19,7 +19,7 @@ module Mutant
19
19
  :isolation,
20
20
  :jobs,
21
21
  :matcher,
22
- :mutation_timeout,
22
+ :mutation,
23
23
  :reporter,
24
24
  :requires,
25
25
  :zombie
@@ -39,6 +39,20 @@ module Mutant
39
39
  mutant.yml
40
40
  ].freeze
41
41
 
42
+ MUTATION_TIMEOUT_DEPRECATION = <<~'MESSAGE'
43
+ Deprecated configuration toplevel key `mutation_timeout` found.
44
+
45
+ This key will be removed in the next mayor version.
46
+ Instead place your mutation timeout configuration under the `mutation` key
47
+ like this:
48
+
49
+ ```
50
+ # mutant.yml
51
+ mutation:
52
+ timeout: 10.0 # float here.
53
+ ```
54
+ MESSAGE
55
+
42
56
  private_constant(*constants(false))
43
57
 
44
58
  # Merge with other config
@@ -59,7 +73,7 @@ module Mutant
59
73
  integration: other.integration || integration,
60
74
  jobs: other.jobs || jobs,
61
75
  matcher: matcher.merge(other.matcher),
62
- mutation_timeout: other.mutation_timeout || mutation_timeout,
76
+ mutation: mutation.merge(other.mutation),
63
77
  requires: requires + other.requires,
64
78
  zombie: zombie || other.zombie
65
79
  )
@@ -69,17 +83,16 @@ module Mutant
69
83
 
70
84
  # Load config file
71
85
  #
72
- # @param [World] world
73
- # @param [Config] config
86
+ # @param [Env] env
74
87
  #
75
88
  # @return [Either<String,Config>]
76
- def self.load_config_file(world)
89
+ def self.load_config_file(env)
77
90
  files = CANDIDATES
78
- .map(&world.pathname.public_method(:new))
91
+ .map(&env.world.pathname.public_method(:new))
79
92
  .select(&:readable?)
80
93
 
81
94
  if files.one?
82
- load_contents(files.first).fmap(&DEFAULT.public_method(:with))
95
+ load_contents(env, files.first).fmap(&DEFAULT.public_method(:with))
83
96
  elsif files.empty?
84
97
  Either::Right.new(DEFAULT)
85
98
  else
@@ -97,14 +110,30 @@ module Mutant
97
110
  )
98
111
  end
99
112
 
100
- def self.load_contents(path)
113
+ def self.load_contents(env, path)
101
114
  Transform::Named
102
- .new(path.to_s, TRANSFORM)
115
+ .new(
116
+ path.to_s,
117
+ sequence(env.config.reporter)
118
+ )
103
119
  .call(path)
104
120
  .lmap(&:compact_message)
105
121
  end
106
122
  private_class_method :load_contents
107
123
 
124
+ def self.sequence(reporter)
125
+ Transform::Sequence.new(
126
+ [
127
+ Transform::Exception.new(SystemCallError, :read.to_proc),
128
+ Transform::Exception.new(YAML::SyntaxError, YAML.public_method(:safe_load)),
129
+ Transform::Primitive.new(Hash),
130
+ Transform::Success.new(->(hash) { deprecations(reporter, hash) }),
131
+ *TRANSFORMS
132
+ ]
133
+ )
134
+ end
135
+ private_class_method :sequence
136
+
108
137
  # The configuration from the environment
109
138
  #
110
139
  # @return [Config]
@@ -139,38 +168,45 @@ module Mutant
139
168
 
140
169
  Either::Right.new(hash)
141
170
  end
142
- TRANSFORM = Transform::Sequence.new(
143
- [
144
- Transform::Exception.new(SystemCallError, :read.to_proc),
145
- Transform::Exception.new(YAML::SyntaxError, YAML.public_method(:safe_load)),
146
- Transform::Hash.new(
147
- optional: [
148
- Transform::Hash::Key.new('coverage_criteria', ->(value) { CoverageCriteria::TRANSFORM.call(value) }),
149
- Transform::Hash::Key.new(
150
- 'environment_variables',
151
- Transform::Sequence.new(
152
- [
153
- Transform::Primitive.new(Hash),
154
- Transform::Block.capture(:environment_variables, &method(:parse_environment_variables))
155
- ]
156
- )
157
- ),
158
- Transform::Hash::Key.new('fail_fast', Transform::BOOLEAN),
159
- Transform::Hash::Key.new('hooks', PATHNAME_ARRAY),
160
- Transform::Hash::Key.new('includes', Transform::STRING_ARRAY),
161
- Transform::Hash::Key.new('integration', Transform::STRING),
162
- Transform::Hash::Key.new('jobs', Transform::INTEGER),
163
- Transform::Hash::Key.new('matcher', Matcher::Config::LOADER),
164
- Transform::Hash::Key.new('mutation_timeout', Transform::FLOAT),
165
- Transform::Hash::Key.new('requires', Transform::STRING_ARRAY)
166
- ],
167
- required: []
168
- ),
169
- Transform::Hash::Symbolize.new
170
- ]
171
- )
172
171
 
173
- private_constant(:TRANSFORM)
172
+ def self.deprecations(reporter, hash)
173
+ if hash.key?('mutation_timeout')
174
+ reporter.warn(MUTATION_TIMEOUT_DEPRECATION)
175
+
176
+ (hash['mutation'] ||= {})['timeout'] ||= hash.delete('mutation_timeout')
177
+ end
178
+
179
+ hash
180
+ end
181
+
182
+ TRANSFORMS = [
183
+ Transform::Hash.new(
184
+ optional: [
185
+ Transform::Hash::Key.new('coverage_criteria', ->(value) { CoverageCriteria::TRANSFORM.call(value) }),
186
+ Transform::Hash::Key.new(
187
+ 'environment_variables',
188
+ Transform::Sequence.new(
189
+ [
190
+ Transform::Primitive.new(Hash),
191
+ Transform::Block.capture(:environment_variables, &method(:parse_environment_variables))
192
+ ]
193
+ )
194
+ ),
195
+ Transform::Hash::Key.new('fail_fast', Transform::BOOLEAN),
196
+ Transform::Hash::Key.new('hooks', PATHNAME_ARRAY),
197
+ Transform::Hash::Key.new('includes', Transform::STRING_ARRAY),
198
+ Transform::Hash::Key.new('integration', Transform::STRING),
199
+ Transform::Hash::Key.new('jobs', Transform::INTEGER),
200
+ Transform::Hash::Key.new('matcher', Matcher::Config::LOADER),
201
+ Transform::Hash::Key.new('mutation', Mutation::Config::TRANSFORM),
202
+ Transform::Hash::Key.new('requires', Transform::STRING_ARRAY)
203
+ ],
204
+ required: []
205
+ ),
206
+ Transform::Hash::Symbolize.new
207
+ ].freeze
208
+
209
+ private_constant(:TRANSFORMS)
174
210
  end # Config
175
211
  # rubocop:enable Metrics/ClassLength
176
212
  end # Mutant
data/lib/mutant/env.rb CHANGED
@@ -137,7 +137,7 @@ module Mutant
137
137
  private
138
138
 
139
139
  def run_mutation_tests(mutation, tests)
140
- config.isolation.call(config.mutation_timeout) do
140
+ config.isolation.call(config.mutation.timeout) do
141
141
  hooks.run(:mutation_insert_pre, mutation)
142
142
  result = mutation.insert(world.kernel)
143
143
  hooks.run(:mutation_insert_post, mutation)
@@ -18,7 +18,7 @@ module Mutant
18
18
  end
19
19
 
20
20
  def self.parse(input)
21
- new(*input.split('/', 2))
21
+ new(*input.split('/', 2).map(&:downcase))
22
22
  end
23
23
 
24
24
  def self.parse_remote(input)
@@ -99,13 +99,20 @@ module Mutant
99
99
  node = matched_node_path.last || return
100
100
 
101
101
  self.class::SUBJECT_CLASS.new(
102
- config: Subject::Config.parse(ast.comment_associations.fetch(node, [])),
102
+ config: subject_config(node),
103
103
  context: context,
104
104
  node: node,
105
105
  visibility: visibility
106
106
  )
107
107
  end
108
108
 
109
+ def subject_config(node)
110
+ Subject::Config.parse(
111
+ comments: ast.comment_associations.fetch(node, []),
112
+ mutation: env.config.mutation
113
+ )
114
+ end
115
+
109
116
  def matched_node_path
110
117
  AST.find_last_path(ast.node, &method(:match?))
111
118
  end
@@ -55,7 +55,10 @@ module Mutant
55
55
  #
56
56
  # @return [Enumerable<Mutant::Mutation>]
57
57
  def generated
58
- Mutator.mutate(node).map do |node|
58
+ Mutator::Node.mutate(
59
+ config: Mutation::Config::DEFAULT,
60
+ node: node
61
+ ).map do |node|
59
62
  Mutation::Evil.new(self, node)
60
63
  end
61
64
  end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mutant
4
+ class Mutation
5
+ class Config
6
+ include Anima.new(:ignore_patterns, :timeout)
7
+
8
+ DEFAULT = new(
9
+ timeout: nil,
10
+ ignore_patterns: []
11
+ )
12
+
13
+ ignore_pattern = Transform::Block.capture('ignore pattern', &AST::Pattern.method(:parse))
14
+
15
+ TRANSFORM = Transform::Sequence.new(
16
+ [
17
+ Transform::Hash.new(
18
+ optional: [
19
+ Transform::Hash::Key.new('ignore_patterns', Transform::Array.new(ignore_pattern)),
20
+ Transform::Hash::Key.new('timeout', Transform::FLOAT)
21
+ ],
22
+ required: []
23
+ ),
24
+ Transform::Hash::Symbolize.new,
25
+ Transform::Success.new(DEFAULT.method(:with))
26
+ ]
27
+ )
28
+
29
+ def merge(other)
30
+ with(
31
+ timeout: other.timeout || timeout
32
+ )
33
+ end
34
+ end # Config
35
+ end # Mutation
36
+ end # Mutant
@@ -50,7 +50,7 @@ module Mutant
50
50
 
51
51
  def emit_argument_mutations
52
52
  children.each_with_index do |child, index|
53
- Mutator.mutate(child).each do |mutant|
53
+ mutate(node: child).each do |mutant|
54
54
  next if invalid_argument_replacement?(mutant, index)
55
55
  emit_child_update(index, mutant)
56
56
  end
@@ -12,8 +12,9 @@ module Mutant
12
12
  private
13
13
 
14
14
  def dispatch
15
+ ignore_single = children.any?(&method(:ignore?))
15
16
  mutate_single_child do |child|
16
- emit(child)
17
+ emit(child) unless ignore_single
17
18
  end
18
19
  end
19
20
  end # Begin
@@ -13,8 +13,11 @@ module Mutant
13
13
  emit_optarg_body_assignments
14
14
  emit_body(N_RAISE)
15
15
  emit_body(N_ZSUPER)
16
- emit_body(nil)
17
- emit_body_mutations if body
16
+
17
+ return unless body && !ignore?(body)
18
+
19
+ emit_body(nil) unless body.children.any?(&method(:ignore?))
20
+ emit_body_mutations
18
21
  end
19
22
 
20
23
  def emit_optarg_body_assignments
@@ -20,14 +20,14 @@ module Mutant
20
20
  end
21
21
 
22
22
  def emit_argument_presence
23
- Util::Array::Presence.call(children).each do |children|
23
+ Util::Array::Presence.call(input: children, parent: nil).each do |children|
24
24
  emit_type(*children) unless children.empty?
25
25
  end
26
26
  end
27
27
 
28
28
  def emit_argument_mutations
29
29
  children.each_with_index do |child, index|
30
- Mutator.mutate(child).each do |mutant|
30
+ mutate(node: child).each do |mutant|
31
31
  unless forbid_argument?(mutant)
32
32
  emit_child_update(index, mutant)
33
33
  end
@@ -33,7 +33,7 @@ module Mutant
33
33
  # Regular expressions with interpolation are skipped.
34
34
  return unless (body_ast = AST::Regexp.expand_regexp_ast(input))
35
35
 
36
- Mutator.mutate(body_ast).each do |mutation|
36
+ mutate(node: body_ast).each do |mutation|
37
37
  source = AST::Regexp.to_expression(mutation).to_s
38
38
  emit_type(s(:str, source), options)
39
39
  end
@@ -15,9 +15,9 @@ module Mutant
15
15
 
16
16
  def dispatch
17
17
  emit_singletons
18
- Util::Symbol.call(value).each(&method(:emit_type))
19
- end
20
18
 
19
+ Util::Symbol.call(input: value, parent: nil).each(&method(:emit_type))
20
+ end
21
21
  end # Symbol
22
22
  end # Literal
23
23
  end # Node
@@ -26,7 +26,7 @@ module Mutant
26
26
  end
27
27
 
28
28
  def mutate_name
29
- Util::Symbol.call(name).each do |name|
29
+ Util::Symbol.call(input: name, parent: nil).each do |name|
30
30
  emit_name(name.upcase)
31
31
  end
32
32
  end
@@ -34,11 +34,11 @@ module Mutant
34
34
  def mutate_name
35
35
  prefix, regexp = MAP.fetch(node.type)
36
36
  stripped = name.to_s.sub(regexp, EMPTY_STRING)
37
- Util::Symbol.call(stripped).each do |name|
37
+
38
+ Util::Symbol.call(input: stripped, parent: nil).each do |name|
38
39
  emit_name(:"#{prefix}#{name}")
39
40
  end
40
41
  end
41
-
42
42
  end # VariableAssignment
43
43
  end # NamedValue
44
44
  end # Node
@@ -15,17 +15,7 @@ module Mutant
15
15
  def dispatch
16
16
  emit_assignment(nil)
17
17
  emit_body_mutations if body
18
- mutate_captures
19
18
  end
20
-
21
- def mutate_captures
22
- return unless captures
23
- Util::Array::Element.call(captures.children).each do |matchers|
24
- next if matchers.any?(&method(:n_nil?))
25
- emit_captures(s(:array, *matchers))
26
- end
27
- end
28
-
29
19
  end # Resbody
30
20
  end # Node
31
21
  end # Mutator
@@ -10,8 +10,37 @@ module Mutant
10
10
  include AbstractType, Unparser::Constants
11
11
  include AST::NamedChildren, AST::NodePredicates, AST::Sexp, AST::Nodes
12
12
 
13
+ include anima.add(:config)
14
+
13
15
  TAUTOLOGY = ->(_input) { true }
14
16
 
17
+ REGISTRY = Registry.new(->(_) { Node::Generic })
18
+
19
+ # Lookup and invoke dedicated AST mutator
20
+ #
21
+ # @param input [Parser::AST::Node]
22
+ # @param parent [nil,Mutant::Mutator::Node]
23
+ #
24
+ # @return [Set<Parser::AST::Node>]
25
+ def self.mutate(config:, node:, parent: nil)
26
+ config.ignore_patterns.each do |pattern|
27
+ return Set.new if pattern.match?(node)
28
+ end
29
+
30
+ self::REGISTRY.lookup(node.type).call(
31
+ config: config,
32
+ input: node,
33
+ parent: parent
34
+ )
35
+ end
36
+
37
+ def self.handle(*types)
38
+ types.each do |type|
39
+ self::REGISTRY.register(type, self)
40
+ end
41
+ end
42
+ private_class_method :handle
43
+
15
44
  # Helper to define a named child
16
45
  #
17
46
  # @param [Parser::AST::Node] node
@@ -37,9 +66,13 @@ module Mutant
37
66
  alias_method :node, :input
38
67
  alias_method :dup_node, :dup_input
39
68
 
69
+ def mutate(node:, parent: nil)
70
+ self.class.mutate(config: config, node: node, parent: parent)
71
+ end
72
+
40
73
  def mutate_child(index, &block)
41
74
  block ||= TAUTOLOGY
42
- Mutator.mutate(children.fetch(index), self).each do |mutation|
75
+ mutate(node: children.fetch(index), parent: self).each do |mutation|
43
76
  next unless block.call(mutation)
44
77
  emit_child_update(index, mutation)
45
78
  end
@@ -96,6 +129,19 @@ module Mutant
96
129
  end
97
130
  end
98
131
 
132
+ def run(mutator)
133
+ mutator.call(
134
+ config: config,
135
+ input: input,
136
+ parent: nil
137
+ ).each(&method(:emit))
138
+ end
139
+
140
+ def ignore?(node)
141
+ config.ignore_patterns.any? do |pattern|
142
+ pattern.match?(node)
143
+ end
144
+ end
99
145
  end # Node
100
146
  end # Mutator
101
147
  end # Mutant
@@ -21,23 +21,6 @@ module Mutant
21
21
  end
22
22
 
23
23
  end # Presence
24
-
25
- # Array element mutator
26
- class Element < Util
27
-
28
- private
29
-
30
- def dispatch
31
- input.each_with_index do |element, index|
32
- Mutator.mutate(element).each do |mutation|
33
- dup = dup_input
34
- dup[index] = mutation
35
- emit(dup)
36
- end
37
- end
38
- end
39
-
40
- end # Element
41
24
  end # Array
42
25
  end # Util
43
26
  end # Mutator
@@ -3,33 +3,13 @@
3
3
  module Mutant
4
4
  # Generator for mutations
5
5
  class Mutator
6
-
7
- REGISTRY = Registry.new(->(_) { Node::Generic })
8
-
9
6
  include(
10
7
  Adamantium,
11
- Concord.new(:input, :parent),
12
8
  AbstractType,
9
+ Anima.new(:input, :parent),
13
10
  Procto
14
11
  )
15
12
 
16
- # Lookup and invoke dedicated AST mutator
17
- #
18
- # @param node [Parser::AST::Node]
19
- # @param parent [nil,Mutant::Mutator::Node]
20
- #
21
- # @return [Set<Parser::AST::Node>]
22
- def self.mutate(node, parent = nil)
23
- self::REGISTRY.lookup(node.type).call(node, parent)
24
- end
25
-
26
- def self.handle(*types)
27
- types.each do |type|
28
- self::REGISTRY.register(type, self)
29
- end
30
- end
31
- private_class_method :handle
32
-
33
13
  # Return output
34
14
  #
35
15
  # @return [Set<Parser::AST::Node>]
@@ -39,7 +19,7 @@ module Mutant
39
19
 
40
20
  private
41
21
 
42
- def initialize(_input, _parent = nil)
22
+ def initialize(_attributes)
43
23
  super
44
24
 
45
25
  @output = Set.new
@@ -60,10 +40,6 @@ module Mutant
60
40
  output << object
61
41
  end
62
42
 
63
- def run(mutator)
64
- mutator.call(input).each(&method(:emit))
65
- end
66
-
67
43
  def dup_input
68
44
  input.dup
69
45
  end
@@ -20,7 +20,7 @@ module Mutant
20
20
  info 'Jobs: %s', object.jobs || 'auto'
21
21
  info 'Includes: %s', object.includes
22
22
  info 'Requires: %s', object.requires
23
- info 'MutationTimeout: %0.9g', object.mutation_timeout if object.mutation_timeout
23
+ info 'MutationTimeout: %0.9g', object.mutation.timeout if object.mutation.timeout
24
24
  end
25
25
  # rubocop:enable Metrics/AbcSize
26
26
 
@@ -3,16 +3,17 @@
3
3
  module Mutant
4
4
  class Subject
5
5
  class Config
6
- include Adamantium, Anima.new(:inline_disable)
6
+ include Adamantium, Anima.new(:inline_disable, :mutation)
7
7
 
8
- DEFAULT = new(inline_disable: false)
8
+ DEFAULT = new(inline_disable: false, mutation: Mutation::Config::DEFAULT)
9
9
 
10
10
  DISABLE_REGEXP = /(\s|^)mutant:disable(?:\s|$)/.freeze
11
11
  SYNTAX_REGEXP = /\A(?:#|=begin\n)/.freeze
12
12
 
13
- def self.parse(comments)
13
+ def self.parse(comments:, mutation:)
14
14
  new(
15
- inline_disable: comments.any? { |comment| DISABLE_REGEXP.match?(comment_body(comment)) }
15
+ inline_disable: comments.any? { |comment| DISABLE_REGEXP.match?(comment_body(comment)) },
16
+ mutation: mutation
16
17
  )
17
18
  end
18
19
 
@@ -12,7 +12,10 @@ module Mutant
12
12
  # @return [undefined]
13
13
  def mutations
14
14
  [neutral_mutation].concat(
15
- Mutator.mutate(node).map do |mutant|
15
+ Mutator::Node.mutate(
16
+ config: config.mutation,
17
+ node: node
18
+ ).map do |mutant|
16
19
  Mutation::Evil.new(self, wrap_node(mutant))
17
20
  end
18
21
  )
@@ -421,6 +421,20 @@ module Mutant
421
421
  end
422
422
  end # Sequence
423
423
 
424
+ # Always successful transformation
425
+ class Success < self
426
+ include Concord.new(:block)
427
+
428
+ # Apply transformation to input
429
+ #
430
+ # @param [Object]
431
+ #
432
+ # @return [Either<Error, Object>]
433
+ def call(input)
434
+ success(block.call(input))
435
+ end
436
+ end # Sequence
437
+
424
438
  # Generic exception transformer
425
439
  class Exception < self
426
440
  include Concord.new(:error_class, :block)
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Mutant
4
4
  # Current mutant version
5
- VERSION = '0.11.11'
5
+ VERSION = '0.11.12'
6
6
  end # Mutant
data/lib/mutant.rb CHANGED
@@ -93,6 +93,7 @@ require 'mutant/parallel/source'
93
93
  require 'mutant/parallel/worker'
94
94
  require 'mutant/require_highjack'
95
95
  require 'mutant/mutation'
96
+ require 'mutant/mutation/config'
96
97
  require 'mutant/mutator'
97
98
  require 'mutant/mutator/util'
98
99
  require 'mutant/mutator/util/array'
@@ -288,7 +289,7 @@ module Mutant
288
289
  isolation: Mutant::Isolation::Fork.new(WORLD),
289
290
  jobs: nil,
290
291
  matcher: Matcher::Config::DEFAULT,
291
- mutation_timeout: nil,
292
+ mutation: Mutation::Config::DEFAULT,
292
293
  reporter: Reporter::CLI.build(WORLD.stdout),
293
294
  requires: EMPTY_ARRAY,
294
295
  zombie: false
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.11.11
4
+ version: 0.11.12
5
5
  platform: ruby
6
6
  authors:
7
7
  - Markus Schirp
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-06-20 00:00:00.000000000 Z
11
+ date: 2022-06-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: diff-lcs
@@ -249,6 +249,7 @@ files:
249
249
  - lib/mutant/meta/example/dsl.rb
250
250
  - lib/mutant/meta/example/verification.rb
251
251
  - lib/mutant/mutation.rb
252
+ - lib/mutant/mutation/config.rb
252
253
  - lib/mutant/mutator.rb
253
254
  - lib/mutant/mutator/node.rb
254
255
  - lib/mutant/mutator/node/and_asgn.rb