summaryrefslogtreecommitdiff
path: root/lib/test/unit/parallel.rb
blob: 18035a4d55cab6ad0b9cc727b39a371ac0ae817a (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
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 inclement_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|
          result = _run_suite(suite, type)
        end
      end

      def _run_suite(suite, type)
        r = report.dup
        orig_stdout = MiniTest::Unit.output
        i,o = IO.pipe
        MiniTest::Unit.output = o

        th = Thread.new do
          begin
            while buf = (self.verbose ? i.gets : i.read(5))
              @stdout.puts "p #{[buf].pack("m").gsub("\n","")}"
            end
          rescue IOError
          rescue Errno::EPIPE
          end
        end

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

        result = orig_run_suite(suite, type)

        MiniTest::Unit.output = 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("m").gsub("\n","")}"
        rescue Errno::EPIPE; end
        return result
      ensure
        MiniTest::Unit.output = 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

        Signal.trap(:INT,"IGNORE")
        @old_loadpath = []
        begin
          @stdout = inclement_io(STDOUT)
          @stdin = inclement_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("m").gsub("\n","")}"
                @stdout.puts "ready"
                next
              end
              _run_suites MiniTest::Unit::TestCase.test_suites-suites, $2.to_sym

              @stdout.puts "ready"
            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("m").gsub("\n","")}"
          rescue Errno::EPIPE;end
          exit
        ensure
          @stdin.close
          @stdout.close
        end
      end
    end
  end
end

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