require 'abstract_unit'
require 'fssm'
require "fileutils"
require "timeout"
class FileWatcherTest < ActiveSupport::TestCase
class DumbBackend < ActiveSupport::FileWatcher::Backend
end
def setup
@watcher = ActiveSupport::FileWatcher.new
# In real life, the backend would take the path and use it to observe the file
# system. In our case, we will manually trigger the events for unit testing,
# so we can pass any path.
@backend = DumbBackend.new("RAILS_WOOT", @watcher)
@payload = []
@watcher.watch %r{^app/assets/.*\.scss$} do |pay|
pay.each do |status, files|
files.sort!
end
@payload << pay
end
end
def test_use_triple_equals
fw = ActiveSupport::FileWatcher.new
called = []
fw.watch("some_arbitrary_file.rb") do |file|
called << "omg"
end
fw.trigger(%w{ some_arbitrary_file.rb })
assert_equal ['omg'], called
end
def test_one_change
@backend.trigger("app/assets/main.scss" => :changed)
assert_equal({:changed => ["app/assets/main.scss"]}, @payload.first)
end
def test_multiple_changes
@backend.trigger("app/assets/main.scss" => :changed, "app/assets/javascripts/foo.coffee" => :changed)
assert_equal([{:changed => ["app/assets/main.scss"]}], @payload)
end
def test_multiple_changes_match
@backend.trigger("app/assets/main.scss" => :changed, "app/assets/print.scss" => :changed, "app/assets/javascripts/foo.coffee" => :changed)
assert_equal([{:changed => ["app/assets/main.scss", "app/assets/print.scss"]}], @payload)
end
def test_multiple_state_changes
@backend.trigger("app/assets/main.scss" => :created, "app/assets/print.scss" => :changed)
assert_equal([{:changed => ["app/assets/print.scss"], :created => ["app/assets/main.scss"]}], @payload)
end
def test_more_blocks
payload = []
@watcher.watch %r{^config/routes\.rb$} do |pay|
payload << pay
end
@backend.trigger "config/routes.rb" => :changed
assert_equal [:changed => ["config/routes.rb"]], payload
assert_equal [], @payload
end
def test_overlapping_watchers
payload = []
@watcher.watch %r{^app/assets/main\.scss$} do |pay|
payload << pay
end
@backend.trigger "app/assets/print.scss" => :changed, "app/assets/main.scss" => :changed
assert_equal [:changed => ["app/assets/main.scss"]], payload
assert_equal [:changed => ["app/assets/main.scss", "app/assets/print.scss"]], @payload
end
end
module FSSM::Backends
class Polling
def initialize(options={})
@handlers = []
@latency = options[:latency] || 0.1
end
def add_handler(handler, preload=true)
handler.refresh(nil, true) if preload
@handlers << handler
end
def run
begin
loop do
start = Time.now.to_f
@handlers.each { |handler| handler.refresh }
nap_time = @latency - (Time.now.to_f - start)
sleep nap_time if nap_time > 0
end
rescue Interrupt
end
end
end
end
class FSSMFileWatcherTest < ActiveSupport::TestCase
class FSSMBackend < ActiveSupport::FileWatcher::Backend
def initialize(path, watcher)
super
monitor = FSSM::Monitor.new
monitor.path(path, '**/*') do |monitor|
monitor.update { |base, relative| trigger relative => :changed }
monitor.delete { |base, relative| trigger relative => :deleted }
monitor.create { |base, relative| trigger relative => :created }
end
@thread = Thread.new do
monitor.run
end
end
def stop
@thread.kill
end
end
def setup
Thread.abort_on_exception = true
@payload = []
@triggered = false
@watcher = ActiveSupport::FileWatcher.new
@path = path = File.expand_path("../tmp", __FILE__)
FileUtils.rm_rf path
create "app/assets/main.scss", true
create "app/assets/javascripts/foo.coffee", true
create "app/assets/print.scss", true
create "app/assets/videos.scss", true
@backend = FSSMBackend.new(path, @watcher)
@watcher.watch %r{^app/assets/.*\.scss$} do |pay|
pay.each do |status, files|
files.sort!
end
@payload << pay
trigger
end
end
def teardown
@backend.stop
Thread.abort_on_exception = false
end
def create(path, past = false)
wait(past) do
path = File.join(@path, path)
FileUtils.mkdir_p(File.dirname(path))
FileUtils.touch(path)
File.utime(Time.now - 100, Time.now - 100, path) if past
end
end
def change(path)
wait do
FileUtils.touch(File.join(@path, path))
end
end
def delete(path)
wait do
FileUtils.rm(File.join(@path, path))
end
end
def wait(past = false)
yield
return if past
begin
Timeout.timeout(1) do
sleep 0.05 until @triggered
end
rescue Timeout::Error
end
@triggered = false
end
def trigger
@triggered = true
end
def test_one_change
change "app/assets/main.scss"
assert_equal({:changed => ["app/assets/main.scss"]}, @payload.first)
end
def test_multiple_changes
change "app/assets/main.scss"
change "app/assets/javascripts/foo.coffee"
assert_equal([{:changed => ["app/assets/main.scss"]}], @payload)
end
def test_multiple_changes_match
change "app/assets/main.scss"
change "app/assets/print.scss"
change "app/assets/javascripts/foo.coffee"
assert_equal([{:changed => ["app/assets/main.scss"]}, {:changed => ["app/assets/print.scss"]}], @payload)
end
def test_multiple_state_changes
create "app/assets/new.scss"
change "app/assets/print.scss"
delete "app/assets/videos.scss"
assert_equal([{:created => ["app/assets/new.scss"]}, {:changed => ["app/assets/print.scss"]}, {:deleted => ["app/assets/videos.scss"]}], @payload)
end
def test_more_blocks
payload = []
@watcher.watch %r{^config/routes\.rb$} do |pay|
payload << pay
trigger
end
create "config/routes.rb"
assert_equal [{:created => ["config/routes.rb"]}], payload
assert_equal [], @payload
end
def test_overlapping_watchers
payload = []
@watcher.watch %r{^app/assets/main\.scss$} do |pay|
payload << pay
trigger
end
change "app/assets/main.scss"
change "app/assets/print.scss"
assert_equal [{:changed => ["app/assets/main.scss"]}], payload
assert_equal [{:changed => ["app/assets/main.scss"]}, {:changed => ["app/assets/print.scss"]}], @payload
end
end