309 lines
6.7 KiB
Ruby
309 lines
6.7 KiB
Ruby
require "drb/drb"
|
|
require "thread"
|
|
require "fileutils"
|
|
require "autospec/reload_css"
|
|
require "autospec/base_runner"
|
|
require "autospec/simple_runner"
|
|
require "autospec/spork_runner"
|
|
|
|
module Autospec; end
|
|
|
|
class Autospec::Runner
|
|
MATCHERS = {}
|
|
def self.watch(pattern, &blk)
|
|
MATCHERS[pattern] = blk
|
|
end
|
|
|
|
watch(%r{^spec/.+_spec\.rb$})
|
|
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/components/#{m[1]}_spec.rb" }
|
|
|
|
# Rails example
|
|
watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
|
|
watch(%r{^app/(.*)(\.erb|\.haml)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" }
|
|
watch(%r{^app/controllers/(.+)_(controller)\.rb$}) { |m| "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb" }
|
|
watch(%r{^spec/support/(.+)\.rb$}) { "spec" }
|
|
watch("app/controllers/application_controller.rb") { "spec/controllers" }
|
|
|
|
# Capybara request specs
|
|
watch(%r{^app/views/(.+)/.*\.(erb|haml)$}) { |m| "spec/requests/#{m[1]}_spec.rb" }
|
|
|
|
# Fabrication
|
|
watch(%r{^spec/fabricators/(.+)_fabricator\.rb$}) { "spec" }
|
|
|
|
RELOAD_MATCHERS = Set.new
|
|
def self.watch_reload(pattern)
|
|
RELOAD_MATCHERS << pattern
|
|
end
|
|
|
|
watch_reload('spec/spec_helper.rb')
|
|
watch_reload('config/(.*).rb')
|
|
watch_reload(%r{app/helpers/(.*).rb})
|
|
|
|
def self.run(opts={})
|
|
self.new.run(opts)
|
|
end
|
|
|
|
def initialize
|
|
@queue = []
|
|
@mutex = Mutex.new
|
|
@signal = ConditionVariable.new
|
|
start_service_queue
|
|
end
|
|
|
|
def run(opts = {})
|
|
|
|
puts "Forced polling (slower) - inotify does not work on network filesystems, use local filesystem to avoid" if opts[:force_polling]
|
|
|
|
if ENV["SPORK"] == "0"
|
|
puts "Using Simple Runner"
|
|
@runner = Autospec::SimpleRunner.new
|
|
else
|
|
puts "Using Spork Runner"
|
|
@runner = Autospec::SporkRunner.new
|
|
end
|
|
@runner.start
|
|
|
|
Signal.trap("HUP") {@runner.stop; exit }
|
|
Signal.trap("SIGINT") {@runner.stop; exit }
|
|
|
|
options = {filter: /^app|^spec|^lib/, relative_paths: true}
|
|
|
|
if opts[:force_polling]
|
|
options[:force_polling] = true
|
|
options[:latency] = opts[:latency] || 3
|
|
end
|
|
|
|
Thread.start do
|
|
Listen.to('.', options ) do |modified, added, removed|
|
|
process_change([modified, added].flatten.compact)
|
|
end
|
|
end
|
|
|
|
@mutex.synchronize do
|
|
@queue << ['spec', 'spec']
|
|
@signal.signal
|
|
end
|
|
|
|
while @runner.running?
|
|
process_queue
|
|
end
|
|
|
|
rescue => e
|
|
puts e
|
|
puts e.backtrace
|
|
@runner.stop
|
|
end
|
|
|
|
def process_queue
|
|
STDIN.gets
|
|
|
|
if @queue.length == 0
|
|
@queue << ['spec', 'spec']
|
|
@signal.signal
|
|
else
|
|
specs = failed_specs(:delete => false)
|
|
puts
|
|
puts
|
|
if specs.length == 0
|
|
puts "No specs have failed yet!"
|
|
puts
|
|
else
|
|
puts "The following specs have failed: "
|
|
specs.each do |s|
|
|
puts s
|
|
end
|
|
puts
|
|
queue_specs(specs.zip specs)
|
|
end
|
|
end
|
|
end
|
|
|
|
def wait_for(timeout_milliseconds)
|
|
timeout = (timeout_milliseconds + 0.0) / 1000
|
|
finish = Time.now + timeout
|
|
t = Thread.new do
|
|
while Time.now < finish && !yield
|
|
sleep(0.001)
|
|
end
|
|
end
|
|
t.join rescue nil
|
|
end
|
|
|
|
def force_polling?
|
|
works = false
|
|
|
|
begin
|
|
require 'rb-inotify'
|
|
require 'fileutils'
|
|
n = INotify::Notifier.new
|
|
FileUtils.touch('./tmp/test_polling')
|
|
|
|
n.watch("./tmp", :modify, :attrib){ works = true }
|
|
quit = false
|
|
Thread.new do
|
|
while !works && !quit
|
|
if IO.select([n.to_io], [], [], 0.1)
|
|
n.process
|
|
end
|
|
end
|
|
end
|
|
sleep 0.01
|
|
|
|
FileUtils.touch('./tmp/test_polling')
|
|
wait_for(100) { works }
|
|
File.unlink('./tmp/test_polling')
|
|
n.stop
|
|
quit = true
|
|
rescue LoadError
|
|
#assume it works (mac)
|
|
works = true
|
|
end
|
|
|
|
!works
|
|
end
|
|
|
|
|
|
def process_change(files)
|
|
return unless files.length > 0
|
|
|
|
specs = []
|
|
hit = false
|
|
files.each do |file|
|
|
RELOAD_MATCHERS.each do |k|
|
|
if k.match(file)
|
|
@runner.reload
|
|
return
|
|
end
|
|
end
|
|
MATCHERS.each do |k,v|
|
|
if m = k.match(file)
|
|
hit = true
|
|
spec = v ? ( v.arity == 1 ? v.call(m) : v.call ) : file
|
|
if File.exists?(spec) || Dir.exists?(spec)
|
|
specs << [file, spec]
|
|
end
|
|
end
|
|
end
|
|
Autospec::ReloadCss::MATCHERS.each do |k,v|
|
|
matches = []
|
|
if k.match(file)
|
|
matches << file
|
|
end
|
|
Autospec::ReloadCss.run_on_change(matches) if matches.present?
|
|
end
|
|
end
|
|
queue_specs(specs) if hit
|
|
rescue => e
|
|
p "failed in watcher"
|
|
p e
|
|
p e.backtrace
|
|
end
|
|
|
|
def queue_specs(specs)
|
|
if specs.length == 0
|
|
locked = @mutex.try_lock
|
|
if locked
|
|
@signal.signal
|
|
@mutex.unlock
|
|
end
|
|
return
|
|
else
|
|
@runner.abort
|
|
end
|
|
|
|
@mutex.synchronize do
|
|
specs.each do |c,spec|
|
|
@queue.delete([c,spec])
|
|
if @queue.last && @queue.last[0] == "focus"
|
|
focus = @queue.pop
|
|
@queue << [c,spec]
|
|
if focus[1].include?(spec) || c != spec
|
|
@queue << focus
|
|
end
|
|
else
|
|
@queue << [c,spec]
|
|
end
|
|
end
|
|
@signal.signal
|
|
end
|
|
end
|
|
|
|
def thread_loop
|
|
@mutex.synchronize do
|
|
last_failed = false
|
|
current = @queue.last
|
|
if current
|
|
last_failed = process_spec(current[1])
|
|
end
|
|
wait = @queue.length == 0 || last_failed
|
|
@signal.wait(@mutex) if wait
|
|
end
|
|
rescue => e
|
|
p "DISASTA PASTA"
|
|
puts e
|
|
puts e.backtrace
|
|
end
|
|
|
|
def process_spec(spec)
|
|
last_failed = false
|
|
result = run_spec(spec)
|
|
if result == 0
|
|
@queue.pop
|
|
else
|
|
last_failed = true
|
|
if result.to_i > 0
|
|
focus_on_failed_tests
|
|
ensure_all_specs_will_run
|
|
end
|
|
end
|
|
|
|
last_failed
|
|
end
|
|
|
|
def start_service_queue
|
|
@worker ||= Thread.new do
|
|
while true
|
|
thread_loop
|
|
end
|
|
end
|
|
end
|
|
|
|
def focus_on_failed_tests
|
|
current = @queue.last
|
|
specs = failed_specs[0..10]
|
|
if current[0] == "focus"
|
|
@queue.pop
|
|
end
|
|
@queue << ["focus", specs.join(" ")]
|
|
end
|
|
|
|
def ensure_all_specs_will_run
|
|
unless @queue.any?{|s,t| t == 'spec'}
|
|
@queue.unshift(['spec','spec'])
|
|
end
|
|
end
|
|
|
|
def failed_specs(opts={:delete => true})
|
|
specs = []
|
|
path = './tmp/rspec_result'
|
|
if File.exist?(path)
|
|
specs = File.open(path) { |file| file.read.split("\n") }
|
|
File.delete(path) if opts[:delete]
|
|
end
|
|
|
|
specs
|
|
end
|
|
|
|
def run_spec(specs)
|
|
File.delete("tmp/rspec_result") if File.exists?("tmp/rspec_result")
|
|
args = ["-f", "progress", specs.split(" "),
|
|
"-r", "#{File.dirname(__FILE__)}/formatter.rb",
|
|
"-f", "Autospec::Formatter"].flatten
|
|
|
|
@runner.run(args, specs)
|
|
|
|
end
|
|
|
|
|
|
end
|