heckle 1.4.1 → 1.4.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.autotest +16 -0
- data/History.txt +26 -0
- data/Manifest.txt +1 -0
- data/README.txt +9 -8
- data/Rakefile +20 -11
- data/bin/heckle +20 -13
- data/lib/heckle.rb +152 -90
- data/lib/test_unit_heckler.rb +24 -9
- data/test/fixtures/heckled.rb +6 -2
- data/test/test_heckle.rb +352 -364
- metadata +83 -57
data/.autotest
ADDED
@@ -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
|
data/History.txt
CHANGED
@@ -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!
|
data/Manifest.txt
CHANGED
data/README.txt
CHANGED
@@ -1,10 +1,11 @@
|
|
1
|
-
heckle
|
2
|
-
|
3
|
-
|
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
|
-
|
24
|
-
|
24
|
+
* ruby2ruby 1.1.2 or greater
|
25
|
+
* ParseTree 1.6.1 or greater
|
25
26
|
|
26
27
|
== INSTALL:
|
27
28
|
|
28
|
-
|
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 |
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
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
|
-
|
17
|
-
|
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(
|
12
|
+
opts.on("-v", "--verbose", "Loudly explain heckle run") do |opt|
|
12
13
|
TestUnitHeckler.debug = true
|
13
14
|
end
|
14
15
|
|
15
|
-
opts.on(
|
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(
|
21
|
-
|
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(
|
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(
|
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(
|
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(
|
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(
|
59
|
-
|
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(
|
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(
|
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
|
+
|
data/lib/heckle.rb
CHANGED
@@ -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.
|
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 =
|
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
|
-
|
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,
|
166
|
+
@reporter.method_loaded(klass_name, method_name, left)
|
158
167
|
|
159
|
-
until
|
160
|
-
@reporter.remaining_mutations
|
168
|
+
until left == 0 do
|
169
|
+
@reporter.remaining_mutations left
|
161
170
|
reset_tree
|
162
171
|
begin
|
163
172
|
process current_tree
|
164
|
-
|
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
|
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 =
|
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
|
-
|
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 =
|
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
|
-
|
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
|
-
|
244
|
+
s(:nil)
|
240
245
|
end
|
241
246
|
|
242
247
|
def process_defn(exp)
|
243
248
|
self.method = exp.shift
|
244
|
-
result =
|
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
|
-
|
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
|
-
|
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
|
294
|
+
mutate_node s(type, var)
|
265
295
|
else
|
266
|
-
mutate_node
|
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
|
-
|
304
|
+
s(type, :_heckle_dummy)
|
275
305
|
else
|
276
306
|
if node.last.first == :nil then
|
277
|
-
|
307
|
+
s(type, var, s(:lit, 42))
|
278
308
|
else
|
279
|
-
|
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
|
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
|
-
|
384
|
+
s(:lit, exp[1] + rand_number)
|
355
385
|
when Symbol
|
356
|
-
|
386
|
+
s(:lit, rand_symbol)
|
357
387
|
when Regexp
|
358
|
-
|
388
|
+
s(:lit, Regexp.new(Regexp.escape(rand_string.gsub(/\//, '\\/'))))
|
359
389
|
when Range
|
360
|
-
|
390
|
+
s(:lit, rand_range)
|
361
391
|
end
|
362
392
|
end
|
363
393
|
|
364
394
|
def process_str(exp)
|
365
|
-
mutate_node
|
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
|
-
|
402
|
+
s(:str, rand_string)
|
373
403
|
end
|
374
404
|
|
375
405
|
def process_if(exp)
|
376
|
-
mutate_node
|
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
|
-
|
413
|
+
s(:if, node[1], node[3], node[2])
|
384
414
|
end
|
385
415
|
|
386
416
|
def process_true(exp)
|
387
|
-
mutate_node
|
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
|
-
|
424
|
+
s(:false)
|
395
425
|
end
|
396
426
|
|
397
427
|
def process_false(exp)
|
398
|
-
mutate_node
|
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
|
-
|
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
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
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
|
-
|
497
|
+
@walk_stack = []
|
498
|
+
walk_and_push current_tree
|
459
499
|
end
|
460
500
|
|
461
501
|
def current_tree
|
462
|
-
|
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
|
-
|
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
|
-
|
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]
|
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
|
-
|
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
|
-
|
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
|
-
|
646
|
+
return yield if @@debug
|
590
647
|
|
591
|
-
|
592
|
-
|
648
|
+
begin
|
649
|
+
dead = File.open("/dev/null", "w")
|
593
650
|
|
594
|
-
|
595
|
-
|
651
|
+
$stdout.flush
|
652
|
+
$stderr.flush
|
596
653
|
|
597
|
-
|
598
|
-
|
654
|
+
oldstdout = $stdout.dup
|
655
|
+
oldstderr = $stderr.dup
|
599
656
|
|
600
|
-
|
657
|
+
$stdout.reopen(dead)
|
658
|
+
$stderr.reopen(dead)
|
601
659
|
|
602
|
-
|
603
|
-
|
604
|
-
|
660
|
+
result = yield
|
661
|
+
|
662
|
+
ensure
|
663
|
+
$stdout.flush
|
664
|
+
$stderr.flush
|
605
665
|
|
606
|
-
|
607
|
-
|
608
|
-
|
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
|
+
|