#!/usr/bin/env ruby require 'reap/task' #require 'test/unit' require 'test/unit/ui/testrunnermediator' require 'facet/string/tabto' require 'facet/progressbar' Test::Unit.run = true # don't run auto tests # _____ _ _____ _ # |_ _|__ __| |_ |_ _|_ _ __| |__ # | |/ -_|_-< _| | |/ _` (_-< / / # |_|\___/__/\__| |_|\__,_/__/_\_\ # # = Test Task # # The Reap test task runs each test in it's own # proccess, making for purer test facility. # # NOTE This works well enough but it is a tad delicate. # It actually marshals test results across stdout->stdin # shell pipe. One consequence of this is that you can't # send debug info to stdout (including #p and #puts). # This, hopefully can be remedied in the future. class Reap::Test < Reap::Task task_desc "Run unit-tests (each in a separate process)." task_available { Reap.projectfile? } task_help %{ reap test Run unit tests. Reap runs each test in a separate proccess to aviod potential conflicts between scripts. files Test files (eg. test/tc_**/*.rb) Defaults to typcial selection. libs List of lookup directories to include in load path. './lib' is always included. live Flag to quickly deactive use of local libs. Test against installed files instead. requires List of any files to pre-require. } alias_method :tst, :task # Run testing task. def run # setup tst.files ||= [ 'test/*/**/*.rb', 'test/**/tc*.rb', 'test/**/test*.rb', 'test/**/*test.rb' ] tst.requires ||= [] tst.live ||= false tst.libs ||= [] #['./lib'] # interal use @results = TestResults.new @errors = [] @failures = [] # Get test files test_libs = tst.libs.join(':') test_files = FileList.new test_files.include(*tst.files) test_files.uniq! if test_files.empty? tell "No test files found." return end #tell "Reap is passing off work to Ruby's Test Suite..." pbar = Console::ProgressBar.new( 'Testing', test_files.size ) # Run tests # (why arn't these unique to begin with?) test_files.each do |test_file| pbar.inc #$stdout << '.'; $stdout.flush if ! File.file?( test_file ) r = nil else r = fork_test( test_file ) end unless r.passed? @errors << r.instance_variable_get('@errors') @failures << r.instance_variable_get('@failures') end @results << r #ruby %{-r"test/unit" "#{f}"} end pbar.finish # Don't know why empty arrays get in them yet, but... @failures.reject! { |e| e == [] } @errors.reject! { |e| e == [] } # Display failures puts puts %{FAILURES:#{@failures.empty? ? ' []' : ''}} @failures.reverse.each { |fails| fails.reverse.each { |failure| puts puts %{ - test : #{failure.test_name}} puts %{ location : #{failure.location}} if failure.message.index("\n") puts %{ message : >} puts failure.message.tabto(6) else puts %{ message : #{failure.message}} end } } # Display errors puts puts %{ERRORS:#{@errors.empty? ? ' []' : ''}} @errors.reverse.each { |errs| errs.reverse.each { |err| puts puts %{ - test : #{err.test_name}} puts %{ message : #{err.exception.message}} puts %{ backtrace :} err.exception.backtrace[0...-1].each { |bt| puts %Q{ - #{bt}} } } } # Display final results puts puts @results puts # old way (don't work right) #puts %Q{ ruby %{-I#{test_libs} -e0 -r"test/unit" \\\n#{test_reqs}#{test_opts}} } if $DEBUG #ruby %{-I#{test_libs} -e0 -r"test/unit" \\\n#{test_reqs}#{test_opts}} #ruby %{-e0 -r"test/unit" \\\n#{test_reqs}#{test_opts}} end private #def dotest( tst, test_file ) #end # Runs a test. # # Currently send program output to null device. # Could send to a logger in future version. def fork_test( testfile ) src = '' unless tst.live l = File.join( Dir.pwd, 'lib' ) if File.directory?( l ) src << %{$:.unshift('#{l}')\n} end tst.libs.each { |r| src << %{$:.unshift('#{r}')\n} } end src << %{ #require 'test/unit' require 'test/unit/collector' require 'test/unit/collector/objectspace' require 'test/unit/ui/testrunnermediator' } tst.requires.each do |fix| src << %Q{ require '#{fix}' } end src << %{ output = STDOUT.dup STDOUT.reopen( PLATFORM =~ /mswin/ ? "NUL" : "/dev/null" ) load('#{testfile}') tests = Test::Unit::Collector::ObjectSpace.new.collect runner = Test::Unit::UI::TestRunnerMediator.new( tests ) result = runner.run_suite begin marshalled = Marshal.dump(result) rescue TypeError => e $stderr << "MARSHAL ERROR\n" $stderr << "TEST: #{testfile}\n" $stderr << "DATA:" << result.inspect exit -1 end output << marshalled STDOUT.reopen(output) output.close } result = IO.popen("ruby","w+") do |ruby| ruby.puts src ruby.close_write ruby.read end p testfile if $VERBOSE begin marsh = Marshal.load(result) rescue ArgumentError $stderr << "\nCannot load marshalled test data.\n" $stderr << result << "\n" exit -1 end return marsh end # # Support class for collecting test results. # class TestResults attr_reader :assertion_count, :run_count, :failure_count, :error_count def initialize @assertion_count = 0 @run_count = 0 @failure_count = 0 @error_count = 0 end def <<( result ) @assertion_count += result.assertion_count @run_count += result.run_count @failure_count += result.failure_count @error_count += result.error_count end def to_s s = %{SUMMARY:\n\n} s << %{ tests : #{@run_count}\n} s << %{ assertions : #{@assertion_count}\n} s << %{ failures : #{@failure_count}\n} s << %{ errors : #{@error_count}\n} s end end end # Rake interface. if defined?(Rake) #require 'reap/rake/adapter' module Rake ReapTest = ::Reap::RakeAdapter( ::Reap::Test ) end end