SlimTest 4.6.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,74 @@
1
+ #!/usr/bin/env ruby -w
2
+
3
+ require 'multiruby'
4
+
5
+ ENV.delete 'RUBYOPT'
6
+
7
+ ARGV << "help" if ARGV.empty?
8
+
9
+ Dir.chdir Multiruby.root_dir
10
+ Multiruby.setup_dirs(false)
11
+
12
+ ARGV.each do |spec|
13
+ case spec
14
+ when "-h", "--help", "help" then
15
+ Multiruby.help
16
+ exit 0
17
+ when "the_usual" then # TODO: update #help
18
+ ARGV.push(*Multiruby::TAGS.map { |v| "mri:tar:#{v.gsub(/_/, '.')}" })
19
+ ARGV << "build" << "update:rubygems"
20
+ system "multigem install --no-ri --no-rdoc rake minitest ZenTest gemcutter rubyforge hoe"
21
+ when "build" then
22
+ Multiruby.build_and_install
23
+ when "clean" then
24
+ Multiruby.clean
25
+ when "list" then
26
+ Multiruby.list
27
+ when /rm:(.*)/ then
28
+ Multiruby.rm $1
29
+ when "rubygems:merge" then
30
+ Multiruby.merge_rubygems
31
+ when "rubygems:update", "update:rubygems" then
32
+ Multiruby.update_rubygems
33
+ ARGV << "build"
34
+ when "update" then
35
+ Multiruby.update
36
+ when "tags" then
37
+ p Multiruby.tags
38
+ when "mri:svn:current" then
39
+ ARGV << "mri:svn:releases" << "mri:svn:branches" << "build"
40
+ when "mri:svn:releases" then
41
+ Multiruby::TAGS.each do |v|
42
+ latest = Multiruby.mri_latest_tag v
43
+ abort "Can't find tag #{v}" unless latest
44
+ ARGV << "mri:svn:tag:#{latest}:mri_rel_#{v}"
45
+ end
46
+ ARGV << "build"
47
+ when /mri:svn:branch:(.*)/ then
48
+ ver = "branches/ruby_#{$1}" unless ver == "trunk"
49
+ Multiruby.svn_co "#{Multiruby::MRI_SVN}/#{$1}", "mri_#{$1}"
50
+ ARGV << "build"
51
+ when "mri:svn:branches" then
52
+ Multiruby::BRANCHES.each do |v|
53
+ ARGV << "mri:svn:branch:#{v}"
54
+ end
55
+ ARGV << "build"
56
+ when /mri:svn:tag:(.*):(.*)/ then
57
+ Multiruby.svn_co "#{Multiruby::MRI_SVN}/tags/#{$1}", $2
58
+ ARGV << "build"
59
+ when /mri:svn:tag:(.*)/ then
60
+ ARGV << "mri:svn:tag:#{$1}:#{$1}" << "build"
61
+ when /mri:list:(.*)/ then
62
+ v = $1
63
+ ver = v[/\d+\.\d+/]
64
+ url = "#{Multiruby::RUBY_URL}/#{ver}/"
65
+
66
+ puts Multiruby.matching_versions(url, v).join("\n")
67
+ when /mri:tar:(.*)/ then
68
+ Multiruby.fetch_tar $1
69
+ ARGV << "build"
70
+ else
71
+ warn "unknown spec #{spec}"
72
+ end
73
+ end
74
+
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env 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 [default]
14
+ # -p plain 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(__FILE__) 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
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env 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
+ ZenTest.usage_with_exit
14
+ end
15
+
16
+ code = ZenTest.fix(*ARGV)
17
+ if defined? $e then
18
+ require 'test/unit'
19
+ eval code
20
+ else
21
+ print code
22
+ end
23
+
@@ -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,16 @@
1
+ # -*- ruby -*-
2
+
3
+ # require 'autotest/autoupdate'
4
+ # require 'autotest/bundler'
5
+ # require 'autotest/isolate'
6
+ # require 'autotest/once'
7
+ # require 'autotest/rcov'
8
+ # require 'autotest/restart'
9
+ # require 'autotest/timestamp'
10
+
11
+ # Autotest::AutoUpdate.sleep_time = o
12
+ # Autotest::AutoUpdate.update_cmd = o
13
+ # Autotest::Isolate.dir = o
14
+ # Autotest::RCov.command = o
15
+ # Autotest::RCov.pattern = o
16
+ # Autotest::RCov.options = o
@@ -0,0 +1,852 @@
1
+ require 'find'
2
+ require 'rbconfig'
3
+
4
+ $TESTING = false unless defined? $TESTING
5
+
6
+ ##
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.
21
+ #
22
+ # Plugins:
23
+ #
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:
27
+ #
28
+ # Autotest.add_hook hook_name { |autotest| ... }
29
+ #
30
+ # The available hooks are listed in +ALL_HOOKS+.
31
+ #
32
+ # See example_dot_autotest.rb for more details.
33
+ #
34
+ # If a hook returns a true value, it signals to autotest that the hook
35
+ # was handled and should not continue executing hooks.
36
+ #
37
+ # Naming:
38
+ #
39
+ # Autotest uses a simple naming scheme to figure out how to map
40
+ # implementation files to test files following the Test::Unit naming
41
+ # scheme.
42
+ #
43
+ # * Test files must be stored in test/
44
+ # * Test files names must start with test_
45
+ # * Test class names must start with Test
46
+ # * Implementation files must be stored in lib/
47
+ # * Implementation files must match up with a test file named
48
+ # test_.*implementation.rb
49
+ #
50
+ # Strategy:
51
+ #
52
+ # 1. Find all files and associate them from impl <-> test.
53
+ # 2. Run all tests.
54
+ # 3. Scan for failures.
55
+ # 4. Detect changes in ANY (ruby?. file, rerun all failures + changed files.
56
+ # 5. Until 0 defects, goto 3.
57
+ # 6. When 0 defects, goto 2.
58
+
59
+ class Autotest
60
+
61
+ RUBY19 = defined? Encoding
62
+
63
+ T0 = Time.at 0
64
+
65
+ ALL_HOOKS = [ :all_good, :died, :green, :initialize,
66
+ :post_initialize, :interrupt, :quit, :ran_command,
67
+ :red, :reset, :run_command, :updated, :waiting ]
68
+
69
+ def self.options
70
+ @@options ||= {}
71
+ end
72
+
73
+ def options
74
+ self.class.options
75
+ end
76
+
77
+ HOOKS = Hash.new { |h,k| h[k] = [] }
78
+ unless defined? WINDOZE then
79
+ WINDOZE = /mswin|mingw/ =~ RbConfig::CONFIG['host_os']
80
+ SEP = WINDOZE ? '&' : ';'
81
+ end
82
+
83
+ @@discoveries = []
84
+
85
+ def self.parse_options args = ARGV
86
+ require 'optparse'
87
+ options = {
88
+ :args => args.dup
89
+ }
90
+
91
+ OptionParser.new do |opts|
92
+ opts.banner = <<-BANNER.gsub(/^ /, '')
93
+ Continuous testing for your ruby app.
94
+
95
+ Autotest automatically tests code that has changed. It
96
+ assumes the code is in lib, and tests are in tests. Autotest
97
+ uses plugins to control what happens. You configure plugins
98
+ with require statements in the .autotest file in your
99
+ project base directory, and a default configuration for all
100
+ your projects in the .autotest file in your home directory.
101
+
102
+ Usage:
103
+ autotest [options]
104
+ BANNER
105
+
106
+ opts.on "-f", "--fast-start", "Do not run full tests at start" do
107
+ options[:no_full_after_start] = true
108
+ end
109
+
110
+ opts.on("-c", "--no-full-after-failed",
111
+ "Do not run all tests on red->green") do
112
+ options[:no_full_after_failed] = true
113
+ end
114
+
115
+ opts.on "-v", "--verbose", "Be annoyingly verbose (debugs .autotest)." do
116
+ options[:verbose] = true
117
+ end
118
+
119
+ opts.on "-q", "--quiet", "Be quiet." do
120
+ options[:quiet] = true
121
+ end
122
+
123
+ opts.on("-r", "--rc CONF", String, "Override path to config file") do |o|
124
+ options[:rc] = Array(o)
125
+ end
126
+
127
+ opts.on("-s", "--style STYLE", String,
128
+ "Manually specify test style. (default: autodiscover)") do |style|
129
+ options[:style] = Array(style)
130
+ end
131
+
132
+ opts.on("-w", "--warnings", "Turn on ruby warnings") do
133
+ $-w = true
134
+ end
135
+
136
+ opts.on "-h", "--help", "Show this." do
137
+ puts opts
138
+ exit 1
139
+ end
140
+ end.parse! args
141
+
142
+ Autotest.options.merge! options
143
+
144
+ options
145
+ end
146
+
147
+ ##
148
+ # Calculates the autotest runner to use to run the tests.
149
+ #
150
+ # Can be overridden with --style, otherwise uses ::autodiscover.
151
+
152
+ def self.runner
153
+ style = options[:style] || Autotest.autodiscover
154
+ target = Autotest
155
+
156
+ unless style.empty? then
157
+ mod = "autotest/#{style.join "_"}"
158
+ puts "loading #{mod}"
159
+ begin
160
+ require mod
161
+ rescue LoadError
162
+ abort "Autotest style #{mod} doesn't seem to exist. Aborting."
163
+ end
164
+ target = Autotest.const_get(style.map {|s| s.capitalize}.join)
165
+ end
166
+
167
+ target
168
+ end
169
+
170
+ ##
171
+ # Add a proc to the collection of discovery procs. See
172
+ # +autodiscover+.
173
+
174
+ def self.add_discovery &proc
175
+ @@discoveries << proc
176
+ end
177
+
178
+ ##
179
+ # Automatically find all potential autotest runner styles by
180
+ # searching your loadpath, vendor/plugins, and rubygems for
181
+ # "autotest/discover.rb". If found, that file is loaded and it
182
+ # should register discovery procs with autotest using
183
+ # +add_discovery+. That proc should return one or more strings
184
+ # describing the user's current environment. Those styles are then
185
+ # combined to dynamically invoke an autotest plugin to suite your
186
+ # environment. That plugin should define a subclass of Autotest with
187
+ # a corresponding name.
188
+ #
189
+ # === Process:
190
+ #
191
+ # 1. All autotest/discover.rb files loaded.
192
+ # 2. Those procs determine your styles (eg ["rails", "rspec"]).
193
+ # 3. Require file by sorting styles and joining (eg 'autotest/rails_rspec').
194
+ # 4. Invoke run method on appropriate class (eg Autotest::RailsRspec.run).
195
+ #
196
+ # === Example autotest/discover.rb:
197
+ #
198
+ # Autotest.add_discovery do
199
+ # "rails" if File.exist? 'config/environment.rb'
200
+ # end
201
+ #
202
+
203
+ def self.autodiscover
204
+ require 'rubygems'
205
+
206
+ # *sigh*
207
+ #
208
+ # This is needed for rspec's hacky discovery mechanism. For some
209
+ # reason rspec2 added generators that create
210
+ # "autotest/discover.rb" right in the project directory instead of
211
+ # keeping it in the rspec gem and properly deciding that the
212
+ # project is an rspec based project or not. See the url for more
213
+ # details:
214
+ #
215
+ # http://rubyforge.org/tracker/?func=detail&atid=1678&aid=28775&group_id=419
216
+ #
217
+ # For the record, the sane way to do it is the bacon way:
218
+ #
219
+ # "Since version 1.0, there is autotest support. You need to tag
220
+ # your test directories (test/ or spec/) by creating an .bacon
221
+ # file there. Autotest then will find it."
222
+ #
223
+ # I'm submitting a counter-patch to rspec to fix stuff properly,
224
+ # but for now I'm stuck with this because their brokenness is
225
+ # documented in multiple books.
226
+ #
227
+ # I'm removing this code once a sane rspec goes out.
228
+
229
+ hacky_discovery = Gem::Specification.any? { |s| s.name =~ /^rspec/ }
230
+ $: << '.' if hacky_discovery
231
+
232
+ Gem.find_files("autotest/discover").each do |f|
233
+ load f
234
+ end
235
+
236
+ # call all discovery procs and determine the style to use
237
+ @@discoveries.map{ |proc| proc.call }.flatten.compact.sort.uniq
238
+ end
239
+
240
+ ##
241
+ # Initialize and run the system.
242
+
243
+ def self.run
244
+ new.run
245
+ end
246
+
247
+ attr_writer :known_files
248
+ attr_accessor(:completed_re,
249
+ :extra_class_map,
250
+ :extra_files,
251
+ :failed_results_re,
252
+ :files_to_test,
253
+ :find_order,
254
+ :interrupted,
255
+ :latest_results,
256
+ :last_mtime,
257
+ :libs,
258
+ :order,
259
+ :output,
260
+ :prefix,
261
+ :results,
262
+ :sleep,
263
+ :tainted,
264
+ :testlib,
265
+ :find_directories,
266
+ :unit_diff,
267
+ :wants_to_quit)
268
+
269
+ alias tainted? tainted
270
+
271
+ ##
272
+ # Initialize the instance and then load the user's .autotest file, if any.
273
+
274
+ def initialize
275
+ # these two are set directly because they're wrapped with
276
+ # add/remove/clear accessor methods
277
+ @exception_list = []
278
+ @test_mappings = []
279
+ @child = nil
280
+
281
+ self.completed_re =
282
+ /\d+ tests, \d+ assertions, \d+ failures, \d+ errors(, \d+ skips)?/
283
+ self.extra_class_map = {}
284
+ self.extra_files = []
285
+ self.failed_results_re = /^\s+\d+\) (?:Failure|Error):\n(.*?)\((.*?)\)/
286
+ self.files_to_test = new_hash_of_arrays
287
+ self.find_order = []
288
+ self.known_files = nil
289
+ self.libs = %w[. lib test].join(File::PATH_SEPARATOR)
290
+ self.order = :random
291
+ self.output = $stderr
292
+ self.prefix = nil
293
+ self.sleep = 1
294
+ self.testlib = "test/unit"
295
+ specified_directories = ARGV.reject { |arg| arg.start_with?("-") } # options are not directories
296
+ self.find_directories = specified_directories.empty? ? ['.'] : specified_directories
297
+ self.unit_diff = nil
298
+ self.latest_results = nil
299
+
300
+ # file in /lib -> run test in /test
301
+ self.add_mapping(/^lib\/.*\.rb$/) do |filename, _|
302
+ possible = File.basename(filename).gsub '_', '_?' # ' stupid emacs
303
+ files_matching %r%^test/.*#{possible}$%
304
+ end
305
+
306
+ # file in /test -> run it
307
+ self.add_mapping(/^test.*\/test_.*rb$/) do |filename, _|
308
+ filename
309
+ end
310
+
311
+ default_configs = [File.expand_path('~/.autotest'), './.autotest']
312
+ configs = options[:rc] || default_configs
313
+
314
+ configs.each do |f|
315
+ load f if File.exist? f
316
+ end
317
+ end
318
+
319
+ ##
320
+ # Repeatedly run failed tests, then all tests, then wait for changes
321
+ # and carry on until killed.
322
+
323
+ def run
324
+ hook :initialize
325
+ hook :post_initialize
326
+
327
+ reset
328
+ add_sigint_handler
329
+
330
+ self.last_mtime = Time.now if options[:no_full_after_start]
331
+
332
+ loop do
333
+ begin # ^c handler
334
+ get_to_green
335
+ if tainted? and not options[:no_full_after_failed] then
336
+ rerun_all_tests
337
+ else
338
+ hook :all_good
339
+ end
340
+ wait_for_changes
341
+ rescue Interrupt
342
+ break if wants_to_quit
343
+ reset
344
+ end
345
+ end
346
+ hook :quit
347
+ rescue Exception => err
348
+ hook(:died, err) or raise err
349
+ end
350
+
351
+ ##
352
+ # Keep running the tests after a change, until all pass.
353
+
354
+ def get_to_green
355
+ begin
356
+ run_tests
357
+ wait_for_changes unless all_good
358
+ end until all_good
359
+ end
360
+
361
+ ##
362
+ # Look for files to test then run the tests and handle the results.
363
+
364
+ def run_tests
365
+ hook :run_command
366
+
367
+ new_mtime = self.find_files_to_test
368
+ return unless new_mtime
369
+ self.last_mtime = new_mtime
370
+
371
+ cmd = self.make_test_cmd self.files_to_test
372
+ return if cmd.empty?
373
+
374
+ puts cmd unless options[:quiet]
375
+
376
+ old_sync = $stdout.sync
377
+ $stdout.sync = true
378
+ self.results = []
379
+ line = []
380
+ begin
381
+ open "| #{cmd}", "r" do |f|
382
+ until f.eof? do
383
+ c = f.getc or break
384
+ if RUBY19 then
385
+ print c
386
+ else
387
+ putc c
388
+ end
389
+ line << c
390
+ if c == ?\n then
391
+ self.results << if RUBY19 then
392
+ line.join
393
+ else
394
+ line.pack "c*"
395
+ end
396
+ line.clear
397
+ end
398
+ end
399
+ end
400
+ ensure
401
+ $stdout.sync = old_sync
402
+ end
403
+ hook :ran_command
404
+ self.results = self.results.join
405
+
406
+ handle_results self.results
407
+ end
408
+
409
+ ############################################################
410
+ # Utility Methods, not essential to reading of logic
411
+
412
+ ##
413
+ # Installs a sigint handler.
414
+
415
+ def add_sigint_handler
416
+ trap 'INT' do
417
+ Process.kill "KILL", @child if @child
418
+
419
+ if self.interrupted then
420
+ self.wants_to_quit = true
421
+ else
422
+ unless hook :interrupt then
423
+ puts "Interrupt a second time to quit"
424
+ self.interrupted = true
425
+ Kernel.sleep 1.5
426
+ end
427
+ raise Interrupt, nil # let the run loop catch it
428
+ end
429
+ end
430
+ end
431
+
432
+ ##
433
+ # Installs a sigquit handler
434
+
435
+ def add_sigquit_handler
436
+ trap 'QUIT' do
437
+ restart
438
+ end
439
+ end
440
+
441
+ def restart
442
+ Process.kill "KILL", @child if @child
443
+
444
+ cmd = [$0, *options[:args]]
445
+
446
+ index = $LOAD_PATH.index RbConfig::CONFIG["sitelibdir"]
447
+
448
+ if index then
449
+ extra = $LOAD_PATH[0...index]
450
+ cmd = [Gem.ruby, "-I", extra.join(":")] + cmd
451
+ end
452
+
453
+ puts cmd.join(" ") if options[:verbose]
454
+
455
+ exec(*cmd)
456
+ end
457
+
458
+ ##
459
+ # If there are no files left to test (because they've all passed),
460
+ # then all is good.
461
+
462
+ def all_good
463
+ files_to_test.empty?
464
+ end
465
+
466
+ ##
467
+ # Convert a path in a string, s, into a class name, changing
468
+ # underscores to CamelCase, etc.
469
+
470
+ def path_to_classname s
471
+ sep = File::SEPARATOR
472
+ f = s.sub(/^test#{sep}/, '').sub(/\.rb$/, '').split sep
473
+ f = f.map { |path| path.split(/_|(\d+)/).map { |seg| seg.capitalize }.join }
474
+ f = f.map { |path| path =~ /^Test/ ? path : "Test#{path}" }
475
+
476
+ f.join '::'
477
+ end
478
+
479
+ ##
480
+ # Returns a hash mapping a file name to the known failures for that
481
+ # file.
482
+
483
+ def consolidate_failures failed
484
+ filters = new_hash_of_arrays
485
+
486
+ class_map = Hash[*self.find_order.grep(/^test/).map { |f| # TODO: ugly
487
+ [path_to_classname(f), f]
488
+ }.flatten]
489
+ class_map.merge! self.extra_class_map
490
+
491
+ failed.each do |method, klass|
492
+ if class_map.has_key? klass then
493
+ filters[class_map[klass]] << method
494
+ else
495
+ output.puts "Unable to map class #{klass} to a file"
496
+ end
497
+ end
498
+
499
+ filters
500
+ end
501
+
502
+ ##
503
+ # Find the files to process, ignoring temporary files, source
504
+ # configuration management files, etc., and return a Hash mapping
505
+ # filename to modification time.
506
+
507
+ def find_files
508
+ result = {}
509
+ targets = self.find_directories + self.extra_files
510
+ self.find_order.clear
511
+
512
+ targets.each do |target|
513
+ order = []
514
+ Find.find target do |f|
515
+ Find.prune if f =~ self.exceptions
516
+
517
+ next if test ?d, f
518
+ next if f =~ /(swp|~|rej|orig)$/ # temporary/patch files
519
+ next if f =~ /^\.\/tmp/ # temporary dir, used by isolate
520
+ next if f =~ /\/\.?#/ # Emacs autosave/cvs merge files
521
+
522
+ filename = f.sub(/^\.\//, '')
523
+
524
+ result[filename] = File.stat(filename).mtime rescue next
525
+ order << filename
526
+ end
527
+ self.find_order.push(*order.sort)
528
+ end
529
+
530
+ result
531
+ end
532
+
533
+ ##
534
+ # Find the files which have been modified, update the recorded
535
+ # timestamps, and use this to update the files to test. Returns
536
+ # the latest mtime of the files modified or nil when nothing was
537
+ # modified.
538
+
539
+ def find_files_to_test files = find_files
540
+ updated = files.select { |filename, mtime| self.last_mtime < mtime }
541
+
542
+ # nothing to update or initially run
543
+ unless updated.empty? || self.last_mtime.to_i == 0 then
544
+ p updated if options[:verbose]
545
+
546
+ hook :updated, updated
547
+ end
548
+
549
+ updated.map { |f,m| test_files_for f }.flatten.uniq.each do |filename|
550
+ self.files_to_test[filename] # creates key with default value
551
+ end
552
+
553
+ if updated.empty? then
554
+ nil
555
+ else
556
+ files.values.max
557
+ end
558
+ end
559
+
560
+ ##
561
+ # Check results for failures, set the "bar" to red or green, and if
562
+ # there are failures record this.
563
+
564
+ def handle_results results
565
+ results = results.gsub(/\e\[\d+m/, '') # strip ascii color
566
+ failed = results.scan self.failed_results_re
567
+ completed = results[self.completed_re]
568
+
569
+ if completed then
570
+ completed = completed.scan(/(\d+) (\w+)/).map { |v, k| [k, v.to_i] }
571
+
572
+ self.latest_results = Hash[*completed.flatten]
573
+ self.files_to_test = consolidate_failures failed
574
+
575
+ color = self.files_to_test.empty? ? :green : :red
576
+ hook color unless $TESTING
577
+ else
578
+ self.latest_results = nil
579
+ end
580
+
581
+ self.tainted = true unless self.files_to_test.empty?
582
+ end
583
+
584
+ ##
585
+ # Lazy accessor for the known_files hash.
586
+
587
+ def known_files
588
+ unless @known_files then
589
+ @known_files = Hash[*find_order.map { |f| [f, true] }.flatten]
590
+ end
591
+ @known_files
592
+ end
593
+
594
+ ##
595
+ # Returns the base of the ruby command.
596
+
597
+ def ruby_cmd
598
+ "#{prefix}#{ruby} -I#{libs} -rubygems"
599
+ end
600
+
601
+ ##
602
+ # Generate the commands to test the supplied files
603
+
604
+ def make_test_cmd files_to_test
605
+ cmds = []
606
+ full, partial = reorder(files_to_test).partition { |k,v| v.empty? }
607
+ diff = self.unit_diff
608
+ diff = " | #{diff}" if diff and diff !~ /^\|/
609
+
610
+ unless full.empty? then
611
+ classes = full.map {|k,v| k}.flatten.uniq
612
+ classes.unshift testlib
613
+ classes = classes.join " "
614
+ cmds << "#{ruby_cmd} -e \"%w[#{classes}].each { |f| require f }\"#{diff}"
615
+ end
616
+
617
+ partial.each do |klass, methods|
618
+ regexp = Regexp.union(*methods).source
619
+ cmds << "#{ruby_cmd} #{klass} -n \"/^(#{regexp})$/\"#{diff}"
620
+ end
621
+
622
+ cmds.join "#{SEP} "
623
+ end
624
+
625
+ def new_hash_of_arrays
626
+ Hash.new { |h,k| h[k] = [] }
627
+ end
628
+
629
+ def reorder files_to_test
630
+ case self.order
631
+ when :alpha then
632
+ files_to_test.sort_by { |k,v| k }
633
+ when :reverse then
634
+ files_to_test.sort_by { |k,v| k }.reverse
635
+ when :random then
636
+ max = files_to_test.size
637
+ files_to_test.sort_by { |k,v| rand max }
638
+ when :natural then
639
+ (self.find_order & files_to_test.keys).map { |f| [f, files_to_test[f]] }
640
+ else
641
+ raise "unknown order type: #{self.order.inspect}"
642
+ end
643
+ end
644
+
645
+ ##
646
+ # Rerun the tests from cold (reset state)
647
+
648
+ def rerun_all_tests
649
+ reset
650
+ run_tests
651
+
652
+ hook :all_good if all_good
653
+ end
654
+
655
+ ##
656
+ # Clear all state information about test failures and whether
657
+ # interrupts will kill autotest.
658
+
659
+ def reset
660
+ self.files_to_test.clear
661
+ self.find_order.clear
662
+
663
+ self.interrupted = false
664
+ self.known_files = nil
665
+ self.last_mtime = T0
666
+ self.tainted = false
667
+ self.wants_to_quit = false
668
+
669
+ hook :reset
670
+ end
671
+
672
+ ##
673
+ # Determine and return the path of the ruby executable.
674
+
675
+ def ruby
676
+ ruby = ENV['RUBY']
677
+ ruby ||= File.join(RbConfig::CONFIG['bindir'],
678
+ RbConfig::CONFIG['ruby_install_name'])
679
+
680
+ ruby.gsub! File::SEPARATOR, File::ALT_SEPARATOR if File::ALT_SEPARATOR
681
+
682
+ return ruby
683
+ end
684
+
685
+ ##
686
+ # Return the name of the file with the tests for filename by finding
687
+ # a +test_mapping+ that matches the file and executing the mapping's
688
+ # proc.
689
+
690
+ def test_files_for filename
691
+ result = @test_mappings.find { |file_re, ignored| filename =~ file_re }
692
+
693
+ p :test_file_for => [filename, result.first] if result and $DEBUG
694
+
695
+ result = result.nil? ? [] : [result.last.call(filename, $~)].flatten
696
+
697
+ output.puts "No tests matched #{filename}" if
698
+ (options[:verbose] or $TESTING) and result.empty?
699
+
700
+ result.sort.uniq.select { |f| known_files[f] }
701
+ end
702
+
703
+ ##
704
+ # Sleep then look for files to test, until there are some.
705
+
706
+ def wait_for_changes
707
+ hook :waiting
708
+ Kernel.sleep self.sleep until find_files_to_test
709
+ end
710
+
711
+ ############################################################
712
+ # File Mappings:
713
+
714
+ ##
715
+ # Returns all known files in the codebase matching +regexp+.
716
+
717
+ def files_matching regexp
718
+ self.find_order.select { |k| k =~ regexp }
719
+ end
720
+
721
+ ##
722
+ # Adds a file mapping, optionally prepending the mapping to the
723
+ # front of the list if +prepend+ is true. +regexp+ should match a
724
+ # file path in the codebase. +proc+ is passed a matched filename and
725
+ # Regexp.last_match. +proc+ should return an array of tests to run.
726
+ #
727
+ # For example, if test_helper.rb is modified, rerun all tests:
728
+ #
729
+ # at.add_mapping(/test_helper.rb/) do |f, _|
730
+ # at.files_matching(/^test.*rb$/)
731
+ # end
732
+
733
+ def add_mapping regexp, prepend = false, &proc
734
+ if prepend then
735
+ @test_mappings.unshift [regexp, proc]
736
+ else
737
+ @test_mappings.push [regexp, proc]
738
+ end
739
+ nil
740
+ end
741
+
742
+ ##
743
+ # Removed a file mapping matching +regexp+.
744
+
745
+ def remove_mapping regexp
746
+ @test_mappings.delete_if do |k,v|
747
+ k == regexp
748
+ end
749
+ nil
750
+ end
751
+
752
+ ##
753
+ # Clears all file mappings. This is DANGEROUS as it entirely
754
+ # disables autotest. You must add at least one file mapping that
755
+ # does a good job of rerunning appropriate tests.
756
+
757
+ def clear_mappings
758
+ @test_mappings.clear
759
+ nil
760
+ end
761
+
762
+ ############################################################
763
+ # Exceptions:
764
+
765
+ ##
766
+ # Adds +regexp+ to the list of exceptions for find_file. This must
767
+ # be called _before_ the exceptions are compiled.
768
+
769
+ def add_exception regexp
770
+ raise "exceptions already compiled" if defined? @exceptions
771
+
772
+ @exception_list << regexp
773
+ nil
774
+ end
775
+
776
+ ##
777
+ # Removes +regexp+ to the list of exceptions for find_file. This
778
+ # must be called _before_ the exceptions are compiled.
779
+
780
+ def remove_exception regexp
781
+ raise "exceptions already compiled" if defined? @exceptions
782
+ @exception_list.delete regexp
783
+ nil
784
+ end
785
+
786
+ ##
787
+ # Clears the list of exceptions for find_file. This must be called
788
+ # _before_ the exceptions are compiled.
789
+
790
+ def clear_exceptions
791
+ raise "exceptions already compiled" if defined? @exceptions
792
+ @exception_list.clear
793
+ nil
794
+ end
795
+
796
+ ##
797
+ # Return a compiled regexp of exceptions for find_files or nil if no
798
+ # filtering should take place. This regexp is generated from
799
+ # +exception_list+.
800
+
801
+ def exceptions
802
+ unless defined? @exceptions then
803
+ @exceptions = if @exception_list.empty? then
804
+ nil
805
+ else
806
+ Regexp.union(*@exception_list)
807
+ end
808
+ end
809
+
810
+ @exceptions
811
+ end
812
+
813
+ ############################################################
814
+ # Hooks:
815
+
816
+ ##
817
+ # Call the event hook named +name+, passing in optional args
818
+ # depending on the hook itself.
819
+ #
820
+ # Returns false if no hook handled the event.
821
+ #
822
+ # === Hook Writers!
823
+ #
824
+ # This executes all registered hooks <em>until one returns truthy</em>.
825
+ # Pay attention to the return value of your block!
826
+
827
+ def hook name, *args
828
+ deprecated = {
829
+ # none currently
830
+ }
831
+
832
+ if deprecated[name] and not HOOKS[name].empty? then
833
+ warn "hook #{name} has been deprecated, use #{deprecated[name]}"
834
+ end
835
+
836
+ HOOKS[name].any? { |plugin| plugin[self, *args] }
837
+ end
838
+
839
+ ##
840
+ # Add the supplied block to the available hooks, with the given
841
+ # name.
842
+
843
+ def self.add_hook name, &block
844
+ HOOKS[name] << block
845
+ end
846
+
847
+ add_hook :died do |at, err|
848
+ warn "Unhandled exception: #{err}"
849
+ warn err.backtrace.join("\n ")
850
+ warn "Quitting"
851
+ end
852
+ end