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.
- data/.travis.yml +3 -3
- data/Changelog.md +21 -0
- data/Gemfile.devtools +1 -0
- data/Guardfile +1 -1
- data/README.md +48 -4
- data/config/flay.yml +2 -2
- data/config/flog.yml +1 -1
- data/config/site.reek +3 -1
- data/lib/mutant.rb +14 -2
- data/lib/mutant/cli.rb +38 -39
- data/lib/mutant/context/scope.rb +37 -32
- data/lib/mutant/killer/forking.rb +53 -0
- data/lib/mutant/killer/rspec.rb +1 -1
- data/lib/mutant/killer/static.rb +14 -0
- data/lib/mutant/matcher.rb +2 -0
- data/lib/mutant/matcher/method.rb +2 -2
- data/lib/mutant/matcher/method/singleton.rb +2 -1
- data/lib/mutant/matcher/object_space.rb +1 -1
- data/lib/mutant/matcher/scope_methods.rb +2 -0
- data/lib/mutant/mutation.rb +26 -0
- data/lib/mutant/mutation/filter/whitelist.rb +1 -1
- data/lib/mutant/mutator.rb +52 -9
- data/lib/mutant/mutator/node.rb +18 -19
- data/lib/mutant/mutator/node/arguments.rb +156 -0
- data/lib/mutant/mutator/node/block.rb +7 -20
- data/lib/mutant/mutator/node/define.rb +18 -1
- data/lib/mutant/mutator/node/iter_19.rb +26 -0
- data/lib/mutant/mutator/node/local_variable_assignment.rb +25 -0
- data/lib/mutant/mutator/node/noop.rb +4 -0
- data/lib/mutant/mutator/node/send.rb +24 -10
- data/lib/mutant/mutator/util.rb +28 -1
- data/lib/mutant/random.rb +1 -0
- data/lib/mutant/reporter.rb +28 -0
- data/lib/mutant/reporter/cli.rb +90 -19
- data/lib/mutant/reporter/null.rb +5 -3
- data/lib/mutant/reporter/stats.rb +65 -9
- data/lib/mutant/runner.rb +41 -2
- data/lib/mutant/strategy.rb +46 -5
- data/lib/mutant/strategy/rspec.rb +11 -4
- data/lib/mutant/strategy/rspec/example_lookup.rb +30 -30
- data/lib/mutant/subject.rb +11 -0
- data/mutant.gemspec +3 -2
- data/spec/integration/mutant/loader_spec.rb +4 -4
- data/spec/shared/mutator_behavior.rb +13 -1
- data/spec/unit/mutant/context/scope/root_spec.rb +20 -8
- data/spec/unit/mutant/context/scope/unqualified_name_spec.rb +2 -2
- data/spec/unit/mutant/killer/rspec/class_methods/new_spec.rb +1 -1
- data/spec/unit/mutant/matcher/chain/each_spec.rb +6 -2
- data/spec/unit/mutant/mutator/node/define/mutation_spec.rb +76 -0
- data/spec/unit/mutant/mutator/node/send/mutation_spec.rb +80 -21
- data/spec/unit/mutant/strategy/rspec/example_lookup/spec_file_spec.rb +3 -3
- metadata +21 -10
- data/lib/mutant/inflector/defaults.rb +0 -64
- data/lib/mutant/inflector/inflections.rb +0 -211
- data/lib/mutant/inflector/methods.rb +0 -151
- data/lib/mutant/inflector/version.rb +0 -5
- data/locator.rb +0 -87
- 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
|
data/lib/mutant/killer/rspec.rb
CHANGED
@@ -29,7 +29,7 @@ module Mutant
|
|
29
29
|
#
|
30
30
|
def run
|
31
31
|
mutation.insert
|
32
|
-
!::RSpec::Core::Runner.run(command_line_arguments,
|
32
|
+
!::RSpec::Core::Runner.run(command_line_arguments, strategy.error_stream, strategy.output_stream).zero?
|
33
33
|
end
|
34
34
|
memoize :run
|
35
35
|
|
data/lib/mutant/killer/static.rb
CHANGED
@@ -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
|
data/lib/mutant/matcher.rb
CHANGED
@@ -72,7 +72,7 @@ module Mutant
|
|
72
72
|
# @return [true]
|
73
73
|
# if method is public
|
74
74
|
#
|
75
|
-
# @
|
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.
|
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
|
-
|
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
|
|
data/lib/mutant/mutation.rb
CHANGED
@@ -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
|
data/lib/mutant/mutator.rb
CHANGED
@@ -12,9 +12,9 @@ module Mutant
|
|
12
12
|
#
|
13
13
|
# @api private
|
14
14
|
#
|
15
|
-
def self.each(
|
16
|
-
return to_enum(__method__,
|
17
|
-
Registry.lookup(
|
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 =
|
56
|
-
|
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
|
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
|
-
|
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
|
192
|
+
Helper.deep_clone(input)
|
150
193
|
end
|
151
194
|
|
152
195
|
end
|
data/lib/mutant/mutator/node.rb
CHANGED
@@ -5,35 +5,35 @@ module Mutant
|
|
5
5
|
class Node < self
|
6
6
|
include AbstractType
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
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
|
16
|
+
def self.identity(node)
|
20
17
|
ToSource.to_source(node)
|
21
18
|
end
|
22
|
-
memoize :source
|
23
19
|
|
24
|
-
|
20
|
+
private
|
21
|
+
|
22
|
+
# Return mutated node
|
25
23
|
#
|
26
|
-
# @return [
|
27
|
-
# if generated node is different from input
|
24
|
+
# @return [Rubinius::AST::Node]
|
28
25
|
#
|
29
|
-
# @
|
30
|
-
#
|
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
|
-
|
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
|
-
|
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
|
|