summaryrefslogtreecommitdiff
path: root/test/lib/test/unit/parallel.rb
blob: 6a319c3c3f7238e0f176e976811075cf27cfa254 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
# frozen_string_literal: false
$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../.."
require 'test/unit'

module Test
  module Unit
    class Worker < Runner # :nodoc:
      class << self
        undef autorun
      end

      alias orig_run_suite mini_run_suite
      undef _run_suite
      undef _run_suites
      undef run

      def increment_io(orig) # :nodoc:
        *rest, io = 32.times.inject([orig.dup]){|ios, | ios << ios.last.dup }
        rest.each(&:close)
        io
      end

      def _run_suites(suites, type) # :nodoc:
        suites.map do |suite|
          _run_suite(suite, type)
        end
      end

      def _run_suite(suite, type) # :nodoc:
        @partial_report = []
        orig_testout = MiniTest::Unit.output
        i,o = IO.pipe

        MiniTest::Unit.output = o
        orig_stdin, orig_stdout = $stdin, $stdout

        th = Thread.new do
          begin
            while buf = (self.verbose ? i.gets : i.readpartial(1024))
              _report "p", buf
            end
          rescue IOError
          rescue Errno::EPIPE
          end
        end

        e, f, s = @errors, @failures, @skips

        begin
          result = orig_run_suite(suite, type)
        rescue Interrupt
          @need_exit = true
          result = [nil,nil]
        end

        MiniTest::Unit.output = orig_testout
        $stdin = orig_stdin
        $stdout = orig_stdout

        o.close
        begin
          th.join
        rescue IOError
          raise unless ["stream closed","closed stream"].include? $!.message
        end
        i.close

        result << @partial_report
        @partial_report = nil
        result << [@errors-e,@failures-f,@skips-s]
        result << ($: - @old_loadpath)
        result << suite.name

        begin
          _report "done", Marshal.dump(result)
        rescue Errno::EPIPE; end
        return result
      ensure
        MiniTest::Unit.output = orig_stdout
        $stdin = orig_stdin if orig_stdin
        $stdout = orig_stdout if orig_stdout
        o.close if o && !o.closed?
        i.close if i && !i.closed?
      end

      def run(args = []) # :nodoc:
        process_args args
        @@stop_auto_run = true
        @opts = @options.dup
        @need_exit = false

        @old_loadpath = []
        begin
          begin
            @stdout = increment_io(STDOUT)
            @stdin = increment_io(STDIN)
          rescue
            exit 2
          end
          exit 2 unless @stdout && @stdin

          @stdout.sync = true
          _report "ready!"
          while buf = @stdin.gets
            case buf.chomp
            when /^loadpath (.+?)$/
              @old_loadpath = $:.dup
              $:.push(*Marshal.load($1.unpack("m")[0].force_encoding("ASCII-8BIT"))).uniq!
            when /^run (.+?) (.+?)$/
              _report "okay"

              @options = @opts.dup
              suites = MiniTest::Unit::TestCase.test_suites

              begin
                require File.realpath($1)
              rescue LoadError
                _report "after", Marshal.dump([$1, ProxyError.new($!)])
                _report "ready"
                next
              end
              _run_suites MiniTest::Unit::TestCase.test_suites-suites, $2.to_sym

              if @need_exit
                begin
                  _report "bye"
                rescue Errno::EPIPE; end
                exit
              else
                _report "ready"
              end
            when /^quit$/
              begin
                _report "bye"
              rescue Errno::EPIPE; end
              exit
            end
          end
        rescue Errno::EPIPE
        rescue Exception => e
          begin
            trace = e.backtrace
            err = ["#{trace.shift}: #{e.message} (#{e.class})"] + trace.map{|t| t.prepend("\t") }

            _report "bye", Marshal.dump(err.join("\n"))
          rescue Errno::EPIPE;end
          exit
        ensure
          @stdin.close if @stdin
          @stdout.close if @stdout
        end
      end

      def _report(res, *args) # :nodoc:
        res = "#{res} #{args.pack("m0")}" unless args.empty?
        @stdout.puts(res)
      end

      def puke(klass, meth, e) # :nodoc:
        if e.is_a?(MiniTest::Skip)
          new_e = MiniTest::Skip.new(e.message)
          new_e.set_backtrace(e.backtrace)
          e = new_e
        end
        @partial_report << [klass.name, meth, e.is_a?(MiniTest::Assertion) ? e : ProxyError.new(e)]
        super
      end
    end
  end
end

if $0 == __FILE__
  module Test
    module Unit
      class TestCase < MiniTest::Unit::TestCase # :nodoc: all
        undef on_parallel_worker?
        def on_parallel_worker?
          true
        end
      end
    end
  end
  require 'rubygems'
  module Gem # :nodoc:
  end
  class Gem::TestCase < MiniTest::Unit::TestCase # :nodoc:
    @@project_dir = File.expand_path('../../../../..', __FILE__)
  end

  Test::Unit::Worker.new.run(ARGV)
end