#!/usr/bin/env ruby

#
# = Synopsis
#
# Run unit tests, usually with the goal of resolving some conflict
# between tests.
#
# = Usage
#
#   test [-d|--debug] [-f|--files] [-h|--help] [-n method] [-v|--verbose] <file> [file] ...
#
# = Description
#
# This script is useful for running a specific subset of unit tests, especially
# when attempting to find a conflict between tests.  By default, it will take
# the first argument you pass it and run that test or test directory with each
# test directory in turn, looking for a failure.  If any tests fail, then
# the script will drill into that test directory, looking for the specific conflict.
#
# In this way, when you have deduced you have two conflicting unit tests (tests which
# pass in isolation but fail when run together), you can tell this script where
# the failure is and leave it alone to find the conflict.
#
# This script is different from the Rakefile because it will run all tests in
# a single process, whereas if you ask the Rakefile to run multiple tests, it will
# run them in separate processes thus making it impossible to find conflicts.
#
# = Examples
#
# Attempt to resolve a conflict between a single test suite that could be anywhere:
#
#   ./test ral/providers/user.rb
#
# Determine whether two individual files conflict:
#
#   ./test --files language/functions.rb ral/providers/provider.rb
#
# Run the same test, but only run a specific unit test:
#
#   ./test -d -n test_search --files language/functions.rb ral/providers/provider.rb
#
# = Options
#
# debug::
#   Enable full debugging.
#
# files::
#   Specify exactly which files to test.
#
# help::
#   Print this help message
#
# n::
#   Specify a single unit test to run.  You can still specify as many files
#   as you want.
#
# verbose::
#   Print extra information.
#
# = Example
#
#   puppet -l /tmp/script.log script.pp
#
# = Author
#
# Luke Kanies
#
# = Copyright
#
# Copyright (c) 2005 Reductive Labs, LLC
# Licensed under the GNU Public License

require 'find'
require 'getoptlong'
include Find

result = GetoptLong.new(
    [ "--debug",    "-d", GetoptLong::NO_ARGUMENT       ],
    [ "--verbose",  "-v", GetoptLong::NO_ARGUMENT       ],
    [ "-n",               GetoptLong::REQUIRED_ARGUMENT ],
    [ "--files",    "-f", GetoptLong::NO_ARGUMENT       ],
    [ "--help",     "-h", GetoptLong::NO_ARGUMENT       ]
)

usage = "USAGE: %s [--help] suite" % $0

$options = {}
keep = []

result.each { |opt,arg|
    case opt
    when "--verbose"
        $options[:verbose] = true
    when "--files"
        $options[:files] = true
    when "--debug"
        $options[:debug] = true
        $options[:verbose] = true
    when "--help"
        puts usage
        exit
    else
        keep << opt
        keep << arg if arg
    end
}

def dirs
    Dir.glob("*").find_all { |d| FileTest.directory?(d) }.reject { |d|
        ["lib", "data"].include?(d)
    }
end

def rake(*args)
    print "trying %s..." % args.join(" ")
    output = %x{rake %s} % args.join(" ")

    if $?.exitstatus == 0
        puts "succeeded"
        return true
    else
        puts "failed"
        return false
    end
end

def resolve(dir)
    dirs = dirs()

    # If the passed dir is a subdir or file, put the basedir last
    if dir.include?(File::SEPARATOR)
        basedir = dir.split(File::SEPARATOR)[0]
        if dirs.include?(basedir)
            dirs.delete(basedir)
            dirs << basedir
        end
    end

    failed = nil
    dirs.each do |d|
        next if d == dir
        unless run([d, dir])
            failed = d
            break
        end
    end
    puts "%s failed" % failed

    files = ruby_files(failed)

    files.each do |file|
        unless run([file, dir])
            puts file
            exit(0)
        end
    end

    exit(1)
end

def ruby_files(dir)
    files = []
    # First collect the entire file list.
    begin
        find(dir) { |f| files << f if f =~ /\.rb$/ }
    rescue => detail
        puts "could not find on %s: %s" % [dir.inspect, detail]
    end
    files
end

def run(files, flags = nil)
    args = %w{ruby}
    args << "-Ilib:../lib"
    args << "lib/rake/puppet_test_loader.rb"
    if flags
        args += flags
    end
    args += ARGV

    print files.join(" ") + "... "
    $stdout.flush

    files.each do |file|
        case File.stat(file).ftype
        when "file"; args << file
        when "directory"; args += ruby_files(file)
        else
            $stderr.puts "Skipping %s; can't handle %s" %
                [file, File.stat(file).ftype]
        end
    end
    args = args.join(" ")
    if $options[:verbose]
        p args
    end
    output = %x{#{args} 2>&1}
    if $options[:debug]
        print output
    end

    if $?.exitstatus == 0
        puts "succeeded"
        return true
    else
        puts "failed"
        puts output
        return false
    end
end

if $options[:files]
    run(ARGV, keep)
else
    dir = ARGV.shift

    unless dir
        $stderr.puts usage
        exit(1)
    end
    resolve(dir)
end
#
#
#files = []
#
#args.each do |test|
#    if FileTest.directory?(test)
#        files += ruby_files(test)
#    end
#end

## Now load all of our files.
#files.each do |file|
#    load file unless file =~ /^-/
#end
#
#runner = Test::Unit::AutoRunner.new(false)
#runner.process_args
#runner.run
