SlimTest 4.6.1.1

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,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