heckle 1.1.1 → 1.2.0
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.
- data/History.txt +12 -0
- data/Manifest.txt +0 -2
- data/bin/heckle +21 -2
- data/lib/heckle.rb +441 -2
- data/lib/test_unit_heckler.rb +30 -20
- data/sample/lib/heckled.rb +13 -0
- data/sample/test/test_heckled.rb +8 -0
- data/test/fixtures/heckled.rb +21 -17
- data/test/test_heckle.rb +221 -466
- metadata +7 -9
- data/lib/heckle/base.rb +0 -352
- data/lib/heckle/reporter.rb +0 -43
metadata
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
|
-
rubygems_version: 0.9.0
|
2
|
+
rubygems_version: 0.9.0.9
|
3
3
|
specification_version: 1
|
4
4
|
name: heckle
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 1.
|
7
|
-
date:
|
6
|
+
version: 1.2.0
|
7
|
+
date: 2007-01-15 00:00:00 -08:00
|
8
8
|
summary: Unit Test Sadism
|
9
9
|
require_paths:
|
10
10
|
- lib
|
@@ -35,8 +35,6 @@ files:
|
|
35
35
|
- Rakefile
|
36
36
|
- bin/heckle
|
37
37
|
- lib/heckle.rb
|
38
|
-
- lib/heckle/base.rb
|
39
|
-
- lib/heckle/reporter.rb
|
40
38
|
- lib/test_unit_heckler.rb
|
41
39
|
- sample/Rakefile
|
42
40
|
- sample/changes.log
|
@@ -58,20 +56,20 @@ requirements: []
|
|
58
56
|
|
59
57
|
dependencies:
|
60
58
|
- !ruby/object:Gem::Dependency
|
61
|
-
name:
|
59
|
+
name: ruby2ruby
|
62
60
|
version_requirement:
|
63
61
|
version_requirements: !ruby/object:Gem::Version::Requirement
|
64
62
|
requirements:
|
65
63
|
- - ">="
|
66
64
|
- !ruby/object:Gem::Version
|
67
|
-
version: 1.1.
|
65
|
+
version: 1.1.0
|
68
66
|
version:
|
69
67
|
- !ruby/object:Gem::Dependency
|
70
|
-
name:
|
68
|
+
name: hoe
|
71
69
|
version_requirement:
|
72
70
|
version_requirements: !ruby/object:Gem::Version::Requirement
|
73
71
|
requirements:
|
74
72
|
- - ">="
|
75
73
|
- !ruby/object:Gem::Version
|
76
|
-
version: 1.1.
|
74
|
+
version: 1.1.7
|
77
75
|
version:
|
data/lib/heckle/base.rb
DELETED
@@ -1,352 +0,0 @@
|
|
1
|
-
require 'parse_tree'
|
2
|
-
require 'ruby2ruby'
|
3
|
-
|
4
|
-
class String
|
5
|
-
def to_class
|
6
|
-
split(/::/).inject(Object) { |klass, name| klass.const_get(name) }
|
7
|
-
end
|
8
|
-
end
|
9
|
-
|
10
|
-
module Heckle
|
11
|
-
VERSION = '1.1.1'
|
12
|
-
|
13
|
-
class Base < SexpProcessor
|
14
|
-
MUTATABLE_NODES = [:if, :lit, :str, :true, :false, :while, :until]
|
15
|
-
|
16
|
-
attr_accessor :klass_name, :method_name, :klass, :method, :mutatees, :original_tree,
|
17
|
-
:mutation_count, :node_count, :failures, :count
|
18
|
-
|
19
|
-
@@debug = false;
|
20
|
-
|
21
|
-
def self.debug=(value)
|
22
|
-
@@debug = value
|
23
|
-
end
|
24
|
-
|
25
|
-
def initialize(klass_name=nil, method_name=nil, reporter = Reporter.new)
|
26
|
-
super()
|
27
|
-
|
28
|
-
@klass_name, @method_name = klass_name, method_name.intern
|
29
|
-
@klass = @method = nil
|
30
|
-
@reporter = reporter
|
31
|
-
|
32
|
-
self.strict = false
|
33
|
-
self.auto_shift_type = true
|
34
|
-
self.expected = Array
|
35
|
-
|
36
|
-
@mutatees = Hash.new
|
37
|
-
@mutation_count = Hash.new
|
38
|
-
@node_count = Hash.new
|
39
|
-
@count = 0
|
40
|
-
|
41
|
-
MUTATABLE_NODES.each {|type| @mutatees[type] = [] }
|
42
|
-
|
43
|
-
@failures = []
|
44
|
-
|
45
|
-
@mutated = false
|
46
|
-
|
47
|
-
grab_mutatees
|
48
|
-
|
49
|
-
@original_tree = current_tree.deep_clone
|
50
|
-
@original_mutatees = mutatees.deep_clone
|
51
|
-
end
|
52
|
-
|
53
|
-
############################################################
|
54
|
-
### Overwrite test_pass? for your own Heckle runner.
|
55
|
-
def tests_pass?
|
56
|
-
raise NotImplementedError
|
57
|
-
end
|
58
|
-
|
59
|
-
def run_tests
|
60
|
-
if tests_pass? then
|
61
|
-
record_passing_mutation
|
62
|
-
else
|
63
|
-
@reporter.report_test_failures
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
############################################################
|
68
|
-
### Running the script
|
69
|
-
|
70
|
-
def validate
|
71
|
-
if mutations_left == 0
|
72
|
-
@reporter.no_mutations(method_name)
|
73
|
-
return
|
74
|
-
end
|
75
|
-
|
76
|
-
@reporter.method_loaded(klass_name, method_name, mutations_left)
|
77
|
-
|
78
|
-
until mutations_left == 0
|
79
|
-
@reporter.remaining_mutations(mutations_left)
|
80
|
-
reset_tree
|
81
|
-
begin
|
82
|
-
process current_tree
|
83
|
-
silence_stream(STDOUT) { run_tests }
|
84
|
-
rescue SyntaxError => e
|
85
|
-
puts "Mutation caused a syntax error: #{e.message}"
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
|
-
reset # in case we're validating again. we should clean up.
|
90
|
-
|
91
|
-
unless @failures.empty?
|
92
|
-
@reporter.no_failures
|
93
|
-
@failures.each do |failure|
|
94
|
-
@reporter.failure(failure)
|
95
|
-
end
|
96
|
-
else
|
97
|
-
@reporter.no_surviving_mutants
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
|
-
def record_passing_mutation
|
102
|
-
@failures << current_code
|
103
|
-
end
|
104
|
-
|
105
|
-
def heckle(exp)
|
106
|
-
src = RubyToRuby.new.process(exp)
|
107
|
-
@reporter.replacing(klass_name, method_name, src) if @@debug
|
108
|
-
klass = klass_name.to_class
|
109
|
-
self.count += 1
|
110
|
-
new_name = "#{method_name}_#{count}"
|
111
|
-
|
112
|
-
klass.send :undef_method, new_name rescue nil
|
113
|
-
klass.send :alias_method, new_name, method_name
|
114
|
-
klass.class_eval(src)
|
115
|
-
end
|
116
|
-
|
117
|
-
############################################################
|
118
|
-
### Processing sexps
|
119
|
-
|
120
|
-
def process_defn(exp)
|
121
|
-
self.method = exp.shift
|
122
|
-
result = [:defn, method]
|
123
|
-
result << process(exp.shift) until exp.empty?
|
124
|
-
heckle(result) if method == method_name
|
125
|
-
@mutated = false
|
126
|
-
reset_node_count
|
127
|
-
|
128
|
-
return result
|
129
|
-
end
|
130
|
-
|
131
|
-
def process_lit(exp)
|
132
|
-
mutate_node [:lit, exp.shift]
|
133
|
-
end
|
134
|
-
|
135
|
-
def mutate_lit(exp)
|
136
|
-
case exp[1]
|
137
|
-
when Fixnum, Float, Bignum
|
138
|
-
[:lit, exp[1] + rand_number]
|
139
|
-
when Symbol
|
140
|
-
[:lit, rand_symbol]
|
141
|
-
when Regexp
|
142
|
-
[:lit, /#{Regexp.escape(rand_string)}/]
|
143
|
-
when Range
|
144
|
-
[:lit, rand_range]
|
145
|
-
end
|
146
|
-
end
|
147
|
-
|
148
|
-
def process_str(exp)
|
149
|
-
mutate_node [:str, exp.shift]
|
150
|
-
end
|
151
|
-
|
152
|
-
def mutate_str(node)
|
153
|
-
[:str, rand_string]
|
154
|
-
end
|
155
|
-
|
156
|
-
def process_if(exp)
|
157
|
-
mutate_node [:if, process(exp.shift), process(exp.shift), process(exp.shift)]
|
158
|
-
end
|
159
|
-
|
160
|
-
def mutate_if(node)
|
161
|
-
[:if, node[1], node[3], node[2]]
|
162
|
-
end
|
163
|
-
|
164
|
-
def process_true(exp)
|
165
|
-
mutate_node [:true]
|
166
|
-
end
|
167
|
-
|
168
|
-
def mutate_true(node)
|
169
|
-
[:false]
|
170
|
-
end
|
171
|
-
|
172
|
-
def process_false(exp)
|
173
|
-
mutate_node [:false]
|
174
|
-
end
|
175
|
-
|
176
|
-
def mutate_false(node)
|
177
|
-
[:true]
|
178
|
-
end
|
179
|
-
|
180
|
-
def process_while(exp)
|
181
|
-
cond, body, head_controlled = grab_conditional_loop_parts(exp)
|
182
|
-
mutate_node [:while, cond, body, head_controlled]
|
183
|
-
end
|
184
|
-
|
185
|
-
def mutate_while(node)
|
186
|
-
[:until, node[1], node[2], node[3]]
|
187
|
-
end
|
188
|
-
|
189
|
-
def process_until(exp)
|
190
|
-
cond, body, head_controlled = grab_conditional_loop_parts(exp)
|
191
|
-
mutate_node [:until, cond, body, head_controlled]
|
192
|
-
end
|
193
|
-
|
194
|
-
def mutate_until(node)
|
195
|
-
[:while, node[1], node[2], node[3]]
|
196
|
-
end
|
197
|
-
|
198
|
-
def mutate_node(node)
|
199
|
-
raise UnsupportedNodeError unless respond_to? "mutate_#{node.first}"
|
200
|
-
increment_node_count node
|
201
|
-
if should_heckle? node
|
202
|
-
increment_mutation_count node
|
203
|
-
return send("mutate_#{node.first}", node)
|
204
|
-
else
|
205
|
-
node
|
206
|
-
end
|
207
|
-
end
|
208
|
-
|
209
|
-
############################################################
|
210
|
-
### Tree operations
|
211
|
-
|
212
|
-
def walk_and_push(node)
|
213
|
-
return unless node.respond_to? :each
|
214
|
-
return if node.is_a? String
|
215
|
-
node.each { |child| walk_and_push(child) }
|
216
|
-
if MUTATABLE_NODES.include? node.first
|
217
|
-
@mutatees[node.first.to_sym].push(node)
|
218
|
-
mutation_count[node] = 0
|
219
|
-
end
|
220
|
-
end
|
221
|
-
|
222
|
-
def grab_mutatees
|
223
|
-
walk_and_push(current_tree)
|
224
|
-
end
|
225
|
-
|
226
|
-
def current_tree
|
227
|
-
ParseTree.translate(klass_name.to_class, method_name)
|
228
|
-
end
|
229
|
-
|
230
|
-
def reset
|
231
|
-
reset_tree
|
232
|
-
reset_mutatees
|
233
|
-
reset_mutation_count
|
234
|
-
end
|
235
|
-
|
236
|
-
def reset_tree
|
237
|
-
return unless original_tree != current_tree
|
238
|
-
@mutated = false
|
239
|
-
|
240
|
-
klass = klass_name.to_class
|
241
|
-
|
242
|
-
self.count += 1
|
243
|
-
new_name = "#{method_name}_#{count}"
|
244
|
-
klass.send :undef_method, new_name rescue nil
|
245
|
-
klass.send :alias_method, new_name, method_name
|
246
|
-
klass.send :alias_method, method_name, "#{method_name}_1"
|
247
|
-
end
|
248
|
-
|
249
|
-
def reset_mutatees
|
250
|
-
@mutatees = @original_mutatees.deep_clone
|
251
|
-
end
|
252
|
-
|
253
|
-
def reset_mutation_count
|
254
|
-
mutation_count.each {|k,v| mutation_count[k] = 0}
|
255
|
-
end
|
256
|
-
|
257
|
-
def reset_node_count
|
258
|
-
node_count.each {|k,v| node_count[k] = 0}
|
259
|
-
end
|
260
|
-
|
261
|
-
def increment_node_count(node)
|
262
|
-
if node_count[node].nil?
|
263
|
-
node_count[node] = 1
|
264
|
-
else
|
265
|
-
node_count[node] += 1
|
266
|
-
end
|
267
|
-
end
|
268
|
-
|
269
|
-
def increment_mutation_count(node)
|
270
|
-
# So we don't re-mutate this later if the tree is reset
|
271
|
-
mutation_count[node] += 1
|
272
|
-
@mutatees[node.first].delete_at(@mutatees[node.first].index(node))
|
273
|
-
@mutated = true
|
274
|
-
end
|
275
|
-
|
276
|
-
############################################################
|
277
|
-
### Convenience methods
|
278
|
-
|
279
|
-
def should_heckle?(exp)
|
280
|
-
return false unless method == method_name
|
281
|
-
mutation_count[exp] = 0 if mutation_count[exp].nil?
|
282
|
-
return false if node_count[exp] <= mutation_count[exp]
|
283
|
-
mutatees[exp.first.to_sym].include?(exp) && !already_mutated?
|
284
|
-
end
|
285
|
-
|
286
|
-
def grab_conditional_loop_parts(exp)
|
287
|
-
cond = process(exp.shift)
|
288
|
-
body = process(exp.shift)
|
289
|
-
head_controlled = exp.shift
|
290
|
-
return cond, body, head_controlled
|
291
|
-
end
|
292
|
-
|
293
|
-
def already_mutated?
|
294
|
-
@mutated
|
295
|
-
end
|
296
|
-
|
297
|
-
def mutations_left
|
298
|
-
sum = 0
|
299
|
-
@mutatees.each {|mut| sum += mut.last.size }
|
300
|
-
sum
|
301
|
-
end
|
302
|
-
|
303
|
-
def current_code
|
304
|
-
RubyToRuby.translate(klass_name.to_class, method_name)
|
305
|
-
end
|
306
|
-
|
307
|
-
def rand_number
|
308
|
-
(rand(10) + 1)*((-1)**rand(2))
|
309
|
-
end
|
310
|
-
|
311
|
-
def rand_string
|
312
|
-
size = rand(50)
|
313
|
-
str = ""
|
314
|
-
size.times { str << rand(126).chr }
|
315
|
-
str
|
316
|
-
end
|
317
|
-
|
318
|
-
def rand_symbol
|
319
|
-
letters = ('a'..'z').to_a + ('A'..'Z').to_a
|
320
|
-
str = ""
|
321
|
-
(rand(50) + 1).times { str << letters[rand(letters.size)] }
|
322
|
-
:"#{str}"
|
323
|
-
end
|
324
|
-
|
325
|
-
def rand_range
|
326
|
-
min = rand(50)
|
327
|
-
max = min + rand(50)
|
328
|
-
min..max
|
329
|
-
end
|
330
|
-
|
331
|
-
# silence_stream taken from Rails ActiveSupport reporting.rb
|
332
|
-
|
333
|
-
# Silences any stream for the duration of the block.
|
334
|
-
#
|
335
|
-
# silence_stream(STDOUT) do
|
336
|
-
# puts 'This will never be seen'
|
337
|
-
# end
|
338
|
-
#
|
339
|
-
# puts 'But this will'
|
340
|
-
def silence_stream(stream)
|
341
|
-
unless @@debug
|
342
|
-
old_stream = stream.dup
|
343
|
-
stream.reopen(RUBY_PLATFORM =~ /mswin/ ? 'NUL:' : '/dev/null')
|
344
|
-
stream.sync = true
|
345
|
-
end
|
346
|
-
yield
|
347
|
-
ensure
|
348
|
-
stream.reopen(old_stream) unless @@debug
|
349
|
-
end
|
350
|
-
|
351
|
-
end
|
352
|
-
end
|
data/lib/heckle/reporter.rb
DELETED
@@ -1,43 +0,0 @@
|
|
1
|
-
module Heckle
|
2
|
-
class Reporter
|
3
|
-
def no_mutations(method_name)
|
4
|
-
puts
|
5
|
-
puts "!"*70
|
6
|
-
puts "!!! #{method_name} has a thick skin. There's nothing to heckle."
|
7
|
-
puts "!"*70
|
8
|
-
puts
|
9
|
-
end
|
10
|
-
|
11
|
-
def method_loaded(klass_name, method_name, mutations_left)
|
12
|
-
puts
|
13
|
-
puts "*"*70
|
14
|
-
puts "*** #{klass_name}\##{method_name} loaded with #{mutations_left} possible mutations"
|
15
|
-
puts "*"*70
|
16
|
-
puts
|
17
|
-
end
|
18
|
-
|
19
|
-
def remaining_mutations(mutations_left)
|
20
|
-
puts "#{mutations_left} mutations remaining..."
|
21
|
-
end
|
22
|
-
|
23
|
-
def no_failures
|
24
|
-
puts "\nThe following mutations didn't cause test failures:\n"
|
25
|
-
end
|
26
|
-
|
27
|
-
def failure(failure)
|
28
|
-
puts "\n#{failure}\n"
|
29
|
-
end
|
30
|
-
|
31
|
-
def no_surviving_mutants
|
32
|
-
puts "No mutants survived. Cool!\n\n"
|
33
|
-
end
|
34
|
-
|
35
|
-
def replacing(klass_name, method_name, src)
|
36
|
-
puts "Replacing #{klass_name}##{method_name} with:\n\n#{src}\n"
|
37
|
-
end
|
38
|
-
|
39
|
-
def report_test_failures
|
40
|
-
puts "Tests failed -- this is good"
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|