class Nitra::Workers::Worker
Attributes
channel[R]
configuration[R]
io[R]
runner_id[R]
worker_number[R]
Public Class Methods
framework_name()
click to toggle source
Return the framework name of this worker
# File lib/nitra/worker.rb, line 22 def framework_name self.name.split("::").last.downcase end
inherited(klass)
click to toggle source
# File lib/nitra/worker.rb, line 11 def inherited(klass) @@worker_classes[klass.framework_name] = klass end
new(runner_id, worker_number, configuration)
click to toggle source
# File lib/nitra/worker.rb, line 30 def initialize(runner_id, worker_number, configuration) @runner_id = runner_id @worker_number = worker_number @configuration = configuration @forked_worker_pid = nil ENV["TEST_ENV_NUMBER"] = worker_number.to_s # Frameworks don't like it when you change the IO between invocations. # So we make one object and flush it after every invocation. @io = StringIO.new end
worker_classes()
click to toggle source
# File lib/nitra/worker.rb, line 15 def worker_classes @@worker_classes end
Public Instance Methods
fork_and_run()
click to toggle source
# File lib/nitra/worker.rb, line 44 def fork_and_run client, server = Nitra::Channel.pipe pid = fork do # This is important. We don't want anything bubbling up to the master that we didn't send there. # We reopen later to get the output from the framework run. $stdout.reopen('/dev/null', 'a') $stderr.reopen('/dev/null', 'a') trap("USR1") { interrupt_forked_worker_and_exit } server.close @channel = client begin run rescue => e channel.write("command" => "error", "process" => "init framework", "text" => e.message, "worker_number" => worker_number) end end client.close [pid, server] end
Protected Instance Methods
clean_up()
click to toggle source
# File lib/nitra/worker.rb, line 82 def clean_up raise 'Subclasses must impliment this method.' end
connect_to_database()
click to toggle source
# File lib/nitra/worker.rb, line 139 def connect_to_database if defined?(Rails) Nitra::RailsTooling.connect_to_database debug("Connected to database #{ActiveRecord::Base.connection.current_database}") end end
debug(*text)
click to toggle source
Sends debug data up to the runner.
# File lib/nitra/worker.rb, line 204 def debug(*text) if configuration.debug channel.write("command" => "debug", "text" => "worker #{runner_id}.#{worker_number}: #{text.join}", "worker_number" => worker_number) end end
interrupt_forked_worker_and_exit()
click to toggle source
Interrupts the forked worker cleanly and exits
# File lib/nitra/worker.rb, line 195 def interrupt_forked_worker_and_exit Process.kill('USR1', @forked_worker_pid) if @forked_worker_pid Process.waitall exit end
load_environment()
click to toggle source
# File lib/nitra/worker.rb, line 70 def load_environment raise 'Subclasses must impliment this method.' end
minimal_file()
click to toggle source
# File lib/nitra/worker.rb, line 74 def minimal_file raise 'Subclasses must impliment this method.' end
preload_framework()
click to toggle source
# File lib/nitra/worker.rb, line 118 def preload_framework debug "running empty spec/feature to make framework run its initialisation" file = Tempfile.new("nitra") begin load_environment file.write(minimal_file) file.close output = Nitra::Utils.capture_output do run_file(file.path, true) end channel.write("command" => "stdout", "process" => "init framework", "text" => output, "worker_number" => worker_number) unless output.empty? ensure file.close unless file.closed? file.unlink io.string = "" end clean_up end
process_file(filename)
click to toggle source
Process the file, forking before hand.
There’s two sets of data we’re interested in, the output from the test framework, and any other output. 1) We capture the framework’s output in the @io object and send that up to the runner in a results message. This happens in the run_x_file methods. 2) Anything else we capture off the stdout/stderr using the pipe and fire off in the stdout message.
# File lib/nitra/worker.rb, line 158 def process_file(filename) debug "Starting to process #{filename}" start_time = Time.now rd, wr = IO.pipe @forked_worker_pid = fork do trap('USR1') { exit! } # at_exit hooks will be run in the parent. $stdout.reopen(wr) $stderr.reopen(wr) rd.close $0 = filename run_file(filename) wr.close exit! # at_exit hooks will be run in the parent. end wr.close output = "" loop do IO.select([rd]) text = rd.read break if text.nil? || text.length.zero? output.concat text end rd.close Process.wait(@forked_worker_pid) if @forked_worker_pid @forked_worker_pid = nil end_time = Time.now channel.write("command" => "stdout", "process" => "test framework", "filename" => filename, "text" => output, "worker_number" => worker_number) unless output.empty? debug "#{filename} processed in #{'%0.2f' % (end_time - start_time)}s" end
reset_cache()
click to toggle source
# File lib/nitra/worker.rb, line 146 def reset_cache Nitra::RailsTooling.reset_cache if defined?(Rails) end
run()
click to toggle source
# File lib/nitra/worker.rb, line 86 def run trap("SIGTERM") do channel.write("command" => "error", "process" => "trap", "text" => 'Received SIGTERM', "worker_number" => worker_number) Process.kill("SIGKILL", Process.pid) end trap("SIGINT") do channel.write("command" => "error", "process" => "trap", "text" => 'Received SIGINT', "worker_number" => worker_number) Process.kill("SIGKILL", Process.pid) end debug "Started, using TEST_ENV_NUMBER #{ENV['TEST_ENV_NUMBER']}" connect_to_database reset_cache preload_framework # Loop until our runner passes us a message from the master to tells us we're finished. loop do debug "Announcing availability" channel.write("command" => "ready", "framework" => self.class.framework_name, "worker_number" => worker_number) debug "Waiting for next job" data = channel.read if data.nil? || data["command"] == "close" debug "Channel closed, exiting" exit elsif data['command'] == "process" filename = data["filename"].chomp process_file(filename) end end end
run_file(filename, preload = false)
click to toggle source
# File lib/nitra/worker.rb, line 78 def run_file(filename, preload = false) raise 'Subclasses must impliment this method.' end