mutant 0.2.4 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. data/.travis.yml +3 -3
  2. data/Changelog.md +21 -0
  3. data/Gemfile.devtools +1 -0
  4. data/Guardfile +1 -1
  5. data/README.md +48 -4
  6. data/config/flay.yml +2 -2
  7. data/config/flog.yml +1 -1
  8. data/config/site.reek +3 -1
  9. data/lib/mutant.rb +14 -2
  10. data/lib/mutant/cli.rb +38 -39
  11. data/lib/mutant/context/scope.rb +37 -32
  12. data/lib/mutant/killer/forking.rb +53 -0
  13. data/lib/mutant/killer/rspec.rb +1 -1
  14. data/lib/mutant/killer/static.rb +14 -0
  15. data/lib/mutant/matcher.rb +2 -0
  16. data/lib/mutant/matcher/method.rb +2 -2
  17. data/lib/mutant/matcher/method/singleton.rb +2 -1
  18. data/lib/mutant/matcher/object_space.rb +1 -1
  19. data/lib/mutant/matcher/scope_methods.rb +2 -0
  20. data/lib/mutant/mutation.rb +26 -0
  21. data/lib/mutant/mutation/filter/whitelist.rb +1 -1
  22. data/lib/mutant/mutator.rb +52 -9
  23. data/lib/mutant/mutator/node.rb +18 -19
  24. data/lib/mutant/mutator/node/arguments.rb +156 -0
  25. data/lib/mutant/mutator/node/block.rb +7 -20
  26. data/lib/mutant/mutator/node/define.rb +18 -1
  27. data/lib/mutant/mutator/node/iter_19.rb +26 -0
  28. data/lib/mutant/mutator/node/local_variable_assignment.rb +25 -0
  29. data/lib/mutant/mutator/node/noop.rb +4 -0
  30. data/lib/mutant/mutator/node/send.rb +24 -10
  31. data/lib/mutant/mutator/util.rb +28 -1
  32. data/lib/mutant/random.rb +1 -0
  33. data/lib/mutant/reporter.rb +28 -0
  34. data/lib/mutant/reporter/cli.rb +90 -19
  35. data/lib/mutant/reporter/null.rb +5 -3
  36. data/lib/mutant/reporter/stats.rb +65 -9
  37. data/lib/mutant/runner.rb +41 -2
  38. data/lib/mutant/strategy.rb +46 -5
  39. data/lib/mutant/strategy/rspec.rb +11 -4
  40. data/lib/mutant/strategy/rspec/example_lookup.rb +30 -30
  41. data/lib/mutant/subject.rb +11 -0
  42. data/mutant.gemspec +3 -2
  43. data/spec/integration/mutant/loader_spec.rb +4 -4
  44. data/spec/shared/mutator_behavior.rb +13 -1
  45. data/spec/unit/mutant/context/scope/root_spec.rb +20 -8
  46. data/spec/unit/mutant/context/scope/unqualified_name_spec.rb +2 -2
  47. data/spec/unit/mutant/killer/rspec/class_methods/new_spec.rb +1 -1
  48. data/spec/unit/mutant/matcher/chain/each_spec.rb +6 -2
  49. data/spec/unit/mutant/mutator/node/define/mutation_spec.rb +76 -0
  50. data/spec/unit/mutant/mutator/node/send/mutation_spec.rb +80 -21
  51. data/spec/unit/mutant/strategy/rspec/example_lookup/spec_file_spec.rb +3 -3
  52. metadata +21 -10
  53. data/lib/mutant/inflector/defaults.rb +0 -64
  54. data/lib/mutant/inflector/inflections.rb +0 -211
  55. data/lib/mutant/inflector/methods.rb +0 -151
  56. data/lib/mutant/inflector/version.rb +0 -5
  57. data/locator.rb +0 -87
  58. data/spec/unit/mutant/context/scope/class_methods/build_spec.rb +0 -29
@@ -1,16 +1,44 @@
1
1
  module Mutant
2
2
  class Killer
3
3
 
4
+ # Killer that executes other killer in forked environment
4
5
  class Forked < self
6
+
7
+ # Initialize object
8
+ #
9
+ # @param [Killer] killer
10
+ # @param [Strategy] strategy
11
+ # @param [Mutation] mutation
12
+ #
13
+ # @api private
14
+ #
5
15
  def initialize(killer, strategy, mutation)
6
16
  @killer = killer
7
17
  super(strategy, mutation)
8
18
  end
9
19
 
20
+ # Return killer type
21
+ #
22
+ # @return [String]
23
+ #
24
+ # @api private
25
+ #
10
26
  def type
11
27
  @killer.type
12
28
  end
13
29
 
30
+ private
31
+
32
+ # Run killer
33
+ #
34
+ # @return [true]
35
+ # if mutant was killed
36
+ #
37
+ # @return [false]
38
+ # otherwise
39
+ #
40
+ # @api private
41
+ #
14
42
  def run
15
43
  fork do
16
44
  killer = @killer.new(strategy, mutation)
@@ -22,15 +50,40 @@ module Mutant
22
50
  end
23
51
  end
24
52
 
53
+ # A killer that executes other killer in forked environemnts
25
54
  class Forking < self
26
55
  include Equalizer.new(:killer)
27
56
 
57
+ # Return killer
58
+ #
59
+ # @return [Killer]
60
+ #
61
+ # @api private
62
+ #
28
63
  attr_reader :killer
29
64
 
65
+ # Initalize killer
66
+ #
67
+ # @param [Killer] killer
68
+ # the killer that will be used
69
+ #
70
+ # @return [undefined]
71
+ #
72
+ # @api private
73
+ #
30
74
  def initialize(killer)
31
75
  @killer = killer
32
76
  end
33
77
 
78
+ # Return killer instance
79
+ #
80
+ # @param [Strategy] strategy
81
+ # @param [Mutation] mutation
82
+ #
83
+ # @return [Killer::Forked]
84
+ #
85
+ # @api private
86
+ #
34
87
  def new(strategy, mutation)
35
88
  Forked.new(killer, strategy, mutation)
36
89
  end
@@ -29,7 +29,7 @@ module Mutant
29
29
  #
30
30
  def run
31
31
  mutation.insert
32
- !::RSpec::Core::Runner.run(command_line_arguments, @error_stream, @output_stream).zero?
32
+ !::RSpec::Core::Runner.run(command_line_arguments, strategy.error_stream, strategy.output_stream).zero?
33
33
  end
34
34
  memoize :run
35
35
 
@@ -1,15 +1,29 @@
1
1
  module Mutant
2
2
  class Killer
3
+ # Abstract base class for killer with static result
3
4
  class Static < self
5
+
6
+ # Return result
7
+ #
8
+ # @return [true]
9
+ # if mutation was killed
10
+ #
11
+ # @return [false]
12
+ # otherwise
13
+ #
14
+ # @api private
15
+ #
4
16
  def run
5
17
  self.class::RESULT
6
18
  end
7
19
 
20
+ # Killer that is always successful
8
21
  class Success < self
9
22
  TYPE = 'success'.freeze
10
23
  RESULT = true
11
24
  end
12
25
 
26
+ # Killer that always fails
13
27
  class Fail < self
14
28
  TYPE = 'fail'.freeze
15
29
  RESULT = false
@@ -43,6 +43,8 @@ module Mutant
43
43
  # @return [nil]
44
44
  # returns nil otherwise
45
45
  #
46
+ # @api private
47
+ #
46
48
  def self.from_string(input)
47
49
  descendants.each do |descendant|
48
50
  matcher = descendant.parse(input)
@@ -72,7 +72,7 @@ module Mutant
72
72
  # @return [true]
73
73
  # if method is public
74
74
  #
75
- # @retur [false]
75
+ # @return [false]
76
76
  # otherwise
77
77
  #
78
78
  # @api private
@@ -103,7 +103,7 @@ module Mutant
103
103
  # @api private
104
104
  #
105
105
  def context
106
- Context::Scope.build(scope, source_path)
106
+ Context::Scope.new(scope, source_path)
107
107
  end
108
108
 
109
109
  # Return full ast
@@ -104,7 +104,8 @@ module Mutant
104
104
  when Rubinius::AST::ConstantAccess
105
105
  receiver_name?(receiver)
106
106
  else
107
- raise 'Can only match receiver on Rubinius::AST::Self or Rubinius::AST::ConstantAccess'
107
+ $stderr.puts "Unable to find singleton method definition only match receiver on Rubinius::AST::Self or Rubinius::AST::ConstantAccess, got #{receiver.class}"
108
+ false
108
109
  end
109
110
  end
110
111
 
@@ -98,7 +98,7 @@ module Mutant
98
98
 
99
99
  # Yield scope if name matches pattern
100
100
  #
101
- # @param [::Module,::Class]
101
+ # @param [::Module,::Class] scope
102
102
  #
103
103
  # @return [undefined]
104
104
  #
@@ -153,6 +153,8 @@ module Mutant
153
153
  #
154
154
  # @return [Enumerable<Symbol>]
155
155
  #
156
+ # @api private
157
+ #
156
158
  def method_names
157
159
  scope = self.scope
158
160
  return [] unless scope.kind_of?(Module)
@@ -109,5 +109,31 @@ module Mutant
109
109
  def initialize(subject, node)
110
110
  @subject, @node = subject, node
111
111
  end
112
+
113
+ class Noop < self
114
+
115
+ # Initialihe object
116
+ #
117
+ # @param [Subject] subject
118
+ #
119
+ # @return [undefined]
120
+ #
121
+ # @api private
122
+ #
123
+ def initialize(subject)
124
+ super(subject, subject.node)
125
+ end
126
+
127
+ # Return identification
128
+ #
129
+ # @return [String]
130
+ #
131
+ # @api private
132
+ #
133
+ def identification
134
+ "noop:#{super}"
135
+ end
136
+ memoize :identification
137
+ end
112
138
  end
113
139
  end
@@ -6,7 +6,7 @@ module Mutant
6
6
 
7
7
  # Test for match
8
8
  #
9
- # @param [Mutation]
9
+ # @param [Mutation] mutation
10
10
  #
11
11
  # @return [true]
12
12
  # returns true if mutation matches whitelist
@@ -12,9 +12,9 @@ module Mutant
12
12
  #
13
13
  # @api private
14
14
  #
15
- def self.each(node, &block)
16
- return to_enum(__method__, node) unless block_given?
17
- Registry.lookup(node.class).new(node, block)
15
+ def self.each(input, &block)
16
+ return to_enum(__method__, input) unless block_given?
17
+ Registry.lookup(input.class).new(input, block)
18
18
 
19
19
  self
20
20
  end
@@ -32,6 +32,18 @@ module Mutant
32
32
  end
33
33
  private_class_method :handle
34
34
 
35
+ # Return identity of object (for deduplication)
36
+ #
37
+ # @param [Object] object
38
+ #
39
+ # @return [Object]
40
+ #
41
+ # @api private
42
+ #
43
+ def self.identity(object)
44
+ object
45
+ end
46
+
35
47
  # Return input
36
48
  #
37
49
  # @return [Object]
@@ -52,12 +64,13 @@ module Mutant
52
64
  # @api private
53
65
  #
54
66
  def initialize(input, block)
55
- @input, @block = Helper.deep_clone(input), block
56
- IceNine.deep_freeze(@input)
67
+ @input, @block = IceNine.deep_freeze(input), block
68
+ @seen = Set.new
69
+ guard(input)
57
70
  dispatch
58
71
  end
59
72
 
60
- # Test if generated object is different from input
73
+ # Test if generated object is not guarded from emmitting
61
74
  #
62
75
  # @param [Object] object
63
76
  #
@@ -69,7 +82,35 @@ module Mutant
69
82
  # @api private
70
83
  #
71
84
  def new?(object)
72
- input != object
85
+ !@seen.include?(self.class.identity(object))
86
+ end
87
+
88
+ # Add object to guarded values
89
+ #
90
+ # @param [Object] object
91
+ #
92
+ # @return [undefined]
93
+ #
94
+ # @api private
95
+ #
96
+ def guard(object)
97
+ @seen << self.class.identity(object)
98
+ end
99
+
100
+ # Test if generated mutation is allowed
101
+ #
102
+ # @param [Object] object
103
+ #
104
+ # @return [true]
105
+ # if mutation is allowed
106
+ #
107
+ # @return [false]
108
+ # otherwise
109
+ #
110
+ # @api private
111
+ #
112
+ def allow?(object)
113
+ true
73
114
  end
74
115
 
75
116
  # Dispatch node generations
@@ -89,7 +130,9 @@ module Mutant
89
130
  # @api private
90
131
  #
91
132
  def emit(object)
92
- return unless new?(object)
133
+ return unless new?(object) and allow?(object)
134
+
135
+ guard(object)
93
136
 
94
137
  emit!(object)
95
138
  end
@@ -146,7 +189,7 @@ module Mutant
146
189
  # @api private
147
190
  #
148
191
  def dup_input
149
- input.dup
192
+ Helper.deep_clone(input)
150
193
  end
151
194
 
152
195
  end
@@ -5,35 +5,35 @@ module Mutant
5
5
  class Node < self
6
6
  include AbstractType
7
7
 
8
- private
9
-
10
- alias_method :node, :input
11
- alias_method :dup_node, :dup_input
12
-
13
- # Return source of input node
8
+ # Return identity of node
9
+ #
10
+ # @param [Rubinius::AST::Node] node
14
11
  #
15
12
  # @return [String]
16
13
  #
17
14
  # @api private
18
15
  #
19
- def source
16
+ def self.identity(node)
20
17
  ToSource.to_source(node)
21
18
  end
22
- memoize :source
23
19
 
24
- # Test if generated node is new
20
+ private
21
+
22
+ # Return mutated node
25
23
  #
26
- # @return [true]
27
- # if generated node is different from input
24
+ # @return [Rubinius::AST::Node]
28
25
  #
29
- # @return [false]
30
- # otherwise
26
+ # @api private
27
+ #
28
+ alias_method :node, :input
29
+
30
+ # Return duplicated node
31
+ #
32
+ # @return [Rubinius::AST::Node]
31
33
  #
32
34
  # @api private
33
35
  #
34
- def new?(node)
35
- source != ToSource.to_source(node)
36
- end
36
+ alias_method :dup_node, :dup_input
37
37
 
38
38
  # Emit a new AST node
39
39
  #
@@ -117,6 +117,7 @@ module Mutant
117
117
  Mutator.each(body) do |mutation|
118
118
  dup = dup_node
119
119
  dup.public_send(:"#{name}=", mutation)
120
+ yield dup if block_given?
120
121
  emit(dup)
121
122
  end
122
123
  end
@@ -155,9 +156,7 @@ module Mutant
155
156
  #
156
157
  # @api private
157
158
  #
158
- def dup_node
159
- node.dup
160
- end
159
+ alias_method :dup_node, :dup_input
161
160
  end
162
161
  end
163
162
  end
@@ -1,6 +1,162 @@
1
1
  module Mutant
2
2
  class Mutator
3
3
  class Node
4
+ # Mutator for pattern arguments
5
+ class PatternVariable < self
6
+
7
+ handle(Rubinius::AST::PatternVariable)
8
+
9
+ private
10
+
11
+ # Emit mutations
12
+ #
13
+ # @return [undefined]
14
+ #
15
+ # @api private
16
+ #
17
+ def dispatch
18
+ emit_attribute_mutations(:name)
19
+ end
20
+ end
21
+
22
+ # Mutantor for default arguments
23
+ class DefaultArguments < self
24
+ handle(Rubinius::AST::DefaultArguments)
25
+
26
+ private
27
+
28
+ # Emit mutations
29
+ #
30
+ # @return [undefined]
31
+ #
32
+ # @api private
33
+ #
34
+ def dispatch
35
+ emit_attribute_mutations(:arguments) do |argument|
36
+ argument.names = argument.arguments.map(&:name)
37
+ end
38
+ end
39
+ end
40
+
41
+ # Mutator for pattern arguments
42
+ class PatternArguments < self
43
+
44
+ handle(Rubinius::AST::PatternArguments)
45
+
46
+ private
47
+
48
+ # Emit mutations
49
+ #
50
+ # @return [undefined]
51
+ #
52
+ # @api private
53
+ #
54
+ def dispatch
55
+ Mutator.each(node.arguments.body) do |mutation|
56
+ dup = dup_node
57
+ dup.arguments.body = mutation
58
+ emit(dup)
59
+ end
60
+ end
61
+
62
+ # Test if mutation should be skipped
63
+ #
64
+ # @return [true]
65
+ # if mutation should be skipped
66
+ #
67
+ # @return [false]
68
+ # otherwise
69
+ #
70
+ # @api private
71
+ #
72
+ def allow?(object)
73
+ object.arguments.body.size >= 2
74
+ end
75
+ end
76
+
77
+ # Mutator for formal arguments
78
+ class FormatlArguments19 < self
79
+
80
+ private
81
+
82
+ handle(Rubinius::AST::FormalArguments19)
83
+
84
+ # Emit mutations
85
+ #
86
+ # @return [undefined]
87
+ #
88
+ # @api private
89
+ #
90
+ def dispatch
91
+ expand_pattern_args
92
+ emit_default_mutations
93
+ emit_required_defaults_mutation
94
+ emit_attribute_mutations(:required) do |mutation|
95
+ mutation.names = mutation.optional + mutation.required
96
+ end
97
+ end
98
+
99
+ # Emit default mutations
100
+ #
101
+ # @return [undefined]
102
+ #
103
+ # @api private
104
+ #
105
+ def emit_default_mutations
106
+ return unless node.defaults
107
+ emit_attribute_mutations(:defaults) do |mutation|
108
+ mutation.optional = mutation.defaults.names
109
+ mutation.names = mutation.required + mutation.optional
110
+ if mutation.defaults.names.empty?
111
+ mutation.defaults = nil
112
+ end
113
+ end
114
+ end
115
+
116
+ # Emit required defaults mutations
117
+ #
118
+ # @return [undefined]
119
+ #
120
+ # @api private
121
+ #
122
+ def emit_required_defaults_mutation
123
+ return unless node.defaults
124
+ arguments = node.defaults.arguments
125
+ arguments.each_index do |index|
126
+ names = arguments.take(index+1).map(&:name)
127
+ dup = dup_node
128
+ defaults = dup.defaults
129
+ defaults.arguments = defaults.arguments.drop(names.size)
130
+ names.each { |name| dup.optional.delete(name) }
131
+ dup.required.concat(names)
132
+ if dup.optional.empty?
133
+ dup.defaults = nil
134
+ end
135
+ emit(dup)
136
+ end
137
+ end
138
+
139
+ # Emit pattern args expansions
140
+ #
141
+ # @return [undefined]
142
+ #
143
+ # @api private
144
+ #
145
+ def expand_pattern_args
146
+ node.required.each_with_index do |argument, index|
147
+ next unless argument.kind_of?(Rubinius::AST::PatternArguments)
148
+ dup = dup_node
149
+ required = dup.required
150
+ required.delete_at(index)
151
+ argument.arguments.body.reverse.each do |node|
152
+ required.insert(index, node.name)
153
+ end
154
+ dup.names |= required
155
+ emit(dup)
156
+ end
157
+ end
158
+ end
159
+
4
160
  # Mutator for arguments
5
161
  class Arguments < self
6
162