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("m").gsub("\n","")}"
            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("m").gsub("\n","")}"
        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("m").gsub("\n","")}"
                @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("m").gsub("\n","")}"
          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
