aboutsummaryrefslogtreecommitdiffstats
path: root/activesupport
diff options
context:
space:
mode:
authorXavier Noria <fxn@hashref.com>2015-10-12 20:41:14 +0200
committerXavier Noria <fxn@hashref.com>2015-11-08 22:49:49 -0800
commit785adabc4b8d892b6e06fca2f259e9c5147e9ca5 (patch)
tree3f6aa360466928f1ecbe59870944d879abb454c4 /activesupport
parent823d3a8de0ccfe2301e51ac1e06e106035edb8e6 (diff)
downloadrails-785adabc4b8d892b6e06fca2f259e9c5147e9ca5.tar.gz
rails-785adabc4b8d892b6e06fca2f259e9c5147e9ca5.tar.bz2
rails-785adabc4b8d892b6e06fca2f259e9c5147e9ca5.zip
implements an evented file update checker [Puneet Agarwal]
This is the implementation of the file update checker written by Puneet Agarwal for GSoC 2015 (except for the tiny version of the listen gem, which was 3.0.2 in the original patch). Puneet's branch became too out of sync with upstream. This is the final work in one single clean commit. Credit goes in the first line using a convention understood by the contrib app.
Diffstat (limited to 'activesupport')
-rw-r--r--activesupport/lib/active_support.rb1
-rw-r--r--activesupport/lib/active_support/file_evented_update_checker.rb67
-rw-r--r--activesupport/test/file_evented_update_checker_test.rb21
-rw-r--r--activesupport/test/file_update_checker_test.rb107
-rw-r--r--activesupport/test/file_update_checker_with_enumerable_test_cases.rb110
5 files changed, 203 insertions, 103 deletions
diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb
index 63277a65b4..3a2a7d28cb 100644
--- a/activesupport/lib/active_support.rb
+++ b/activesupport/lib/active_support.rb
@@ -34,6 +34,7 @@ module ActiveSupport
autoload :Dependencies
autoload :DescendantsTracker
autoload :FileUpdateChecker
+ autoload :FileEventedUpdateChecker
autoload :LogSubscriber
autoload :Notifications
diff --git a/activesupport/lib/active_support/file_evented_update_checker.rb b/activesupport/lib/active_support/file_evented_update_checker.rb
new file mode 100644
index 0000000000..d45576bb00
--- /dev/null
+++ b/activesupport/lib/active_support/file_evented_update_checker.rb
@@ -0,0 +1,67 @@
+require 'listen'
+
+module ActiveSupport
+ class FileEventedUpdateChecker
+ attr_reader :listener
+ def initialize(files, directories={}, &block)
+ @files = files.map { |f| File.expand_path(f)}.to_set
+ @dirs = Hash.new
+ directories.each do |key,value|
+ @dirs[File.expand_path(key)] = Array(value) if !Array(value).empty?
+ end
+ @block = block
+ @modified = false
+ watch_dirs = base_directories
+ @listener = Listen.to(*watch_dirs,&method(:changed)) if !watch_dirs.empty?
+ @listener.start if @listener
+ end
+
+ def updated?
+ @modified
+ end
+
+ def execute
+ @block.call
+ ensure
+ @modified = false
+ end
+
+ def execute_if_updated
+ if updated?
+ execute
+ true
+ else
+ false
+ end
+ end
+
+ private
+
+ def watching?(file)
+ return true if @files.include?(file)
+ cfile = file
+ while !cfile.eql? "/"
+ cfile = File.expand_path("#{cfile}/..")
+ if !@dirs[cfile].nil? and file.end_with?(*(@dirs[cfile].map {|ext| ".#{ext.to_s}"}))
+ return true
+ end
+ end
+ false
+ end
+
+ def changed(modified, added, removed)
+ return if updated?
+ if (modified + added + removed).any? { |f| watching? f }
+ @modified = true
+ end
+ end
+
+ def base_directories
+ (@files.map { |f| existing_parent(File.expand_path("#{f}/..")) } + @dirs.keys.map {|dir| existing_parent(dir)}).uniq
+ end
+
+ def existing_parent(path)
+ File.exist?(path) ? path : existing_parent(File.expand_path("#{path}/.."))
+ end
+ end
+end
diff --git a/activesupport/test/file_evented_update_checker_test.rb b/activesupport/test/file_evented_update_checker_test.rb
new file mode 100644
index 0000000000..09087738dc
--- /dev/null
+++ b/activesupport/test/file_evented_update_checker_test.rb
@@ -0,0 +1,21 @@
+require 'abstract_unit'
+require 'fileutils'
+require 'thread'
+require 'file_update_checker_with_enumerable_test_cases'
+
+MTIME_FIXTURES_PATH = File.expand_path("../fixtures", __FILE__)
+
+class FileEventedUpdateCheckerWithEnumerableTest < ActiveSupport::TestCase
+ include FileUpdateCheckerWithEnumerableTestCases
+ def build_new_watcher(files, dirs={}, &block)
+ ActiveSupport::FileEventedUpdateChecker.new(files, dirs, &block)
+ end
+
+ def test_modified_should_become_true_when_watched_file_is_updated
+ watcher = ActiveSupport::FileEventedUpdateChecker.new(FILES){ i += 1 }
+ assert_equal watcher.updated?, false
+ FileUtils.rm(FILES)
+ sleep 1
+ assert_equal watcher.updated?, true
+ end
+end
diff --git a/activesupport/test/file_update_checker_test.rb b/activesupport/test/file_update_checker_test.rb
index bd1df0f858..c61193d133 100644
--- a/activesupport/test/file_update_checker_test.rb
+++ b/activesupport/test/file_update_checker_test.rb
@@ -1,112 +1,13 @@
require 'abstract_unit'
require 'fileutils'
require 'thread'
+require 'file_update_checker_with_enumerable_test_cases'
MTIME_FIXTURES_PATH = File.expand_path("../fixtures", __FILE__)
class FileUpdateCheckerWithEnumerableTest < ActiveSupport::TestCase
- FILES = %w(1.txt 2.txt 3.txt)
-
- def setup
- FileUtils.mkdir_p("tmp_watcher")
- FileUtils.touch(FILES)
- end
-
- def teardown
- FileUtils.rm_rf("tmp_watcher")
- FileUtils.rm_rf(FILES)
- end
-
- def test_should_not_execute_the_block_if_no_paths_are_given
- i = 0
- checker = ActiveSupport::FileUpdateChecker.new([]){ i += 1 }
- checker.execute_if_updated
- assert_equal 0, i
- end
-
- def test_should_not_invoke_the_block_if_no_file_has_changed
- i = 0
- checker = ActiveSupport::FileUpdateChecker.new(FILES){ i += 1 }
- 5.times { assert !checker.execute_if_updated }
- assert_equal 0, i
- end
-
- def test_should_invoke_the_block_if_a_file_has_changed
- i = 0
- checker = ActiveSupport::FileUpdateChecker.new(FILES){ i += 1 }
- sleep(1)
- FileUtils.touch(FILES)
- assert checker.execute_if_updated
- assert_equal 1, i
- end
-
- def test_should_be_robust_enough_to_handle_deleted_files
- i = 0
- checker = ActiveSupport::FileUpdateChecker.new(FILES){ i += 1 }
- FileUtils.rm(FILES)
- assert checker.execute_if_updated
- assert_equal 1, i
- end
-
- def test_should_be_robust_to_handle_files_with_wrong_modified_time
- i = 0
- now = Time.now
- time = Time.mktime(now.year + 1, now.month, now.day) # wrong mtime from the future
- File.utime time, time, FILES[2]
-
- checker = ActiveSupport::FileUpdateChecker.new(FILES){ i += 1 }
-
- sleep(1)
- FileUtils.touch(FILES[0..1])
-
- assert checker.execute_if_updated
- assert_equal 1, i
- end
-
- def test_should_cache_updated_result_until_execute
- i = 0
- checker = ActiveSupport::FileUpdateChecker.new(FILES){ i += 1 }
- assert !checker.updated?
-
- sleep(1)
- FileUtils.touch(FILES)
-
- assert checker.updated?
- checker.execute
- assert !checker.updated?
- end
-
- def test_should_invoke_the_block_if_a_watched_dir_changed_its_glob
- i = 0
- checker = ActiveSupport::FileUpdateChecker.new([], "tmp_watcher" => [:txt]){ i += 1 }
- FileUtils.cd "tmp_watcher" do
- FileUtils.touch(FILES)
- end
- assert checker.execute_if_updated
- assert_equal 1, i
- end
-
- def test_should_not_invoke_the_block_if_a_watched_dir_changed_its_glob
- i = 0
- checker = ActiveSupport::FileUpdateChecker.new([], "tmp_watcher" => :rb){ i += 1 }
- FileUtils.cd "tmp_watcher" do
- FileUtils.touch(FILES)
- end
- assert !checker.execute_if_updated
- assert_equal 0, i
- end
-
- def test_should_not_block_if_a_strange_filename_used
- FileUtils.mkdir_p("tmp_watcher/valid,yetstrange,path,")
- FileUtils.touch(FILES.map { |file_name| "tmp_watcher/valid,yetstrange,path,/#{file_name}" })
-
- test = Thread.new do
- ActiveSupport::FileUpdateChecker.new([],"tmp_watcher/valid,yetstrange,path," => :txt) { i += 1 }
- Thread.exit
- end
- test.priority = -1
- test.join(5)
-
- assert !test.alive?
+ include FileUpdateCheckerWithEnumerableTestCases
+ def build_new_watcher(files, dirs={}, &block)
+ ActiveSupport::FileUpdateChecker.new(files, dirs, &block)
end
end
diff --git a/activesupport/test/file_update_checker_with_enumerable_test_cases.rb b/activesupport/test/file_update_checker_with_enumerable_test_cases.rb
new file mode 100644
index 0000000000..cd1f12d42f
--- /dev/null
+++ b/activesupport/test/file_update_checker_with_enumerable_test_cases.rb
@@ -0,0 +1,110 @@
+module FileUpdateCheckerWithEnumerableTestCases
+ FILES = %w(1.txt 2.txt 3.txt)
+
+ def setup
+ FileUtils.mkdir_p("tmp_watcher")
+ FileUtils.touch(FILES)
+ end
+
+ def teardown
+ FileUtils.rm_rf("tmp_watcher")
+ FileUtils.rm_rf(FILES)
+ end
+
+ def test_should_not_execute_the_block_if_no_paths_are_given
+ i = 0
+ checker = build_new_watcher([]){ i += 1}
+ checker.execute_if_updated
+ assert_equal 0, i
+ end
+
+ def test_should_not_invoke_the_block_if_no_file_has_changed
+ i = 0
+ checker = build_new_watcher(FILES){ i += 1 }
+ 5.times { assert !checker.execute_if_updated }
+ assert_equal 0, i
+ end
+
+ def test_should_invoke_the_block_if_a_file_has_changed
+ i = 0
+ checker = build_new_watcher(FILES){ i += 1 }
+ sleep(1)
+ FileUtils.touch(FILES)
+ sleep(1) #extra
+ assert checker.execute_if_updated
+ assert_equal 1, i
+ end
+
+ def test_should_be_robust_enough_to_handle_deleted_files
+ i = 0
+ checker = build_new_watcher(FILES){ i += 1 }
+ FileUtils.rm(FILES)
+ sleep(1) #extra
+ assert checker.execute_if_updated
+ assert_equal 1, i
+ end
+
+ def test_should_be_robust_to_handle_files_with_wrong_modified_time
+ i = 0
+ now = Time.now
+ time = Time.mktime(now.year + 1, now.month, now.day) # wrong mtime from the future
+ File.utime time, time, FILES[2]
+
+ checker = build_new_watcher(FILES){ i += 1 }
+
+ sleep(1)
+ FileUtils.touch(FILES[0..1])
+ sleep(1) #extra
+ assert checker.execute_if_updated
+ assert_equal 1, i
+ end
+
+ def test_should_cache_updated_result_until_execute
+ i = 0
+ checker = build_new_watcher(FILES){ i += 1 }
+ assert !checker.updated?
+
+ sleep(1)
+ FileUtils.touch(FILES)
+ sleep(1) #extra
+ assert checker.updated?
+ checker.execute
+ assert !checker.updated?
+ end
+
+ def test_should_invoke_the_block_if_a_watched_dir_changed_its_glob
+ i = 0
+ checker = build_new_watcher([], "tmp_watcher" => [:txt]){ i += 1 }
+ FileUtils.cd "tmp_watcher" do
+ FileUtils.touch(FILES)
+ end
+ sleep(1) #extra
+ assert checker.execute_if_updated
+ assert_equal 1, i
+ end
+
+ def test_should_not_invoke_the_block_if_a_watched_dir_changed_its_glob
+ i = 0
+ checker = build_new_watcher([], "tmp_watcher" => :rb){ i += 1 }
+ FileUtils.cd "tmp_watcher" do
+ FileUtils.touch(FILES)
+ end
+ sleep(1) #extra
+ assert !checker.execute_if_updated
+ assert_equal 0, i
+ end
+
+ def test_should_not_block_if_a_strange_filename_used
+ FileUtils.mkdir_p("tmp_watcher/valid,yetstrange,path,")
+ FileUtils.touch(FILES.map { |file_name| "tmp_watcher/valid,yetstrange,path,/#{file_name}" })
+
+ test = Thread.new do
+ build_new_watcher([],"tmp_watcher/valid,yetstrange,path," => :txt) { i += 1 }
+ Thread.exit
+ end
+ test.priority = -1
+ test.join(5)
+
+ assert !test.alive?
+ end
+end