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.new.process ParseTree.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: warn "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.