autotest-standalone 4.5.0

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