#!/usr/bin/env ruby

# Teststack: A way to preload your Rails app stack for your iterative test running
# Based on ideas from Jesse Storimer here:
# http://www.jstorimer.com/blogs/workingwithcode/8136295-screencast-faster-rails-test-runs-with-unix
# https://gist.github.com/jstorimer/5862436
# Usage: Run this file without args to run the server; run it with test file args to connect to the server and run tests

require 'socket'

# Monkeypatch IO to make streaming socket IO API nicer...
# Based on ideas from http://coderrr.wordpress.com/2008/10/21/when-to-use-readpartial-instead-of-read-in-ruby/
class IO
  def drain(data = nil)
    while !self.closed? && buf = readpartial_rescued(1024)
      data << buf if data && buf
      yield buf if block_given?
    end
    data
  end

  private

  def readpartial_rescued(size)
    readpartial(size)
  rescue EOFError
    nil
  end
end

class TestClient
  def initialize(test_files = ARGV)
    socket = UNIXSocket.new('.testing.sock')
    ARGV.each do |test_file|
      socket.write(test_file + "\n")
    end
    socket.close_write
    socket.drain do |buf|
      print buf
    end
  end
end

class TestServer
  def initialize
    ENV['RAILS_ENV'] = 'test'
    %w[test lib .].each{ |p| $:.unshift(p) unless $:.include? p }

    rails_boot = time {
      require 'config/application'
      require 'test/test_helper'
    }
    puts "Rails, config/application, and test/test_helper loaded after #{'%.2f' % rails_boot}s..."
    puts "You may now run '#{File.basename __FILE__}' with test file arguments in another terminal window."

    server = UNIXServer.new('.testing.sock')

    parent_process_id = Process.pid
    at_exit {
      if Process.pid == parent_process_id
        File.unlink('.testing.sock')
      end
    }

    trap('INT') { exit }

    loop do
      client = server.accept
      test_files = client.read.split("\n")
      pid = fork do
        $stdout.reopen(client)
        test_files.each do |test_file|
          require test_file
          $stdout.flush
        end
      end

      Process.wait(pid)
      client.close
    end
  end
  private
  def time
    t = Time.now
    yield
    Time.now - t
  end
end

if ARGV.empty?
  TestServer.new
else
  TestClient.new
end