Permalink
Browse files

(#12126) Implement reloading changed files

This adds a reload_changed method to the autoloader. It checks all loaded
files for changes (based on the mtime of the files) and reload any paths that
have changed. Deleted files are still listed as loaded since there is no way
to actually unload code in Ruby.
  • Loading branch information...
1 parent 432426f commit 4e8c36867c229c80a2e4415edf7a3ecd820312b3 @pcarlisle pcarlisle committed Feb 13, 2012
Showing with 73 additions and 4 deletions.
  1. +19 −4 lib/puppet/util/autoload.rb
  2. +54 −0 spec/unit/util/autoload_spec.rb
@@ -13,12 +13,13 @@ class Puppet::Util::Autoload
@loaded = {}
class << self
- attr_reader :autoloaders, :loaded
+ attr_reader :autoloaders
+ attr_accessor :loaded
private :autoloaders, :loaded
# List all loaded files.
def list_loaded
- @loaded.keys.sort { |a,b| a[0] <=> b[0] }.collect do |path, hash|
+ loaded.keys.sort { |a,b| a[0] <=> b[0] }.collect do |path, hash|
"#{path}: #{hash[:file]}"
end
end
@@ -30,15 +31,25 @@ def list_loaded
# reloaded.
def loaded?(path)
path = path.to_s.sub(/\.rb$/, '')
- @loaded.include?(path)
+ loaded.include?(path)
end
# Save the fact that a given path has been loaded. This is so
# we can load downloaded plugins if they've already been loaded
# into memory.
def mark_loaded(name, file)
$" << name + ".rb" unless $".include?(name)
- @loaded[name] = [file, File.mtime(file)]
+ loaded[name] = [file, File.mtime(file)]
+ end
+
+ def changed?(name)
+ return true unless loaded.include?(name)
+ file, old_mtime = loaded[name]
+ begin
+ old_mtime != File.mtime(file)
+ rescue Errno::ENOENT
+ true
+ end
end
# Load a single plugin by name. We use 'load' here so we can reload a
@@ -73,6 +84,10 @@ def loadall(path)
end
end
+ def reload_changed
+ loaded.keys.each { |file| load_file(file) if changed?(file) }
+ end
+
def files_to_load(path)
search_directories.map {|dir| files_in_dir(dir, path) }.flatten.uniq
end
@@ -151,4 +151,58 @@
@autoload.loadall
end
end
+
+ describe "when reloading files" do
+ before :each do
+ @file_a = "/a/file.rb"
+ @file_b = "/b/file.rb"
+ @first_time = Time.utc(2010, 'jan', 1, 6, 30)
+ @second_time = @first_time + 60
+ end
+
+ after :each do
+ $LOADED_FEATURES.delete("a/file.rb")
+ $LOADED_FEATURES.delete("b/file.rb")
+ end
+
+ describe "in one directory" do
+ before :each do
+ @autoload.class.stubs(:searchpath).returns %w{/a}
+ File.expects(:mtime).with(@file_a).returns(@first_time)
+ @autoload.class.mark_loaded("file", @file_a)
+ end
+
+ it "should reload if mtime changes" do
+ File.stubs(:mtime).with(@file_a).returns(@first_time + 60)
+ File.stubs(:exist?).with(@file_a).returns true
+ Kernel.expects(:load).with(@file_a, optionally(anything))
+ @autoload.class.reload_changed
+ end
+
+ it "should do nothing if the file is deleted" do
+ File.expects(:mtime).with(@file_a).raises(Errno::ENOENT)
+ File.stubs(:exist?).with(@file_a).returns false
+ Kernel.expects(:load).never
+ @autoload.class.reload_changed
+ end
+ end
+
+ describe "in two directories" do
+ before :each do
+ @autoload.class.stubs(:searchpath).returns %w{/a /b}
+ File.expects(:mtime).with(@file_a).returns(@first_time)
+ @autoload.class.mark_loaded("file", @file_a)
+ end
+
+ it "should load b/file when a/file is deleted" do
+ File.stubs(:mtime).with(@file_a).raises(Errno::ENOENT)
+ File.stubs(:exist?).with(@file_a).returns false
+ File.stubs(:exist?).with(@file_b).returns true
+ File.stubs(:mtime).with(@file_b).returns @first_time
+ Kernel.expects(:load).with(@file_b, optionally(anything))
+ @autoload.class.reload_changed
+ @autoload.class.send(:loaded)["file"].should == [@file_b, @first_time]
+ end
+ end
+ end
end

0 comments on commit 4e8c368

Please sign in to comment.