From 908b2d6da71e790cd3de2375c73794b15ff1fff9 Mon Sep 17 00:00:00 2001 From: Ryan Davis Date: Thu, 14 Dec 2006 22:50:42 -0800 Subject: [PATCH] Pulled flat history from subversion. [git-p4: depot-paths = "//src/heckle/dev/": change = 2769] --- History.txt | 19 ++ Manifest.txt | 13 + README.txt | 49 +++ Rakefile | 17 + bin/heckle | 21 ++ lib/heckle.rb | 334 +++++++++++++++++++ lib/test_unit_heckler.rb | 14 + sample/Rakefile | 16 + sample/changes.log | 91 ++++++ sample/lib/heckled.rb | 63 ++++ sample/test/test_heckled.rb | 19 ++ test/fixtures/heckled.rb | 100 ++++++ test/test_heckle.rb | 637 ++++++++++++++++++++++++++++++++++++ 13 files changed, 1393 insertions(+) create mode 100644 History.txt create mode 100644 Manifest.txt create mode 100644 README.txt create mode 100644 Rakefile create mode 100644 bin/heckle create mode 100644 lib/heckle.rb create mode 100644 lib/test_unit_heckler.rb create mode 100644 sample/Rakefile create mode 100644 sample/changes.log create mode 100644 sample/lib/heckled.rb create mode 100644 sample/test/test_heckled.rb create mode 100644 test/fixtures/heckled.rb create mode 100644 test/test_heckle.rb diff --git a/History.txt b/History.txt new file mode 100644 index 0000000..c25b502 --- /dev/null +++ b/History.txt @@ -0,0 +1,19 @@ +== svn +* 11 major enhancements: + * Able to roll back original method after processing. + * Can mutate numeric literals. + * Can mutate strings. + * Can mutate a node at a time. + * Can mutate if/unless + * Decoupled from Test::Unit + * Cleaner output + * Can mutate true and false. + * Can mutate while and until. + * Can mutate regexes, ranges, symbols + * Can run against entire classes + +== 1.0.0 / 2006-10-22 + +* 1 major enhancement + * Birthday! + diff --git a/Manifest.txt b/Manifest.txt new file mode 100644 index 0000000..2eea7fe --- /dev/null +++ b/Manifest.txt @@ -0,0 +1,13 @@ +History.txt +Manifest.txt +README.txt +Rakefile +bin/heckle +lib/heckle.rb +lib/test_unit_heckler.rb +sample/Rakefile +sample/changes.log +sample/lib/heckled.rb +sample/test/test_heckled.rb +test/fixtures/heckled.rb +test/test_heckle.rb diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..1a447ba --- /dev/null +++ b/README.txt @@ -0,0 +1,49 @@ +heckle + by Ryan Davis and Kevin Clark + http://www.rubyforge.org/projects/seattlerb + +== DESCRIPTION: + +Unit Testing Sadism. More description coming soon. I'm punting to get +this launched ASAP. + +== FEATURES/PROBLEMS: + +* needs some love. haha. + +== SYNOPSYS: + + FIX + +== REQUIREMENTS: + ++ FIX + +== INSTALL: + ++ sudo gem install heckle + +== LICENSE: + +(The MIT License) + +Copyright (c) 2006 Ryan Davis and Kevin Clark + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..e5d24d2 --- /dev/null +++ b/Rakefile @@ -0,0 +1,17 @@ +# -*- ruby -*- + +require 'rubygems' +require 'hoe' +require './lib/heckle.rb' + +Hoe.new('heckle', Heckle::VERSION) do |p| + p.rubyforge_name = 'seattlerb' + p.summary = 'Unit Test Sadism' + p.description = p.paragraphs_of('README.txt', 2..5).join("\n\n") + p.url = p.paragraphs_of('README.txt', 0).first.split(/\n/)[1..-1] + p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n") + + p.extra_deps << ['ruby2ruby', '>= 1.1.0'] +end + +# vim: syntax=Ruby diff --git a/bin/heckle b/bin/heckle new file mode 100644 index 0000000..b3d44da --- /dev/null +++ b/bin/heckle @@ -0,0 +1,21 @@ +#!/usr/local/bin/ruby + +$:.unshift File.join(File.dirname(__FILE__), *%w[.. lib]) +require 'test_unit_heckler' + +file = ARGV.shift +impl = ARGV.shift +meth = ARGV.shift + +unless file and impl then + abort "usage: #{File.basename($0)} file.rb impl_class_name [impl_method_name]" +end + +load file + +if meth + heckle = TestUnitHeckler.new(impl, meth) + heckle.validate +else + TestUnitHeckler.validate(impl) +end diff --git a/lib/heckle.rb b/lib/heckle.rb new file mode 100644 index 0000000..63f2e38 --- /dev/null +++ b/lib/heckle.rb @@ -0,0 +1,334 @@ +require 'rubygems' +require 'parse_tree' +require 'ruby2ruby' +require 'logger' + +class String + def to_class + split(/::/).inject(Object) { |klass, name| klass.const_get(name) } + end +end + +class Heckle < SexpProcessor + VERSION = '1.0.0' + + MUTATABLE_NODES = [:if, :lit, :str, :true, :false, :while, :until] + + attr_accessor :file, :klass_name, :method_name, :klass, :method, :mutatees, :original_tree, + :mutation_count, :node_count, :failures + + @@debug = false; + + def self.debug=(value) + @@debug = value + end + + def self.validate(klass_name) + klass_name.to_class.instance_methods(false).each do |meth| + heckler = self.new(klass_name, meth) + heckler.validate + end + end + + def initialize(klass_name=nil, method_name=nil) + super() + + @klass_name, @method_name = klass_name, method_name.intern + @klass = @method = nil + + load_test_files + + self.strict = false + self.auto_shift_type = true + self.expected = Array + + @mutatees = Hash.new + @mutation_count = Hash.new + @node_count = Hash.new + + MUTATABLE_NODES.each {|type| @mutatees[type] = [] } + + @failures = [] + + @mutated = false + + grab_mutatees + + @original_tree = current_tree.deep_clone + @original_mutatees = mutatees.deep_clone + end + + ############################################################ + ### Overwrite test_pass?, load_test_files for your own Heckle runner. + def tests_pass? + raise NotImplementedError + end + + def run_tests + if tests_pass? then + record_passing_mutation + else + report_test_failures + end + end + + def load_test_files + Dir.glob(ENV['TESTS'] || 'test/test_*.rb').each {|test| require test} + end + + ############################################################ + ### Running the script + + def validate + puts "Validating #{file}" + if tests_pass? then + puts "Tests passed -- heckling" + + until @mutatees.collect {|k,v| v}.uniq.flatten.empty? + reset_tree if current_tree != original_tree + process current_tree + silence_stream(STDOUT) { run_tests } + end + + unless @failures.empty? + puts "The following mutations didn't cause test failures:\n" + @failures.each {|failure| puts "\n#{failure}\n"} + else + puts "No mutants survived. Cool!" + end + else + puts "Tests failed... fix and run heckle again" + end + end + + def record_passing_mutation + @failures << current_code + end + + def heckle(exp) + puts "\nHeckling #{klass_name}##{method_name}\n" if @@debug + src = RubyToRuby.new.process(exp) + puts "Replacing #{klass_name}##{method_name} with:\n\n#{src}\n" if @@debug + klass_name.to_class.class_eval(src) + end + + ############################################################ + ### Processing sexps + + def process_defn(exp) + self.method = exp.shift + result = [:defn, method] + result << process(exp.shift) until exp.empty? + heckle(result) if method == method_name + @mutated = false + reset_node_count + + return result + end + + def process_lit(exp) + mutate_node [:lit, exp.shift] + end + + def mutate_lit(exp) + case exp[1] + when Fixnum, Float, Bignum + [:lit, exp[1] + rand(10)] + when Symbol + [:lit, :"#{rand_string}"] + when Regexp + [:lit, /#{Regexp.escape(rand_string)}/] + when Range + [:lit, rand_range] + end + end + + def process_str(exp) + mutate_node [:str, exp.shift] + end + + def mutate_str(node) + [:str, rand_string] + end + + def process_if(exp) + mutate_node [:if, process(exp.shift), process(exp.shift), process(exp.shift)] + end + + def mutate_if(node) + [:if, node[1], node[3], node[2]] + end + + def process_true(exp) + mutate_node [:true] + end + + def mutate_true(node) + [:false] + end + + def process_false(exp) + mutate_node [:false] + end + + def mutate_false(node) + [:true] + end + + def process_while(exp) + cond, body, head_controlled = grab_conditional_loop_parts(exp) + mutate_node [:while, cond, body, head_controlled] + end + + def mutate_while(node) + [:until, node[1], node[2], node[3]] + end + + def process_until(exp) + cond, body, head_controlled = grab_conditional_loop_parts(exp) + mutate_node [:until, cond, body, head_controlled] + end + + def mutate_until(node) + [:while, node[1], node[2], node[3]] + end + + def mutate_node(node) + raise UnsupportedNodeError unless respond_to? "mutate_#{node.first}" + increment_node_count node + if should_heckle? node + increment_mutation_count node + return send("mutate_#{node.first}", node) + else + node + end + end + + ############################################################ + ### Tree operations + + def walk_and_push(node) + return unless node.respond_to? :each + return if node.is_a? String + node.each { |child| walk_and_push(child) } + if MUTATABLE_NODES.include? node.first + @mutatees[node.first.to_sym].push(node) + mutation_count[node] = 0 + end + end + + def grab_mutatees + walk_and_push(current_tree) + end + + def current_tree + ParseTree.translate(klass_name.to_class, method_name) + end + + def reset + reset_tree + reset_mutatees + reset_mutation_count + end + + def reset_tree + return unless original_tree != current_tree + @mutated = false + + r2r = RubyToRuby.new + src = r2r.process(original_tree.deep_clone) + klass_name.to_class.class_eval(src) + end + + def reset_mutatees + @mutatees = @original_mutatees.deep_clone + end + + def reset_mutation_count + mutation_count.each {|k,v| mutation_count[k] = 0} + end + + def reset_node_count + node_count.each {|k,v| node_count[k] = 0} + end + + def increment_node_count(node) + if node_count[node].nil? + node_count[node] = 1 + else + node_count[node] += 1 + end + end + + def increment_mutation_count(node) + # So we don't re-mutate this later if the tree is reset + mutation_count[node] += 1 + @mutatees[node.first].delete_at(@mutatees[node.first].index(node)) + @mutated = true + end + + ############################################################ + ### Convenience methods + + def should_heckle?(exp) + return false unless method == method_name + mutation_count[exp] = 0 if mutation_count[exp].nil? + return false if node_count[exp] <= mutation_count[exp] + mutatees[exp.first.to_sym].include?(exp) && !already_mutated? + end + + def grab_conditional_loop_parts(exp) + cond = process(exp.shift) + body = process(exp.shift) + head_controlled = exp.shift + return cond, body, head_controlled + end + + def already_mutated? + @mutated + end + + def current_code + RubyToRuby.translate(klass_name.to_class, method_name) + end + + def rand_string + size = rand(100) + str = "" + size.times { str << rand(126).chr } + str + end + + def rand_range + min = rand(50) + max = min + rand(50) + min..max + end + + def abort_and_report_incomplete_tests + abort "*** Tests passed again after heckling, your tests are incomplete" + end + + def report_test_failures + puts "Tests failed -- this is good" + end + + # silence_stream taken from Rails ActiveSupport reporting.rb + + # Silences any stream for the duration of the block. + # + # silence_stream(STDOUT) do + # puts 'This will never be seen' + # end + # + # puts 'But this will' + def silence_stream(stream) + old_stream = stream.dup + stream.reopen(RUBY_PLATFORM =~ /mswin/ ? 'NUL:' : '/dev/null') + stream.sync = true + yield + ensure + stream.reopen(old_stream) + end + +end diff --git a/lib/test_unit_heckler.rb b/lib/test_unit_heckler.rb new file mode 100644 index 0000000..9755c94 --- /dev/null +++ b/lib/test_unit_heckler.rb @@ -0,0 +1,14 @@ +#!/usr/bin/env ruby + +require 'test/unit/autorunner' +require 'heckle' + +class TestUnitHeckler < Heckle + def initialize(klass_name=nil, method_name=nil) + super(klass_name, method_name) + end + + def tests_pass? + Test::Unit::AutoRunner.run + end +end \ No newline at end of file diff --git a/sample/Rakefile b/sample/Rakefile new file mode 100644 index 0000000..f56f1df --- /dev/null +++ b/sample/Rakefile @@ -0,0 +1,16 @@ +# -*- ruby -*- +require 'rubygems' +require 'rake/testtask' + +task :default => :test + +desc "Run basic tests" +Rake::TestTask.new { |t| + t.pattern = 'test/test_*.rb' + t.verbose = true + t.warning = true +} + + + +# vim: syntax=Ruby diff --git a/sample/changes.log b/sample/changes.log new file mode 100644 index 0000000..7d03b93 --- /dev/null +++ b/sample/changes.log @@ -0,0 +1,91 @@ +# Logfile created on Fri Nov 10 22:52:10 PST 2006 by logger.rb/1.5.2.7 +I, [2006-11-10T22:52:10.310486 #498] INFO -- : Validating +I, [2006-11-10T22:52:10.326577 #498] INFO -- : Heckling Heckled#uses_strings +I, [2006-11-10T22:52:10.328115 #498] INFO -- : Replacing #uses_strings with: +def uses_strings + (@names << "I\rmeo&+eIZ{bD2dj1Z_e\001{Y!](CbI'!`@\020Wlg4,w\t\02181/4J\t\v@][ L!Xy[\r\\MJ[0.+gh}\001ks\026_-BK") + (@names << "Hello, Jeff") + (@names << "Hi, Frank") +end +I, [2006-11-10T22:52:10.344547 #498] INFO -- : Heckling Heckled#uses_strings +I, [2006-11-10T22:52:10.346021 #498] INFO -- : Replacing #uses_strings with: +def uses_strings + (@names << "Hello, Robert") + (@names << "\030ym+3\f:\031\023`\r:O\a") + (@names << "Hi, Frank") +end +I, [2006-11-10T22:52:10.356743 #498] INFO -- : Heckling Heckled#uses_strings +I, [2006-11-10T22:52:10.358234 #498] INFO -- : Replacing #uses_strings with: +def uses_strings + (@names << "Hello, Robert") + (@names << "Hello, Jeff") + (@names << "o,NY\020\027V0v\032dEw\010<*4\026uh$\026\010vhl(A\"ybX$sd\"M\006qbH\032TAV/") +end +I, [2006-11-10T22:52:10.369174 #498] INFO -- : Heckling Heckled#uses_strings +I, [2006-11-10T22:52:10.370636 #498] INFO -- : Replacing #uses_strings with: +def uses_strings + (@names << "Hello, Robert") + (@names << "Hello, Jeff") + (@names << "Hi, Frank") +end +I, [2006-11-10T23:17:54.953429 #569] INFO -- : Validating +I, [2006-11-10T23:19:05.369315 #570] INFO -- : Validating +I, [2006-11-10T23:20:01.512545 #571] INFO -- : Validating +I, [2006-11-10T23:20:08.080030 #572] INFO -- : Validating +I, [2006-11-10T23:20:08.100363 #572] INFO -- : Heckling Heckled#uses_strings +I, [2006-11-10T23:20:08.101943 #572] INFO -- : Replacing #uses_strings with: +def uses_strings + (@names << "\034\000\021[\r\aM!L=qfU'#") + (@names << "Hello, Jeff") + (@names << "Hi, Frank") +end +I, [2006-11-10T23:20:08.120455 #572] INFO -- : Heckling Heckled#uses_strings +I, [2006-11-10T23:20:08.122176 #572] INFO -- : Replacing #uses_strings with: +def uses_strings + (@names << "Hello, Robert") + (@names << "\r\025 $\n(&6P^X<\000\t..[\003$I\025\037\001\027E$/P\025= 10 + i += 1 + end + i + end + + def uses_numeric_literals + i = 1 + i += 10 + i -= 3.5 + end + + def uses_strings + @names << "Hello, Robert" + @names << "Hello, Jeff" + @names << "Hi, Frank" + end + + def uses_different_types + i = 1 + b = "Hello, Joe" + c = 3.3 + end + + def uses_the_same_literal + i = 1 + i = 1 + i = 1 + end + + def uses_if + if true + if false + return + end + end + end + + def uses_unless + unless true + if false + return + end + end + end +end diff --git a/sample/test/test_heckled.rb b/sample/test/test_heckled.rb new file mode 100644 index 0000000..bb06a5e --- /dev/null +++ b/sample/test/test_heckled.rb @@ -0,0 +1,19 @@ +#!/usr/bin/env ruby +# +# Created by Kevin Clark on 2006-11-10. +# Copyright (c) 2006. All rights reserved. + +require "test/unit" + +$:.unshift File.join(File.dirname(__FILE__), *%w[.. lib]) +require "heckled" + +class TestHeckled < Test::Unit::TestCase + def setup + @heckled = Heckled.new + end + def test_uses_strings + @heckled.uses_strings + assert_equal ["Hello, Robert", "Hello, Jeff", "Hi, Frank"], @heckled.names + end +end \ No newline at end of file diff --git a/test/fixtures/heckled.rb b/test/fixtures/heckled.rb new file mode 100644 index 0000000..efeb55b --- /dev/null +++ b/test/fixtures/heckled.rb @@ -0,0 +1,100 @@ +class Heckled + attr_accessor :names + + def initialize + @names = [] + end + + def uses_many_things + i = 1 + while i < 10 + i += 1 + until some_func + some_other_func + end + return true if "hi there" == "changeling" + return false + end + i + end + + def uses_while + while some_func + some_other_func + end + end + + def uses_until + until some_func + some_other_func + end + end + + def uses_numeric_literals + i = 1 + i += 2147483648 + i -= 3.5 + end + + def uses_strings + @names << "Hello, Robert" + @names << "Hello, Jeff" + @names << "Hi, Frank" + end + + def uses_different_types + i = 1 + b = "Hello, Joe" + c = 3.3 + end + + def uses_the_same_literal + i = 1 + i = 1 + i = 1 + end + + def uses_if + if some_func + if some_other_func + return + end + end + end + + def uses_boolean + true + false + end + + def uses_unless + unless true + if false + return + end + end + end + + def uses_symbols + i = :blah + i = :blah + i = :and_blah + end + + def uses_regexes + i = /a.*/ + i = /c{2,4}+/ + i = /123/ + end + + def uses_ranges + i = 6..100 + i = -1..9 + i = 1..4 + end + + private + + def some_func; end + def some_other_func; end +end diff --git a/test/test_heckle.rb b/test/test_heckle.rb new file mode 100644 index 0000000..96214ae --- /dev/null +++ b/test/test_heckle.rb @@ -0,0 +1,637 @@ +$:.unshift(File.dirname(__FILE__) + '/fixtures') +$:.unshift(File.dirname(__FILE__) + '/../lib') + +require 'test/unit/testcase' +require 'test/unit' if $0 == __FILE__ +require 'test_unit_heckler' +require 'heckled' + +class TestHeckle < Test::Unit::TestCase + def setup + @heckler = Heckle.new("Heckled", "uses_many_things") + end + + def test_should_set_original_tree + expected = [:defn, + :uses_many_things, + [:scope, + [:block, + [:args], + [:lasgn, :i, [:lit, 1]], + [:while, + [:call, [:lvar, :i], :<, [:array, [:lit, 10]]], + [:block, + [:lasgn, :i, [:call, [:lvar, :i], :+, [:array, [:lit, 1]]]], + [:until, [:vcall, :some_func], [:vcall, :some_other_func], true], + [:if, + [:call, [:str, "hi there"], :==, [:array, [:str, "changeling"]]], + [:return, [:true]], + nil], + [:return, [:false]]], + true], + [:lvar, :i]]]] + + assert_equal expected, @heckler.original_tree + end + + def test_should_grab_mutatees_from_method + # expected is from tree of uses_while + expected = { + :lit=>[[:lit, 1], [:lit, 10], [:lit, 1]], + :if=>[[:if, + [:call, [:str, "hi there"], :==, [:array, [:str, "changeling"]]], + [:return, [:true]], + nil]], + :str => [[:str, "hi there"], [:str, "changeling"]], + :true => [[:true]], + :false => [[:false]], + :while=> + [[:while, + [:call, [:lvar, :i], :<, [:array, [:lit, 10]]], + [:block, + [:lasgn, :i, [:call, [:lvar, :i], :+, [:array, [:lit, 1]]]], + [:until, [:vcall, :some_func], [:vcall, :some_other_func], true], + [:if, + [:call, [:str, "hi there"], :==, [:array, [:str, "changeling"]]], + [:return, [:true]], + nil], + [:return, [:false]]], + true]], + :until => [[:until, [:vcall, :some_func], [:vcall, :some_other_func], true]] + } + + assert_equal expected, @heckler.mutatees + end + + def test_reset + original_tree = @heckler.current_tree.deep_clone + original_mutatees = @heckler.mutatees.deep_clone + + 3.times { @heckler.process(@heckler.current_tree) } + + assert_not_equal original_tree, @heckler.current_tree + assert_not_equal original_mutatees, @heckler.mutatees + + @heckler.reset + assert_equal original_tree, @heckler.current_tree + assert_equal original_mutatees, @heckler.mutatees + end + + def test_reset_tree + original_tree = @heckler.current_tree.deep_clone + + @heckler.process(@heckler.current_tree) + assert_not_equal original_tree, @heckler.current_tree + + @heckler.reset_tree + assert_equal original_tree, @heckler.current_tree + end + + def test_reset_should_work_over_several_process_calls + original_tree = @heckler.current_tree.deep_clone + original_mutatees = @heckler.mutatees.deep_clone + + @heckler.process(@heckler.current_tree) + assert_not_equal original_tree, @heckler.current_tree + assert_not_equal original_mutatees, @heckler.mutatees + + @heckler.reset + assert_equal original_tree, @heckler.current_tree + assert_equal original_mutatees, @heckler.mutatees + + 3.times { @heckler.process(@heckler.current_tree) } + assert_not_equal original_tree, @heckler.current_tree + assert_not_equal original_mutatees, @heckler.mutatees + + @heckler.reset + assert_equal original_tree, @heckler.current_tree + assert_equal original_mutatees, @heckler.mutatees + end + + def test_reset_mutatees + original_mutatees = @heckler.mutatees.deep_clone + + @heckler.process(@heckler.current_tree) + assert_not_equal original_mutatees, @heckler.mutatees + + @heckler.reset_mutatees + assert_equal original_mutatees, @heckler.mutatees + end + + def teardown + @heckler.reset + end +end + +class Heckle + def rand(*args) + 5 + end +end + +class TestHeckleNumbers < Test::Unit::TestCase + def setup + @heckler = Heckle.new("Heckled", "uses_numeric_literals") + end + + def test_literals_should_flip_one_at_a_time + expected = [:defn, + :uses_numeric_literals, + [:scope, + [:block, + [:args], + [:lasgn, :i, [:lit, 6]], + [:lasgn, :i, [:call, [:lvar, :i], :+, [:array, [:lit, 2147483648]]]], + [:lasgn, :i, [:call, [:lvar, :i], :-, [:array, [:lit, 3.5]]]]]]] + + @heckler.process(@heckler.current_tree) + assert_equal expected, @heckler.current_tree + + @heckler.reset_tree + + expected = [:defn, + :uses_numeric_literals, + [:scope, + [:block, + [:args], + [:lasgn, :i, [:lit, 1]], + [:lasgn, :i, [:call, [:lvar, :i], :+, [:array, [:lit, 2147483653]]]], + [:lasgn, :i, [:call, [:lvar, :i], :-, [:array, [:lit, 3.5]]]]]]] + + @heckler.process(@heckler.current_tree) + assert_equal expected, @heckler.current_tree + + @heckler.reset_tree + + expected = [:defn, + :uses_numeric_literals, + [:scope, + [:block, + [:args], + [:lasgn, :i, [:lit, 1]], + [:lasgn, :i, [:call, [:lvar, :i], :+, [:array, [:lit, 2147483648]]]], + [:lasgn, :i, [:call, [:lvar, :i], :-, [:array, [:lit, 8.5]]]]]]] + + @heckler.process(@heckler.current_tree) + assert_equal expected, @heckler.current_tree + end + + def teardown + @heckler.reset + end +end + +class TestHeckleSymbols < Test::Unit::TestCase + def setup + @heckler = Heckle.new("Heckled", "uses_symbols") + end + + def test_default_structure + expected = [:defn, + :uses_symbols, + [:scope, + [:block, + [:args], + [:lasgn, :i, [:lit, :blah]], + [:lasgn, :i, [:lit, :blah]], + [:lasgn, :i, [:lit, :and_blah]]]]] + assert_equal expected, @heckler.current_tree + end + + + def test_should_randomize_symbol + expected = [:defn, + :uses_symbols, + [:scope, + [:block, + [:args], + [:lasgn, :i, [:lit, :"l33t h4x0r"]], + [:lasgn, :i, [:lit, :blah]], + [:lasgn, :i, [:lit, :and_blah]]]]] + @heckler.process(@heckler.current_tree) + assert_equal expected, @heckler.current_tree + + @heckler.reset_tree + + expected = [:defn, + :uses_symbols, + [:scope, + [:block, + [:args], + [:lasgn, :i, [:lit, :blah]], + [:lasgn, :i, [:lit, :"l33t h4x0r"]], + [:lasgn, :i, [:lit, :and_blah]]]]] + @heckler.process(@heckler.current_tree) + assert_equal expected, @heckler.current_tree + + @heckler.reset_tree + + expected = [:defn, + :uses_symbols, + [:scope, + [:block, + [:args], + [:lasgn, :i, [:lit, :blah]], + [:lasgn, :i, [:lit, :blah]], + [:lasgn, :i, [:lit, :"l33t h4x0r"]]]]] + @heckler.process(@heckler.current_tree) + assert_equal expected, @heckler.current_tree + end +end + +class TestHeckleRegexes < Test::Unit::TestCase + def setup + @heckler = Heckle.new("Heckled", "uses_regexes") + end + + def test_default_structure + expected = [:defn, + :uses_regexes, + [:scope, + [:block, + [:args], + [:lasgn, :i, [:lit, /a.*/]], + [:lasgn, :i, [:lit, /c{2,4}+/]], + [:lasgn, :i, [:lit, /123/]]]]] + assert_equal expected, @heckler.current_tree + end + + + def test_should_randomize_symbol + expected = [:defn, + :uses_regexes, + [:scope, + [:block, + [:args], + [:lasgn, :i, [:lit, /l33t\ h4x0r/]], + [:lasgn, :i, [:lit, /c{2,4}+/]], + [:lasgn, :i, [:lit, /123/]]]]] + @heckler.process(@heckler.current_tree) + assert_equal expected, @heckler.current_tree + + @heckler.reset_tree + + expected = [:defn, + :uses_regexes, + [:scope, + [:block, + [:args], + [:lasgn, :i, [:lit, /a.*/]], + [:lasgn, :i, [:lit, /l33t\ h4x0r/]], + [:lasgn, :i, [:lit, /123/]]]]] + @heckler.process(@heckler.current_tree) + assert_equal expected, @heckler.current_tree + + @heckler.reset_tree + + expected = [:defn, + :uses_regexes, + [:scope, + [:block, + [:args], + [:lasgn, :i, [:lit, /a.*/]], + [:lasgn, :i, [:lit, /c{2,4}+/]], + [:lasgn, :i, [:lit, /l33t\ h4x0r/]]]]] + @heckler.process(@heckler.current_tree) + assert_equal expected, @heckler.current_tree + end +end + +class TestHeckleRanges < Test::Unit::TestCase + def setup + @heckler = Heckle.new("Heckled", "uses_ranges") + end + + def test_default_structure + expected = [:defn, + :uses_ranges, + [:scope, + [:block, + [:args], + [:lasgn, :i, [:lit, 6..100]], + [:lasgn, :i, [:lit, -1..9]], + [:lasgn, :i, [:lit, 1..4]]]]] + assert_equal expected, @heckler.current_tree + end + + def test_should_randomize_symbol + expected = [:defn, + :uses_ranges, + [:scope, + [:block, + [:args], + [:lasgn, :i, [:lit, 5..10]], + [:lasgn, :i, [:lit, -1..9]], + [:lasgn, :i, [:lit, 1..4]]]]] + @heckler.process(@heckler.current_tree) + assert_equal expected, @heckler.current_tree + + @heckler.reset_tree + + expected = [:defn, + :uses_ranges, + [:scope, + [:block, + [:args], + [:lasgn, :i, [:lit, 6..100]], + [:lasgn, :i, [:lit, 5..10]], + [:lasgn, :i, [:lit, 1..4]]]]] + @heckler.process(@heckler.current_tree) + assert_equal expected, @heckler.current_tree + + @heckler.reset_tree + + expected = [:defn, + :uses_ranges, + [:scope, + [:block, + [:args], + [:lasgn, :i, [:lit, 6..100]], + [:lasgn, :i, [:lit, -1..9]], + [:lasgn, :i, [:lit, 5..10]]]]] + @heckler.process(@heckler.current_tree) + assert_equal expected, @heckler.current_tree + end +end + + +class TestHeckleSameLiteral < Test::Unit::TestCase + def setup + @heckler = Heckle.new("Heckled", "uses_the_same_literal") + end + + def teardown + @heckler.reset + end + + def test_original_tree + expected = [:defn, + :uses_the_same_literal, + [:scope, + [:block, + [:args], + [:lasgn, :i, [:lit, 1]], + [:lasgn, :i, [:lit, 1]], + [:lasgn, :i, [:lit, 1]]]]] + + assert_equal expected, @heckler.current_tree + end + + def test_literals_should_flip_one_at_a_time + # structure of uses_numeric_literals with first literal +5 (from stubbed rand) + expected = [:defn, + :uses_the_same_literal, + [:scope, + [:block, + [:args], + [:lasgn, :i, [:lit, 6]], + [:lasgn, :i, [:lit, 1]], + [:lasgn, :i, [:lit, 1]]]]] + + @heckler.process(@heckler.current_tree) + assert_equal expected, @heckler.current_tree + + @heckler.reset_tree + + expected = [:defn, + :uses_the_same_literal, + [:scope, + [:block, + [:args], + [:lasgn, :i, [:lit, 1]], + [:lasgn, :i, [:lit, 6]], + [:lasgn, :i, [:lit, 1]]]]] + + @heckler.process(@heckler.current_tree) + assert_equal expected, @heckler.current_tree + + @heckler.reset_tree + + expected = [:defn, + :uses_the_same_literal, + [:scope, + [:block, + [:args], + [:lasgn, :i, [:lit, 1]], + [:lasgn, :i, [:lit, 1]], + [:lasgn, :i, [:lit, 6]]]]] + + @heckler.process(@heckler.current_tree) + assert_equal expected, @heckler.current_tree + end +end + +class Heckle + def rand_string + "l33t h4x0r" + end +end + +class TestHeckleStrings < Test::Unit::TestCase + def setup + @heckler = Heckle.new("Heckled", "uses_strings") + end + + def teardown + @heckler.reset + end + + def test_default_structure + expected = [:defn, + :uses_strings, + [:scope, + [:block, + [:args], + [:call, [:ivar, :@names], :<<, [:array, [:str, "Hello, Robert"]]], + [:call, [:ivar, :@names], :<<, [:array, [:str, "Hello, Jeff"]]], + [:call, [:ivar, :@names], :<<, [:array, [:str, "Hi, Frank"]]]]]] + assert_equal expected, @heckler.current_tree + end + + def test_should_heckle_string_literals + expected = [:defn, + :uses_strings, + [:scope, + [:block, + [:args], + [:call, [:ivar, :@names], :<<, [:array, [:str, "l33t h4x0r"]]], + [:call, [:ivar, :@names], :<<, [:array, [:str, "Hello, Jeff"]]], + [:call, [:ivar, :@names], :<<, [:array, [:str, "Hi, Frank"]]]]]] + @heckler.process(@heckler.current_tree) + assert_equal expected, @heckler.current_tree + + @heckler.reset_tree + + expected = [:defn, + :uses_strings, + [:scope, + [:block, + [:args], + [:call, [:ivar, :@names], :<<, [:array, [:str, "Hello, Robert"]]], + [:call, [:ivar, :@names], :<<, [:array, [:str, "l33t h4x0r"]]], + [:call, [:ivar, :@names], :<<, [:array, [:str, "Hi, Frank"]]]]]] + + @heckler.process(@heckler.current_tree) + assert_equal expected, @heckler.current_tree + + @heckler.reset_tree + + expected = [:defn, + :uses_strings, + [:scope, + [:block, + [:args], + [:call, [:ivar, :@names], :<<, [:array, [:str, "Hello, Robert"]]], + [:call, [:ivar, :@names], :<<, [:array, [:str, "Hello, Jeff"]]], + [:call, [:ivar, :@names], :<<, [:array, [:str, "l33t h4x0r"]]]]]] + + @heckler.process(@heckler.current_tree) + assert_equal expected, @heckler.current_tree + end +end + +class TestHeckleIfs < Test::Unit::TestCase + def setup + @heckler = Heckle.new("Heckled", "uses_if") + end + + def teardown + @heckler.reset + end + + def test_default_structure + expected = [:defn, + :uses_if, + [:scope, + [:block, + [:args], + [:if, + [:vcall, :some_func], + [:if, [:vcall, :some_other_func], [:return], nil], + nil]]]] + + + assert_equal expected, @heckler.current_tree + end + + def test_should_flip_if_to_unless + expected = [:defn, + :uses_if, + [:scope, + [:block, + [:args], + [:if, + [:vcall, :some_func], + [:if, [:vcall, :some_other_func], nil, [:return]], + nil]]]] + + @heckler.process(@heckler.current_tree) + assert_equal expected, @heckler.current_tree + + @heckler.reset_tree + + expected = [:defn, + :uses_if, + [:scope, + [:block, + [:args], + [:if, + [:vcall, :some_func], + nil, + [:if, [:vcall, :some_other_func], [:return], nil]]]]] + + @heckler.process(@heckler.current_tree) + assert_equal expected, @heckler.current_tree + end +end + +class TestHeckleBooleans < Test::Unit::TestCase + def setup + @heckler = Heckle.new("Heckled", "uses_boolean") + end + + def teardown + @heckler.reset + end + + def test_default_structure + expected = [:defn, :uses_boolean, [:scope, [:block, [:args], [:true], [:false]]]] + assert_equal expected, @heckler.current_tree + end + + + def test_should_flip_true_to_false_and_false_to_true + expected = [:defn, :uses_boolean, [:scope, [:block, [:args], [:false], [:false]]]] + + @heckler.process(@heckler.current_tree) + assert_equal expected, @heckler.current_tree + + @heckler.reset_tree + + expected = [:defn, :uses_boolean, [:scope, [:block, [:args], [:true], [:true]]]] + @heckler.process(@heckler.current_tree) + assert_equal expected, @heckler.current_tree + end +end + +class TestHeckleWhile < Test::Unit::TestCase + def setup + @heckler = Heckle.new("Heckled", "uses_while") + end + + def teardown + @heckler.reset + end + + def test_default_structure + expected = [:defn, + :uses_while, + [:scope, + [:block, + [:args], + [:while, [:vcall, :some_func], [:vcall, :some_other_func], true]]]] + assert_equal expected, @heckler.current_tree + end + + def test_flips_while_to_until + expected = [:defn, + :uses_while, + [:scope, + [:block, + [:args], + [:until, [:vcall, :some_func], [:vcall, :some_other_func], true]]]] + @heckler.process(@heckler.current_tree) + assert_equal expected, @heckler.current_tree + end +end + +class TestHeckleUntil < Test::Unit::TestCase + def setup + @heckler = Heckle.new("Heckled", "uses_until") + end + + def teardown + @heckler.reset + end + + def test_default_structure + expected = [:defn, + :uses_until, + [:scope, + [:block, + [:args], + [:until, [:vcall, :some_func], [:vcall, :some_other_func], true]]]] + assert_equal expected, @heckler.current_tree + end + + def test_flips_until_to_while + expected = [:defn, + :uses_until, + [:scope, + [:block, + [:args], + [:while, [:vcall, :some_func], [:vcall, :some_other_func], true]]]] + @heckler.process(@heckler.current_tree) + assert_equal expected, @heckler.current_tree + end +end