autotest-standalone 4.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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