Class: Chaser
- Inherits:
-
Object
- Object
- Chaser
- Defined in:
- lib/zombie-chaser/chaser.rb
Overview
Test Unit Sadism
Direct Known Subclasses
Defined Under Namespace
Constant Summary collapse
- VERSION =
The version of Chaser you are using.
'0.0.4'
- WINDOZE =
Is this platform MS Windows-like?
RUBY_PLATFORM =~ /mswin/
- NULL_PATH =
Path to the bit bucket.
WINDOZE ? 'NUL:' : '/dev/null'
- @@debug =
false
- @@guess_timeout =
true
- @@timeout =
default to something longer (can be overridden by runners)
60
Instance Attribute Summary collapse
-
#klass ⇒ Object
Class being chased.
-
#klass_name ⇒ Object
Name of class being chased.
-
#method ⇒ Object
Method being chased.
-
#method_name ⇒ Object
Name of method being chased.
-
#old_method ⇒ Object
readonly
The original version of the method being chased.
Class Method Summary collapse
Instance Method Summary collapse
-
#aliasing_class(method_name) ⇒ Object
Convenience methods.
- #calculate_proxy_method_name(original_name) ⇒ Object
- #clean_method_name ⇒ Object
-
#initialize(klass_name = nil, method_name = nil, reporter = Reporter.new) ⇒ Chaser
constructor
Creates a new Chaser that will chase
klass_name
andmethod_name
, sending results toreporter
. -
#interface_puts(*args) ⇒ Object
For the benefit of puts calls in ZombieTestChaser.validate.
- #modify_class_method ⇒ Object
-
#modify_instance_method ⇒ Object
Ruby 1.8 doesn’t allow define_method to handle blocks.
- #modify_method ⇒ Object
-
#mutate_value(value) ⇒ Object
Replaces the value with a random value.
-
#rand_number ⇒ Object
Returns a random Fixnum.
-
#rand_range ⇒ Object
Returns a random Range.
-
#rand_string ⇒ Object
Returns a random String.
-
#rand_symbol ⇒ Object
Returns a random Symbol.
- #record_passing_mutation ⇒ Object
- #run_tests ⇒ Object
-
#silence_stream ⇒ Object
Suppresses output on $stdout and $stderr.
-
#tests_pass? ⇒ Boolean
Overwrite test_pass? for your own Chaser runner.
- #unmodify_class_method ⇒ Object
- #unmodify_instance_method ⇒ Object
- #unmodify_method ⇒ Object
-
#validate ⇒ Object
Running the script.
Constructor Details
#initialize(klass_name = nil, method_name = nil, reporter = Reporter.new) ⇒ Chaser
Creates a new Chaser that will chase klass_name
and method_name
, sending results to reporter
.
88 89 90 91 92 93 94 95 96 97 98 99 100 |
# File 'lib/zombie-chaser/chaser.rb', line 88 def initialize(klass_name = nil, method_name = nil, reporter = Reporter.new) @klass_name = klass_name @method_name = method_name.intern if method_name @klass = klass_name.to_class if klass_name @method = nil @reporter = reporter @mutated = false @failure = false end |
Instance Attribute Details
#klass ⇒ Object
Class being chased
36 37 38 |
# File 'lib/zombie-chaser/chaser.rb', line 36 def klass @klass end |
#klass_name ⇒ Object
Name of class being chased
41 42 43 |
# File 'lib/zombie-chaser/chaser.rb', line 41 def klass_name @klass_name end |
#method ⇒ Object
Method being chased
46 47 48 |
# File 'lib/zombie-chaser/chaser.rb', line 46 def method @method end |
#method_name ⇒ Object
Name of method being chased
51 52 53 |
# File 'lib/zombie-chaser/chaser.rb', line 51 def method_name @method_name end |
#old_method ⇒ Object (readonly)
The original version of the method being chased
56 57 58 |
# File 'lib/zombie-chaser/chaser.rb', line 56 def old_method @old_method end |
Class Method Details
.debug ⇒ Object
62 63 64 |
# File 'lib/zombie-chaser/chaser.rb', line 62 def self.debug @@debug end |
.debug=(value) ⇒ Object
66 67 68 |
# File 'lib/zombie-chaser/chaser.rb', line 66 def self.debug=(value) @@debug = value end |
.guess_timeout? ⇒ Boolean
75 76 77 |
# File 'lib/zombie-chaser/chaser.rb', line 75 def self.guess_timeout? @@guess_timeout end |
.timeout=(value) ⇒ Object
70 71 72 73 |
# File 'lib/zombie-chaser/chaser.rb', line 70 def self.timeout=(value) @@timeout = value @@guess_timeout = false # We've set the timeout, don't guess end |
Instance Method Details
#aliasing_class(method_name) ⇒ Object
Convenience methods
275 276 277 |
# File 'lib/zombie-chaser/chaser.rb', line 275 def aliasing_class(method_name) method_name.to_s =~ /self\./ ? class << @klass; self; end : @klass end |
#calculate_proxy_method_name(original_name) ⇒ Object
148 149 150 151 152 153 154 155 156 157 158 |
# File 'lib/zombie-chaser/chaser.rb', line 148 def calculate_proxy_method_name(original_name) result = "__chaser_proxy__#{original_name}" character_renaming = {"[]" => "square_brackets", "^" => "exclusive_or", "=" => "equals", "&" => "ampersand", "*" => "splat", "+" => "plus", "-" => "minus", "%" => "percent", "~" => "tilde", "@" => "at", "/" => "forward_slash", "<" => "less_than", ">" => "greater_than"} character_renaming.each do |characters, renamed_string_portion| result.gsub!(characters, renamed_string_portion) end result end |
#clean_method_name ⇒ Object
279 280 281 |
# File 'lib/zombie-chaser/chaser.rb', line 279 def clean_method_name method_name.to_s.gsub(/self\./, '') end |
#interface_puts(*args) ⇒ Object
For the benefit of puts calls in ZombieTestChaser.validate
80 81 82 |
# File 'lib/zombie-chaser/chaser.rb', line 80 def interface_puts(*args) @reporter.interface_puts(*args) end |
#modify_class_method ⇒ Object
204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 |
# File 'lib/zombie-chaser/chaser.rb', line 204 def modify_class_method chaser = self @mutated = true @old_method = aliasing_class(@method_name).instance_method(clean_method_name) aliasing_class(@method_name).class_eval do remove_method(chaser.clean_method_name.to_sym) end chaser_proxy_method_name = calculate_proxy_method_name(clean_method_name) workaround_method_code_string = <<-EOM def #{@method_name}(*args, &block) #{chaser_proxy_method_name}(block, *args) end EOM @klass.class_eval do eval(workaround_method_code_string) end aliasing_class(@method_name).send(:define_method, chaser_proxy_method_name) do |block, *args| original_value = chaser.old_method.bind(self).call(*args) do |*yielded_values| mutated_yielded_values = yielded_values.map{|value| chaser.mutate_value(value)} block.call(*mutated_yielded_values) end chaser.mutate_value(original_value) end end |
#modify_instance_method ⇒ Object
Ruby 1.8 doesn’t allow define_method to handle blocks. The blog post coderrr.wordpress.com/2008/10/29/using-define_method-with-blocks-in-ruby-18/ show that define_method has problems, and showed how to do workaround_method_code_string
181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 |
# File 'lib/zombie-chaser/chaser.rb', line 181 def modify_instance_method chaser = self @mutated = true @old_method = @klass.instance_method(@method_name) chaser_proxy_method_name = calculate_proxy_method_name(@method_name) workaround_method_code_string = <<-EOM def #{@method_name}(*args, &block) #{chaser_proxy_method_name}(block, *args) end EOM @klass.class_eval do remove_method(chaser.clean_method_name.to_sym) eval(workaround_method_code_string) end @klass.send(:define_method, chaser_proxy_method_name) do |block, *args| original_value = chaser.old_method.bind(self).call(*args) do |*yielded_values| mutated_yielded_values = yielded_values.map{|value| chaser.mutate_value(value)} block.call(*mutated_yielded_values) end chaser.mutate_value(original_value) end end |
#modify_method ⇒ Object
231 232 233 234 235 236 237 |
# File 'lib/zombie-chaser/chaser.rb', line 231 def modify_method if method_name.to_s =~ /self\./ modify_class_method else modify_instance_method end end |
#mutate_value(value) ⇒ Object
Replaces the value with a random value.
251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 |
# File 'lib/zombie-chaser/chaser.rb', line 251 def mutate_value(value) case value when Fixnum, Float, Bignum value + rand_number when String rand_string when Symbol rand_symbol when Regexp Regexp.new(Regexp.escape(rand_string.gsub(/\//, '\\/'))) when Range rand_range when NilClass, FalseClass rand_number when TrueClass false else nil end end |
#rand_number ⇒ Object
Returns a random Fixnum.
286 287 288 |
# File 'lib/zombie-chaser/chaser.rb', line 286 def rand_number (rand(100) + 1)*((-1)**rand(2)) end |
#rand_range ⇒ Object
Returns a random Range
313 314 315 316 317 |
# File 'lib/zombie-chaser/chaser.rb', line 313 def rand_range min = rand(50) max = min + rand(50) min..max end |
#rand_string ⇒ Object
Returns a random String
293 294 295 296 297 298 |
# File 'lib/zombie-chaser/chaser.rb', line 293 def rand_string size = rand(50) str = "" size.times { str << rand(126).chr } str end |
#rand_symbol ⇒ Object
Returns a random Symbol
303 304 305 306 307 308 |
# File 'lib/zombie-chaser/chaser.rb', line 303 def rand_symbol letters = ('a'..'z').to_a + ('A'..'Z').to_a str = "" (rand(50) + 1).times { str << letters[rand(letters.size)] } :"#{str}" end |
#record_passing_mutation ⇒ Object
144 145 146 |
# File 'lib/zombie-chaser/chaser.rb', line 144 def record_passing_mutation @failure = true end |
#run_tests ⇒ Object
109 110 111 112 113 114 115 |
# File 'lib/zombie-chaser/chaser.rb', line 109 def run_tests if zombie_survives? then record_passing_mutation else @reporter.report_test_failures end end |
#silence_stream ⇒ Object
Suppresses output on $stdout and $stderr.
322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 |
# File 'lib/zombie-chaser/chaser.rb', line 322 def silence_stream return yield if @@debug begin dead = File.open(Chaser::NULL_PATH, "w") $stdout.flush $stderr.flush oldstdout = $stdout.dup oldstderr = $stderr.dup $stdout.reopen(dead) $stderr.reopen(dead) result = yield ensure $stdout.flush $stderr.flush $stdout.reopen(oldstdout) $stderr.reopen(oldstderr) result end end |
#tests_pass? ⇒ Boolean
Overwrite test_pass? for your own Chaser runner.
105 106 107 |
# File 'lib/zombie-chaser/chaser.rb', line 105 def tests_pass? raise NotImplementedError end |
#unmodify_class_method ⇒ Object
169 170 171 172 173 174 175 176 |
# File 'lib/zombie-chaser/chaser.rb', line 169 def unmodify_class_method chaser = self @mutated = false chaser_proxy_method_name = calculate_proxy_method_name(clean_method_name) aliasing_class(@method_name).send(:define_method, chaser_proxy_method_name) do |block, *args| chaser.old_method.bind(self).call(*args) {|*yielded_values| block.call(*yielded_values)} end end |
#unmodify_instance_method ⇒ Object
160 161 162 163 164 165 166 167 |
# File 'lib/zombie-chaser/chaser.rb', line 160 def unmodify_instance_method chaser = self @mutated = false chaser_proxy_method_name = calculate_proxy_method_name(@method_name) @klass.send(:define_method, chaser_proxy_method_name) do |block, *args| chaser.old_method.bind(self).call(*args) {|*yielded_values| block.call(*yielded_values)} end end |
#unmodify_method ⇒ Object
239 240 241 242 243 244 245 |
# File 'lib/zombie-chaser/chaser.rb', line 239 def unmodify_method if method_name.to_s =~ /self\./ #TODO fix duplication. Give the test a name unmodify_class_method else unmodify_instance_method end end |
#validate ⇒ Object
Running the script
120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 |
# File 'lib/zombie-chaser/chaser.rb', line 120 def validate @reporter.method_loaded(klass_name, method_name) begin modify_method timeout(@@timeout, Chaser::Timeout) { run_tests } rescue Chaser::Timeout @reporter.warning "Your tests timed out. Chaser may have caused an infinite loop." rescue Interrupt @reporter.warning 'Mutation canceled, hit ^C again to exit' sleep 2 end unmodify_method # in case we're validating again. we should clean up. if @failure @reporter.report_failure false else @reporter.no_surviving_mutant true end end |