ZenTest 3.2.0 → 3.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +21 -0
- data/Manifest.txt +8 -24
- data/Rakefile +32 -8
- data/bin/autotest +4 -1
- data/bin/multiruby +20 -6
- data/bin/ruby_fork +6 -0
- data/bin/ruby_fork_client +6 -0
- data/example_dot_autotest.rb +148 -0
- data/lib/autotest.rb +201 -254
- data/lib/rails_autotest.rb +48 -110
- data/lib/ruby_fork.rb +178 -0
- data/lib/test/rails.rb +2 -2
- data/lib/test/rails/controller_test_case.rb +1 -1
- data/lib/test/rails/helper_test_case.rb +60 -0
- data/lib/test/rails/pp_html_document.rb +74 -0
- data/lib/test/rails/rake_tasks.rb +13 -12
- data/lib/test/rails/view_test_case.rb +2 -2
- data/lib/test/zentest_assertions.rb +39 -0
- data/lib/unit_diff.rb +6 -3
- data/lib/zentest.rb +1 -1
- data/test/test_autotest.rb +160 -208
- data/test/test_rails_autotest.rb +115 -138
- data/test/test_ruby_fork.rb +172 -0
- data/test/test_unit_diff.rb +69 -1
- data/test/test_zentest_assertions.rb +66 -0
- metadata +13 -27
- data/test/data/normal/lib/.#photo.rb +0 -0
- data/test/data/normal/lib/blah.rb +0 -0
- data/test/data/normal/lib/photo.rb +0 -0
- data/test/data/normal/test/#test_photo.rb# +0 -0
- data/test/data/normal/test/test_camelcase.rb +0 -0
- data/test/data/normal/test/test_photo.rb +0 -0
- data/test/data/normal/test/test_route.rb +0 -0
- data/test/data/normal/test/test_user.rb +0 -0
- data/test/data/rails/app/controllers/admin/theme_controller.rb +0 -0
- data/test/data/rails/app/controllers/route_controller.rb +0 -0
- data/test/data/rails/app/models/flickr_photo.rb +0 -0
- data/test/data/rails/app/models/route.rb +0 -0
- data/test/data/rails/app/views/route/index.rhtml +0 -0
- data/test/data/rails/config/environment.rb +0 -0
- data/test/data/rails/config/routes.rb +0 -0
- data/test/data/rails/test/controllers/route_controller_test.rb +0 -0
- data/test/data/rails/test/fixtures/routes.yml +0 -0
- data/test/data/rails/test/functional/admin/themes_controller_test.rb +0 -0
- data/test/data/rails/test/functional/dummy_controller_test.rb +0 -0
- data/test/data/rails/test/functional/route_controller_test.rb +0 -0
- data/test/data/rails/test/unit/flickr_photo_test.rb +0 -0
- data/test/data/rails/test/unit/photo_test.rb +0 -0
- data/test/data/rails/test/unit/route_test.rb +0 -0
- data/test/data/rails/test/views/route_view_test.rb +0 -0
data/History.txt
CHANGED
@@ -1,3 +1,24 @@
|
|
1
|
+
*** 3.3.0 / 2006-07-28
|
2
|
+
|
3
|
+
+ 1 major enhancement:
|
4
|
+
+ autotest has been rewritten to be much cleaner, now has a plugin system.
|
5
|
+
+ 5 minor enhancement:
|
6
|
+
+ test/rails adds helper tests, "stolen" from Geoff's work, (which was "stolen" from ryan's work. :P)
|
7
|
+
+ autotest turnaround is now faster.
|
8
|
+
+ Added more prune dirs to autotest.
|
9
|
+
+ test/rails rewinds IOs containing captured output. Added assert_empty.
|
10
|
+
+ Document that autotest doesn't run the db:test:prepare rake task when in Rails mode.
|
11
|
+
+ Added ruby_fork, but haven't fully plugged into autotest yet.
|
12
|
+
+ 7 bug fixes:
|
13
|
+
+ Add SIGINT handler to unit_diff to give a more graceful exit.
|
14
|
+
+ Don't strip <> from tempfiles, parse_diff does it for us.
|
15
|
+
+ Fixed autotest problems on windoze. Ugh.
|
16
|
+
+ Fixed broken pipe bug and newline bug in unit_diff.
|
17
|
+
+ Make request_method in ControllerTestCase a String.
|
18
|
+
+ multitest installs rubygems if tarball found in versions dir.
|
19
|
+
+ multitest only configures when makefile is missing. Rebuilds much faster now.
|
20
|
+
+ ruby_fork exits without backtrace and allows redirection of output.
|
21
|
+
|
1
22
|
*** 3.2.0 / 2006-04-10
|
2
23
|
|
3
24
|
+ 1 major enhancement:
|
data/Manifest.txt
CHANGED
@@ -6,48 +6,32 @@ Rakefile
|
|
6
6
|
bin/autotest
|
7
7
|
bin/multiruby
|
8
8
|
bin/rails_test_audit
|
9
|
+
bin/ruby_fork
|
10
|
+
bin/ruby_fork_client
|
9
11
|
bin/unit_diff
|
10
12
|
bin/zentest
|
11
13
|
example.txt
|
12
14
|
example1.rb
|
13
15
|
example2.rb
|
16
|
+
example_dot_autotest.rb
|
14
17
|
lib/autotest.rb
|
15
18
|
lib/rails_autotest.rb
|
19
|
+
lib/ruby_fork.rb
|
16
20
|
lib/test/rails.rb
|
17
21
|
lib/test/rails/controller_test_case.rb
|
18
22
|
lib/test/rails/functional_test_case.rb
|
23
|
+
lib/test/rails/helper_test_case.rb
|
19
24
|
lib/test/rails/ivar_proxy.rb
|
25
|
+
lib/test/rails/pp_html_document.rb
|
20
26
|
lib/test/rails/rake_tasks.rb
|
21
27
|
lib/test/rails/test_case.rb
|
22
28
|
lib/test/rails/view_test_case.rb
|
23
29
|
lib/test/zentest_assertions.rb
|
24
30
|
lib/unit_diff.rb
|
25
31
|
lib/zentest.rb
|
26
|
-
test/data/normal/lib/.#photo.rb
|
27
|
-
test/data/normal/lib/blah.rb
|
28
|
-
test/data/normal/lib/photo.rb
|
29
|
-
test/data/normal/test/#test_photo.rb#
|
30
|
-
test/data/normal/test/test_camelcase.rb
|
31
|
-
test/data/normal/test/test_photo.rb
|
32
|
-
test/data/normal/test/test_route.rb
|
33
|
-
test/data/normal/test/test_user.rb
|
34
|
-
test/data/rails/app/controllers/admin/theme_controller.rb
|
35
|
-
test/data/rails/app/controllers/route_controller.rb
|
36
|
-
test/data/rails/app/models/flickr_photo.rb
|
37
|
-
test/data/rails/app/models/route.rb
|
38
|
-
test/data/rails/app/views/route/index.rhtml
|
39
|
-
test/data/rails/config/environment.rb
|
40
|
-
test/data/rails/config/routes.rb
|
41
|
-
test/data/rails/test/controllers/route_controller_test.rb
|
42
|
-
test/data/rails/test/fixtures/routes.yml
|
43
|
-
test/data/rails/test/functional/admin/themes_controller_test.rb
|
44
|
-
test/data/rails/test/functional/dummy_controller_test.rb
|
45
|
-
test/data/rails/test/functional/route_controller_test.rb
|
46
|
-
test/data/rails/test/unit/flickr_photo_test.rb
|
47
|
-
test/data/rails/test/unit/photo_test.rb
|
48
|
-
test/data/rails/test/unit/route_test.rb
|
49
|
-
test/data/rails/test/views/route_view_test.rb
|
50
32
|
test/test_autotest.rb
|
51
33
|
test/test_rails_autotest.rb
|
34
|
+
test/test_ruby_fork.rb
|
52
35
|
test/test_unit_diff.rb
|
53
36
|
test/test_zentest.rb
|
37
|
+
test/test_zentest_assertions.rb
|
data/Rakefile
CHANGED
@@ -4,6 +4,7 @@ require 'rake'
|
|
4
4
|
require 'rake/testtask'
|
5
5
|
require 'rake/rdoctask'
|
6
6
|
require 'rake/gempackagetask'
|
7
|
+
require 'rake/contrib/sshpublisher'
|
7
8
|
require 'rbconfig'
|
8
9
|
|
9
10
|
require './lib/zentest.rb'
|
@@ -25,8 +26,12 @@ spec = Gem::Specification.new do |s|
|
|
25
26
|
s.instance_variable_set "@description", paragraphs[3..10].join("\n\n")
|
26
27
|
s.instance_variable_set "@summary", paragraphs[12]
|
27
28
|
|
29
|
+
s.homepage = "http://www.zenspider.com/ZSS/Products/ZenTest/"
|
30
|
+
s.rubyforge_project = "zentest"
|
31
|
+
s.has_rdoc = true
|
32
|
+
|
28
33
|
if $DEBUG then
|
29
|
-
puts "
|
34
|
+
puts "#{s.name} #{s.version}"
|
30
35
|
puts
|
31
36
|
puts s.executables.sort.inspect
|
32
37
|
puts
|
@@ -36,10 +41,6 @@ spec = Gem::Specification.new do |s|
|
|
36
41
|
puts "** description:"
|
37
42
|
puts s.description
|
38
43
|
end
|
39
|
-
|
40
|
-
s.homepage = "http://www.zenspider.com/ZSS/Products/ZenTest/"
|
41
|
-
s.rubyforge_project = "zentest"
|
42
|
-
s.has_rdoc = true
|
43
44
|
end
|
44
45
|
|
45
46
|
desc 'Build Gem'
|
@@ -69,6 +70,16 @@ Rake::RDocTask.new :rdoc do |rd|
|
|
69
70
|
rd.options << '-d' if `which dot` =~ /\/dot/ unless RUBY_PLATFORM =~ /win32/
|
70
71
|
rd.options << '-t ZenTest RDoc'
|
71
72
|
end
|
73
|
+
|
74
|
+
desc 'Upload RDoc to RubyForge'
|
75
|
+
task :upload => :rdoc do
|
76
|
+
|
77
|
+
user = "#{ENV['USER']}@rubyforge.org"
|
78
|
+
project = '/var/www/gforge-projects/zentest'
|
79
|
+
local_dir = 'doc'
|
80
|
+
pub = Rake::SshDirPublisher.new user, project, local_dir
|
81
|
+
pub.upload
|
82
|
+
end
|
72
83
|
|
73
84
|
$prefix = ENV['PREFIX'] || Config::CONFIG['prefix']
|
74
85
|
$bin = File.join($prefix, 'bin')
|
@@ -76,9 +87,8 @@ $lib = Config::CONFIG['sitelibdir']
|
|
76
87
|
$bins = spec.executables
|
77
88
|
$libs = spec.files.grep(/^lib\//).map { |f| f.sub(/^lib\//, '') }.sort
|
78
89
|
|
79
|
-
task :
|
80
|
-
|
81
|
-
p $libs
|
90
|
+
task :autotest do
|
91
|
+
ruby "-Ilib ./bin/autotest"
|
82
92
|
end
|
83
93
|
|
84
94
|
task :install do
|
@@ -114,5 +124,19 @@ task :clean => [ :clobber_rdoc, :clobber_package ] do
|
|
114
124
|
rm_f Dir["**/*~"]
|
115
125
|
end
|
116
126
|
|
127
|
+
task :help do
|
128
|
+
Rake.application.options.show_task_pattern = //
|
129
|
+
Rake.application.display_tasks_and_comments
|
130
|
+
end
|
131
|
+
|
132
|
+
task :sort do
|
133
|
+
begin
|
134
|
+
sh 'for f in lib/*.rb; do echo $f; grep "^ *def " $f | grep -v sort=skip > x; sort x > y; echo $f; echo; diff x y; done'
|
135
|
+
sh 'for f in test/test_*.rb; do echo $f; grep "^ *def.test_" $f > x; sort x > y; echo $f; echo; diff x y; done'
|
136
|
+
ensure
|
137
|
+
sh 'rm x y'
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
117
141
|
# vim:syntax=ruby
|
118
142
|
|
data/bin/autotest
CHANGED
@@ -17,7 +17,10 @@ if $h or $help then
|
|
17
17
|
help << nil
|
18
18
|
help << "\t-rails\t\tForce rails mode."
|
19
19
|
help << "\t\t\tRails will be automatically detected by the presence of"
|
20
|
-
help << "\t\t\tconfig/environment.rb"
|
20
|
+
help << "\t\t\tconfig/environment.rb. Use this if you don't have one."
|
21
|
+
help << nil
|
22
|
+
help << "\t\t\tYou may need to run 'rake db:test:prepare' before"
|
23
|
+
help << "\t\t\tstarting autotest on a rails project."
|
21
24
|
help << nil
|
22
25
|
help << "\t-vcs=NAME\tVersion control system to update."
|
23
26
|
help << "\t\t\tAutotest will automatically update every vcstime"
|
data/bin/multiruby
CHANGED
@@ -36,8 +36,13 @@ Dir.chdir root_dir do
|
|
36
36
|
end
|
37
37
|
end
|
38
38
|
|
39
|
+
tarballs = Dir["versions/rubygems*.tgz"]
|
40
|
+
raise "You should delete all but one rubygem tarball" if tarballs.size > 1
|
41
|
+
rubygem_tarball = File.expand_path tarballs.last
|
42
|
+
|
39
43
|
Dir.chdir "build" do
|
40
44
|
Dir["../versions/ruby*.tar.gz"].each do |tarball|
|
45
|
+
next if tarball =~ /rubygems/
|
41
46
|
build_dir = File.basename tarball, ".tar.gz"
|
42
47
|
version = build_dir.sub(/^ruby-?/, '')
|
43
48
|
versions << version
|
@@ -46,15 +51,26 @@ Dir.chdir root_dir do
|
|
46
51
|
unless test ?d, build_dir then
|
47
52
|
puts "creating #{inst_dir}"
|
48
53
|
Dir.mkdir inst_dir
|
49
|
-
|
50
|
-
system "tar zxf #{tarball}"
|
54
|
+
run "tar zxf #{tarball}"
|
51
55
|
end
|
52
56
|
Dir.chdir build_dir do
|
57
|
+
puts "building and installing #{version}"
|
53
58
|
run "autoconf" unless test ?f, "configure"
|
54
59
|
FileUtils.rm_r "ext/readline" if test ?d, "ext/readline"
|
55
|
-
run "./configure --prefix #{inst_dir} &> log.configure"
|
56
|
-
run "make -j4 &> log.build"
|
60
|
+
run "./configure --prefix #{inst_dir} &> log.configure" unless test ?f, "Makefile"
|
61
|
+
run "nice make -j4 &> log.build"
|
57
62
|
run "make install &> log.install"
|
63
|
+
build_dir = Dir.pwd
|
64
|
+
|
65
|
+
if rubygem_tarball then
|
66
|
+
next if version.gsub(/\./, '') =~ /^19/ # doesn't seem to work w/ ruby 1.9, filed bug
|
67
|
+
rubygems = File.basename rubygem_tarball, ".tgz"
|
68
|
+
run "tar zxf #{rubygem_tarball}" unless test ?d, rubygems
|
69
|
+
|
70
|
+
Dir.chdir rubygems do
|
71
|
+
run "../ruby ./setup.rb &> ../log.rubygems"
|
72
|
+
end
|
73
|
+
end
|
58
74
|
end
|
59
75
|
end
|
60
76
|
end
|
@@ -78,5 +94,3 @@ puts
|
|
78
94
|
puts "TOTAL RESULT = #{result} failures"
|
79
95
|
|
80
96
|
exit result
|
81
|
-
|
82
|
-
|
data/bin/ruby_fork
ADDED
@@ -0,0 +1,148 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
|
3
|
+
module AutoGrowl
|
4
|
+
def self.growl title, msg, pri=0
|
5
|
+
system "growlnotify -n autotest --image /Applications/Mail.app/Contents/Resources/Caution.tiff -p #{pri} -m #{msg.inspect} #{title}"
|
6
|
+
end
|
7
|
+
|
8
|
+
Autotest.add_hook :run do |at|
|
9
|
+
growl "Run", "Run" unless $TESTING
|
10
|
+
end
|
11
|
+
|
12
|
+
Autotest.add_hook :red do |at|
|
13
|
+
growl "Tests Failed", "#{at.files_to_test.size} tests failed", 2
|
14
|
+
end
|
15
|
+
|
16
|
+
Autotest.add_hook :green do |at|
|
17
|
+
growl "Tests Passed", "All tests passed", -2 if at.tainted
|
18
|
+
end
|
19
|
+
|
20
|
+
Autotest.add_hook :init do |at|
|
21
|
+
growl "autotest", "autotest was started" unless $TESTING
|
22
|
+
end
|
23
|
+
|
24
|
+
Autotest.add_hook :interrupt do |at|
|
25
|
+
growl "autotest", "autotest was reset" unless $TESTING
|
26
|
+
end
|
27
|
+
|
28
|
+
Autotest.add_hook :quit do |at|
|
29
|
+
growl "autotest", "autotest is exiting" unless $TESTING
|
30
|
+
end
|
31
|
+
|
32
|
+
Autotest.add_hook :all do |at|_hook
|
33
|
+
growl "autotest", "Tests have fully passed", -2 unless $TESTING
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
module HtmlConsole
|
38
|
+
MAX = 30
|
39
|
+
STATUS = {}
|
40
|
+
PATH = File.expand_path("~/Sites/autotest.html")
|
41
|
+
|
42
|
+
def self.update
|
43
|
+
STATUS.delete STATUS.keys.sort.last if STATUS.size > MAX
|
44
|
+
File.open(PATH, "w") do |f|
|
45
|
+
f.puts "<title>Autotest Status</title>"
|
46
|
+
STATUS.sort.reverse.each do |t,s|
|
47
|
+
if s > 0 then
|
48
|
+
f.puts "<p style=\"color:red\">#{t}: #{s}"
|
49
|
+
else
|
50
|
+
f.puts "<p style=\"color:green\">#{t}: #{s}"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
Autotest.add_hook :red do |at|
|
57
|
+
STATUS[Time.now] = at.files_to_test.size
|
58
|
+
update
|
59
|
+
end
|
60
|
+
|
61
|
+
Autotest.add_hook :green do |at|
|
62
|
+
STATUS[Time.now] = 0
|
63
|
+
update
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
require 'osx/cocoa'
|
68
|
+
include Math
|
69
|
+
include OSX
|
70
|
+
|
71
|
+
OSX::NSBundle.bundleWithPath(File.expand_path("~/Library/Frameworks/Aquaterm.framework")).load
|
72
|
+
OSX.ns_import :AQTAdapter
|
73
|
+
|
74
|
+
class StatusBoard
|
75
|
+
BLACK = 0
|
76
|
+
WHITE = 1
|
77
|
+
RED = 2
|
78
|
+
GREEN = 3
|
79
|
+
GRAY = 4
|
80
|
+
|
81
|
+
def initialize
|
82
|
+
@past = []
|
83
|
+
|
84
|
+
@adapter = AQTAdapter.alloc.init
|
85
|
+
@adapter.openPlotWithIndex 1
|
86
|
+
@adapter.setPlotSize([122,122])
|
87
|
+
@adapter.setPlotTitle("Autotest Status")
|
88
|
+
|
89
|
+
@adapter.setColormapEntry_red_green_blue(0, 0.0, 0.0, 0.0) # black
|
90
|
+
@adapter.setColormapEntry_red_green_blue(1, 1.0, 1.0, 1.0) # white
|
91
|
+
@adapter.setColormapEntry_red_green_blue(2, 1.0, 0.0, 0.0) # red
|
92
|
+
@adapter.setColormapEntry_red_green_blue(3, 0.0, 1.0, 0.0) # green
|
93
|
+
@adapter.setColormapEntry_red_green_blue(4, 0.7, 0.7, 0.7) # gray
|
94
|
+
|
95
|
+
draw
|
96
|
+
end
|
97
|
+
|
98
|
+
def draw
|
99
|
+
# @past = @past[10..-1] if @past.size >= 100
|
100
|
+
@past.shift if @past.size > 100
|
101
|
+
|
102
|
+
@adapter.takeColorFromColormapEntry(@past.last ? GREEN : RED)
|
103
|
+
@adapter.addFilledRect([0, 0, 122, 122])
|
104
|
+
|
105
|
+
@adapter.takeColorFromColormapEntry(BLACK)
|
106
|
+
@adapter.addFilledRect([10, 10, 102, 102])
|
107
|
+
|
108
|
+
@adapter.takeColorFromColormapEntry(GRAY)
|
109
|
+
@adapter.addFilledRect([11, 11, 100, 100])
|
110
|
+
|
111
|
+
@adapter.takeColorFromColormapEntry(0)
|
112
|
+
|
113
|
+
@past.each_with_index do |passed,i|
|
114
|
+
x = i % 10
|
115
|
+
y = i / 10
|
116
|
+
|
117
|
+
@adapter.takeColorFromColormapEntry(passed ? GREEN : RED)
|
118
|
+
@adapter.addFilledRect([x*10+11, y*10+11, 10, 10])
|
119
|
+
end
|
120
|
+
@adapter.renderPlot
|
121
|
+
end
|
122
|
+
|
123
|
+
def pass
|
124
|
+
@past.push true
|
125
|
+
draw
|
126
|
+
end
|
127
|
+
|
128
|
+
def fail
|
129
|
+
@past.push false
|
130
|
+
draw
|
131
|
+
end
|
132
|
+
|
133
|
+
def close
|
134
|
+
@adapter.closePlot
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
unless $TESTING then
|
139
|
+
board = StatusBoard.new
|
140
|
+
|
141
|
+
Autotest.add_hook :red do |at|
|
142
|
+
board.fail unless $TESTING
|
143
|
+
end
|
144
|
+
|
145
|
+
Autotest.add_hook :green do |at|
|
146
|
+
board.pass unless $TESTING
|
147
|
+
end
|
148
|
+
end
|
data/lib/autotest.rb
CHANGED
@@ -1,342 +1,289 @@
|
|
1
|
-
$TESTING = defined? $TESTING
|
2
|
-
|
3
1
|
require 'find'
|
4
2
|
require 'rbconfig'
|
5
3
|
|
4
|
+
$TESTING = false unless defined? $TESTING
|
5
|
+
|
6
6
|
##
|
7
|
-
# Autotest continuously
|
7
|
+
# Autotest continuously scans the files in your project for changes
|
8
|
+
# and runs the appropriate tests. Test failures are run until they
|
9
|
+
# have all passed. Then the full test suite is run to ensure that
|
10
|
+
# nothing else was inadvertantly broken.
|
11
|
+
#
|
12
|
+
# If you want Autotest to start over from the top, hit ^C once. If
|
13
|
+
# you want Autotest to quit, hit ^C twice.
|
14
|
+
#
|
15
|
+
# Rails:
|
16
|
+
#
|
17
|
+
# The autotest command will automatically discover a Rails directory
|
18
|
+
# by looking for config/environment.rb. When Rails is discovered,
|
19
|
+
# autotest uses RailsAutotest to perform file mappings and other work.
|
20
|
+
# See RailsAutotest for details.
|
8
21
|
#
|
9
|
-
#
|
10
|
-
# figures out the appropriate tests to run and runs them. If a test fails
|
11
|
-
# Autotest will run just that test until you get it to pass.
|
22
|
+
# Plugins:
|
12
23
|
#
|
13
|
-
#
|
14
|
-
#
|
24
|
+
# Plugins are available by creating a .autotest file either in your
|
25
|
+
# project root or in your home directory. You can then write event
|
26
|
+
# handlers in the form of:
|
15
27
|
#
|
16
|
-
#
|
17
|
-
#
|
28
|
+
# Autotest.add_hook hook_name { |autotest| ... }
|
29
|
+
#
|
30
|
+
# The available hooks are: run, interrupt, quit, ran_command, red,
|
31
|
+
# green, all_good, and reset.
|
32
|
+
#
|
33
|
+
# See example_dot_autotest.rb for more details.
|
34
|
+
#
|
35
|
+
# Naming:
|
36
|
+
#
|
37
|
+
# Autotest uses a simple naming scheme to figure out how to map
|
38
|
+
# implementation files to test files following the Test::Unit naming
|
39
|
+
# scheme.
|
18
40
|
#
|
19
41
|
# * Test files must be stored in test/
|
20
42
|
# * Test files names must start with test_
|
21
|
-
# * Test
|
43
|
+
# * Test class names must start with Test
|
22
44
|
# * Implementation files must be stored in lib/
|
23
45
|
# * Implementation files must match up with a test file named
|
24
46
|
# test_.*implementation.rb
|
25
|
-
|
26
|
-
#
|
47
|
+
#
|
48
|
+
# Strategy:
|
27
49
|
#
|
28
50
|
# 1) find all files and associate them from impl <-> test
|
29
51
|
# 2) run all tests
|
30
52
|
# 3) scan for failures
|
31
53
|
# 4) detect changes in ANY (ruby?) file, rerun all failures + changed files
|
32
|
-
# NOTE: this runs in a loop, loop handling should be improved slightly to
|
33
|
-
# have less crap (ruby command, failure count).
|
34
54
|
# 5) until 0 defects, goto 3
|
35
55
|
# 6) when 0 defects, goto 2
|
36
56
|
|
37
57
|
class Autotest
|
38
|
-
|
58
|
+
|
59
|
+
HOOKS = Hash.new { |h,k| h[k] = [] }
|
60
|
+
|
39
61
|
def self.run
|
40
62
|
new.run
|
41
63
|
end
|
42
64
|
|
43
|
-
|
44
|
-
# Creates a new Autotest. If @exceptions is set, updated_files will use it
|
45
|
-
# to reject filenames.
|
65
|
+
attr_accessor :exceptions, :files, :files_to_test, :interrupted, :last_mtime, :libs, :output, :tainted
|
46
66
|
|
47
67
|
def initialize
|
48
|
-
@interrupt = false
|
49
68
|
@files = Hash.new Time.at(0)
|
50
|
-
@
|
69
|
+
@files_to_test = Hash.new { |h,k| h[k] = [] }
|
70
|
+
@exceptions = false
|
71
|
+
@libs = '.:lib:test'
|
72
|
+
@output = $stderr
|
73
|
+
@sleep = 2
|
51
74
|
end
|
52
75
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
76
|
+
def run
|
77
|
+
hook :run
|
78
|
+
reset
|
79
|
+
add_sigint_handler
|
80
|
+
|
81
|
+
loop do # ^c handler
|
82
|
+
begin
|
83
|
+
get_to_green
|
84
|
+
rerun_all_tests if @tainted
|
85
|
+
wait_for_changes
|
86
|
+
rescue Interrupt
|
87
|
+
if @wants_to_quit then
|
88
|
+
break
|
89
|
+
else
|
90
|
+
reset
|
91
|
+
hook :interrupt
|
92
|
+
end
|
93
|
+
end
|
63
94
|
end
|
95
|
+
hook :quit
|
96
|
+
end
|
64
97
|
|
65
|
-
|
66
|
-
|
98
|
+
def get_to_green
|
99
|
+
until all_good do
|
100
|
+
run_tests
|
101
|
+
wait_for_changes unless all_good
|
67
102
|
end
|
68
103
|
end
|
69
104
|
|
105
|
+
def run_tests
|
106
|
+
find_files_to_test # failed + changed/affected
|
107
|
+
cmd = make_test_cmd @files_to_test
|
70
108
|
|
71
|
-
|
72
|
-
# Selects test files to run that match failures in +failed_file+ based on
|
73
|
-
# +updated_files+ and +tests+.
|
74
|
-
#
|
75
|
-
# Only test files matching +failed_file+ will be returned so the test
|
76
|
-
# runner's -n flag will correctly match the failed tests.
|
77
|
-
#--
|
78
|
-
# failed_test_files must never check for updated files, retest_failed reuses
|
79
|
-
# +updated_files+.
|
80
|
-
|
81
|
-
def failed_test_files(failed_file, tests, updated_files)
|
82
|
-
return [] if updated_files.empty?
|
83
|
-
|
84
|
-
updated_tests = updated_files.select { |f| f =~ /^test/ }
|
109
|
+
puts cmd
|
85
110
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
files = (updated_files + tests).uniq
|
90
|
-
tests_to_filter = map_file_names(files).flatten.uniq
|
91
|
-
end
|
111
|
+
@results = `#{cmd}`
|
112
|
+
hook :ran_command
|
113
|
+
puts @results
|
92
114
|
|
93
|
-
|
115
|
+
handle_results(@results)
|
94
116
|
end
|
95
117
|
|
96
|
-
|
97
|
-
#
|
118
|
+
############################################################
|
119
|
+
# Utility Methods, not essential to reading of logic
|
98
120
|
|
99
|
-
def
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
out << "# #{test}:"
|
110
|
-
out << "# #{filter.join "\n# "}"
|
121
|
+
def add_sigint_handler
|
122
|
+
trap 'INT' do
|
123
|
+
if @interrupted then
|
124
|
+
@wants_to_quit = true
|
125
|
+
else
|
126
|
+
puts "Interrupt a second time to quit"
|
127
|
+
@interrupted = true
|
128
|
+
sleep 1.5
|
129
|
+
raise Interrupt # let the run loop catch it
|
130
|
+
end
|
111
131
|
end
|
112
|
-
|
113
|
-
return out.join("\n")
|
114
132
|
end
|
115
133
|
|
116
|
-
|
117
|
-
|
118
|
-
|
134
|
+
def all_good
|
135
|
+
@files_to_test.empty?
|
136
|
+
end
|
119
137
|
|
120
|
-
def
|
121
|
-
|
138
|
+
def consolidate_failures(failed)
|
139
|
+
filters = Hash.new { |h,k| h[k] = [] }
|
122
140
|
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
when %r%^test/test_% then
|
132
|
-
tests << filename # always run tests
|
133
|
-
when %r%^(doc|pkg)/% then
|
134
|
-
# ignore
|
141
|
+
failed.each do |method, klass|
|
142
|
+
failed_file_name = klass.gsub(/(.)([A-Z])/, '\1_?\2')
|
143
|
+
failed_files = @files.keys.grep(/#{failed_file_name}/i)
|
144
|
+
case failed_files.size
|
145
|
+
when 0 then
|
146
|
+
@output.puts "Unable to map class #{klass} to a file" # FIX for testing
|
147
|
+
when 1 then
|
148
|
+
filters[failed_files.last] << method
|
135
149
|
else
|
136
|
-
|
150
|
+
@output.puts "multiple files matched class #{klass} #{failed_files.inspect}."
|
151
|
+
# nothing yet
|
137
152
|
end
|
138
153
|
end
|
139
154
|
|
140
|
-
return
|
155
|
+
return filters
|
141
156
|
end
|
142
157
|
|
143
|
-
|
144
|
-
|
158
|
+
def find_files
|
159
|
+
result = {}
|
160
|
+
Find.find '.' do |f|
|
161
|
+
Find.prune if @exceptions and f =~ @exceptions and test ?d, f
|
162
|
+
Find.prune if f =~ /(\.(svn|hg)|CVS|tmp|public|doc|pkg)$/ # prune dirs
|
145
163
|
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
end
|
164
|
+
next if test ?d, f
|
165
|
+
next if f =~ /(swp|~|rej|orig)$/ # temporary/patch files
|
166
|
+
next if f =~ /\/\.?#/ # Emacs autosave/cvs merge files
|
150
167
|
|
151
|
-
|
152
|
-
# Retests failed tests.
|
153
|
-
#--
|
154
|
-
# Runs through each failure and runs tests matching the failure. If an
|
155
|
-
# implementation file was updated all failed tests should be run.
|
156
|
-
#
|
157
|
-
# TODO collapse multiple failures in the same file (in test) and use | in
|
158
|
-
# the filter.
|
159
|
-
|
160
|
-
def retest_failed(failed, tests)
|
161
|
-
puts "# Waiting for changes"
|
162
|
-
|
163
|
-
# -t and -n includes all tests that match either filter, not tests that
|
164
|
-
# match both filters, so figure out which TestCase to run from the filename,
|
165
|
-
# and use -n on that.
|
166
|
-
until failed.empty? do
|
167
|
-
sleep 5 unless $TESTING
|
168
|
-
|
169
|
-
updated = updated_files
|
170
|
-
|
171
|
-
# REFACTOR
|
172
|
-
failed.map! do |filter, failed_test|
|
173
|
-
failed_files = failed_test_files failed_test, tests, updated
|
174
|
-
break [filter, failed_test] if failed_files.empty?
|
175
|
-
|
176
|
-
puts "# Rerunning failures: #{failed_files.join ' '}"
|
177
|
-
|
178
|
-
test_filter = " -n #{filter}" unless filter == "'/^(default_test)/'"
|
179
|
-
cmd = "#{ruby} -Ilib:test #{failed_files.join ' '}#{test_filter} | unit_diff -u"
|
180
|
-
|
181
|
-
puts "+ #{cmd}"
|
182
|
-
result = `#{cmd}`
|
183
|
-
puts result
|
184
|
-
status = result.split($/).last
|
185
|
-
rerun = status =~ / 0 failures, 0 errors/ ? nil : [filter, failed_test]
|
186
|
-
puts "# Waiting for changes" if rerun
|
187
|
-
rerun # needed for map!
|
188
|
-
end
|
168
|
+
filename = f.sub(/^\.\//, '')
|
189
169
|
|
190
|
-
|
170
|
+
result[filename] = File.stat(filename).mtime
|
191
171
|
end
|
172
|
+
return result
|
192
173
|
end
|
193
174
|
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
def ruby
|
198
|
-
return File.join(Config::CONFIG['bindir'],
|
199
|
-
Config::CONFIG['ruby_install_name'])
|
200
|
-
end
|
175
|
+
def find_files_to_test(files=find_files)
|
176
|
+
updated = []
|
201
177
|
|
202
|
-
|
203
|
-
|
178
|
+
# TODO: keep an mtime at app level and drop the files hash
|
179
|
+
files.each do |filename, mtime|
|
180
|
+
next if @files[filename] >= mtime
|
204
181
|
|
205
|
-
|
206
|
-
|
207
|
-
if @interrupt then
|
208
|
-
puts "# Ok, you really want to quit, doing so"
|
209
|
-
exit
|
182
|
+
tests_for_file(filename).each do |f|
|
183
|
+
@files_to_test[f] # creates key with default value
|
210
184
|
end
|
211
|
-
# STDERR.puts "\t#{caller.join "\n\t"}"
|
212
|
-
puts "# hit ^C again to quit"
|
213
|
-
sleep 1.5 # give them enough time to hit ^C again
|
214
|
-
@interrupt = true # if they hit ^C again,
|
215
|
-
raise Interrupt # let the run loop catch it
|
216
|
-
end
|
217
|
-
|
218
|
-
begin
|
219
|
-
last_update = Time.at 0
|
220
|
-
|
221
|
-
loop do
|
222
|
-
if $vcs and Time.now > last_update + $vcstime then
|
223
|
-
last_update = Time.now
|
224
|
-
case $vcs
|
225
|
-
when 'cvs' then
|
226
|
-
system 'cvs up'
|
227
|
-
when 'p4' then
|
228
|
-
system 'p4 sync'
|
229
|
-
when 'svn' then
|
230
|
-
system 'svn up'
|
231
|
-
else
|
232
|
-
puts "# Sorry, I don't know what version control system \"#{$vcs}\" is"
|
233
|
-
end
|
234
|
-
end
|
235
185
|
|
236
|
-
|
237
|
-
test files unless files.empty?
|
238
|
-
sleep 5
|
239
|
-
end
|
240
|
-
rescue Interrupt
|
241
|
-
@interrupt = false # they didn't hit ^C in time
|
242
|
-
puts "# ok, restarting from the top"
|
243
|
-
@files.clear
|
244
|
-
retry
|
186
|
+
@files[filename] = mtime
|
245
187
|
end
|
246
|
-
end
|
247
|
-
|
248
|
-
##
|
249
|
-
# Runs tests for files in +updated+. Implementation files are looked up
|
250
|
-
# with map_file_names.
|
251
|
-
#
|
252
|
-
# Returns true if any of the tests ever failed.
|
253
|
-
|
254
|
-
def test(updated)
|
255
|
-
ever_failed = false
|
256
|
-
|
257
|
-
map_file_names(updated).each do |tests|
|
258
|
-
next if tests.empty?
|
259
|
-
puts '# Testing updated files'
|
260
|
-
cmd = "#{ruby} -Ilib:test -e '#{tests.inspect}.each { |f| load f }' | unit_diff -u"
|
261
|
-
puts "+ #{cmd}"
|
262
|
-
results = `#{cmd}`
|
263
|
-
puts results
|
264
|
-
|
265
|
-
if results =~ / 0 failures, 0 errors\Z/ then
|
266
|
-
puts '# Passed'
|
267
|
-
next
|
268
|
-
end
|
269
188
|
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
if failed.empty? then
|
275
|
-
puts '# Test::Unit exited without a parseable failure or error message.'
|
276
|
-
puts '# You probably have a syntax error in your code.'
|
277
|
-
puts '# I\'ll retry in 10 seconds'
|
278
|
-
sleep 10
|
279
|
-
redo
|
280
|
-
end
|
189
|
+
previous = @last_mtime
|
190
|
+
@last_mtime = @files.values.sort.last
|
191
|
+
@last_mtime > previous
|
192
|
+
end
|
281
193
|
|
282
|
-
|
194
|
+
def handle_results(results)
|
195
|
+
failed = results.scan(/^\s+\d+\) (?:Failure|Error):\n(.*?)\((.*?)\)/)
|
196
|
+
@files_to_test = consolidate_failures failed
|
197
|
+
unless @files_to_test.empty? then
|
198
|
+
hook :red
|
199
|
+
else
|
200
|
+
hook :green
|
201
|
+
end unless $TESTING
|
202
|
+
@tainted = true unless @files_to_test.empty?
|
203
|
+
end
|
283
204
|
|
284
|
-
|
285
|
-
|
205
|
+
def make_test_cmd files_to_test
|
206
|
+
cmds = []
|
207
|
+
full, partial = files_to_test.partition { |k,v| v.empty? }
|
286
208
|
|
287
|
-
|
209
|
+
unless full.empty? then
|
210
|
+
classes = full.map {|k,v| k}.flatten.join(' ')
|
211
|
+
cmds << "#{ruby} -I#{@libs} -rtest/unit -e \"%w[#{classes}].each { |f| load f }\" | unit_diff -u"
|
288
212
|
end
|
289
213
|
|
290
|
-
|
291
|
-
|
292
|
-
else # We'll immediately test everything, so don't print this out.
|
293
|
-
puts '# All passed'
|
294
|
-
puts "# Waiting for changes"
|
214
|
+
partial.each do |klass, methods|
|
215
|
+
cmds << "#{ruby} -I#{@libs} #{klass} -n \"/^(#{Regexp.union(*methods).source})$/\" | unit_diff -u"
|
295
216
|
end
|
296
217
|
|
297
|
-
return
|
218
|
+
return cmds.join('; ')
|
298
219
|
end
|
299
220
|
|
300
|
-
|
301
|
-
|
302
|
-
|
221
|
+
def rerun_all_tests
|
222
|
+
reset
|
223
|
+
run_tests
|
224
|
+
hook :all_good if all_good
|
225
|
+
end
|
303
226
|
|
304
|
-
def
|
305
|
-
|
306
|
-
|
307
|
-
@
|
308
|
-
|
227
|
+
def reset
|
228
|
+
@interrupted = @wants_to_quit = false
|
229
|
+
@files.clear
|
230
|
+
@files_to_test.clear
|
231
|
+
@last_mtime = Time.at(0)
|
232
|
+
find_files_to_test # failed + changed/affected
|
233
|
+
@tainted = false
|
234
|
+
hook :reset
|
309
235
|
end
|
236
|
+
|
237
|
+
def ruby
|
238
|
+
ruby = File.join(Config::CONFIG['bindir'],
|
239
|
+
Config::CONFIG['ruby_install_name'])
|
310
240
|
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
# initialize.
|
241
|
+
unless File::ALT_SEPARATOR.nil? then
|
242
|
+
ruby.gsub! File::SEPARATOR, File::ALT_SEPARATOR
|
243
|
+
end
|
315
244
|
|
316
|
-
|
317
|
-
|
245
|
+
return ruby
|
246
|
+
end
|
318
247
|
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
248
|
+
def tests_for_file(filename)
|
249
|
+
case filename
|
250
|
+
when /^lib\/.*\.rb$/ then
|
251
|
+
impl = File.basename(filename).gsub '_', '_?'
|
252
|
+
@files.keys.select do |k|
|
253
|
+
k =~ %r%^test/.*#{impl}$%
|
325
254
|
end
|
255
|
+
when /^test\/test_.*rb$/ then
|
256
|
+
[filename]
|
257
|
+
else
|
258
|
+
@output.puts "Dunno! #{filename}" if $TESTING
|
259
|
+
[]
|
260
|
+
end
|
261
|
+
end
|
326
262
|
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
263
|
+
def wait_for_changes
|
264
|
+
begin
|
265
|
+
sleep @sleep
|
266
|
+
end until find_files_to_test
|
267
|
+
end
|
332
268
|
|
333
|
-
|
269
|
+
############################################################
|
270
|
+
# Hooks:
|
334
271
|
|
335
|
-
|
272
|
+
def hook(name)
|
273
|
+
HOOKS[name].each do |plugin|
|
274
|
+
plugin[self]
|
336
275
|
end
|
337
|
-
|
338
|
-
return updated
|
339
276
|
end
|
340
277
|
|
278
|
+
def self.add_hook(name, &block)
|
279
|
+
HOOKS[name] << block
|
280
|
+
end
|
341
281
|
end
|
342
282
|
|
283
|
+
if test ?f, './.autotest' then
|
284
|
+
load './.autotest'
|
285
|
+
elsif test ?f, File.expand_path('~/.autotest') then
|
286
|
+
load File.expand_path('~/.autotest')
|
287
|
+
else
|
288
|
+
puts "couldn't find ./.autotest in #{Dir.pwd}"
|
289
|
+
end
|