diff --git a/lib/puppet/util/autoload.rb b/lib/puppet/util/autoload.rb index 5f13d936b..535d9ef2e 100644 --- a/lib/puppet/util/autoload.rb +++ b/lib/puppet/util/autoload.rb @@ -1,147 +1,146 @@ require 'puppet/util/warnings' # Autoload paths, either based on names or all at once. class Puppet::Util::Autoload include Puppet::Util include Puppet::Util::Warnings @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) @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) path = name.to_s + ".rb" eachdir do |dir| file = File.join(dir, path) next unless FileTest.exists?(file) begin Kernel.load file, @wrap name = symbolize(name) loaded name, file return true rescue LoadError => 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 false end end return false 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. eachdir do |dir| Dir.glob("#{dir}/*.rb").each do |file| name = File.basename(file).sub(".rb", '').intern next if loaded?(name) - rubypath = File.join(@path, name.to_s) begin - Kernel.require rubypath + Kernel.require file loaded(name, file) rescue => detail if Puppet[:trace] puts detail.backtrace end warn "Could not autoload %s: %s" % [file.inspect, detail] end end end end # Yield each subdir in turn. def eachdir searchpath.each do |dir| subdir = File.join(dir, @path) yield subdir if FileTest.directory?(subdir) end end # The list of directories to search through for loadable plugins. def searchpath # JJM: Search for optional lib directories in each module bundle. module_lib_dirs = Puppet[:modulepath].split(":").collect do |d| Dir.glob("%s/*/{plugins,lib}" % d).select do |f| FileTest.directory?(f) end end.flatten [module_lib_dirs, Puppet[:libdir], $:].flatten end end diff --git a/test/util/autoload.rb b/test/util/autoload.rb index 3fe18352c..05363d13e 100755 --- a/test/util/autoload.rb +++ b/test/util/autoload.rb @@ -1,145 +1,145 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../lib/puppettest' require 'puppet' require 'puppet/util/autoload' require 'puppettest' class TestAutoload < Test::Unit::TestCase include PuppetTest @things = [] def self.newthing(name) @things << name end def self.thing?(name) @things.include? name end def self.clear @things.clear end def mkfile(name, path) # Now create a file to load File.open(path, "w") do |f| f.puts %{ TestAutoload.newthing(:#{name.to_s}) } end end def mk_loader(name) dir = tempfile() $: << dir cleanup do $:.delete(dir) end Dir.mkdir(dir) rbdir = File.join(dir, name.to_s) Dir.mkdir(rbdir) loader = nil assert_nothing_raised { loader = Puppet::Util::Autoload.new(self.class, name) } return rbdir, loader end def test_load dir, loader = mk_loader(:yayness) assert_equal(loader.object_id, Puppet::Util::Autoload[self.class].object_id, "Did not retrieve loader object by class") # Make sure we don't fail on missing files assert_nothing_raised { assert_equal(false, loader.load(:mything), "got incorrect return on failed load") } # Now create a couple of files for testing path = File.join(dir, "mything.rb") mkfile(:mything, path) opath = File.join(dir, "othing.rb") mkfile(:othing, opath) # Now try to actually load it. assert_nothing_raised { assert_equal(true, loader.load(:mything), "got incorrect return on load") } assert(loader.loaded?(:mything), "Not considered loaded") assert(self.class.thing?(:mything), "Did not get loaded thing") self.class.clear [:mything, :othing].each do |thing| loader.load(thing) assert(loader.loaded?(thing), "#{thing.to_s} not considered loaded") assert(loader.loaded?("%s.rb" % thing), "#{thing.to_s} not considered loaded with .rb") assert(Puppet::Util::Autoload.loaded?("yayness/%s" % thing), "%s not considered loaded by the main class" % thing) assert(Puppet::Util::Autoload.loaded?("yayness/%s.rb" % thing), "%s not considered loaded by the main class with .rb" % thing) assert(self.class.thing?(thing), "Did not get loaded #{thing.to_s}") end end # Make sure that autoload dynamically modifies $: with the libdir as # appropriate. def test_searchpath dir = Puppet[:libdir] loader = Puppet::Util::Autoload.new(self, "testing") assert(loader.send(:searchpath).include?(dir), "searchpath does not include the libdir") end - # This causes very strange behaviour in the tests. We need to make sure we - # require the same path that a user would use, otherwise we'll result in - # a reload of the - def test_require_does_not_cause_reload + # This tests #1064, which was caused by using the unqualified + # path for requires, which was initially done so that the kernel + # would keep track of which files got loaded. + def test_require_uses_full_path loadname = "testing" loader = Puppet::Util::Autoload.new(self.class, loadname) basedir = "/some/dir" dir = File.join(basedir, loadname) loader.expects(:eachdir).yields(dir) subname = "instance" file = File.join(dir, subname) + ".rb" Dir.expects(:glob).with("#{dir}/*.rb").returns(file) - Kernel.expects(:require).with(File.join(loadname, subname)) + Kernel.expects(:require).with(file) loader.loadall end def test_searchpath_includes_plugin_dirs moddir = "/what/ever" libdir = "/other/dir" Puppet.settings.stubs(:value).with(:modulepath).returns(moddir) Puppet.settings.stubs(:value).with(:libdir).returns(libdir) loadname = "testing" loader = Puppet::Util::Autoload.new(self.class, loadname) # Currently, include both plugins and libs. paths = %w{plugins lib}.inject({}) { |hash, d| hash[d] = File.join(moddir, "testing", d); FileTest.stubs(:directory?).with(hash[d]).returns(true); hash } Dir.expects(:glob).with("#{moddir}/*/{plugins,lib}").returns(paths.values) searchpath = loader.searchpath paths.each do |dir, path| assert(searchpath.include?(path), "search path did not include path for %s" % dir) end end end