heckle 1.4.3 → 2.0.0.b1

Sign up to get free protection for your applications and to get access to all the features.
data.tar.gz.sig CHANGED
Binary file
data/.autotest CHANGED
@@ -3,10 +3,12 @@
3
3
  require 'autotest/restart'
4
4
 
5
5
  Autotest.add_hook :initialize do |at|
6
+ at.libs << ":../../ParseTree/dev/lib"
7
+ at.libs << ":../../ParseTree/dev/test"
6
8
  at.libs << ":../../RubyInline/dev/lib"
7
- at.libs << ":../../ParseTree/dev/lib:../../ParseTree/dev/test"
9
+ at.libs << ":../../ruby2ruby/1.3.1/lib"
10
+ at.libs << ":../../ZenTest/dev/lib"
8
11
  at.libs << ":../../sexp_processor/dev/lib"
9
- at.libs << ":../../ruby2ruby/dev/lib"
10
12
 
11
13
  %w(Dasgn Iter Dasgncurr Cvasgn Boolean Call Callblock
12
14
  ClassMethod Gasgn Iasgn If Lasgn Masgn Ranges Regexes
File without changes
@@ -1,3 +1,19 @@
1
+ === 2.0.0.b1 / 2012-10-02
2
+
3
+ * 1 major enhancement:
4
+
5
+ * Hot damn! It works with ruby_parser!!! (phiggins)
6
+
7
+ * 1 minor enhancement:
8
+
9
+ * 1.9 support! (phiggins)
10
+
11
+ * 3 bug fixes:
12
+
13
+ * Fixed Ruby2Ruby usage (raggi)
14
+ * Fixed dependencies so heckle doesn't use ruby_parser 3 and friends.
15
+ * Fixed grammar in description. Reported by Jean Lange.
16
+
1
17
  === 1.4.3 / 2009-06-23
2
18
 
3
19
  * 2 minor enhancements:
@@ -6,10 +6,18 @@ Rakefile
6
6
  bin/heckle
7
7
  lib/autotest/heckle.rb
8
8
  lib/heckle.rb
9
+ lib/heckle_runner.rb
10
+ lib/minitest_heckler.rb
9
11
  lib/test_unit_heckler.rb
10
12
  sample/Rakefile
11
13
  sample/changes.log
12
- sample/lib/heckled.rb
13
14
  sample/test/test_heckled.rb
15
+ test/fixtures/heckle_dummy.rb
14
16
  test/fixtures/heckled.rb
17
+ test/fixtures/minitest_project/README.txt
18
+ test/fixtures/minitest_project/Rakefile
19
+ test/fixtures/minitest_project/lib/doubler.rb
20
+ test/fixtures/minitest_project/test/test_doubler_with_a_number.rb
21
+ test/fixtures/minitest_project/test/test_doubler_with_bad_input.rb
15
22
  test/test_heckle.rb
23
+ test/test_heckle_runner.rb
data/README.txt CHANGED
@@ -1,11 +1,12 @@
1
1
  = heckle
2
2
 
3
- * http://www.rubyforge.org/projects/seattlerb
4
- * http://seattlerb.rubyforge.org/heckle
3
+ home :: http://ruby.sadi.st/Heckle.html
4
+ repo :: https://github.com/seattlerb/heckle
5
+ rdoc :: http://seattlerb.rubyforge.org/heckle
5
6
 
6
7
  == DESCRIPTION:
7
8
 
8
- Heckle is unit test sadism(tm) at it's core. Heckle is a mutation tester. It modifies your code and runs your tests to make sure they fail. The idea is that if code can be changed and your tests don't notice, either that code isn't being covered or it doesn't do anything.
9
+ Heckle is unit test sadism(tm) at its core. Heckle is a mutation tester. It modifies your code and runs your tests to make sure they fail. The idea is that if code can be changed and your tests don't notice, either that code isn't being covered or it doesn't do anything.
9
10
 
10
11
  It's like hiring a white-hat hacker to try to break into your server and making sure you detect it. You learn the most by trying to break things and watching the outcome in an act of unit test sadism.
11
12
 
@@ -32,7 +33,7 @@ It's like hiring a white-hat hacker to try to break into your server and making
32
33
 
33
34
  (The MIT License)
34
35
 
35
- Copyright (c) 2006-2008 Ryan Davis and Kevin Clark
36
+ Copyright (c) Ryan Davis, seattle.rb, and Kevin Clark
36
37
 
37
38
  Permission is hereby granted, free of charge, to any person obtaining
38
39
  a copy of this software and associated documentation files (the
data/Rakefile CHANGED
@@ -3,28 +3,28 @@
3
3
  require 'rubygems'
4
4
  require 'hoe'
5
5
 
6
- Hoe.add_include_dirs("../../ParseTree/dev/lib",
7
- "../../ParseTree/dev/test",
8
- "../../RubyInline/dev/lib",
9
- "../../ruby2ruby/dev/lib",
6
+ Hoe.add_include_dirs("../../RubyInline/dev/lib",
7
+ "../../ruby2ruby/1.3.1/lib",
10
8
  "../../ZenTest/dev/lib",
11
9
  "../../sexp_processor/dev/lib",
10
+ "../../ruby_parser/2.3.1/lib",
12
11
  "lib")
13
12
 
14
13
  Hoe.plugin :seattlerb
15
14
 
16
15
  Hoe.spec 'heckle' do
17
- developer 'Ryan Davis', 'ryand-ruby@zenspider.com'
18
- developer 'Eric Hodel', 'drbrain@segment7.net'
19
- developer 'Kevin Clark', 'kevin.clark@gmail.com'
16
+ developer 'Ryan Davis', 'ryand-ruby@zenspider.com'
17
+ developer 'Pete Higgins', 'pete@peterhiggins.org'
18
+ # developer 'Eric Hodel', 'drbrain@segment7.net'
19
+ # developer 'Kevin Clark', 'kevin.clark@gmail.com'
20
20
 
21
- self.rubyforge_name = 'seattlerb'
21
+ clean_globs << File.expand_path("~/.ruby_inline")
22
22
 
23
- clean_globs << File.expand_path("~/.ruby_inline")
24
- extra_deps << ['ParseTree', '>= 2.0.0']
25
- extra_deps << ['ruby2ruby', '>= 1.1.6']
26
- extra_deps << ['ZenTest', '>= 3.5.2']
27
- multiruby_skip << "1.9"
23
+ dependency 'ruby_parser', '~> 2.3.1'
24
+ dependency 'ruby2ruby', '~> 1.3.0'
25
+ dependency 'ZenTest', '~> 4.7.0'
26
+
27
+ self.test_globs = ["test/test_*.rb"]
28
28
  end
29
29
 
30
30
  # vim: syntax=ruby
data/bin/heckle CHANGED
@@ -1,96 +1,8 @@
1
- #!/usr/local/bin/ruby
1
+ #!/usr/bin/env ruby
2
2
 
3
- $:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
4
- require 'test_unit_heckler'
5
- require 'optparse'
3
+ $LOAD_PATH << File.join(File.dirname(__FILE__), '..', 'lib')
4
+ $LOAD_PATH << 'lib'
6
5
 
7
- force = false
8
- nodes = Heckle::MUTATABLE_NODES
9
-
10
- opts = OptionParser.new do |opts|
11
- opts.banner = "Usage: #{File.basename($0)} class_name [method_name]"
12
- opts.on("-v", "--verbose", "Loudly explain heckle run") do |opt|
13
- TestUnitHeckler.debug = true
14
- end
15
-
16
- opts.on("-V", "--version", "Prints Heckle's version number") do |opt|
17
- puts "Heckle #{Heckle::VERSION}"
18
- exit 0
19
- end
20
-
21
- opts.on("-t", "--tests TEST_PATTERN",
22
- "Location of tests (glob)") do |pattern|
23
- TestUnitHeckler.test_pattern = pattern
24
- end
25
-
26
- opts.on("-F", "--force", "Ignore initial test failures",
27
- "Best used with --focus") do |opt|
28
- force = true
29
- end
30
-
31
- opts.on( "--assignments", "Only mutate assignments") do |opt|
32
- puts "!"*70
33
- puts "!!! Heckling assignments only"
34
- puts "!"*70
35
- puts
36
-
37
- nodes = Heckle::ASGN_NODES
38
- end
39
-
40
- opts.on("-b", "--branches", "Only mutate branches") do |opt|
41
- puts "!"*70
42
- puts "!!! Heckling branches only"
43
- puts "!"*70
44
- puts
45
-
46
- nodes = Heckle::BRANCH_NODES
47
- end
48
-
49
- opts.on("-f", "--focus", "Apply the eye of sauron") do |opt|
50
- puts "!"*70
51
- puts "!!! Running in focused mode. FEEL THE EYE OF SAURON!!!"
52
- puts "!"*70
53
- puts
54
-
55
- TestUnitHeckler.focus = true
56
- end
57
-
58
- opts.on("-T", "--timeout SECONDS", "The maximum time for a test run in seconds",
59
- "Used to catch infinite loops") do |timeout|
60
- Heckle.timeout = timeout.to_i
61
- puts "Setting timeout at #{timeout} seconds."
62
- end
63
-
64
- opts.on("-n", "--nodes NODES", "Nodes to mutate",
65
- "Possible values: #{Heckle::MUTATABLE_NODES.join(',')}") do |opt|
66
- nodes = opt.split(',').collect {|n| n.to_sym }
67
- puts "Mutating nodes: #{nodes.inspect}"
68
- end
69
-
70
- opts.on("-x", "--exclude-nodes NODES", "Nodes to exclude") do |opt|
71
- exclusions = opt.split(',').collect {|n| n.to_sym }
72
- nodes = nodes - exclusions
73
- puts "Mutating without nodes: #{exclusions.inspect}"
74
- end
75
-
76
- opts.on("-h", "--help", "Show this message") do |opt|
77
- puts opts
78
- exit 0
79
- end
80
- end
81
-
82
- looks_like_rails = test ?f, 'config/environment.rb'
83
- TestUnitHeckler.test_pattern = "test/**/*.rb" if looks_like_rails
84
-
85
- opts.parse!
86
-
87
- impl = ARGV.shift
88
- meth = ARGV.shift
89
-
90
- unless impl then
91
- puts opts
92
- exit 1
93
- end
94
-
95
- exit TestUnitHeckler.validate(impl, meth, nodes, force)
6
+ require 'heckle_runner'
96
7
 
8
+ exit HeckleRunner.run
@@ -1,5 +1,5 @@
1
1
  require 'rubygems'
2
- require 'parse_tree'
2
+ require 'ruby_parser'
3
3
  require 'sexp_processor'
4
4
  require 'ruby2ruby'
5
5
  require 'timeout'
@@ -11,6 +11,17 @@ class String # :nodoc:
11
11
  end
12
12
  end
13
13
 
14
+ class Sexp
15
+ # REFACTOR: move to sexp.rb
16
+ def each_sexp
17
+ self.each do |sexp|
18
+ next unless Sexp === sexp
19
+
20
+ yield sexp
21
+ end
22
+ end
23
+ end
24
+
14
25
  ##
15
26
  # Test Unit Sadism
16
27
 
@@ -21,7 +32,7 @@ class Heckle < SexpProcessor
21
32
  ##
22
33
  # The version of Heckle you are using.
23
34
 
24
- VERSION = '1.4.3'
35
+ VERSION = '2.0.0.b1'
25
36
 
26
37
  ##
27
38
  # Branch node types.
@@ -131,9 +142,11 @@ class Heckle < SexpProcessor
131
142
 
132
143
  @mutated = false
133
144
 
145
+ @original_tree = rewrite find_scope_and_method
146
+ @current_tree = @original_tree.deep_clone
147
+
134
148
  grab_mutatees
135
149
 
136
- @original_tree = current_tree.deep_clone
137
150
  @original_mutatees = mutatees.deep_clone
138
151
  end
139
152
 
@@ -203,21 +216,23 @@ class Heckle < SexpProcessor
203
216
  end
204
217
 
205
218
  def heckle(exp)
206
- exp_copy = exp.deep_clone
219
+ @current_tree = exp.deep_clone
207
220
  src = begin
208
221
  Ruby2Ruby.new.process(exp)
209
222
  rescue => e
210
- puts "Error: #{e.message} with: #{klass_name}##{method_name}: #{exp_copy.inspect}"
223
+ puts "Error: #{e.message} with: #{klass_name}##{method_name}: #{@current_tree.inspect}"
211
224
  raise e
212
225
  end
213
226
 
214
- original = Ruby2Ruby.new.process(@original_tree.deep_clone)
215
- @reporter.replacing(klass_name, method_name, original, src) if @@debug
227
+ if @@debug
228
+ original = Ruby2Ruby.new.process(@original_tree.deep_clone)
229
+ @reporter.replacing(klass_name, method_name, original, src)
230
+ end
216
231
 
217
- clean_name = method_name.to_s.gsub(/self\./, '')
218
232
  self.count += 1
219
- new_name = "h#{count}_#{clean_name}"
220
233
 
234
+ clean_name = method_name.to_s.gsub(/self\./, '')
235
+ new_name = "h#{count}_#{clean_name}"
221
236
  klass = aliasing_class method_name
222
237
  klass.send :remove_method, new_name rescue nil
223
238
  klass.send :alias_method, new_name, clean_name
@@ -499,13 +514,102 @@ class Heckle < SexpProcessor
499
514
  end
500
515
 
501
516
  def current_tree
502
- ur = Unifier.new
517
+ @current_tree.deep_clone
518
+ end
519
+
520
+ # Copied from Flay#process
521
+ def find_scope_and_method
522
+ expand_dirs_to_files.each do |file|
523
+ #warn "Processing #{file}" if option[:verbose]
503
524
 
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)
525
+ ext = File.extname(file).sub(/^\./, '')
526
+ ext = "rb" if ext.nil? || ext.empty?
527
+ msg = "process_#{ext}"
507
528
 
508
- rewrite sexp
529
+ unless respond_to? msg then
530
+ warn " Unknown file type: #{ext}, defaulting to ruby"
531
+ msg = "process_rb"
532
+ end
533
+
534
+ begin
535
+ sexp = begin
536
+ send msg, file
537
+ rescue => e
538
+ warn " #{e.message.strip}"
539
+ warn " skipping #{file}"
540
+ nil
541
+ end
542
+
543
+ next unless sexp
544
+
545
+ found = find_scope sexp
546
+
547
+ return found if found
548
+ rescue SyntaxError => e
549
+ warn " skipping #{file}: #{e.message}"
550
+ end
551
+ end
552
+
553
+ raise "Couldn't find method."
554
+ end
555
+
556
+ def process_rb file
557
+ RubyParser.new.process(File.read(file), file)
558
+ end
559
+
560
+ def find_scope sexp, nesting=nil
561
+ nesting ||= klass_name.split("::").map {|k| k.to_sym }
562
+ current, *nesting = nesting
563
+
564
+ sexp = s(:block, sexp) unless sexp.first == :block
565
+
566
+ sexp.each_sexp do |node|
567
+ next unless [:class, :module].include? node.first
568
+ next unless node[1] == current
569
+
570
+ block = node.detect {|s| Sexp === s && s[0] == :scope }[1]
571
+
572
+ if nesting.empty?
573
+ return sexp if method_name.nil?
574
+
575
+ m = find_method block
576
+
577
+ return m if m
578
+ else
579
+ s = find_scope block, nesting
580
+
581
+ return s if s
582
+ end
583
+ end
584
+
585
+ nil
586
+ end
587
+
588
+ def find_method sexp
589
+ class_method = method_name.to_s =~ /^self\./
590
+ clean_name = method_name.to_s.sub(/^self\./, '').to_sym
591
+
592
+ sexp = s(:block, sexp) unless sexp.first == :block
593
+
594
+ sexp.each_sexp do |node|
595
+ if class_method
596
+ return node if node[0] == :defs && node[2] == clean_name
597
+ else
598
+ return node if node[0] == :defn && node[1] == clean_name
599
+ end
600
+ end
601
+
602
+ nil
603
+ end
604
+
605
+ def expand_dirs_to_files(dirs='.')
606
+ Array(dirs).flatten.map { |p|
607
+ if File.directory? p then
608
+ Dir[File.join(p, '**', "*.rb")]
609
+ else
610
+ p
611
+ end
612
+ }.flatten
509
613
  end
510
614
 
511
615
  def reset
@@ -518,6 +622,8 @@ class Heckle < SexpProcessor
518
622
  return unless original_tree != current_tree
519
623
  @mutated = false
520
624
 
625
+ @current_tree = original_tree.deep_clone
626
+
521
627
  self.count += 1
522
628
 
523
629
  clean_name = method_name.to_s.gsub(/self\./, '')
@@ -600,7 +706,7 @@ class Heckle < SexpProcessor
600
706
  end
601
707
 
602
708
  def current_code
603
- Ruby2Ruby.translate(klass_name.to_class, method_name)
709
+ Ruby2Ruby.new.process current_tree
604
710
  end
605
711
 
606
712
  ##
@@ -744,7 +850,7 @@ class Heckle < SexpProcessor
744
850
  # All nodes that can be mutated by Heckle.
745
851
 
746
852
  MUTATABLE_NODES = instance_methods.grep(/mutate_/).sort.map do |meth|
747
- meth.sub(/mutate_/, '').intern
853
+ meth.to_s.sub(/mutate_/, '').intern
748
854
  end - [:asgn, :node] # Ignore these methods
749
855
 
750
856
  ##
@@ -0,0 +1,113 @@
1
+ require 'optparse'
2
+ require 'heckle'
3
+ require 'minitest_heckler'
4
+
5
+ class HeckleRunner
6
+ def self.run argv=ARGV
7
+ options = parse_args argv
8
+
9
+ class_or_module, method = argv.shift, argv.shift
10
+
11
+ new(class_or_module, method, options).run
12
+ end
13
+
14
+ def self.parse_args argv
15
+ options = {
16
+ :force => false,
17
+ :nodes => Heckle::MUTATABLE_NODES,
18
+ :debug => false,
19
+ :focus => false,
20
+ :timeout => 5,
21
+ :test_pattern => 'test/test_*.rb',
22
+ }
23
+
24
+ OptionParser.new do |opts|
25
+ opts.version = Heckle::VERSION
26
+ opts.program_name = File.basename $0
27
+ opts.banner = "Usage: #{opts.program_name} class_name [method_name]"
28
+ opts.on("-v", "--verbose", "Loudly explain heckle run") do
29
+ options[:debug] = true
30
+ end
31
+
32
+ opts.on("-t", "--tests TEST_PATTERN",
33
+ "Location of tests (glob)") do |pattern|
34
+ options[:test_pattern] = pattern
35
+ end
36
+
37
+ opts.on("-F", "--force", "Ignore initial test failures",
38
+ "Best used with --focus") do
39
+ options[:force] = true
40
+ end
41
+
42
+ opts.on( "--assignments", "Only mutate assignments") do
43
+ puts "!"*70
44
+ puts "!!! Heckling assignments only"
45
+ puts "!"*70
46
+ puts
47
+
48
+ options[:nodes] = Heckle::ASGN_NODES
49
+ end
50
+
51
+ opts.on("-b", "--branches", "Only mutate branches") do
52
+ puts "!"*70
53
+ puts "!!! Heckling branches only"
54
+ puts "!"*70
55
+ puts
56
+
57
+ options[:nodes] = Heckle::BRANCH_NODES
58
+ end
59
+
60
+ opts.on("-f", "--focus", "Apply the eye of sauron") do
61
+ puts "!"*70
62
+ puts "!!! Running in focused mode. FEEL THE EYE OF SAURON!!!"
63
+ puts "!"*70
64
+ puts
65
+
66
+ options[:focus] = true
67
+ end
68
+
69
+ opts.on("-T", "--timeout SECONDS", "The maximum time for a test run in seconds",
70
+ "Used to catch infinite loops") do |timeout|
71
+ timeout = timeout.to_i
72
+ puts "Setting timeout at #{timeout} seconds."
73
+ options[:timeout] = timeout
74
+ end
75
+
76
+ opts.on("-n", "--nodes NODES", "Nodes to mutate",
77
+ "Possible values: #{Heckle::MUTATABLE_NODES.join(',')}") do |opt|
78
+ nodes = opt.split(',').collect {|n| n.to_sym }
79
+ options[:nodes] = nodes
80
+ puts "Mutating nodes: #{nodes.inspect}"
81
+ end
82
+
83
+ opts.on("-x", "--exclude-nodes NODES", "Nodes to exclude") do |opt|
84
+ exclusions = opt.split(',').collect {|n| n.to_sym }
85
+ options[:nodes] = options[:nodes] - exclusions
86
+ puts "Mutating without nodes: #{exclusions.inspect}"
87
+ end
88
+ end.parse! argv
89
+
90
+ p options if options[:debug]
91
+
92
+ # TODO: Pass options to Heckle's initializer instead.
93
+ Heckle.debug = options[:debug]
94
+ Heckle.timeout = options[:timeout]
95
+
96
+ options
97
+ end
98
+
99
+ # TODO: this sucks
100
+ def heckler
101
+ MiniTestHeckler
102
+ end
103
+
104
+ def initialize class_or_module, method, options={}
105
+ @class_or_module = class_or_module
106
+ @method = method
107
+ @options = options
108
+ end
109
+
110
+ def run
111
+ heckler.new(@class_or_module, @method, @options).validate
112
+ end
113
+ end