ZenTest 3.2.0 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|