summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lib/puppet/parser/parser_support.rb115
-rwxr-xr-xspec/unit/parser/parser.rb72
2 files changed, 134 insertions, 53 deletions
diff --git a/lib/puppet/parser/parser_support.rb b/lib/puppet/parser/parser_support.rb
index f1c1da0b0..bc3444c2a 100644
--- a/lib/puppet/parser/parser_support.rb
+++ b/lib/puppet/parser/parser_support.rb
@@ -112,36 +112,22 @@ class Puppet::Parser::Parser
end
def find_or_load(namespace, name, type)
- method = "find_" + type.to_s
- if result = @loaded_code.send(method, namespace, name)
- return result
- end
-
+ method = "find_#{type}"
fullname = (namespace + "::" + name).sub(/^::/, '')
- self.load(fullname)
-
- if result = @loaded_code.send(method, namespace, name)
- return result
- end
-
- # Try to load the module init file if we're a qualified
- # name
- if fullname.include?("::")
- module_name = fullname.split("::")[0]
+ names_to_try = [fullname]
- self.load(module_name)
+ # Try to load the module init file if we're a qualified name
+ names_to_try << fullname.split("::")[0] if fullname.include?("::")
- if result = @loaded_code.send(method, namespace, name)
- return result
- end
- end
-
- # Now try to load the bare name on its own. This is
- # appropriate if the class we're looking for is in a
+ # Otherwise try to load the bare name on its own. This
+ # is appropriate if the class we're looking for is in a
# module that's different from our namespace.
- self.load(name)
+ names_to_try << name
- @loaded_code.send(method, namespace, name)
+ until (result = @loaded_code.send(method, namespace, name)) or names_to_try.empty? do
+ self.load(names_to_try.shift)
+ end
+ return result
end
# Import our files.
@@ -207,42 +193,67 @@ class Puppet::Parser::Parser
@lexer = Puppet::Parser::Lexer.new()
@files = {}
@loaded = []
- end
-
- # Try to load a class, since we could not find it.
- def load(classname)
- return false if classname == ""
- filename = classname.gsub("::", File::SEPARATOR)
-
- # First try to load the top-level module
- mod = filename.scan(/^[\w-]+/).shift
- unless @loaded.include?(mod)
- @loaded << mod
- begin
- import(mod)
- Puppet.info "Autoloaded module %s" % mod
- rescue Puppet::ImportError => detail
- # We couldn't load the module
+ @loading = {}
+ @loading.extend(MonitorMixin)
+ class << @loading
+ def done_with(item)
+ synchronize do
+ delete(item)[:busy].signal if self.has_key?(item) and self[item][:loader] == Thread.current
+ end
+ end
+ def owner_of(item)
+ synchronize do
+ if !self.has_key? item
+ self[item] = { :loader => Thread.current, :busy => self.new_cond}
+ :nobody
+ elsif self[item][:loader] == Thread.current
+ :this_thread
+ else
+ flag = self[item][:busy]
+ flag.wait
+ flag.signal
+ :another_thread
+ end
+ end
end
end
+ end
- # We don't know whether we're looking for a class or definition, so we have
- # to test for both.
- return true if @loaded_code.hostclass(classname) || @loaded_code.definition(classname)
-
- unless @loaded.include?(filename)
- @loaded << filename
- # Then the individual file
+ # Utility method factored out of load
+ def able_to_import?(classname,item,msg)
+ unless @loaded.include?(item)
begin
- import(filename)
- Puppet.info "Autoloaded file %s from module %s" % [filename, mod]
+ case @loading.owner_of(item)
+ when :this_thread
+ return
+ when :another_thread
+ return able_to_import?(classname,item,msg)
+ when :nobody
+ import(item)
+ Puppet.info "Autoloaded #{msg}"
+ @loaded << item
+ end
rescue Puppet::ImportError => detail
- # We couldn't load the file
+ # We couldn't load the item
+ ensure
+ @loading.done_with(item)
end
end
# We don't know whether we're looking for a class or definition, so we have
# to test for both.
- return true if @loaded_code.hostclass(classname) || @loaded_code.definition(classname)
+ return @loaded_code.hostclass(classname) || @loaded_code.definition(classname)
+ end
+
+ # Try to load a class, since we could not find it.
+ def load(classname)
+ return false if classname == ""
+ filename = classname.gsub("::", File::SEPARATOR)
+ mod = filename.scan(/^[\w-]+/).shift
+
+ # First try to load the top-level module then the individual file
+ [[mod, "module %s" % mod ],
+ [filename,"file %s from module %s" % [filename, mod]]
+ ].any? { |item,description| able_to_import?(classname,item,description) }
end
# Split an fq name into a namespace and name
diff --git a/spec/unit/parser/parser.rb b/spec/unit/parser/parser.rb
index bd12f7155..75d0c05a3 100755
--- a/spec/unit/parser/parser.rb
+++ b/spec/unit/parser/parser.rb
@@ -328,4 +328,74 @@ describe Puppet::Parser do
@parser.version.should == "output"
end
end
- end
+
+ describe Puppet::Parser,"when looking up definitions" do
+ it "should check for them by name" do
+ @parser.stubs(:find_or_load).with("namespace","name",:definition).returns(:this_value)
+ @parser.find_definition("namespace","name").should == :this_value
+ end
+ end
+
+ describe Puppet::Parser,"when looking up hostclasses" do
+ it "should check for them by name" do
+ @parser.stubs(:find_or_load).with("namespace","name",:hostclass).returns(:this_value)
+ @parser.find_hostclass("namespace","name").should == :this_value
+ end
+ end
+
+ describe Puppet::Parser,"when looking up names" do
+ before :each do
+ @loaded_code = mock 'loaded code'
+ @loaded_code.stubs(:find_my_type).with('Loaded_namespace', 'Loaded_name').returns(true)
+ @loaded_code.stubs(:find_my_type).with('Bogus_namespace', 'Bogus_name' ).returns(false)
+ @parser = Puppet::Parser::Parser.new :environment => "development",:loaded_code => @loaded_code
+ end
+
+ describe "that are already loaded" do
+ it "should not try to load anything" do
+ @parser.expects(:load).never
+ @parser.find_or_load("Loaded_namespace","Loaded_name",:my_type)
+ end
+ it "should return true" do
+ @parser.find_or_load("Loaded_namespace","Loaded_name",:my_type).should == true
+ end
+ end
+
+ describe "that aren't already loaded" do
+ it "should first attempt to load them with the fully qualified name" do
+ @loaded_code.stubs(:find_my_type).with("Foo_namespace","Foo_name").returns(false,true,true)
+ @parser.expects(:load).with("Foo_namespace::Foo_name").returns(true).then.raises(Exception)
+ @parser.find_or_load("Foo_namespace","Foo_name",:my_type).should == true
+ end
+
+ it "should next attempt to load them with the namespace" do
+ @loaded_code.stubs(:find_my_type).with("Foo_namespace","Foo_name").returns(false,false,true,true)
+ @parser.expects(:load).with("Foo_namespace::Foo_name").returns(false).then.raises(Exception)
+ @parser.expects(:load).with("Foo_namespace").returns(true).then.raises(Exception)
+ @parser.find_or_load("Foo_namespace","Foo_name",:my_type).should == true
+ end
+
+ it "should return false if the name isn't found" do
+ @parser.stubs(:load).returns(false)
+ @parser.find_or_load("Bogus_namespace","Bogus_name",:my_type).should == false
+ end
+ end
+ end
+
+ describe Puppet::Parser,"when loading classnames" do
+ before :each do
+ @loaded_code = mock 'loaded code'
+ @parser = Puppet::Parser::Parser.new :environment => "development",:loaded_code => @loaded_code
+ end
+
+ it "should just return false if the classname is empty" do
+ @parser.expects(:import).never
+ @parser.load("").should == false
+ end
+
+ it "should just return true if the item is loaded" do
+ pending "Need to access internal state (@parser's @loaded) to force this"
+ @parser.load("").should == false
+ end
+ end
+end