mutant 0.3.0.beta4 → 0.3.0.beta5
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.
- checksums.yaml +4 -4
- data/.travis.yml +1 -1
- data/README.md +4 -4
- data/TODO +0 -9
- data/bin/mutant +2 -2
- data/bin/zombie +2 -2
- data/config/flay.yml +1 -1
- data/config/reek.yml +1 -2
- data/lib/mutant.rb +3 -0
- data/lib/mutant/cache.rb +30 -0
- data/lib/mutant/cli.rb +4 -1
- data/lib/mutant/cli/classifier.rb +17 -34
- data/lib/mutant/cli/classifier/method.rb +2 -2
- data/lib/mutant/cli/classifier/namespace.rb +1 -1
- data/lib/mutant/config.rb +1 -1
- data/lib/mutant/constants.rb +2 -0
- data/lib/mutant/differ.rb +3 -3
- data/lib/mutant/loader.rb +1 -1
- data/lib/mutant/matcher.rb +3 -3
- data/lib/mutant/matcher/method.rb +6 -73
- data/lib/mutant/matcher/method/finder.rb +72 -0
- data/lib/mutant/matcher/method/singleton.rb +2 -2
- data/lib/mutant/matcher/methods.rb +2 -2
- data/lib/mutant/matcher/namespace.rb +2 -2
- data/lib/mutant/matcher/scope.rb +2 -2
- data/lib/mutant/mutation.rb +4 -1
- data/lib/mutant/mutator/node.rb +1 -1
- data/lib/mutant/zombifier.rb +255 -0
- data/mutant.gemspec +3 -3
- data/spec/integration/mutant/zombie_spec.rb +3 -3
- data/spec/shared/method_matcher_behavior.rb +3 -4
- data/spec/spec_helper.rb +4 -0
- data/spec/unit/mutant/cli/class_methods/new_spec.rb +6 -6
- data/spec/unit/mutant/cli/classifier/class_methods/build_spec.rb +9 -8
- data/spec/unit/mutant/context/scope/root_spec.rb +3 -3
- data/spec/unit/mutant/killer/rspec/class_methods/new_spec.rb +7 -5
- data/spec/unit/mutant/matcher/method/instance/each_spec.rb +8 -7
- data/spec/unit/mutant/matcher/method/singleton/each_spec.rb +5 -4
- data/spec/unit/mutant/matcher/methods/instance/each_spec.rb +5 -4
- data/spec/unit/mutant/matcher/methods/singleton/each_spec.rb +5 -4
- data/spec/unit/mutant/matcher/namespace/each_spec.rb +5 -3
- data/spec/unit/mutant/strategy/rspec/dm2/lookup/method/instance/spec_files_spec.rb +1 -1
- data/spec/unit/mutant/strategy/rspec/dm2/lookup/method/singleton/spec_files_spec.rb +2 -2
- data/spec/unit/mutant/subject/context_spec.rb +2 -2
- data/spec/unit/mutant/subject/each_spec.rb +1 -1
- data/spec/unit/mutant/subject/node_spec.rb +2 -2
- metadata +9 -8
- data/spec/support/zombie.rb +0 -174
@@ -0,0 +1,72 @@
|
|
1
|
+
module Mutant
|
2
|
+
class Matcher
|
3
|
+
class Method
|
4
|
+
|
5
|
+
# Visitor to find last match inside AST
|
6
|
+
class Finder
|
7
|
+
|
8
|
+
# Run finder
|
9
|
+
#
|
10
|
+
# @param [Parser::AST::Node]
|
11
|
+
#
|
12
|
+
# @return [Parser::AST::Node]
|
13
|
+
# if found
|
14
|
+
#
|
15
|
+
# @return [nil]
|
16
|
+
# otherwise
|
17
|
+
#
|
18
|
+
# @api private
|
19
|
+
#
|
20
|
+
#
|
21
|
+
def self.run(root, &predicate)
|
22
|
+
new(root, predicate).match
|
23
|
+
end
|
24
|
+
|
25
|
+
private_class_method :new
|
26
|
+
|
27
|
+
# Return match
|
28
|
+
#
|
29
|
+
# @return [Parser::AST::Node]
|
30
|
+
#
|
31
|
+
# @api private
|
32
|
+
#
|
33
|
+
attr_reader :match
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
# Initialize object
|
38
|
+
#
|
39
|
+
# @param [Parer::AST::Node]
|
40
|
+
#
|
41
|
+
# @return [undefined]
|
42
|
+
#
|
43
|
+
# @api private
|
44
|
+
#
|
45
|
+
#
|
46
|
+
def initialize(root, predicate)
|
47
|
+
@root, @predicate = root, predicate
|
48
|
+
visit(root)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Visit node
|
52
|
+
#
|
53
|
+
# @param [Parser::AST::Node] node
|
54
|
+
#
|
55
|
+
# @return [undefined]
|
56
|
+
#
|
57
|
+
# @api private
|
58
|
+
#
|
59
|
+
def visit(node)
|
60
|
+
if @predicate.call(node)
|
61
|
+
@match = node
|
62
|
+
end
|
63
|
+
|
64
|
+
node.children.each do |child|
|
65
|
+
visit(child) if child.kind_of?(Parser::AST::Node)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
end # Finder
|
70
|
+
end # Method
|
71
|
+
end # Matcher
|
72
|
+
end # Mutant
|
@@ -2,7 +2,7 @@ module Mutant
|
|
2
2
|
class Matcher
|
3
3
|
# Abstract base class for matcher that returns method subjects extracted from scope
|
4
4
|
class Methods < self
|
5
|
-
include AbstractType, Concord::Public.new(:scope)
|
5
|
+
include AbstractType, Concord::Public.new(:cache, :scope)
|
6
6
|
|
7
7
|
# Enumerate subjects
|
8
8
|
#
|
@@ -59,7 +59,7 @@ module Mutant
|
|
59
59
|
# @api private
|
60
60
|
#
|
61
61
|
def emit_matches(method)
|
62
|
-
matcher.new(scope, method).each do |subject|
|
62
|
+
matcher.new(cache, scope, method).each do |subject|
|
63
63
|
yield subject
|
64
64
|
end
|
65
65
|
end
|
@@ -3,7 +3,7 @@ module Mutant
|
|
3
3
|
|
4
4
|
# Matcher for specific namespace
|
5
5
|
class Namespace < self
|
6
|
-
include Concord::Public.new(:namespace)
|
6
|
+
include Concord::Public.new(:cache, :namespace)
|
7
7
|
|
8
8
|
# Enumerate subjects
|
9
9
|
#
|
@@ -19,7 +19,7 @@ module Mutant
|
|
19
19
|
return to_enum unless block_given?
|
20
20
|
|
21
21
|
scopes.each do |scope|
|
22
|
-
Scope.each(scope, &block)
|
22
|
+
Scope.each(cache, scope, &block)
|
23
23
|
end
|
24
24
|
|
25
25
|
self
|
data/lib/mutant/matcher/scope.rb
CHANGED
@@ -2,7 +2,7 @@ module Mutant
|
|
2
2
|
class Matcher
|
3
3
|
# Matcher for specific namespace
|
4
4
|
class Scope < self
|
5
|
-
include Concord::Public.new(:scope)
|
5
|
+
include Concord::Public.new(:cache, :scope)
|
6
6
|
|
7
7
|
MATCHERS = [
|
8
8
|
Matcher::Methods::Singleton,
|
@@ -23,7 +23,7 @@ module Mutant
|
|
23
23
|
return to_enum unless block_given?
|
24
24
|
|
25
25
|
MATCHERS.each do |matcher|
|
26
|
-
matcher.each(scope, &block)
|
26
|
+
matcher.each(cache, scope, &block)
|
27
27
|
end
|
28
28
|
|
29
29
|
self
|
data/lib/mutant/mutation.rb
CHANGED
@@ -30,7 +30,10 @@ module Mutant
|
|
30
30
|
|
31
31
|
# Insert mutated node
|
32
32
|
#
|
33
|
-
# FIXME:
|
33
|
+
# FIXME:
|
34
|
+
# Cache subject visibility in a better way! Ideally dont mutate it implicitly.
|
35
|
+
# Also subject.public? should NOT be a public interface it is a detail of method
|
36
|
+
# mutations.
|
34
37
|
#
|
35
38
|
# @return [self]
|
36
39
|
#
|
data/lib/mutant/mutator/node.rb
CHANGED
@@ -0,0 +1,255 @@
|
|
1
|
+
module Mutant
|
2
|
+
# Zombifier namespace
|
3
|
+
module Zombifier
|
4
|
+
|
5
|
+
# Excluded from zombification, reasons
|
6
|
+
#
|
7
|
+
# * Relies dynamic require, zombifier does not know how to recurse here (racc)
|
8
|
+
# * Unparser bug (optparse)
|
9
|
+
# * Toplevel reference/cbase nodes in code (rspec)
|
10
|
+
# * Creates useless toplevel modules that get vendored under ::Zombie (set)
|
11
|
+
#
|
12
|
+
STOP = %w(
|
13
|
+
set
|
14
|
+
rspec
|
15
|
+
diff/lcs
|
16
|
+
parser
|
17
|
+
parser/all
|
18
|
+
parser/current
|
19
|
+
racc/parser
|
20
|
+
optparse
|
21
|
+
).to_set
|
22
|
+
|
23
|
+
# Perform self zombification
|
24
|
+
#
|
25
|
+
# @return [self]
|
26
|
+
#
|
27
|
+
# @api private
|
28
|
+
#
|
29
|
+
def self.zombify
|
30
|
+
run('mutant')
|
31
|
+
end
|
32
|
+
|
33
|
+
# Zombify gem
|
34
|
+
#
|
35
|
+
# @param [String] name
|
36
|
+
#
|
37
|
+
# @return [self]
|
38
|
+
#
|
39
|
+
# @api private
|
40
|
+
#
|
41
|
+
def self.run(name)
|
42
|
+
Gem.new(name).zombify
|
43
|
+
end
|
44
|
+
|
45
|
+
# Zombifier subject, compatible with mutants loader
|
46
|
+
class Subject < Mutant::Subject
|
47
|
+
include NodeHelpers
|
48
|
+
|
49
|
+
# Return new object
|
50
|
+
#
|
51
|
+
# @param [File]
|
52
|
+
#
|
53
|
+
# @return [Subject]
|
54
|
+
#
|
55
|
+
# @api private
|
56
|
+
#
|
57
|
+
def self.new(file)
|
58
|
+
super(file, file.node)
|
59
|
+
end
|
60
|
+
|
61
|
+
# Perform zombification on subject
|
62
|
+
#
|
63
|
+
# @return [self]
|
64
|
+
#
|
65
|
+
# @api private
|
66
|
+
#
|
67
|
+
def zombify
|
68
|
+
$stderr.puts "Zombifying #{context.source_path}"
|
69
|
+
Loader::Eval.run(zombified_root, self)
|
70
|
+
self
|
71
|
+
end
|
72
|
+
memoize :zombify
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
# Return zombified root
|
77
|
+
#
|
78
|
+
# @return [Parser::AST::Node]
|
79
|
+
#
|
80
|
+
# @api private
|
81
|
+
#
|
82
|
+
def zombified_root
|
83
|
+
s(:module, s(:const, nil, :Zombie), node)
|
84
|
+
end
|
85
|
+
|
86
|
+
end # Subject
|
87
|
+
|
88
|
+
# File containing source beeing zombified
|
89
|
+
class File
|
90
|
+
include Adamantium::Flat, Concord::Public.new(:source_path)
|
91
|
+
|
92
|
+
CACHE = {}
|
93
|
+
|
94
|
+
# Zombify contents of file
|
95
|
+
#
|
96
|
+
# @return [self]
|
97
|
+
#
|
98
|
+
# @api private
|
99
|
+
#
|
100
|
+
def zombify
|
101
|
+
subject.zombify
|
102
|
+
required_paths.each do |path|
|
103
|
+
file = File.find(path)
|
104
|
+
next unless file
|
105
|
+
file.zombify
|
106
|
+
end
|
107
|
+
self
|
108
|
+
end
|
109
|
+
|
110
|
+
# Find file
|
111
|
+
#
|
112
|
+
# @param [String] logical_name
|
113
|
+
#
|
114
|
+
# @return [File]
|
115
|
+
# if found
|
116
|
+
#
|
117
|
+
# @raise [RuntimeError]
|
118
|
+
# if file cannot be found
|
119
|
+
#
|
120
|
+
def self.find(logical_name)
|
121
|
+
return if STOP.include?(logical_name)
|
122
|
+
CACHE.fetch(logical_name) do
|
123
|
+
CACHE[logical_name] = find_uncached(logical_name)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
# Find file without cache
|
128
|
+
#
|
129
|
+
# @param [String] logical_name
|
130
|
+
#
|
131
|
+
# @return [File]
|
132
|
+
# if found
|
133
|
+
#
|
134
|
+
# @return [nil]
|
135
|
+
# otherwise
|
136
|
+
#
|
137
|
+
# @api private
|
138
|
+
#
|
139
|
+
def self.find_uncached(logical_name)
|
140
|
+
file_name =
|
141
|
+
unless logical_name.end_with?('.rb')
|
142
|
+
"#{logical_name}.rb"
|
143
|
+
else
|
144
|
+
logical_name
|
145
|
+
end
|
146
|
+
|
147
|
+
$LOAD_PATH.each do |path|
|
148
|
+
path = Pathname.new(path).join(file_name)
|
149
|
+
if path.exist?
|
150
|
+
$stderr.puts "Loading #{path}"
|
151
|
+
return new(path)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
$stderr.puts "Cannot find #{file_name} in $LOAD_PATH"
|
156
|
+
nil
|
157
|
+
end
|
158
|
+
|
159
|
+
# Return subject
|
160
|
+
#
|
161
|
+
# @return [Subject]
|
162
|
+
#
|
163
|
+
# @api private
|
164
|
+
#
|
165
|
+
def subject
|
166
|
+
Subject.new(self)
|
167
|
+
end
|
168
|
+
memoize :subject
|
169
|
+
|
170
|
+
# Return node
|
171
|
+
#
|
172
|
+
# @return [Parser::AST::Node]
|
173
|
+
#
|
174
|
+
# @api private
|
175
|
+
#
|
176
|
+
def node
|
177
|
+
Parser::CurrentRuby.parse(::File.read(source_path))
|
178
|
+
end
|
179
|
+
memoize :node
|
180
|
+
|
181
|
+
RECEIVER_INDEX = 0
|
182
|
+
SELECTOR_INDEX = 1
|
183
|
+
ARGUMENT_INDEX = 2..-1.freeze
|
184
|
+
|
185
|
+
# Return required paths
|
186
|
+
#
|
187
|
+
# @return [Enumerable<String>]
|
188
|
+
#
|
189
|
+
# @api private
|
190
|
+
#
|
191
|
+
def required_paths
|
192
|
+
require_nodes.map do |node|
|
193
|
+
arguments = node.children[ARGUMENT_INDEX]
|
194
|
+
unless arguments.length == 1
|
195
|
+
raise "Require node with not exactly one argument: #{node}"
|
196
|
+
end
|
197
|
+
argument = arguments.first
|
198
|
+
unless argument.type == :str
|
199
|
+
raise "Require argument is not a literal string: #{argument}"
|
200
|
+
end
|
201
|
+
argument.children.first
|
202
|
+
end
|
203
|
+
end
|
204
|
+
memoize :required_paths
|
205
|
+
|
206
|
+
private
|
207
|
+
|
208
|
+
# Return require nodes
|
209
|
+
#
|
210
|
+
# @return [Enumerable<Parser::AST::Node>]
|
211
|
+
#
|
212
|
+
# @api private
|
213
|
+
#
|
214
|
+
def require_nodes
|
215
|
+
children = node.type == :begin ? node.children : [node]
|
216
|
+
children.select do |node|
|
217
|
+
children = node.children
|
218
|
+
node.type == :send && children.at(RECEIVER_INDEX).nil? && children.at(SELECTOR_INDEX) == :require
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
end # File
|
223
|
+
|
224
|
+
# Gem beeing zombified
|
225
|
+
class Gem
|
226
|
+
include Adamantium::Flat, Concord.new(:name)
|
227
|
+
|
228
|
+
# Return subjects
|
229
|
+
#
|
230
|
+
# @return [Enumerable<Subject>]
|
231
|
+
#
|
232
|
+
# @api private
|
233
|
+
#
|
234
|
+
def zombify
|
235
|
+
root_file.zombify
|
236
|
+
end
|
237
|
+
memoize :zombify
|
238
|
+
|
239
|
+
private
|
240
|
+
|
241
|
+
# Return root souce file
|
242
|
+
#
|
243
|
+
# @return [File]
|
244
|
+
#
|
245
|
+
# @api private
|
246
|
+
#
|
247
|
+
def root_file
|
248
|
+
File.find(name) || raise("No root file!")
|
249
|
+
end
|
250
|
+
memoize :root_file
|
251
|
+
|
252
|
+
end # Gem
|
253
|
+
|
254
|
+
end # Zombifier
|
255
|
+
end # Mutant
|
data/mutant.gemspec
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |gem|
|
4
4
|
gem.name = 'mutant'
|
5
|
-
gem.version = '0.3.0.
|
5
|
+
gem.version = '0.3.0.beta5'
|
6
6
|
gem.authors = [ 'Markus Schirp' ]
|
7
7
|
gem.email = [ 'mbj@schirp-dso.com' ]
|
8
8
|
|
@@ -16,8 +16,8 @@ Gem::Specification.new do |gem|
|
|
16
16
|
gem.extra_rdoc_files = %w[TODO LICENSE]
|
17
17
|
gem.executables = [ 'mutant' ]
|
18
18
|
|
19
|
-
gem.add_runtime_dependency('parser', '~> 2.0.
|
20
|
-
gem.add_runtime_dependency('unparser', '~> 0.0.
|
19
|
+
gem.add_runtime_dependency('parser', '~> 2.0.beta9')
|
20
|
+
gem.add_runtime_dependency('unparser', '~> 0.0.6')
|
21
21
|
gem.add_runtime_dependency('ice_nine', '~> 0.8.0')
|
22
22
|
gem.add_runtime_dependency('descendants_tracker', '~> 0.0.1')
|
23
23
|
gem.add_runtime_dependency('adamantium', '~> 0.0.8')
|
@@ -1,8 +1,8 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Mutant, 'as a zombie' do
|
4
|
-
|
5
|
-
|
6
|
-
Zombie
|
4
|
+
specify 'it allows to create zombie from mutant' do
|
5
|
+
Mutant::Zombifier.run('mutant')
|
6
|
+
Zombie.constants.should include(:Mutant)
|
7
7
|
end
|
8
8
|
end
|