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"
ERROR_RUNNING =
"error_running"

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(environment_number, options) ⇒ Worker

Returns a new instance of Worker.



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

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]
  self.options = options
  @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

#optionsObject

Returns the value of attribute options.



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

def options
  @options
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.



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

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

#kill_nowObject



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

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

#quit_when_idle_and_wait_for_quitObject



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

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

#reapObject



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

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

#receive_and_act_on_message_from_workerObject



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

def receive_and_act_on_message_from_worker
  act_on_message_from_worker(receive_message_from_worker)
end

#report_error(message) ⇒ Object



231
232
233
234
235
236
237
# File 'lib/rspec/multiprocess_runner/worker.rb', line 231

def report_error(message)
  send_message_to_coordinator(
    status: ERROR_RUNNING,
    message: message,
    filename: @current_file,
  )
end

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



220
221
222
223
224
225
226
227
228
229
# File 'lib/rspec/multiprocess_runner/worker.rb', line 220

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



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

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



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

def shutdown_now
  terminate_then_kill(5)
end

#socketObject



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

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

#stalled?Boolean

Returns:

  • (Boolean)


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

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

#startObject

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



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

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



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

def test_env_number
  if environment_number == 1 && !options.first_is_1
    ""
  else
    environment_number.to_s
  end
end

#to_json(options = nil) ⇒ Object



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

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)


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

def working?
  @current_file
end