diff options
-rw-r--r-- | Gemfile | 1 | ||||
-rw-r--r-- | Gemfile.lock | 10 | ||||
-rw-r--r-- | activesupport/lib/active_support.rb | 1 | ||||
-rw-r--r-- | activesupport/lib/active_support/file_evented_update_checker.rb | 67 | ||||
-rw-r--r-- | activesupport/test/file_evented_update_checker_test.rb | 21 | ||||
-rw-r--r-- | activesupport/test/file_update_checker_test.rb | 107 | ||||
-rw-r--r-- | activesupport/test/file_update_checker_with_enumerable_test_cases.rb | 110 | ||||
-rw-r--r-- | railties/lib/rails/application/configuration.rb | 2 | ||||
-rw-r--r-- | railties/lib/rails/generators/rails/app/templates/Gemfile | 3 |
9 files changed, 218 insertions, 104 deletions
@@ -45,6 +45,7 @@ end # Active Support. gem 'dalli', '>= 2.2.1' +gem 'listen', '~> 3.0.3' # Active Job. group :job do diff --git a/Gemfile.lock b/Gemfile.lock index 4d58065000..b3225dedea 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -212,6 +212,9 @@ GEM delayed_job (>= 3.0, < 5) erubis (2.7.0) execjs (2.6.0) + ffi (1.9.10) + ffi (1.9.10-x64-mingw32) + ffi (1.9.10-x86-mingw32) hitimes (1.2.3) hitimes (1.2.3-x86-mingw32) i18n (0.7.0) @@ -219,6 +222,9 @@ GEM kindlerb (0.1.1) mustache nokogiri + listen (3.0.3) + rb-fsevent (>= 0.9.3) + rb-inotify (>= 0.9) loofah (2.0.3) nokogiri (>= 1.5.9) metaclass (0.0.4) @@ -252,6 +258,9 @@ GEM rails-html-sanitizer (1.0.2) loofah (~> 2.0) rake (10.4.2) + rb-fsevent (0.9.6) + rb-inotify (0.9.5) + ffi (>= 0.5.0) rdoc (4.2.0) redcarpet (3.2.3) redis (3.2.1) @@ -333,6 +342,7 @@ DEPENDENCIES jquery-rails! json kindlerb (= 0.1.1) + listen (~> 3.0.3) mail! minitest (< 5.3.4) mocha (~> 0.14) 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 diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb index 785671f70b..6d68eea220 100644 --- a/railties/lib/rails/application/configuration.rb +++ b/railties/lib/rails/application/configuration.rb @@ -44,7 +44,7 @@ module Rails @railties_order = [:all] @relative_url_root = ENV["RAILS_RELATIVE_URL_ROOT"] @reload_classes_only_on_change = true - @file_watcher = ActiveSupport::FileUpdateChecker + @file_watcher = (defined?(Listen) && Listen::Adapter.select()!=Listen::Adapter::Polling)? ActiveSupport::FileEventedUpdateChecker : ActiveSupport::FileUpdateChecker @exceptions_app = nil @autoflush_log = true @log_formatter = ActiveSupport::Logger::SimpleFormatter.new diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile index 975be07622..87ef60288d 100644 --- a/railties/lib/rails/generators/rails/app/templates/Gemfile +++ b/railties/lib/rails/generators/rails/app/templates/Gemfile @@ -53,3 +53,6 @@ end # Windows does not include zoneinfo files, so bundle the tzinfo-data gem gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] + +# Uncomment this, to increase the performance +# gem 'listen', '~> 3.0.3' |