chaser 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +109 -0
- data/Manifest.txt +10 -0
- data/README.txt +47 -0
- data/Rakefile +22 -0
- data/bin/chaser +55 -0
- data/lib/chaser.rb +329 -0
- data/lib/test_unit_chaser.rb +105 -0
- data/test/fixtures/chased.rb +13 -0
- data/test/test_chaser.rb +76 -0
- metadata +67 -0
data/History.txt
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
History for Chaser:
|
2
|
+
|
3
|
+
* Coming soon
|
4
|
+
|
5
|
+
History for Heckle:
|
6
|
+
|
7
|
+
=== 1.4.3 / 2009-06-23
|
8
|
+
|
9
|
+
* 2 minor enhancements:
|
10
|
+
|
11
|
+
* Added autotest/heckle plugin
|
12
|
+
* Skipping testing on 1.9
|
13
|
+
|
14
|
+
=== 1.4.2 / 2009-02-08
|
15
|
+
|
16
|
+
* 2 bug fixes:
|
17
|
+
|
18
|
+
* Fixed Ruby2Ruby dependency and Ruby2Ruby references (name changed).
|
19
|
+
Reported by David Chelimsky
|
20
|
+
* Fix bug #11435 where [:iter, [:call], ...] would cause an endless
|
21
|
+
loop. Reported by Thomas Preymesser.
|
22
|
+
|
23
|
+
=== 1.4.1 / 2007-06-05
|
24
|
+
|
25
|
+
* 3 bug fixes:
|
26
|
+
|
27
|
+
* Add zentest as a heckle dependency. Closes #10996
|
28
|
+
* Fixed heckling of call with blocks.
|
29
|
+
* Fix test_unit_heckler's test_pass? so it returns the result of the
|
30
|
+
run rather than ARGV.clear
|
31
|
+
|
32
|
+
=== 1.4.0 / 2007-05-18
|
33
|
+
|
34
|
+
* 2 major enhancements:
|
35
|
+
|
36
|
+
* Method calls are now heckled (by removal).
|
37
|
+
* Assignments are now heckled (by value changing).
|
38
|
+
|
39
|
+
* 3 minor enhancements:
|
40
|
+
|
41
|
+
* Added --focus to feel the Eye of Sauron (specify unit tests to run).
|
42
|
+
* Specify nodes to be included/excluded in heckle with -n/-x.
|
43
|
+
* Test only assignments with --assignments
|
44
|
+
|
45
|
+
=== 1.3.0 / 2007-02-12
|
46
|
+
|
47
|
+
* 1 major enhancement:
|
48
|
+
|
49
|
+
* Unified diffs for mutatated methods
|
50
|
+
|
51
|
+
* 4 minor enhancements:
|
52
|
+
|
53
|
+
* Now returns exit status 1 if failed.
|
54
|
+
* Added a simple report at the end.
|
55
|
+
* Runs are now sorted by method.
|
56
|
+
* Autodetects rails and changes test_pattern accordingly.
|
57
|
+
|
58
|
+
* 2 bug fixes:
|
59
|
+
|
60
|
+
* Aborts when an unknown method is supplied.
|
61
|
+
* Escapes slashes in random regexps.
|
62
|
+
|
63
|
+
=== 1.2.0 / 2007-01-15
|
64
|
+
|
65
|
+
* 2 major enhancements:
|
66
|
+
|
67
|
+
* Timeout for tests set dynamically and overridable with -T
|
68
|
+
* Class method support with "self.method_name"
|
69
|
+
|
70
|
+
* 3 minor enhancements:
|
71
|
+
|
72
|
+
* -b allows heckling of branches only
|
73
|
+
* Restructured class heirarchy and got rid of Base and others.
|
74
|
+
* Revamped the tests and reduced size by 60%.
|
75
|
+
|
76
|
+
* 1 bug fix:
|
77
|
+
|
78
|
+
* Fixed the infinite loop caused by syntax errors
|
79
|
+
|
80
|
+
=== 1.1.1 / 2006-12-20
|
81
|
+
|
82
|
+
* 3 bug fixes:
|
83
|
+
|
84
|
+
* Load tests properly when supplying method name.
|
85
|
+
* Make sure random symbols have at least one character.
|
86
|
+
* Removed all extra warnings from the unit tests. Consolidated and cleaned.
|
87
|
+
|
88
|
+
=== 1.1.0 / 2006-12-19
|
89
|
+
|
90
|
+
* 12 major enhancements:
|
91
|
+
|
92
|
+
* Able to roll back original method after processing.
|
93
|
+
* Can mutate numeric literals.
|
94
|
+
* Can mutate strings.
|
95
|
+
* Can mutate a node at a time.
|
96
|
+
* Can mutate if/unless
|
97
|
+
* Decoupled from Test::Unit
|
98
|
+
* Cleaner output
|
99
|
+
* Can mutate true and false.
|
100
|
+
* Can mutate while and until.
|
101
|
+
* Can mutate regexes, ranges, symbols
|
102
|
+
* Can run against entire classes
|
103
|
+
* Command line options!
|
104
|
+
|
105
|
+
=== 1.0.0 / 2006-10-22
|
106
|
+
|
107
|
+
* 1 major enhancement
|
108
|
+
|
109
|
+
* Birthday!
|
data/Manifest.txt
ADDED
data/README.txt
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
= chaser
|
2
|
+
|
3
|
+
* Web site coming real soon now
|
4
|
+
|
5
|
+
== DESCRIPTION:
|
6
|
+
|
7
|
+
Chaser is unit test sadism(tm), like Seattlerb's Heckle. It's more or less mutation testing, except that rather than mutating every line of code it can get its claws into, it merely modifies the return value of targeted methods. If the unit tests don't notice the modified return values, or the program going haywire as a result of using those modified return values, then they aren't doing their jobs properly.
|
8
|
+
|
9
|
+
Unit test sadism is a trademark of Ryan Davis and Kevin Clark, and is used without permission.
|
10
|
+
|
11
|
+
== FEATURES/PROBLEMS:
|
12
|
+
|
13
|
+
* It only mutates the return values of methods.
|
14
|
+
* It should be able to mutate class methods, and work in ruby 1.9 or Windows.
|
15
|
+
|
16
|
+
== REQUIREMENTS:
|
17
|
+
|
18
|
+
* None!
|
19
|
+
|
20
|
+
== INSTALL:
|
21
|
+
|
22
|
+
* sudo gem install agrimm-chaser
|
23
|
+
|
24
|
+
== LICENSE:
|
25
|
+
|
26
|
+
(The MIT License)
|
27
|
+
|
28
|
+
Copyright (c) 2006-2009 Ryan Davis and Kevin Clark and Andrew Grimm
|
29
|
+
|
30
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
31
|
+
a copy of this software and associated documentation files (the
|
32
|
+
'Software'), to deal in the Software without restriction, including
|
33
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
34
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
35
|
+
permit persons to whom the Software is furnished to do so, subject to
|
36
|
+
the following conditions:
|
37
|
+
|
38
|
+
The above copyright notice and this permission notice shall be
|
39
|
+
included in all copies or substantial portions of the Software.
|
40
|
+
|
41
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
42
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
43
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
44
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
45
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
46
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
47
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
task :default => [:test]
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'jeweler'
|
5
|
+
require File.dirname(__FILE__) + "/lib/chaser.rb"
|
6
|
+
Jeweler::Tasks.new do |gemspec|
|
7
|
+
gemspec.name = "chaser"
|
8
|
+
gemspec.summary = "Unit test sadism, with less masochism"
|
9
|
+
gemspec.description = "Lightweight mutation testing in any flavor of ruby"
|
10
|
+
gemspec.email = "andrew.j.grimm@gmail.com"
|
11
|
+
gemspec.authors = ["Andrew Grimm", "Ryan Davis", "Eric Hodel", "Kevin Clark"]
|
12
|
+
gemspec.add_dependency('test-unit') #Ruby 1.9 doesn't have full test-unit in the standard library.
|
13
|
+
gemspec.version = Chaser::VERSION
|
14
|
+
end
|
15
|
+
rescue LoadError
|
16
|
+
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
17
|
+
end
|
18
|
+
|
19
|
+
task :test do
|
20
|
+
ruby "test/test_chaser.rb"
|
21
|
+
end
|
22
|
+
|
data/bin/chaser
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
#!/usr/local/bin/ruby
|
2
|
+
|
3
|
+
$:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
|
4
|
+
require 'test_unit_chaser'
|
5
|
+
require 'optparse'
|
6
|
+
|
7
|
+
force = false
|
8
|
+
|
9
|
+
opts = OptionParser.new do |opts|
|
10
|
+
opts.banner = "Usage: #{File.basename($0)} class_name [method_name]"
|
11
|
+
opts.on("-v", "--verbose", "Loudly explain chaser run") do |opt|
|
12
|
+
TestUnitChaser.debug = true
|
13
|
+
end
|
14
|
+
|
15
|
+
opts.on("-V", "--version", "Prints Chaser's version number") do |opt|
|
16
|
+
puts "Chaser #{Chaser::VERSION}"
|
17
|
+
exit 0
|
18
|
+
end
|
19
|
+
|
20
|
+
opts.on("-t", "--tests TEST_PATTERN",
|
21
|
+
"Location of tests (glob)") do |pattern|
|
22
|
+
TestUnitChaser.test_pattern = pattern
|
23
|
+
end
|
24
|
+
|
25
|
+
opts.on("-F", "--force", "Ignore initial test failures") do |opt|
|
26
|
+
force = true
|
27
|
+
end
|
28
|
+
|
29
|
+
opts.on("-T", "--timeout SECONDS", "The maximum time for a test run in seconds",
|
30
|
+
"Used to catch infinite loops") do |timeout|
|
31
|
+
Chaser.timeout = timeout.to_i
|
32
|
+
puts "Setting timeout at #{timeout} seconds."
|
33
|
+
end
|
34
|
+
|
35
|
+
opts.on("-h", "--help", "Show this message") do |opt|
|
36
|
+
puts opts
|
37
|
+
exit 0
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
looks_like_rails = test ?f, 'config/environment.rb'
|
42
|
+
TestUnitChaser.test_pattern = "test/**/*.rb" if looks_like_rails
|
43
|
+
|
44
|
+
opts.parse!
|
45
|
+
|
46
|
+
impl = ARGV.shift
|
47
|
+
meth = ARGV.shift
|
48
|
+
|
49
|
+
unless impl then
|
50
|
+
puts opts
|
51
|
+
exit 1
|
52
|
+
end
|
53
|
+
|
54
|
+
exit TestUnitChaser.validate(impl, meth, force)
|
55
|
+
|
data/lib/chaser.rb
ADDED
@@ -0,0 +1,329 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'timeout'
|
3
|
+
|
4
|
+
class String # :nodoc:
|
5
|
+
def to_class
|
6
|
+
split(/::/).inject(Object) { |klass, name| klass.const_get(name) }
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
##
|
11
|
+
# Test Unit Sadism
|
12
|
+
|
13
|
+
class Chaser
|
14
|
+
|
15
|
+
class Timeout < Timeout::Error; end
|
16
|
+
|
17
|
+
##
|
18
|
+
# The version of Chaser you are using.
|
19
|
+
|
20
|
+
VERSION = '0.0.1'
|
21
|
+
|
22
|
+
##
|
23
|
+
# Is this platform MS Windows-like?
|
24
|
+
|
25
|
+
WINDOZE = RUBY_PLATFORM =~ /mswin/
|
26
|
+
|
27
|
+
##
|
28
|
+
# Path to the bit bucket.
|
29
|
+
|
30
|
+
NULL_PATH = WINDOZE ? 'NUL:' : '/dev/null'
|
31
|
+
|
32
|
+
##
|
33
|
+
# Class being chased
|
34
|
+
|
35
|
+
attr_accessor :klass
|
36
|
+
|
37
|
+
##
|
38
|
+
# Name of class being chased
|
39
|
+
|
40
|
+
attr_accessor :klass_name
|
41
|
+
|
42
|
+
##
|
43
|
+
# Method being chased
|
44
|
+
|
45
|
+
attr_accessor :method
|
46
|
+
|
47
|
+
##
|
48
|
+
# Name of method being chased
|
49
|
+
|
50
|
+
attr_accessor :method_name
|
51
|
+
|
52
|
+
##
|
53
|
+
# The original version of the method being chased
|
54
|
+
|
55
|
+
attr_reader :old_method
|
56
|
+
|
57
|
+
@@debug = false
|
58
|
+
@@guess_timeout = true
|
59
|
+
@@timeout = 60 # default to something longer (can be overridden by runners)
|
60
|
+
|
61
|
+
def self.debug
|
62
|
+
@@debug
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.debug=(value)
|
66
|
+
@@debug = value
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.timeout=(value)
|
70
|
+
@@timeout = value
|
71
|
+
@@guess_timeout = false # We've set the timeout, don't guess
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.guess_timeout?
|
75
|
+
@@guess_timeout
|
76
|
+
end
|
77
|
+
|
78
|
+
##
|
79
|
+
# Creates a new Chaser that will chase +klass_name+ and +method_name+,
|
80
|
+
# sending results to +reporter+.
|
81
|
+
|
82
|
+
def initialize(klass_name = nil, method_name = nil, reporter = Reporter.new)
|
83
|
+
@klass_name = klass_name
|
84
|
+
@method_name = method_name.intern if method_name
|
85
|
+
|
86
|
+
@klass = klass_name.to_class
|
87
|
+
|
88
|
+
@method = nil
|
89
|
+
@reporter = reporter
|
90
|
+
|
91
|
+
@mutated = false
|
92
|
+
|
93
|
+
@failure = false
|
94
|
+
end
|
95
|
+
|
96
|
+
##
|
97
|
+
# Overwrite test_pass? for your own Chaser runner.
|
98
|
+
|
99
|
+
def tests_pass?
|
100
|
+
raise NotImplementedError
|
101
|
+
end
|
102
|
+
|
103
|
+
def run_tests
|
104
|
+
if tests_pass? then
|
105
|
+
record_passing_mutation
|
106
|
+
else
|
107
|
+
@reporter.report_test_failures
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
############################################################
|
112
|
+
### Running the script
|
113
|
+
|
114
|
+
def validate
|
115
|
+
@reporter.method_loaded(klass_name, method_name)
|
116
|
+
|
117
|
+
begin
|
118
|
+
modify_method
|
119
|
+
timeout(@@timeout, Chaser::Timeout) { run_tests }
|
120
|
+
rescue Chaser::Timeout
|
121
|
+
@reporter.warning "Your tests timed out. Chaser may have caused an infinite loop."
|
122
|
+
rescue Interrupt
|
123
|
+
@reporter.warning 'Mutation canceled, hit ^C again to exit'
|
124
|
+
sleep 2
|
125
|
+
end
|
126
|
+
|
127
|
+
unmodify_method # in case we're validating again. we should clean up.
|
128
|
+
|
129
|
+
if @failure
|
130
|
+
@reporter.report_failure
|
131
|
+
false
|
132
|
+
else
|
133
|
+
@reporter.no_surviving_mutant
|
134
|
+
true
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def record_passing_mutation
|
139
|
+
@failure = true
|
140
|
+
end
|
141
|
+
|
142
|
+
def unmodify_instance_method
|
143
|
+
chaser = self
|
144
|
+
@mutated = false
|
145
|
+
@klass.send(:define_method, @method_name) do |*args|
|
146
|
+
chaser.old_method.bind(self).call(*args)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def unmodify_class_method
|
151
|
+
chaser = self
|
152
|
+
@mutated = false
|
153
|
+
aliasing_class(@method_name).send(:define_method, clean_method_name) do |*args|
|
154
|
+
chaser.old_method.bind(self).call(*args)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def modify_instance_method
|
159
|
+
chaser = self
|
160
|
+
@mutated = true
|
161
|
+
@old_method = @klass.instance_method(@method_name)
|
162
|
+
@klass.send(:define_method, @method_name) do |*args|
|
163
|
+
chaser.mutate_value(chaser.old_method.bind(self).call(*args))
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
def modify_class_method
|
168
|
+
chaser = self
|
169
|
+
@mutated = true
|
170
|
+
@old_method = aliasing_class(@method_name).instance_method(clean_method_name)
|
171
|
+
aliasing_class(@method_name).send(:define_method, clean_method_name) do |*args|
|
172
|
+
chaser.mutate_value(chaser.old_method.bind(self).call(*args))
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
def modify_method
|
177
|
+
if method_name.to_s =~ /self\./
|
178
|
+
modify_class_method
|
179
|
+
else
|
180
|
+
modify_instance_method
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
def unmodify_method
|
185
|
+
if method_name.to_s =~ /self\./ #TODO fix duplication. Give the test a name
|
186
|
+
unmodify_class_method
|
187
|
+
else
|
188
|
+
unmodify_instance_method
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
|
193
|
+
##
|
194
|
+
# Replaces the value with a random value.
|
195
|
+
|
196
|
+
def mutate_value(value)
|
197
|
+
case value
|
198
|
+
when Fixnum, Float, Bignum
|
199
|
+
value + rand_number
|
200
|
+
when String
|
201
|
+
rand_string
|
202
|
+
when Symbol
|
203
|
+
rand_symbol
|
204
|
+
when Regexp
|
205
|
+
Regexp.new(Regexp.escape(rand_string.gsub(/\//, '\\/')))
|
206
|
+
when Range
|
207
|
+
rand_range
|
208
|
+
when NilClass, FalseClass
|
209
|
+
rand_number
|
210
|
+
when TrueClass
|
211
|
+
false
|
212
|
+
else
|
213
|
+
nil
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
############################################################
|
218
|
+
### Convenience methods
|
219
|
+
|
220
|
+
def aliasing_class(method_name)
|
221
|
+
method_name.to_s =~ /self\./ ? class << @klass; self; end : @klass
|
222
|
+
end
|
223
|
+
|
224
|
+
def clean_method_name
|
225
|
+
method_name.to_s.gsub(/self\./, '')
|
226
|
+
end
|
227
|
+
|
228
|
+
##
|
229
|
+
# Returns a random Fixnum.
|
230
|
+
|
231
|
+
def rand_number
|
232
|
+
(rand(100) + 1)*((-1)**rand(2))
|
233
|
+
end
|
234
|
+
|
235
|
+
##
|
236
|
+
# Returns a random String
|
237
|
+
|
238
|
+
def rand_string
|
239
|
+
size = rand(50)
|
240
|
+
str = ""
|
241
|
+
size.times { str << rand(126).chr }
|
242
|
+
str
|
243
|
+
end
|
244
|
+
|
245
|
+
##
|
246
|
+
# Returns a random Symbol
|
247
|
+
|
248
|
+
def rand_symbol
|
249
|
+
letters = ('a'..'z').to_a + ('A'..'Z').to_a
|
250
|
+
str = ""
|
251
|
+
(rand(50) + 1).times { str << letters[rand(letters.size)] }
|
252
|
+
:"#{str}"
|
253
|
+
end
|
254
|
+
|
255
|
+
##
|
256
|
+
# Returns a random Range
|
257
|
+
|
258
|
+
def rand_range
|
259
|
+
min = rand(50)
|
260
|
+
max = min + rand(50)
|
261
|
+
min..max
|
262
|
+
end
|
263
|
+
|
264
|
+
##
|
265
|
+
# Suppresses output on $stdout and $stderr.
|
266
|
+
|
267
|
+
def silence_stream
|
268
|
+
return yield if @@debug
|
269
|
+
|
270
|
+
begin
|
271
|
+
dead = File.open(Chaser::NULL_PATH, "w")
|
272
|
+
|
273
|
+
$stdout.flush
|
274
|
+
$stderr.flush
|
275
|
+
|
276
|
+
oldstdout = $stdout.dup
|
277
|
+
oldstderr = $stderr.dup
|
278
|
+
|
279
|
+
$stdout.reopen(dead)
|
280
|
+
$stderr.reopen(dead)
|
281
|
+
|
282
|
+
result = yield
|
283
|
+
|
284
|
+
ensure
|
285
|
+
$stdout.flush
|
286
|
+
$stderr.flush
|
287
|
+
|
288
|
+
$stdout.reopen(oldstdout)
|
289
|
+
$stderr.reopen(oldstderr)
|
290
|
+
result
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
class Reporter
|
295
|
+
def method_loaded(klass_name, method_name)
|
296
|
+
info "#{klass_name}\##{method_name} loaded"
|
297
|
+
end
|
298
|
+
|
299
|
+
def warning(message)
|
300
|
+
puts "!" * 70
|
301
|
+
puts "!!! #{message}"
|
302
|
+
puts "!" * 70
|
303
|
+
puts
|
304
|
+
end
|
305
|
+
|
306
|
+
def info(message)
|
307
|
+
puts "*"*70
|
308
|
+
puts "*** #{message}"
|
309
|
+
puts "*"*70
|
310
|
+
puts
|
311
|
+
end
|
312
|
+
|
313
|
+
def report_failure
|
314
|
+
puts
|
315
|
+
puts "The affected method didn't cause test failures."
|
316
|
+
puts
|
317
|
+
end
|
318
|
+
|
319
|
+
def no_surviving_mutant
|
320
|
+
puts "The mutant didn't survive. Cool!\n\n"
|
321
|
+
end
|
322
|
+
|
323
|
+
def report_test_failures
|
324
|
+
puts "Tests failed -- this is good" if Chaser.debug
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
end
|
329
|
+
|
@@ -0,0 +1,105 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'test/unit/autorunner'
|
4
|
+
require 'test/unit/testcase'
|
5
|
+
require 'chaser'
|
6
|
+
|
7
|
+
$: << 'lib' << 'test'
|
8
|
+
|
9
|
+
# Make sure test/unit doesn't swallow our timeout
|
10
|
+
begin
|
11
|
+
Test::Unit::TestCase::PASSTHROUGH_EXCEPTIONS << Chaser::Timeout
|
12
|
+
rescue NameError
|
13
|
+
# ignore
|
14
|
+
end
|
15
|
+
|
16
|
+
class TestUnitChaser < Chaser
|
17
|
+
|
18
|
+
@@test_pattern = 'test/test_*.rb'
|
19
|
+
@@tests_loaded = false
|
20
|
+
|
21
|
+
def self.test_pattern=(value)
|
22
|
+
@@test_pattern = value
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.load_test_files
|
26
|
+
@@tests_loaded = true
|
27
|
+
Dir.glob(@@test_pattern).each {|test| require test}
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.validate(klass_name, method_name = nil, force = false)
|
31
|
+
load_test_files
|
32
|
+
klass = klass_name.to_class
|
33
|
+
|
34
|
+
# Does the method exist?
|
35
|
+
klass_methods = klass.singleton_methods(false).collect {|meth| "self.#{meth}"}
|
36
|
+
if method_name
|
37
|
+
if method_name =~ /self\./
|
38
|
+
abort "Unknown method: #{klass_name}.#{method_name.gsub('self.', '')}" unless klass_methods.include? method_name
|
39
|
+
else
|
40
|
+
abort "Unknown method: #{klass_name}##{method_name}" unless klass.instance_methods(false).include? method_name
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
initial_time = Time.now
|
45
|
+
|
46
|
+
chaser = self.new(klass_name)
|
47
|
+
|
48
|
+
passed = chaser.tests_pass?
|
49
|
+
|
50
|
+
unless force or passed then
|
51
|
+
abort "Initial run of tests failed... fix and run chaser again"
|
52
|
+
end
|
53
|
+
|
54
|
+
if self.guess_timeout? then
|
55
|
+
running_time = Time.now - initial_time
|
56
|
+
adjusted_timeout = (running_time * 2 < 5) ? 5 : (running_time * 2).ceil
|
57
|
+
self.timeout = adjusted_timeout
|
58
|
+
end
|
59
|
+
|
60
|
+
puts "Timeout set to #{adjusted_timeout} seconds."
|
61
|
+
|
62
|
+
if passed then
|
63
|
+
puts "Initial tests pass. Let's rumble."
|
64
|
+
else
|
65
|
+
puts "Initial tests failed but you forced things. Let's rumble."
|
66
|
+
end
|
67
|
+
puts
|
68
|
+
|
69
|
+
methods = method_name ? Array(method_name) : klass.instance_methods(false) + klass_methods
|
70
|
+
|
71
|
+
counts = Hash.new(0)
|
72
|
+
methods.sort.each do |method_name|
|
73
|
+
result = self.new(klass_name, method_name).validate
|
74
|
+
counts[result] += 1
|
75
|
+
end
|
76
|
+
all_good = counts[false] == 0
|
77
|
+
|
78
|
+
puts "Chaser Results:"
|
79
|
+
puts
|
80
|
+
puts "Passed : %3d" % counts[true]
|
81
|
+
puts "Failed : %3d" % counts[false]
|
82
|
+
puts
|
83
|
+
|
84
|
+
if all_good then
|
85
|
+
puts "All chasing was thwarted! YAY!!!"
|
86
|
+
else
|
87
|
+
puts "Improve the tests and try again."
|
88
|
+
end
|
89
|
+
|
90
|
+
all_good
|
91
|
+
end
|
92
|
+
|
93
|
+
def initialize(klass_name=nil, method_name=nil)
|
94
|
+
super
|
95
|
+
self.class.load_test_files unless @@tests_loaded
|
96
|
+
end
|
97
|
+
|
98
|
+
def tests_pass?
|
99
|
+
silence_stream do
|
100
|
+
result = Test::Unit::AutoRunner.run
|
101
|
+
ARGV.clear
|
102
|
+
result
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
data/test/test_chaser.rb
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__) + '/fixtures')
|
2
|
+
$:.unshift(File.dirname(__FILE__) + '/../lib')
|
3
|
+
|
4
|
+
require 'test/unit/testcase'
|
5
|
+
require 'test/unit' if $0 == __FILE__
|
6
|
+
require 'test_unit_chaser'
|
7
|
+
require 'chased'
|
8
|
+
|
9
|
+
class TestChaser < Chaser
|
10
|
+
def rand(*args)
|
11
|
+
5
|
12
|
+
end
|
13
|
+
|
14
|
+
def rand_string
|
15
|
+
"l33t h4x0r"
|
16
|
+
end
|
17
|
+
|
18
|
+
def rand_number(*args)
|
19
|
+
5
|
20
|
+
end
|
21
|
+
|
22
|
+
def rand_symbol
|
23
|
+
:"l33t h4x0r"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class ChaserTestCase < Test::Unit::TestCase
|
28
|
+
unless defined? Mini then
|
29
|
+
undef_method :default_test
|
30
|
+
alias :refute_equal :assert_not_equal
|
31
|
+
end
|
32
|
+
|
33
|
+
def setup
|
34
|
+
end
|
35
|
+
|
36
|
+
def teardown
|
37
|
+
@chaser.unmodify_method if defined?(@chaser) && @chaser
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_unmodified_behaves_as_expected
|
41
|
+
chased = Chased.new
|
42
|
+
assert_equal 5, chased.add(2,3), "Unmodified version should equal 5"
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_modify_and_unmodify_instance_method
|
46
|
+
@chaser = TestChaser.new("Chased", "add")
|
47
|
+
chased = Chased.new
|
48
|
+
assert_equal 5, chased.add(2,3), "method has been modified before it should have been"
|
49
|
+
@chaser.modify_method
|
50
|
+
assert_equal 10, chased.add(2,3), "method hasn't been modified"
|
51
|
+
@chaser.unmodify_method
|
52
|
+
assert_equal 5, chased.add(2,3), "method should be back to normal, but it isn't"
|
53
|
+
end
|
54
|
+
|
55
|
+
def test_modify_and_unmodify_string
|
56
|
+
@chaser = TestChaser.new("Chased", "say_hello")
|
57
|
+
chased = Chased.new
|
58
|
+
assert_equal "G'day!", chased.say_hello, "method has been modified before it should have been"
|
59
|
+
@chaser.modify_method
|
60
|
+
assert_equal "l33t h4x0r", chased.say_hello, "method hasn't been modified"
|
61
|
+
@chaser.unmodify_method
|
62
|
+
assert_equal "G'day!", chased.say_hello, "method should be back to normal, but it isn't"
|
63
|
+
end
|
64
|
+
|
65
|
+
def test_modify_and_unmodify_class_method
|
66
|
+
@chaser = TestChaser.new("Chased", "self.static_method")
|
67
|
+
assert_equal "Zap!", Chased.static_method, "class method has been modified before it should have been"
|
68
|
+
@chaser.modify_method
|
69
|
+
assert_equal "l33t h4x0r", Chased.static_method, "class method hasn't been modified"
|
70
|
+
@chaser.unmodify_method
|
71
|
+
assert_equal "Zap!", Chased.static_method, "class method should be back to normal, but it isn't"
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
|
metadata
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: chaser
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Andrew Grimm
|
8
|
+
- Ryan Davis
|
9
|
+
- Eric Hodel
|
10
|
+
- Kevin Clark
|
11
|
+
autorequire:
|
12
|
+
bindir: bin
|
13
|
+
cert_chain: []
|
14
|
+
|
15
|
+
date: 2009-11-07 00:00:00 +11:00
|
16
|
+
default_executable: chaser
|
17
|
+
dependencies: []
|
18
|
+
|
19
|
+
description: Lightweight mutation testing in any flavor of ruby
|
20
|
+
email: andrew.j.grimm@gmail.com
|
21
|
+
executables:
|
22
|
+
- chaser
|
23
|
+
extensions: []
|
24
|
+
|
25
|
+
extra_rdoc_files:
|
26
|
+
- README.txt
|
27
|
+
files:
|
28
|
+
- History.txt
|
29
|
+
- Manifest.txt
|
30
|
+
- README.txt
|
31
|
+
- Rakefile
|
32
|
+
- bin/chaser
|
33
|
+
- lib/chaser.rb
|
34
|
+
- lib/test_unit_chaser.rb
|
35
|
+
- test/fixtures/chased.rb
|
36
|
+
- test/test_chaser.rb
|
37
|
+
has_rdoc: true
|
38
|
+
homepage:
|
39
|
+
licenses: []
|
40
|
+
|
41
|
+
post_install_message:
|
42
|
+
rdoc_options:
|
43
|
+
- --charset=UTF-8
|
44
|
+
require_paths:
|
45
|
+
- lib
|
46
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
47
|
+
requirements:
|
48
|
+
- - ">="
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: "0"
|
51
|
+
version:
|
52
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: "0"
|
57
|
+
version:
|
58
|
+
requirements: []
|
59
|
+
|
60
|
+
rubyforge_project:
|
61
|
+
rubygems_version: 1.3.5
|
62
|
+
signing_key:
|
63
|
+
specification_version: 3
|
64
|
+
summary: Unit test sadism, with less masochism
|
65
|
+
test_files:
|
66
|
+
- test/fixtures/chased.rb
|
67
|
+
- test/test_chaser.rb
|