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