#!/usr/bin/env ruby
#--
#   Copyright (C) 2001, 2002, 2003 Matt Armstrong.  All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice,
#    this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
# 3. The name of the author may not be used to endorse or promote products
#    derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
# NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
# TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#

# Base for all the test cases, providing a default setup and teardown

require 'rubyunit'
require 'rbconfig.rb'
require 'tempfile'
require 'find'
require 'ftools'

begin
  require 'pp'
rescue LoadError
end

class TestBase < RUNIT::TestCase
  include Config

  attr_reader :scratch_dir

  # NoMethodError was introduced in ruby 1.7
  NO_METHOD_ERROR = if RUBY_VERSION >= "1.7"
		      NoMethodError
		    else
		      NameError
		    end

  # Print a string to a temporary file and return the file opened.
  # This lets you have some test data in a string, but access it with
  # a file.
  def string_as_file(string, strip_whitespace = true)
    if strip_whitespace
      temp = ""
      string.each_line { |line|
	temp += line.sub(/^[ \t]+/, '')
      }
      string = temp
    end
    file = Tempfile.new("ruby.string_as_file.")
    begin
      file.print(string)
      file.close()
      file.open()
      yield file
    ensure
      file.close(true)
    end
  end

  # Return true if the given file contains a line matching regexp
  def file_contains(filename, regexp)
    unless regexp.kind_of?(Regexp)
      regexp = Regexp.new(regexp)
    end
    detected = nil
    File.open(filename) { |f|
      detected = f.detect { |line|
	line =~ regexp
      }
    }
    ! detected.nil?
  end

  # Deletes everything in directory +dir+, including any
  # subdirectories
  def cleandir(dir)
    if FileTest.directory?(dir)
      files = []
      Find.find(dir) { |f|
	files.push(f)
      }
      files.shift		# get rid of 'dir'
      files.reverse_each { |f|
	if FileTest.directory?(f)
	  Dir.delete(f)
	else
	  File.delete(f)
	end
      }
    end
  end

  def setup
    @scratch_dir = File.join(Dir.getwd, "_scratch_" + name)
    @data_dir = File.join(Dir.getwd, "tests", "data")
    @scratch_hash = {}

    cleandir(@scratch_dir)
    Dir.rmdir(@scratch_dir) if FileTest.directory?(@scratch_dir)
    Dir.mkdir(@scratch_dir) unless FileTest.directory?(@scratch_dir)
  end

  def ruby_program
    File.join(CONFIG['bindir'], CONFIG['ruby_install_name'])
  end

  def data_filename(name)
    File.join(@data_dir, name)
  end

  def data_as_file(name)
    unless name =~ %r{^/}
      name = data_filename(name)
    end
    File.open(name) { |f|
      yield f
    }
  rescue Errno::ENOENT
    assert_fail("data file #{name.inspect} does not exist")
  end

  def data_as_string(name)
    data_as_file(name) { |f|
      f.read
    }
  end

  def scratch_filename(name)
    if @scratch_hash.key?(name)
      temp = @scratch_hash[name]
      temp = temp.succ
      @scratch_hash[name] = name = temp
    else
      temp = name.dup
      temp << '.0' unless temp =~ /\.\d+$/
      @scratch_hash[name] = temp
    end
    File.join(@scratch_dir, name)
  end

  def scratch_file_write(name)
    name = scratch_filename(name)
    File.open(name, 'w') { |f|
      yield f
    }
  end

  def teardown
    unless $! || ((defined? passed?) && !passed?)
      cleandir(@scratch_dir)
      Dir.rmdir(@scratch_dir) if FileTest.directory?(@scratch_dir)
    end
  end

  def call_fails(arg, &block)
    begin
      yield arg
    rescue Exception
      return true
    end
    return false
  end

  # if a random string failes, run it through this function to find the
  # shortest fail case
  def find_shortest_failure(str, &block)
    unless call_fails(str, &block)
      raise "hey, the input didn't fail!"
    else
      # Chop off stuff from the beginning and then the end
      # until it stops failing
      bad = str
      0.upto(bad.length) {|index|
	bad.length.downto(1) {|length|
	  begin
	    loop {
	      s = bad.dup
	      s[index,length] = ''
	      break if bad == s
	      break unless call_fails(s, &block)
	      bad = s
	    }
	  rescue IndexError
	    break
	  end
	}
      }
      raise "shortest failure is #{bad.inspect}"
    end
  end

end
