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.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -1
  3. data/README.md +4 -4
  4. data/TODO +0 -9
  5. data/bin/mutant +2 -2
  6. data/bin/zombie +2 -2
  7. data/config/flay.yml +1 -1
  8. data/config/reek.yml +1 -2
  9. data/lib/mutant.rb +3 -0
  10. data/lib/mutant/cache.rb +30 -0
  11. data/lib/mutant/cli.rb +4 -1
  12. data/lib/mutant/cli/classifier.rb +17 -34
  13. data/lib/mutant/cli/classifier/method.rb +2 -2
  14. data/lib/mutant/cli/classifier/namespace.rb +1 -1
  15. data/lib/mutant/config.rb +1 -1
  16. data/lib/mutant/constants.rb +2 -0
  17. data/lib/mutant/differ.rb +3 -3
  18. data/lib/mutant/loader.rb +1 -1
  19. data/lib/mutant/matcher.rb +3 -3
  20. data/lib/mutant/matcher/method.rb +6 -73
  21. data/lib/mutant/matcher/method/finder.rb +72 -0
  22. data/lib/mutant/matcher/method/singleton.rb +2 -2
  23. data/lib/mutant/matcher/methods.rb +2 -2
  24. data/lib/mutant/matcher/namespace.rb +2 -2
  25. data/lib/mutant/matcher/scope.rb +2 -2
  26. data/lib/mutant/mutation.rb +4 -1
  27. data/lib/mutant/mutator/node.rb +1 -1
  28. data/lib/mutant/zombifier.rb +255 -0
  29. data/mutant.gemspec +3 -3
  30. data/spec/integration/mutant/zombie_spec.rb +3 -3
  31. data/spec/shared/method_matcher_behavior.rb +3 -4
  32. data/spec/spec_helper.rb +4 -0
  33. data/spec/unit/mutant/cli/class_methods/new_spec.rb +6 -6
  34. data/spec/unit/mutant/cli/classifier/class_methods/build_spec.rb +9 -8
  35. data/spec/unit/mutant/context/scope/root_spec.rb +3 -3
  36. data/spec/unit/mutant/killer/rspec/class_methods/new_spec.rb +7 -5
  37. data/spec/unit/mutant/matcher/method/instance/each_spec.rb +8 -7
  38. data/spec/unit/mutant/matcher/method/singleton/each_spec.rb +5 -4
  39. data/spec/unit/mutant/matcher/methods/instance/each_spec.rb +5 -4
  40. data/spec/unit/mutant/matcher/methods/singleton/each_spec.rb +5 -4
  41. data/spec/unit/mutant/matcher/namespace/each_spec.rb +5 -3
  42. data/spec/unit/mutant/strategy/rspec/dm2/lookup/method/instance/spec_files_spec.rb +1 -1
  43. data/spec/unit/mutant/strategy/rspec/dm2/lookup/method/singleton/spec_files_spec.rb +2 -2
  44. data/spec/unit/mutant/subject/context_spec.rb +2 -2
  45. data/spec/unit/mutant/subject/each_spec.rb +1 -1
  46. data/spec/unit/mutant/subject/node_spec.rb +2 -2
  47. metadata +9 -8
  48. 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
@@ -16,8 +16,8 @@ module Mutant
16
16
  end
17
17
  memoize :identification
18
18
 
19
- RECEIVER_INDEX = 0
20
- NAME_INDEX = 1
19
+ RECEIVER_INDEX = 0
20
+ NAME_INDEX = 1
21
21
  CONST_NAME_INDEX = 1
22
22
 
23
23
  private
@@ -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
@@ -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
@@ -30,7 +30,10 @@ module Mutant
30
30
 
31
31
  # Insert mutated node
32
32
  #
33
- # FIXME: Cache subject visibility in a better way! Ideally dont mutate it.
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
  #
@@ -173,7 +173,7 @@ module Mutant
173
173
 
174
174
  # Emit a new AST node with NilLiteral class
175
175
  #
176
- # @return [Rubinius::AST::NilLiteral]
176
+ # @return [undefined]
177
177
  #
178
178
  # @api private
179
179
  #
@@ -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.beta4'
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.beta7')
20
- gem.add_runtime_dependency('unparser', '~> 0.0.4')
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
- pending 'allows to create zombie from mutant' do
5
- Zombie.setup
6
- Zombie::Runner
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