grosser-autotest 4.0.3

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