mutant 0.9.13 → 0.10.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -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 (authors & candidates).any?
21
+ if (licensed & candidates).any?
24
22
  success
25
23
  else
26
- failure(authors, candidates)
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(value.fetch('repositories').map(&Repository.method(:parse)))
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 (repositories.to_set & actual).any?
63
+ if (licensed & actual).any?
63
64
  success
64
65
  else
65
- failure(repositories, actual)
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
@@ -3,7 +3,19 @@
3
3
  module Mutant
4
4
  module Meta
5
5
  class Example
6
- include Adamantium, Anima.new(:file, :node, :types, :expected)
6
+ include Adamantium
7
+ include Anima.new(
8
+ :expected,
9
+ :file,
10
+ :lvars,
11
+ :node,
12
+ :original_source,
13
+ :types
14
+ )
15
+
16
+ class Expected
17
+ include Anima.new(:original_source, :node)
18
+ end
7
19
 
8
20
  # Verification instance for example
9
21
  #
@@ -13,13 +25,13 @@ module Mutant
13
25
  end
14
26
  memoize :verification
15
27
 
16
- # Normalized source
28
+ # Original source as generated by unparser
17
29
  #
18
30
  # @return [String]
19
- def source
31
+ def original_source_generated
20
32
  Unparser.unparse(node)
21
33
  end
22
- memoize :source
34
+ memoize :original_source_generated
23
35
 
24
36
  # Generated mutations on example source
25
37
  #
@@ -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