Description: restore tests and rake_tasks v1.1.2
Author: HIGUCHI Daisuke (VDR dai) <dai@debian.org>
Origin: vendor
Forwarded: not-needed
Last-Update: 2017-10-22

diff --git a/Rakefile b/Rakefile
new file mode 100644
index 0000000..c9b1e8a
--- /dev/null
+++ b/Rakefile
@@ -0,0 +1,37 @@
+require 'bundler/gem_tasks'
+
+$:.unshift File.dirname(__FILE__) unless $:.include? '.'
+
+ROOT = '.'
+LIB_ROOT = File.join ROOT, 'lib'
+
+task :default => :test
+
+if File.directory? 'rake_tasks'
+  
+  # load rake tasks from subfolder
+  for task_file in Dir['rake_tasks/*.rake'].sort
+    load task_file
+  end
+  
+else
+  
+  # fallback tasks when rake_tasks folder is not present (eg. in the distribution package)
+  desc 'Run CodeRay tests (basic)'
+  task :test do
+    ruby './test/functional/suite.rb'
+    ruby './test/functional/for_redcloth.rb'
+  end
+  
+  gem 'rdoc' if defined? gem
+  require 'rdoc/task'
+  desc 'Generate documentation for CodeRay'
+  Rake::RDocTask.new :doc do |rd|
+    rd.title = 'CodeRay Documentation'
+    rd.main = 'README_INDEX.rdoc'
+    rd.rdoc_files.add Dir['lib']
+    rd.rdoc_files.add rd.main
+    rd.rdoc_dir = 'doc'
+  end
+  
+end
diff --git a/rake_tasks/benchmark.rake b/rake_tasks/benchmark.rake
new file mode 100644
index 0000000..8edeffb
--- /dev/null
+++ b/rake_tasks/benchmark.rake
@@ -0,0 +1,6 @@
+desc 'Do a benchmark'
+task :benchmark do
+  ruby 'bench/bench.rb ruby html'
+end
+
+task :bench => :benchmark
diff --git a/rake_tasks/code_statistics.rb b/rake_tasks/code_statistics.rb
new file mode 100644
index 0000000..0a2016b
--- /dev/null
+++ b/rake_tasks/code_statistics.rb
@@ -0,0 +1,171 @@
+# From rails (http://rubyonrails.com)
+#
+# Improved by murphy
+class CodeStatistics
+
+  TEST_TYPES = /\btest/i
+
+  # Create a new Code Statistic.
+  #
+  # Rakefile Example:
+  #
+  #  desc 'Report code statistics (LOC) from the application'
+  #  task :stats => :copy_files do
+  #    require 'rake_helpers/code_statistics'
+  #    CodeStatistics.new(
+  #      ["Main", "lib"],
+  #      ["Tests", "test"],
+  #      ["Demos", "demo"]
+  #    ).to_s
+  #   end
+  def initialize(*pairs)
+    @pairs = pairs
+    @statistics = calculate_statistics
+    @total = if pairs.empty? then nil else calculate_total end
+  end
+
+  # Print a textual table viewing the stats
+  #
+  # Intended for console output.
+  def print
+    print_header
+    @pairs.each { |name, path| print_line name, @statistics[name] }
+    print_splitter
+
+    if @total
+      print_line 'Total', @total
+      print_splitter
+    end
+
+    print_code_test_stats
+  end
+
+private
+
+  DEFAULT_FILE_PATTERN = /\.rb$/
+
+  def calculate_statistics
+    @pairs.inject({}) do |stats, (name, path, pattern, is_ruby_code)|
+      pattern ||= DEFAULT_FILE_PATTERN
+      path = File.join path, '*.rb'
+      stats[name] = calculate_directory_statistics path, pattern, is_ruby_code
+      stats
+    end
+  end
+
+  def calculate_directory_statistics directory, pattern = DEFAULT_FILE_PATTERN, is_ruby_code = true
+    is_ruby_code = true if is_ruby_code.nil?
+    stats = Hash.new 0
+
+    Dir[directory].each do |file_name|
+      p "Scanning #{file_name}..." if $VERBOSE
+      next unless file_name =~ pattern
+
+      lines = codelines = classes = modules = methods = 0
+      empty_lines = comment_lines = 0
+      in_comment_block = false
+
+      File.readlines(file_name).each do |line|
+        lines += 1
+        if line[/^\s*$/]
+          empty_lines += 1
+        elsif is_ruby_code
+          case line
+          when /^=end\b/
+            comment_lines += 1
+            in_comment_block = false
+          when in_comment_block
+            comment_lines += 1
+          when /^\s*class\b/
+            classes += 1
+          when /^\s*module\b/
+            modules += 1
+          when /^\s*def\b/
+            methods += 1
+          when /^\s*#/
+            comment_lines += 1
+          when /^=begin\b/
+            in_comment_block = false
+            comment_lines += 1
+          when /^__END__$/
+            in_comment_block = true
+          end
+        end
+      end
+
+      codelines = lines - comment_lines - empty_lines
+
+      stats[:lines] += lines
+      stats[:comments] += comment_lines
+      stats[:codelines] += codelines
+      stats[:classes] += classes
+      stats[:modules] += modules
+      stats[:methods] += methods
+      stats[:files] += 1
+    end
+
+    stats
+  end
+
+  def calculate_total
+    total = Hash.new 0
+    @statistics.each_value { |pair| pair.each { |k, v| total[k] += v } }
+    total
+  end
+
+  def calculate_code
+    code_loc = 0
+    @statistics.each { |k, v| code_loc += v[:codelines] unless k[TEST_TYPES] }
+    code_loc
+  end
+
+  def calculate_tests
+    test_loc = 0
+    @statistics.each { |k, v| test_loc += v[:codelines] if k[TEST_TYPES] }
+    test_loc
+  end
+
+  def print_header
+    print_splitter
+    puts "| T=Test  Name              | Files | Lines |   LOC | Comments | Classes | Modules | Methods | M/C | LOC/M |"
+    print_splitter
+  end
+
+  def print_splitter
+    puts "+---------------------------+-------+-------+-------+----------+---------+---------+---------+-----+-------+"
+  end
+
+  def print_line name, statistics
+    m_over_c = (statistics[:methods] / (statistics[:classes] + statistics[:modules])) rescue m_over_c = 0
+    loc_over_m = (statistics[:codelines] / statistics[:methods]) - 2 rescue loc_over_m = 0
+
+    if name[TEST_TYPES]
+      name = "T #{name}"
+    else
+      name = "  #{name}"
+    end
+
+    line = "| %-25s | %5d | %5d | %5d | %8d | %7d | %7d | %7d | %3d | %5d |" % (
+      [name, *statistics.values_at(:files, :lines, :codelines, :comments, :classes, :modules, :methods)] +
+      [m_over_c, loc_over_m] )
+
+    puts line
+  end
+
+  def print_code_test_stats
+    code = calculate_code
+    tests = calculate_tests
+
+    puts "  Code LOC = #{code}     Test LOC = #{tests}     Code:Test Ratio = [1 : #{sprintf("%.2f", tests.to_f/code)}]"
+    puts ""
+  end
+
+end
+
+# Run a test script.
+if $0 == __FILE__
+  $VERBOSE = true
+  CodeStatistics.new(
+    ['This dir', File.dirname(__FILE__)]
+  ).print
+end
diff --git a/rake_tasks/documentation.rake b/rake_tasks/documentation.rake
new file mode 100644
index 0000000..4f7cef7
--- /dev/null
+++ b/rake_tasks/documentation.rake
@@ -0,0 +1,23 @@
+begin
+  if RUBY_VERSION >= '1.8.7'
+    gem 'rdoc' if defined? gem
+    require 'rdoc/task'
+  else
+    require 'rake/rdoctask'
+  end
+rescue LoadError
+  warn 'Please gem install rdoc.'
+end
+
+desc 'Generate documentation for CodeRay'
+Rake::RDocTask.new :doc do |rd|
+  rd.main = 'lib/README'
+  rd.title = 'CodeRay Documentation'
+  
+  rd.options << '--line-numbers' << '--tab-width' << '2'
+  
+  rd.main = 'README_INDEX.rdoc'
+  rd.rdoc_files.add 'README_INDEX.rdoc'
+  rd.rdoc_files.add Dir['lib']
+  rd.rdoc_dir = 'doc'
+end if defined? Rake::RDocTask
diff --git a/rake_tasks/generator.rake b/rake_tasks/generator.rake
new file mode 100644
index 0000000..284adcb
--- /dev/null
+++ b/rake_tasks/generator.rake
@@ -0,0 +1,72 @@
+namespace :generate do
+  desc 'generates a new scanner NAME=lang [ALT=alternative,plugin,ids] [EXT=file,extensions] [BASE=base lang]'
+  task :scanner do
+    raise 'I need a scanner name; use NAME=lang' unless scanner_class_name = ENV['NAME']
+    raise "Invalid lang: #{scanner_class_name}; use NAME=lang." unless /\A\w+\z/ === scanner_class_name
+    require 'active_support/all'
+    lang = scanner_class_name.underscore
+    class_name = scanner_class_name.camelize
+    
+    def scanner_file_for_lang lang
+      File.join(LIB_ROOT, 'coderay', 'scanners', lang + '.rb')
+    end
+    
+    scanner_file = scanner_file_for_lang lang
+    if File.exist? scanner_file
+      print "#{scanner_file} already exists. Overwrite? [y|N] "
+      exit unless $stdin.gets.chomp.downcase == 'y'
+    end
+    
+    base_lang = ENV.fetch('BASE', 'json')
+    base_scanner_file = scanner_file_for_lang(base_lang)
+    puts "Reading base scanner #{base_scanner_file}..."
+    base_scanner = File.read base_scanner_file
+    puts "Writing new scanner #{scanner_file}..."
+    File.open(scanner_file, 'w') do |file|
+      file.write base_scanner.
+        sub(/class \w+ < Scanner/, "class #{class_name} < Scanner").
+        sub('# Scanner for JSON (JavaScript Object Notation).', "# A scanner for #{scanner_class_name}.").
+        sub(/register_for :\w+/, "register_for :#{lang}").
+        sub(/file_extension '\S+'/, "file_extension '#{ENV.fetch('EXT', lang).split(',').first}'")
+    end
+    
+    test_dir = File.join(ROOT, 'test', 'scanners', lang)
+    unless File.exist? test_dir
+      puts "Creating test folder #{test_dir}..."
+      sh "mkdir #{test_dir}"
+    end
+    test_suite_file = File.join(test_dir, 'suite.rb')
+    unless File.exist? test_suite_file
+      puts "Creating test suite file #{test_suite_file}..."
+      base_suite = File.read File.join(test_dir, '..', 'ruby', 'suite.rb')
+      File.open(test_suite_file, 'w') do |file|
+        file.write base_suite.sub(/class Ruby/, "class #{class_name}")
+      end
+    end
+    
+    if extensions = ENV['EXT']
+      file_type_file = File.join(LIB_ROOT, 'coderay', 'helpers', 'filetype.rb')
+      puts "Not automated. Remember to add your extensions to #{file_type_file}:"
+      for ext in extensions.split(',')
+        puts "    '#{ext}' => :#{lang},"
+      end
+    end
+    
+    if alternative_ids = ENV['ALT'] && alternative_ids != lang
+      map_file = File.join(LIB_ROOT, 'coderay', 'scanners', '_map.rb')
+      puts "Not automated. Remember to add your alternative plugin ids to #{map_file}:"
+      for id in alternative_ids.split(',')
+        puts "  :#{id} => :#{lang},"
+      end
+    end
+    
+    print 'Add to git? [Y|n] '
+    answer = $stdin.gets.chomp.downcase
+    if answer.empty? || answer == 'y'
+      sh "git add #{scanner_file}"
+      cd File.join('test', 'scanners') do
+        sh "git add #{lang}"
+      end
+    end
+  end
+end
diff --git a/rake_tasks/statistic.rake b/rake_tasks/statistic.rake
new file mode 100644
index 0000000..d30e9b1
--- /dev/null
+++ b/rake_tasks/statistic.rake
@@ -0,0 +1,19 @@
+desc 'Report code statistics (LOC) from the application'
+task :stats do
+  require './rake_tasks/code_statistics'
+  CodeStatistics.new(
+    ['Main', 'lib', /coderay.rb$/],
+    ['CodeRay', 'lib/coderay/'],
+    ['  Scanners', 'lib/coderay/scanners/**'],
+    ['  Encoders', 'lib/coderay/encoders/**'],
+    ['  Helpers', 'lib/coderay/helpers/**'],
+    ['  Styles', 'lib/coderay/styles/**'],
+    ['Executable', 'bin', /coderay$/],
+    ['Executable Tests', 'test/executable/**'],
+    ['Functional Tests', 'test/functional/**'],
+    ['Scanner Tests', 'test/scanners/**', /suite\.rb$/],
+    ['Unit Tests', 'test/unit/**'],
+    # ['  Test Data', 'test/scanners/**', /\.in\./, false],
+    ['Demos', 'sample/**']
+  ).print
+end
diff --git a/rake_tasks/test.rake b/rake_tasks/test.rake
new file mode 100644
index 0000000..1a23a5b
--- /dev/null
+++ b/rake_tasks/test.rake
@@ -0,0 +1,82 @@
+namespace :test do
+  desc 'run functional tests'
+  task :functional do
+    ruby './test/functional/suite.rb'
+    ruby './test/functional/for_redcloth.rb' unless (''.chop! rescue true)
+  end
+  
+  desc 'run unit tests'
+  task :units do
+    ruby './test/unit/suite.rb'
+  end
+  
+  scanner_suite = 'test/scanners/suite.rb'
+  desc 'run all scanner tests'
+  task :scanners => :update_scanner_suite do
+    ruby scanner_suite
+  end
+  
+  desc 'update scanner test suite from GitHub'
+  task :update_scanner_suite do
+    if File.exist? scanner_suite
+      Dir.chdir File.dirname(scanner_suite) do
+        if File.directory? '.git'
+          puts 'Updating scanner test suite...'
+          sh 'git pull'
+        elsif File.directory? '.svn'
+          raise <<-ERROR
+Found the deprecated Subversion scanner test suite in ./#{File.dirname(scanner_suite)}.
+Please rename or remove it and run again to use the GitHub repository:
+
+  mv test/scanners test/scanners-old
+          ERROR
+        else
+          raise 'No scanner test suite found.'
+        end
+      end
+    else
+      puts 'Downloading scanner test suite...'
+      sh 'git clone https://github.com/rubychan/coderay-scanner-tests.git test/scanners/'
+    end
+  end
+  
+  namespace :scanner do
+    Dir['./test/scanners/*'].each do |scanner|
+      next unless File.directory? scanner
+      lang = File.basename(scanner)
+      desc "run all scanner tests for #{lang}"
+      task lang => :update_scanner_suite do
+        ruby "./test/scanners/suite.rb #{lang}"
+      end
+    end
+  end
+  
+  desc 'clean test output files'
+  task :clean do
+    for file in Dir['test/scanners/**/*.actual.*']
+      rm file
+    end
+    for file in Dir['test/scanners/**/*.debug.diff']
+      rm file
+    end
+    for file in Dir['test/scanners/**/*.debug.diff.html']
+      rm file
+    end
+    for file in Dir['test/scanners/**/*.expected.html']
+      rm file
+    end
+  end
+  
+  desc 'test the CodeRay executable'
+  task :exe do
+    if RUBY_VERSION >= '1.8.7'
+      ruby './test/executable/suite.rb'
+    else
+      puts
+      puts "Can't run executable tests because shoulda-context requires Ruby 1.8.7+."
+      puts "Skipping."
+    end
+  end
+end
+
+task :test => %w(test:functional test:units test:exe)
diff --git a/test/executable/source.py b/test/executable/source.py
new file mode 100644
index 0000000..1bb2c00
--- /dev/null
+++ b/test/executable/source.py
@@ -0,0 +1 @@
+class ClassName(): pass
\ No newline at end of file
diff --git a/test/executable/source.rb b/test/executable/source.rb
new file mode 100644
index 0000000..226f15f
--- /dev/null
+++ b/test/executable/source.rb
@@ -0,0 +1 @@
+class ClassName; end
\ No newline at end of file
diff --git a/test/executable/source_with_comments.rb b/test/executable/source_with_comments.rb
new file mode 100644
index 0000000..ec79358
--- /dev/null
+++ b/test/executable/source_with_comments.rb
@@ -0,0 +1,3 @@
+# a class
+class ClassName
+end
diff --git a/test/executable/suite.rb b/test/executable/suite.rb
new file mode 100644
index 0000000..997405c
--- /dev/null
+++ b/test/executable/suite.rb
@@ -0,0 +1,226 @@
+require 'test/unit'
+require 'rubygems' unless defined? Gem
+require 'shoulda-context'
+
+require 'pathname'
+require 'json'
+
+$:.unshift File.expand_path('../../../lib', __FILE__)
+require 'coderay'
+
+puts "Running CodeRay #{CodeRay::VERSION} executable tests..."
+
+class TestCodeRayExecutable < Test::Unit::TestCase
+  
+  ROOT_DIR = Pathname.new(File.dirname(__FILE__)) + '..' + '..'
+  EXECUTABLE = ROOT_DIR + 'bin' + 'coderay'
+  RUBY_COMMAND = 'ruby'
+  EXE_COMMAND =
+    if RUBY_PLATFORM === 'java' && `ruby --ng -e '' 2> /dev/null` && $?.success?
+      # use Nailgun
+      "#{RUBY_COMMAND}--ng -I%s %s"
+    else
+      "#{RUBY_COMMAND} -I%s %s"
+    end % [ROOT_DIR + 'lib', EXECUTABLE]
+  
+  def coderay args, options = {}
+    if options[:fake_tty]
+      command = "#{EXE_COMMAND} #{args} --tty"
+    else
+      command = "#{EXE_COMMAND} #{args}"
+    end
+    
+    puts command if $DEBUG
+    
+    if options[:input]
+      output = IO.popen "#{command} 2>&1", "r+" do |io|
+        io.write options[:input]
+        io.close_write
+        io.read
+      end
+    else
+      output = `#{command} 2>&1`
+    end
+    
+    if output[EXECUTABLE.to_s]
+      raise output
+    else
+      output
+    end
+  end
+  
+  context 'a simple call with no arguments' do
+    should 'work' do
+      assert_nothing_raised { coderay('') }
+    end
+    should 'print version and help' do
+      assert_match(/CodeRay #{CodeRay::VERSION}/, coderay(''))
+      assert_match(/usage:/, coderay(''))
+    end
+  end
+  
+  context 'version' do
+    should 'be printed with -v' do
+      assert_match(/\ACodeRay #{CodeRay::VERSION}\Z/, coderay('-v'))
+    end
+    should 'be printed with --version' do
+      assert_match(/\ACodeRay #{CodeRay::VERSION}\Z/, coderay('--version'))
+    end
+  end
+  
+  context 'help' do
+    should 'be printed with -h' do
+      assert_match(/^usage:/, coderay('-h'))
+    end
+    should 'be printed with --help' do
+      assert_match(/^usage:/, coderay('--help'))
+    end
+    should 'be printed with subcommand help' do
+      assert_match(/^usage:/, coderay('help'))
+    end
+  end
+  
+  context 'commands' do
+    should 'be printed with subcommand commands' do
+      assert_match(/^ +help/, coderay('commands'))
+      assert_match(/^ +version/, coderay('commands'))
+    end
+  end
+  
+  context 'highlighting a file to the terminal' do
+    source_file = ROOT_DIR + 'test/executable/source.py'
+    
+    source = File.read source_file
+    
+    ansi_seq = /\e\[[0-9;]+m/
+    
+    should 'not throw an error' do
+      assert_nothing_raised { coderay(source_file, :fake_tty => true) }
+    end
+    should 'output its contents to stdout' do
+      target = coderay(source_file, :fake_tty => true)
+      assert_equal source, target.chomp.gsub(ansi_seq, '')
+    end
+    should 'output ANSI-colored text' do
+      target = coderay(source_file, :fake_tty => true)
+      assert_not_equal source, target.chomp
+      assert_equal 6, target.scan(ansi_seq).size
+    end
+  end
+  
+  context 'highlighting a file into a pipe (source.rb -html > source.rb.html)' do
+    source_file = ROOT_DIR + 'test/executable/source.rb'
+    target_file = "#{source_file}.html"
+    command = "#{source_file} -html > #{target_file}"
+    
+    source = File.read source_file
+    
+    pre = %r{<td class="code"><pre>(.*?)</pre>}m
+    tag = /<[^>]*>/
+    
+    should 'not throw an error' do
+      assert_nothing_raised { coderay(command) }
+    end
+    should 'output its contents to the pipe' do
+      coderay(command)
+      target = File.read(target_file)
+      if target = target[pre, 1]
+        assert_equal source, target.gsub(tag, '').strip
+      else
+        flunk "target code has no <pre> tag: #{target}"
+      end
+    end
+    should 'output valid HTML' do
+      coderay(command)
+      target = File.read(target_file)
+      assert_not_equal source, target[pre, 1]
+      assert_equal 6, target[pre, 1].scan(tag).size
+      assert_match %r{\A<!DOCTYPE html>\n<html>\n<head>}, target
+    end
+  end
+  
+  context 'highlighting a file into another file (source.rb source.rb.json)' do
+    source_file = ROOT_DIR + 'test/executable/source.rb'
+    target_file = "#{source_file}.json"
+    command = "#{source_file} #{target_file}"
+    
+    source = File.read source_file
+    
+    text = /"text":"([^"]*)"/
+    
+    should 'not throw an error' do
+      assert_nothing_raised { coderay(command) }
+    end
+    should 'output its contents to the file' do
+      coderay(command)
+      target = File.read(target_file)
+      assert_equal source, target.scan(text).join
+    end
+    should 'output JSON' do
+      coderay(command)
+      target = File.read(target_file)
+      assert_not_equal source, target
+      assert_equal 6, target.scan(text).size
+    end
+  end
+  
+  context 'highlighting a file without explicit input type (source.py)' do
+    source_file = ROOT_DIR + 'test/executable/source.py'
+    command = "#{source_file} -html"
+    
+    source = File.read source_file
+    
+    pre = %r{<td class="code"><pre>(.*?)</pre>}m
+    tag_class = /<span class="([^>"]*)"?[^>]*>/
+    
+    should 'respect the file extension and highlight the input as Python' do
+      target = coderay(command)
+      assert_equal %w(keyword class keyword), target[pre, 1].scan(tag_class).flatten
+    end
+  end
+  
+  context 'highlighting a file with explicit input type (-ruby source.py)' do
+    source_file = ROOT_DIR + 'test/executable/source.py'
+    command = "-ruby #{source_file} -html"
+    
+    source = File.read source_file
+    
+    pre = %r{<td class="code"><pre>(.*?)</pre>}m
+    tag_class = /<span class="([^>"]*)"?[^>]*>/
+    
+    should 'ignore the file extension and highlight the input as Ruby' do
+      target = coderay(command)
+      assert_equal %w(keyword class), target[pre, 1].scan(tag_class).flatten
+    end
+  end
+  
+  context 'highlighting a file with explicit input and output type (-ruby source.py -span)' do
+    source_file = ROOT_DIR + 'test/executable/source.py'
+    command = "-ruby #{source_file} -span"
+    
+    source = File.read source_file
+    
+    span_tags = /<\/?span[^>]*>/
+    
+    should 'just respect the output type and include span tags' do
+      target = coderay(command)
+      assert_equal source, target.chomp.gsub(span_tags, '')
+    end
+  end
+  
+  context 'the LOC counter' do
+    source_file = ROOT_DIR + 'test/executable/source_with_comments.rb'
+    command = "-ruby -loc"
+    
+    should 'work' do
+      output = coderay(command, :input => <<-CODE)
+# test
+=begin
+=end
+test
+      CODE
+      assert_equal "1\n", output
+    end
+  end
+  
+end
diff --git a/test/functional/basic.rb b/test/functional/basic.rb
new file mode 100644
index 0000000..752d4ba
--- /dev/null
+++ b/test/functional/basic.rb
@@ -0,0 +1,318 @@
+# encoding: utf-8
+require 'test/unit'
+require File.expand_path('../../lib/assert_warning', __FILE__)
+
+$:.unshift File.expand_path('../../../lib', __FILE__)
+require 'coderay'
+
+class BasicTest < Test::Unit::TestCase
+  
+  def test_version
+    assert_nothing_raised do
+      assert_match(/\A\d\.\d\.\d?\z/, CodeRay::VERSION)
+    end
+  end
+  
+  def with_empty_load_path
+    old_load_path = $:.dup
+    $:.clear
+    yield
+  ensure
+    $:.replace old_load_path
+  end
+  
+  def test_autoload
+    with_empty_load_path do
+      assert_nothing_raised do
+        CodeRay::Scanners::Java::BuiltinTypes
+      end
+    end
+  end
+  
+  RUBY_TEST_CODE = 'puts "Hello, World!"'
+  
+  RUBY_TEST_TOKENS = [
+    ['puts', :ident],
+    [' ', :space],
+    [:begin_group, :string],
+      ['"', :delimiter],
+      ['Hello, World!', :content],
+      ['"', :delimiter],
+    [:end_group, :string]
+  ].flatten
+  def test_simple_scan
+    assert_nothing_raised do
+      assert_equal RUBY_TEST_TOKENS, CodeRay.scan(RUBY_TEST_CODE, :ruby).tokens
+    end
+  end
+  
+  RUBY_TEST_HTML = 'puts <span class="string"><span class="delimiter">&quot;</span>' + 
+    '<span class="content">Hello, World!</span><span class="delimiter">&quot;</span></span>'
+  def test_simple_highlight
+    assert_nothing_raised do
+      assert_equal RUBY_TEST_HTML, CodeRay.scan(RUBY_TEST_CODE, :ruby).html
+    end
+  end
+  
+  def test_scan_file
+    CodeRay.scan_file __FILE__
+  end
+  
+  def test_encode
+    assert_equal 1, CodeRay.encode('test', :python, :count)
+  end
+  
+  def test_encode_tokens
+    assert_equal 1, CodeRay.encode_tokens(CodeRay::Tokens['test', :string], :count)
+  end
+  
+  def test_encode_file
+    assert_equal File.read(__FILE__), CodeRay.encode_file(__FILE__, :text)
+  end
+  
+  def test_highlight
+    assert_match '<pre>test</pre>', CodeRay.highlight('test', :python)
+  end
+  
+  def test_highlight_file
+    assert_match "require <span class=\"string\"><span class=\"delimiter\">'</span><span class=\"content\">test/unit</span><span class=\"delimiter\">'</span></span>\n", CodeRay.highlight_file(__FILE__)
+  end
+  
+  def test_duo
+    assert_equal(RUBY_TEST_CODE,
+      CodeRay::Duo[:plain, :text].highlight(RUBY_TEST_CODE))
+    assert_equal(RUBY_TEST_CODE,
+      CodeRay::Duo[:plain => :text].highlight(RUBY_TEST_CODE))
+  end
+  
+  def test_duo_stream
+    assert_equal(RUBY_TEST_CODE,
+      CodeRay::Duo[:plain, :text].highlight(RUBY_TEST_CODE, :stream => true))
+  end
+  
+  def test_comment_filter
+    assert_equal <<-EXPECTED, CodeRay.scan(<<-INPUT, :ruby).comment_filter.text
+#!/usr/bin/env ruby
+
+code
+
+more code  
+      EXPECTED
+#!/usr/bin/env ruby
+=begin
+A multi-line comment.
+=end
+code
+# A single-line comment.
+more code  # and another comment, in-line.
+      INPUT
+  end
+  
+  def test_lines_of_code
+    assert_equal 2, CodeRay.scan(<<-INPUT, :ruby).lines_of_code
+#!/usr/bin/env ruby
+=begin
+A multi-line comment.
+=end
+code
+# A single-line comment.
+more code  # and another comment, in-line.
+      INPUT
+    rHTML = <<-RHTML
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+  <meta http-equiv="content-type" content="text/html;charset=UTF-8" />
+  <title><%= controller.controller_name.titleize %>: <%= controller.action_name %></title>
+  <%= stylesheet_link_tag 'scaffold' %>
+</head>
+<body>
+
+<p style="color: green"><%= flash[:notice] %></p>
+
+<div id="main">
+  <%= yield %>
+</div>
+
+</body>
+</html>
+      RHTML
+    assert_equal 0, CodeRay.scan(rHTML, :html).lines_of_code
+    assert_equal 0, CodeRay.scan(rHTML, :php).lines_of_code
+    assert_equal 0, CodeRay.scan(rHTML, :yaml).lines_of_code
+    assert_equal 4, CodeRay.scan(rHTML, :erb).lines_of_code
+  end
+  
+  def test_list_of_encoders
+    assert_kind_of(Array, CodeRay::Encoders.list)
+    assert CodeRay::Encoders.list.include?(:count)
+  end
+  
+  def test_list_of_scanners
+    assert_kind_of(Array, CodeRay::Scanners.list)
+    assert CodeRay::Scanners.list.include?(:text)
+  end
+  
+  def test_token_kinds
+    assert_kind_of Hash, CodeRay::TokenKinds
+    for kind, css_class in CodeRay::TokenKinds
+      assert_kind_of Symbol, kind
+      if css_class != false
+        assert_kind_of String, css_class, "TokenKinds[%p] == %p" % [kind, css_class]
+      end
+    end
+    assert_equal 'reserved', CodeRay::TokenKinds[:reserved]
+    assert_equal false,      CodeRay::TokenKinds[:shibboleet]
+  end
+  
+  class Milk < CodeRay::Encoders::Encoder
+    FILE_EXTENSION = 'cocoa'
+  end
+  
+  class HoneyBee < CodeRay::Encoders::Encoder
+  end
+  
+  def test_encoder_file_extension
+    assert_nothing_raised do
+      assert_equal 'html', CodeRay::Encoders::Page::FILE_EXTENSION
+      assert_equal 'cocoa', Milk::FILE_EXTENSION
+      assert_equal 'cocoa', Milk.new.file_extension
+      assert_equal 'honeybee', HoneyBee::FILE_EXTENSION
+      assert_equal 'honeybee', HoneyBee.new.file_extension
+    end
+    assert_raise NameError do
+      HoneyBee::MISSING_CONSTANT
+    end
+  end
+  
+  def test_encoder_tokens
+    encoder = CodeRay::Encoders::Encoder.new
+    encoder.send :setup, {}
+    assert_raise(ArgumentError) { encoder.token :strange, '' }
+    encoder.token 'test', :debug
+  end
+  
+  def test_encoder_deprecated_interface
+    encoder = CodeRay::Encoders::Encoder.new
+    encoder.send :setup, {}
+    assert_warning 'Using old Tokens#<< interface.' do
+      encoder << ['test', :content]
+    end
+    assert_raise ArgumentError do
+      encoder << [:strange, :input]
+    end
+    assert_raise ArgumentError do
+      encoder.encode_tokens [['test', :token]]
+    end
+  end
+  
+  def encoder_token_interface_deprecation_warning_given
+    CodeRay::Encoders::Encoder.send :class_variable_get, :@@CODERAY_TOKEN_INTERFACE_DEPRECATION_WARNING_GIVEN
+  end
+  
+  def test_scanner_file_extension
+    assert_equal 'rb', CodeRay::Scanners::Ruby.file_extension
+    assert_equal 'rb', CodeRay::Scanners::Ruby.new.file_extension
+    assert_equal 'java', CodeRay::Scanners::Java.file_extension
+    assert_equal 'java', CodeRay::Scanners::Java.new.file_extension
+  end
+  
+  def test_scanner_lang
+    assert_equal :ruby, CodeRay::Scanners::Ruby.lang
+    assert_equal :ruby, CodeRay::Scanners::Ruby.new.lang
+    assert_equal :java, CodeRay::Scanners::Java.lang
+    assert_equal :java, CodeRay::Scanners::Java.new.lang
+  end
+  
+  def test_scanner_tokenize
+    assert_equal ['foo', :plain], CodeRay::Scanners::Plain.new.tokenize('foo')
+    assert_equal [['foo', :plain], ['bar', :plain]], CodeRay::Scanners::Plain.new.tokenize(['foo', 'bar'])
+    CodeRay::Scanners::Plain.new.tokenize 42
+  end
+  
+  def test_scanner_tokens
+    scanner = CodeRay::Scanners::Plain.new
+    scanner.tokenize('foo')
+    assert_equal ['foo', :plain], scanner.tokens
+    scanner.string = ''
+    assert_equal ['', :plain], scanner.tokens
+  end
+  
+  def test_scanner_line_and_column
+    scanner = CodeRay::Scanners::Plain.new "foo\nbär+quux"
+    assert_equal 0, scanner.pos
+    assert_equal 1, scanner.line
+    assert_equal 1, scanner.column
+    scanner.scan(/foo/)
+    assert_equal 3, scanner.pos
+    assert_equal 1, scanner.line
+    assert_equal 4, scanner.column
+    scanner.scan(/\n/)
+    assert_equal 4, scanner.pos
+    assert_equal 2, scanner.line
+    assert_equal 1, scanner.column
+    scanner.scan(/b/)
+    assert_equal 5, scanner.pos
+    assert_equal 2, scanner.line
+    assert_equal 2, scanner.column
+    scanner.scan(/a/)
+    assert_equal 5, scanner.pos
+    assert_equal 2, scanner.line
+    assert_equal 2, scanner.column
+    scanner.scan(/ä/)
+    assert_equal 7, scanner.pos
+    assert_equal 2, scanner.line
+    assert_equal 4, scanner.column
+    scanner.scan(/r/)
+    assert_equal 8, scanner.pos
+    assert_equal 2, scanner.line
+    assert_equal 5, scanner.column
+  end
+  
+  def test_scanner_use_subclasses
+    assert_raise NotImplementedError do
+      CodeRay::Scanners::Scanner.new
+    end
+  end
+  
+  class InvalidScanner < CodeRay::Scanners::Scanner
+  end
+  
+  def test_scanner_scan_tokens
+    assert_raise NotImplementedError do
+      InvalidScanner.new.tokenize ''
+    end
+  end
+  
+  class RaisingScanner < CodeRay::Scanners::Scanner
+    def scan_tokens encoder, options
+      raise_inspect 'message', [], :initial
+    end
+  end
+  
+  def test_scanner_raise_inspect
+    assert_raise CodeRay::Scanners::Scanner::ScanError do
+      RaisingScanner.new.tokenize ''
+    end
+  end
+  
+  def test_scan_a_frozen_string
+    assert_nothing_raised do
+      CodeRay.scan RUBY_VERSION, :ruby
+      CodeRay.scan RUBY_VERSION, :plain
+    end
+  end
+  
+  def test_scan_a_non_string
+    assert_nothing_raised do
+      CodeRay.scan 42, :ruby
+      CodeRay.scan nil, :ruby
+      CodeRay.scan self, :ruby
+      CodeRay.encode ENV.to_hash, :ruby, :page
+      CodeRay.highlight CodeRay, :plain
+    end
+  end
+  
+end
diff --git a/test/functional/examples.rb b/test/functional/examples.rb
new file mode 100644
index 0000000..985ef87
--- /dev/null
+++ b/test/functional/examples.rb
@@ -0,0 +1,129 @@
+require 'test/unit'
+
+$:.unshift File.expand_path('../../../lib', __FILE__)
+require 'coderay'
+
+class ExamplesTest < Test::Unit::TestCase
+  
+  def test_examples
+    # output as HTML div (using inline CSS styles)
+    div = CodeRay.scan('puts "Hello, world!"', :ruby).div
+    assert_equal <<-DIV, div
+<div class="CodeRay">
+  <div class="code"><pre>puts <span style="background-color:hsla(0,100%,50%,0.05)"><span style="color:#710">&quot;</span><span style="color:#D20">Hello, world!</span><span style="color:#710">&quot;</span></span></pre></div>
+</div>
+    DIV
+    
+    # ...with line numbers
+    div = CodeRay.scan(<<-CODE.chomp, :ruby).div(:line_numbers => :table)
+5.times do
+  puts 'Hello, world!'
+end
+    CODE
+    assert_equal <<-DIV, div
+<table class="CodeRay"><tr>
+  <td class="line-numbers"><pre><a href="#n1" name="n1">1</a>
+<a href="#n2" name="n2">2</a>
+<a href="#n3" name="n3">3</a>
+</pre></td>
+  <td class="code"><pre><span style="color:#00D">5</span>.times <span style="color:#080;font-weight:bold">do</span>
+  puts <span style="background-color:hsla(0,100%,50%,0.05)"><span style="color:#710">'</span><span style="color:#D20">Hello, world!</span><span style="color:#710">'</span></span>
+<span style="color:#080;font-weight:bold">end</span></pre></td>
+</tr></table>
+    DIV
+    
+    # output as standalone HTML page (using CSS classes)
+    page = CodeRay.scan('puts "Hello, world!"', :ruby).page
+    assert_match <<-PAGE, page
+<body>
+
+<table class="CodeRay"><tr>
+  <td class="line-numbers"><pre><a href="#n1" name="n1">1</a>
+</pre></td>
+  <td class="code"><pre>puts <span class="string"><span class="delimiter">&quot;</span><span class="content">Hello, world!</span><span class="delimiter">&quot;</span></span></pre></td>
+</tr></table>
+
+</body>
+    PAGE
+    
+    # keep scanned tokens for later use
+    tokens = CodeRay.scan('{ "just": "an", "example": 42 }', :json)
+    assert_kind_of CodeRay::TokensProxy, tokens
+    
+    assert_equal ["{", :operator, " ", :space, :begin_group, :key,
+      "\"", :delimiter, "just", :content, "\"", :delimiter,
+      :end_group, :key, ":", :operator, " ", :space,
+      :begin_group, :string, "\"", :delimiter, "an", :content,
+      "\"", :delimiter, :end_group, :string, ",", :operator,
+      " ", :space, :begin_group, :key, "\"", :delimiter,
+      "example", :content, "\"", :delimiter, :end_group, :key,
+      ":", :operator, " ", :space, "42", :integer,
+      " ", :space, "}", :operator], tokens.tokens
+    
+    # produce a token statistic
+    assert_equal <<-STATISTIC, tokens.statistic
+
+Code Statistics
+
+Tokens                  26
+  Non-Whitespace        15
+Bytes Total             31
+
+Token Types (7):
+  type                     count     ratio    size (average)
+-------------------------------------------------------------
+  TOTAL                       26  100.00 %     1.2
+  delimiter                    6   23.08 %     1.0
+  operator                     5   19.23 %     1.0
+  space                        5   19.23 %     1.0
+  key                          4   15.38 %     0.0
+  :begin_group                 3   11.54 %     0.0
+  :end_group                   3   11.54 %     0.0
+  content                      3   11.54 %     4.3
+  string                       2    7.69 %     0.0
+  integer                      1    3.85 %     2.0
+
+    STATISTIC
+    
+    # count the tokens
+    assert_equal 26, tokens.count
+    
+    # produce a HTML div, but with CSS classes
+    div = tokens.div(:css => :class)
+    assert_equal <<-DIV, div
+<div class="CodeRay">
+  <div class="code"><pre>{ <span class="key"><span class="delimiter">&quot;</span><span class="content">just</span><span class="delimiter">&quot;</span></span>: <span class="string"><span class="delimiter">&quot;</span><span class="content">an</span><span class="delimiter">&quot;</span></span>, <span class="key"><span class="delimiter">&quot;</span><span class="content">example</span><span class="delimiter">&quot;</span></span>: <span class="integer">42</span> }</pre></div>
+</div>
+    DIV
+    
+    # highlight a file (HTML div); guess the file type base on the extension
+    assert_equal :ruby, CodeRay::FileType[__FILE__]
+    
+    # get a new scanner for Python
+    python_scanner = CodeRay.scanner :python
+    assert_kind_of CodeRay::Scanners::Python, python_scanner
+    
+    # get a new encoder for terminal
+    terminal_encoder = CodeRay.encoder :term
+    assert_kind_of CodeRay::Encoders::Terminal, terminal_encoder
+    
+    # scanning into tokens
+    tokens = python_scanner.tokenize 'import this;  # The Zen of Python'
+    assert_equal ["import", :keyword, " ", :space, "this", :include,
+      ";", :operator, "  ", :space, "# The Zen of Python", :comment], tokens
+    
+    # format the tokens
+    term = terminal_encoder.encode_tokens(tokens)
+    assert_equal "\e[32mimport\e[0m \e[31mthis\e[0m;  \e[1;30m# The Zen of Python\e[0m", term
+    
+    # re-using scanner and encoder
+    ruby_highlighter = CodeRay::Duo[:ruby, :div]
+    div = ruby_highlighter.encode('puts "Hello, world!"')
+    assert_equal <<-DIV, div
+<div class="CodeRay">
+  <div class="code"><pre>puts <span style="background-color:hsla(0,100%,50%,0.05)"><span style="color:#710">&quot;</span><span style="color:#D20">Hello, world!</span><span style="color:#710">&quot;</span></span></pre></div>
+</div>
+    DIV
+  end
+  
+end
diff --git a/test/functional/for_redcloth.rb b/test/functional/for_redcloth.rb
new file mode 100644
index 0000000..9fd244e
--- /dev/null
+++ b/test/functional/for_redcloth.rb
@@ -0,0 +1,78 @@
+require 'test/unit'
+
+$:.unshift File.expand_path('../../../lib', __FILE__)
+require 'coderay'
+
+begin
+  require 'rubygems' unless defined? Gem
+  gem 'RedCloth', '>= 4.0.3' rescue nil
+  require 'redcloth'
+rescue LoadError
+  warn 'RedCloth not found - skipping for_redcloth tests.'
+  undef RedCloth if defined? RedCloth
+end
+
+class BasicTest < Test::Unit::TestCase
+  
+  def test_for_redcloth
+    require 'coderay/for_redcloth'
+    assert_equal "<p><span lang=\"ruby\" class=\"CodeRay\">puts <span style=\"background-color:hsla(0,100%,50%,0.05)\"><span style=\"color:#710\">&quot;</span><span style=\"color:#D20\">Hello, World!</span><span style=\"color:#710\">&quot;</span></span></span></p>",
+      RedCloth.new('@[ruby]puts "Hello, World!"@').to_html
+    assert_equal <<-BLOCKCODE.chomp,
+<div lang="ruby" class="CodeRay">
+  <div class="code"><pre>puts <span style="background-color:hsla(0,100%,50%,0.05)"><span style="color:#710">&quot;</span><span style="color:#D20">Hello, World!</span><span style="color:#710">&quot;</span></span></pre></div>
+</div>
+      BLOCKCODE
+      RedCloth.new('bc[ruby]. puts "Hello, World!"').to_html
+  end
+  
+  def test_for_redcloth_no_lang
+    require 'coderay/for_redcloth'
+    assert_equal "<p><code>puts \"Hello, World!\"</code></p>",
+      RedCloth.new('@puts "Hello, World!"@').to_html
+    assert_equal <<-BLOCKCODE.chomp,
+<pre><code>puts \"Hello, World!\"</code></pre>
+      BLOCKCODE
+      RedCloth.new('bc. puts "Hello, World!"').to_html
+  end
+  
+  def test_for_redcloth_style
+    require 'coderay/for_redcloth'
+    assert_equal <<-BLOCKCODE.chomp,
+<pre style=\"color: red;\"><code style=\"color: red;\">puts \"Hello, World!\"</code></pre>
+      BLOCKCODE
+      RedCloth.new('bc{color: red}. puts "Hello, World!"').to_html
+  end
+  
+  def test_for_redcloth_escapes
+    require 'coderay/for_redcloth'
+    assert_equal '<p><span lang="ruby" class="CodeRay">&gt;</span></p>',
+      RedCloth.new('@[ruby]>@').to_html
+    assert_equal <<-BLOCKCODE.chomp,
+<div lang="ruby" class="CodeRay">
+  <div class="code"><pre>&amp;</pre></div>
+</div>
+      BLOCKCODE
+      RedCloth.new('bc[ruby]. &').to_html
+  end
+  
+  def test_for_redcloth_escapes2
+    require 'coderay/for_redcloth'
+    assert_equal "<p><span lang=\"c\" class=\"CodeRay\"><span style=\"color:#579\">#include</span> <span style=\"color:#B44;font-weight:bold\">&lt;test.h&gt;</span></span></p>",
+      RedCloth.new('@[c]#include <test.h>@').to_html
+  end
+  
+  # See http://jgarber.lighthouseapp.com/projects/13054/tickets/124-code-markup-does-not-allow-brackets.
+  def test_for_redcloth_false_positive
+    require 'coderay/for_redcloth'
+    assert_equal '<p><code>[project]_dff.skjd</code></p>',
+      RedCloth.new('@[project]_dff.skjd@').to_html
+    # false positive, but expected behavior / known issue
+    assert_equal "<p><span lang=\"ruby\" class=\"CodeRay\">_dff.skjd</span></p>",
+      RedCloth.new('@[ruby]_dff.skjd@').to_html
+    assert_equal <<-BLOCKCODE.chomp, RedCloth.new('bc. [project]_dff.skjd').to_html
+<pre><code>[project]_dff.skjd</code></pre>
+    BLOCKCODE
+  end
+  
+end if defined? RedCloth
\ No newline at end of file
diff --git a/test/functional/suite.rb b/test/functional/suite.rb
new file mode 100644
index 0000000..ec23eec
--- /dev/null
+++ b/test/functional/suite.rb
@@ -0,0 +1,15 @@
+require 'test/unit'
+
+$VERBOSE = $CODERAY_DEBUG = true
+$:.unshift File.expand_path('../../../lib', __FILE__)
+require 'coderay'
+
+mydir = File.dirname(__FILE__)
+suite = Dir[File.join(mydir, '*.rb')].
+  map { |tc| File.basename(tc).sub(/\.rb$/, '') } - %w'suite for_redcloth'
+
+puts "Running basic CodeRay #{CodeRay::VERSION} tests: #{suite.join(', ')}"
+
+for test_case in suite
+  load File.join(mydir, test_case + '.rb')
+end
diff --git a/test/lib/README b/test/lib/README
new file mode 100644
index 0000000..7c41648
--- /dev/null
+++ b/test/lib/README
@@ -0,0 +1,2 @@
+Contents:
+- test/unit: We need the old Test::Unit for the scanner test suite to work with Ruby 1.9.
diff --git a/test/lib/assert_warning.rb b/test/lib/assert_warning.rb
new file mode 100644
index 0000000..828b464
--- /dev/null
+++ b/test/lib/assert_warning.rb
@@ -0,0 +1,15 @@
+class Test::Unit::TestCase
+  
+  def assert_warning expected_warning
+    require 'stringio'
+    oldstderr = $stderr
+    $stderr = StringIO.new
+    yield
+    $stderr.rewind
+    given_warning = $stderr.read.chomp
+    assert_equal expected_warning, given_warning
+  ensure
+    $stderr = oldstderr
+  end
+  
+end
diff --git a/test/lib/test/unit.rb b/test/lib/test/unit.rb
new file mode 100644
index 0000000..b71f644
--- /dev/null
+++ b/test/lib/test/unit.rb
@@ -0,0 +1,280 @@
+require 'test/unit/testcase'
+require 'test/unit/autorunner'
+
+module Test # :nodoc:
+  #
+  # = Test::Unit - Ruby Unit Testing Framework
+  # 
+  # == Introduction
+  # 
+  # Unit testing is making waves all over the place, largely due to the
+  # fact that it is a core practice of XP. While XP is great, unit testing
+  # has been around for a long time and has always been a good idea. One
+  # of the keys to good unit testing, though, is not just writing tests,
+  # but having tests. What's the difference? Well, if you just _write_ a
+  # test and throw it away, you have no guarantee that something won't
+  # change later which breaks your code. If, on the other hand, you _have_
+  # tests (obviously you have to write them first), and run them as often
+  # as possible, you slowly build up a wall of things that cannot break
+  # without you immediately knowing about it. This is when unit testing
+  # hits its peak usefulness.
+  # 
+  # Enter Test::Unit, a framework for unit testing in Ruby, helping you to
+  # design, debug and evaluate your code by making it easy to write and
+  # have tests for it.
+  # 
+  # 
+  # == Notes
+  # 
+  # Test::Unit has grown out of and superceded Lapidary.
+  # 
+  # 
+  # == Feedback
+  # 
+  # I like (and do my best to practice) XP, so I value early releases,
+  # user feedback, and clean, simple, expressive code. There is always
+  # room for improvement in everything I do, and Test::Unit is no
+  # exception. Please, let me know what you think of Test::Unit as it
+  # stands, and what you'd like to see expanded/changed/improved/etc. If
+  # you find a bug, let me know ASAP; one good way to let me know what the
+  # bug is is to submit a new test that catches it :-) Also, I'd love to
+  # hear about any successes you have with Test::Unit, and any
+  # documentation you might add will be greatly appreciated. My contact
+  # info is below.
+  # 
+  # 
+  # == Contact Information
+  # 
+  # A lot of discussion happens about Ruby in general on the ruby-talk
+  # mailing list (http://www.ruby-lang.org/en/ml.html), and you can ask
+  # any questions you might have there. I monitor the list, as do many
+  # other helpful Rubyists, and you're sure to get a quick answer. Of
+  # course, you're also welcome to email me (Nathaniel Talbott) directly
+  # at mailto:testunit@talbott.ws, and I'll do my best to help you out.
+  # 
+  # 
+  # == Credits
+  # 
+  # I'd like to thank...
+  # 
+  # Matz, for a great language!
+  # 
+  # Masaki Suketa, for his work on RubyUnit, which filled a vital need in
+  # the Ruby world for a very long time. I'm also grateful for his help in
+  # polishing Test::Unit and getting the RubyUnit compatibility layer
+  # right. His graciousness in allowing Test::Unit to supercede RubyUnit
+  # continues to be a challenge to me to be more willing to defer my own
+  # rights.
+  # 
+  # Ken McKinlay, for his interest and work on unit testing, and for his
+  # willingness to dialog about it. He was also a great help in pointing
+  # out some of the holes in the RubyUnit compatibility layer.
+  # 
+  # Dave Thomas, for the original idea that led to the extremely simple
+  # "require 'test/unit'", plus his code to improve it even more by
+  # allowing the selection of tests from the command-line. Also, without
+  # RDoc, the documentation for Test::Unit would stink a lot more than it
+  # does now.
+  # 
+  # Everyone who's helped out with bug reports, feature ideas,
+  # encouragement to continue, etc. It's a real privilege to be a part of
+  # the Ruby community.
+  # 
+  # The guys at RoleModel Software, for putting up with me repeating, "But
+  # this would be so much easier in Ruby!" whenever we're coding in Java.
+  # 
+  # My Creator, for giving me life, and giving it more abundantly.
+  # 
+  # 
+  # == License
+  # 
+  # Test::Unit is copyright (c) 2000-2003 Nathaniel Talbott. It is free
+  # software, and is distributed under the Ruby license. See the COPYING
+  # file in the standard Ruby distribution for details.
+  # 
+  # 
+  # == Warranty
+  # 
+  # This software is provided "as is" and without any express or
+  # implied warranties, including, without limitation, the implied
+  # warranties of merchantibility and fitness for a particular
+  # purpose.
+  # 
+  # 
+  # == Author
+  # 
+  # Nathaniel Talbott.
+  # Copyright (c) 2000-2003, Nathaniel Talbott
+  #
+  # ----
+  #
+  # = Usage
+  #
+  # The general idea behind unit testing is that you write a _test_
+  # _method_ that makes certain _assertions_ about your code, working
+  # against a _test_ _fixture_. A bunch of these _test_ _methods_ are
+  # bundled up into a _test_ _suite_ and can be run any time the
+  # developer wants. The results of a run are gathered in a _test_
+  # _result_ and displayed to the user through some UI. So, lets break
+  # this down and see how Test::Unit provides each of these necessary
+  # pieces.
+  #
+  #
+  # == Assertions
+  #
+  # These are the heart of the framework. Think of an assertion as a
+  # statement of expected outcome, i.e. "I assert that x should be equal
+  # to y". If, when the assertion is executed, it turns out to be
+  # correct, nothing happens, and life is good. If, on the other hand,
+  # your assertion turns out to be false, an error is propagated with
+  # pertinent information so that you can go back and make your
+  # assertion succeed, and, once again, life is good. For an explanation
+  # of the current assertions, see Test::Unit::Assertions.
+  #
+  #
+  # == Test Method & Test Fixture
+  #
+  # Obviously, these assertions have to be called within a context that
+  # knows about them and can do something meaningful with their
+  # pass/fail value. Also, it's handy to collect a bunch of related
+  # tests, each test represented by a method, into a common test class
+  # that knows how to run them. The tests will be in a separate class
+  # from the code they're testing for a couple of reasons. First of all,
+  # it allows your code to stay uncluttered with test code, making it
+  # easier to maintain. Second, it allows the tests to be stripped out
+  # for deployment, since they're really there for you, the developer,
+  # and your users don't need them. Third, and most importantly, it
+  # allows you to set up a common test fixture for your tests to run
+  # against.
+  #
+  # What's a test fixture? Well, tests do not live in a vacuum; rather,
+  # they're run against the code they are testing. Often, a collection
+  # of tests will run against a common set of data, also called a
+  # fixture. If they're all bundled into the same test class, they can
+  # all share the setting up and tearing down of that data, eliminating
+  # unnecessary duplication and making it much easier to add related
+  # tests.
+  #
+  # Test::Unit::TestCase wraps up a collection of test methods together
+  # and allows you to easily set up and tear down the same test fixture
+  # for each test. This is done by overriding #setup and/or #teardown,
+  # which will be called before and after each test method that is
+  # run. The TestCase also knows how to collect the results of your
+  # assertions into a Test::Unit::TestResult, which can then be reported
+  # back to you... but I'm getting ahead of myself. To write a test,
+  # follow these steps:
+  #
+  # * Make sure Test::Unit is in your library path.
+  # * require 'test/unit' in your test script.
+  # * Create a class that subclasses Test::Unit::TestCase.
+  # * Add a method that begins with "test" to your class.
+  # * Make assertions in your test method.
+  # * Optionally define #setup and/or #teardown to set up and/or tear
+  #   down your common test fixture.
+  # * You can now run your test as you would any other Ruby
+  #   script... try it and see!
+  #
+  # A really simple test might look like this (#setup and #teardown are
+  # commented out to indicate that they are completely optional):
+  #
+  #     require 'test/unit'
+  #     
+  #     class TC_MyTest < Test::Unit::TestCase
+  #       # def setup
+  #       # end
+  #     
+  #       # def teardown
+  #       # end
+  #     
+  #       def test_fail
+  #         assert(false, 'Assertion was false.')
+  #       end
+  #     end
+  #
+  #
+  # == Test Runners
+  #
+  # So, now you have this great test class, but you still need a way to
+  # run it and view any failures that occur during the run. This is
+  # where Test::Unit::UI::Console::TestRunner (and others, such as
+  # Test::Unit::UI::GTK::TestRunner) comes into play. The console test
+  # runner is automatically invoked for you if you require 'test/unit'
+  # and simply run the file. To use another runner, or to manually
+  # invoke a runner, simply call its run class method and pass in an
+  # object that responds to the suite message with a
+  # Test::Unit::TestSuite. This can be as simple as passing in your
+  # TestCase class (which has a class suite method). It might look
+  # something like this:
+  #
+  #    require 'test/unit/ui/console/testrunner'
+  #    Test::Unit::UI::Console::TestRunner.run(TC_MyTest)
+  #
+  #
+  # == Test Suite
+  #
+  # As more and more unit tests accumulate for a given project, it
+  # becomes a real drag running them one at a time, and it also
+  # introduces the potential to overlook a failing test because you
+  # forget to run it. Suddenly it becomes very handy that the
+  # TestRunners can take any object that returns a Test::Unit::TestSuite
+  # in response to a suite method. The TestSuite can, in turn, contain
+  # other TestSuites or individual tests (typically created by a
+  # TestCase). In other words, you can easily wrap up a group of
+  # TestCases and TestSuites like this:
+  #
+  #  require 'test/unit/testsuite'
+  #  require 'tc_myfirsttests'
+  #  require 'tc_moretestsbyme'
+  #  require 'ts_anothersetoftests'
+  #
+  #  class TS_MyTests
+  #    def self.suite
+  #      suite = Test::Unit::TestSuite.new
+  #      suite << TC_MyFirstTests.suite
+  #      suite << TC_MoreTestsByMe.suite
+  #      suite << TS_AnotherSetOfTests.suite
+  #      return suite
+  #    end
+  #  end
+  #  Test::Unit::UI::Console::TestRunner.run(TS_MyTests)
+  #
+  # Now, this is a bit cumbersome, so Test::Unit does a little bit more
+  # for you, by wrapping these up automatically when you require
+  # 'test/unit'. What does this mean? It means you could write the above
+  # test case like this instead:
+  #
+  #  require 'test/unit'
+  #  require 'tc_myfirsttests'
+  #  require 'tc_moretestsbyme'
+  #  require 'ts_anothersetoftests'
+  #
+  # Test::Unit is smart enough to find all the test cases existing in
+  # the ObjectSpace and wrap them up into a suite for you. It then runs
+  # the dynamic suite using the console TestRunner.
+  #
+  #
+  # == Questions?
+  #
+  # I'd really like to get feedback from all levels of Ruby
+  # practitioners about typos, grammatical errors, unclear statements,
+  # missing points, etc., in this document (or any other).
+  #
+
+  module Unit
+    # If set to false Test::Unit will not automatically run at exit.
+    def self.run=(flag)
+      @run = flag
+    end
+
+    # Automatically run tests at exit?
+    def self.run?
+      @run ||= false
+    end
+  end
+end
+
+at_exit do
+  unless $! || Test::Unit.run?
+    exit Test::Unit::AutoRunner.run
+  end
+end
diff --git a/test/lib/test/unit/assertionfailederror.rb b/test/lib/test/unit/assertionfailederror.rb
new file mode 100644
index 0000000..a21e4b5
--- /dev/null
+++ b/test/lib/test/unit/assertionfailederror.rb
@@ -0,0 +1,14 @@
+#--
+#
+# Author:: Nathaniel Talbott.
+# Copyright:: Copyright (c) 2000-2002 Nathaniel Talbott. All rights reserved.
+# License:: Ruby license.
+
+module Test
+  module Unit
+
+    # Thrown by Test::Unit::Assertions when an assertion fails.
+    class AssertionFailedError < StandardError
+    end
+  end
+end
diff --git a/test/lib/test/unit/assertions.rb b/test/lib/test/unit/assertions.rb
new file mode 100644
index 0000000..aa97799
--- /dev/null
+++ b/test/lib/test/unit/assertions.rb
@@ -0,0 +1,622 @@
+# Author:: Nathaniel Talbott.
+# Copyright:: Copyright (c) 2000-2003 Nathaniel Talbott. All rights reserved.
+# License:: Ruby license.
+
+require 'test/unit/assertionfailederror'
+require 'test/unit/util/backtracefilter'
+
+module Test
+  module Unit
+
+    ##
+    # Test::Unit::Assertions contains the standard Test::Unit assertions.
+    # Assertions is included in Test::Unit::TestCase.
+    #
+    # To include it in your own code and use its functionality, you simply
+    # need to rescue Test::Unit::AssertionFailedError. Additionally you may
+    # override add_assertion to get notified whenever an assertion is made.
+    #
+    # Notes:
+    # * The message to each assertion, if given, will be propagated with the
+    #   failure.
+    # * It is easy to add your own assertions based on assert_block().
+    #
+    # = Example Custom Assertion
+    #
+    #   def deny(boolean, message = nil)
+    #     message = build_message message, '<?> is not false or nil.', boolean
+    #     assert_block message do
+    #       not boolean
+    #     end
+    #   end
+
+    module Assertions
+
+      ##
+      # The assertion upon which all other assertions are based. Passes if the
+      # block yields true.
+      #
+      # Example:
+      #   assert_block "Couldn't do the thing" do
+      #     do_the_thing
+      #   end
+
+      public
+      def assert_block(message="assert_block failed.") # :yields: 
+        _wrap_assertion do
+          if (! yield)
+            raise AssertionFailedError.new(message.to_s)
+          end
+        end
+      end
+
+      ##
+      # Asserts that +boolean+ is not false or nil.
+      #
+      # Example:
+      #   assert [1, 2].include?(5)
+
+      public
+      def assert(boolean, message=nil)
+        _wrap_assertion do
+          assert_block("assert should not be called with a block.") { !block_given? }
+          assert_block(build_message(message, "<?> is not true.", boolean)) { boolean }
+        end
+      end
+
+      ##
+      # Passes if +expected+ == +actual.
+      #
+      # Note that the ordering of arguments is important, since a helpful
+      # error message is generated when this one fails that tells you the
+      # values of expected and actual.
+      #
+      # Example:
+      #   assert_equal 'MY STRING', 'my string'.upcase
+
+      public
+      def assert_equal(expected, actual, message=nil)
+        full_message = build_message(message, <<EOT, expected, actual)
+<?> expected but was
+<?>.
+EOT
+        assert_block(full_message) { expected == actual }
+      end
+
+      private
+      def _check_exception_class(args) # :nodoc:
+        args.partition do |klass|
+          next if klass.instance_of?(Module)
+          assert(Exception >= klass, "Should expect a class of exception, #{klass}")
+          true
+        end
+      end
+
+      private
+      def _expected_exception?(actual_exception, exceptions, modules) # :nodoc:
+        exceptions.include?(actual_exception.class) or
+          modules.any? {|mod| actual_exception.is_a?(mod)}
+      end
+
+      ##
+      # Passes if the block raises one of the given exceptions.
+      #
+      # Example:
+      #   assert_raise RuntimeError, LoadError do
+      #     raise 'Boom!!!'
+      #   end
+
+      public
+      def assert_raise(*args)
+        _wrap_assertion do
+          if Module === args.last
+            message = ""
+          else
+            message = args.pop
+          end
+          exceptions, modules = _check_exception_class(args)
+          expected = args.size == 1 ? args.first : args
+          actual_exception = nil
+          full_message = build_message(message, "<?> exception expected but none was thrown.", expected)
+          assert_block(full_message) do
+            begin
+              yield
+            rescue Exception => actual_exception
+              break
+            end
+            false
+          end
+          full_message = build_message(message, "<?> exception expected but was\n?", expected, actual_exception)
+          assert_block(full_message) {_expected_exception?(actual_exception, exceptions, modules)}
+          actual_exception
+        end
+      end
+
+      ##
+      # Alias of assert_raise.
+      #
+      # Will be deprecated in 1.9, and removed in 2.0.
+
+      public
+      def assert_raises(*args, &block)
+        assert_raise(*args, &block)
+      end
+
+      ##
+      # Passes if +object+ .instance_of? +klass+
+      #
+      # Example:
+      #   assert_instance_of String, 'foo'
+
+      public
+      def assert_instance_of(klass, object, message="")
+        _wrap_assertion do
+          assert_equal(Class, klass.class, "assert_instance_of takes a Class as its first argument")
+          full_message = build_message(message, <<EOT, object, klass, object.class)
+<?> expected to be an instance of
+<?> but was
+<?>.
+EOT
+          assert_block(full_message){object.instance_of?(klass)}
+        end
+      end
+
+      ##
+      # Passes if +object+ is nil.
+      #
+      # Example:
+      #   assert_nil [1, 2].uniq!
+
+      public
+      def assert_nil(object, message="")
+        assert_equal(nil, object, message)
+      end
+
+      ##
+      # Passes if +object+ .kind_of? +klass+
+      #
+      # Example:
+      #   assert_kind_of Object, 'foo'
+
+      public
+      def assert_kind_of(klass, object, message="")
+        _wrap_assertion do
+          assert(klass.kind_of?(Module), "The first parameter to assert_kind_of should be a kind_of Module.")
+          full_message = build_message(message, "<?>\nexpected to be kind_of\\?\n<?> but was\n<?>.", object, klass, object.class)
+          assert_block(full_message){object.kind_of?(klass)}
+        end
+      end
+
+      ##
+      # Passes if +object+ .respond_to? +method+
+      #
+      # Example:
+      #   assert_respond_to 'bugbear', :slice
+
+      public
+      def assert_respond_to(object, method, message="")
+        _wrap_assertion do
+          full_message = build_message(nil, "<?>\ngiven as the method name argument to #assert_respond_to must be a Symbol or #respond_to\\?(:to_str).", method)
+
+          assert_block(full_message) do
+            method.kind_of?(Symbol) || method.respond_to?(:to_str)
+          end
+          full_message = build_message(message, <<EOT, object, object.class, method)
+<?>
+of type <?>
+expected to respond_to\\?<?>.
+EOT
+          assert_block(full_message) { object.respond_to?(method) }
+        end
+      end
+
+      ##
+      # Passes if +string+ =~ +pattern+.
+      #
+      # Example:
+      #   assert_match(/\d+/, 'five, 6, seven')
+
+      public
+      def assert_match(pattern, string, message="")
+        _wrap_assertion do
+          pattern = case(pattern)
+            when String
+              Regexp.new(Regexp.escape(pattern))
+            else
+              pattern
+          end
+          full_message = build_message(message, "<?> expected to be =~\n<?>.", string, pattern)
+          assert_block(full_message) { string =~ pattern }
+        end
+      end
+
+      ##
+      # Passes if +actual+ .equal? +expected+ (i.e. they are the same
+      # instance).
+      #
+      # Example:
+      #   o = Object.new
+      #   assert_same o, o
+
+      public
+      def assert_same(expected, actual, message="")
+        full_message = build_message(message, <<EOT, expected, expected.__id__, actual, actual.__id__)
+<?>
+with id <?> expected to be equal\\? to
+<?>
+with id <?>.
+EOT
+        assert_block(full_message) { actual.equal?(expected) }
+      end
+
+      ##
+      # Compares the +object1+ with +object2+ using +operator+.
+      #
+      # Passes if object1.__send__(operator, object2) is true.
+      #
+      # Example:
+      #   assert_operator 5, :>=, 4
+
+      public
+      def assert_operator(object1, operator, object2, message="")
+        _wrap_assertion do
+          full_message = build_message(nil, "<?>\ngiven as the operator for #assert_operator must be a Symbol or #respond_to\\?(:to_str).", operator)
+          assert_block(full_message){operator.kind_of?(Symbol) || operator.respond_to?(:to_str)}
+          full_message = build_message(message, <<EOT, object1, AssertionMessage.literal(operator), object2)
+<?> expected to be
+?
+<?>.
+EOT
+          assert_block(full_message) { object1.__send__(operator, object2) }
+        end
+      end
+
+      ##
+      # Passes if block does not raise an exception.
+      #
+      # Example:
+      #   assert_nothing_raised do
+      #     [1, 2].uniq
+      #   end
+
+      public
+      def assert_nothing_raised(*args)
+        _wrap_assertion do
+          if Module === args.last
+            message = ""
+          else
+            message = args.pop
+          end
+          exceptions, modules = _check_exception_class(args)
+          begin
+            yield
+          rescue Exception => e
+            if ((args.empty? && !e.instance_of?(AssertionFailedError)) ||
+                _expected_exception?(e, exceptions, modules))
+              assert_block(build_message(message, "Exception raised:\n?", e)){false}
+            else
+              raise
+            end
+          end
+          nil
+        end
+      end
+
+      ##
+      # Flunk always fails.
+      #
+      # Example:
+      #   flunk 'Not done testing yet.'
+
+      public
+      def flunk(message="Flunked")
+        assert_block(build_message(message)){false}
+      end
+
+      ##
+      # Passes if ! +actual+ .equal? +expected+
+      #
+      # Example:
+      #   assert_not_same Object.new, Object.new
+
+      public
+      def assert_not_same(expected, actual, message="")
+        full_message = build_message(message, <<EOT, expected, expected.__id__, actual, actual.__id__)
+<?>
+with id <?> expected to not be equal\\? to
+<?>
+with id <?>.
+EOT
+        assert_block(full_message) { !actual.equal?(expected) }
+      end
+
+      ##
+      # Passes if +expected+ != +actual+
+      #
+      # Example:
+      #   assert_not_equal 'some string', 5
+
+      public
+      def assert_not_equal(expected, actual, message="")
+        full_message = build_message(message, "<?> expected to be != to\n<?>.", expected, actual)
+        assert_block(full_message) { expected != actual }
+      end
+
+      ##
+      # Passes if ! +object+ .nil?
+      #
+      # Example:
+      #   assert_not_nil '1 two 3'.sub!(/two/, '2')
+
+      public
+      def assert_not_nil(object, message="")
+        full_message = build_message(message, "<?> expected to not be nil.", object)
+        assert_block(full_message){!object.nil?}
+      end
+
+      ##
+      # Passes if +regexp+ !~ +string+ 
+      #
+      # Example:
+      #   assert_no_match(/two/, 'one 2 three')
+
+      public
+      def assert_no_match(regexp, string, message="")
+        _wrap_assertion do
+          assert_instance_of(Regexp, regexp, "The first argument to assert_no_match should be a Regexp.")
+          full_message = build_message(message, "<?> expected to not match\n<?>.", regexp, string)
+          assert_block(full_message) { regexp !~ string }
+        end
+      end
+
+      UncaughtThrow = {NameError => /^uncaught throw \`(.+)\'$/,
+                       ThreadError => /^uncaught throw \`(.+)\' in thread /} #`
+
+      ##
+      # Passes if the block throws +expected_symbol+
+      #
+      # Example:
+      #   assert_throws :done do
+      #     throw :done
+      #   end
+
+      public
+      def assert_throws(expected_symbol, message="", &proc)
+        _wrap_assertion do
+          assert_instance_of(Symbol, expected_symbol, "assert_throws expects the symbol that should be thrown for its first argument")
+          assert_block("Should have passed a block to assert_throws."){block_given?}
+          caught = true
+          begin
+            catch(expected_symbol) do
+              proc.call
+              caught = false
+            end
+            full_message = build_message(message, "<?> should have been thrown.", expected_symbol)
+            assert_block(full_message){caught}
+          rescue NameError, ThreadError => error
+            if UncaughtThrow[error.class] !~ error.message
+              raise error
+            end
+            full_message = build_message(message, "<?> expected to be thrown but\n<?> was thrown.", expected_symbol, $1.intern)
+            flunk(full_message)
+          end
+        end
+      end
+
+      ##
+      # Passes if block does not throw anything.
+      #
+      # Example:
+      #  assert_nothing_thrown do
+      #    [1, 2].uniq
+      #  end
+
+      public
+      def assert_nothing_thrown(message="", &proc)
+        _wrap_assertion do
+          assert(block_given?, "Should have passed a block to assert_nothing_thrown")
+          begin
+            proc.call
+          rescue NameError, ThreadError => error
+            if UncaughtThrow[error.class] !~ error.message
+              raise error
+            end
+            full_message = build_message(message, "<?> was thrown when nothing was expected", $1.intern)
+            flunk(full_message)
+          end
+          assert(true, "Expected nothing to be thrown")
+        end
+      end
+
+      ##
+      # Passes if +expected_float+ and +actual_float+ are equal
+      # within +delta+ tolerance.
+      #
+      # Example:
+      #   assert_in_delta 0.05, (50000.0 / 10**6), 0.00001
+
+      public
+      def assert_in_delta(expected_float, actual_float, delta, message="")
+        _wrap_assertion do
+          {expected_float => "first float", actual_float => "second float", delta => "delta"}.each do |float, name|
+            assert_respond_to(float, :to_f, "The arguments must respond to to_f; the #{name} did not")
+          end
+          assert_operator(delta, :>=, 0.0, "The delta should not be negative")
+          full_message = build_message(message, <<EOT, expected_float, actual_float, delta)
+<?> and
+<?> expected to be within
+<?> of each other.
+EOT
+          assert_block(full_message) { (expected_float.to_f - actual_float.to_f).abs <= delta.to_f }
+        end
+      end
+
+      ##
+      # Passes if the method send returns a true value.
+      #
+      # +send_array+ is composed of:
+      # * A receiver
+      # * A method
+      # * Arguments to the method
+      #
+      # Example:
+      #   assert_send [[1, 2], :include?, 4]
+
+      public
+      def assert_send(send_array, message="")
+        _wrap_assertion do
+          assert_instance_of(Array, send_array, "assert_send requires an array of send information")
+          assert(send_array.size >= 2, "assert_send requires at least a receiver and a message name")
+          full_message = build_message(message, <<EOT, send_array[0], AssertionMessage.literal(send_array[1].to_s), send_array[2..-1])
+<?> expected to respond to
+<?(?)> with a true value.
+EOT
+          assert_block(full_message) { send_array[0].__send__(send_array[1], *send_array[2..-1]) }
+        end
+      end
+
+      ##
+      # Builds a failure message.  +head+ is added before the +template+ and
+      # +arguments+ replaces the '?'s positionally in the template.
+
+      public
+      def build_message(head, template=nil, *arguments)
+        template &&= template.chomp
+        return AssertionMessage.new(head, template, arguments)
+      end
+
+      private
+      def _wrap_assertion
+        @_assertion_wrapped ||= false
+        unless (@_assertion_wrapped)
+          @_assertion_wrapped = true
+          begin
+            add_assertion
+            return yield
+          ensure
+            @_assertion_wrapped = false
+          end
+        else
+          return yield
+        end
+      end
+      
+      ##
+      # Called whenever an assertion is made.  Define this in classes that
+      # include Test::Unit::Assertions to record assertion counts.
+
+      private
+      def add_assertion
+      end
+
+      ##
+      # Select whether or not to use the pretty-printer. If this option is set
+      # to false before any assertions are made, pp.rb will not be required.
+
+      public
+      def self.use_pp=(value)
+        AssertionMessage.use_pp = value
+      end
+      
+      # :stopdoc:
+
+      class AssertionMessage
+        @use_pp = true
+        class << self
+          attr_accessor :use_pp
+        end
+
+        class Literal
+          def initialize(value)
+            @value = value
+          end
+          
+          def inspect
+            @value.to_s
+          end
+        end
+
+        class Template
+          def self.create(string)
+            parts = (string ? string.scan(/(?=[^\\])\?|(?:\\\?|[^\?])+/m) : [])
+            self.new(parts)
+          end
+
+          attr_reader :count
+
+          def initialize(parts)
+            @parts = parts
+            @count = parts.find_all{|e| e == '?'}.size
+          end
+
+          def result(parameters)
+            raise "The number of parameters does not match the number of substitutions." if(parameters.size != count)
+            params = parameters.dup
+            @parts.collect{|e| e == '?' ? params.shift : e.gsub(/\\\?/m, '?')}.join('')
+          end
+        end
+
+        def self.literal(value)
+          Literal.new(value)
+        end
+
+        include Util::BacktraceFilter
+
+        def initialize(head, template_string, parameters)
+          @head = head
+          @template_string = template_string
+          @parameters = parameters
+        end
+
+        def convert(object)
+          case object
+            when Exception
+              <<EOM.chop
+Class: <#{convert(object.class)}>
+Message: <#{convert(object.message)}>
+---Backtrace---
+#{filter_backtrace(object.backtrace).join("\n")}
+---------------
+EOM
+            else
+              if(self.class.use_pp)
+                begin
+                  require 'pp'
+                rescue LoadError
+                  self.class.use_pp = false
+                  return object.inspect
+                end unless(defined?(PP))
+                PP.pp(object, '').chomp
+              else
+                object.inspect
+              end
+          end
+        end
+
+        def template
+          @template ||= Template.create(@template_string)
+        end
+
+        def add_period(string)
+          (string =~ /\.\Z/ ? string : string + '.')
+        end
+
+        def to_s
+          message_parts = []
+          if (@head)
+            head = @head.to_s 
+            unless(head.empty?)
+              message_parts << add_period(head)
+            end
+          end
+          tail = template.result(@parameters.collect{|e| convert(e)})
+          message_parts << tail unless(tail.empty?)
+          message_parts.join("\n")
+        end
+      end
+
+      # :startdoc:
+
+    end
+  end
+end
diff --git a/test/lib/test/unit/autorunner.rb b/test/lib/test/unit/autorunner.rb
new file mode 100644
index 0000000..0dfc01c
--- /dev/null
+++ b/test/lib/test/unit/autorunner.rb
@@ -0,0 +1,219 @@
+require 'test/unit/ui/testrunnerutilities'
+require 'optparse'
+
+module Test
+  module Unit
+    class AutoRunner
+      def self.run(force_standalone=false, default_dir=nil, argv=ARGV, &block)
+        r = new(force_standalone || standalone?, &block)
+        r.base = default_dir
+        r.process_args(argv)
+        r.run
+      end
+      
+      def self.standalone?
+        return false unless("-e" == $0)
+        ObjectSpace.each_object(Class) do |klass|
+          return false if(klass < TestCase)
+        end
+        true
+      end
+
+      RUNNERS = {
+        :console => proc do |r|
+          require 'test/unit/ui/console/testrunner'
+          Test::Unit::UI::Console::TestRunner
+        end,
+        :gtk => proc do |r|
+          require 'test/unit/ui/gtk/testrunner'
+          Test::Unit::UI::GTK::TestRunner
+        end,
+        :gtk2 => proc do |r|
+          require 'test/unit/ui/gtk2/testrunner'
+          Test::Unit::UI::GTK2::TestRunner
+        end,
+        :fox => proc do |r|
+          require 'test/unit/ui/fox/testrunner'
+          Test::Unit::UI::Fox::TestRunner
+        end,
+        :tk => proc do |r|
+          require 'test/unit/ui/tk/testrunner'
+          Test::Unit::UI::Tk::TestRunner
+        end,
+      }
+
+      OUTPUT_LEVELS = [
+        [:silent, UI::SILENT],
+        [:progress, UI::PROGRESS_ONLY],
+        [:normal, UI::NORMAL],
+        [:verbose, UI::VERBOSE],
+      ]
+
+      COLLECTORS = {
+        :objectspace => proc do |r|
+          require 'test/unit/collector/objectspace'
+          c = Collector::ObjectSpace.new
+          c.filter = r.filters
+          c.collect($0.sub(/\.rb\Z/, ''))
+        end,
+        :dir => proc do |r|
+          require 'test/unit/collector/dir'
+          c = Collector::Dir.new
+          c.filter = r.filters
+          c.pattern.concat(r.pattern) if(r.pattern)
+          c.exclude.concat(r.exclude) if(r.exclude)
+          c.base = r.base
+          $:.push(r.base) if r.base
+          c.collect(*(r.to_run.empty? ? ['.'] : r.to_run))
+        end,
+      }
+
+      attr_reader :suite
+      attr_accessor :output_level, :filters, :to_run, :pattern, :exclude, :base, :workdir
+      attr_writer :runner, :collector
+
+      def initialize(standalone)
+        Unit.run = true
+        @standalone = standalone
+        @runner = RUNNERS[:console]
+        @collector = COLLECTORS[(standalone ? :dir : :objectspace)]
+        @filters = []
+        @to_run = []
+        @output_level = UI::NORMAL
+        @workdir = nil
+        yield(self) if(block_given?)
+      end
+
+      def process_args(args = ARGV)
+        begin
+          options.order!(args) {|arg| @to_run << arg}
+        rescue OptionParser::ParseError => e
+          puts e
+          puts options
+          $! = nil
+          abort
+        else
+          @filters << proc{false} unless(@filters.empty?)
+        end
+        not @to_run.empty?
+      end
+
+      def options
+        @options ||= OptionParser.new do |o|
+          o.banner = "Test::Unit automatic runner."
+          o.banner << "\nUsage: #{$0} [options] [-- untouched arguments]"
+
+          o.on
+          o.on('-r', '--runner=RUNNER', RUNNERS,
+               "Use the given RUNNER.",
+               "(" + keyword_display(RUNNERS) + ")") do |r|
+            @runner = r
+          end
+
+          if(@standalone)
+            o.on('-b', '--basedir=DIR', "Base directory of test suites.") do |b|
+              @base = b
+            end
+
+            o.on('-w', '--workdir=DIR', "Working directory to run tests.") do |w|
+              @workdir = w
+            end
+
+            o.on('-a', '--add=TORUN', Array,
+                 "Add TORUN to the list of things to run;",
+                 "can be a file or a directory.") do |a|
+              @to_run.concat(a)
+            end
+
+            @pattern = []
+            o.on('-p', '--pattern=PATTERN', Regexp,
+                 "Match files to collect against PATTERN.") do |e|
+              @pattern << e
+            end
+
+            @exclude = []
+            o.on('-x', '--exclude=PATTERN', Regexp,
+                 "Ignore files to collect against PATTERN.") do |e|
+              @exclude << e
+            end
+          end
+
+          o.on('-n', '--name=NAME', String,
+               "Runs tests matching NAME.",
+               "(patterns may be used).") do |n|
+            n = (%r{\A/(.*)/\Z} =~ n ? Regexp.new($1) : n)
+            case n
+            when Regexp
+              @filters << proc{|t| n =~ t.method_name ? true : nil}
+            else
+              @filters << proc{|t| n == t.method_name ? true : nil}
+            end
+          end
+
+          o.on('-t', '--testcase=TESTCASE', String,
+               "Runs tests in TestCases matching TESTCASE.",
+               "(patterns may be used).") do |n|
+            n = (%r{\A/(.*)/\Z} =~ n ? Regexp.new($1) : n)
+            case n
+            when Regexp
+              @filters << proc{|t| n =~ t.class.name ? true : nil}
+            else
+              @filters << proc{|t| n == t.class.name ? true : nil}
+            end
+          end
+
+          o.on('-I', "--load-path=DIR[#{File::PATH_SEPARATOR}DIR...]",
+               "Appends directory list to $LOAD_PATH.") do |dirs|
+            $LOAD_PATH.concat(dirs.split(File::PATH_SEPARATOR))
+          end
+
+          o.on('-v', '--verbose=[LEVEL]', OUTPUT_LEVELS,
+               "Set the output level (default is verbose).",
+               "(" + keyword_display(OUTPUT_LEVELS) + ")") do |l|
+            @output_level = l || UI::VERBOSE
+          end
+
+          o.on('--',
+               "Stop processing options so that the",
+               "remaining options will be passed to the",
+               "test."){o.terminate}
+
+          o.on('-h', '--help', 'Display this help.'){puts o; exit}
+
+          o.on_tail
+          o.on_tail('Deprecated options:')
+
+          o.on_tail('--console', 'Console runner (use --runner).') do
+            warn("Deprecated option (--console).")
+            @runner = RUNNERS[:console]
+          end
+
+          o.on_tail('--gtk', 'GTK runner (use --runner).') do
+            warn("Deprecated option (--gtk).")
+            @runner = RUNNERS[:gtk]
+          end
+
+          o.on_tail('--fox', 'Fox runner (use --runner).') do
+            warn("Deprecated option (--fox).")
+            @runner = RUNNERS[:fox]
+          end
+
+          o.on_tail
+        end
+      end
+
+      def keyword_display(array)
+        list = array.collect {|e, *| e.to_s}
+        Array === array or list.sort!
+        list.collect {|e| e.sub(/^(.)([A-Za-z]+)(?=\w*$)/, '\\1[\\2]')}.join(", ")
+      end
+
+      def run
+        @suite = @collector[self]
+        result = @runner[self] or return false
+        Dir.chdir(@workdir) if @workdir
+        result.run(@suite, @output_level).passed?
+      end
+    end
+  end
+end
diff --git a/test/lib/test/unit/collector.rb b/test/lib/test/unit/collector.rb
new file mode 100644
index 0000000..9e9e654
--- /dev/null
+++ b/test/lib/test/unit/collector.rb
@@ -0,0 +1,43 @@
+module Test
+  module Unit
+    module Collector
+      def initialize
+        @filters = []
+      end
+
+      def filter=(filters)
+        @filters = case(filters)
+          when Proc
+            [filters]
+          when Array
+            filters
+        end
+      end
+
+      def add_suite(destination, suite)
+        to_delete = suite.tests.find_all{|t| !include?(t)}
+        to_delete.each{|t| suite.delete(t)}
+        destination << suite unless(suite.size == 0)
+      end
+
+      def include?(test)
+        return true if(@filters.empty?)
+        @filters.each do |filter|
+          result = filter[test]
+          if(result.nil?)
+            next
+          elsif(!result)
+            return false
+          else
+            return true
+          end
+        end
+        true
+      end
+
+      def sort(suites)
+        suites.sort_by{|s| s.name}
+      end
+    end
+  end
+end
diff --git a/test/lib/test/unit/collector/dir.rb b/test/lib/test/unit/collector/dir.rb
new file mode 100644
index 0000000..97c8d28
--- /dev/null
+++ b/test/lib/test/unit/collector/dir.rb
@@ -0,0 +1,107 @@
+require 'test/unit/testsuite'
+require 'test/unit/collector'
+
+module Test
+  module Unit
+    module Collector
+      class Dir
+        include Collector
+
+        attr_reader :pattern, :exclude
+        attr_accessor :base
+
+        def initialize(dir=::Dir, file=::File, object_space=::ObjectSpace, req=nil)
+          super()
+          @dir = dir
+          @file = file
+          @object_space = object_space
+          @req = req
+          @pattern = [/\btest_.*\.rb\Z/m]
+          @exclude = []
+        end
+
+        def collect(*from)
+          basedir = @base
+          $:.push(basedir) if basedir
+          if(from.empty?)
+            recursive_collect('.', find_test_cases)
+          elsif(from.size == 1)
+            recursive_collect(from.first, find_test_cases)
+          else
+            suites = []
+            from.each do |f|
+              suite = recursive_collect(f, find_test_cases)
+              suites << suite unless(suite.tests.empty?)
+            end
+            suite = TestSuite.new("[#{from.join(', ')}]")
+            sort(suites).each{|s| suite << s}
+            suite
+          end
+        ensure
+          $:.delete_at($:.rindex(basedir)) if basedir
+        end
+
+        def find_test_cases(ignore=[])
+          cases = []
+          @object_space.each_object(Class) do |c|
+            cases << c if(c < TestCase && !ignore.include?(c))
+          end
+          ignore.concat(cases)
+          cases
+        end
+
+        def recursive_collect(name, already_gathered)
+          sub_suites = []
+          path = realdir(name)
+          if @file.directory?(path)
+	    dir_name = name unless name == '.'
+            @dir.entries(path).each do |e|
+              next if(e == '.' || e == '..')
+              e_name = dir_name ? @file.join(dir_name, e) : e
+              if @file.directory?(realdir(e_name))
+                next if /\ACVS\z/ =~ e
+                sub_suite = recursive_collect(e_name, already_gathered)
+                sub_suites << sub_suite unless(sub_suite.empty?)
+              else
+                next if /~\z/ =~ e_name or /\A\.\#/ =~ e
+                if @pattern and !@pattern.empty?
+                  next unless @pattern.any? {|pat| pat =~ e_name}
+                end
+                if @exclude and !@exclude.empty?
+                  next if @exclude.any? {|pat| pat =~ e_name}
+                end
+                collect_file(e_name, sub_suites, already_gathered)
+              end
+            end
+          else
+            collect_file(name, sub_suites, already_gathered)
+          end
+          suite = TestSuite.new(@file.basename(name))
+          sort(sub_suites).each{|s| suite << s}
+          suite
+        end
+
+        def collect_file(name, suites, already_gathered)
+          dir = @file.dirname(@file.expand_path(name, @base))
+          $:.unshift(dir)
+          if(@req)
+            @req.require(name)
+          else
+            require(name)
+          end
+          find_test_cases(already_gathered).each{|t| add_suite(suites, t.suite)}
+        ensure
+          $:.delete_at($:.rindex(dir)) if(dir)
+        end
+
+	def realdir(path)
+	  if @base
+	    @file.join(@base, path)
+	  else
+	    path
+	  end
+	end
+      end
+    end
+  end
+end
diff --git a/test/lib/test/unit/error.rb b/test/lib/test/unit/error.rb
new file mode 100644
index 0000000..43a813f
--- /dev/null
+++ b/test/lib/test/unit/error.rb
@@ -0,0 +1,56 @@
+#--
+#
+# Author:: Nathaniel Talbott.
+# Copyright:: Copyright (c) 2000-2002 Nathaniel Talbott. All rights reserved.
+# License:: Ruby license.
+
+require 'test/unit/util/backtracefilter'
+
+module Test
+  module Unit
+
+    # Encapsulates an error in a test. Created by
+    # Test::Unit::TestCase when it rescues an exception thrown
+    # during the processing of a test.
+    class Error
+      include Util::BacktraceFilter
+
+      attr_reader(:test_name, :exception)
+
+      SINGLE_CHARACTER = 'E'
+
+      # Creates a new Error with the given test_name and
+      # exception.
+      def initialize(test_name, exception)
+        @test_name = test_name
+        @exception = exception
+      end
+
+      # Returns a single character representation of an error.
+      def single_character_display
+        SINGLE_CHARACTER
+      end
+
+      # Returns the message associated with the error.
+      def message
+        "#{@exception.class.name}: #{@exception.message}"
+      end
+
+      # Returns a brief version of the error description.
+      def short_display
+        "#@test_name: #{message.split("\n")[0]}"
+      end
+
+      # Returns a verbose version of the error description.
+      def long_display
+        backtrace = filter_backtrace(@exception.backtrace).join("\n    ")
+        "Error:\n#@test_name:\n#{message}\n    #{backtrace}"
+      end
+
+      # Overridden to return long_display.
+      def to_s
+        long_display
+      end
+    end
+  end
+end
diff --git a/test/lib/test/unit/failure.rb b/test/lib/test/unit/failure.rb
new file mode 100644
index 0000000..832c998
--- /dev/null
+++ b/test/lib/test/unit/failure.rb
@@ -0,0 +1,51 @@
+#--
+#
+# Author:: Nathaniel Talbott.
+# Copyright:: Copyright (c) 2000-2002 Nathaniel Talbott. All rights reserved.
+# License:: Ruby license.
+
+module Test
+  module Unit
+
+    # Encapsulates a test failure. Created by Test::Unit::TestCase
+    # when an assertion fails.
+    class Failure
+      attr_reader :test_name, :location, :message
+      
+      SINGLE_CHARACTER = 'F'
+
+      # Creates a new Failure with the given location and
+      # message.
+      def initialize(test_name, location, message)
+        @test_name = test_name
+        @location = location
+        @message = message
+      end
+      
+      # Returns a single character representation of a failure.
+      def single_character_display
+        SINGLE_CHARACTER
+      end
+
+      # Returns a brief version of the error description.
+      def short_display
+        "#@test_name: #{@message.split("\n")[0]}"
+      end
+
+      # Returns a verbose version of the error description.
+      def long_display
+        location_display = if(location.size == 1)
+          location[0].sub(/\A(.+:\d+).*/, ' [\\1]')
+        else
+          "\n    [#{location.join("\n     ")}]"
+        end
+        "Failure:\n#@test_name#{location_display}:\n#@message"
+      end
+
+      # Overridden to return long_display.
+      def to_s
+        long_display
+      end
+    end
+  end
+end
diff --git a/test/lib/test/unit/testcase.rb b/test/lib/test/unit/testcase.rb
new file mode 100644
index 0000000..f53b460
--- /dev/null
+++ b/test/lib/test/unit/testcase.rb
@@ -0,0 +1,160 @@
+#--
+#
+# Author:: Nathaniel Talbott.
+# Copyright:: Copyright (c) 2000-2003 Nathaniel Talbott. All rights reserved.
+# License:: Ruby license.
+
+require 'test/unit/assertions'
+require 'test/unit/failure'
+require 'test/unit/error'
+require 'test/unit/testsuite'
+require 'test/unit/assertionfailederror'
+require 'test/unit/util/backtracefilter'
+
+module Test
+  module Unit
+
+    # Ties everything together. If you subclass and add your own
+    # test methods, it takes care of making them into tests and
+    # wrapping those tests into a suite. It also does the
+    # nitty-gritty of actually running an individual test and
+    # collecting its results into a Test::Unit::TestResult object.
+    class TestCase
+      include Assertions
+      include Util::BacktraceFilter
+      
+      attr_reader :method_name
+      
+      STARTED = name + "::STARTED"
+      FINISHED = name + "::FINISHED"
+
+      ##
+      # These exceptions are not caught by #run.
+
+      PASSTHROUGH_EXCEPTIONS = [NoMemoryError, SignalException, Interrupt,
+                                SystemExit]
+
+      # Creates a new instance of the fixture for running the
+      # test represented by test_method_name.
+      def initialize(test_method_name)
+        unless(respond_to?(test_method_name) and
+               (method(test_method_name).arity == 0 ||
+                method(test_method_name).arity == -1))
+          throw :invalid_test
+        end
+        @method_name = test_method_name
+        @test_passed = true
+      end
+
+      # Rolls up all of the test* methods in the fixture into
+      # one suite, creating a new instance of the fixture for
+      # each method.
+      def self.suite
+        method_names = public_instance_methods(true)
+        tests = method_names.delete_if {|method_name| method_name !~ /^test./}
+        suite = TestSuite.new(name)
+        tests.sort.each do
+          |test|
+          catch(:invalid_test) do
+            suite << new(test)
+          end
+        end
+        if (suite.empty?)
+          catch(:invalid_test) do
+            suite << new("default_test")
+          end
+        end
+        return suite
+      end
+
+      # Runs the individual test method represented by this
+      # instance of the fixture, collecting statistics, failures
+      # and errors in result.
+      def run(result)
+        yield(STARTED, name)
+        @_result = result
+        begin
+          setup
+          __send__(@method_name)
+        rescue AssertionFailedError => e
+          add_failure(e.message, e.backtrace)
+        rescue Exception
+          raise if PASSTHROUGH_EXCEPTIONS.include? $!.class
+          add_error($!)
+        ensure
+          begin
+            teardown
+          rescue AssertionFailedError => e
+            add_failure(e.message, e.backtrace)
+          rescue Exception
+            raise if PASSTHROUGH_EXCEPTIONS.include? $!.class
+            add_error($!)
+          end
+        end
+        result.add_run
+        yield(FINISHED, name)
+      end
+
+      # Called before every test method runs. Can be used
+      # to set up fixture information.
+      def setup
+      end
+
+      # Called after every test method runs. Can be used to tear
+      # down fixture information.
+      def teardown
+      end
+      
+      def default_test
+        flunk("No tests were specified")
+      end
+
+      # Returns whether this individual test passed or
+      # not. Primarily for use in teardown so that artifacts
+      # can be left behind if the test fails.
+      def passed?
+        return @test_passed
+      end
+      private :passed?
+
+      def size
+        1
+      end
+
+      def add_assertion
+        @_result.add_assertion
+      end
+      private :add_assertion
+
+      def add_failure(message, all_locations=caller())
+        @test_passed = false
+        @_result.add_failure(Failure.new(name, filter_backtrace(all_locations), message))
+      end
+      private :add_failure
+
+      def add_error(exception)
+        @test_passed = false
+        @_result.add_error(Error.new(name, exception))
+      end
+      private :add_error
+
+      # Returns a human-readable name for the specific test that
+      # this instance of TestCase represents.
+      def name
+        "#{@method_name}(#{self.class.name})"
+      end
+
+      # Overridden to return #name.
+      def to_s
+        name
+      end
+      
+      # It's handy to be able to compare TestCase instances.
+      def ==(other)
+        return false unless(other.kind_of?(self.class))
+        return false unless(@method_name == other.method_name)
+        self.class == other.class
+      end
+    end
+  end
+end
diff --git a/test/lib/test/unit/testresult.rb b/test/lib/test/unit/testresult.rb
new file mode 100644
index 0000000..e3a472e
--- /dev/null
+++ b/test/lib/test/unit/testresult.rb
@@ -0,0 +1,80 @@
+#--
+# Author:: Nathaniel Talbott.
+# Copyright:: Copyright (c) 2000-2002 Nathaniel Talbott. All rights reserved.
+# License:: Ruby license.
+
+require 'test/unit/util/observable'
+
+module Test
+  module Unit
+
+    # Collects Test::Unit::Failure and Test::Unit::Error so that
+    # they can be displayed to the user. To this end, observers
+    # can be added to it, allowing the dynamic updating of, say, a
+    # UI.
+    class TestResult
+      include Util::Observable
+
+      CHANGED = "CHANGED"
+      FAULT = "FAULT"
+
+      attr_reader(:run_count, :assertion_count)
+
+      # Constructs a new, empty TestResult.
+      def initialize
+        @run_count, @assertion_count = 0, 0
+        @failures, @errors = Array.new, Array.new
+      end
+
+      # Records a test run.
+      def add_run
+        @run_count += 1
+        notify_listeners(CHANGED, self)
+      end
+
+      # Records a Test::Unit::Failure.
+      def add_failure(failure)
+        @failures << failure
+        notify_listeners(FAULT, failure)
+        notify_listeners(CHANGED, self)
+      end
+
+      # Records a Test::Unit::Error.
+      def add_error(error)
+        @errors << error
+        notify_listeners(FAULT, error)
+        notify_listeners(CHANGED, self)
+      end
+
+      # Records an individual assertion.
+      def add_assertion
+        @assertion_count += 1
+        notify_listeners(CHANGED, self)
+      end
+
+      # Returns a string contain the recorded runs, assertions,
+      # failures and errors in this TestResult.
+      def to_s
+        "#{run_count} tests, #{assertion_count} assertions, #{failure_count} failures, #{error_count} errors"
+      end
+
+      # Returns whether or not this TestResult represents
+      # successful completion.
+      def passed?
+        return @failures.empty? && @errors.empty?
+      end
+
+      # Returns the number of failures this TestResult has
+      # recorded.
+      def failure_count
+        return @failures.size
+      end
+
+      # Returns the number of errors this TestResult has
+      # recorded.
+      def error_count
+        return @errors.size
+      end
+    end
+  end
+end
diff --git a/test/lib/test/unit/testsuite.rb b/test/lib/test/unit/testsuite.rb
new file mode 100644
index 0000000..6fea976
--- /dev/null
+++ b/test/lib/test/unit/testsuite.rb
@@ -0,0 +1,76 @@
+#--
+#
+# Author:: Nathaniel Talbott.
+# Copyright:: Copyright (c) 2000-2003 Nathaniel Talbott. All rights reserved.
+# License:: Ruby license.
+
+module Test
+  module Unit
+
+    # A collection of tests which can be #run.
+    #
+    # Note: It is easy to confuse a TestSuite instance with
+    # something that has a static suite method; I know because _I_
+    # have trouble keeping them straight. Think of something that
+    # has a suite method as simply providing a way to get a
+    # meaningful TestSuite instance.
+    class TestSuite
+      attr_reader :name, :tests
+      
+      STARTED = name + "::STARTED"
+      FINISHED = name + "::FINISHED"
+
+      # Creates a new TestSuite with the given name.
+      def initialize(name="Unnamed TestSuite")
+        @name = name
+        @tests = []
+      end
+
+      # Runs the tests and/or suites contained in this
+      # TestSuite.
+      def run(result, &progress_block)
+        yield(STARTED, name)
+        @tests.each do |test|
+          test.run(result, &progress_block)
+        end
+        yield(FINISHED, name)
+      end
+
+      # Adds the test to the suite.
+      def <<(test)
+        @tests << test
+        self
+      end
+
+      def delete(test)
+        @tests.delete(test)
+      end
+
+      # Retuns the rolled up number of tests in this suite;
+      # i.e. if the suite contains other suites, it counts the
+      # tests within those suites, not the suites themselves.
+      def size
+        total_size = 0
+        @tests.each { |test| total_size += test.size }
+        total_size
+      end
+      
+      def empty?
+        tests.empty?
+      end
+
+      # Overridden to return the name given the suite at
+      # creation.
+      def to_s
+        @name
+      end
+      
+      # It's handy to be able to compare TestSuite instances.
+      def ==(other)
+        return false unless(other.kind_of?(self.class))
+        return false unless(@name == other.name)
+        @tests == other.tests
+      end
+    end
+  end
+end
diff --git a/test/lib/test/unit/ui/console/testrunner.rb b/test/lib/test/unit/ui/console/testrunner.rb
new file mode 100644
index 0000000..6b600e3
--- /dev/null
+++ b/test/lib/test/unit/ui/console/testrunner.rb
@@ -0,0 +1,127 @@
+#--
+#
+# Author:: Nathaniel Talbott.
+# Copyright:: Copyright (c) 2000-2003 Nathaniel Talbott. All rights reserved.
+# License:: Ruby license.
+
+require 'test/unit/ui/testrunnermediator'
+require 'test/unit/ui/testrunnerutilities'
+
+module Test
+  module Unit
+    module UI
+      module Console
+
+        # Runs a Test::Unit::TestSuite on the console.
+        class TestRunner
+          extend TestRunnerUtilities
+
+          # Creates a new TestRunner for running the passed
+          # suite. If quiet_mode is true, the output while
+          # running is limited to progress dots, errors and
+          # failures, and the final result. io specifies
+          # where runner output should go to; defaults to
+          # STDOUT.
+          def initialize(suite, output_level=NORMAL, io=STDOUT)
+            if (suite.respond_to?(:suite))
+              @suite = suite.suite
+            else
+              @suite = suite
+            end
+            @output_level = output_level
+            @io = io
+            @already_outputted = false
+            @faults = []
+          end
+
+          # Begins the test run.
+          def start
+            setup_mediator
+            attach_to_mediator
+            return start_mediator
+          end
+
+          private
+          def setup_mediator
+            @mediator = create_mediator(@suite)
+            suite_name = @suite.to_s
+            if ( @suite.kind_of?(Module) )
+              suite_name = @suite.name
+            end
+            output("Loaded suite #{suite_name}")
+          end
+          
+          def create_mediator(suite)
+            return TestRunnerMediator.new(suite)
+          end
+          
+          def attach_to_mediator
+            @mediator.add_listener(TestResult::FAULT, &method(:add_fault))
+            @mediator.add_listener(TestRunnerMediator::STARTED, &method(:started))
+            @mediator.add_listener(TestRunnerMediator::FINISHED, &method(:finished))
+            @mediator.add_listener(TestCase::STARTED, &method(:test_started))
+            @mediator.add_listener(TestCase::FINISHED, &method(:test_finished))
+          end
+          
+          def start_mediator
+            return @mediator.run_suite
+          end
+          
+          def add_fault(fault)
+            @faults << fault
+            output_single(fault.single_character_display, PROGRESS_ONLY)
+            @already_outputted = true
+          end
+          
+          def started(result)
+            @result = result
+            output("Started")
+          end
+          
+          def finished(elapsed_time)
+            nl
+            output("Finished in #{elapsed_time} seconds.")
+            @faults.each_with_index do |fault, index|
+              nl
+              output("%3d) %s" % [index + 1, fault.long_display])
+            end
+            nl
+            output(@result)
+          end
+          
+          def test_started(name)
+            output_single(name + ": ", VERBOSE)
+          end
+          
+          def test_finished(name)
+            output_single(".", PROGRESS_ONLY) unless (@already_outputted)
+            nl(VERBOSE)
+            @already_outputted = false
+          end
+          
+          def nl(level=NORMAL)
+            output("", level)
+          end
+          
+          def output(something, level=NORMAL)
+            @io.puts(something) if (output?(level))
+            @io.flush
+          end
+          
+          def output_single(something, level=NORMAL)
+            @io.write(something) if (output?(level))
+            @io.flush
+          end
+          
+          def output?(level)
+            level <= @output_level
+          end
+        end
+      end
+    end
+  end
+end
+
+if __FILE__ == $0
+  Test::Unit::UI::Console::TestRunner.start_command_line_test
+end
diff --git a/test/lib/test/unit/ui/testrunnermediator.rb b/test/lib/test/unit/ui/testrunnermediator.rb
new file mode 100644
index 0000000..d34510d
--- /dev/null
+++ b/test/lib/test/unit/ui/testrunnermediator.rb
@@ -0,0 +1,68 @@
+#--
+#
+# Author:: Nathaniel Talbott.
+# Copyright:: Copyright (c) 2000-2002 Nathaniel Talbott. All rights reserved.
+# License:: Ruby license.
+
+require 'test/unit'
+require 'test/unit/util/observable'
+require 'test/unit/testresult'
+
+module Test
+  module Unit
+    module UI
+
+      # Provides an interface to write any given UI against,
+      # hopefully making it easy to write new UIs.
+      class TestRunnerMediator
+        RESET = name + "::RESET"
+        STARTED = name + "::STARTED"
+        FINISHED = name + "::FINISHED"
+        
+        include Util::Observable
+        
+        # Creates a new TestRunnerMediator initialized to run
+        # the passed suite.
+        def initialize(suite)
+          @suite = suite
+        end
+
+        # Runs the suite the TestRunnerMediator was created
+        # with.
+        def run_suite
+          Unit.run = true
+          begin_time = Time.now
+          notify_listeners(RESET, @suite.size)
+          result = create_result
+          notify_listeners(STARTED, result)
+          result_listener = result.add_listener(TestResult::CHANGED) do |updated_result|
+            notify_listeners(TestResult::CHANGED, updated_result)
+          end
+          
+          fault_listener = result.add_listener(TestResult::FAULT) do |fault|
+            notify_listeners(TestResult::FAULT, fault)
+          end
+          
+          @suite.run(result) do |channel, value|
+            notify_listeners(channel, value)
+          end
+          
+          result.remove_listener(TestResult::FAULT, fault_listener)
+          result.remove_listener(TestResult::CHANGED, result_listener)
+          end_time = Time.now
+          elapsed_time = end_time - begin_time
+          notify_listeners(FINISHED, elapsed_time) #"Finished in #{elapsed_time} seconds.")
+          return result
+        end
+
+        private
+        # A factory method to create the result the mediator
+        # should run with. Can be overridden by subclasses if
+        # one wants to use a different result.
+        def create_result
+          return TestResult.new
+        end
+      end
+    end
+  end
+end
diff --git a/test/lib/test/unit/ui/testrunnerutilities.rb b/test/lib/test/unit/ui/testrunnerutilities.rb
new file mode 100644
index 0000000..70b885b
--- /dev/null
+++ b/test/lib/test/unit/ui/testrunnerutilities.rb
@@ -0,0 +1,46 @@
+#--
+#
+# Author:: Nathaniel Talbott.
+# Copyright:: Copyright (c) 2000-2002 Nathaniel Talbott. All rights reserved.
+# License:: Ruby license.
+
+module Test
+  module Unit
+    module UI
+
+      SILENT = 0
+      PROGRESS_ONLY = 1
+      NORMAL = 2
+      VERBOSE = 3
+
+      # Provides some utilities common to most, if not all,
+      # TestRunners.
+      #
+      #--
+      #
+      # Perhaps there ought to be a TestRunner superclass? There
+      # seems to be a decent amount of shared code between test
+      # runners.
+
+      module TestRunnerUtilities
+
+        # Creates a new TestRunner and runs the suite.
+        def run(suite, output_level=NORMAL)
+          return new(suite, output_level).start
+        end
+
+        # Takes care of the ARGV parsing and suite
+        # determination necessary for running one of the
+        # TestRunners from the command line.
+        def start_command_line_test
+          if ARGV.empty?
+            puts "You should supply the name of a test suite file to the runner"
+            exit
+          end
+          require ARGV[0].gsub(/.+::/, '')
+          new(eval(ARGV[0])).start
+        end
+      end
+    end
+  end
+end
diff --git a/test/lib/test/unit/util/backtracefilter.rb b/test/lib/test/unit/util/backtracefilter.rb
new file mode 100644
index 0000000..7ebec2d
--- /dev/null
+++ b/test/lib/test/unit/util/backtracefilter.rb
@@ -0,0 +1,40 @@
+module Test
+  module Unit
+    module Util
+      module BacktraceFilter
+        TESTUNIT_FILE_SEPARATORS = %r{[\\/:]}
+        TESTUNIT_PREFIX = __FILE__.split(TESTUNIT_FILE_SEPARATORS)[0..-3]
+        TESTUNIT_RB_FILE = /\.rb\Z/
+        
+        def filter_backtrace(backtrace, prefix=nil)
+          return ["No backtrace"] unless(backtrace)
+          split_p = if(prefix)
+            prefix.split(TESTUNIT_FILE_SEPARATORS)
+          else
+            TESTUNIT_PREFIX
+          end
+          match = proc do |e|
+            split_e = e.split(TESTUNIT_FILE_SEPARATORS)[0, split_p.size]
+            next false unless(split_e[0..-2] == split_p[0..-2])
+            split_e[-1].sub(TESTUNIT_RB_FILE, '') == split_p[-1]
+          end
+          return backtrace unless(backtrace.detect(&match))
+          found_prefix = false
+          new_backtrace = backtrace.reverse.reject do |e|
+            if(match[e])
+              found_prefix = true
+              true
+            elsif(found_prefix)
+              false
+            else
+              true
+            end
+          end.reverse
+          new_backtrace = (new_backtrace.empty? ? backtrace : new_backtrace)
+          new_backtrace = new_backtrace.reject(&match)
+          new_backtrace.empty? ? backtrace : new_backtrace
+        end
+      end
+    end
+  end
+end
diff --git a/test/lib/test/unit/util/observable.rb b/test/lib/test/unit/util/observable.rb
new file mode 100644
index 0000000..3567d34
--- /dev/null
+++ b/test/lib/test/unit/util/observable.rb
@@ -0,0 +1,90 @@
+#--
+#
+# Author:: Nathaniel Talbott.
+# Copyright:: Copyright (c) 2000-2002 Nathaniel Talbott. All rights reserved.
+# License:: Ruby license.
+
+require 'test/unit/util/procwrapper'
+
+module Test
+  module Unit
+    module Util
+
+      # This is a utility class that allows anything mixing
+      # it in to notify a set of listeners about interesting
+      # events.
+      module Observable
+        # We use this for defaults since nil might mean something
+        NOTHING = "NOTHING/#{__id__}"
+
+        # Adds the passed proc as a listener on the
+        # channel indicated by channel_name. listener_key
+        # is used to remove the listener later; if none is
+        # specified, the proc itself is used.
+        #
+        # Whatever is used as the listener_key is
+        # returned, making it very easy to use the proc
+        # itself as the listener_key:
+        #
+        #  listener = add_listener("Channel") { ... }
+        #  remove_listener("Channel", listener)
+        def add_listener(channel_name, listener_key=NOTHING, &listener) # :yields: value
+          unless(block_given?)
+            raise ArgumentError.new("No callback was passed as a listener")
+          end
+      
+          key = listener_key
+          if (listener_key == NOTHING)
+            listener_key = listener
+            key = ProcWrapper.new(listener)
+          end
+      
+          channels[channel_name] ||= {}
+          channels[channel_name][key] = listener
+          return listener_key
+        end
+
+        # Removes the listener indicated by listener_key
+        # from the channel indicated by
+        # channel_name. Returns the registered proc, or
+        # nil if none was found.
+        def remove_listener(channel_name, listener_key)
+          channel = channels[channel_name]
+          return nil unless (channel)
+          key = listener_key
+          if (listener_key.instance_of?(Proc))
+            key = ProcWrapper.new(listener_key)
+          end
+          if (channel.has_key?(key))
+            return channel.delete(key)
+          end
+          return nil
+        end
+
+        # Calls all the procs registered on the channel
+        # indicated by channel_name. If value is
+        # specified, it is passed in to the procs,
+        # otherwise they are called with no arguments.
+        #
+        #--
+        #
+        # Perhaps this should be private? Would it ever
+        # make sense for an external class to call this
+        # method directly?
+        def notify_listeners(channel_name, *arguments)
+          channel = channels[channel_name]
+          return 0 unless (channel)
+          listeners = channel.values
+          listeners.each { |listener| listener.call(*arguments) }
+          return listeners.size
+        end
+
+        private
+        def channels
+          @channels ||= {}
+          return @channels
+        end
+      end
+    end
+  end
+end
diff --git a/test/lib/test/unit/util/procwrapper.rb b/test/lib/test/unit/util/procwrapper.rb
new file mode 100644
index 0000000..ad72521
--- /dev/null
+++ b/test/lib/test/unit/util/procwrapper.rb
@@ -0,0 +1,48 @@
+#--
+#
+# Author:: Nathaniel Talbott.
+# Copyright:: Copyright (c) 2000-2002 Nathaniel Talbott. All rights reserved.
+# License:: Ruby license.
+
+module Test
+  module Unit
+    module Util
+
+      # Allows the storage of a Proc passed through '&' in a
+      # hash.
+      #
+      # Note: this may be inefficient, since the hash being
+      # used is not necessarily very good. In Observable,
+      # efficiency is not too important, since the hash is
+      # only accessed when adding and removing listeners,
+      # not when notifying.
+
+      class ProcWrapper
+
+        # Creates a new wrapper for a_proc.
+        def initialize(a_proc)
+          @a_proc = a_proc
+          @hash = a_proc.inspect.sub(/^(#<#{a_proc.class}:)/){''}.sub(/(>)$/){''}.hex
+        end
+
+        def hash
+          return @hash
+        end
+
+        def ==(other)
+          case(other)
+            when ProcWrapper
+              return @a_proc == other.to_proc
+            else
+              return super
+          end
+        end
+        alias :eql? :==
+
+        def to_proc
+          return @a_proc
+        end
+      end
+    end
+  end
+end
diff --git a/test/unit/comment_filter.rb b/test/unit/comment_filter.rb
new file mode 100644
index 0000000..e255d07
--- /dev/null
+++ b/test/unit/comment_filter.rb
@@ -0,0 +1,53 @@
+require 'test/unit'
+require 'coderay'
+
+class CommentFilterTest < Test::Unit::TestCase
+  
+  def test_filtering_comments
+    tokens = CodeRay.scan <<-RUBY, :ruby
+#!/usr/bin/env ruby
+# a minimal Ruby program
+puts "Hello world!"
+    RUBY
+    assert_equal <<-RUBY_FILTERED, tokens.comment_filter.text
+#!/usr/bin/env ruby
+
+puts "Hello world!"
+    RUBY_FILTERED
+  end
+  
+  def test_filtering_docstrings
+    tokens = CodeRay.scan <<-PYTHON, :python
+'''
+Assuming this is file mymodule.py then this string, being the
+first statement in the file will become the mymodule modules
+docstring when the file is imported
+'''
+
+class Myclass():
+    """
+    The class's docstring
+    """
+
+    def mymethod(self):
+        '''The method's docstring'''
+
+def myfunction():
+    """The function's docstring"""
+    PYTHON
+    assert_equal <<-PYTHON_FILTERED.chomp, tokens.comment_filter.text
+
+
+class Myclass():
+    
+
+    def mymethod(self):
+        
+
+def myfunction():
+    
+
+PYTHON_FILTERED
+  end
+  
+end
\ No newline at end of file
diff --git a/test/unit/count.rb b/test/unit/count.rb
new file mode 100644
index 0000000..448e8f1
--- /dev/null
+++ b/test/unit/count.rb
@@ -0,0 +1,15 @@
+require 'test/unit'
+require 'coderay'
+
+class CountTest < Test::Unit::TestCase
+  
+  def test_count
+    tokens = CodeRay.scan <<-RUBY.strip, :ruby
+#!/usr/bin/env ruby
+# a minimal Ruby program
+puts "Hello world!"
+    RUBY
+    assert_equal 11, tokens.encode(:count)
+  end
+  
+end
\ No newline at end of file
diff --git a/test/unit/debug.rb b/test/unit/debug.rb
new file mode 100644
index 0000000..88baf56
--- /dev/null
+++ b/test/unit/debug.rb
@@ -0,0 +1,77 @@
+require 'test/unit'
+require 'coderay'
+
+class DebugEncoderTest < Test::Unit::TestCase
+  
+  def test_creation
+    debug = nil
+    assert_nothing_raised do
+      debug = CodeRay.encoder :debug
+    end
+    assert CodeRay::Encoders::Debug < CodeRay::Encoders::Encoder
+    assert_kind_of CodeRay::Encoders::Encoder, debug
+  end
+  
+  TEST_INPUT = CodeRay::Tokens[
+    ['10', :integer],
+    ['(\\)', :operator],
+    [:begin_group, :string],
+    ['test', :content],
+    [:end_group, :string],
+    [:begin_line, :head],
+    ["\n", :space],
+    ["\n  \t", :space],
+    ["   \n", :space],
+    ["[]", :method],
+    [:end_line, :head],
+  ].flatten
+  TEST_OUTPUT = <<-'DEBUG'.chomp
+integer(10)operator((\\\))string<content(test)>head[
+
+  	   
+method([])]
+  DEBUG
+  
+  def test_filtering_text_tokens
+    assert_equal TEST_OUTPUT, CodeRay::Encoders::Debug.new.encode_tokens(TEST_INPUT)
+    assert_equal TEST_OUTPUT, TEST_INPUT.debug
+  end
+  
+end
+
+class DebugScannerTest < Test::Unit::TestCase
+  
+  def test_creation
+    assert CodeRay::Scanners::Debug < CodeRay::Scanners::Scanner
+    debug = nil
+    assert_nothing_raised do
+      debug = CodeRay.scanner :debug
+    end
+    assert_kind_of CodeRay::Scanners::Scanner, debug
+  end
+  
+  TEST_INPUT = <<-'DEBUG'.chomp
+integer(10)operator((\\\))string<content(test)>test[
+
+  	   
+method([])]
+  DEBUG
+  TEST_OUTPUT = CodeRay::Tokens[
+    ['10', :integer],
+    ['(\\)', :operator],
+    [:begin_group, :string],
+    ['test', :content],
+    [:end_group, :string],
+    [:begin_line, :unknown],
+    ["\n\n  \t   \n", :space],
+    ["[]", :method],
+    [:end_line, :unknown],
+  ].flatten
+  
+  def test_filtering_text_tokens
+    assert_equal TEST_OUTPUT, CodeRay::Scanners::Debug.new.tokenize(TEST_INPUT)
+    assert_kind_of CodeRay::TokensProxy, CodeRay.scan(TEST_INPUT, :debug)
+    assert_equal TEST_OUTPUT, CodeRay.scan(TEST_INPUT, :debug).tokens
+  end
+  
+end
diff --git a/test/unit/duo.rb b/test/unit/duo.rb
new file mode 100644
index 0000000..05c26a5
--- /dev/null
+++ b/test/unit/duo.rb
@@ -0,0 +1,35 @@
+require 'test/unit'
+require 'yaml'
+require 'coderay'
+
+class DuoTest < Test::Unit::TestCase
+  
+  def test_two_arguments
+    duo = CodeRay::Duo[:ruby, :html]
+    assert_kind_of CodeRay::Scanners[:ruby], duo.scanner
+    assert_kind_of CodeRay::Encoders[:html], duo.encoder
+  end
+  
+  def test_two_hash
+    duo = CodeRay::Duo[:ruby => :html]
+    assert_kind_of CodeRay::Scanners[:ruby], duo.scanner
+    assert_kind_of CodeRay::Encoders[:html], duo.encoder
+  end
+  
+  def test_call
+    duo = CodeRay::Duo[:python => :yml]
+    yaml = [["def", :keyword],
+            [" ", :space],
+            ["test", :method],
+            [":", :operator],
+            [" ", :space],
+            [:begin_group, :string],
+            ["\"", :delimiter],
+            ["pass", :content],
+            ["\"", :delimiter],
+            [:end_group, :string]]
+    
+    assert_equal yaml, YAML.load(duo.call('def test: "pass"'))
+  end
+  
+end
diff --git a/test/unit/file_type.rb b/test/unit/file_type.rb
new file mode 100644
index 0000000..263517b
--- /dev/null
+++ b/test/unit/file_type.rb
@@ -0,0 +1,116 @@
+require 'test/unit'
+require File.expand_path('../../lib/assert_warning', __FILE__)
+
+require 'coderay/helpers/file_type'
+
+class FileTypeTests < Test::Unit::TestCase
+  
+  include CodeRay
+  
+  def test_fetch
+    assert_raise FileType::UnknownFileType do
+      FileType.fetch ''
+    end
+    
+    assert_throws :not_found do
+      FileType.fetch '.' do
+        throw :not_found
+      end
+    end
+    
+    assert_equal :default, FileType.fetch('c', :default)
+  end
+  
+  def test_block_supersedes_default_warning
+    assert_warning 'Block supersedes default value argument; use either.' do
+      FileType.fetch('c', :default) { }
+    end
+  end
+  
+  def test_ruby
+    assert_equal :ruby, FileType[__FILE__]
+    assert_equal :ruby, FileType['test.rb']
+    assert_equal :ruby, FileType['test.java.rb']
+    assert_equal :java, FileType['test.rb.java']
+    assert_equal :ruby, FileType['C:\\Program Files\\x\\y\\c\\test.rbw']
+    assert_equal :ruby, FileType['/usr/bin/something/Rakefile']
+    assert_equal :ruby, FileType['~/myapp/gem/Rantfile']
+    assert_equal :ruby, FileType['./lib/tasks\repository.rake']
+    assert_not_equal :ruby, FileType['test_rb']
+    assert_not_equal :ruby, FileType['Makefile']
+    assert_not_equal :ruby, FileType['set.rb/set']
+    assert_not_equal :ruby, FileType['~/projects/blabla/rb']
+  end
+  
+  def test_c
+    assert_equal :c, FileType['test.c']
+    assert_equal :c, FileType['C:\\Program Files\\x\\y\\c\\test.h']
+    assert_not_equal :c, FileType['test_c']
+    assert_not_equal :c, FileType['Makefile']
+    assert_not_equal :c, FileType['set.h/set']
+    assert_not_equal :c, FileType['~/projects/blabla/c']
+  end
+  
+  def test_cpp
+    assert_equal :cpp, FileType['test.c++']
+    assert_equal :cpp, FileType['test.cxx']
+    assert_equal :cpp, FileType['test.hh']
+    assert_equal :cpp, FileType['test.hpp']
+    assert_equal :cpp, FileType['test.cu']
+    assert_equal :cpp, FileType['test.C']
+    assert_not_equal :cpp, FileType['test.c']
+    assert_not_equal :cpp, FileType['test.h']
+  end
+  
+  def test_html
+    assert_equal :html, FileType['test.htm']
+    assert_equal :html, FileType['test.xhtml']
+    assert_equal :html, FileType['test.html.xhtml']
+    assert_equal :erb, FileType['_form.rhtml']
+    assert_equal :erb, FileType['_form.html.erb']
+  end
+  
+  def test_yaml
+    assert_equal :yaml, FileType['test.yml']
+    assert_equal :yaml, FileType['test.yaml']
+    assert_equal :yaml, FileType['my.html.yaml']
+    assert_not_equal :yaml, FileType['YAML']
+  end
+  
+  def test_pathname
+    require 'pathname'
+    pn = Pathname.new 'test.rb'
+    assert_equal :ruby, FileType[pn]
+    dir = Pathname.new '/etc/var/blubb'
+    assert_equal :ruby, FileType[dir + pn]
+    assert_equal :cpp, FileType[dir + 'test.cpp']
+  end
+  
+  def test_no_shebang
+    dir = './test'
+    if File.directory? dir
+      Dir.chdir dir do
+        assert_equal :c, FileType['test.c']
+      end
+    end
+  end
+  
+  def test_shebang_empty_file
+    require 'tmpdir'
+    tmpfile = File.join(Dir.tmpdir, 'bla')
+    File.open(tmpfile, 'w') { }  # touch
+    assert_equal nil, FileType[tmpfile, true]
+  end
+  
+  def test_shebang_no_file
+    assert_equal nil, FileType['i do not exist', true]
+  end
+  
+  def test_shebang
+    require 'tmpdir'
+    tmpfile = File.join(Dir.tmpdir, 'bla')
+    File.open(tmpfile, 'w') { |f| f.puts '#!/usr/bin/env ruby' }
+    assert_equal :ruby, FileType[tmpfile, true]
+  end
+  
+end
diff --git a/test/unit/filter.rb b/test/unit/filter.rb
new file mode 100644
index 0000000..25dff77
--- /dev/null
+++ b/test/unit/filter.rb
@@ -0,0 +1,38 @@
+require 'test/unit'
+require 'coderay'
+
+class FilterTest < Test::Unit::TestCase
+  
+  def test_creation
+    filter = nil
+    assert_nothing_raised do
+      filter = CodeRay.encoder :filter
+    end
+    assert CodeRay::Encoders::Filter < CodeRay::Encoders::Encoder
+    assert_kind_of CodeRay::Encoders::Encoder, filter
+  end
+  
+  def test_filtering_text_tokens
+    tokens = CodeRay::Tokens.new
+    10.times do |i|
+      tokens.text_token i.to_s, :index
+    end
+    assert_equal tokens, CodeRay::Encoders::Filter.new.encode_tokens(tokens)
+    assert_equal tokens, tokens.filter
+  end
+  
+  def test_filtering_block_tokens
+    tokens = CodeRay::Tokens.new
+    10.times do |i|
+      tokens.begin_group :index
+      tokens.text_token i.to_s, :content
+      tokens.end_group :index
+      tokens.begin_line :index
+      tokens.text_token i.to_s, :content
+      tokens.end_line :index
+    end
+    assert_equal tokens, CodeRay::Encoders::Filter.new.encode_tokens(tokens)
+    assert_equal tokens, tokens.filter
+  end
+  
+end
diff --git a/test/unit/html.rb b/test/unit/html.rb
new file mode 100644
index 0000000..0072635
--- /dev/null
+++ b/test/unit/html.rb
@@ -0,0 +1,103 @@
+require 'test/unit'
+require 'coderay'
+
+class HtmlTest < Test::Unit::TestCase
+  
+  def test_break_lines_option
+    snippets = {}
+    
+    snippets[:ruby] = {}
+    
+    snippets[:ruby][:in] = <<-RUBY
+ruby_inside = <<-RUBY_INSIDE
+This is tricky,
+isn't it?
+RUBY_INSIDE
+    RUBY
+    
+    snippets[:ruby][:expected_with_option_off] = <<-HTML_OPT_INDEPENDENT_LINES_OFF
+ruby_inside = <span class=\"string\"><span class=\"delimiter\">&lt;&lt;-RUBY_INSIDE</span></span><span class=\"string\"><span class=\"content\">
+This is tricky,
+isn't it?</span><span class=\"delimiter\">
+RUBY_INSIDE</span></span>
+    HTML_OPT_INDEPENDENT_LINES_OFF
+    
+    snippets[:ruby][:expected_with_option_on] = <<-HTML_OPT_INDEPENDENT_LINES_ON
+ruby_inside = <span class=\"string\"><span class=\"delimiter\">&lt;&lt;-RUBY_INSIDE</span></span><span class=\"string\"><span class=\"content\"></span></span>
+<span class=\"string\"><span class=\"content\">This is tricky,</span></span>
+<span class=\"string\"><span class=\"content\">isn't it?</span><span class=\"delimiter\"></span></span>
+<span class=\"string\"><span class=\"delimiter\">RUBY_INSIDE</span></span>
+    HTML_OPT_INDEPENDENT_LINES_ON
+    
+    snippets[:java] = {}
+    
+    snippets[:java][:in] = <<-JAVA
+import java.lang.*;
+
+/**
+ * This is some multiline javadoc
+ * used to test the
+ */
+public class Test {
+  public static final String MESSAGE = "My message\
+    To the world";
+
+  static void main() {
+    /*
+     * Another multiline
+     * comment
+     */
+    System.out.println(MESSAGE);
+  }
+}
+    JAVA
+    
+    snippets[:java][:expected_with_option_off] = <<-HTML_OPT_INDEPENDENT_LINES_OFF
+<span class=\"keyword\">import</span> <span class=\"include\">java.lang</span>.*;
+
+<span class=\"comment\">/**
+ * This is some multiline javadoc
+ * used to test the
+ */</span>
+<span class=\"directive\">public</span> <span class=\"type\">class</span> <span class=\"class\">Test</span> {
+  <span class=\"directive\">public</span> <span class=\"directive\">static</span> <span class=\"directive\">final</span> <span class=\"predefined-type\">String</span> MESSAGE = <span class=\"string\"><span class=\"delimiter\">&quot;</span><span class=\"content\">My message    To the world</span><span class=\"delimiter\">&quot;</span></span>;
+
+  <span class=\"directive\">static</span> <span class=\"type\">void</span> main() {
+    <span class=\"comment\">/*
+     * Another multiline
+     * comment
+     */</span>
+    <span class=\"predefined-type\">System</span>.out.println(MESSAGE);
+  }
+}
+    HTML_OPT_INDEPENDENT_LINES_OFF
+    
+    snippets[:java][:expected_with_option_on] = <<-HTML_OPT_INDEPENDENT_LINES_ON
+<span class=\"keyword\">import</span> <span class=\"include\">java.lang</span>.*;
+
+<span class=\"comment\">/**</span>
+<span class=\"comment\"> * This is some multiline javadoc</span>
+<span class=\"comment\"> * used to test the</span>
+<span class=\"comment\"> */</span>
+<span class=\"directive\">public</span> <span class=\"type\">class</span> <span class=\"class\">Test</span> {
+  <span class=\"directive\">public</span> <span class=\"directive\">static</span> <span class=\"directive\">final</span> <span class=\"predefined-type\">String</span> MESSAGE = <span class=\"string\"><span class=\"delimiter\">&quot;</span><span class=\"content\">My message    To the world</span><span class=\"delimiter\">&quot;</span></span>;
+
+  <span class=\"directive\">static</span> <span class=\"type\">void</span> main() {
+    <span class=\"comment\">/*</span>
+<span class=\"comment\">     * Another multiline</span>
+<span class=\"comment\">     * comment</span>
+<span class=\"comment\">     */</span>
+    <span class=\"predefined-type\">System</span>.out.println(MESSAGE);
+  }
+}
+    HTML_OPT_INDEPENDENT_LINES_ON
+    
+    for lang, code in snippets
+      tokens = CodeRay.scan code[:in], lang
+      
+      assert_equal code[:expected_with_option_off], tokens.html
+      assert_equal code[:expected_with_option_off], tokens.html(:break_lines => false)
+      assert_equal code[:expected_with_option_on],  tokens.html(:break_lines => true)
+    end
+  end
+end
diff --git a/test/unit/json_encoder.rb b/test/unit/json_encoder.rb
new file mode 100644
index 0000000..4e44a64
--- /dev/null
+++ b/test/unit/json_encoder.rb
@@ -0,0 +1,28 @@
+require 'test/unit'
+require 'coderay'
+
+class JSONEncoderTest < Test::Unit::TestCase
+  
+  def test_json_output
+    old_load_paths = $:.dup
+    begin
+      $:.delete '.'
+      $:.delete File.dirname(__FILE__)
+      json = CodeRay.scan('puts "Hello world!"', :ruby).json
+      assert_equal [
+        {"type"=>"text", "text"=>"puts", "kind"=>"ident"},
+        {"type"=>"text", "text"=>" ", "kind"=>"space"},
+        {"type"=>"block", "action"=>"open", "kind"=>"string"},
+        {"type"=>"text", "text"=>"\"", "kind"=>"delimiter"},
+        {"type"=>"text", "text"=>"Hello world!", "kind"=>"content"},
+        {"type"=>"text", "text"=>"\"", "kind"=>"delimiter"},
+        {"type"=>"block", "action"=>"close", "kind"=>"string"},
+      ], JSON.load(json)
+    ensure
+      for path in old_load_paths - $:
+        $: << path
+      end
+    end
+  end
+  
+end
\ No newline at end of file
diff --git a/test/unit/lines_of_code.rb b/test/unit/lines_of_code.rb
new file mode 100644
index 0000000..e2c0caf
--- /dev/null
+++ b/test/unit/lines_of_code.rb
@@ -0,0 +1,52 @@
+require 'test/unit'
+require 'coderay'
+$VERBOSE = true
+
+require File.expand_path('../../lib/assert_warning', __FILE__)
+
+class LinesOfCodeTest < Test::Unit::TestCase
+  
+  def test_creation
+    assert CodeRay::Encoders::LinesOfCode < CodeRay::Encoders::Encoder
+    filter = nil
+    assert_nothing_raised do
+      filter = CodeRay.encoder :loc
+    end
+    assert_kind_of CodeRay::Encoders::LinesOfCode, filter
+    assert_nothing_raised do
+      filter = CodeRay.encoder :lines_of_code
+    end
+    assert_kind_of CodeRay::Encoders::LinesOfCode, filter
+  end
+  
+  def test_lines_of_code
+    tokens = CodeRay.scan <<-RUBY, :ruby
+#!/usr/bin/env ruby
+
+# a minimal Ruby program
+puts "Hello world!"
+    RUBY
+    assert_equal 1, CodeRay::Encoders::LinesOfCode.new.encode_tokens(tokens)
+    assert_equal 1, tokens.lines_of_code
+    assert_equal 1, tokens.loc
+  end
+  
+  class ScannerMockup
+    KINDS_NOT_LOC = [:space]
+  end
+  
+  def test_filtering_block_tokens
+    tokens = CodeRay::Tokens.new
+    tokens.concat ["Hello\n", :world]
+    tokens.concat ["\n", :space]
+    tokens.concat ["Hello\n", :comment]
+    
+    assert_warning 'Tokens have no associated scanner, counting all nonempty lines.' do
+      assert_equal 1, tokens.lines_of_code
+    end
+    
+    tokens.scanner = ScannerMockup.new
+    assert_equal 2, tokens.lines_of_code
+  end
+  
+end
\ No newline at end of file
diff --git a/test/unit/null.rb b/test/unit/null.rb
new file mode 100644
index 0000000..d3a9b0d
--- /dev/null
+++ b/test/unit/null.rb
@@ -0,0 +1,14 @@
+require 'test/unit'
+require 'coderay'
+
+class NullTest < Test::Unit::TestCase
+  
+  def test_null
+    ruby = <<-RUBY
+puts "Hello world!"
+    RUBY
+    tokens = CodeRay.scan ruby, :ruby
+    assert_equal '', tokens.encode(:null)
+  end
+  
+end
\ No newline at end of file
diff --git a/test/unit/plugin.rb b/test/unit/plugin.rb
new file mode 100755
index 0000000..41eec51
--- /dev/null
+++ b/test/unit/plugin.rb
@@ -0,0 +1,69 @@
+require 'test/unit'
+require 'pathname'
+
+$:.unshift File.expand_path('../../../lib', __FILE__)
+require 'coderay'
+
+class PluginScannerTest < Test::Unit::TestCase
+  
+  module Plugins
+    extend CodeRay::PluginHost
+    plugin_path File.dirname(__FILE__), 'plugins'
+    class Plugin
+      extend CodeRay::Plugin
+      plugin_host Plugins
+    end
+  end
+  
+  module PluginsWithDefault
+    extend CodeRay::PluginHost
+    plugin_path File.dirname(__FILE__), 'plugins_with_default'
+    class Plugin
+      extend CodeRay::Plugin
+      plugin_host PluginsWithDefault
+    end
+    default :default_plugin
+  end
+  
+  def test_load
+    require Pathname.new(__FILE__).realpath.dirname + 'plugins' + 'user_defined' + 'user_plugin'
+    assert_equal 'UserPlugin', Plugins.load(:user_plugin).name
+  end
+  
+  def test_load_all
+    assert_instance_of Symbol, Plugins.load_all.first
+    assert_operator Plugins.all_plugins.first, :<, Plugins::Plugin
+    assert_equal 'The Example', Plugins.all_plugins.map { |plugin| plugin.title }.sort.first
+  end
+  
+  def test_default
+    assert_nothing_raised do
+      assert_operator PluginsWithDefault[:gargamel], :<, PluginsWithDefault::Plugin
+    end
+    assert_equal PluginsWithDefault::Default, PluginsWithDefault.default
+  end
+  
+  def test_plugin_not_found
+    assert_raise CodeRay::PluginHost::PluginNotFound do
+      Plugins[:thestral]
+    end
+    assert_raise ArgumentError do
+      Plugins[14]
+    end
+    assert_raise ArgumentError do
+      Plugins['test/test']
+    end
+    assert_raise CodeRay::PluginHost::PluginNotFound do
+      PluginsWithDefault[:example_without_register_for]
+    end
+  end
+  
+  def test_autoload_constants
+    assert_operator Plugins::Example, :<, Plugins::Plugin
+  end
+  
+  def test_title
+    assert_equal 'The Example', Plugins::Example.title
+  end
+  
+end
diff --git a/test/unit/plugins/example.rb b/test/unit/plugins/example.rb
new file mode 100644
index 0000000..af1aeba
--- /dev/null
+++ b/test/unit/plugins/example.rb
@@ -0,0 +1,6 @@
+class Example < PluginScannerTest::Plugins::Plugin
+  
+  register_for :example
+  title 'The Example'
+  
+end
diff --git a/test/unit/plugins/user_defined/user_plugin.rb b/test/unit/plugins/user_defined/user_plugin.rb
new file mode 100644
index 0000000..f47c934
--- /dev/null
+++ b/test/unit/plugins/user_defined/user_plugin.rb
@@ -0,0 +1,5 @@
+class UserPlugin < PluginScannerTest::Plugins::Plugin
+  
+  register_for :user_plugin
+  
+end
diff --git a/test/unit/plugins_with_default/default_plugin.rb b/test/unit/plugins_with_default/default_plugin.rb
new file mode 100644
index 0000000..ae9e4c5
--- /dev/null
+++ b/test/unit/plugins_with_default/default_plugin.rb
@@ -0,0 +1,5 @@
+class DefaultPlugin < PluginScannerTest::PluginsWithDefault::Plugin
+  
+  register_for :default_plugin
+  
+end
diff --git a/test/unit/plugins_with_default/example_without_register_for.rb b/test/unit/plugins_with_default/example_without_register_for.rb
new file mode 100644
index 0000000..083baf6
--- /dev/null
+++ b/test/unit/plugins_with_default/example_without_register_for.rb
@@ -0,0 +1,5 @@
+class ExampleWithoutRegisterFor < PluginScannerTest::PluginsWithDefault::Plugin
+  
+  register_for :wrong_id
+  
+end
diff --git a/test/unit/statistic.rb b/test/unit/statistic.rb
new file mode 100644
index 0000000..1326dca
--- /dev/null
+++ b/test/unit/statistic.rb
@@ -0,0 +1,59 @@
+require 'test/unit'
+require 'coderay'
+
+class StatisticEncoderTest < Test::Unit::TestCase
+  
+  def test_creation
+    assert CodeRay::Encoders::Statistic < CodeRay::Encoders::Encoder
+    stats = nil
+    assert_nothing_raised do
+      stats = CodeRay.encoder :statistic
+    end
+    assert_kind_of CodeRay::Encoders::Encoder, stats
+  end
+  
+  TEST_INPUT = CodeRay::Tokens[
+    ['10', :integer],
+    ['(\\)', :operator],
+    [:begin_group, :string],
+    ['test', :content],
+    [:end_group, :string],
+    [:begin_line, :test],
+    ["\n", :space],
+    ["\n  \t", :space],
+    ["   \n", :space],
+    ["[]", :method],
+    [:end_line, :test],
+  ].flatten
+  TEST_OUTPUT = <<-'DEBUG'
+
+Code Statistics
+
+Tokens                  11
+  Non-Whitespace         4
+Bytes Total             20
+
+Token Types (7):
+  type                     count     ratio    size (average)
+-------------------------------------------------------------
+  TOTAL                       11  100.00 %     1.8
+  space                        3   27.27 %     3.0
+  string                       2   18.18 %     0.0
+  test                         2   18.18 %     0.0
+  :begin_group                 1    9.09 %     0.0
+  :begin_line                  1    9.09 %     0.0
+  :end_group                   1    9.09 %     0.0
+  :end_line                    1    9.09 %     0.0
+  content                      1    9.09 %     4.0
+  integer                      1    9.09 %     2.0
+  method                       1    9.09 %     2.0
+  operator                     1    9.09 %     3.0
+
+  DEBUG
+  
+  def test_filtering_text_tokens
+    assert_equal TEST_OUTPUT, CodeRay::Encoders::Statistic.new.encode_tokens(TEST_INPUT)
+    assert_equal TEST_OUTPUT, TEST_INPUT.statistic
+  end
+  
+end
\ No newline at end of file
diff --git a/test/unit/suite.rb b/test/unit/suite.rb
new file mode 100755
index 0000000..417dfed
--- /dev/null
+++ b/test/unit/suite.rb
@@ -0,0 +1,16 @@
+require 'test/unit'
+require 'rubygems'
+
+$VERBOSE = $CODERAY_DEBUG = true
+$:.unshift 'lib'
+
+mydir = File.dirname(__FILE__)
+suite = Dir[File.join(mydir, '*.rb')].
+  map { |tc| File.basename(tc).sub(/\.rb$/, '') } - %w'suite vhdl'
+
+puts "Running CodeRay unit tests: #{suite.join(', ')}"
+
+helpers = %w(file_type word_list tokens)
+for test_case in helpers + (suite - helpers)
+  load File.join(mydir, test_case + '.rb')
+end
diff --git a/test/unit/text.rb b/test/unit/text.rb
new file mode 100644
index 0000000..db086f5
--- /dev/null
+++ b/test/unit/text.rb
@@ -0,0 +1,14 @@
+require 'test/unit'
+require 'coderay'
+
+class TextTest < Test::Unit::TestCase
+  
+  def test_count
+    ruby = <<-RUBY
+puts "Hello world!"
+    RUBY
+    tokens = CodeRay.scan ruby, :ruby
+    assert_equal ruby, tokens.encode(:text)
+  end
+  
+end
\ No newline at end of file
diff --git a/test/unit/token_kind_filter.rb b/test/unit/token_kind_filter.rb
new file mode 100644
index 0000000..13bae52
--- /dev/null
+++ b/test/unit/token_kind_filter.rb
@@ -0,0 +1,50 @@
+require 'test/unit'
+require 'coderay'
+
+class TokenKindFilterTest < Test::Unit::TestCase
+  
+  def test_creation
+    assert CodeRay::Encoders::TokenKindFilter < CodeRay::Encoders::Encoder
+    assert CodeRay::Encoders::TokenKindFilter < CodeRay::Encoders::Filter
+    filter = nil
+    assert_nothing_raised do
+      filter = CodeRay.encoder :token_kind_filter
+    end
+    assert_instance_of CodeRay::Encoders::TokenKindFilter, filter
+  end
+  
+  def test_filtering_text_tokens
+    tokens = CodeRay::Tokens.new
+    for i in 1..10
+      tokens.text_token i.to_s, :index
+      tokens.text_token ' ', :space if i < 10
+    end
+    assert_equal 10, CodeRay::Encoders::TokenKindFilter.new.encode_tokens(tokens, :exclude => :space).count
+    assert_equal 10, tokens.token_kind_filter(:exclude => :space).count
+    assert_equal 9, CodeRay::Encoders::TokenKindFilter.new.encode_tokens(tokens, :include => :space).count
+    assert_equal 9, tokens.token_kind_filter(:include => :space).count
+    assert_equal 0, CodeRay::Encoders::TokenKindFilter.new.encode_tokens(tokens, :exclude => :all).count
+    assert_equal 0, tokens.token_kind_filter(:exclude => :all).count
+  end
+  
+  def test_filtering_block_tokens
+    tokens = CodeRay::Tokens.new
+    10.times do |i|
+      tokens.begin_group :index
+      tokens.text_token i.to_s, :content
+      tokens.end_group :index
+      tokens.begin_group :naught if i == 5
+      tokens.end_group :naught if i == 7
+      tokens.begin_line :blubb
+      tokens.text_token i.to_s, :content
+      tokens.end_line :blubb
+    end
+    assert_equal 16, CodeRay::Encoders::TokenKindFilter.new.encode_tokens(tokens, :include => :blubb).count
+    assert_equal 16, tokens.token_kind_filter(:include => :blubb).count
+    assert_equal 24, CodeRay::Encoders::TokenKindFilter.new.encode_tokens(tokens, :include => [:blubb, :content]).count
+    assert_equal 24, tokens.token_kind_filter(:include => [:blubb, :content]).count
+    assert_equal 32, CodeRay::Encoders::TokenKindFilter.new.encode_tokens(tokens, :exclude => :index).count
+    assert_equal 32, tokens.token_kind_filter(:exclude => :index).count
+  end
+  
+end
diff --git a/test/unit/tokens.rb b/test/unit/tokens.rb
new file mode 100644
index 0000000..73b0fd5
--- /dev/null
+++ b/test/unit/tokens.rb
@@ -0,0 +1,77 @@
+require 'test/unit'
+require 'coderay'
+
+class TokensTest < Test::Unit::TestCase
+  
+  def test_creation
+    assert CodeRay::Tokens < Array
+    tokens = nil
+    assert_nothing_raised do
+      tokens = CodeRay::Tokens.new
+    end
+    assert_kind_of Array, tokens
+  end
+  
+  def test_adding_tokens
+    tokens = make_tokens
+    assert_equal tokens.size, 8
+    assert_equal tokens.count, 4
+  end
+  
+  def test_to_s
+    assert_equal 'string()', make_tokens.to_s
+  end
+  
+  def test_encode_with_nonsense
+    assert_raise NoMethodError do
+      make_tokens.nonsense
+    end
+  end
+  
+  def test_split_into_parts
+    parts_4_3 = [
+      ["stri", :type],
+      ["ng", :type, :begin_group, :operator, "(", :content, :end_group, :operator],
+      [:begin_group, :operator, ")", :content, :end_group, :operator]
+    ]
+    assert_equal parts_4_3, make_tokens.split_into_parts(4, 3)
+    assert_equal [make_tokens.to_a], make_tokens.split_into_parts
+    
+    parts_7_0_1 = [
+      ["string", :type, :begin_group, :operator, "(", :content, :end_group, :operator],
+      [],
+      [:begin_group, :operator, ")", :content, :end_group, :operator]
+    ]
+    assert_equal parts_7_0_1, make_tokens.split_into_parts(7, 0, 1)
+    
+    line = CodeRay::Tokens[:begin_line, :head, '...', :plain]
+    line_parts = [
+      [:begin_line, :head, ".", :plain, :end_line, :head],
+      [:begin_line, :head, "..", :plain]
+    ]
+    assert_equal line_parts, line.split_into_parts(1)
+    
+    assert_raise ArgumentError do
+      CodeRay::Tokens[:bullshit, :input].split_into_parts
+    end
+    assert_raise ArgumentError do
+      CodeRay::Tokens[42, 43].split_into_parts
+    end
+  end
+  
+  def test_encode
+    assert_match(/\A\[\{(?:"type":"text"|"text":"string"|"kind":"type"|,){5}\},/, make_tokens.encode(:json))
+  end
+  
+  def make_tokens
+    tokens = CodeRay::Tokens.new
+    assert_nothing_raised do
+      tokens.text_token 'string', :type
+      tokens.begin_group :operator
+      tokens.text_token '()', :content
+      tokens.end_group :operator
+    end
+    tokens
+  end
+  
+end
\ No newline at end of file
diff --git a/test/unit/word_list.rb b/test/unit/word_list.rb
new file mode 100644
index 0000000..40d5a26
--- /dev/null
+++ b/test/unit/word_list.rb
@@ -0,0 +1,64 @@
+require 'test/unit'
+require 'coderay/helpers/word_list'
+
+class WordListTest < Test::Unit::TestCase
+  
+  include CodeRay
+  
+  # define word arrays
+  RESERVED_WORDS = %w[
+    asm break case continue default do else
+    ...
+  ]
+  
+  PREDEFINED_TYPES = %w[
+    int long short char void
+    ...
+  ]
+  
+  PREDEFINED_CONSTANTS = %w[
+    EOF NULL ...
+  ]
+  
+  # make a WordList
+  IDENT_KIND = WordList.new(:ident).
+    add(RESERVED_WORDS, :reserved).
+    add(PREDEFINED_TYPES, :predefined_type).
+    add(PREDEFINED_CONSTANTS, :predefined_constant)
+  
+  def test_word_list_example
+    assert_equal :predefined_type, IDENT_KIND['void']
+    # assert_equal :predefined_constant, IDENT_KIND['...']  # not specified
+  end
+  
+  def test_word_list
+    list = WordList.new(:ident).add(['foobar'], :reserved)
+    assert_equal :reserved, list['foobar']
+    assert_equal :ident, list['FooBar']
+    assert_equal 1, list.size
+  end
+  
+  def test_case_ignoring_word_list
+    list = WordList::CaseIgnoring.new(:ident).add(['foobar'], :reserved)
+    assert_equal :ident, list['foo']
+    assert_equal :reserved, list['foobar']
+    assert_equal :reserved, list['FooBar']
+    assert_equal 1, list.size
+    
+    list = WordList::CaseIgnoring.new(:ident).add(['FooBar'], :reserved)
+    assert_equal :ident, list['foo']
+    assert_equal :reserved, list['foobar']
+    assert_equal :reserved, list['FooBar']
+    assert_equal 1, list.size
+  end
+  
+  def test_dup
+    list = WordList.new(:ident).add(['foobar'], :reserved)
+    assert_equal :reserved, list['foobar']
+    list2 = list.dup
+    list2.add(%w[foobar], :keyword)
+    assert_equal :keyword, list2['foobar']
+    assert_equal :reserved, list['foobar']
+  end
+  
+end
