heckle 1.4.1 → 1.4.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,16 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'autotest/restart'
4
+
5
+ Autotest.add_hook :initialize do |at|
6
+ at.libs << ":../../RubyInline/dev/lib"
7
+ at.libs << ":../../ParseTree/dev/lib:../../ParseTree/dev/test"
8
+ at.libs << ":../../sexp_processor/dev/lib"
9
+ at.libs << ":../../ruby2ruby/dev/lib"
10
+
11
+ %w(Dasgn Iter Dasgncurr Cvasgn Boolean Call Callblock
12
+ ClassMethod Gasgn Iasgn If Lasgn Masgn Ranges Regexes
13
+ SameLiteral Strings Symbols Until While).each do |klass|
14
+ at.extra_class_map["TestHeckle#{klass}"] = "test/test_heckle.rb"
15
+ end
16
+ end
@@ -1,6 +1,16 @@
1
+ === 1.4.2 / 2009-02-08
2
+
3
+ * 2 bug fixes:
4
+
5
+ * Fixed Ruby2Ruby dependency and Ruby2Ruby references (name changed).
6
+ Reported by David Chelimsky
7
+ * Fix bug #11435 where [:iter, [:call], ...] would cause an endless
8
+ loop. Reported by Thomas Preymesser.
9
+
1
10
  === 1.4.1 / 2007-06-05
2
11
 
3
12
  * 3 bug fixes:
13
+
4
14
  * Add zentest as a heckle dependency. Closes #10996
5
15
  * Fixed heckling of call with blocks.
6
16
  * Fix test_unit_heckler's test_pass? so it returns the result of the
@@ -9,9 +19,12 @@
9
19
  === 1.4.0 / 2007-05-18
10
20
 
11
21
  * 2 major enhancements:
22
+
12
23
  * Method calls are now heckled (by removal).
13
24
  * Assignments are now heckled (by value changing).
25
+
14
26
  * 3 minor enhancements:
27
+
15
28
  * Added --focus to feel the Eye of Sauron (specify unit tests to run).
16
29
  * Specify nodes to be included/excluded in heckle with -n/-x.
17
30
  * Test only assignments with --assignments
@@ -19,31 +32,42 @@
19
32
  === 1.3.0 / 2007-02-12
20
33
 
21
34
  * 1 major enhancement:
35
+
22
36
  * Unified diffs for mutatated methods
37
+
23
38
  * 4 minor enhancements:
39
+
24
40
  * Now returns exit status 1 if failed.
25
41
  * Added a simple report at the end.
26
42
  * Runs are now sorted by method.
27
43
  * Autodetects rails and changes test_pattern accordingly.
44
+
28
45
  * 2 bug fixes:
46
+
29
47
  * Aborts when an unknown method is supplied.
30
48
  * Escapes slashes in random regexps.
31
49
 
32
50
  === 1.2.0 / 2007-01-15
33
51
 
34
52
  * 2 major enhancements:
53
+
35
54
  * Timeout for tests set dynamically and overridable with -T
36
55
  * Class method support with "self.method_name"
56
+
37
57
  * 3 minor enhancements:
58
+
38
59
  * -b allows heckling of branches only
39
60
  * Restructured class heirarchy and got rid of Base and others.
40
61
  * Revamped the tests and reduced size by 60%.
62
+
41
63
  * 1 bug fix:
64
+
42
65
  * Fixed the infinite loop caused by syntax errors
43
66
 
44
67
  === 1.1.1 / 2006-12-20
45
68
 
46
69
  * 3 bug fixes:
70
+
47
71
  * Load tests properly when supplying method name.
48
72
  * Make sure random symbols have at least one character.
49
73
  * Removed all extra warnings from the unit tests. Consolidated and cleaned.
@@ -51,6 +75,7 @@
51
75
  === 1.1.0 / 2006-12-19
52
76
 
53
77
  * 12 major enhancements:
78
+
54
79
  * Able to roll back original method after processing.
55
80
  * Can mutate numeric literals.
56
81
  * Can mutate strings.
@@ -67,4 +92,5 @@
67
92
  === 1.0.0 / 2006-10-22
68
93
 
69
94
  * 1 major enhancement
95
+
70
96
  * Birthday!
@@ -1,3 +1,4 @@
1
+ .autotest
1
2
  History.txt
2
3
  Manifest.txt
3
4
  README.txt
data/README.txt CHANGED
@@ -1,10 +1,11 @@
1
- heckle
2
- http://www.rubyforge.org/projects/seattlerb
3
- by Ryan Davis and Kevin Clark
1
+ = heckle
2
+
3
+ * http://www.rubyforge.org/projects/seattlerb
4
+ * http://seattlerb.rubyforge.org/heckle
4
5
 
5
6
  == DESCRIPTION:
6
7
 
7
- Heckle is a mutation tester. It modifies your code and runs your tests to make sure they fail. The idea is that if code can be changed and your tests don't notice, either that code isn't being covered or it doesn't do anything.
8
+ Heckle is unit test sadism(tm) at it's core. Heckle is a mutation tester. It modifies your code and runs your tests to make sure they fail. The idea is that if code can be changed and your tests don't notice, either that code isn't being covered or it doesn't do anything.
8
9
 
9
10
  It's like hiring a white-hat hacker to try to break into your server and making sure you detect it. You learn the most by trying to break things and watching the outcome in an act of unit test sadism.
10
11
 
@@ -20,18 +21,18 @@ It's like hiring a white-hat hacker to try to break into your server and making
20
21
 
21
22
  == REQUIREMENTS:
22
23
 
23
- + ruby2ruby 1.1.2 or greater
24
- + ParseTree 1.6.1 or greater
24
+ * ruby2ruby 1.1.2 or greater
25
+ * ParseTree 1.6.1 or greater
25
26
 
26
27
  == INSTALL:
27
28
 
28
- + sudo gem install heckle
29
+ * sudo gem install heckle
29
30
 
30
31
  == LICENSE:
31
32
 
32
33
  (The MIT License)
33
34
 
34
- Copyright (c) 2006 Ryan Davis and Kevin Clark
35
+ Copyright (c) 2006-2008 Ryan Davis and Kevin Clark
35
36
 
36
37
  Permission is hereby granted, free of charge, to any person obtaining
37
38
  a copy of this software and associated documentation files (the
data/Rakefile CHANGED
@@ -1,21 +1,30 @@
1
1
  # -*- ruby -*-
2
2
 
3
- $: << 'lib'
4
-
5
3
  require 'rubygems'
6
4
  require 'hoe'
5
+
6
+ Hoe.add_include_dirs("../../ParseTree/dev/lib",
7
+ "../../ParseTree/dev/test",
8
+ "../../RubyInline/dev/lib",
9
+ "../../ruby2ruby/dev/lib",
10
+ "../../ZenTest/dev/lib",
11
+ "../../sexp_processor/dev/lib",
12
+ "lib")
13
+
7
14
  require './lib/heckle.rb'
8
15
 
9
- Hoe.new('heckle', Heckle::VERSION) do |p|
10
- p.rubyforge_name = 'seattlerb'
11
- p.summary = 'Unit Test Sadism'
12
- p.description = p.paragraphs_of('README.txt', 2).join("\n\n")
13
- p.url = p.paragraphs_of('README.txt', 0).first.split(/\n/)[1..-1]
14
- p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n")
16
+ Hoe.new('heckle', Heckle::VERSION) do |heckle|
17
+ heckle.rubyforge_name = 'seattlerb'
18
+
19
+ heckle.developer('Ryan Davis', 'ryand-ruby@zenspider.com')
20
+ heckle.developer('Eric Hodel', 'drbrain@segment7.net')
21
+ heckle.developer('Kevin Clark', 'kevin.clark@gmail.com')
22
+
23
+ heckle.clean_globs << File.expand_path("~/.ruby_inline")
15
24
 
16
- p.extra_deps << ['ruby2ruby', '>= 1.1.0']
17
- p.extra_deps << ['ZenTest', '>= 3.5.2']
18
-
25
+ heckle.extra_deps << ['ParseTree', '>= 2.0.0']
26
+ heckle.extra_deps << ['ruby2ruby', '>= 1.1.6']
27
+ heckle.extra_deps << ['ZenTest', '>= 3.5.2']
19
28
  end
20
29
 
21
30
  # vim: syntax=Ruby
data/bin/heckle CHANGED
@@ -4,25 +4,31 @@ $:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
4
4
  require 'test_unit_heckler'
5
5
  require 'optparse'
6
6
 
7
+ force = false
7
8
  nodes = Heckle::MUTATABLE_NODES
8
9
 
9
10
  opts = OptionParser.new do |opts|
10
11
  opts.banner = "Usage: #{File.basename($0)} class_name [method_name]"
11
- opts.on( "-v", "--verbose", "Loudly explain heckle run" ) do |opt|
12
+ opts.on("-v", "--verbose", "Loudly explain heckle run") do |opt|
12
13
  TestUnitHeckler.debug = true
13
14
  end
14
15
 
15
- opts.on( "-V", "--version", "Prints Heckle's version number") do |opt|
16
+ opts.on("-V", "--version", "Prints Heckle's version number") do |opt|
16
17
  puts "Heckle #{Heckle::VERSION}"
17
18
  exit 0
18
19
  end
19
20
 
20
- opts.on( "-t", "--tests TEST_PATTERN",
21
- "Location of tests (glob)" ) do |pattern|
21
+ opts.on("-t", "--tests TEST_PATTERN",
22
+ "Location of tests (glob)") do |pattern|
22
23
  TestUnitHeckler.test_pattern = pattern
23
24
  end
24
25
 
25
- opts.on( "--assignments", "Only mutate assignments" ) do |opt|
26
+ opts.on("-F", "--force", "Ignore initial test failures",
27
+ "Best used with --focus") do |opt|
28
+ force = true
29
+ end
30
+
31
+ opts.on( "--assignments", "Only mutate assignments") do |opt|
26
32
  puts "!"*70
27
33
  puts "!!! Heckling assignments only"
28
34
  puts "!"*70
@@ -31,7 +37,7 @@ opts = OptionParser.new do |opts|
31
37
  nodes = Heckle::ASGN_NODES
32
38
  end
33
39
 
34
- opts.on( "-b", "--branches", "Only mutate branches" ) do |opt|
40
+ opts.on("-b", "--branches", "Only mutate branches") do |opt|
35
41
  puts "!"*70
36
42
  puts "!!! Heckling branches only"
37
43
  puts "!"*70
@@ -40,7 +46,7 @@ opts = OptionParser.new do |opts|
40
46
  nodes = Heckle::BRANCH_NODES
41
47
  end
42
48
 
43
- opts.on( "-f", "--focus", "Apply the eye of sauron" ) do |opt|
49
+ opts.on("-f", "--focus", "Apply the eye of sauron") do |opt|
44
50
  puts "!"*70
45
51
  puts "!!! Running in focused mode. FEEL THE EYE OF SAURON!!!"
46
52
  puts "!"*70
@@ -49,25 +55,25 @@ opts = OptionParser.new do |opts|
49
55
  TestUnitHeckler.focus = true
50
56
  end
51
57
 
52
- opts.on( "-T", "--timeout SECONDS", "The maximum time for a test run in seconds",
58
+ opts.on("-T", "--timeout SECONDS", "The maximum time for a test run in seconds",
53
59
  "Used to catch infinite loops") do |timeout|
54
60
  Heckle.timeout = timeout.to_i
55
61
  puts "Setting timeout at #{timeout} seconds."
56
62
  end
57
63
 
58
- opts.on( "-n", "--nodes NODES", "Nodes to mutate",
59
- "Possible values: #{Heckle::MUTATABLE_NODES.join(',')}" ) do |opt|
64
+ opts.on("-n", "--nodes NODES", "Nodes to mutate",
65
+ "Possible values: #{Heckle::MUTATABLE_NODES.join(',')}") do |opt|
60
66
  nodes = opt.split(',').collect {|n| n.to_sym }
61
67
  puts "Mutating nodes: #{nodes.inspect}"
62
68
  end
63
69
 
64
- opts.on( "-x", "--exclude-nodes NODES", "Nodes to exclude") do |opt|
70
+ opts.on("-x", "--exclude-nodes NODES", "Nodes to exclude") do |opt|
65
71
  exclusions = opt.split(',').collect {|n| n.to_sym }
66
72
  nodes = nodes - exclusions
67
73
  puts "Mutating without nodes: #{exclusions.inspect}"
68
74
  end
69
75
 
70
- opts.on( "-h", "--help", "Show this message") do |opt|
76
+ opts.on("-h", "--help", "Show this message") do |opt|
71
77
  puts opts
72
78
  exit 0
73
79
  end
@@ -86,4 +92,5 @@ unless impl then
86
92
  exit 1
87
93
  end
88
94
 
89
- exit TestUnitHeckler.validate(impl, meth, nodes)
95
+ exit TestUnitHeckler.validate(impl, meth, nodes, force)
96
+
@@ -1,5 +1,6 @@
1
1
  require 'rubygems'
2
2
  require 'parse_tree'
3
+ require 'sexp_processor'
3
4
  require 'ruby2ruby'
4
5
  require 'timeout'
5
6
  require 'tempfile'
@@ -15,10 +16,12 @@ end
15
16
 
16
17
  class Heckle < SexpProcessor
17
18
 
19
+ class Timeout < Timeout::Error; end
20
+
18
21
  ##
19
22
  # The version of Heckle you are using.
20
23
 
21
- VERSION = '1.4.1'
24
+ VERSION = '1.4.2'
22
25
 
23
26
  ##
24
27
  # Branch node types.
@@ -79,6 +82,10 @@ class Heckle < SexpProcessor
79
82
  @@guess_timeout = true
80
83
  @@timeout = 60 # default to something longer (can be overridden by runners)
81
84
 
85
+ def self.debug
86
+ @@debug
87
+ end
88
+
82
89
  def self.debug=(value)
83
90
  @@debug = value
84
91
  end
@@ -110,11 +117,11 @@ class Heckle < SexpProcessor
110
117
 
111
118
  self.strict = false
112
119
  self.auto_shift_type = true
113
- self.expected = Array
120
+ self.expected = Sexp
114
121
 
115
122
  @mutatees = Hash.new
116
- @mutation_count = Hash.new
117
- @node_count = Hash.new
123
+ @mutation_count = Hash.new 0
124
+ @node_count = Hash.new 0
118
125
  @count = 0
119
126
 
120
127
  @mutatable_nodes = nodes
@@ -149,24 +156,31 @@ class Heckle < SexpProcessor
149
156
  ### Running the script
150
157
 
151
158
  def validate
152
- if mutations_left == 0
159
+ left = mutations_left
160
+
161
+ if left == 0 then
153
162
  @reporter.no_mutations(method_name)
154
163
  return
155
164
  end
156
165
 
157
- @reporter.method_loaded(klass_name, method_name, mutations_left)
166
+ @reporter.method_loaded(klass_name, method_name, left)
158
167
 
159
- until mutations_left == 0
160
- @reporter.remaining_mutations(mutations_left)
168
+ until left == 0 do
169
+ @reporter.remaining_mutations left
161
170
  reset_tree
162
171
  begin
163
172
  process current_tree
164
- silence_stream { timeout(@@timeout) { run_tests } }
173
+ timeout(@@timeout, Heckle::Timeout) { run_tests }
165
174
  rescue SyntaxError => e
166
175
  @reporter.warning "Mutation caused a syntax error:\n\n#{e.message}}"
167
- rescue Timeout::Error
176
+ rescue Heckle::Timeout
168
177
  @reporter.warning "Your tests timed out. Heckle may have caused an infinite loop."
178
+ rescue Interrupt
179
+ @reporter.warning 'Mutation canceled, hit ^C again to exit'
180
+ sleep 2
169
181
  end
182
+
183
+ left = mutations_left
170
184
  end
171
185
 
172
186
  reset # in case we're validating again. we should clean up.
@@ -174,7 +188,7 @@ class Heckle < SexpProcessor
174
188
  unless @failures.empty?
175
189
  @reporter.no_failures
176
190
  @failures.each do |failure|
177
- original = RubyToRuby.new.process(@original_tree.deep_clone)
191
+ original = Ruby2Ruby.new.process(@original_tree.deep_clone)
178
192
  @reporter.failure(original, failure)
179
193
  end
180
194
  false
@@ -191,13 +205,13 @@ class Heckle < SexpProcessor
191
205
  def heckle(exp)
192
206
  exp_copy = exp.deep_clone
193
207
  src = begin
194
- RubyToRuby.new.process(exp)
208
+ Ruby2Ruby.new.process(exp)
195
209
  rescue => e
196
210
  puts "Error: #{e.message} with: #{klass_name}##{method_name}: #{exp_copy.inspect}"
197
211
  raise e
198
212
  end
199
213
 
200
- original = RubyToRuby.new.process(@original_tree.deep_clone)
214
+ original = Ruby2Ruby.new.process(@original_tree.deep_clone)
201
215
  @reporter.replacing(klass_name, method_name, original, src) if @@debug
202
216
 
203
217
  clean_name = method_name.to_s.gsub(/self\./, '')
@@ -220,50 +234,66 @@ class Heckle < SexpProcessor
220
234
  meth = exp.shift
221
235
  args = process(exp.shift)
222
236
 
223
- out = [:call, recv, meth]
224
- out << args if args
225
-
226
- stack = caller.map { |s| s[/process_\w+/] }.compact
227
-
228
- if stack.first != "process_iter" then
229
- mutate_node out
230
- else
231
- out
232
- end
237
+ mutate_node s(:call, recv, meth, args)
233
238
  end
234
239
 
235
240
  ##
236
241
  # Replaces the call node with nil.
237
242
 
238
243
  def mutate_call(node)
239
- [:nil]
244
+ s(:nil)
240
245
  end
241
246
 
242
247
  def process_defn(exp)
243
248
  self.method = exp.shift
244
- result = [:defn, method]
249
+ result = s(:defn, method)
245
250
  result << process(exp.shift) until exp.empty?
246
251
  heckle(result) if method == method_name
247
252
 
248
253
  return result
249
254
  ensure
250
255
  @mutated = false
251
- reset_node_count
256
+ node_count.clear
257
+ end
258
+
259
+ def process_defs(exp)
260
+ recv = process exp.shift
261
+ meth = exp.shift
262
+
263
+ self.method = "#{Ruby2Ruby.new.process(recv.deep_clone)}.#{meth}".intern
264
+
265
+ result = s(:defs, recv, meth)
266
+ result << process(exp.shift) until exp.empty?
267
+
268
+ heckle(result) if method == method_name
269
+
270
+ return result
271
+ ensure
272
+ @mutated = false
273
+ node_count.clear
252
274
  end
253
275
 
254
276
  ##
255
277
  # So process_call works correctly
256
278
 
257
279
  def process_iter(exp)
258
- [:iter, process(exp.shift), process(exp.shift), process(exp.shift)]
280
+ call = process exp.shift
281
+ args = process exp.shift
282
+ body = process exp.shift
283
+
284
+ mutate_node s(:iter, call, args, body)
285
+ end
286
+
287
+ def mutate_iter(exp)
288
+ s(:nil)
259
289
  end
260
290
 
261
291
  def process_asgn(type, exp)
262
292
  var = exp.shift
263
293
  if exp.empty? then
264
- mutate_node [type, var]
294
+ mutate_node s(type, var)
265
295
  else
266
- mutate_node [type, var, process(exp.shift)]
296
+ mutate_node s(type, var, process(exp.shift))
267
297
  end
268
298
  end
269
299
 
@@ -271,12 +301,12 @@ class Heckle < SexpProcessor
271
301
  type = node.shift
272
302
  var = node.shift
273
303
  if node.empty? then
274
- [:lasgn, :_heckle_dummy]
304
+ s(type, :_heckle_dummy)
275
305
  else
276
306
  if node.last.first == :nil then
277
- [type, var, [:lit, 42]]
307
+ s(type, var, s(:lit, 42))
278
308
  else
279
- [type, var, [:nil]]
309
+ s(type, var, s(:nil))
280
310
  end
281
311
  end
282
312
  end
@@ -342,7 +372,7 @@ class Heckle < SexpProcessor
342
372
  alias mutate_lasgn mutate_asgn
343
373
 
344
374
  def process_lit(exp)
345
- mutate_node [:lit, exp.shift]
375
+ mutate_node s(:lit, exp.shift)
346
376
  end
347
377
 
348
378
  ##
@@ -351,82 +381,82 @@ class Heckle < SexpProcessor
351
381
  def mutate_lit(exp)
352
382
  case exp[1]
353
383
  when Fixnum, Float, Bignum
354
- [:lit, exp[1] + rand_number]
384
+ s(:lit, exp[1] + rand_number)
355
385
  when Symbol
356
- [:lit, rand_symbol]
386
+ s(:lit, rand_symbol)
357
387
  when Regexp
358
- [:lit, Regexp.new(Regexp.escape(rand_string.gsub(/\//, '\\/')))]
388
+ s(:lit, Regexp.new(Regexp.escape(rand_string.gsub(/\//, '\\/'))))
359
389
  when Range
360
- [:lit, rand_range]
390
+ s(:lit, rand_range)
361
391
  end
362
392
  end
363
393
 
364
394
  def process_str(exp)
365
- mutate_node [:str, exp.shift]
395
+ mutate_node s(:str, exp.shift)
366
396
  end
367
397
 
368
398
  ##
369
399
  # Replaces the value of the :str node with a random value.
370
400
 
371
401
  def mutate_str(node)
372
- [:str, rand_string]
402
+ s(:str, rand_string)
373
403
  end
374
404
 
375
405
  def process_if(exp)
376
- mutate_node [:if, process(exp.shift), process(exp.shift), process(exp.shift)]
406
+ mutate_node s(:if, process(exp.shift), process(exp.shift), process(exp.shift))
377
407
  end
378
408
 
379
409
  ##
380
410
  # Swaps the then and else parts of the :if node.
381
411
 
382
412
  def mutate_if(node)
383
- [:if, node[1], node[3], node[2]]
413
+ s(:if, node[1], node[3], node[2])
384
414
  end
385
415
 
386
416
  def process_true(exp)
387
- mutate_node [:true]
417
+ mutate_node s(:true)
388
418
  end
389
419
 
390
420
  ##
391
421
  # Swaps for a :false node.
392
422
 
393
423
  def mutate_true(node)
394
- [:false]
424
+ s(:false)
395
425
  end
396
426
 
397
427
  def process_false(exp)
398
- mutate_node [:false]
428
+ mutate_node s(:false)
399
429
  end
400
430
 
401
431
  ##
402
432
  # Swaps for a :true node.
403
433
 
404
434
  def mutate_false(node)
405
- [:true]
435
+ s(:true)
406
436
  end
407
437
 
408
438
  def process_while(exp)
409
439
  cond, body, head_controlled = grab_conditional_loop_parts(exp)
410
- mutate_node [:while, cond, body, head_controlled]
440
+ mutate_node s(:while, cond, body, head_controlled)
411
441
  end
412
442
 
413
443
  ##
414
444
  # Swaps for a :until node.
415
445
 
416
446
  def mutate_while(node)
417
- [:until, node[1], node[2], node[3]]
447
+ s(:until, node[1], node[2], node[3])
418
448
  end
419
449
 
420
450
  def process_until(exp)
421
451
  cond, body, head_controlled = grab_conditional_loop_parts(exp)
422
- mutate_node [:until, cond, body, head_controlled]
452
+ mutate_node s(:until, cond, body, head_controlled)
423
453
  end
424
454
 
425
455
  ##
426
456
  # Swaps for a :while node.
427
457
 
428
458
  def mutate_until(node)
429
- [:while, node[1], node[2], node[3]]
459
+ s(:while, node[1], node[2], node[3])
430
460
  end
431
461
 
432
462
  def mutate_node(node)
@@ -444,28 +474,44 @@ class Heckle < SexpProcessor
444
474
  ############################################################
445
475
  ### Tree operations
446
476
 
447
- def walk_and_push(node)
477
+ def walk_and_push(node, index = 0)
448
478
  return unless node.respond_to? :each
449
479
  return if node.is_a? String
450
- node.each { |child| walk_and_push(child) }
451
- if @mutatable_nodes.include? node.first
452
- @mutatees[node.first.to_sym].push(node)
453
- mutation_count[node] = 0
480
+
481
+ @walk_stack.push node.first
482
+ node.each_with_index { |child_node, i| walk_and_push child_node, i }
483
+ @walk_stack.pop
484
+
485
+ if @mutatable_nodes.include? node.first and
486
+ # HACK skip over call nodes that are the first child of an iter or
487
+ # they'll get added twice
488
+ #
489
+ # I think heckle really needs two processors, one for finding and one
490
+ # for heckling.
491
+ !(node.first == :call and index == 1 and @walk_stack.last == :iter) then
492
+ @mutatees[node.first].push(node)
454
493
  end
455
494
  end
456
495
 
457
496
  def grab_mutatees
458
- walk_and_push(current_tree)
497
+ @walk_stack = []
498
+ walk_and_push current_tree
459
499
  end
460
500
 
461
501
  def current_tree
462
- ParseTree.translate(klass_name.to_class, method_name)
502
+ ur = Unifier.new
503
+
504
+ sexp = ParseTree.translate(klass_name.to_class, method_name)
505
+ raise "sexp invalid for #{klass_name}##{method_name}" if sexp == [nil]
506
+ sexp = ur.process(sexp)
507
+
508
+ rewrite sexp
463
509
  end
464
510
 
465
511
  def reset
466
512
  reset_tree
467
513
  reset_mutatees
468
- reset_mutation_count
514
+ mutation_count.clear
469
515
  end
470
516
 
471
517
  def reset_tree
@@ -488,26 +534,15 @@ class Heckle < SexpProcessor
488
534
  @mutatees = @original_mutatees.deep_clone
489
535
  end
490
536
 
491
- def reset_mutation_count
492
- mutation_count.each {|k,v| mutation_count[k] = 0}
493
- end
494
-
495
- def reset_node_count
496
- node_count.each {|k,v| node_count[k] = 0}
497
- end
498
-
499
537
  def increment_node_count(node)
500
- if node_count[node].nil?
501
- node_count[node] = 1
502
- else
503
- node_count[node] += 1
504
- end
538
+ node_count[node] += 1
505
539
  end
506
540
 
507
541
  def increment_mutation_count(node)
508
542
  # So we don't re-mutate this later if the tree is reset
509
543
  mutation_count[node] += 1
510
- @mutatees[node.first].delete_at(@mutatees[node.first].index(node))
544
+ mutatee_type = @mutatees[node.first]
545
+ mutatee_type.delete_at mutatee_type.index(node)
511
546
  @mutated = true
512
547
  end
513
548
 
@@ -520,9 +555,10 @@ class Heckle < SexpProcessor
520
555
 
521
556
  def should_heckle?(exp)
522
557
  return false unless method == method_name
523
- mutation_count[exp] = 0 if mutation_count[exp].nil?
524
558
  return false if node_count[exp] <= mutation_count[exp]
525
- ( mutatees[exp.first.to_sym] || [] ).include?(exp) && !already_mutated?
559
+ key = exp.first.to_sym
560
+
561
+ mutatees.include?(key) && mutatees[key].include?(exp) && !already_mutated?
526
562
  end
527
563
 
528
564
  def grab_conditional_loop_parts(exp)
@@ -537,13 +573,34 @@ class Heckle < SexpProcessor
537
573
  end
538
574
 
539
575
  def mutations_left
576
+ @last_mutations_left ||= -1
577
+
540
578
  sum = 0
541
- @mutatees.each {|mut| sum += mut.last.size }
579
+ @mutatees.each { |mut| sum += mut.last.size }
580
+
581
+ if sum == @last_mutations_left then
582
+ puts 'bug!'
583
+ puts
584
+ require 'pp'
585
+ puts 'mutatees:'
586
+ pp @mutatees
587
+ puts
588
+ puts 'original tree:'
589
+ pp @original_tree
590
+ puts
591
+ puts "Infinite loop detected!"
592
+ puts "Please save this output to an attachment and submit a ticket here:"
593
+ puts "http://rubyforge.org/tracker/?func=add&group_id=1513&atid=5921"
594
+ exit 1
595
+ else
596
+ @last_mutations_left = sum
597
+ end
598
+
542
599
  sum
543
600
  end
544
601
 
545
602
  def current_code
546
- RubyToRuby.translate(klass_name.to_class, method_name)
603
+ Ruby2Ruby.translate(klass_name.to_class, method_name)
547
604
  end
548
605
 
549
606
  ##
@@ -586,26 +643,30 @@ class Heckle < SexpProcessor
586
643
  # Suppresses output on $stdout and $stderr.
587
644
 
588
645
  def silence_stream
589
- dead = File.open("/dev/null", "w")
646
+ return yield if @@debug
590
647
 
591
- $stdout.flush
592
- $stderr.flush
648
+ begin
649
+ dead = File.open("/dev/null", "w")
593
650
 
594
- oldstdout = $stdout.dup
595
- oldstderr = $stderr.dup
651
+ $stdout.flush
652
+ $stderr.flush
596
653
 
597
- $stdout.reopen(dead)
598
- $stderr.reopen(dead)
654
+ oldstdout = $stdout.dup
655
+ oldstderr = $stderr.dup
599
656
 
600
- result = yield
657
+ $stdout.reopen(dead)
658
+ $stderr.reopen(dead)
601
659
 
602
- ensure
603
- $stdout.flush
604
- $stderr.flush
660
+ result = yield
661
+
662
+ ensure
663
+ $stdout.flush
664
+ $stderr.flush
605
665
 
606
- $stdout.reopen(oldstdout)
607
- $stderr.reopen(oldstderr)
608
- result
666
+ $stdout.reopen(oldstdout)
667
+ $stderr.reopen(oldstderr)
668
+ result
669
+ end
609
670
  end
610
671
 
611
672
  class Reporter
@@ -675,7 +736,7 @@ class Heckle < SexpProcessor
675
736
  end
676
737
 
677
738
  def report_test_failures
678
- puts "Tests failed -- this is good"
739
+ puts "Tests failed -- this is good" if Heckle.debug
679
740
  end
680
741
  end
681
742
 
@@ -694,3 +755,4 @@ class Heckle < SexpProcessor
694
755
  end
695
756
 
696
757
  end
758
+