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.
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