Skip to content

Commit

Permalink
(#12126) Implement reloading changed files
Browse files Browse the repository at this point in the history
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
pcarlisle committed Feb 28, 2012
1 parent 432426f commit 4e8c368
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 4 deletions.
23 changes: 19 additions & 4 deletions lib/puppet/util/autoload.rb
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
54 changes: 54 additions & 0 deletions spec/unit/util/autoload_spec.rb
Expand Up @@ -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.