diff --git a/lib/puppet/util/autoload.rb b/lib/puppet/util/autoload.rb index 51fdaadad..ceaabe46a 100644 --- a/lib/puppet/util/autoload.rb +++ b/lib/puppet/util/autoload.rb @@ -1,162 +1,162 @@ require 'puppet/util/warnings' require 'puppet/util/cacher' # Autoload paths, either based on names or all at once. class Puppet::Util::Autoload require 'puppet/util/autoload/file_cache' include Puppet::Util include Puppet::Util::Warnings include Puppet::Util::Cacher include Puppet::Util::Autoload::FileCache @autoloaders = {} @loaded = [] class << self attr_reader :autoloaders private :autoloaders end # Send [], []=, and :clear to the @autloaders hash Puppet::Util.classproxy self, :autoloaders, "[]", "[]=" # List all loaded files. def self.list_loaded @loaded.sort { |a,b| a[0] <=> b[0] }.collect do |path, hash| "%s: %s" % [path, hash[:file]] end end # Has a given path been loaded? This is used for testing whether a # changed file should be loaded or just ignored. This is only # used in network/client/master, when downloading plugins, to # see if a given plugin is currently loaded and thus should be # reloaded. def self.loaded?(path) path = path.to_s.sub(/\.rb$/, '') @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 self.loaded(file) $" << file + ".rb" unless $".include?(file) @loaded << file unless @loaded.include?(file) end attr_accessor :object, :path, :objwarn, :wrap def initialize(obj, path, options = {}) @path = path.to_s if @path !~ /^\w/ raise ArgumentError, "Autoload paths cannot be fully qualified" end @object = obj self.class[obj] = self options.each do |opt, value| opt = opt.intern if opt.is_a? String begin self.send(opt.to_s + "=", value) rescue NoMethodError raise ArgumentError, "%s is not a valid option" % opt end end unless defined? @wrap @wrap = true end end # Load a single plugin by name. We use 'load' here so we can reload a # given plugin. def load(name) return false if named_file_missing?(name) path = name.to_s + ".rb" searchpath.each do |dir| file = File.join(dir, path) next unless file_exist?(file) begin Kernel.load file, @wrap name = symbolize(name) loaded name, file return true rescue SystemExit,NoMemoryError raise rescue Exception => detail # I have no idea what's going on here, but different versions # of ruby are raising different errors on missing files. unless detail.to_s =~ /^no such file/i warn "Could not autoload %s: %s" % [name, detail] if Puppet[:trace] puts detail.backtrace end end return named_file_is_missing(name) end end return named_file_is_missing(name) end # Mark the named object as loaded. Note that this supports unqualified # queries, while we store the result as a qualified query in the class. def loaded(name, file) self.class.loaded(File.join(@path, name.to_s)) end # Indicate whether the specfied plugin has been loaded. def loaded?(name) self.class.loaded?(File.join(@path, name.to_s)) end # Load all instances that we can. This uses require, rather than load, # so that already-loaded files don't get reloaded unnecessarily. def loadall # Load every instance of everything we can find. searchpath.each do |dir| Dir.glob("#{dir}/*.rb").each do |file| name = File.basename(file).sub(".rb", '').intern next if loaded?(name) begin Kernel.require file loaded(name, file) rescue SystemExit,NoMemoryError raise rescue Exception => detail if Puppet[:trace] puts detail.backtrace end warn "Could not autoload %s: %s" % [file.inspect, detail] end end end end # The list of directories to search through for loadable plugins. # We have to hard-code the ttl because this library is used by # so many other classes it's hard to get the load-order such that # the defaults load before this. cached_attr(:searchpath, :ttl => 15) do search_directories.collect { |d| File.join(d, @path) }.find_all { |d| FileTest.directory?(d) } end def module_directories # We have to require this late in the process because otherwise we might have # load order issues. require 'puppet/node/environment' Puppet::Node::Environment.new.modulepath.collect do |dir| Dir.entries(dir).reject { |f| f =~ /^\./ }.collect { |f| File.join(dir, f) } end.flatten.collect { |d| [File.join(d, "plugins"), File.join(d, "lib")] }.flatten.find_all do |d| FileTest.directory?(d) end end - def search_directories - [module_directories, Puppet[:libdir], $:].flatten + def search_directories(dummy_argument=:work_arround_for_ruby_GC_bug) + [module_directories, Puppet[:libdir].split(File::PATH_SEPARATOR), $:].flatten end end diff --git a/spec/unit/util/autoload.rb b/spec/unit/util/autoload.rb index 4e1384246..220cb5f68 100755 --- a/spec/unit/util/autoload.rb +++ b/spec/unit/util/autoload.rb @@ -1,132 +1,133 @@ #!/usr/bin/env ruby Dir.chdir(File.dirname(__FILE__)) { (s = lambda { |f| File.exist?(f) ? require(f) : Dir.chdir("..") { s.call(f) } }).call("spec/spec_helper.rb") } require 'puppet/util/autoload' describe Puppet::Util::Autoload do before do @autoload = Puppet::Util::Autoload.new("foo", "tmp") @autoload.stubs(:eachdir).yields "/my/dir" end it "should use the Cacher module" do Puppet::Util::Autoload.ancestors.should be_include(Puppet::Util::Cacher) end it "should use a ttl of 15 for the search path" do Puppet::Util::Autoload.attr_ttl(:searchpath).should == 15 end describe "when building the search path" do it "should collect all of the plugins and lib directories that exist in the current environment's module path" do Puppet.settings.expects(:value).with(:environment).returns "foo" Puppet.settings.expects(:value).with(:modulepath, :foo).returns "/a:/b:/c" Dir.expects(:entries).with("/a").returns %w{one two} Dir.expects(:entries).with("/b").returns %w{one two} FileTest.stubs(:directory?).returns false FileTest.expects(:directory?).with("/a").returns true FileTest.expects(:directory?).with("/b").returns true %w{/a/one/plugins /a/two/lib /b/one/plugins /b/two/lib}.each do |d| FileTest.expects(:directory?).with(d).returns true end @autoload.module_directories.should == %w{/a/one/plugins /a/two/lib /b/one/plugins /b/two/lib} end it "should not look for lib directories in directories starting with '.'" do Puppet.settings.expects(:value).with(:environment).returns "foo" Puppet.settings.expects(:value).with(:modulepath, :foo).returns "/a" Dir.expects(:entries).with("/a").returns %w{. ..} FileTest.expects(:directory?).with("/a").returns true FileTest.expects(:directory?).with("/a/./lib").never FileTest.expects(:directory?).with("/a/./plugins").never FileTest.expects(:directory?).with("/a/../lib").never FileTest.expects(:directory?).with("/a/../plugins").never @autoload.module_directories end it "should include the module directories, the Puppet libdir, and all of the Ruby load directories" do + Puppet.stubs(:[]).with(:libdir).returns(%w{/libdir1 /lib/dir/two /third/lib/dir}.join(File::PATH_SEPARATOR)) @autoload.expects(:module_directories).returns %w{/one /two} - @autoload.search_directories.should == ["/one", "/two", Puppet[:libdir], $:].flatten + @autoload.search_directories.should == %w{/one /two /libdir1 /lib/dir/two /third/lib/dir} + $: end it "should include in its search path all of the search directories that have a subdirectory matching the autoload path" do @autoload = Puppet::Util::Autoload.new("foo", "loaddir") @autoload.expects(:search_directories).returns %w{/one /two /three} FileTest.expects(:directory?).with("/one/loaddir").returns true FileTest.expects(:directory?).with("/two/loaddir").returns false FileTest.expects(:directory?).with("/three/loaddir").returns true @autoload.searchpath.should == ["/one/loaddir", "/three/loaddir"] end end it "should include its FileCache module" do Puppet::Util::Autoload.ancestors.should be_include(Puppet::Util::Autoload::FileCache) end describe "when loading a file" do before do @autoload.stubs(:searchpath).returns %w{/a} end [RuntimeError, LoadError, SyntaxError].each do |error| it "should not die an if a #{error.to_s} exception is thrown" do @autoload.stubs(:file_exist?).returns true Kernel.expects(:load).raises error @autoload.load("foo") end end it "should skip files that it knows are missing" do @autoload.expects(:named_file_missing?).with("foo").returns true @autoload.expects(:eachdir).never @autoload.load("foo") end it "should register that files are missing if they cannot be found" do @autoload.load("foo") @autoload.should be_named_file_missing("foo") end it "should register loaded files with the main loaded file list so they are not reloaded by ruby" do @autoload.stubs(:file_exist?).returns true Kernel.stubs(:load) @autoload.load("myfile") $".should be_include("tmp/myfile.rb") end end describe "when loading all files" do before do @autoload.stubs(:searchpath).returns %w{/a} Dir.stubs(:glob).returns "/path/to/file.rb" @autoload.class.stubs(:loaded?).returns(false) end [RuntimeError, LoadError, SyntaxError].each do |error| it "should not die an if a #{error.to_s} exception is thrown" do Kernel.expects(:require).raises error lambda { @autoload.loadall }.should_not raise_error end end it "should require the full path to the file" do Kernel.expects(:require).with("/path/to/file.rb") @autoload.loadall end end end