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