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.

@@ -25,10 +25,11 @@ module Mutant
25
25
  #
26
26
  # @return [undefined]
27
27
  def initialize(file, types)
28
+ @expected = []
28
29
  @file = file
30
+ @lvars = []
31
+ @source = nil
29
32
  @types = types
30
- @node = nil
31
- @expected = []
32
33
  end
33
34
 
34
35
  # Example captured by DSL
@@ -38,28 +39,41 @@ module Mutant
38
39
  # @raise [RuntimeError]
39
40
  # in case example cannot be build
40
41
  def example
41
- fail 'source not defined' unless @node
42
+ fail 'source not defined' unless @source
42
43
  Example.new(
43
- file: @file,
44
- node: @node,
45
- types: @types,
46
- expected: @expected
44
+ expected: @expected,
45
+ file: @file,
46
+ lvars: @lvars,
47
+ node: @node,
48
+ original_source: @source,
49
+ types: @types
47
50
  )
48
51
  end
49
52
 
53
+ # Declare a local variable
54
+ #
55
+ # @param [Symbol]
56
+ def declare_lvar(name)
57
+ @lvars << name
58
+ end
59
+
50
60
  private
51
61
 
52
62
  def source(input)
53
- fail 'source already defined' if @node
54
- @node = node(input)
63
+ fail 'source already defined' if @source
64
+
65
+ @source = input
66
+ @node = node(input)
55
67
  end
56
68
 
57
69
  def mutation(input)
58
- node = node(input)
59
- if @expected.include?(node)
70
+ expected = Expected.new(original_source: input, node: node(input))
71
+
72
+ if @expected.include?(expected)
60
73
  fail "Mutation for input: #{input.inspect} is already expected"
61
74
  end
62
- @expected << node
75
+
76
+ @expected << expected
63
77
  end
64
78
 
65
79
  def singleton_mutations
@@ -70,14 +84,17 @@ module Mutant
70
84
  def node(input)
71
85
  case input
72
86
  when String
73
- Unparser::Preprocessor.run(Unparser.parse(input))
74
- when ::Parser::AST::Node
75
- input
87
+ parser.parse(Unparser.buffer(input))
76
88
  else
77
- fail "Cannot coerce to node: #{input.inspect}"
89
+ fail "Unsupported input: #{input.inspect}"
78
90
  end
79
91
  end
80
92
 
93
+ def parser
94
+ Unparser.parser.tap do |parser|
95
+ @lvars.each(&parser.static_env.public_method(:declare))
96
+ end
97
+ end
81
98
  end # DSL
82
99
  end # Example
83
100
  end # Meta
@@ -11,54 +11,96 @@ module Mutant
11
11
  #
12
12
  # @return [Boolean]
13
13
  def success?
14
- [missing, unexpected, no_diffs, invalid_syntax].all?(&:empty?)
14
+ [
15
+ original_verification,
16
+ invalid,
17
+ missing,
18
+ no_diffs,
19
+ unexpected
20
+ ].all?(&:empty?)
15
21
  end
16
22
  memoize :success?
17
23
 
18
- # Error report
19
- #
20
- # @return [String]
21
24
  def error_report
22
- fail 'no error report on successful validation' if success?
23
-
24
- YAML.dump(
25
- 'file' => example.file,
26
- 'original_ast' => example.node.inspect,
27
- 'original_source' => example.source,
28
- 'missing' => format_mutations(missing),
29
- 'unexpected' => format_mutations(unexpected),
30
- 'invalid_syntax' => format_mutations(invalid_syntax),
31
- 'no_diff' => no_diff_report
32
- )
25
+ reports.join("\n")
33
26
  end
34
- memoize :error_report
35
27
 
36
28
  private
37
29
 
30
+ def reports
31
+ reports = [example.file]
32
+ reports.concat(original)
33
+ reports.concat(original_verification)
34
+ reports.concat(make_report('Missing mutations:', missing))
35
+ reports.concat(make_report('Unexpected mutations:', unexpected))
36
+ reports.concat(make_report('No-Diff mutations:', no_diffs))
37
+ reports.concat(invalid)
38
+ end
39
+
40
+ def make_report(label, mutations)
41
+ if mutations.any?
42
+ [label, mutations.map(&method(:report_mutation))]
43
+ else
44
+ []
45
+ end
46
+ end
47
+
48
+ def report_mutation(mutation)
49
+ [
50
+ mutation.node.inspect,
51
+ mutation.source
52
+ ]
53
+ end
54
+
55
+ def original
56
+ [
57
+ 'Original:',
58
+ example.node,
59
+ example.original_source
60
+ ]
61
+ end
62
+
63
+ def original_verification
64
+ validation = Unparser::Validation.from_string(example.original_source)
65
+ if validation.success?
66
+ []
67
+ else
68
+ [
69
+ prefix('[original]', validation.report)
70
+ ]
71
+ end
72
+ end
73
+
74
+ def prefix(prefix, string)
75
+ string.each_line.map do |line|
76
+ "#{prefix} #{line}"
77
+ end.join
78
+ end
79
+
80
+ def invalid
81
+ mutations.each_with_object([]) do |mutation, aggregate|
82
+ validation = Unparser::Validation.from_node(mutation.node)
83
+ aggregate << prefix('[invalid-mutation]', validation.report) unless validation.success?
84
+ end
85
+ end
86
+ memoize :invalid
87
+
38
88
  def unexpected
39
89
  mutations.reject do |mutation|
40
- example.expected.include?(mutation.node)
90
+ example.expected.any? { |expected| expected.node.eql?(mutation.node) }
41
91
  end
42
92
  end
43
93
  memoize :unexpected
44
94
 
45
95
  def missing
46
- (example.expected - mutations.map(&:node)).map do |node|
47
- Mutation::Evil.new(self, node)
96
+ (example.expected.map(&:node) - mutations.map(&:node)).map do |node|
97
+ Mutation::Evil.new(nil, node)
48
98
  end
49
99
  end
50
100
  memoize :missing
51
101
 
52
- def invalid_syntax
53
- mutations.reject do |mutation|
54
- ::Parser::CurrentRuby.parse(mutation.source)
55
- rescue ::Parser::SyntaxError # rubocop:disable Lint/SuppressedException
56
- end
57
- end
58
- memoize :invalid_syntax
59
-
60
102
  def no_diffs
61
- mutations.select { |mutation| mutation.source.eql?(example.source) }
103
+ mutations.select { |mutation| mutation.source.eql?(example.original_source_generated) }
62
104
  end
63
105
  memoize :no_diffs
64
106
 
@@ -75,7 +75,7 @@ module Mutant
75
75
  end
76
76
 
77
77
  def emit_nil
78
- emit(N_NIL) unless left_assignment?
78
+ emit(N_NIL) unless left_op_assignment?
79
79
  end
80
80
 
81
81
  def parent_node
@@ -86,7 +86,7 @@ module Mutant
86
86
  parent_node&.type
87
87
  end
88
88
 
89
- def left_assignment?
89
+ def left_op_assignment?
90
90
  AST::Types::OP_ASSIGN.include?(parent_type) && parent.node.children.first.equal?(node)
91
91
  end
92
92
 
@@ -3,17 +3,19 @@
3
3
  module Mutant
4
4
  class Mutator
5
5
  class Node
6
+ # Mutator for dynamic literals
7
+ class DynamicLiteral < self
6
8
 
7
- # Dstr mutator
8
- class Dstr < Generic
9
-
10
- handle(:dstr)
9
+ handle(:dstr, :dsym)
11
10
 
12
11
  private
13
12
 
14
13
  def dispatch
15
- super()
16
14
  emit_singletons
15
+
16
+ children.each_index do |index|
17
+ mutate_child(index, &method(:n_begin?))
18
+ end
17
19
  end
18
20
 
19
21
  end # Dstr
@@ -24,7 +24,7 @@ module Mutant
24
24
  end
25
25
 
26
26
  def emit_send_forms
27
- return if left_assignment?
27
+ return if left_op_assignment?
28
28
 
29
29
  SEND_REPLACEMENTS.each do |selector|
30
30
  emit(s(:send, receiver, selector, *indices))
@@ -43,7 +43,7 @@ module Mutant
43
43
 
44
44
  def mutate_indices
45
45
  children_indices(index_range).each do |index|
46
- emit_propagation(children.fetch(index)) unless left_assignment?
46
+ emit_propagation(children.fetch(index)) unless left_op_assignment?
47
47
  delete_child(index)
48
48
  mutate_child(index)
49
49
  end
@@ -77,7 +77,7 @@ module Mutant
77
77
  def dispatch
78
78
  super()
79
79
 
80
- return if left_assignment?
80
+ return if left_op_assignment?
81
81
 
82
82
  emit_index_read
83
83
  emit(children.last)
@@ -89,7 +89,7 @@ module Mutant
89
89
  end
90
90
 
91
91
  def index_range
92
- if left_assignment?
92
+ if left_op_assignment?
93
93
  NO_VALUE_RANGE
94
94
  else
95
95
  REGULAR_RANGE
@@ -18,7 +18,7 @@ module Mutant
18
18
  }
19
19
 
20
20
  MAP = IceNine.deep_freeze(
21
- Hash[map.map { |type, prefix| [type, [prefix, /^#{::Regexp.escape(prefix)}/]] }]
21
+ map.transform_values { |prefix| [prefix, /^#{::Regexp.escape(prefix)}/] }
22
22
  )
23
23
 
24
24
  handle(*MAP.keys)
@@ -15,10 +15,24 @@ module Mutant
15
15
 
16
16
  def dispatch
17
17
  emit_singletons
18
+
19
+ left_mutations
20
+
21
+ emit_right_mutations
22
+ end
23
+
24
+ def left_mutations
18
25
  emit_left_mutations do |node|
19
26
  !n_self?(node)
20
27
  end
21
- emit_right_mutations
28
+
29
+ emit_left_promotion if n_send?(left)
30
+ end
31
+
32
+ def emit_left_promotion
33
+ receiver = left.children.first
34
+
35
+ emit_left(s(:ivasgn, *receiver)) if n_ivar?(receiver)
22
36
  end
23
37
 
24
38
  end # OpAsgn
@@ -159,7 +159,7 @@ module Mutant
159
159
  end
160
160
 
161
161
  def emit_naked_receiver
162
- emit(receiver) if receiver
162
+ emit(receiver) if receiver && !left_op_assignment?
163
163
  end
164
164
 
165
165
  def mutate_arguments
@@ -8,6 +8,7 @@ module Mutant
8
8
  class AttributeAssignment < self
9
9
 
10
10
  ATTRIBUTE_RANGE = (0..-2).freeze
11
+
11
12
  private_constant(*constants(false))
12
13
 
13
14
  private
@@ -28,7 +28,7 @@ module Mutant
28
28
  * Bug in your test suite
29
29
  * Bug in your test suite under concurrency
30
30
 
31
- The following exception was raised:
31
+ The following exception was raised while reading the killfork result:
32
32
 
33
33
  ```
34
34
  %s
@@ -60,8 +60,8 @@ module Mutant
60
60
  #
61
61
  # @return [undefined]
62
62
  def run
63
- __send__(MAP.fetch(object.class))
64
63
  print_log_messages
64
+ __send__(MAP.fetch(object.class))
65
65
  end
66
66
 
67
67
  private
@@ -73,7 +73,13 @@ module Mutant
73
73
  def print_log_messages
74
74
  log = object.log
75
75
 
76
- puts(LOG_MESSAGES % log) unless log.empty?
76
+ return if log.empty?
77
+
78
+ puts('Log messages (combined stderr and stdout):')
79
+
80
+ log.each_line do |line|
81
+ puts('[killfork] %<line>s' % { line: line })
82
+ end
77
83
  end
78
84
 
79
85
  def visit_child_error
@@ -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
- match_expression.prefix?(test.expression)
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
@@ -42,7 +42,7 @@ module Mutant
42
42
  private
43
43
 
44
44
  def wrap_node(mutant)
45
- s(:begin, mutant, s(:send, nil, :memoize, s(:args, s(:sym, name), *options)))
45
+ s(:begin, mutant, s(:send, nil, :memoize, s(:sym, name), *options))
46
46
  end
47
47
 
48
48
  # The optional AST node for adamantium memoization options
@@ -4,7 +4,7 @@ module Mutant
4
4
  # Abstract base class for test that might kill a mutation
5
5
  class Test
6
6
  include Adamantium::Flat, Anima.new(
7
- :expression,
7
+ :expressions,
8
8
  :id
9
9
  )
10
10
 
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Mutant
4
4
  # Current mutant version
5
- VERSION = '0.9.12'
5
+ VERSION = '0.10.2'
6
6
  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.9.12
4
+ version: 0.10.2
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-09-10 00:00:00.000000000 Z
11
+ date: 2020-11-02 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.4.8
187
+ version: 0.5.3
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.4.8
194
+ version: 0.5.3
195
195
  - !ruby/object:Gem::Dependency
196
196
  name: variable
197
197
  requirement: !ruby/object:Gem::Requirement
@@ -254,28 +254,28 @@ dependencies:
254
254
  requirements:
255
255
  - - "~>"
256
256
  - !ruby/object:Gem::Version
257
- version: 1.2.0
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.2.0
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: 0.79.0
271
+ version: '1.0'
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: 0.79.0
278
+ version: '1.0'
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
@@ -356,8 +359,7 @@ files:
356
359
  - lib/mutant/mutator/node/const.rb
357
360
  - lib/mutant/mutator/node/define.rb
358
361
  - lib/mutant/mutator/node/defined.rb
359
- - lib/mutant/mutator/node/dstr.rb
360
- - lib/mutant/mutator/node/dsym.rb
362
+ - lib/mutant/mutator/node/dynamic_literal.rb
361
363
  - lib/mutant/mutator/node/generic.rb
362
364
  - lib/mutant/mutator/node/if.rb
363
365
  - lib/mutant/mutator/node/index.rb
@@ -461,14 +463,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
461
463
  requirements:
462
464
  - - ">="
463
465
  - !ruby/object:Gem::Version
464
- version: '0'
466
+ version: '2.5'
465
467
  required_rubygems_version: !ruby/object:Gem::Requirement
466
468
  requirements:
467
469
  - - ">="
468
470
  - !ruby/object:Gem::Version
469
471
  version: '0'
470
472
  requirements: []
471
- rubygems_version: 3.0.3
473
+ rubygems_version: 3.1.4
472
474
  signing_key:
473
475
  specification_version: 4
474
476
  summary: ''