heckle 1.2.0 → 1.3.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 +13 -0
- data/bin/heckle +6 -3
- data/lib/heckle.rb +42 -14
- data/lib/test_unit_heckler.rb +38 -11
- metadata +3 -3
data/History.txt
CHANGED
@@ -1,3 +1,16 @@
|
|
1
|
+
== 1.3.0 / 2007-02-12
|
2
|
+
|
3
|
+
* 1 major enhancement:
|
4
|
+
* Unified diffs for mutatated methods
|
5
|
+
* 4 minor enhancements:
|
6
|
+
* Now returns exit status 1 if failed.
|
7
|
+
* Added a simple report at the end.
|
8
|
+
* Runs are now sorted by method.
|
9
|
+
* Autodetects rails and changes test_pattern accordingly.
|
10
|
+
* 2 bug fixes:
|
11
|
+
* Aborts when an unknown method is supplied.
|
12
|
+
* Escapes slashes in random regexps.
|
13
|
+
|
1
14
|
== 1.2.0 / 2007-01-15
|
2
15
|
|
3
16
|
* 2 major enhancements:
|
data/bin/heckle
CHANGED
@@ -9,7 +9,7 @@ opts = OptionParser.new do |opts|
|
|
9
9
|
opts.on( "-v", "--verbose", "Loudly explain heckle run" ) do |opt|
|
10
10
|
TestUnitHeckler.debug = true
|
11
11
|
end
|
12
|
-
|
12
|
+
|
13
13
|
opts.on( "-V", "--version", "Prints Heckle's version number") do |opt|
|
14
14
|
puts "Heckle #{Heckle::VERSION}"
|
15
15
|
exit 0
|
@@ -28,7 +28,7 @@ opts = OptionParser.new do |opts|
|
|
28
28
|
|
29
29
|
Heckle::MUTATABLE_NODES.replace [:if, :while, :until]
|
30
30
|
end
|
31
|
-
|
31
|
+
|
32
32
|
opts.on( "-T", "--timeout SECONDS", "The maximum time for a test run in seconds",
|
33
33
|
"Used to catch infinite loops") do |timeout|
|
34
34
|
Heckle.timeout = timeout.to_i
|
@@ -41,6 +41,9 @@ opts = OptionParser.new do |opts|
|
|
41
41
|
end
|
42
42
|
end
|
43
43
|
|
44
|
+
looks_like_rails = test ?f, 'config/environment.rb'
|
45
|
+
TestUnitHeckler.test_pattern = "test/**/*.rb" if looks_like_rails
|
46
|
+
|
44
47
|
opts.parse!
|
45
48
|
|
46
49
|
impl = ARGV.shift
|
@@ -51,4 +54,4 @@ unless impl then
|
|
51
54
|
exit 1
|
52
55
|
end
|
53
56
|
|
54
|
-
TestUnitHeckler.validate(impl, meth)
|
57
|
+
exit TestUnitHeckler.validate(impl, meth) ? 0 : 1
|
data/lib/heckle.rb
CHANGED
@@ -2,6 +2,7 @@ require 'rubygems'
|
|
2
2
|
require 'parse_tree'
|
3
3
|
require 'ruby2ruby'
|
4
4
|
require 'timeout'
|
5
|
+
require 'tempfile'
|
5
6
|
|
6
7
|
class String
|
7
8
|
def to_class
|
@@ -10,10 +11,11 @@ class String
|
|
10
11
|
end
|
11
12
|
|
12
13
|
class Heckle < SexpProcessor
|
13
|
-
VERSION = '1.
|
14
|
+
VERSION = '1.3.0'
|
14
15
|
MUTATABLE_NODES = [:if, :lit, :str, :true, :false, :while, :until]
|
15
16
|
WINDOZE = RUBY_PLATFORM =~ /mswin/
|
16
17
|
NULL_PATH = WINDOZE ? 'NUL:' : '/dev/null'
|
18
|
+
DIFF = WINDOZE ? 'diff.exe' : 'diff'
|
17
19
|
|
18
20
|
attr_accessor(:klass_name, :method_name, :klass, :method, :mutatees,
|
19
21
|
:original_tree, :mutation_count, :node_count,
|
@@ -111,10 +113,13 @@ class Heckle < SexpProcessor
|
|
111
113
|
unless @failures.empty?
|
112
114
|
@reporter.no_failures
|
113
115
|
@failures.each do |failure|
|
114
|
-
@
|
116
|
+
original = RubyToRuby.new.process(@original_tree.deep_clone)
|
117
|
+
@reporter.failure(original, failure)
|
115
118
|
end
|
119
|
+
false
|
116
120
|
else
|
117
121
|
@reporter.no_surviving_mutants
|
122
|
+
true
|
118
123
|
end
|
119
124
|
end
|
120
125
|
|
@@ -123,14 +128,16 @@ class Heckle < SexpProcessor
|
|
123
128
|
end
|
124
129
|
|
125
130
|
def heckle(exp)
|
126
|
-
|
131
|
+
exp_copy = exp.deep_clone
|
127
132
|
src = begin
|
128
133
|
RubyToRuby.new.process(exp)
|
129
134
|
rescue => e
|
130
|
-
puts "Error: #{e.message} with: #{klass_name}##{method_name}: #{
|
135
|
+
puts "Error: #{e.message} with: #{klass_name}##{method_name}: #{exp_copy.inspect}"
|
131
136
|
raise e
|
132
137
|
end
|
133
|
-
|
138
|
+
|
139
|
+
original = RubyToRuby.new.process(@original_tree.deep_clone)
|
140
|
+
@reporter.replacing(klass_name, method_name, original, src) if @@debug
|
134
141
|
|
135
142
|
clean_name = method_name.to_s.gsub(/self\./, '')
|
136
143
|
self.count += 1
|
@@ -170,7 +177,7 @@ class Heckle < SexpProcessor
|
|
170
177
|
when Symbol
|
171
178
|
[:lit, rand_symbol]
|
172
179
|
when Regexp
|
173
|
-
[:lit,
|
180
|
+
[:lit, Regexp.new(Regexp.escape(rand_string.gsub(/\//, '\\/')))]
|
174
181
|
when Range
|
175
182
|
[:lit, rand_range]
|
176
183
|
end
|
@@ -344,7 +351,7 @@ class Heckle < SexpProcessor
|
|
344
351
|
end
|
345
352
|
|
346
353
|
def rand_number
|
347
|
-
(rand(
|
354
|
+
(rand(100) + 1)*((-1)**rand(2))
|
348
355
|
end
|
349
356
|
|
350
357
|
def rand_string
|
@@ -404,7 +411,6 @@ class Heckle < SexpProcessor
|
|
404
411
|
end
|
405
412
|
|
406
413
|
def warning(message)
|
407
|
-
puts
|
408
414
|
puts "!" * 70
|
409
415
|
puts "!!! #{message}"
|
410
416
|
puts "!" * 70
|
@@ -412,7 +418,6 @@ class Heckle < SexpProcessor
|
|
412
418
|
end
|
413
419
|
|
414
420
|
def info(message)
|
415
|
-
puts
|
416
421
|
puts "*"*70
|
417
422
|
puts "*** #{message}"
|
418
423
|
puts "*"*70
|
@@ -420,19 +425,42 @@ class Heckle < SexpProcessor
|
|
420
425
|
end
|
421
426
|
|
422
427
|
def no_failures
|
423
|
-
puts
|
428
|
+
puts
|
429
|
+
puts "The following mutations didn't cause test failures:"
|
430
|
+
puts
|
431
|
+
end
|
432
|
+
|
433
|
+
def diff(original, mutation)
|
434
|
+
length = [original.split(/\n/).size, mutation.split(/\n/).size].max
|
435
|
+
|
436
|
+
Tempfile.open("orig") do |a|
|
437
|
+
a.puts(original)
|
438
|
+
a.flush
|
439
|
+
|
440
|
+
Tempfile.open("fail") do |b|
|
441
|
+
b.puts(mutation)
|
442
|
+
b.flush
|
443
|
+
|
444
|
+
diff_flags = " "
|
445
|
+
|
446
|
+
output = `#{Heckle::DIFF} -U #{length} --label original #{a.path} --label mutation #{b.path}`
|
447
|
+
puts output.sub(/^@@.*?\n/, '')
|
448
|
+
puts
|
449
|
+
end
|
450
|
+
end
|
424
451
|
end
|
425
452
|
|
426
|
-
def failure(failure)
|
427
|
-
|
453
|
+
def failure(original, failure)
|
454
|
+
self.diff original, failure
|
428
455
|
end
|
429
456
|
|
430
457
|
def no_surviving_mutants
|
431
458
|
puts "No mutants survived. Cool!\n\n"
|
432
459
|
end
|
433
460
|
|
434
|
-
def replacing(klass_name, method_name, src)
|
435
|
-
puts "Replacing #{klass_name}##{method_name} with:\n\n
|
461
|
+
def replacing(klass_name, method_name, original, src)
|
462
|
+
puts "Replacing #{klass_name}##{method_name} with:\n\n"
|
463
|
+
diff(original, src)
|
436
464
|
end
|
437
465
|
|
438
466
|
def report_test_failures
|
data/lib/test_unit_heckler.rb
CHANGED
@@ -20,32 +20,59 @@ class TestUnitHeckler < Heckle
|
|
20
20
|
def self.validate(klass_name, method_name = nil)
|
21
21
|
load_test_files
|
22
22
|
klass = klass_name.to_class
|
23
|
-
|
23
|
+
|
24
|
+
# Does the method exist?
|
25
|
+
klass_methods = klass.singleton_methods(false).collect {|meth| "self.#{meth}"}
|
26
|
+
if method_name
|
27
|
+
if method_name =~ /self\./
|
28
|
+
abort "Unknown method: #{klass_name}.#{method_name.gsub('self.', '')}" unless klass_methods.include? method_name
|
29
|
+
else
|
30
|
+
abort "Unknown method: #{klass_name}##{method_name}" unless klass.instance_methods(false).include? method_name
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
24
34
|
initial_time = Time.now
|
25
35
|
|
26
36
|
unless self.new(klass_name).tests_pass? then
|
27
37
|
abort "Initial run of tests failed... fix and run heckle again"
|
28
38
|
end
|
29
|
-
|
39
|
+
|
30
40
|
if self.guess_timeout?
|
31
41
|
running_time = (Time.now - initial_time)
|
32
42
|
adjusted_timeout = (running_time * 2 < 5) ? 5 : (running_time * 2)
|
33
43
|
self.timeout = adjusted_timeout
|
34
44
|
puts "Setting timeout at #{adjusted_timeout} seconds." if @@debug
|
35
|
-
|
45
|
+
|
36
46
|
end
|
37
|
-
|
38
|
-
puts "Initial tests pass. Let's rumble."
|
47
|
+
|
39
48
|
self.timeout = adjusted_timeout
|
40
|
-
|
49
|
+
|
41
50
|
puts "Initial tests pass. Let's rumble."
|
42
|
-
|
43
|
-
|
51
|
+
puts
|
52
|
+
|
44
53
|
methods = method_name ? Array(method_name) : klass.instance_methods(false) + klass_methods
|
45
|
-
|
46
|
-
|
47
|
-
|
54
|
+
|
55
|
+
counts = Hash.new(0)
|
56
|
+
methods.sort.each do |method_name|
|
57
|
+
result = self.new(klass_name, method_name).validate
|
58
|
+
counts[result] += 1
|
48
59
|
end
|
60
|
+
all_good = counts[false] == 0
|
61
|
+
|
62
|
+
puts "Heckle Results:"
|
63
|
+
puts
|
64
|
+
puts "Passed : %3d" % counts[true]
|
65
|
+
puts "Failed : %3d" % counts[false]
|
66
|
+
puts "Thick Skin: %3d" % counts[nil]
|
67
|
+
puts
|
68
|
+
|
69
|
+
if all_good then
|
70
|
+
puts "All heckling was thwarted! YAY!!!"
|
71
|
+
else
|
72
|
+
puts "Improve the tests and try again."
|
73
|
+
end
|
74
|
+
|
75
|
+
all_good
|
49
76
|
end
|
50
77
|
|
51
78
|
def initialize(klass_name=nil, method_name=nil)
|
metadata
CHANGED
@@ -3,8 +3,8 @@ 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: 2007-
|
6
|
+
version: 1.3.0
|
7
|
+
date: 2007-02-13 00:00:00 -08:00
|
8
8
|
summary: Unit Test Sadism
|
9
9
|
require_paths:
|
10
10
|
- lib
|
@@ -71,5 +71,5 @@ dependencies:
|
|
71
71
|
requirements:
|
72
72
|
- - ">="
|
73
73
|
- !ruby/object:Gem::Version
|
74
|
-
version: 1.
|
74
|
+
version: 1.2.0
|
75
75
|
version:
|