Class: Heckle
- Inherits:
-
SexpProcessor
- Object
- SexpProcessor
- Heckle
- Defined in:
- lib/heckle.rb
Overview
Test Unit Sadism
Direct Known Subclasses
Defined Under Namespace
Constant Summary collapse
- VERSION =
The version of Heckle you are using.
'1.4.3'
- BRANCH_NODES =
Branch node types.
[:if, :until, :while]
- WINDOZE =
Is this platform MS Windows-like?
RUBY_PLATFORM =~ /mswin/
- NULL_PATH =
Path to the bit bucket.
WINDOZE ? 'NUL:' : '/dev/null'
- DIFF =
diff(1) executable
WINDOZE ? 'diff.exe' : 'diff'
- MUTATABLE_NODES =
All nodes that can be mutated by Heckle.
instance_methods.grep(/mutate_/).sort.map do |meth| meth.sub(/mutate_/, '').intern end - [:asgn, :node]
- ASGN_NODES =
All assignment nodes that can be mutated by Heckle..
MUTATABLE_NODES.map { |n| n.to_s }.grep(/asgn/).map do |n| n.intern end
- @@debug =
false
- @@guess_timeout =
true
- @@timeout =
default to something longer (can be overridden by runners)
60
Instance Attribute Summary collapse
-
#count ⇒ Object
Mutation count.
-
#failures ⇒ Object
Mutations that caused failures.
-
#klass ⇒ Object
Class being heckled.
-
#klass_name ⇒ Object
Name of class being heckled.
-
#method ⇒ Object
Method being heckled.
-
#method_name ⇒ Object
Name of method being heckled.
-
#mutatees ⇒ Object
:nodoc:.
-
#mutation_count ⇒ Object
:nodoc:.
-
#node_count ⇒ Object
:nodoc:.
-
#original_tree ⇒ Object
:nodoc:.
Class Method Summary collapse
Instance Method Summary collapse
-
#aliasing_class(method_name) ⇒ Object
Convenience methods.
- #already_mutated? ⇒ Boolean
- #current_code ⇒ Object
- #current_tree ⇒ Object
- #grab_conditional_loop_parts(exp) ⇒ Object
- #grab_mutatees ⇒ Object
- #heckle(exp) ⇒ Object
- #increment_mutation_count(node) ⇒ Object
- #increment_node_count(node) ⇒ Object
-
#initialize(klass_name = nil, method_name = nil, nodes = Heckle::MUTATABLE_NODES, reporter = Reporter.new) ⇒ Heckle
constructor
Creates a new Heckle that will heckle
klass_name
andmethod_name
, sending results toreporter
. - #mutate_asgn(node) ⇒ Object (also: #mutate_cvasgn, #mutate_dasgn, #mutate_dasgn_curr, #mutate_iasgn, #mutate_gasgn, #mutate_lasgn)
-
#mutate_call(node) ⇒ Object
Replaces the call node with nil.
-
#mutate_false(node) ⇒ Object
Swaps for a :true node.
-
#mutate_if(node) ⇒ Object
Swaps the then and else parts of the :if node.
- #mutate_iter(exp) ⇒ Object
-
#mutate_lit(exp) ⇒ Object
Replaces the value of the :lit node with a random value.
- #mutate_node(node) ⇒ Object
-
#mutate_str(node) ⇒ Object
Replaces the value of the :str node with a random value.
-
#mutate_true(node) ⇒ Object
Swaps for a :false node.
-
#mutate_until(node) ⇒ Object
Swaps for a :while node.
-
#mutate_while(node) ⇒ Object
Swaps for a :until node.
- #mutations_left ⇒ Object
- #process_asgn(type, exp) ⇒ Object
-
#process_call(exp) ⇒ Object
Processing sexps.
- #process_cvasgn(exp) ⇒ Object
- #process_dasgn(exp) ⇒ Object
- #process_dasgn_curr(exp) ⇒ Object
- #process_defn(exp) ⇒ Object
- #process_defs(exp) ⇒ Object
- #process_false(exp) ⇒ Object
- #process_gasgn(exp) ⇒ Object
- #process_iasgn(exp) ⇒ Object
- #process_if(exp) ⇒ Object
-
#process_iter(exp) ⇒ Object
So process_call works correctly.
- #process_lasgn(exp) ⇒ Object
- #process_lit(exp) ⇒ Object
- #process_str(exp) ⇒ Object
- #process_true(exp) ⇒ Object
- #process_until(exp) ⇒ Object
- #process_while(exp) ⇒ Object
-
#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
- #reset ⇒ Object
- #reset_mutatees ⇒ Object
- #reset_tree ⇒ Object
- #run_tests ⇒ Object
- #should_heckle?(exp) ⇒ Boolean
-
#silence_stream ⇒ Object
Suppresses output on $stdout and $stderr.
-
#tests_pass? ⇒ Boolean
Overwrite test_pass? for your own Heckle runner.
-
#validate ⇒ Object
Running the script.
-
#walk_and_push(node, index = 0) ⇒ Object
Tree operations.
Constructor Details
#initialize(klass_name = nil, method_name = nil, nodes = Heckle::MUTATABLE_NODES, reporter = Reporter.new) ⇒ Heckle
Creates a new Heckle that will heckle klass_name
and method_name
, sending results to reporter
.
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 |
# File 'lib/heckle.rb', line 106 def initialize(klass_name = nil, method_name = nil, nodes = Heckle::MUTATABLE_NODES, reporter = Reporter.new) super() @klass_name = klass_name @method_name = method_name.intern if method_name @klass = klass_name.to_class @method = nil @reporter = reporter self.strict = false self.auto_shift_type = true self.expected = Sexp @mutatees = Hash.new @mutation_count = Hash.new 0 @node_count = Hash.new 0 @count = 0 @mutatable_nodes = nodes @mutatable_nodes.each {|type| @mutatees[type] = [] } @failures = [] @mutated = false grab_mutatees @original_tree = current_tree.deep_clone @original_mutatees = mutatees.deep_clone end |
Instance Attribute Details
#count ⇒ Object
Mutation count
49 50 51 |
# File 'lib/heckle.rb', line 49 def count @count end |
#failures ⇒ Object
Mutations that caused failures
54 55 56 |
# File 'lib/heckle.rb', line 54 def failures @failures end |
#klass ⇒ Object
Class being heckled
59 60 61 |
# File 'lib/heckle.rb', line 59 def klass @klass end |
#klass_name ⇒ Object
Name of class being heckled
64 65 66 |
# File 'lib/heckle.rb', line 64 def klass_name @klass_name end |
#method ⇒ Object
Method being heckled
69 70 71 |
# File 'lib/heckle.rb', line 69 def method @method end |
#method_name ⇒ Object
Name of method being heckled
74 75 76 |
# File 'lib/heckle.rb', line 74 def method_name @method_name end |
#mutatees ⇒ Object
:nodoc:
76 77 78 |
# File 'lib/heckle.rb', line 76 def mutatees @mutatees end |
#mutation_count ⇒ Object
:nodoc:
77 78 79 |
# File 'lib/heckle.rb', line 77 def mutation_count @mutation_count end |
#node_count ⇒ Object
:nodoc:
78 79 80 |
# File 'lib/heckle.rb', line 78 def node_count @node_count end |
#original_tree ⇒ Object
:nodoc:
79 80 81 |
# File 'lib/heckle.rb', line 79 def original_tree @original_tree end |
Class Method Details
.debug ⇒ Object
85 86 87 |
# File 'lib/heckle.rb', line 85 def self.debug @@debug end |
.debug=(value) ⇒ Object
89 90 91 |
# File 'lib/heckle.rb', line 89 def self.debug=(value) @@debug = value end |
.guess_timeout? ⇒ Boolean
98 99 100 |
# File 'lib/heckle.rb', line 98 def self.guess_timeout? @@guess_timeout end |
.timeout=(value) ⇒ Object
93 94 95 96 |
# File 'lib/heckle.rb', line 93 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
552 553 554 |
# File 'lib/heckle.rb', line 552 def aliasing_class(method_name) method_name.to_s =~ /self\./ ? class << @klass; self; end : @klass end |
#already_mutated? ⇒ Boolean
571 572 573 |
# File 'lib/heckle.rb', line 571 def already_mutated? @mutated end |
#current_code ⇒ Object
602 603 604 |
# File 'lib/heckle.rb', line 602 def current_code Ruby2Ruby.translate(klass_name.to_class, method_name) end |
#current_tree ⇒ Object
501 502 503 504 505 506 507 508 509 |
# File 'lib/heckle.rb', line 501 def current_tree ur = Unifier.new sexp = ParseTree.translate(klass_name.to_class, method_name) raise "sexp invalid for #{klass_name}##{method_name}" if sexp == [nil] sexp = ur.process(sexp) rewrite sexp end |
#grab_conditional_loop_parts(exp) ⇒ Object
564 565 566 567 568 569 |
# File 'lib/heckle.rb', line 564 def grab_conditional_loop_parts(exp) cond = process(exp.shift) body = process(exp.shift) head_controlled = exp.shift return cond, body, head_controlled end |
#grab_mutatees ⇒ Object
496 497 498 499 |
# File 'lib/heckle.rb', line 496 def grab_mutatees @walk_stack = [] walk_and_push current_tree end |
#heckle(exp) ⇒ Object
205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 |
# File 'lib/heckle.rb', line 205 def heckle(exp) exp_copy = exp.deep_clone src = begin Ruby2Ruby.new.process(exp) rescue => e puts "Error: #{e.} with: #{klass_name}##{method_name}: #{exp_copy.inspect}" raise e end original = Ruby2Ruby.new.process(@original_tree.deep_clone) @reporter.replacing(klass_name, method_name, original, src) if @@debug clean_name = method_name.to_s.gsub(/self\./, '') self.count += 1 new_name = "h#{count}_#{clean_name}" klass = aliasing_class method_name klass.send :remove_method, new_name rescue nil klass.send :alias_method, new_name, clean_name klass.send :remove_method, clean_name rescue nil @klass.class_eval src, "(#{new_name})" end |
#increment_mutation_count(node) ⇒ Object
541 542 543 544 545 546 547 |
# File 'lib/heckle.rb', line 541 def increment_mutation_count(node) # So we don't re-mutate this later if the tree is reset mutation_count[node] += 1 mutatee_type = @mutatees[node.first] mutatee_type.delete_at mutatee_type.index(node) @mutated = true end |
#increment_node_count(node) ⇒ Object
537 538 539 |
# File 'lib/heckle.rb', line 537 def increment_node_count(node) node_count[node] += 1 end |
#mutate_asgn(node) ⇒ Object Also known as: mutate_cvasgn, mutate_dasgn, mutate_dasgn_curr, mutate_iasgn, mutate_gasgn, mutate_lasgn
300 301 302 303 304 305 306 307 308 309 310 311 312 |
# File 'lib/heckle.rb', line 300 def mutate_asgn(node) type = node.shift var = node.shift if node.empty? then s(type, :_heckle_dummy) else if node.last.first == :nil then s(type, var, s(:lit, 42)) else s(type, var, s(:nil)) end end end |
#mutate_call(node) ⇒ Object
Replaces the call node with nil.
243 244 245 |
# File 'lib/heckle.rb', line 243 def mutate_call(node) s(:nil) end |
#mutate_false(node) ⇒ Object
Swaps for a :true node.
434 435 436 |
# File 'lib/heckle.rb', line 434 def mutate_false(node) s(:true) end |
#mutate_if(node) ⇒ Object
Swaps the then and else parts of the :if node.
412 413 414 |
# File 'lib/heckle.rb', line 412 def mutate_if(node) s(:if, node[1], node[3], node[2]) end |
#mutate_iter(exp) ⇒ Object
287 288 289 |
# File 'lib/heckle.rb', line 287 def mutate_iter(exp) s(:nil) end |
#mutate_lit(exp) ⇒ Object
Replaces the value of the :lit node with a random value.
381 382 383 384 385 386 387 388 389 390 391 392 |
# File 'lib/heckle.rb', line 381 def mutate_lit(exp) case exp[1] when Fixnum, Float, Bignum s(:lit, exp[1] + rand_number) when Symbol s(:lit, rand_symbol) when Regexp s(:lit, Regexp.new(Regexp.escape(rand_string.gsub(/\//, '\\/')))) when Range s(:lit, rand_range) end end |
#mutate_node(node) ⇒ Object
462 463 464 465 466 467 468 469 470 471 472 |
# File 'lib/heckle.rb', line 462 def mutate_node(node) raise UnsupportedNodeError unless respond_to? "mutate_#{node.first}" increment_node_count node if should_heckle? node then increment_mutation_count node return send("mutate_#{node.first}", node) else node end end |
#mutate_str(node) ⇒ Object
Replaces the value of the :str node with a random value.
401 402 403 |
# File 'lib/heckle.rb', line 401 def mutate_str(node) s(:str, rand_string) end |
#mutate_true(node) ⇒ Object
Swaps for a :false node.
423 424 425 |
# File 'lib/heckle.rb', line 423 def mutate_true(node) s(:false) end |
#mutate_until(node) ⇒ Object
Swaps for a :while node.
458 459 460 |
# File 'lib/heckle.rb', line 458 def mutate_until(node) s(:while, node[1], node[2], node[3]) end |
#mutate_while(node) ⇒ Object
Swaps for a :until node.
446 447 448 |
# File 'lib/heckle.rb', line 446 def mutate_while(node) s(:until, node[1], node[2], node[3]) end |
#mutations_left ⇒ Object
575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 |
# File 'lib/heckle.rb', line 575 def mutations_left @last_mutations_left ||= -1 sum = 0 @mutatees.each { |mut| sum += mut.last.size } if sum == @last_mutations_left then puts 'bug!' puts require 'pp' puts 'mutatees:' pp @mutatees puts puts 'original tree:' pp @original_tree puts puts "Infinite loop detected!" puts "Please save this output to an attachment and submit a ticket here:" puts "http://rubyforge.org/tracker/?func=add&group_id=1513&atid=5921" exit 1 else @last_mutations_left = sum end sum end |
#process_asgn(type, exp) ⇒ Object
291 292 293 294 295 296 297 298 |
# File 'lib/heckle.rb', line 291 def process_asgn(type, exp) var = exp.shift if exp.empty? then mutate_node s(type, var) else mutate_node s(type, var, process(exp.shift)) end end |
#process_call(exp) ⇒ Object
Processing sexps
232 233 234 235 236 237 238 |
# File 'lib/heckle.rb', line 232 def process_call(exp) recv = process(exp.shift) meth = exp.shift args = process(exp.shift) mutate_node s(:call, recv, meth, args) end |
#process_cvasgn(exp) ⇒ Object
314 315 316 |
# File 'lib/heckle.rb', line 314 def process_cvasgn(exp) process_asgn :cvasgn, exp end |
#process_dasgn(exp) ⇒ Object
324 325 326 |
# File 'lib/heckle.rb', line 324 def process_dasgn(exp) process_asgn :dasgn, exp end |
#process_dasgn_curr(exp) ⇒ Object
334 335 336 |
# File 'lib/heckle.rb', line 334 def process_dasgn_curr(exp) process_asgn :dasgn_curr, exp end |
#process_defn(exp) ⇒ Object
247 248 249 250 251 252 253 254 255 256 257 |
# File 'lib/heckle.rb', line 247 def process_defn(exp) self.method = exp.shift result = s(:defn, method) result << process(exp.shift) until exp.empty? heckle(result) if method == method_name return result ensure @mutated = false node_count.clear end |
#process_defs(exp) ⇒ Object
259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 |
# File 'lib/heckle.rb', line 259 def process_defs(exp) recv = process exp.shift meth = exp.shift self.method = "#{Ruby2Ruby.new.process(recv.deep_clone)}.#{meth}".intern result = s(:defs, recv, meth) result << process(exp.shift) until exp.empty? heckle(result) if method == method_name return result ensure @mutated = false node_count.clear end |
#process_false(exp) ⇒ Object
427 428 429 |
# File 'lib/heckle.rb', line 427 def process_false(exp) mutate_node s(:false) end |
#process_gasgn(exp) ⇒ Object
354 355 356 |
# File 'lib/heckle.rb', line 354 def process_gasgn(exp) process_asgn :gasgn, exp end |
#process_iasgn(exp) ⇒ Object
344 345 346 |
# File 'lib/heckle.rb', line 344 def process_iasgn(exp) process_asgn :iasgn, exp end |
#process_if(exp) ⇒ Object
405 406 407 |
# File 'lib/heckle.rb', line 405 def process_if(exp) mutate_node s(:if, process(exp.shift), process(exp.shift), process(exp.shift)) end |
#process_iter(exp) ⇒ Object
So process_call works correctly
279 280 281 282 283 284 285 |
# File 'lib/heckle.rb', line 279 def process_iter(exp) call = process exp.shift args = process exp.shift body = process exp.shift mutate_node s(:iter, call, args, body) end |
#process_lasgn(exp) ⇒ Object
364 365 366 |
# File 'lib/heckle.rb', line 364 def process_lasgn(exp) process_asgn :lasgn, exp end |
#process_lit(exp) ⇒ Object
374 375 376 |
# File 'lib/heckle.rb', line 374 def process_lit(exp) mutate_node s(:lit, exp.shift) end |
#process_str(exp) ⇒ Object
394 395 396 |
# File 'lib/heckle.rb', line 394 def process_str(exp) mutate_node s(:str, exp.shift) end |
#process_true(exp) ⇒ Object
416 417 418 |
# File 'lib/heckle.rb', line 416 def process_true(exp) mutate_node s(:true) end |
#process_until(exp) ⇒ Object
450 451 452 453 |
# File 'lib/heckle.rb', line 450 def process_until(exp) cond, body, head_controlled = grab_conditional_loop_parts(exp) mutate_node s(:until, cond, body, head_controlled) end |
#process_while(exp) ⇒ Object
438 439 440 441 |
# File 'lib/heckle.rb', line 438 def process_while(exp) cond, body, head_controlled = grab_conditional_loop_parts(exp) mutate_node s(:while, cond, body, head_controlled) end |
#rand_number ⇒ Object
Returns a random Fixnum.
609 610 611 |
# File 'lib/heckle.rb', line 609 def rand_number (rand(100) + 1)*((-1)**rand(2)) end |
#rand_range ⇒ Object
Returns a random Range
636 637 638 639 640 |
# File 'lib/heckle.rb', line 636 def rand_range min = rand(50) max = min + rand(50) min..max end |
#rand_string ⇒ Object
Returns a random String
616 617 618 619 620 621 |
# File 'lib/heckle.rb', line 616 def rand_string size = rand(50) str = "" size.times { str << rand(126).chr } str end |
#rand_symbol ⇒ Object
Returns a random Symbol
626 627 628 629 630 631 |
# File 'lib/heckle.rb', line 626 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
201 202 203 |
# File 'lib/heckle.rb', line 201 def record_passing_mutation @failures << current_code end |
#reset ⇒ Object
511 512 513 514 515 |
# File 'lib/heckle.rb', line 511 def reset reset_tree reset_mutatees mutation_count.clear end |
#reset_mutatees ⇒ Object
533 534 535 |
# File 'lib/heckle.rb', line 533 def reset_mutatees @mutatees = @original_mutatees.deep_clone end |
#reset_tree ⇒ Object
517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 |
# File 'lib/heckle.rb', line 517 def reset_tree return unless original_tree != current_tree @mutated = false self.count += 1 clean_name = method_name.to_s.gsub(/self\./, '') new_name = "h#{count}_#{clean_name}" klass = aliasing_class method_name klass.send :undef_method, new_name rescue nil klass.send :alias_method, new_name, clean_name klass.send :alias_method, clean_name, "h1_#{clean_name}" end |
#run_tests ⇒ Object
147 148 149 150 151 152 153 |
# File 'lib/heckle.rb', line 147 def run_tests if tests_pass? then record_passing_mutation else @reporter.report_test_failures end end |
#should_heckle?(exp) ⇒ Boolean
556 557 558 559 560 561 562 |
# File 'lib/heckle.rb', line 556 def should_heckle?(exp) return false unless method == method_name return false if node_count[exp] <= mutation_count[exp] key = exp.first.to_sym mutatees.include?(key) && mutatees[key].include?(exp) && !already_mutated? end |
#silence_stream ⇒ Object
Suppresses output on $stdout and $stderr.
645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 |
# File 'lib/heckle.rb', line 645 def silence_stream return yield if @@debug begin dead = File.open("/dev/null", "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 Heckle runner.
143 144 145 |
# File 'lib/heckle.rb', line 143 def tests_pass? raise NotImplementedError end |
#validate ⇒ Object
Running the script
158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 |
# File 'lib/heckle.rb', line 158 def validate left = mutations_left if left == 0 then @reporter.no_mutations(method_name) return end @reporter.method_loaded(klass_name, method_name, left) until left == 0 do @reporter.remaining_mutations left reset_tree begin process current_tree timeout(@@timeout, Heckle::Timeout) { run_tests } rescue SyntaxError => e @reporter.warning "Mutation caused a syntax error:\n\n#{e.}}" rescue Heckle::Timeout @reporter.warning "Your tests timed out. Heckle may have caused an infinite loop." rescue Interrupt @reporter.warning 'Mutation canceled, hit ^C again to exit' sleep 2 end left = mutations_left end reset # in case we're validating again. we should clean up. unless @failures.empty? @reporter.no_failures @failures.each do |failure| original = Ruby2Ruby.new.process(@original_tree.deep_clone) @reporter.failure(original, failure) end false else @reporter.no_surviving_mutants true end end |
#walk_and_push(node, index = 0) ⇒ Object
Tree operations
477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 |
# File 'lib/heckle.rb', line 477 def walk_and_push(node, index = 0) return unless node.respond_to? :each return if node.is_a? String @walk_stack.push node.first node.each_with_index { |child_node, i| walk_and_push child_node, i } @walk_stack.pop if @mutatable_nodes.include? node.first and # HACK skip over call nodes that are the first child of an iter or # they'll get added twice # # I think heckle really needs two processors, one for finding and one # for heckling. !(node.first == :call and index == 1 and @walk_stack.last == :iter) then @mutatees[node.first].push(node) end end |