grosser-autotest 4.0.3

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.
data/README.markdown ADDED
@@ -0,0 +1,74 @@
1
+ Autotest is a continous testing facility meant to be used during
2
+ development.
3
+ As soon as you save a file, autotest will run the corresponding dependent tests.
4
+
5
+ Requirements
6
+ ============
7
+ * Ruby 1.6+, JRuby 1.1.2+, or rubinius
8
+ * Test::Unit or miniunit
9
+ * rubygems
10
+ * diff.exe on windoze. Try http://gnuwin32.sourceforge.net/packages.html
11
+
12
+ Install
13
+ =======
14
+ It is recommended to uninstall ZenTest first, otherwise I do not know what happens...
15
+ sudo gem uninstall ZenTest
16
+ You can install it from github, but then many solutions that build on autotest will fail unless you use `require 'grosser-autotest'`:
17
+ sudo gem install grosser-autotest -s http://gems.github.com
18
+ It may be better to install it from source:
19
+ git clone git://github.com/grosser/autotest.git
20
+ cd autotest
21
+ rake install
22
+
23
+ Setup
24
+ =====
25
+ ###Options
26
+ -f do not run all tests when starting
27
+ -c do not rerun all tests after all failed tests pass
28
+ TODO there are more...
29
+
30
+
31
+ TODO
32
+ ====
33
+ - add documentation for hooks / flags
34
+ - remove globals
35
+ - cleanup bin/autotest Dir hacks / passing of globals
36
+ - cleanup bin/unit_diff
37
+ - add gnome notification library
38
+
39
+
40
+ License
41
+ =======
42
+
43
+ ###This is only ripped from ZenTest
44
+ Ripper: [Michael Grosser](http://pragmatig.wordpress.com)
45
+
46
+ ### ZenTest Authors
47
+ - http://www.zenspider.com/ZSS/Products/ZenTest/
48
+ - http://rubyforge.org/projects/zentest/
49
+ - ryand-ruby@zenspider.com
50
+
51
+
52
+ (The MIT License)
53
+
54
+ Copyright (c) 2001-2006 Ryan Davis, Eric Hodel, Zen Spider Software
55
+
56
+ Permission is hereby granted, free of charge, to any person obtaining
57
+ a copy of this software and associated documentation files (the
58
+ "Software"), to deal in the Software without restriction, including
59
+ without limitation the rights to use, copy, modify, merge, publish,
60
+ distribute, sublicense, and/or sell copies of the Software, and to
61
+ permit persons to whom the Software is furnished to do so, subject to
62
+ the following conditions:
63
+
64
+ The above copyright notice and this permission notice shall be
65
+ included in all copies or substantial portions of the Software.
66
+
67
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
68
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
69
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
70
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
71
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
72
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
73
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
74
+
data/VERSION.yml ADDED
@@ -0,0 +1,4 @@
1
+ ---
2
+ :major: 4
3
+ :minor: 0
4
+ :patch: 3
data/bin/autotest ADDED
@@ -0,0 +1,52 @@
1
+ require 'rubygems'
2
+ require 'optparse'
3
+
4
+ options = {}
5
+ OptionParser.new do |opts|
6
+ opts.banner = <<BANNER
7
+ Continuouse testing for your ruby app.
8
+
9
+ Usage:
10
+ autotest [options]
11
+ BANNER
12
+ opts.on("-f", "--fast-start","Do not run full tests at start") { options[:no_full_after_start] = true }
13
+ opts.on("-c", "--no-full-after-failed","Do not run full tests after failed test passed") { options[:no_full_after_failed] = true }
14
+ opts.on("-v", "--verbose","Be verbose. Prints files that autotest doesn't know how to map to tests") { options[:verbose] = true }
15
+ opts.on("-q", "--quiet","Be quiet.") { options[:quiet] = true }
16
+ opts.on("-h", "--help","Show this.") { puts opts;exit }
17
+ end.parse!
18
+
19
+ #TODO remove this ? what does it do ?
20
+ class Dir
21
+ class << self
22
+ alias :old_index :[]
23
+ def [](*args)
24
+ $-w, old_warn = false, $-w
25
+ old_index(*args)
26
+ ensure
27
+ $-w = old_warn
28
+ end
29
+ end
30
+ end
31
+
32
+ #TODO pass options instead of globals
33
+ $v = options[:verbose]
34
+ $f = options[:no_full_after_start]
35
+ $c = options[:no_full_after_failed]
36
+ $q = options[:quiet]
37
+
38
+ #run the correct Autotest variant fitting to the local structure
39
+ require 'autotest'
40
+ target = Autotest
41
+ style = Autotest.autodiscover
42
+ unless style.empty? then
43
+ mod = "autotest/#{style.join("_")}"
44
+ puts "loading #{mod}" unless options[:quiet]
45
+ begin
46
+ require mod
47
+ rescue LoadError
48
+ abort "Autotest style #{mod} doesn't seem to exist. Aborting."
49
+ end
50
+ target = Autotest.const_get(style.map {|s| s.capitalize}.join)
51
+ end
52
+ target.run
data/bin/unit_diff ADDED
@@ -0,0 +1,37 @@
1
+ #!/usr/local/bin/ruby -ws
2
+ #
3
+ # unit_diff - a ruby unit test filter by Ryan Davis <ryand-ruby@zenspider.com>
4
+ #
5
+ # usage:
6
+ #
7
+ # test.rb | unit_diff [options]
8
+ # options:
9
+ # -b ignore whitespace differences
10
+ # -c contextual diff
11
+ # -h show usage
12
+ # -k keep temp diff files around
13
+ # -u unified diff
14
+ # -v display version
15
+
16
+ require 'unit_diff'
17
+
18
+ ############################################################
19
+
20
+ #TODO fix this...
21
+ if defined? $v then
22
+ puts "#{File.basename $0} v. #{ZenTest::VERSION}"
23
+ exit 0
24
+ end
25
+
26
+ if defined? $h then
27
+ File.open(__FILE__) do |f|
28
+ begin; end until f.readline =~ /usage:/
29
+ f.readline
30
+ while line = f.readline and line.sub!(/^# ?/, '')
31
+ $stderr.puts line
32
+ end
33
+ end
34
+ exit 0
35
+ end
36
+
37
+ UnitDiff.unit_diff
data/lib/autotest.rb ADDED
@@ -0,0 +1,661 @@
1
+ require 'find'
2
+ require 'rbconfig'
3
+
4
+ $v ||= false
5
+ $TESTING = false unless defined? $TESTING
6
+
7
+ ##
8
+ # Autotest continuously scans the files in your project for changes
9
+ # and runs the appropriate tests. Test failures are run until they
10
+ # have all passed. Then the full test suite is run to ensure that
11
+ # nothing else was inadvertantly broken.
12
+ #
13
+ # If you want Autotest to start over from the top, hit ^C once. If
14
+ # you want Autotest to quit, hit ^C twice.
15
+ #
16
+ # Rails:
17
+ #
18
+ # The autotest command will automatically discover a Rails directory
19
+ # by looking for config/environment.rb. When Rails is discovered,
20
+ # autotest uses RailsAutotest to perform file mappings and other work.
21
+ # See RailsAutotest for details.
22
+ #
23
+ # Plugins:
24
+ #
25
+ # Plugins are available by creating a .autotest file either in your
26
+ # project root or in your home directory. You can then write event
27
+ # handlers in the form of:
28
+ #
29
+ # Autotest.add_hook hook_name { |autotest| ... }
30
+ #
31
+ # The available hooks are listed in +ALL_HOOKS+.
32
+ #
33
+ # See example_dot_autotest.rb for more details.
34
+ #
35
+ # If a hook returns a true value, it signals to autotest that the hook
36
+ # was handled and should not continue executing hooks.
37
+ #
38
+ # Naming:
39
+ #
40
+ # Autotest uses a simple naming scheme to figure out how to map
41
+ # implementation files to test files following the Test::Unit naming
42
+ # scheme.
43
+ #
44
+ # * Test files must be stored in test/
45
+ # * Test files names must start with test_
46
+ # * Test class names must start with Test
47
+ # * Implementation files must be stored in lib/
48
+ # * Implementation files must match up with a test file named
49
+ # test_.*implementation.rb
50
+ #
51
+ # Strategy:
52
+ #
53
+ # 1. Find all files and associate them from impl <-> test.
54
+ # 2. Run all tests.
55
+ # 3. Scan for failures.
56
+ # 4. Detect changes in ANY (ruby?. file, rerun all failures + changed files.
57
+ # 5. Until 0 defects, goto 3.
58
+ # 6. When 0 defects, goto 2.
59
+
60
+ class Autotest
61
+
62
+ T0 = Time.at 0
63
+
64
+ ALL_HOOKS = [ :all_good, :died, :green, :initialize, :interrupt, :quit,
65
+ :ran_command, :red, :reset, :run_command, :updated, :waiting ]
66
+
67
+ RERUN_ALL_AFTER_FAILED_PASS = !$c
68
+
69
+
70
+ HOOKS = Hash.new { |h,k| h[k] = [] }
71
+ unless defined? WINDOZE then
72
+ WINDOZE = /win32/ =~ RUBY_PLATFORM
73
+ SEP = WINDOZE ? '&' : ';'
74
+ end
75
+
76
+ @@discoveries = []
77
+
78
+ ##
79
+ # Add a proc to the collection of discovery procs. See
80
+ # +autodiscover+.
81
+
82
+ def self.add_discovery &proc
83
+ @@discoveries << proc
84
+ end
85
+
86
+ ##
87
+ # Automatically find all potential autotest runner styles by
88
+ # searching your loadpath, vendor/plugins, and rubygems for
89
+ # "autotest/discover.rb". If found, that file is loaded and it
90
+ # should register discovery procs with autotest using
91
+ # +add_discovery+. That proc should return one or more strings
92
+ # describing the user's current environment. Those styles are then
93
+ # combined to dynamically invoke an autotest plugin to suite your
94
+ # environment. That plugin should define a subclass of Autotest with
95
+ # a corresponding name.
96
+ #
97
+ # === Process:
98
+ #
99
+ # 1. All autotest/discover.rb files loaded.
100
+ # 2. Those procs determine your styles (eg ["rails", "rspec"]).
101
+ # 3. Require file by sorting styles and joining (eg 'autotest/rails_rspec').
102
+ # 4. Invoke run method on appropriate class (eg Autotest::RailsRspec.run).
103
+ #
104
+ # === Example autotest/discover.rb:
105
+ #
106
+ # Autotest.add_discovery do
107
+ # "rails" if File.exist? 'config/environment.rb'
108
+ # end
109
+ #
110
+
111
+ def self.autodiscover
112
+ style = []
113
+
114
+ paths = $:.dup
115
+ paths.push 'lib'
116
+ paths.push(*Dir["vendor/plugins/*/lib"])
117
+
118
+ begin
119
+ require 'rubygems'
120
+ paths.push(*Gem.latest_load_paths)
121
+ rescue LoadError => e
122
+ # do nothing
123
+ end
124
+
125
+ paths.each do |d|
126
+ next unless File.directory? d
127
+ f = File.join(d, 'autotest', 'discover.rb')
128
+ if File.exist? f then
129
+ $: << d unless $:.include? d
130
+ load f
131
+ end
132
+ end
133
+
134
+ @@discoveries.map { |proc| proc.call }.flatten.compact.sort.uniq
135
+ end
136
+
137
+ ##
138
+ # Initialize and run the system.
139
+
140
+ def self.run
141
+ new.run
142
+ end
143
+
144
+ attr_writer :known_files
145
+ attr_accessor(:completed_re,
146
+ :extra_class_map,
147
+ :extra_files,
148
+ :failed_results_re,
149
+ :files_to_test,
150
+ :find_order,
151
+ :interrupted,
152
+ :last_mtime,
153
+ :libs,
154
+ :order,
155
+ :output,
156
+ :results,
157
+ :sleep,
158
+ :tainted,
159
+ :testlib,
160
+ :find_directories,
161
+ :unit_diff,
162
+ :wants_to_quit)
163
+
164
+ ##
165
+ # Initialize the instance and then load the user's .autotest file, if any.
166
+
167
+ def initialize
168
+ # these two are set directly because they're wrapped with
169
+ # add/remove/clear accessor methods
170
+ @exception_list = []
171
+ @test_mappings = []
172
+
173
+ self.completed_re = /\d+ tests, \d+ assertions, \d+ failures, \d+ errors/
174
+ self.extra_class_map = {}
175
+ self.extra_files = []
176
+ self.failed_results_re = /^\s+\d+\) (?:Failure|Error):\n(.*?)\((.*?)\)/
177
+ self.files_to_test = new_hash_of_arrays
178
+ self.find_order = []
179
+ self.known_files = nil
180
+ self.libs = %w[. lib test].join(File::PATH_SEPARATOR)
181
+ self.order = :random
182
+ self.output = $stderr
183
+ self.sleep = 1
184
+ self.testlib = "test/unit"
185
+ self.find_directories = ['.']
186
+ self.unit_diff = "unit_diff -u"
187
+
188
+ self.add_mapping(/^lib\/.*\.rb$/) do |filename, _|
189
+ possible = File.basename(filename).gsub '_', '_?'
190
+ files_matching %r%^test/.*#{possible}$%
191
+ end
192
+
193
+ self.add_mapping(/^test.*\/test_.*rb$/) do |filename, _|
194
+ filename
195
+ end
196
+
197
+ [File.expand_path('~/.autotest'), './.autotest'].each do |f|
198
+ load f if File.exist? f
199
+ end
200
+ end
201
+
202
+ ##
203
+ # Repeatedly run failed tests, then all tests, then wait for changes
204
+ # and carry on until killed.
205
+
206
+ def run
207
+ hook :initialize
208
+ reset
209
+ add_sigint_handler
210
+
211
+ self.last_mtime = Time.now if $f
212
+
213
+ loop do # ^c handler
214
+ begin
215
+ get_to_green
216
+ if self.tainted and RERUN_ALL_AFTER_FAILED_PASS then
217
+ rerun_all_tests
218
+ else
219
+ hook :all_good
220
+ end
221
+ wait_for_changes
222
+ rescue Interrupt
223
+ break if self.wants_to_quit
224
+ reset
225
+ end
226
+ end
227
+ hook :quit
228
+ rescue Exception
229
+ hook :died
230
+ end
231
+
232
+ ##
233
+ # Keep running the tests after a change, until all pass.
234
+
235
+ def get_to_green
236
+ begin
237
+ run_tests
238
+ wait_for_changes unless all_good
239
+ end until all_good
240
+ end
241
+
242
+ ##
243
+ # Look for files to test then run the tests and handle the results.
244
+
245
+ def run_tests
246
+ hook :run_command
247
+
248
+ new_mtime = self.find_files_to_test
249
+ return unless new_mtime
250
+ self.last_mtime = new_mtime
251
+
252
+ cmd = self.make_test_cmd self.files_to_test
253
+ return if cmd.empty?
254
+
255
+ puts cmd unless $q
256
+
257
+ old_sync = $stdout.sync
258
+ $stdout.sync = true
259
+ self.results = []
260
+ line = []
261
+ begin
262
+ open("| #{cmd}", "r") do |f|
263
+ until f.eof? do
264
+ c = f.getc
265
+ putc c
266
+ line << c
267
+ if c == ?\n then
268
+ self.results << if RUBY_VERSION >= "1.9" then
269
+ line.join
270
+ else
271
+ line.pack "c*"
272
+ end
273
+ line.clear
274
+ end
275
+ end
276
+ end
277
+ ensure
278
+ $stdout.sync = old_sync
279
+ end
280
+ hook :ran_command
281
+ self.results = self.results.join
282
+
283
+ handle_results(self.results)
284
+ end
285
+
286
+ ############################################################
287
+ # Utility Methods, not essential to reading of logic
288
+
289
+ ##
290
+ # Installs a sigint handler.
291
+
292
+ def add_sigint_handler
293
+ trap 'INT' do
294
+ if self.interrupted then
295
+ self.wants_to_quit = true
296
+ else
297
+ unless hook :interrupt then
298
+ puts "Interrupt a second time to quit"
299
+ self.interrupted = true
300
+ Kernel.sleep 1.5
301
+ end
302
+ raise Interrupt, nil # let the run loop catch it
303
+ end
304
+ end
305
+ end
306
+
307
+ ##
308
+ # If there are no files left to test (because they've all passed),
309
+ # then all is good.
310
+
311
+ def all_good
312
+ files_to_test.empty?
313
+ end
314
+
315
+ ##
316
+ # Convert a path in a string, s, into a class name, changing
317
+ # underscores to CamelCase, etc.
318
+
319
+ def path_to_classname(s)
320
+ sep = File::SEPARATOR
321
+ f = s.sub(/^test#{sep}/, '').sub(/\.rb$/, '').split(sep)
322
+ f = f.map { |path| path.split(/_|(\d+)/).map { |seg| seg.capitalize }.join }
323
+ f = f.map { |path| path =~ /^Test/ ? path : "Test#{path}" }
324
+ f.join('::')
325
+ end
326
+
327
+ ##
328
+ # Returns a hash mapping a file name to the known failures for that
329
+ # file.
330
+
331
+ def consolidate_failures(failed)
332
+ filters = new_hash_of_arrays
333
+
334
+ class_map = Hash[*self.find_order.grep(/^test/).map { |f| # TODO: ugly
335
+ [path_to_classname(f), f]
336
+ }.flatten]
337
+ class_map.merge!(self.extra_class_map)
338
+
339
+ failed.each do |method, klass|
340
+ if class_map.has_key? klass then
341
+ filters[class_map[klass]] << method
342
+ else
343
+ output.puts "Unable to map class #{klass} to a file"
344
+ end
345
+ end
346
+
347
+ return filters
348
+ end
349
+
350
+ ##
351
+ # Find the files to process, ignoring temporary files, source
352
+ # configuration management files, etc., and return a Hash mapping
353
+ # filename to modification time.
354
+
355
+ def find_files
356
+ result = {}
357
+ targets = self.find_directories + self.extra_files
358
+ self.find_order.clear
359
+
360
+ targets.each do |target|
361
+ order = []
362
+ Find.find(target) do |f|
363
+ Find.prune if f =~ self.exceptions
364
+
365
+ next if test ?d, f
366
+ next if f =~ /(swp|~|rej|orig)$/ # temporary/patch files
367
+ next if f =~ /\/\.?#/ # Emacs autosave/cvs merge files
368
+
369
+ filename = f.sub(/^\.\//, '')
370
+
371
+ result[filename] = File.stat(filename).mtime rescue next
372
+ order << filename
373
+ end
374
+ self.find_order.push(*order.sort)
375
+ end
376
+
377
+ return result
378
+ end
379
+
380
+ ##
381
+ # Find the files which have been modified, update the recorded
382
+ # timestamps, and use this to update the files to test. Returns true
383
+ # if any file is newer than the previously recorded most recent
384
+ # file.
385
+
386
+ def find_files_to_test(files=find_files)
387
+ updated = files.select { |filename, mtime| self.last_mtime < mtime }
388
+
389
+ p updated if $v unless updated.empty? || self.last_mtime.to_i == 0
390
+
391
+ hook :updated, updated unless updated.empty? || self.last_mtime.to_i == 0
392
+
393
+ updated.map { |f,m| test_files_for(f) }.flatten.uniq.each do |filename|
394
+ self.files_to_test[filename] # creates key with default value
395
+ end
396
+
397
+ if updated.empty? then
398
+ nil
399
+ else
400
+ files.values.max
401
+ end
402
+ end
403
+
404
+ ##
405
+ # Check results for failures, set the "bar" to red or green, and if
406
+ # there are failures record this.
407
+
408
+ def handle_results(results)
409
+ failed = results.scan(self.failed_results_re)
410
+ completed = results =~ self.completed_re
411
+
412
+ self.files_to_test = consolidate_failures failed if completed
413
+
414
+ color = completed && self.files_to_test.empty? ? :green : :red
415
+ hook color unless $TESTING
416
+
417
+ self.tainted = true unless self.files_to_test.empty?
418
+ end
419
+
420
+ ##
421
+ # Lazy accessor for the known_files hash.
422
+
423
+ def known_files
424
+ unless @known_files then
425
+ @known_files = Hash[*find_order.map { |f| [f, true] }.flatten]
426
+ end
427
+ @known_files
428
+ end
429
+
430
+ ##
431
+ # Generate the commands to test the supplied files
432
+
433
+ def make_test_cmd files_to_test
434
+ cmds = []
435
+ full, partial = reorder(files_to_test).partition { |k,v| v.empty? }
436
+
437
+ unless full.empty? then
438
+ classes = full.map {|k,v| k}.flatten.uniq
439
+ classes.unshift testlib
440
+ cmds << "#{ruby} -I#{libs} -rubygems -e \"%w[#{classes.join(' ')}].each { |f| require f }\" | #{unit_diff}"
441
+ end
442
+
443
+ partial.each do |klass, methods|
444
+ regexp = Regexp.union(*methods).source
445
+ cmds << "#{ruby} -I#{libs} #{klass} -n \"/^(#{regexp})$/\" | #{unit_diff}"
446
+ end
447
+
448
+ return cmds.join("#{SEP} ")
449
+ end
450
+
451
+ def new_hash_of_arrays
452
+ Hash.new { |h,k| h[k] = [] }
453
+ end
454
+
455
+ def reorder files_to_test
456
+ case self.order
457
+ when :alpha then
458
+ files_to_test.sort_by { |k,v| k }
459
+ when :reverse then
460
+ files_to_test.sort_by { |k,v| k }.reverse
461
+ when :random then
462
+ max = files_to_test.size
463
+ files_to_test.sort_by { |k,v| rand(max) }
464
+ when :natural then
465
+ (self.find_order & files_to_test.keys).map { |f| [f, files_to_test[f]] }
466
+ else
467
+ raise "unknown order type: #{self.order.inspect}"
468
+ end
469
+ end
470
+
471
+ ##
472
+ # Rerun the tests from cold (reset state)
473
+
474
+ def rerun_all_tests
475
+ reset
476
+ run_tests
477
+
478
+ hook :all_good if all_good
479
+ end
480
+
481
+ ##
482
+ # Clear all state information about test failures and whether
483
+ # interrupts will kill autotest.
484
+
485
+ def reset
486
+ self.files_to_test.clear
487
+ self.find_order.clear
488
+ self.interrupted = false
489
+ self.known_files = nil
490
+ self.last_mtime = T0
491
+ self.tainted = false
492
+ self.wants_to_quit = false
493
+
494
+ hook :reset
495
+ end
496
+
497
+ ##
498
+ # Determine and return the path of the ruby executable.
499
+
500
+ def ruby
501
+ ruby = ENV['RUBY']
502
+ ruby ||= File.join(Config::CONFIG['bindir'],
503
+ Config::CONFIG['ruby_install_name'])
504
+
505
+ ruby.gsub! File::SEPARATOR, File::ALT_SEPARATOR if File::ALT_SEPARATOR
506
+
507
+ return ruby
508
+ end
509
+
510
+ ##
511
+ # Return the name of the file with the tests for filename by finding
512
+ # a +test_mapping+ that matches the file and executing the mapping's
513
+ # proc.
514
+
515
+ def test_files_for(filename)
516
+ result = @test_mappings.find { |file_re, ignored| filename =~ file_re }
517
+
518
+ p :test_file_for => [filename, result.first] if result and $DEBUG
519
+
520
+ result = result.nil? ? [] : Array(result.last.call(filename, $~))
521
+
522
+ output.puts "No tests matched #{filename}" if
523
+ ($v or $TESTING) and result.empty?
524
+
525
+ result.sort.uniq.select { |f| known_files[f] }
526
+ end
527
+
528
+ ##
529
+ # Sleep then look for files to test, until there are some.
530
+
531
+ def wait_for_changes
532
+ hook :waiting
533
+ Kernel.sleep self.sleep until find_files_to_test
534
+ end
535
+
536
+ ############################################################
537
+ # File Mappings:
538
+
539
+ ##
540
+ # Returns all known files in the codebase matching +regexp+.
541
+
542
+ def files_matching regexp
543
+ self.find_order.select { |k| k =~ regexp }
544
+ end
545
+
546
+ ##
547
+ # Adds a file mapping. +regexp+ should match a file path in the
548
+ # codebase. +proc+ is passed a matched filename and
549
+ # Regexp.last_match. +proc+ should return an array of tests to run.
550
+ #
551
+ # For example, if test_helper.rb is modified, rerun all tests:
552
+ #
553
+ # at.add_mapping(/test_helper.rb/) do |f, _|
554
+ # at.files_matching(/^test.*rb$/)
555
+ # end
556
+
557
+ def add_mapping(regexp, &proc)
558
+ @test_mappings << [regexp, proc]
559
+ nil
560
+ end
561
+
562
+ ##
563
+ # Removed a file mapping matching +regexp+.
564
+
565
+ def remove_mapping regexp
566
+ @test_mappings.delete_if do |k,v|
567
+ k == regexp
568
+ end
569
+ nil
570
+ end
571
+
572
+ ##
573
+ # Clears all file mappings. This is DANGEROUS as it entirely
574
+ # disables autotest. You must add at least one file mapping that
575
+ # does a good job of rerunning appropriate tests.
576
+
577
+ def clear_mappings
578
+ @test_mappings.clear
579
+ nil
580
+ end
581
+
582
+ ############################################################
583
+ # Exceptions:
584
+
585
+ ##
586
+ # Adds +regexp+ to the list of exceptions for find_file. This must
587
+ # be called _before_ the exceptions are compiled.
588
+
589
+ def add_exception regexp
590
+ raise "exceptions already compiled" if defined? @exceptions
591
+ @exception_list << regexp
592
+ nil
593
+ end
594
+
595
+ ##
596
+ # Removes +regexp+ to the list of exceptions for find_file. This
597
+ # must be called _before_ the exceptions are compiled.
598
+
599
+ def remove_exception regexp
600
+ raise "exceptions already compiled" if defined? @exceptions
601
+ @exception_list.delete regexp
602
+ nil
603
+ end
604
+
605
+ ##
606
+ # Clears the list of exceptions for find_file. This must be called
607
+ # _before_ the exceptions are compiled.
608
+
609
+ def clear_exceptions
610
+ raise "exceptions already compiled" if defined? @exceptions
611
+ @exception_list.clear
612
+ nil
613
+ end
614
+
615
+ ##
616
+ # Return a compiled regexp of exceptions for find_files or nil if no
617
+ # filtering should take place. This regexp is generated from
618
+ # +exception_list+.
619
+
620
+ def exceptions
621
+ unless defined? @exceptions then
622
+ if @exception_list.empty? then
623
+ @exceptions = nil
624
+ else
625
+ @exceptions = Regexp.union(*@exception_list)
626
+ end
627
+ end
628
+
629
+ @exceptions
630
+ end
631
+
632
+ ############################################################
633
+ # Hooks:
634
+
635
+ ##
636
+ # Call the event hook named +name+, executing all registered hooks
637
+ # until one returns true. Returns false if no hook handled the
638
+ # event.
639
+
640
+ def hook(name, *args)
641
+ deprecated = {
642
+ # none currently
643
+ }
644
+
645
+ if deprecated[name] and not HOOKS[name].empty? then
646
+ warn "hook #{name} has been deprecated, use #{deprecated[name]}"
647
+ end
648
+
649
+ HOOKS[name].any? do |plugin|
650
+ plugin[self, *args]
651
+ end
652
+ end
653
+
654
+ ##
655
+ # Add the supplied block to the available hooks, with the given
656
+ # name.
657
+
658
+ def self.add_hook(name, &block)
659
+ HOOKS[name] << block
660
+ end
661
+ end