Class: Heckle

### Home Classes Methods

### In Files

- lib/heckle.rb

### Parent

SexpProcessor

### Namespace

- CLASS Heckle::Reporter
- CLASS Heckle::Timeout

### Methods

- ::debug
- ::debug=
- ::guess_timeout?
- ::new
- ::timeout=
- #aliasing_class
- #already_mutated?
- #current_code
- #current_tree
- #grab_conditional_loop_parts
- #grab_mutatees
- #heckle
- #increment_mutation_count
- #increment_node_count
- #mutate_asgn
- #mutate_call
- #mutate_cvasgn
- #mutate_dasgn
- #mutate_dasgn_curr
- #mutate_false
- #mutate_gasgn
- #mutate_iasgn
- #mutate_if
- #mutate_iter
- #mutate_lasgn
- #mutate_lit
- #mutate_node
- #mutate_str
- #mutate_true
- #mutate_until
- #mutate_while
- #mutations_left
- #process_asgn
- #process_call
- #process_cvasgn
- #process_dasgn
- #process_dasgn_curr
- #process_defn
- #process_defs
- #process_false
- #process_gasgn
- #process_iasgn
- #process_if
- #process_iter
- #process_lasgn
- #process_lit
- #process_str
- #process_true
- #process_until
- #process_while
- #rand_number
- #rand_range
- #rand_string
- #rand_symbol
- #record_passing_mutation
- #reset
- #reset_mutatees
- #reset_tree
- #run_tests
- #should_heckle?
- #silence_stream
- #tests_pass?
- #validate
- #walk_and_push

### Files

- History.txt
- Manifest.txt
- README.txt

### Class/Module Index

Quicksearch

- Heckle
- Heckle::Reporter
- Heckle::Timeout
- Autotest
- Autotest::Heckle
- TestUnitHeckler

No matching classes.

# Heckle

Test Unit Sadism

### Constants

ASGN_NODES

  All assignment nodes that can be mutated by Heckle..

BRANCH_NODES

  Branch node types.

DIFF

  diff(1) executable

MUTATABLE_NODES

  All nodes that can be mutated by Heckle.

NULL_PATH

  Path to the bit bucket.

VERSION

  The version of Heckle you are using.

WINDOZE

  Is this platform MS Windows-like?

### 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
    def self.debug
      @@debug
    end

debug=(value) click to toggle source

    # File lib/heckle.rb, line 89
    def self.debug=(value)
      @@debug = value
    end

guess_timeout?() click to toggle source

    # File lib/heckle.rb, line 98
    def self.guess_timeout?
      @@guess_timeout
    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
    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

timeout=(value) click to toggle source

    # File lib/heckle.rb, line 93
    def self.timeout=(value)
      @@timeout = value
      @@guess_timeout = false # We've set the timeout, don't guess
    end

### Public Instance Methods

aliasing_class(method_name) click to toggle source

Convenience methods

    # File lib/heckle.rb, line 552
    def aliasing_class(method_name)
      method_name.to_s =~ /self\./ ? class << @klass; self; end : @klass
    end

already_mutated?() click to toggle source

    # File lib/heckle.rb, line 571
    def already_mutated?
      @mutated
    end

current_code() click to toggle source

    # File lib/heckle.rb, line 602
    def current_code
      Ruby2Ruby.translate(klass_name.to_class, method_name)
    end

current_tree() click to toggle source

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

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

    # File lib/heckle.rb, line 496
    def grab_mutatees
      @walk_stack = []
      walk_and_push current_tree
    end

heckle(exp) click to toggle source

    # 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.message} 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) click to toggle source

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

    # File lib/heckle.rb, line 537
    def increment_node_count(node)
      node_count[node] += 1
    end

mutate_asgn(node) click to toggle source

    # 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

Also aliased as: mutate_cvasgn, mutate_dasgn, mutate_dasgn_curr, mutate_iasgn, mutate_gasgn, mutate_lasgn
mutate_call(node) click to toggle source

Replaces the call node with nil.

    # File lib/heckle.rb, line 243
    def mutate_call(node)
      s(:nil)
    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
    def mutate_false(node)
      s(:true)
    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
    def mutate_if(node)
      s(:if, node[1], node[3], node[2])
    end

mutate_iter(exp) click to toggle source

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

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

Replaces the value of the :str node with a random value.

    # File lib/heckle.rb, line 401
    def mutate_str(node)
      s(:str, rand_string)
    end

mutate_true(node) click to toggle source

Swaps for a :false node.

    # File lib/heckle.rb, line 423
    def mutate_true(node)
      s(:false)
    end

mutate_until(node) click to toggle source

Swaps for a :while node.

    # File lib/heckle.rb, line 458
    def mutate_until(node)
      s(:while, node[1], node[2], node[3])
    end

mutate_while(node) click to toggle source

Swaps for a :until node.

    # File lib/heckle.rb, line 446
    def mutate_while(node)
      s(:until, node[1], node[2], node[3])
    end

mutations_left() click to toggle source

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

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

Processing sexps

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

    # File lib/heckle.rb, line 314
    def process_cvasgn(exp)
      process_asgn :cvasgn, exp
    end

process_dasgn(exp) click to toggle source

    # File lib/heckle.rb, line 324
    def process_dasgn(exp)
      process_asgn :dasgn, exp
    end

process_dasgn_curr(exp) click to toggle source

    # File lib/heckle.rb, line 334
    def process_dasgn_curr(exp)
      process_asgn :dasgn_curr, exp
    end

process_defn(exp) click to toggle source

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

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

    # File lib/heckle.rb, line 427
    def process_false(exp)
      mutate_node s(:false)
    end

process_gasgn(exp) click to toggle source

    # File lib/heckle.rb, line 354
    def process_gasgn(exp)
      process_asgn :gasgn, exp
    end

process_iasgn(exp) click to toggle source

    # File lib/heckle.rb, line 344
    def process_iasgn(exp)
      process_asgn :iasgn, exp
    end

process_if(exp) click to toggle source

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

So process_call works correctly

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

    # File lib/heckle.rb, line 364
    def process_lasgn(exp)
      process_asgn :lasgn, exp
    end

process_lit(exp) click to toggle source

    # File lib/heckle.rb, line 374
    def process_lit(exp)
      mutate_node s(:lit, exp.shift)
    end

process_str(exp) click to toggle source

    # File lib/heckle.rb, line 394
    def process_str(exp)
      mutate_node s(:str, exp.shift)
    end

process_true(exp) click to toggle source

    # File lib/heckle.rb, line 416
    def process_true(exp)
      mutate_node s(:true)
    end

process_until(exp) click to toggle source

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

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

Returns a random Fixnum.

    # File lib/heckle.rb, line 609
    def rand_number
      (rand(100) + 1)*((-1)**rand(2))
    end

rand_range() click to toggle source

Returns a random Range

    # File lib/heckle.rb, line 636
    def rand_range
      min = rand(50)
      max = min + rand(50)
      min..max
    end

rand_string() click to toggle source

Returns a random String

    # File lib/heckle.rb, line 616
    def rand_string
      size = rand(50)
      str = ""
      size.times { str << rand(126).chr }
      str
    end

rand_symbol() click to toggle source

Returns a random Symbol

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

    # File lib/heckle.rb, line 201
    def record_passing_mutation
      @failures << current_code
    end

reset() click to toggle source

    # File lib/heckle.rb, line 511
    def reset
      reset_tree
      reset_mutatees
      mutation_count.clear
    end

reset_mutatees() click to toggle source

    # File lib/heckle.rb, line 533
    def reset_mutatees
      @mutatees = @original_mutatees.deep_clone
    end

reset_tree() click to toggle source

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

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

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

Suppresses output on $stdout and $stderr.

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

Overwrite test_pass? for your own Heckle runner.

    # File lib/heckle.rb, line 143
    def tests_pass?
      raise NotImplementedError
    end

validate() click to toggle source

Running the script

    # 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.message}}"
        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) click to toggle source

Tree operations

    # 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

[Validate]

Generated with the Darkfish Rdoc Generator 2.