This file is indexed.

/usr/lib/ruby/vendor_ruby/autotest.rb is in ruby-zentest 4.11.0-2.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
require "find"
require "rbconfig"

##
# Autotest continuously scans the files in your project for changes
# and runs the appropriate tests.  Test failures are run until they
# have all passed. Then the full test suite is run to ensure that
# nothing else was inadvertantly broken.
#
# If you want Autotest to start over from the top, hit ^C once.  If
# you want Autotest to quit, hit ^C twice.
#
# Rails:
#
# The autotest command will automatically discover a Rails directory
# by looking for config/environment.rb. When Rails is discovered,
# autotest uses RailsAutotest to perform file mappings and other work.
# See RailsAutotest for details.
#
# Plugins:
#
# Plugins are available by creating a .autotest file either in your
# project root or in your home directory. You can then write event
# handlers in the form of:
#
#   Autotest.add_hook hook_name { |autotest| ... }
#
# The available hooks are listed in +ALL_HOOKS+.
#
# See example_dot_autotest.rb for more details.
#
# If a hook returns a true value, it signals to autotest that the hook
# was handled and should not continue executing hooks.
#
# Naming:
#
# Autotest uses a simple naming scheme to figure out how to map
# implementation files to test files following the Test::Unit naming
# scheme.
#
# * Test files must be stored in test/
# * Test files names must start with test_
# * Test class names must start with Test
# * Implementation files must be stored in lib/
# * Implementation files must match up with a test file named
#   test_.*<impl-name>.rb
#
# Strategy:
#
# 1. Find all files and associate them from impl <-> test.
# 2. Run all tests.
# 3. Scan for failures.
# 4. Detect changes in ANY (ruby?. file, rerun all failures + changed files.
# 5. Until 0 defects, goto 3.
# 6. When 0 defects, goto 2.

class Autotest

  RUBY19 = defined? Encoding

  T0 = Time.at 0

  ALL_HOOKS = [ :all_good, :died, :green, :initialize,
                :post_initialize, :interrupt, :quit, :ran_command,
                :red, :reset, :run_command, :updated, :waiting ]

  def self.options
    @@options ||= {}
  end

  def options
    self.class.options
  end

  HOOKS = Hash.new { |h,k| h[k] = [] }

  unless defined? WINDOZE then
    WINDOZE = /mswin|mingw/ =~ RbConfig::CONFIG['host_os']
    SEP = WINDOZE ? '&' : ';'
  end

  @@discoveries = []

  def self.parse_options args = ARGV
    require 'optparse'
    options = {
      :args => args.dup
    }

    OptionParser.new do |opts|
      opts.banner = <<-BANNER.gsub(/^        /, '')
        Continuous testing for your ruby app.

          Autotest automatically tests code that has changed. It assumes
          the code is in lib, and tests are in test/test_*.rb. Autotest
          uses plugins to control what happens. You configure plugins
          with require statements in the .autotest file in your
          project base directory, and a default configuration for all
          your projects in the .autotest file in your home directory.

        Usage:
            autotest [options]
      BANNER

      opts.on "-f", "--fast-start", "Do not run full tests at start" do
        options[:no_full_after_start] = true
      end

      opts.on("-c", "--no-full-after-failed",
              "Do not run all tests on red->green") do
        options[:no_full_after_failed] = true
      end

      opts.on "-d", "--debug", "Debug mode, for reporting bugs." do
        require "pp"
        options[:debug] = true
      end

      opts.on "-v", "--verbose", "Be annoyingly verbose (debugs .autotest)." do
        options[:verbose] = true
      end

      opts.on "-q", "--quiet", "Be quiet." do
        options[:quiet] = true
      end

      opts.on("-r", "--rc CONF", String, "Override path to config file") do |o|
        options[:rc] = Array(o)
      end

      opts.on("-s", "--style STYLE", String,
              "Manually specify test style. (default: autodiscover)") do |style|
        options[:style] = Array(style)
      end

      opts.on("-w", "--warnings", "Turn on ruby warnings") do
        $-w = true
      end

      opts.on "-h", "--help", "Show this." do
        puts opts
        exit 1
      end
    end.parse! args

    Autotest.options.merge! options

    options
  end

  ##
  # Calculates the autotest runner to use to run the tests.
  #
  # Can be overridden with --style, otherwise uses ::autodiscover.

  def self.runner
    style = options[:style] || Autotest.autodiscover
    target = Autotest

    unless style.empty? then
      mod = "autotest/#{style.join "_"}"
      puts "loading #{mod}"
      begin
        require mod
      rescue LoadError
        abort "Autotest style #{mod} doesn't seem to exist. Aborting."
      end
      target = Autotest.const_get(style.map {|s| s.capitalize}.join)
    end

    target
  end

  ##
  # Add a proc to the collection of discovery procs. See
  # +autodiscover+.

  def self.add_discovery &proc
    @@discoveries << proc
  end

  ##
  # Automatically find all potential autotest runner styles by
  # searching your loadpath, vendor/plugins, and rubygems for
  # "autotest/discover.rb". If found, that file is loaded and it
  # should register discovery procs with autotest using
  # +add_discovery+. That proc should return one or more strings
  # describing the user's current environment. Those styles are then
  # combined to dynamically invoke an autotest plugin to suite your
  # environment. That plugin should define a subclass of Autotest with
  # a corresponding name.
  #
  # === Process:
  #
  # 1. All autotest/discover.rb files loaded.
  # 2. Those procs determine your styles (eg ["rails", "rspec"]).
  # 3. Require file by sorting styles and joining (eg 'autotest/rails_rspec').
  # 4. Invoke run method on appropriate class (eg Autotest::RailsRspec.run).
  #
  # === Example autotest/discover.rb:
  #
  #   Autotest.add_discovery do
  #     "rails" if File.exist? 'config/environment.rb'
  #   end
  #

  def self.autodiscover
# Remove rubygems requirement by commenting out
#    require 'rubygems'

    # *sigh*
    #
    # This is needed for rspec's hacky discovery mechanism. For some
    # reason rspec2 added generators that create
    # "autotest/discover.rb" right in the project directory instead of
    # keeping it in the rspec gem and properly deciding that the
    # project is an rspec based project or not. See the url for more
    # details:
    #
    # http://rubyforge.org/tracker/?func=detail&atid=1678&aid=28775&group_id=419
    #
    # For the record, the sane way to do it is the bacon way:
    #
    # "Since version 1.0, there is autotest support. You need to tag
    # your test directories (test/ or spec/) by creating an .bacon
    # file there. Autotest then will find it."
    #
    # I'm submitting a counter-patch to rspec to fix stuff properly,
    # but for now I'm stuck with this because their brokenness is
    # documented in multiple books.
    #
    # I'm removing this code once a sane rspec goes out.

    hacky_discovery = Gem::Specification.any? { |s| s.name =~ /^rspec/ }
    $: << '.' if hacky_discovery

    Gem.find_files("autotest/discover*").each do |f|
      load f
    end

    # call all discovery procs and determine the style to use
    @@discoveries.map{ |proc| proc.call }.flatten.compact.sort.uniq
  end

  ##
  # Initialize and run the system.

  def self.run
    new.run
  end

  attr_writer :known_files
  attr_accessor :completed_re
  attr_accessor :extra_class_map
  attr_accessor :extra_files
  attr_accessor :failed_results_re
  attr_accessor :files_to_test
  attr_accessor :find_directories
  attr_accessor :find_order
  attr_accessor :interrupted
  attr_accessor :last_mtime
  attr_accessor :latest_results
  attr_accessor :libs
  attr_accessor :order # TODO: deprecate and remove
  attr_accessor :output
  attr_accessor :prefix
  attr_accessor :results
  attr_accessor :sleep
  attr_accessor :tainted
  attr_accessor :test_mappings
  attr_accessor :testlib
  attr_accessor :testprefix
  attr_accessor :unit_diff
  attr_accessor :wants_to_quit

  alias tainted? tainted

  ##
  # Initialize the instance and then load the user's .autotest file, if any.

  def initialize
    # these two are set directly because they're wrapped with
    # add/remove/clear accessor methods
    @exception_list = []
    @child = nil
    self.test_mappings = []

    self.completed_re =
      /\d+ (tests|runs), \d+ assertions, \d+ failures, \d+ errors(, \d+ skips)?/
    self.extra_class_map   = {}
    self.extra_files       = []
    self.failed_results_re = /^\s*\d+\) (?:Failure|Error):\n(.*?)(?: [\(\[](.*?)[\)\]])?:$/
    self.files_to_test     = new_hash_of_arrays
    self.find_order        = []
    self.known_files       = nil
    self.libs              = %w[. lib test].join(File::PATH_SEPARATOR)
    self.order             = :random
    self.output            = $stderr
    self.prefix            = nil
    self.sleep             = 1
    self.testlib           = "minitest/autorun" # TODO: rename
    self.testprefix        = "gem 'minitest'" # TODO: rename

    specified_directories  = ARGV.reject { |arg| arg.start_with?("-") } # options are not directories
    self.find_directories  = specified_directories.empty? ? ['.'] : specified_directories
    self.unit_diff         = nil
    self.latest_results    = nil

    # file in /lib -> run test in /test
    self.add_mapping(/^lib\/.*\.rb$/) do |filename, _|
      possible = File.basename(filename).gsub '_', '_?'
      files_matching %r%^test/.*#{possible}$%
    end

    # file in /test -> run it
    self.add_mapping(/^test.*\/test_.*rb$/) do |filename, _|
      filename
    end

    default_configs = [File.expand_path('~/.autotest'), './.autotest']
    configs = options[:rc] || default_configs

    configs.each do |f|
      load f if File.exist? f
    end
  end

  def debug
    find_files_to_test

    puts "Known test files:"
    puts
    pp files_to_test.keys.sort

    class_map = self.class_map

    puts
    puts "Known class map:"
    puts
    pp class_map
  end

  def class_map
    class_map = Hash[*self.find_order.grep(/^test/).map { |f| # TODO: ugly
                       [path_to_classname(f), f]
                     }.flatten]
    class_map.merge! self.extra_class_map
    class_map
  end

  ##
  # Repeatedly run failed tests, then all tests, then wait for changes
  # and carry on until killed.

  def run
    hook :initialize
    hook :post_initialize

    reset
    add_sigint_handler

    self.last_mtime = Time.now if options[:no_full_after_start]

    self.debug if options[:debug]

    loop do
      begin # ^c handler
        get_to_green
        if tainted? and not options[:no_full_after_failed] then
          rerun_all_tests
        else
          hook :all_good
        end
        wait_for_changes
      rescue Interrupt
        break if wants_to_quit
        reset
      end
    end
    hook :quit
    puts
  rescue Exception => err
    hook(:died, err) or (
      warn "Unhandled exception: #{err}"
      warn err.backtrace.join("\n  ")
      warn "Quitting"
    )
  end

  ##
  # Keep running the tests after a change, until all pass.

  def get_to_green
    begin
      run_tests
      wait_for_changes unless all_good
    end until all_good
  end

  ##
  # Look for files to test then run the tests and handle the results.

  def run_tests
    new_mtime = self.find_files_to_test
    return unless new_mtime
    self.last_mtime = new_mtime

    cmd = self.make_test_cmd self.files_to_test
    return if cmd.empty?

    hook :run_command, cmd

    puts cmd unless options[:quiet]

    old_sync = $stdout.sync
    $stdout.sync = true
    self.results = []
    line = []
    begin
      open "| #{cmd}", "r" do |f|
        until f.eof? do
          c = f.getc or break
          if RUBY19 then
            print c
          else
            putc c
          end
          line << c
          if c == ?\n then
            self.results << if RUBY19 then
                              line.join
                            else
                              line.pack "c*"
                            end
            line.clear
          end
        end
      end
    ensure
      $stdout.sync = old_sync
    end
    hook :ran_command
    self.results = self.results.join

    handle_results self.results
  end

  ############################################################
  # Utility Methods, not essential to reading of logic

  ##
  # Installs a sigint handler.

  def add_sigint_handler
    trap 'INT' do
      Process.kill "KILL", @child if @child

      if self.interrupted then
        self.wants_to_quit = true
      else
        unless hook :interrupt then
          puts "Interrupt a second time to quit"
          self.interrupted = true
          Kernel.sleep 1.5
        end
        raise Interrupt, nil # let the run loop catch it
      end
    end
  end

  ##
  # Installs a sigquit handler

  def add_sigquit_handler
    trap 'QUIT' do
      restart
    end
  end

  def restart
    Process.kill "KILL", @child if @child

    cmd = [$0, *options[:args]]

    index = $LOAD_PATH.index RbConfig::CONFIG["sitelibdir"]

    if index then
      extra = $LOAD_PATH[0...index]
      cmd = [Gem.ruby, "-I", extra.join(":")] + cmd
    end

    puts cmd.join(" ") if options[:verbose]

    exec(*cmd)
  end

  ##
  # If there are no files left to test (because they've all passed),
  # then all is good.

  def all_good
    files_to_test.empty?
  end

  ##
  # Convert a path in a string, s, into a class name, changing
  # underscores to CamelCase, etc.

  def path_to_classname s
    sep = File::SEPARATOR
    f = s.sub(/^test#{sep}/, '').sub(/\.rb$/, '').split sep
    f = f.map { |path| path.split(/_|(\d+)/).map { |seg| seg.capitalize }.join }
    f = f.map { |path| path =~ /^Test/ ? path : "Test#{path}"  }

    f.join '::'
  end

  ##
  # Returns a hash mapping a file name to the known failures for that
  # file.

  def consolidate_failures failed
    filters = new_hash_of_arrays

    class_map = self.class_map

    failed.each do |method, klass|
      if class_map.has_key? klass then
        filters[class_map[klass]] << method
      else
        output.puts "Unable to map class #{klass} to a file"
      end
    end

    filters
  end

  ##
  # Find the files to process, ignoring temporary files, source
  # configuration management files, etc., and return a Hash mapping
  # filename to modification time.

  def find_files
    result = {}
    targets = self.find_directories + self.extra_files
    self.find_order.clear

    targets.each do |target|
      order = []
      Find.find target do |f|
        Find.prune if f =~ self.exceptions
        Find.prune if f =~ /^\.\/tmp/    # temp dir, used by isolate

        next unless File.file? f
        next if f =~ /(swp|~|rej|orig)$/ # temporary/patch files
        next if f =~ /(,v)$/             # RCS files
        next if f =~ /\/\.?#/            # Emacs autosave/cvs merge files

        filename = f.sub(/^\.\//, '')

        result[filename] = File.stat(filename).mtime rescue next
        order << filename
      end
      self.find_order.push(*order.sort)
    end

    result
  end

  ##
  # Find the files which have been modified, update the recorded
  # timestamps, and use this to update the files to test. Returns
  # the latest mtime of the files modified or nil when nothing was
  # modified.

  def find_files_to_test files = find_files
    updated = files.select { |filename, mtime| self.last_mtime < mtime }

    # nothing to update or initially run
    unless updated.empty? || self.last_mtime.to_i == 0 then
      p updated if options[:verbose]

      hook :updated, updated
    end

    updated.map { |f,m| test_files_for f }.flatten.uniq.each do |filename|
      self.files_to_test[filename] # creates key with default value
    end

    if updated.empty? then
      nil
    else
      files.values.max
    end
  end

  ##
  # Check results for failures, set the "bar" to red or green, and if
  # there are failures record this.

  def handle_results results
    results = results.gsub(/\e\[\d+m/, '') # strip ascii color
    failed = results.scan(self.failed_results_re).map { |m, k|
      k, m = $1, $2 if m =~ /(\w+)\#(\w+)/ # minitest 5 output
      [m, k]
    }

    completed = results[self.completed_re]

    if completed then
      completed = completed.scan(/(\d+) (\w+)/).map { |v, k| [k, v.to_i] }

      self.latest_results = Hash[*completed.flatten]
      self.files_to_test  = consolidate_failures failed

      color = failed.empty? ? :green : :red
      hook color
    else
      self.latest_results = nil
    end

    self.tainted = true unless self.files_to_test.empty?
  end

  ##
  # Lazy accessor for the known_files hash.

  def known_files
    unless @known_files then
      @known_files = {}
      find_order.each {|f| @known_files[f] = true }
    end
    @known_files
  end

  ##
  # Generate the commands to test the supplied files

  def make_test_cmd files_to_test
    if options[:debug] then
      puts "Files to test:"
      puts
      pp files_to_test
      puts
    end

    cmds = []
    full, partial = reorder(files_to_test).partition { |k,v| v.empty? }

    diff = self.unit_diff
    diff = " | #{diff}" if diff and diff !~ /^\|/

    unless full.empty? then
      classes = full.map {|k,v| k}.flatten.uniq
      classes.unshift testlib
      classes = classes.join " "
      cmds << "#{ruby_cmd} -e \"#{testprefix}; %w[#{classes}].each { |f| require f }\"#{diff}"
    end

    partial.each do |klass, methods|
      regexp = Regexp.union(*methods).source
      cmds << "#{ruby_cmd} #{klass} -n \"/^(#{regexp})$/\"#{diff}"
    end

    cmds.join "#{SEP} "
  end

  def new_hash_of_arrays
    Hash.new { |h,k| h[k] = [] }
  end

  def reorder files_to_test
    case self.order
    when :alpha then
      files_to_test.sort_by { |k,v| k }
    when :reverse then
      files_to_test.sort_by { |k,v| k }.reverse
    when :random then
      max = files_to_test.size
      files_to_test.sort_by { |k,v| rand max }
    when :natural then
      (self.find_order & files_to_test.keys).map { |f| [f, files_to_test[f]] }
    else
      raise "unknown order type: #{self.order.inspect}"
    end
  end

  ##
  # Rerun the tests from cold (reset state)

  def rerun_all_tests
    reset
    run_tests

    hook :all_good if all_good
  end

  ##
  # Clear all state information about test failures and whether
  # interrupts will kill autotest.

  def reset
    self.files_to_test.clear
    self.find_order.clear

    self.interrupted   = false
    self.known_files   = nil
    self.last_mtime    = T0
    self.tainted       = false
    self.wants_to_quit = false

    hook :reset
  end

  ##
  # Determine and return the path of the ruby executable.

  def ruby
    ruby = ENV['RUBY']
    ruby ||= File.join(RbConfig::CONFIG['bindir'],
                       RbConfig::CONFIG['ruby_install_name'])

    ruby.gsub! File::SEPARATOR, File::ALT_SEPARATOR if File::ALT_SEPARATOR

    return ruby
  end

  ##
  # Returns the base of the ruby command.

  def ruby_cmd
    "#{prefix}#{ruby} -I#{libs} -rubygems"
  end

  ##
  # Return the name of the file with the tests for filename by finding
  # a +test_mapping+ that matches the file and executing the mapping's
  # proc.

  def test_files_for filename
    result = []

    self.test_mappings.each do |file_re, proc|
      if filename =~ file_re then
        result = [proc.call(filename, $~)].
          flatten.sort.uniq.select { |f| known_files[f] }
        break unless result.empty?
      end
    end

    pp :test_file_for => [filename, result.first] if result and options[:debug]

    output.puts "No tests matched #{filename}" if
      options[:verbose] and result.empty?

    return result
  end

  ##
  # Sleep then look for files to test, until there are some.

  def wait_for_changes
    hook :waiting
    Kernel.sleep self.sleep until find_files_to_test
  end

  ############################################################
  # File Mappings:

  ##
  # Returns all known files in the codebase matching +regexp+.

  def files_matching regexp
    self.find_order.select { |k| k =~ regexp }
  end

  ##
  # Adds a file mapping, optionally prepending the mapping to the
  # front of the list if +prepend+ is true. +regexp+ should match a
  # file path in the codebase. +proc+ is passed a matched filename and
  # Regexp.last_match. +proc+ should return an array of tests to run.
  #
  # For example, if test_helper.rb is modified, rerun all tests:
  #
  #   at.add_mapping(/test_helper.rb/) do |f, _|
  #     at.files_matching(/^test.*rb$/)
  #   end

  def add_mapping regexp, prepend = false, &proc
    if prepend then
      @test_mappings.unshift [regexp, proc]
    else
      @test_mappings.push [regexp, proc]
    end
    nil
  end

  ##
  # Removed a file mapping matching +regexp+.

  def remove_mapping regexp
    @test_mappings.delete_if do |k,v|
      k == regexp
    end
    nil
  end

  ##
  # Clears all file mappings. This is DANGEROUS as it entirely
  # disables autotest. You must add at least one file mapping that
  # does a good job of rerunning appropriate tests.

  def clear_mappings
    @test_mappings.clear
    nil
  end

  ############################################################
  # Exceptions:

  ##
  # Adds +regexp+ to the list of exceptions for find_file. This must
  # be called _before_ the exceptions are compiled.

  def add_exception regexp
    raise "exceptions already compiled" if defined? @exceptions

    @exception_list << regexp
    nil
  end

  ##
  # Removes +regexp+ to the list of exceptions for find_file. This
  # must be called _before_ the exceptions are compiled.

  def remove_exception regexp
    raise "exceptions already compiled" if defined? @exceptions
    @exception_list.delete regexp
    nil
  end

  ##
  # Clears the list of exceptions for find_file. This must be called
  # _before_ the exceptions are compiled.

  def clear_exceptions
    raise "exceptions already compiled" if defined? @exceptions
    @exception_list.clear
    nil
  end

  ##
  # Return a compiled regexp of exceptions for find_files or nil if no
  # filtering should take place. This regexp is generated from
  # +exception_list+.

  def exceptions
    unless defined? @exceptions then
      @exceptions = if @exception_list.empty? then
                      nil
                    else
                      Regexp.union(*@exception_list)
                    end
    end

    @exceptions
  end

  ############################################################
  # Hooks:

  ##
  # Call the event hook named +name+, passing in optional args
  # depending on the hook itself.
  #
  # Returns false if no hook handled the event.
  #
  # === Hook Writers!
  #
  # This executes all registered hooks <em>until one returns truthy</em>.
  # Pay attention to the return value of your block!

  def hook name, *args
    deprecated = {
      # none currently
    }

    if deprecated[name] and not HOOKS[name].empty? then
      warn "hook #{name} has been deprecated, use #{deprecated[name]}"
    end

    HOOKS[name].any? { |plugin| plugin[self, *args] }
  end

  ##
  # Add the supplied block to the available hooks, with the given
  # name.

  def self.add_hook name, &block
    HOOKS[name] << block
  end
end