2013-11-01 23:57:50 +01:00
require " listen "
require " thread "
require " fileutils "
require " autospec/reload_css "
require " autospec/base_runner "
module Autospec ; end
class Autospec :: Manager
def self . run ( opts = { } )
2013-11-05 11:01:17 +01:00
self . new ( opts ) . run
2013-11-01 23:57:50 +01:00
end
2013-11-05 11:01:17 +01:00
def initialize ( opts = { } )
@opts = opts
@debug = opts [ :debug ]
2013-11-01 23:57:50 +01:00
@queue = [ ]
@mutex = Mutex . new
@signal = ConditionVariable . new
2013-11-28 11:04:53 -05:00
@runners = [ ruby_runner ]
2015-05-22 10:05:17 +10:00
if ENV [ " QUNIT " ] == " 1 "
@runners << javascript_runner
else
puts " Skipping JS tests, run them in the browser at /qunit or add QUNIT=1 to env "
end
2013-11-05 11:01:17 +01:00
end
2013-11-01 23:57:50 +01:00
2013-11-05 11:01:17 +01:00
def run
2013-11-01 23:57:50 +01:00
Signal . trap ( " HUP " ) { stop_runners ; exit }
Signal . trap ( " INT " ) { stop_runners ; exit }
ensure_all_specs_will_run
start_runners
start_service_queue
listen_for_changes
puts " Press [ENTER] to stop the current run "
while @runners . any? ( & :running? )
STDIN . gets
process_queue
end
rescue = > e
fail ( e , " failed in run " )
ensure
stop_runners
end
private
def ruby_runner
if ENV [ " SPORK " ]
require " autospec/spork_runner "
Autospec :: SporkRunner . new
else
require " autospec/simple_runner "
Autospec :: SimpleRunner . new
end
end
def javascript_runner
require " autospec/qunit_runner "
Autospec :: QunitRunner . new
end
def ensure_all_specs_will_run
2013-11-05 11:01:17 +01:00
puts " @@@@@@@@@@@@ ensure_all_specs_will_run " if @debug
2013-11-01 23:57:50 +01:00
@runners . each do | runner |
2014-08-15 03:24:55 +05:30
@queue << [ 'spec' , 'spec' , runner ] unless @queue . any? { | _ , s , r | s == " spec " && r == runner }
2013-11-01 23:57:50 +01:00
end
end
[ :start , :stop , :abort ] . each do | verb |
define_method ( " #{ verb } _runners " ) do
2013-11-05 11:01:17 +01:00
puts " @@@@@@@@@@@@ #{ verb } _runners " if @debug
2013-11-01 23:57:50 +01:00
@runners . each ( & verb )
end
end
def start_service_queue
2013-11-05 11:01:17 +01:00
puts " @@@@@@@@@@@@ start_service_queue " if @debug
2013-11-01 23:57:50 +01:00
Thread . new do
while true
thread_loop
end
end
end
# the main loop, will run the specs in the queue till one fails or the queue is empty
def thread_loop
2013-11-05 11:01:17 +01:00
puts " @@@@@@@@@@@@ thread_loop " if @debug
2013-11-01 23:57:50 +01:00
@mutex . synchronize do
current = @queue . first
last_failed = false
last_failed = process_spec ( current ) if current
# stop & wait for the queue to have at least one item or when there's been a failure
2013-11-05 11:01:17 +01:00
if @debug
puts " @@@@@@@@@@@@ waiting because... "
puts " @@@@@@@@@@@@ ...current spec has failed " if last_failed
puts " @@@@@@@@@@@@ ...queue is empty " if @queue . length == 0
end
2013-11-01 23:57:50 +01:00
@signal . wait ( @mutex ) if @queue . length == 0 || last_failed
end
rescue = > e
fail ( e , " failed in main loop " )
end
# will actually run the spec and check whether the spec has failed or not
def process_spec ( current )
2013-11-05 11:01:17 +01:00
puts " @@@@@@@@@@@@ process_spec --> #{ current } " if @debug
2013-11-01 23:57:50 +01:00
has_failed = false
# retrieve the instance of the runner
runner = current [ 2 ]
# actually run the spec (blocking call)
result = runner . run ( current [ 1 ] ) . to_i
if result == 0
2013-11-05 11:01:17 +01:00
puts " @@@@@@@@@@@@ success " if @debug
2013-11-01 23:57:50 +01:00
# remove the spec from the queue
@queue . shift
else
2013-11-05 11:01:17 +01:00
puts " @@@@@@@@@@@@ failure " if @debug
2013-11-01 23:57:50 +01:00
has_failed = true
if result > 0
focus_on_failed_tests ( current )
ensure_all_specs_will_run
end
end
has_failed
end
def focus_on_failed_tests ( current )
2013-11-05 11:01:17 +01:00
puts " @@@@@@@@@@@@ focus_on_failed_tests --> #{ current } " if @debug
2013-11-01 23:57:50 +01:00
runner = current [ 2 ]
# we only want 1 focus in the queue
@queue . shift if current [ 0 ] == " focus "
# focus on the first 10 failed specs
failed_specs = runner . failed_specs [ 0 .. 10 ]
2016-07-08 12:54:38 +10:00
puts " @@@@@@@@@@@@ failed_specs --> #{ failed_specs } " if @debug
# try focus tag
if failed_specs . length > 0
filename , _ = failed_specs [ 0 ] . split ( " : " )
if filename
spec = File . read ( filename )
start , _ = spec . split ( / \ S* # focus \ S*$ / )
if start . length < spec . length
line = start . scan ( / \ n / ) . length + 1
puts " Found # focus tag on line #{ line } ! "
failed_specs = [ " #{ filename } : #{ line + 1 } " ]
end
end
end
2013-11-01 23:57:50 +01:00
# focus on the failed specs
@queue . unshift [ " focus " , failed_specs . join ( " " ) , runner ] if failed_specs . length > 0
end
2013-11-05 11:01:17 +01:00
def listen_for_changes
puts " @@@@@@@@@@@@ listen_for_changes " if @debug
2013-11-01 23:57:50 +01:00
options = {
2015-11-02 13:24:24 +11:00
ignore : / ^lib \/ autospec / ,
2013-11-01 23:57:50 +01:00
}
2013-11-05 11:01:17 +01:00
if @opts [ :force_polling ]
2013-11-01 23:57:50 +01:00
options [ :force_polling ] = true
2013-11-05 11:01:17 +01:00
options [ :latency ] = @opts [ :latency ] || 3
2013-11-01 23:57:50 +01:00
end
2015-11-02 13:24:24 +11:00
path = File . expand_path ( File . dirname ( __FILE__ ) + " ../../.. " )
# to speed up boot we use a thread
2015-12-17 13:17:24 +11:00
[ " spec " , " lib " , " app " , " config " , " test " , " vendor " , " plugins " ] . each do | watch |
puts " @@@@@@@@@ Listen to #{ path } / #{ watch } #{ options } " if @debug
Thread . new do
begin
Listen . to ( " #{ path } / #{ watch } " , options ) do | modified , added , _ |
paths = [ modified , added ] . flatten
paths . compact!
paths . map! { | long | long [ ( path . length + 1 ) .. - 1 ] }
process_change ( paths )
end
rescue = > e
puts " FAILED to listen on changes to #{ path } / #{ watch } "
puts e
2015-11-02 13:24:24 +11:00
end
2013-11-01 23:57:50 +01:00
end
end
2015-11-02 13:24:24 +11:00
2013-11-01 23:57:50 +01:00
end
def process_change ( files )
return if files . length == 0
2013-11-05 11:01:17 +01:00
puts " @@@@@@@@@@@@ process_change --> #{ files } " if @debug
2013-11-01 23:57:50 +01:00
specs = [ ]
hit = false
files . each do | file |
@runners . each do | runner |
# reloaders
runner . reloaders . each do | k |
if k . match ( file )
2013-11-05 11:01:17 +01:00
puts " @@@@@@@@@@@@ #{ file } matched a reloader for #{ runner } " if @debug
2013-11-01 23:57:50 +01:00
runner . reload
return
end
end
# watchers
runner . watchers . each do | k , v |
if m = k . match ( file )
2013-11-05 11:01:17 +01:00
puts " @@@@@@@@@@@@ #{ file } matched a watcher for #{ runner } " if @debug
2013-11-01 23:57:50 +01:00
hit = true
spec = v ? ( v . arity == 1 ? v . call ( m ) : v . call ) : file
specs << [ file , spec , runner ] if File . exists? ( spec ) || Dir . exists? ( spec )
end
end
end
# special watcher for styles/templates
2014-08-15 03:24:55 +05:30
Autospec :: ReloadCss :: WATCHERS . each do | k , _ |
2013-11-01 23:57:50 +01:00
matches = [ ]
matches << file if k . match ( file )
Autospec :: ReloadCss . run_on_change ( matches ) if matches . present?
end
end
queue_specs ( specs ) if hit
rescue = > e
fail ( e , " failed in watcher " )
end
def queue_specs ( specs )
2013-11-05 11:01:17 +01:00
puts " @@@@@@@@@@@@ queue_specs --> #{ specs } " if @debug
2013-11-01 23:57:50 +01:00
if specs . length == 0
locked = @mutex . try_lock
if locked
@signal . signal
@mutex . unlock
end
return
else
abort_runners
end
2013-11-05 11:01:17 +01:00
puts " @@@@@@@@@@@@ waiting for the mutex " if @debug
2013-11-01 23:57:50 +01:00
@mutex . synchronize do
2013-11-05 11:01:17 +01:00
puts " @@@@@@@@@@@@ queueing specs " if @debug
puts " @@@@@@@@@@@@ #{ @queue } " if @debug
2013-11-01 23:57:50 +01:00
specs . each do | file , spec , runner |
# make sure there's no other instance of this spec in the queue
2014-08-15 03:24:55 +05:30
@queue . delete_if { | _ , s , r | s . strip == spec . strip && r == runner }
2013-11-01 23:57:50 +01:00
# deal with focused specs
if @queue . first && @queue . first [ 0 ] == " focus "
focus = @queue . shift
@queue . unshift ( [ file , spec , runner ] )
if focus [ 1 ] . include? ( spec ) || file != spec
@queue . unshift ( focus )
end
else
@queue . unshift ( [ file , spec , runner ] )
end
end
2013-11-05 11:01:17 +01:00
puts " @@@@@@@@@@@@ specs queued " if @debug
puts " @@@@@@@@@@@@ #{ @queue } " if @debug
2013-11-01 23:57:50 +01:00
@signal . signal
end
end
def process_queue
2013-11-05 11:01:17 +01:00
puts " @@@@@@@@@@@@ process_queue " if @debug
2013-11-01 23:57:50 +01:00
if @queue . length == 0
2013-11-05 11:01:17 +01:00
puts " @@@@@@@@@@@@ queue is empty... " if @debug
2013-11-01 23:57:50 +01:00
ensure_all_specs_will_run
@signal . signal
else
current = @queue . first
runner = current [ 2 ]
specs = runner . failed_specs
puts
puts
if specs . length == 0
2013-11-05 11:01:17 +01:00
puts " No specs have failed yet! "
2013-11-01 23:57:50 +01:00
puts
else
puts " The following specs have failed: "
specs . each { | s | puts s }
puts
specs = specs . map { | s | [ s , s , runner ] }
queue_specs ( specs )
end
end
end
def fail ( exception , message = nil )
puts message if message
puts exception . message
puts exception . backtrace . join ( " \n " )
end
end