mutant 0.3.0.beta4 → 0.3.0.beta5
Sign up to get free protection for your applications and to get access to all the features.
- 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
|