diff options
Diffstat (limited to 'activesupport')
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 |