#!/usr/bin/env ruby require 'reap/task' #require 'test/unit' require 'test/unit/ui/testrunnermediator' require 'facet/string/tabto' Test::Unit.run = true # don't run auto tests # Test Task # # The Reap test task runs each test in # it's own process, making for purer # test facility. # # NOTE: This works well enough but it is a bit of # a hack. 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_help %{ Attributes files Test files (eg. test/tc_**/*.rb) Defaults to typcial selection. requires List of any files to pre-require. 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. } attr_accessor :files, :requires, :live, :libs def init @files ||= [ 'test/*/**/*.rb', 'test/**/tc*.rb', 'test/**/test*.rb', 'test/**/*test.rb' ] @requires ||= [] @live ||= false @libs ||= [] #['./lib'] # interal use @results = TestResults.new @errors = [] @failures = [] end def run # get test files test_libs = @libs.join(':') test_files = FileList.new test_files.include(*@files) if test_files.empty? puts "No test files found." return end print "Reap is shelling out work to Ruby's Test Suite...\n" test_files.uniq.each { |f| $stdout << '.'; $stdout.flush dotest( f ) #ruby %{-r"test/unit" "#{f}"} } puts "done." # 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 # Runs a test. def dotest( test_file ) 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 end # Currently send program output to null device. # Could send to a logger in future version. def fork_test( testfile ) src = '' unless live l = File.join( Dir.pwd, 'lib' ) if File.directory?( l ) src << %{$:.unshift('#{l}')\n} end 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' } 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