1require 'test/unit' 2 3module Test 4 module Unit 5 class Worker < Runner 6 class << self 7 undef autorun 8 end 9 10 alias orig_run_suite mini_run_suite 11 undef _run_suite 12 undef _run_suites 13 undef run 14 15 def increment_io(orig) 16 *rest, io = 32.times.inject([orig.dup]){|ios, | ios << ios.last.dup } 17 rest.each(&:close) 18 io 19 end 20 21 def _run_suites(suites, type) 22 suites.map do |suite| 23 _run_suite(suite, type) 24 end 25 end 26 27 def _run_suite(suite, type) 28 @partial_report = [] 29 orig_testout = MiniTest::Unit.output 30 i,o = IO.pipe 31 32 MiniTest::Unit.output = o 33 orig_stdin, orig_stdout = $stdin, $stdout 34 35 th = Thread.new do 36 begin 37 while buf = (self.verbose ? i.gets : i.read(5)) 38 _report "p", buf 39 end 40 rescue IOError 41 rescue Errno::EPIPE 42 end 43 end 44 45 e, f, s = @errors, @failures, @skips 46 47 begin 48 result = orig_run_suite(suite, type) 49 rescue Interrupt 50 @need_exit = true 51 result = [nil,nil] 52 end 53 54 MiniTest::Unit.output = orig_testout 55 $stdin = orig_stdin 56 $stdout = orig_stdout 57 58 o.close 59 begin 60 th.join 61 rescue IOError 62 raise unless ["stream closed","closed stream"].include? $!.message 63 end 64 i.close 65 66 result << @partial_report 67 @partial_report = nil 68 result << [@errors-e,@failures-f,@skips-s] 69 result << ($: - @old_loadpath) 70 result << suite.name 71 72 begin 73 _report "done", Marshal.dump(result) 74 rescue Errno::EPIPE; end 75 return result 76 ensure 77 MiniTest::Unit.output = orig_stdout 78 $stdin = orig_stdin 79 $stdout = orig_stdout 80 o.close if o && !o.closed? 81 i.close if i && !i.closed? 82 end 83 84 def run(args = []) 85 process_args args 86 @@stop_auto_run = true 87 @opts = @options.dup 88 @need_exit = false 89 90 @old_loadpath = [] 91 begin 92 begin 93 @stdout = increment_io(STDOUT) 94 @stdin = increment_io(STDIN) 95 rescue 96 exit 2 97 end 98 exit 2 unless @stdout && @stdin 99 100 @stdout.sync = true 101 _report "ready!" 102 while buf = @stdin.gets 103 case buf.chomp 104 when /^loadpath (.+?)$/ 105 @old_loadpath = $:.dup 106 $:.push(*Marshal.load($1.unpack("m")[0].force_encoding("ASCII-8BIT"))).uniq! 107 when /^run (.+?) (.+?)$/ 108 _report "okay" 109 110 @options = @opts.dup 111 suites = MiniTest::Unit::TestCase.test_suites 112 113 begin 114 require $1 115 rescue LoadError 116 _report "after", Marshal.dump([$1, ProxyError.new($!)]) 117 _report "ready" 118 next 119 end 120 _run_suites MiniTest::Unit::TestCase.test_suites-suites, $2.to_sym 121 122 if @need_exit 123 begin 124 _report "bye" 125 rescue Errno::EPIPE; end 126 exit 127 else 128 _report "ready" 129 end 130 when /^quit$/ 131 begin 132 _report "bye" 133 rescue Errno::EPIPE; end 134 exit 135 end 136 end 137 rescue Errno::EPIPE 138 rescue Exception => e 139 begin 140 trace = e.backtrace 141 err = ["#{trace.shift}: #{e.message} (#{e.class})"] + trace.map{|t| t.prepend("\t") } 142 143 _report "bye", Marshal.dump(err.join("\n")) 144 rescue Errno::EPIPE;end 145 exit 146 ensure 147 @stdin.close if @stdin 148 @stdout.close if @stdout 149 end 150 end 151 152 def _report(res, *args) 153 res = "#{res} #{args.pack("m0")}" unless args.empty? 154 @stdout.puts(res) 155 end 156 157 def puke(klass, meth, e) 158 @partial_report << [klass.name, meth, e.is_a?(MiniTest::Assertion) ? e : ProxyError.new(e)] 159 super 160 end 161 end 162 end 163end 164 165if $0 == __FILE__ 166 module Test 167 module Unit 168 class TestCase < MiniTest::Unit::TestCase 169 undef on_parallel_worker? 170 def on_parallel_worker? 171 true 172 end 173 end 174 end 175 end 176 require 'rubygems' 177 class Gem::TestCase < MiniTest::Unit::TestCase 178 @@project_dir = File.expand_path('../../../..', __FILE__) 179 end 180 181 Test::Unit::Worker.new.run(ARGV) 182end 183