aboutsummaryrefslogblamecommitdiffstats
path: root/activesupport/test/file_watcher_test.rb
blob: 027453f86ad2c34ecbb901ebeebf1381b612f1e4 (plain) (tree)
1
2
3
4
5
                       

                   
                 
 





















                                                                                  









                                                









































                                                                                                                                              



















































                                                                        


                      











                                                       




                                                     
             








                                     


                                           
 


                                                              


                  


                                             


                  




















                                          


























                                                                                                                                                      



                                                   
             










                                                              
             







                                                                                                            
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