ryanbriones-ZenTest 3.11.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. data/History.txt +523 -0
  2. data/Manifest.txt +69 -0
  3. data/README.txt +110 -0
  4. data/Rakefile +68 -0
  5. data/articles/Article.css +721 -0
  6. data/articles/getting_started_with_autotest.html +532 -0
  7. data/articles/how_to_use_zentest.txt +393 -0
  8. data/bin/autotest +55 -0
  9. data/bin/multiruby +40 -0
  10. data/bin/multiruby_setup +68 -0
  11. data/bin/rails_test_audit +80 -0
  12. data/bin/unit_diff +38 -0
  13. data/bin/zentest +28 -0
  14. data/example.txt +42 -0
  15. data/example1.rb +7 -0
  16. data/example2.rb +15 -0
  17. data/example_dot_autotest.rb +45 -0
  18. data/lib/autotest.rb +654 -0
  19. data/lib/autotest/autoupdate.rb +26 -0
  20. data/lib/autotest/camping.rb +37 -0
  21. data/lib/autotest/cctray.rb +57 -0
  22. data/lib/autotest/discover.rb +6 -0
  23. data/lib/autotest/emacs.rb +35 -0
  24. data/lib/autotest/email_notify.rb +66 -0
  25. data/lib/autotest/fixtures.rb +12 -0
  26. data/lib/autotest/growl.rb +28 -0
  27. data/lib/autotest/heckle.rb +14 -0
  28. data/lib/autotest/html_report.rb +31 -0
  29. data/lib/autotest/jabber_notify.rb +111 -0
  30. data/lib/autotest/kdenotify.rb +14 -0
  31. data/lib/autotest/menu.rb +51 -0
  32. data/lib/autotest/migrate.rb +7 -0
  33. data/lib/autotest/notify.rb +34 -0
  34. data/lib/autotest/once.rb +9 -0
  35. data/lib/autotest/pretty.rb +83 -0
  36. data/lib/autotest/rails.rb +81 -0
  37. data/lib/autotest/rcov.rb +22 -0
  38. data/lib/autotest/redgreen.rb +21 -0
  39. data/lib/autotest/restart.rb +11 -0
  40. data/lib/autotest/screen.rb +73 -0
  41. data/lib/autotest/shame.rb +45 -0
  42. data/lib/autotest/snarl.rb +51 -0
  43. data/lib/autotest/timestamp.rb +9 -0
  44. data/lib/functional_test_matrix.rb +92 -0
  45. data/lib/multiruby.rb +401 -0
  46. data/lib/test/rails.rb +295 -0
  47. data/lib/test/rails/controller_test_case.rb +382 -0
  48. data/lib/test/rails/functional_test_case.rb +79 -0
  49. data/lib/test/rails/helper_test_case.rb +64 -0
  50. data/lib/test/rails/ivar_proxy.rb +31 -0
  51. data/lib/test/rails/pp_html_document.rb +74 -0
  52. data/lib/test/rails/rake_tasks.rb +50 -0
  53. data/lib/test/rails/render_tree.rb +93 -0
  54. data/lib/test/rails/test_case.rb +28 -0
  55. data/lib/test/rails/view_test_case.rb +597 -0
  56. data/lib/test/zentest_assertions.rb +134 -0
  57. data/lib/unit_diff.rb +259 -0
  58. data/lib/zentest.rb +566 -0
  59. data/lib/zentest_mapping.rb +99 -0
  60. data/test/test_autotest.rb +449 -0
  61. data/test/test_help.rb +36 -0
  62. data/test/test_rails_autotest.rb +229 -0
  63. data/test/test_rails_controller_test_case.rb +58 -0
  64. data/test/test_rails_helper_test_case.rb +48 -0
  65. data/test/test_rails_view_test_case.rb +275 -0
  66. data/test/test_unit_diff.rb +319 -0
  67. data/test/test_zentest.rb +566 -0
  68. data/test/test_zentest_assertions.rb +128 -0
  69. data/test/test_zentest_mapping.rb +222 -0
  70. metadata +151 -0
@@ -0,0 +1,68 @@
1
+ #!/usr/bin/env ruby -w
2
+
3
+ require 'multiruby'
4
+
5
+ ARGV << "help" if ARGV.empty?
6
+
7
+ Dir.chdir Multiruby.root_dir
8
+ Multiruby.setup_dirs(false)
9
+
10
+ ARGV.each do |spec|
11
+ case spec
12
+ when "-h", "help" then
13
+ Multiruby.help
14
+ exit 0
15
+ when "list" then
16
+ Multiruby.list
17
+ exit 0
18
+ when "build" then
19
+ # do nothing
20
+ when /rm:(.*)/ then
21
+ Multiruby.rm $1
22
+ when "clean" then
23
+ Multiruby.clean
24
+ exit 0
25
+ when "update" then
26
+ Multiruby.update
27
+ when "update:rubygems" then
28
+ Multiruby.update_rubygems
29
+ when "rubygems:merge" then
30
+ Multiruby.merge_rubygems
31
+ when "mri:svn:current" then
32
+ ARGV << "mri:svn:releases" << "mri:svn:branches"
33
+ when "mri:svn:releases" then
34
+ Multiruby::TAGS.each do |v|
35
+ latest = Multiruby.mri_latest_tag v
36
+ abort "Can't find tag #{v}" unless latest
37
+ ARGV << "mri:svn:tag:#{latest}:mri_rel_#{v}"
38
+ end
39
+ when "mri:svn:branches" then
40
+ Multiruby::BRANCHES.each do |v|
41
+ ARGV << "mri:svn:branch:#{v}"
42
+ end
43
+ when /mri:svn:branch:(.*)/ then
44
+ dir = ver = $1
45
+ ver = "branches/ruby_#{ver}" unless ver == "trunk"
46
+
47
+ Multiruby.svn_co "#{Multiruby::MRI_SVN}/#{ver}", "mri_#{dir}"
48
+ when /mri:svn:tag:(.*):(.*)/ then
49
+ ver, dir = $1, $2
50
+
51
+ Multiruby.svn_co "#{Multiruby::MRI_SVN}/tags/#{ver}", dir
52
+ when /mri:svn:tag:(.*)/ then
53
+ ver = $1
54
+
55
+ ARGV << "mri:svn:tag:#{ver}:#{ver}"
56
+ when /mri:tar:(.*)/ then
57
+ v = $1
58
+ Multiruby.fetch_tar v
59
+ when /rbx:ln:(.*)/ then
60
+ Multiruby.rbx_ln $1
61
+ when /rbx:git:current/ then
62
+ Multiruby.git_clone "#{Multiruby::RBX_GIT}/code", "rubinius"
63
+ else
64
+ warn "unknown spec #{spec}"
65
+ end
66
+ end
67
+
68
+ Multiruby.build_and_install
@@ -0,0 +1,80 @@
1
+ #!/usr/local/bin/ruby -w
2
+
3
+ # TODO: Probably has dup code with ZenTest.
4
+ # TODO: Map controller assert_assigns to view test ivar assignments.
5
+ # TODO: Make this a rake task, rake test:audit.
6
+
7
+ # test_cases[test_case][test][ivar] = value
8
+ def build_test_cases(type)
9
+ test_cases = Hash.new { |h,k|
10
+ h[k] = Hash.new { |h,k|
11
+ h[k] = Hash.new { |h,k|
12
+ h[k] = {} } } }
13
+
14
+ test_case = nil
15
+ test = nil
16
+
17
+ fixtures = Hash.new { |h,k| h[k] = [] }
18
+
19
+ Dir["test/#{type}/*rb"].each do |test|
20
+ File.open test do |fp|
21
+ fp.each_line do |line|
22
+ case line
23
+ when /^class (.*)(View|Controller)Test </
24
+ test_case = $1
25
+ when /^\s+def (test_\S+)/
26
+ test = $1
27
+ when /^\s+controller\[(.+?)\] = (.*)$/,
28
+ /^\s+assert_assigned (.*?), (.*)/
29
+ ivar = $1
30
+ value = $2
31
+ test_cases[test_case][test][ivar] = value
32
+ when /fixtures (.*)/ then
33
+ fixtures[test_case].push(*$1.split(', '))
34
+ end
35
+ end
36
+ end
37
+ end
38
+
39
+ return test_cases, fixtures
40
+ end
41
+
42
+ view_test_cases, view_fixtures = build_test_cases 'views'
43
+ controller_test_cases, controller_fixtures = build_test_cases 'controller'
44
+
45
+ out = []
46
+
47
+ view_test_cases.sort_by { |tc,_| tc }.each do |test_case, tests|
48
+ out << "require 'test/test_helper'"
49
+ out << nil
50
+ out << "class #{test_case}ControllerTest < Test::Rails::ControllerTestCase"
51
+ out << nil
52
+
53
+ fixtures = controller_fixtures[test_case] - view_fixtures[test_case]
54
+
55
+ unless fixtures.empty? then
56
+ fixtures.each do |fixture|
57
+ out << " fixtures #{fixture}"
58
+ end
59
+ out << nil
60
+ end
61
+
62
+ tests.sort_by { |t,_| t }.each do |test, ivars|
63
+ ivars = view_test_cases[test_case][test].keys -
64
+ controller_test_cases[test_case][test].keys
65
+
66
+ next if ivars.empty?
67
+
68
+ out << " def #{test}"
69
+ ivars.sort.each do |ivar|
70
+ value = view_test_cases[test_case][test][ivar]
71
+ out << " assert_assigned #{ivar}, #{value}"
72
+ end
73
+ out << " end"
74
+ out << nil
75
+ end
76
+ out << "end"
77
+ out << nil
78
+ end
79
+
80
+ puts out.join("\n")
data/bin/unit_diff ADDED
@@ -0,0 +1,38 @@
1
+ #!/usr/local/bin/ruby -ws
2
+ #
3
+ # unit_diff - a ruby unit test filter by Ryan Davis <ryand-ruby@zenspider.com>
4
+ #
5
+ # usage:
6
+ #
7
+ # test.rb | unit_diff [options]
8
+ # options:
9
+ # -b ignore whitespace differences
10
+ # -c contextual diff
11
+ # -h show usage
12
+ # -k keep temp diff files around
13
+ # -l prefix line numbers on the diffs
14
+ # -u unified diff
15
+ # -v display version
16
+
17
+ require 'unit_diff'
18
+ require 'zentest'
19
+
20
+ ############################################################
21
+
22
+ if defined? $v then
23
+ puts "#{File.basename $0} v. #{ZenTest::VERSION}"
24
+ exit 0
25
+ end
26
+
27
+ if defined? $h then
28
+ File.open($0) do |f|
29
+ begin; end until f.readline =~ /usage:/
30
+ f.readline
31
+ while line = f.readline and line.sub!(/^# ?/, '')
32
+ $stderr.puts line
33
+ end
34
+ end
35
+ exit 0
36
+ end
37
+
38
+ UnitDiff.unit_diff
data/bin/zentest ADDED
@@ -0,0 +1,28 @@
1
+ #!/usr/local/bin/ruby -swI .
2
+
3
+ require 'ZenTest'
4
+
5
+ $TESTING = true # for ZenWeb and any other testing infrastructure code
6
+
7
+ if defined? $v then
8
+ puts "#{File.basename $0} v#{ZenTest::VERSION}"
9
+ exit 0
10
+ end
11
+
12
+ if defined? $h then
13
+ puts "usage: #{File.basename $0} [-h -v] test-and-implementation-files..."
14
+ puts " -h display this information"
15
+ puts " -v display version information"
16
+ puts " -r Reverse mapping (ClassTest instead of TestClass)"
17
+ puts " -e (Rapid XP) eval the code generated instead of printing it"
18
+ exit 0
19
+ end
20
+
21
+ code = ZenTest.fix(*ARGV)
22
+ if defined? $e then
23
+ require 'test/unit'
24
+ eval code
25
+ else
26
+ print code
27
+ end
28
+
data/example.txt ADDED
@@ -0,0 +1,42 @@
1
+
2
+
3
+
4
+ What do we do to get people writing tests?
5
+ What do we do to get people writing tests first?
6
+
7
+ I didn't know it's name, but apparently it's the Lettuce Principal,
8
+ (see http://blog.toolshed.com/2003/03/the_lettuce_pri.html).
9
+
10
+ We NEED to make testing as easy as possible to get them testing.
11
+
12
+
13
+
14
+
15
+
16
+
17
+
18
+
19
+
20
+
21
+
22
+
23
+
24
+
25
+
26
+ Class Under Test Test Class
27
+ ######################################################################
28
+
29
+ module Something module TestSomething
30
+ class Thingy class TestThingy
31
+ def do_something def test_do_something_normal
32
+ # ... thingy = Thingy.new
33
+ end result = thingy.do_something
34
+ end assert(result.blahblah)
35
+ end end
36
+ def test_do_something_edgecase
37
+ thingy = Thingy.new
38
+ result = thingy.do_something
39
+ assert(result.blahblah)
40
+ end
41
+ end
42
+ end
data/example1.rb ADDED
@@ -0,0 +1,7 @@
1
+ module Something
2
+ class Thingy
3
+ def do_something
4
+ # ...
5
+ end
6
+ end
7
+ end
data/example2.rb ADDED
@@ -0,0 +1,15 @@
1
+ module TestSomething
2
+ class TestThingy
3
+ def test_do_something_normal
4
+ thingy = Thingy.new
5
+ result = thingy.do_something
6
+ assert(result.blahblah)
7
+ end
8
+ def test_do_something_edgecase
9
+ thingy = Thingy.new
10
+ result = thingy.do_something
11
+ assert(result.blahblah)
12
+ end
13
+ end
14
+ end
15
+
@@ -0,0 +1,45 @@
1
+ # -*- ruby -*-
2
+
3
+ # require 'autotest/autoupdate'
4
+ # require 'autotest/camping'
5
+ # require 'autotest/cctray'
6
+ # require 'autotest/emacs'
7
+ # require 'autotest/email_notify'
8
+ # require 'autotest/fixtures'
9
+ # require 'autotest/growl'
10
+ # require 'autotest/heckle'
11
+ # require 'autotest/html_report'
12
+ # require 'autotest/jabber_notify'
13
+ # require 'autotest/kdenotify'
14
+ # require 'autotest/menu'
15
+ # require 'autotest/migrate'
16
+ # require 'autotest/notify'
17
+ # require 'autotest/once'
18
+ # require 'autotest/pretty'
19
+ # require 'autotest/rcov'
20
+ # require 'autotest/redgreen'
21
+ # require 'autotest/restart'
22
+ # require 'autotest/screen'
23
+ # require 'autotest/shame'
24
+ # require 'autotest/snarl'
25
+ # require 'autotest/timestamp'
26
+
27
+ # Autotest::AutoUpdate.sleep_time = o
28
+ # Autotest::AutoUpdate.update_cmd = o
29
+ # Autotest::CCTray.project_name = name
30
+ # Autotest::Emacs.command = o
31
+ # Autotest::EmailNotify.smtp_settings = o
32
+ # Autotest::EmailNotify.from = o
33
+ # Autotest::EmailNotify.recipients = o
34
+ # Autotest::EmailNotify.use_svn = o
35
+ # Autotest::EmailNotify.report_every_run = o
36
+ # Autotest::Growl.growl title, msg, pri = 0, img = nil
37
+ # Autotest::JabberNotify.recipients = o
38
+ # Autotest::JabberNotify.account = o
39
+ # Autotest::JabberNotify.password = o
40
+ # Autotest::JabberNotify.use_svn = o
41
+ # Autotest::JabberNotify.report_every_run = o
42
+ # Autotest::RCov.command = o
43
+ # Autotest::RCov.pattern = o
44
+ # Autotest::Shame.chat_app = o
45
+ # Autotest::Snarl.snarl title, msg, ico = nil
data/lib/autotest.rb ADDED
@@ -0,0 +1,654 @@
1
+ require 'find'
2
+ require 'rbconfig'
3
+
4
+ $v ||= false
5
+ $TESTING = false unless defined? $TESTING
6
+
7
+ ##
8
+ # Autotest continuously scans the files in your project for changes
9
+ # and runs the appropriate tests. Test failures are run until they
10
+ # have all passed. Then the full test suite is run to ensure that
11
+ # nothing else was inadvertantly broken.
12
+ #
13
+ # If you want Autotest to start over from the top, hit ^C once. If
14
+ # you want Autotest to quit, hit ^C twice.
15
+ #
16
+ # Rails:
17
+ #
18
+ # The autotest command will automatically discover a Rails directory
19
+ # by looking for config/environment.rb. When Rails is discovered,
20
+ # autotest uses RailsAutotest to perform file mappings and other work.
21
+ # See RailsAutotest for details.
22
+ #
23
+ # Plugins:
24
+ #
25
+ # Plugins are available by creating a .autotest file either in your
26
+ # project root or in your home directory. You can then write event
27
+ # handlers in the form of:
28
+ #
29
+ # Autotest.add_hook hook_name { |autotest| ... }
30
+ #
31
+ # The available hooks are: initialize, run, run_command, ran_command,
32
+ # red, green, updated, all_good, reset, interrupt, and quit.
33
+ #
34
+ # See example_dot_autotest.rb for more details.
35
+ #
36
+ # If a hook returns a true value, it signals to autotest that the hook
37
+ # was handled and should not continue executing hooks.
38
+ #
39
+ # Naming:
40
+ #
41
+ # Autotest uses a simple naming scheme to figure out how to map
42
+ # implementation files to test files following the Test::Unit naming
43
+ # scheme.
44
+ #
45
+ # * Test files must be stored in test/
46
+ # * Test files names must start with test_
47
+ # * Test class names must start with Test
48
+ # * Implementation files must be stored in lib/
49
+ # * Implementation files must match up with a test file named
50
+ # test_.*implementation.rb
51
+ #
52
+ # Strategy:
53
+ #
54
+ # 1. Find all files and associate them from impl <-> test.
55
+ # 2. Run all tests.
56
+ # 3. Scan for failures.
57
+ # 4. Detect changes in ANY (ruby?. file, rerun all failures + changed files.
58
+ # 5. Until 0 defects, goto 3.
59
+ # 6. When 0 defects, goto 2.
60
+
61
+ class Autotest
62
+
63
+ T0 = Time.at 0
64
+
65
+ ALL_HOOKS = [ :all_good, :initialize, :interrupt, :quit, :ran_command,
66
+ :reset, :run_command, :waiting ]
67
+
68
+ HOOKS = Hash.new { |h,k| h[k] = [] }
69
+ unless defined? WINDOZE then
70
+ WINDOZE = /win32/ =~ RUBY_PLATFORM
71
+ SEP = WINDOZE ? '&' : ';'
72
+ end
73
+
74
+ @@discoveries = []
75
+
76
+ ##
77
+ # Add a proc to the collection of discovery procs. See
78
+ # +autodiscover+.
79
+
80
+ def self.add_discovery &proc
81
+ @@discoveries << proc
82
+ end
83
+
84
+ ##
85
+ # Automatically find all potential autotest runner styles by
86
+ # searching your loadpath, vendor/plugins, and rubygems for
87
+ # "autotest/discover.rb". If found, that file is loaded and it
88
+ # should register discovery procs with autotest using
89
+ # +add_discovery+. That proc should return one or more strings
90
+ # describing the user's current environment. Those styles are then
91
+ # combined to dynamically invoke an autotest plugin to suite your
92
+ # environment. That plugin should define a subclass of Autotest with
93
+ # a corresponding name.
94
+ #
95
+ # === Process:
96
+ #
97
+ # 1. All autotest/discover.rb files loaded.
98
+ # 2. Those procs determine your styles (eg ["rails", "rspec"]).
99
+ # 3. Require file by sorting styles and joining (eg 'autotest/rails_rspec').
100
+ # 4. Invoke run method on appropriate class (eg Autotest::RailsRspec.run).
101
+ #
102
+ # === Example autotest/discover.rb:
103
+ #
104
+ # Autotest.add_discovery do
105
+ # "rails" if File.exist? 'config/environment.rb'
106
+ # end
107
+ #
108
+
109
+ def self.autodiscover
110
+ style = []
111
+
112
+ paths = $:.dup
113
+ paths.push 'lib'
114
+ paths.push(*Dir["vendor/plugins/*/lib"])
115
+
116
+ begin
117
+ require 'rubygems'
118
+ paths.push(*Gem.latest_load_paths)
119
+ rescue LoadError => e
120
+ # do nothing
121
+ end
122
+
123
+ paths.each do |d|
124
+ next unless File.directory? d
125
+ f = File.join(d, 'autotest', 'discover.rb')
126
+ if File.exist? f then
127
+ $: << d unless $:.include? d
128
+ load f
129
+ end
130
+ end
131
+
132
+ @@discoveries.map { |proc| proc.call }.flatten.compact.sort.uniq
133
+ end
134
+
135
+ ##
136
+ # Initialize and run the system.
137
+
138
+ def self.run
139
+ new.run
140
+ end
141
+
142
+ attr_writer :known_files
143
+ attr_accessor(:completed_re,
144
+ :extra_class_map,
145
+ :extra_files,
146
+ :failed_results_re,
147
+ :files_to_test,
148
+ :find_order,
149
+ :interrupted,
150
+ :last_mtime,
151
+ :libs,
152
+ :order,
153
+ :output,
154
+ :results,
155
+ :sleep,
156
+ :tainted,
157
+ :find_directories,
158
+ :unit_diff,
159
+ :wants_to_quit)
160
+
161
+ ##
162
+ # Initialize the instance and then load the user's .autotest file, if any.
163
+
164
+ def initialize
165
+ # these two are set directly because they're wrapped with
166
+ # add/remove/clear accessor methods
167
+ @exception_list = []
168
+ @test_mappings = []
169
+
170
+ self.completed_re = /\d+ tests, \d+ assertions, \d+ failures, \d+ errors/
171
+ self.extra_class_map = {}
172
+ self.extra_files = []
173
+ self.failed_results_re = /^\s+\d+\) (?:Failure|Error):\n(.*?)\((.*?)\)/
174
+ self.files_to_test = new_hash_of_arrays
175
+ self.find_order = []
176
+ self.known_files = nil
177
+ self.libs = %w[. lib test].join(File::PATH_SEPARATOR)
178
+ self.order = :random
179
+ self.output = $stderr
180
+ self.sleep = 1
181
+ self.find_directories = ['.']
182
+ self.unit_diff = "unit_diff -u"
183
+
184
+ self.add_mapping(/^lib\/.*\.rb$/) do |filename, _|
185
+ possible = File.basename(filename).gsub '_', '_?'
186
+ files_matching %r%^test/.*#{possible}$%
187
+ end
188
+
189
+ self.add_mapping(/^test.*\/test_.*rb$/) do |filename, _|
190
+ filename
191
+ end
192
+
193
+ [File.expand_path('~/.autotest'), './.autotest'].each do |f|
194
+ load f if File.exist? f
195
+ end
196
+ end
197
+
198
+ ##
199
+ # Repeatedly run failed tests, then all tests, then wait for changes
200
+ # and carry on until killed.
201
+
202
+ def run
203
+ hook :initialize
204
+ reset
205
+ add_sigint_handler
206
+
207
+ self.last_mtime = Time.now if $f
208
+
209
+ loop do # ^c handler
210
+ begin
211
+ get_to_green
212
+ if self.tainted then
213
+ rerun_all_tests
214
+ else
215
+ hook :all_good
216
+ end
217
+ wait_for_changes
218
+ rescue Interrupt
219
+ break if self.wants_to_quit
220
+ reset
221
+ end
222
+ end
223
+ hook :quit
224
+ end
225
+
226
+ ##
227
+ # Keep running the tests after a change, until all pass.
228
+
229
+ def get_to_green
230
+ begin
231
+ run_tests
232
+ wait_for_changes unless all_good
233
+ end until all_good
234
+ end
235
+
236
+ ##
237
+ # Look for files to test then run the tests and handle the results.
238
+
239
+ def run_tests
240
+ hook :run_command
241
+
242
+ new_mtime = self.find_files_to_test
243
+ return unless new_mtime
244
+ self.last_mtime = new_mtime
245
+
246
+ cmd = self.make_test_cmd self.files_to_test
247
+ return if cmd.empty?
248
+
249
+ puts cmd unless $q
250
+
251
+ old_sync = $stdout.sync
252
+ $stdout.sync = true
253
+ self.results = []
254
+ line = []
255
+ begin
256
+ open("| #{cmd}", "r") do |f|
257
+ until f.eof? do
258
+ c = f.getc
259
+ putc c
260
+ line << c
261
+ if c == ?\n then
262
+ self.results << if RUBY_VERSION >= "1.9" then
263
+ line.join
264
+ else
265
+ line.pack "c*"
266
+ end
267
+ line.clear
268
+ end
269
+ end
270
+ end
271
+ ensure
272
+ $stdout.sync = old_sync
273
+ end
274
+ hook :ran_command
275
+ self.results = self.results.join
276
+
277
+ handle_results(self.results)
278
+ end
279
+
280
+ ############################################################
281
+ # Utility Methods, not essential to reading of logic
282
+
283
+ ##
284
+ # Installs a sigint handler.
285
+
286
+ def add_sigint_handler
287
+ trap 'INT' do
288
+ if self.interrupted then
289
+ self.wants_to_quit = true
290
+ else
291
+ unless hook :interrupt then
292
+ puts "Interrupt a second time to quit"
293
+ self.interrupted = true
294
+ Kernel.sleep 1.5
295
+ end
296
+ raise Interrupt, nil # let the run loop catch it
297
+ end
298
+ end
299
+ end
300
+
301
+ ##
302
+ # If there are no files left to test (because they've all passed),
303
+ # then all is good.
304
+
305
+ def all_good
306
+ files_to_test.empty?
307
+ end
308
+
309
+ ##
310
+ # Convert a path in a string, s, into a class name, changing
311
+ # underscores to CamelCase, etc.
312
+
313
+ def path_to_classname(s)
314
+ sep = File::SEPARATOR
315
+ f = s.sub(/^test#{sep}/, '').sub(/\.rb$/, '').split(sep)
316
+ f = f.map { |path| path.split(/_|(\d+)/).map { |seg| seg.capitalize }.join }
317
+ f = f.map { |path| path =~ /^Test/ ? path : "Test#{path}" }
318
+ f.join('::')
319
+ end
320
+
321
+ ##
322
+ # Returns a hash mapping a file name to the known failures for that
323
+ # file.
324
+
325
+ def consolidate_failures(failed)
326
+ filters = new_hash_of_arrays
327
+
328
+ class_map = Hash[*self.find_order.grep(/^test/).map { |f| # TODO: ugly
329
+ [path_to_classname(f), f]
330
+ }.flatten]
331
+ class_map.merge!(self.extra_class_map)
332
+
333
+ failed.each do |method, klass|
334
+ if class_map.has_key? klass then
335
+ filters[class_map[klass]] << method
336
+ else
337
+ output.puts "Unable to map class #{klass} to a file"
338
+ end
339
+ end
340
+
341
+ return filters
342
+ end
343
+
344
+ ##
345
+ # Find the files to process, ignoring temporary files, source
346
+ # configuration management files, etc., and return a Hash mapping
347
+ # filename to modification time.
348
+
349
+ def find_files
350
+ result = {}
351
+ targets = self.find_directories + self.extra_files
352
+ self.find_order.clear
353
+
354
+ targets.each do |target|
355
+ order = []
356
+ Find.find(target) do |f|
357
+ Find.prune if f =~ self.exceptions
358
+
359
+ next if test ?d, f
360
+ next if f =~ /(swp|~|rej|orig)$/ # temporary/patch files
361
+ next if f =~ /\/\.?#/ # Emacs autosave/cvs merge files
362
+
363
+ filename = f.sub(/^\.\//, '')
364
+
365
+ result[filename] = File.stat(filename).mtime rescue next
366
+ order << filename
367
+ end
368
+ self.find_order.push(*order.sort)
369
+ end
370
+
371
+ return result
372
+ end
373
+
374
+ ##
375
+ # Find the files which have been modified, update the recorded
376
+ # timestamps, and use this to update the files to test. Returns true
377
+ # if any file is newer than the previously recorded most recent
378
+ # file.
379
+
380
+ def find_files_to_test(files=find_files)
381
+ updated = files.select { |filename, mtime| self.last_mtime < mtime }
382
+
383
+ p updated if $v unless updated.empty? || self.last_mtime.to_i == 0
384
+
385
+ hook :updated, updated unless updated.empty? || self.last_mtime.to_i == 0
386
+
387
+ updated.map { |f,m| test_files_for(f) }.flatten.uniq.each do |filename|
388
+ self.files_to_test[filename] # creates key with default value
389
+ end
390
+
391
+ if updated.empty? then
392
+ nil
393
+ else
394
+ files.values.max
395
+ end
396
+ end
397
+
398
+ ##
399
+ # Check results for failures, set the "bar" to red or green, and if
400
+ # there are failures record this.
401
+
402
+ def handle_results(results)
403
+ failed = results.scan(self.failed_results_re)
404
+ completed = results =~ self.completed_re
405
+
406
+ self.files_to_test = consolidate_failures failed if completed
407
+
408
+ color = completed && self.files_to_test.empty? ? :green : :red
409
+ hook color unless $TESTING
410
+
411
+ self.tainted = true unless self.files_to_test.empty?
412
+ end
413
+
414
+ ##
415
+ # Lazy accessor for the known_files hash.
416
+
417
+ def known_files
418
+ unless @known_files then
419
+ @known_files = Hash[*find_order.map { |f| [f, true] }.flatten]
420
+ end
421
+ @known_files
422
+ end
423
+
424
+ ##
425
+ # Generate the commands to test the supplied files
426
+
427
+ def make_test_cmd files_to_test
428
+ cmds = []
429
+ full, partial = reorder(files_to_test).partition { |k,v| v.empty? }
430
+
431
+ unless full.empty? then
432
+ classes = full.map {|k,v| k}.flatten.uniq.join(' ')
433
+ cmds << "#{ruby} -I#{libs} -rtest/unit -e \"%w[#{classes}].each { |f| require f }\" | #{unit_diff}"
434
+ end
435
+
436
+ partial.each do |klass, methods|
437
+ regexp = Regexp.union(*methods).source
438
+ cmds << "#{ruby} -I#{libs} #{klass} -n \"/^(#{regexp})$/\" | #{unit_diff}"
439
+ end
440
+
441
+ return cmds.join("#{SEP} ")
442
+ end
443
+
444
+ def new_hash_of_arrays
445
+ Hash.new { |h,k| h[k] = [] }
446
+ end
447
+
448
+ def reorder files_to_test
449
+ case self.order
450
+ when :alpha then
451
+ files_to_test.sort_by { |k,v| k }
452
+ when :reverse then
453
+ files_to_test.sort_by { |k,v| k }.reverse
454
+ when :random then
455
+ max = files_to_test.size
456
+ files_to_test.sort_by { |k,v| rand(max) }
457
+ when :natural then
458
+ (self.find_order & files_to_test.keys).map { |f| [f, files_to_test[f]] }
459
+ else
460
+ raise "unknown order type: #{self.order.inspect}"
461
+ end
462
+ end
463
+
464
+ ##
465
+ # Rerun the tests from cold (reset state)
466
+
467
+ def rerun_all_tests
468
+ reset
469
+ run_tests
470
+
471
+ hook :all_good if all_good
472
+ end
473
+
474
+ ##
475
+ # Clear all state information about test failures and whether
476
+ # interrupts will kill autotest.
477
+
478
+ def reset
479
+ self.files_to_test.clear
480
+ self.find_order.clear
481
+ self.interrupted = false
482
+ self.known_files = nil
483
+ self.last_mtime = T0
484
+ self.tainted = false
485
+ self.wants_to_quit = false
486
+
487
+ hook :reset
488
+ end
489
+
490
+ ##
491
+ # Determine and return the path of the ruby executable.
492
+
493
+ def ruby
494
+ ruby = ENV['RUBY']
495
+ ruby ||= File.join(Config::CONFIG['bindir'],
496
+ Config::CONFIG['ruby_install_name'])
497
+
498
+ ruby.gsub! File::SEPARATOR, File::ALT_SEPARATOR if File::ALT_SEPARATOR
499
+
500
+ return ruby
501
+ end
502
+
503
+ ##
504
+ # Return the name of the file with the tests for filename by finding
505
+ # a +test_mapping+ that matches the file and executing the mapping's
506
+ # proc.
507
+
508
+ def test_files_for(filename)
509
+ result = @test_mappings.find { |file_re, ignored| filename =~ file_re }
510
+
511
+ p :test_file_for => [filename, result.first] if result and $DEBUG
512
+
513
+ result = result.nil? ? [] : Array(result.last.call(filename, $~))
514
+
515
+ output.puts "No tests matched #{filename}" if
516
+ ($v or $TESTING) and result.empty?
517
+
518
+ result.sort.uniq.select { |f| known_files[f] }
519
+ end
520
+
521
+ ##
522
+ # Sleep then look for files to test, until there are some.
523
+
524
+ def wait_for_changes
525
+ hook :waiting
526
+ Kernel.sleep self.sleep until find_files_to_test
527
+ end
528
+
529
+ ############################################################
530
+ # File Mappings:
531
+
532
+ ##
533
+ # Returns all known files in the codebase matching +regexp+.
534
+
535
+ def files_matching regexp
536
+ self.find_order.select { |k| k =~ regexp }
537
+ end
538
+
539
+ ##
540
+ # Adds a file mapping. +regexp+ should match a file path in the
541
+ # codebase. +proc+ is passed a matched filename and
542
+ # Regexp.last_match. +proc+ should return an array of tests to run.
543
+ #
544
+ # For example, if test_helper.rb is modified, rerun all tests:
545
+ #
546
+ # at.add_mapping(/test_helper.rb/) do |f, _|
547
+ # at.files_matching(/^test.*rb$/)
548
+ # end
549
+
550
+ def add_mapping(regexp, &proc)
551
+ @test_mappings << [regexp, proc]
552
+ nil
553
+ end
554
+
555
+ ##
556
+ # Removed a file mapping matching +regexp+.
557
+
558
+ def remove_mapping regexp
559
+ @test_mappings.delete_if do |k,v|
560
+ k == regexp
561
+ end
562
+ nil
563
+ end
564
+
565
+ ##
566
+ # Clears all file mappings. This is DANGEROUS as it entirely
567
+ # disables autotest. You must add at least one file mapping that
568
+ # does a good job of rerunning appropriate tests.
569
+
570
+ def clear_mappings
571
+ @test_mappings.clear
572
+ nil
573
+ end
574
+
575
+ ############################################################
576
+ # Exceptions:
577
+
578
+ ##
579
+ # Adds +regexp+ to the list of exceptions for find_file. This must
580
+ # be called _before_ the exceptions are compiled.
581
+
582
+ def add_exception regexp
583
+ raise "exceptions already compiled" if defined? @exceptions
584
+ @exception_list << regexp
585
+ nil
586
+ end
587
+
588
+ ##
589
+ # Removes +regexp+ to the list of exceptions for find_file. This
590
+ # must be called _before_ the exceptions are compiled.
591
+
592
+ def remove_exception regexp
593
+ raise "exceptions already compiled" if defined? @exceptions
594
+ @exception_list.delete regexp
595
+ nil
596
+ end
597
+
598
+ ##
599
+ # Clears the list of exceptions for find_file. This must be called
600
+ # _before_ the exceptions are compiled.
601
+
602
+ def clear_exceptions
603
+ raise "exceptions already compiled" if defined? @exceptions
604
+ @exception_list.clear
605
+ nil
606
+ end
607
+
608
+ ##
609
+ # Return a compiled regexp of exceptions for find_files or nil if no
610
+ # filtering should take place. This regexp is generated from
611
+ # +exception_list+.
612
+
613
+ def exceptions
614
+ unless defined? @exceptions then
615
+ if @exception_list.empty? then
616
+ @exceptions = nil
617
+ else
618
+ @exceptions = Regexp.union(*@exception_list)
619
+ end
620
+ end
621
+
622
+ @exceptions
623
+ end
624
+
625
+ ############################################################
626
+ # Hooks:
627
+
628
+ ##
629
+ # Call the event hook named +name+, executing all registered hooks
630
+ # until one returns true. Returns false if no hook handled the
631
+ # event.
632
+
633
+ def hook(name, *args)
634
+ deprecated = {
635
+ # none currently
636
+ }
637
+
638
+ if deprecated[name] and not HOOKS[name].empty? then
639
+ warn "hook #{name} has been deprecated, use #{deprecated[name]}"
640
+ end
641
+
642
+ unless HOOKS[name].empty? then
643
+ HOOKS[name].each { |plugin| plugin[self, *args] }
644
+ end
645
+ end
646
+
647
+ ##
648
+ # Add the supplied block to the available hooks, with the given
649
+ # name.
650
+
651
+ def self.add_hook(name, &block)
652
+ HOOKS[name] << block
653
+ end
654
+ end