class Nitra::Runner

Attributes

configuration[R]
framework[R]
runner_id[R]
server_channel[R]
tasks[R]
workers[R]

Public Class Methods

new(configuration, server_channel, runner_id) click to toggle source
# File lib/nitra/runner.rb, line 6
def initialize(configuration, server_channel, runner_id)
  ENV["RAILS_ENV"] = configuration.environment

  @workers         = {}
  @runner_id       = runner_id
  @framework       = configuration.framework
  @configuration   = configuration
  @server_channel  = server_channel
  @tasks           = Nitra::Tasks.new(self)

  configuration.calculate_default_process_count
  server_channel.raise_epipe_on_write_error = true
end

Public Instance Methods

debug(*text) click to toggle source
# File lib/nitra/runner.rb, line 41
def debug(*text)
  if configuration.debug
    server_channel.write("command" => "debug", "text" => "runner #{runner_id}: #{text.join}")
  end
end
run() click to toggle source
# File lib/nitra/runner.rb, line 20
def run
  tasks.run(:before_runner)

  load_rails_environment

  tasks.run(:before_worker, configuration.process_count)

  start_workers

  trap("SIGTERM") { $aborted = true }
  trap("SIGINT") { $aborted = true }

  hand_out_files_to_workers

  tasks.run(:after_runner)
rescue Errno::EPIPE
ensure
  trap("SIGTERM", "DEFAULT")
  trap("SIGINT", "DEFAULT")
end

Protected Instance Methods

close_worker(worker_number, worker_channel) click to toggle source
# File lib/nitra/runner.rb, line 169
def close_worker(worker_number, worker_channel)
  debug "Sending close message to #{worker_number}"
  worker_channel.write "command" => "close"
  workers.delete worker_number
end
hand_out_files_to_workers() click to toggle source
# File lib/nitra/runner.rb, line 79
def hand_out_files_to_workers
  while !$aborted && workers.length > 0
    Nitra::Channel.read_select(worker_pipes + [server_channel]).each do |worker_channel|

      # This is our back-channel that lets us know in case the master is dead.
      kill_workers if worker_channel == server_channel && server_channel.rd.eof?

      unless data = worker_channel.read
        worker_number, worker_hash = workers.find {|number, hash| hash[:pipe] == worker_channel}
        workers.delete worker_number
        debug "Worker #{worker_number} unexpectedly died."
        next
      end

      case data['command']
      when "debug", "stdout", "error"
        server_channel.write(data)

      when "result"
        handle_result(data)

      when "ready"
        handle_ready(data, worker_channel)
      end
    end
  end
end
handle_ready(data, worker_channel) click to toggle source
# File lib/nitra/runner.rb, line 147
def handle_ready(data, worker_channel)
  worker_number = data["worker_number"]
  server_channel.write("command" => "next", "framework" => data["framework"])
  data = server_channel.read

  case data["command"]
  when "framework"
    close_worker(worker_number, worker_channel)

    @framework = data["framework"]
    debug "Restarting #{worker_number} with framework #{framework}"
    start_worker(worker_number)

  when "file"
    debug "Sending #{data["filename"]} to #{worker_number}"
    worker_channel.write "command" => "process", "filename" => data["filename"]

  when "drain"
    close_worker(worker_number, worker_channel)
  end
end
handle_result(data) click to toggle source

This parses the results we got back from the worker.

It needs rewriting when we finally rewrite the workers to use custom formatters.

Also, it’s probably buggy as hell…

# File lib/nitra/runner.rb, line 114
def handle_result(data)
  #defaults - theoretically anything can end up here so we just want to pass on useful data
  result_text = ""
  example_count = 0
  failure_count = 0
  return_code = data["return_code"].to_i

  # Rspec result
  if m = data['text'].match(/(\d+) examples?, (\d+) failure/)
    example_count = m[1].to_i
    failure_count = m[2].to_i

  # Cucumber result
  elsif m = data['text'].match(/(\d+) scenarios?.+$/)
    example_count = m[1].to_i
    if m = data['text'].match(/\d+ scenarios? \(.*(\d+) [failed|undefined].*\)/)
      failure_count = m[1].to_i
    else
      failure_count = 0
    end
  end

  result_text = data['text'] if failure_count > 0 || return_code != 0

  server_channel.write(
    "command"       => "result",
    "filename"      => data["filename"],
    "return_code"   => return_code,
    "example_count" => example_count,
    "failure_count" => failure_count,
    "text"          => result_text)
end
kill_workers() click to toggle source

Kill the workers.

# File lib/nitra/runner.rb, line 178
def kill_workers
  worker_pids = workers.collect{|index, hash| hash[:pid]}
  worker_pids.each {|pid| Process.kill('USR1', pid) rescue Errno::ESRCH}
  Process.waitall
  exit
end
load_rails_environment() click to toggle source
# File lib/nitra/runner.rb, line 49
def load_rails_environment
  return unless File.file?('config/application.rb')
  debug "Loading rails environment..."

  ENV["TEST_ENV_NUMBER"] = "1"

  output = Nitra::Utils.capture_output do
    require './config/application'
    Rails.application.require_environment!
    ActiveRecord::Base.connection.disconnect!
  end

  server_channel.write("command" => "stdout", "process" => "rails initialisation", "text" => output)
end
start_worker(index) click to toggle source
# File lib/nitra/runner.rb, line 70
def start_worker(index)
  pid, pipe = Nitra::Workers::Worker.worker_classes[framework].new(runner_id, index, configuration).fork_and_run
  workers[index] = {:pid => pid, :pipe => pipe}
end
start_workers() click to toggle source
# File lib/nitra/runner.rb, line 64
def start_workers
  (1..configuration.process_count).collect do |index|
    start_worker(index)
  end
end
worker_pipes() click to toggle source
# File lib/nitra/runner.rb, line 75
def worker_pipes
  workers.collect {|index, worker_hash| worker_hash[:pipe]}
end