summaryrefslogtreecommitdiff
path: root/spec/mspec/lib/mspec/runner/parallel.rb
blob: 6a9ecd155d7bd81db9b894f494e7db586bd1cad8 (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
class ParallelRunner
  def initialize(files, processes, formatter, argv)
    @files = files
    @processes = processes
    @formatter = formatter
    @argv = argv
    @last_files = {}
    @output_files = []
    @success = true
  end

  def launch_children
    @children = @processes.times.map { |i|
      name = tmp "mspec-multi-#{i}"
      @output_files << name

      env = {
        "SPEC_TEMP_DIR" => "#{SPEC_TEMP_DIR}_#{i}",
        "MSPEC_MULTI" => i.to_s
      }
      command = @argv + ["-fy", "-o", name]
      $stderr.puts "$ #{command.join(' ')}" if $MSPEC_DEBUG
      IO.popen([env, *command, close_others: false], "rb+")
    }
  end

  def handle(child, message)
    case message
    when '.'
      @formatter.unload
      send_new_file_or_quit(child)
    else
      if message == nil
        msg = "A child mspec-run process died unexpectedly"
      else
        msg = "A child mspec-run process printed unexpected output on STDOUT"
        while chunk = (child.read_nonblock(4096) rescue nil)
          message += chunk
        end
        message.chomp!('.')
        msg += ": #{message.inspect}"
      end

      if last_file = @last_files[child]
        msg += " while running #{last_file}"
      end

      @success = false
      quit(child)
      abort "\n#{msg}"
    end
  end

  def quit(child)
    begin
      child.puts "QUIT"
    rescue Errno::EPIPE
      # The child process already died
    end
    _pid, status = Process.wait2(child.pid)
    @success &&= status.success?
    child.close
    @children.delete(child)
  end

  def send_new_file_or_quit(child)
    if @files.empty?
      quit(child)
    else
      file = @files.shift
      @last_files[child] = file
      child.puts file
    end
  end

  def run
    MSpec.register_files @files
    launch_children

    puts @children.map { |child| child.gets }.uniq
    @formatter.start
    begin
      @children.each { |child| send_new_file_or_quit(child) }

      until @children.empty?
        IO.select(@children)[0].each { |child|
          handle(child, child.read(1))
        }
      end
    ensure
      @children.dup.each { |child| quit(child) }
      @formatter.aggregate_results(@output_files)
      @formatter.finish
    end

    @success
  end
end