Class: RSpec::MultiprocessRunner::Worker

Inherits:
Object
  • Object
show all
Defined in:
lib/rspec/multiprocess_runner/worker.rb

Overview

This object has several roles:

  • It forks the worker process

  • In the coordinator process, it is used to send messages to the worker and track the worker’s status, completed specs, and example results.

  • In the worker process, it is used to send messages to the coordinator and actually run specs.

Constant Summary collapse

COMMAND_QUIT =
"quit"
COMMAND_RUN_FILE =
"run_file"
STATUS_EXAMPLE_COMPLETE =
"example_complete"
STATUS_RUN_COMPLETE =
"run_complete"

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(environment_number, options) ⇒ Worker

Returns a new instance of Worker.



33
34
35
36
37
38
39
40
41
# File 'lib/rspec/multiprocess_runner/worker.rb', line 33

def initialize(environment_number, options)
  @environment_number = environment_number
  @worker_socket, @coordinator_socket = Socket.pair(:UNIX, :STREAM)
  @rspec_arguments = (options[:rspec_options] || []) + ["--format", ReportingFormatter.to_s]
  @example_timeout_seconds = options[:example_timeout_seconds]
  @file_timeout_seconds = options[:file_timeout_seconds]
  @test_env_number_first_is_1 = options[:test_env_number_first_is_1]
  @example_results = []
end

Instance Attribute Details

#current_fileObject (readonly)

Returns the value of attribute current_file.



24
25
26
# File 'lib/rspec/multiprocess_runner/worker.rb', line 24

def current_file
  @current_file
end

#deactivation_reasonObject

Returns the value of attribute deactivation_reason.



25
26
27
# File 'lib/rspec/multiprocess_runner/worker.rb', line 25

def deactivation_reason
  @deactivation_reason
end

#environment_numberObject (readonly)

Returns the value of attribute environment_number.



24
25
26
# File 'lib/rspec/multiprocess_runner/worker.rb', line 24

def environment_number
  @environment_number
end

#example_resultsObject (readonly)

Returns the value of attribute example_results.



24
25
26
# File 'lib/rspec/multiprocess_runner/worker.rb', line 24

def example_results
  @example_results
end

#pidObject (readonly)

Returns the value of attribute pid.



24
25
26
# File 'lib/rspec/multiprocess_runner/worker.rb', line 24

def pid
  @pid
end

Instance Method Details

#==(other) ⇒ Object

Workers can be found in the coordinator process by their coordinator socket.



45
46
47
48
49
50
51
52
# File 'lib/rspec/multiprocess_runner/worker.rb', line 45

def ==(other)
  case other
  when Socket
    other == @coordinator_socket
  else
    super
  end
end

#kill_nowObject



137
138
139
140
# File 'lib/rspec/multiprocess_runner/worker.rb', line 137

def kill_now
  Process.kill(:KILL, pid)
  Process.detach(pid)
end

#quit_when_idle_and_wait_for_quitObject



106
107
108
109
# File 'lib/rspec/multiprocess_runner/worker.rb', line 106

def quit_when_idle_and_wait_for_quit
  send_message_to_worker(command: COMMAND_QUIT)
  Process.wait(self.pid)
end

#reapObject



142
143
144
# File 'lib/rspec/multiprocess_runner/worker.rb', line 142

def reap
  terminate_then_kill(3, "Reaping troubled process #{environment_number} (#{pid}; #{@current_file})")
end

#receive_and_act_on_message_from_workerObject



146
147
148
# File 'lib/rspec/multiprocess_runner/worker.rb', line 146

def receive_and_act_on_message_from_worker
  act_on_message_from_worker(receive_message_from_worker)
end

#report_example_result(example_status, description, line_number, details) ⇒ Object



217
218
219
220
221
222
223
224
225
226
# File 'lib/rspec/multiprocess_runner/worker.rb', line 217

def report_example_result(example_status, description, line_number, details)
  send_message_to_coordinator(
    status: STATUS_EXAMPLE_COMPLETE,
    example_status: example_status,
    description: description,
    line_number: line_number,
    details: details,
    filename: @current_file
  )
end

#run_file(filename) ⇒ Object



111
112
113
114
115
# File 'lib/rspec/multiprocess_runner/worker.rb', line 111

def run_file(filename)
  send_message_to_worker(command: COMMAND_RUN_FILE, filename: filename)
  @current_file = filename
  @current_file_started_at = @current_example_started_at = Time.now
end

#shutdown_nowObject



133
134
135
# File 'lib/rspec/multiprocess_runner/worker.rb', line 133

def shutdown_now
  terminate_then_kill(5)
end

#socketObject



92
93
94
95
96
97
98
# File 'lib/rspec/multiprocess_runner/worker.rb', line 92

def socket
  if self.pid == Process.pid
    @worker_socket
  else
    @coordinator_socket
  end
end

#stalled?Boolean

Returns:

  • (Boolean)


121
122
123
124
125
126
127
128
129
130
131
# File 'lib/rspec/multiprocess_runner/worker.rb', line 121

def stalled?
  file_stalled =
    if @file_timeout_seconds
      working? && (Time.now - @current_file_started_at > @file_timeout_seconds)
    end
  example_stalled =
    if @example_timeout_seconds
      working? && (Time.now - @current_example_started_at > @example_timeout_seconds)
    end
  file_stalled || example_stalled
end

#startObject

Forks the worker process. In the parent, returns the PID.



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
# File 'lib/rspec/multiprocess_runner/worker.rb', line 64

def start
  pid = fork
  if pid
    @worker_socket.close
    @pid = pid
  else
    @coordinator_socket.close
    @pid = Process.pid
    ENV["TEST_ENV_NUMBER"] = test_env_number

    # reset TERM handler so that
    # - the coordinator's version (if any) is not executed twice
    # - it actually terminates the process, instead of doing the ruby
    #   default (throw an exception, which gets caught by RSpec)
    Kernel.trap("TERM", "SYSTEM_DEFAULT")
    # rely on the coordinator to handle INT
    Kernel.trap("INT", "IGNORE")
    # prevent RSpec from trapping INT, also
    ::RSpec::Core::Runner.instance_eval { def self.trap_interrupt; end }

    # Disable RSpec's at_exit hook that would try to run whatever is in ARGV
    ::RSpec::Core::Runner.disable_autorun!

    set_process_name
    run_loop
  end
end

#test_env_numberObject



54
55
56
57
58
59
60
# File 'lib/rspec/multiprocess_runner/worker.rb', line 54

def test_env_number
  if environment_number == 1 && !@test_env_number_first_is_1
    ""
  else
    environment_number.to_s
  end
end

#to_json(options = nil) ⇒ Object



150
151
152
# File 'lib/rspec/multiprocess_runner/worker.rb', line 150

def to_json(options = nil)
  { "pid" => @pid, "environment_number" => @environment_number, "current_file" => @current_file, "deactivation_reason" => @deactivation_reason }.to_json
end

#working?Boolean

Returns:

  • (Boolean)


117
118
119
# File 'lib/rspec/multiprocess_runner/worker.rb', line 117

def working?
  @current_file
end