In Files

Parent

Namespace

Heckle

Test Unit Sadism

Constants

VERSION

The version of Heckle you are using.

BRANCH_NODES

Branch node types.

WINDOZE

Is this platform MS Windows-like?

NULL_PATH

Path to the bit bucket.

DIFF

diff(1) executable

MUTATABLE_NODES

All nodes that can be mutated by Heckle.

ASGN_NODES

All assignment nodes that can be mutated by Heckle..

Attributes

count[RW]

Mutation count

failures[RW]

Mutations that caused failures

klass[RW]

Class being heckled

klass_name[RW]

Name of class being heckled

method[RW]

Method being heckled

method_name[RW]

Name of method being heckled

Public Class Methods

debug() click to toggle source
    # File lib/heckle.rb, line 85
85:   def self.debug
86:     @@debug
87:   end
debug=(value) click to toggle source
    # File lib/heckle.rb, line 89
89:   def self.debug=(value)
90:     @@debug = value
91:   end
guess_timeout?() click to toggle source
     # File lib/heckle.rb, line 98
 98:   def self.guess_timeout?
 99:     @@guess_timeout
100:   end
new(klass_name = nil, method_name = nil, nodes = Heckle::MUTATABLE_NODES, reporter = Reporter.new) click to toggle source

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
timeout=(value) click to toggle source
    # File lib/heckle.rb, line 93
93:   def self.timeout=(value)
94:     @@timeout = value
95:     @@guess_timeout = false # We've set the timeout, don't guess
96:   end

Public Instance Methods

aliasing_class(method_name) click to toggle source
                                                        

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
already_mutated?() click to toggle source
     # File lib/heckle.rb, line 571
571:   def already_mutated?
572:     @mutated
573:   end
current_code() click to toggle source
     # File lib/heckle.rb, line 602
602:   def current_code
603:     Ruby2Ruby.translate(klass_name.to_class, method_name)
604:   end
current_tree() click to toggle source
     # 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
grab_conditional_loop_parts(exp) click to toggle source
     # 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
grab_mutatees() click to toggle source
     # File lib/heckle.rb, line 496
496:   def grab_mutatees
497:     @walk_stack = []
498:     walk_and_push current_tree
499:   end
heckle(exp) click to toggle source
     # 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
increment_mutation_count(node) click to toggle source
     # 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
increment_node_count(node) click to toggle source
     # File lib/heckle.rb, line 537
537:   def increment_node_count(node)
538:     node_count[node] += 1
539:   end
mutate_asgn(node) click to toggle source
     # 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
mutate_call(node) click to toggle source

Replaces the call node with nil.

     # File lib/heckle.rb, line 243
243:   def mutate_call(node)
244:     s(:nil)
245:   end
mutate_cvasgn(node) click to toggle source

Replaces the value of the cvasgn with nil if its some value, and 42 if its nil.

Alias for: mutate_asgn
mutate_dasgn(node) click to toggle source

Replaces the value of the dasgn with nil if its some value, and 42 if its nil.

Alias for: mutate_asgn
mutate_dasgn_curr(node) click to toggle source

Replaces the value of the dasgn_curr with nil if its some value, and 42 if its nil.

Alias for: mutate_asgn
mutate_false(node) click to toggle source

Swaps for a :true node.

     # File lib/heckle.rb, line 434
434:   def mutate_false(node)
435:     s(:true)
436:   end
mutate_gasgn(node) click to toggle source

Replaces the value of the gasgn with nil if its some value, and 42 if its nil.

Alias for: mutate_asgn
mutate_iasgn(node) click to toggle source

Replaces the value of the iasgn with nil if its some value, and 42 if its nil.

Alias for: mutate_asgn
mutate_if(node) click to toggle source

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
mutate_iter(exp) click to toggle source
     # File lib/heckle.rb, line 287
287:   def mutate_iter(exp)
288:     s(:nil)
289:   end
mutate_lasgn(node) click to toggle source

Replaces the value of the lasgn with nil if its some value, and 42 if its nil.

Alias for: mutate_asgn
mutate_lit(exp) click to toggle source

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
mutate_node(node) click to toggle source
     # 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
mutate_str(node) click to toggle source

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
mutate_true(node) click to toggle source

Swaps for a :false node.

     # File lib/heckle.rb, line 423
423:   def mutate_true(node)
424:     s(:false)
425:   end
mutate_until(node) click to toggle source

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
mutate_while(node) click to toggle source

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
mutations_left() click to toggle source
     # 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
process_asgn(type, exp) click to toggle source
     # 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
process_call(exp) click to toggle source
                                                        

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
process_cvasgn(exp) click to toggle source
     # File lib/heckle.rb, line 314
314:   def process_cvasgn(exp)
315:     process_asgn :cvasgn, exp
316:   end
process_dasgn(exp) click to toggle source
     # File lib/heckle.rb, line 324
324:   def process_dasgn(exp)
325:     process_asgn :dasgn, exp
326:   end
process_dasgn_curr(exp) click to toggle source
     # File lib/heckle.rb, line 334
334:   def process_dasgn_curr(exp)
335:     process_asgn :dasgn_curr, exp
336:   end
process_defn(exp) click to toggle source
     # 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
process_defs(exp) click to toggle source
     # 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
process_false(exp) click to toggle source
     # File lib/heckle.rb, line 427
427:   def process_false(exp)
428:     mutate_node s(:false)
429:   end
process_gasgn(exp) click to toggle source
     # File lib/heckle.rb, line 354
354:   def process_gasgn(exp)
355:     process_asgn :gasgn, exp
356:   end
process_iasgn(exp) click to toggle source
     # File lib/heckle.rb, line 344
344:   def process_iasgn(exp)
345:     process_asgn :iasgn, exp
346:   end
process_if(exp) click to toggle source
     # 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
process_iter(exp) click to toggle source

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
process_lasgn(exp) click to toggle source
     # File lib/heckle.rb, line 364
364:   def process_lasgn(exp)
365:     process_asgn :lasgn, exp
366:   end
process_lit(exp) click to toggle source
     # File lib/heckle.rb, line 374
374:   def process_lit(exp)
375:     mutate_node s(:lit, exp.shift)
376:   end
process_str(exp) click to toggle source
     # File lib/heckle.rb, line 394
394:   def process_str(exp)
395:     mutate_node s(:str, exp.shift)
396:   end
process_true(exp) click to toggle source
     # File lib/heckle.rb, line 416
416:   def process_true(exp)
417:     mutate_node s(:true)
418:   end
process_until(exp) click to toggle source
     # 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
process_while(exp) click to toggle source
     # 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
rand_number() click to toggle source

Returns a random Fixnum.

     # File lib/heckle.rb, line 609
609:   def rand_number
610:     (rand(100) + 1)*((1)**rand(2))
611:   end
rand_range() click to toggle source

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
rand_string() click to toggle source

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
rand_symbol() click to toggle source

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
record_passing_mutation() click to toggle source
     # File lib/heckle.rb, line 201
201:   def record_passing_mutation
202:     @failures << current_code
203:   end
reset() click to toggle source
     # File lib/heckle.rb, line 511
511:   def reset
512:     reset_tree
513:     reset_mutatees
514:     mutation_count.clear
515:   end
reset_mutatees() click to toggle source
     # File lib/heckle.rb, line 533
533:   def reset_mutatees
534:     @mutatees = @original_mutatees.deep_clone
535:   end
reset_tree() click to toggle source
     # 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
run_tests() click to toggle source
     # 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
should_heckle?(exp) click to toggle source
     # 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
silence_stream() click to toggle source

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
tests_pass?() click to toggle source

Overwrite test_pass? for your own Heckle runner.

     # File lib/heckle.rb, line 143
143:   def tests_pass?
144:     raise NotImplementedError
145:   end
validate() click to toggle source
                                                        

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
walk_and_push(node, index = 0) click to toggle source
                                                        

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.

[Validate]

Generated with the Darkfish Rdoc Generator 1.1.6.