Class: Chaser
- Inherits:
-
Object
- Object
- Chaser
- Defined in:
- lib/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
. - #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
.
83 84 85 86 87 88 89 90 91 92 93 94 95 |
# File 'lib/chaser.rb', line 83 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/chaser.rb', line 36 def klass @klass end |
#klass_name ⇒ Object
Name of class being chased
41 42 43 |
# File 'lib/chaser.rb', line 41 def klass_name @klass_name end |
#method ⇒ Object
Method being chased
46 47 48 |
# File 'lib/chaser.rb', line 46 def method @method end |
#method_name ⇒ Object
Name of method being chased
51 52 53 |
# File 'lib/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/chaser.rb', line 56 def old_method @old_method end |
Class Method Details
.debug ⇒ Object
62 63 64 |
# File 'lib/chaser.rb', line 62 def self.debug @@debug end |
.debug=(value) ⇒ Object
66 67 68 |
# File 'lib/chaser.rb', line 66 def self.debug=(value) @@debug = value end |
.guess_timeout? ⇒ Boolean
75 76 77 |
# File 'lib/chaser.rb', line 75 def self.guess_timeout? @@guess_timeout end |
.timeout=(value) ⇒ Object
70 71 72 73 |
# File 'lib/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
264 265 266 |
# File 'lib/chaser.rb', line 264 def aliasing_class(method_name) method_name.to_s =~ /self\./ ? class << @klass; self; end : @klass end |
#calculate_proxy_method_name(original_name) ⇒ Object
143 144 145 146 147 148 149 150 151 152 153 |
# File 'lib/chaser.rb', line 143 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
268 269 270 |
# File 'lib/chaser.rb', line 268 def clean_method_name method_name.to_s.gsub(/self\./, '') end |
#modify_class_method ⇒ Object
198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 |
# File 'lib/chaser.rb', line 198 def modify_class_method chaser = self @mutated = true @old_method = aliasing_class(@method_name).instance_method(clean_method_name) 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
176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 |
# File 'lib/chaser.rb', line 176 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 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
220 221 222 223 224 225 226 |
# File 'lib/chaser.rb', line 220 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.
240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 |
# File 'lib/chaser.rb', line 240 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.
275 276 277 |
# File 'lib/chaser.rb', line 275 def rand_number (rand(100) + 1)*((-1)**rand(2)) end |
#rand_range ⇒ Object
Returns a random Range
302 303 304 305 306 |
# File 'lib/chaser.rb', line 302 def rand_range min = rand(50) max = min + rand(50) min..max end |
#rand_string ⇒ Object
Returns a random String
282 283 284 285 286 287 |
# File 'lib/chaser.rb', line 282 def rand_string size = rand(50) str = "" size.times { str << rand(126).chr } str end |
#rand_symbol ⇒ Object
Returns a random Symbol
292 293 294 295 296 297 |
# File 'lib/chaser.rb', line 292 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
139 140 141 |
# File 'lib/chaser.rb', line 139 def record_passing_mutation @failure = true end |
#run_tests ⇒ Object
104 105 106 107 108 109 110 |
# File 'lib/chaser.rb', line 104 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.
311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 |
# File 'lib/chaser.rb', line 311 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.
100 101 102 |
# File 'lib/chaser.rb', line 100 def tests_pass? raise NotImplementedError end |
#unmodify_class_method ⇒ Object
164 165 166 167 168 169 170 171 |
# File 'lib/chaser.rb', line 164 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
155 156 157 158 159 160 161 162 |
# File 'lib/chaser.rb', line 155 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
228 229 230 231 232 233 234 |
# File 'lib/chaser.rb', line 228 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
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 |
# File 'lib/chaser.rb', line 115 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 |