BanzaiMan-ZenTest 4.2

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