summaryrefslogtreecommitdiff
path: root/lib/test/unit/parallel.rb
blob: 049b470e40fb7c14129f9d43d1f6d771080209e0 (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
require 'test/unit'

module Test
  module Unit
    class Worker < Runner
      class << self
        undef autorun
      end

      alias orig_run_suite _run_suite
      undef _run_suite
      undef _run_suites
      undef run

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

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

      def _run_suite(suite, type)
        r = report.dup
        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.read(5))
              @stdout.puts "p #{[buf].pack("m0")}"
            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 << (report - r)
        result << [@errors-e,@failures-f,@skips-s]
        result << ($: - @old_loadpath)
        result << suite.name

        begin
          @stdout.puts "done #{[Marshal.dump(result)].pack("m0")}"
        rescue Errno::EPIPE; end
        return result
      ensure
        MiniTest::Unit.output = orig_stdout
        $stdin = orig_stdin
        $stdout = orig_stdout
        o.close if o && !o.closed?
        i.close if i && !i.closed?
      end

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

        @old_loadpath = []
        begin
          @stdout = increment_io(STDOUT)
          @stdin = increment_io(STDIN)
          @stdout.sync = true
          @stdout.puts "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 (.+?) (.+?)$/
              @stdout.puts "okay"

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

              begin
                require $1
              rescue LoadError
                @stdout.puts "after #{[Marshal.dump([$1, $!])].pack("m0")}"
                @stdout.puts "ready"
                next
              end
              _run_suites MiniTest::Unit::TestCase.test_suites-suites, $2.to_sym

              if @need_exit
                begin
                  @stdout.puts "bye"
                rescue Errno::EPIPE; end
                exit
              else
                @stdout.puts "ready"
              end
            when /^quit$/
              begin
                @stdout.puts "bye"
              rescue Errno::EPIPE; end
              exit
            end
          end
        rescue Errno::EPIPE
        rescue Exception => e
          begin
            @stdout.puts "bye #{[Marshal.dump(e)].pack("m0")}"
          rescue Errno::EPIPE;end
          exit
        ensure
          @stdin.close
          @stdout.close
        end
      end
    end
  end
end

if $0 == __FILE__
  module Test
    module Unit
      class TestCase < MiniTest::Unit::TestCase
        def on_parallel_worker?
          true
        end
      end
    end
  end

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