Fixing #3668 - fixed autoloading classes from modules
This involved essentially moving all of the importing and loading code out of the Parser and into a new 'TypeLoader' class. The parser and the ResourceTypeCollection classes now delegate to that class for all file handling. Most of the code paths are also now much cleaner, and a bit of redundancy was removed. Signed-off-by: Luke Kanies <luke@puppetlabs.com>
This commit is contained in:
Родитель
f66095d35b
Коммит
d61a69a0e5
|
@ -633,12 +633,11 @@ selectlhand: name
|
|||
| regex
|
||||
|
||||
# These are only used for importing, and we don't interpolate there.
|
||||
qtexts: quotedtext { result = [val[0].value] }
|
||||
| qtexts COMMA quotedtext {
|
||||
results = val[0] << val[2].value
|
||||
}
|
||||
string: STRING { result = [val[0][:value]] }
|
||||
strings: string
|
||||
| strings COMMA string { result = val[0] += val[2] }
|
||||
|
||||
import: IMPORT qtexts {
|
||||
import: IMPORT strings {
|
||||
val[1].each do |file|
|
||||
import(file)
|
||||
end
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -107,91 +107,15 @@ class Puppet::Parser::Parser
|
|||
end
|
||||
|
||||
def find_hostclass(namespace, name)
|
||||
find_or_load(namespace, name, :hostclass)
|
||||
known_resource_types.find_or_load(namespace, name, :hostclass)
|
||||
end
|
||||
|
||||
def find_definition(namespace, name)
|
||||
find_or_load(namespace, name, :definition)
|
||||
known_resource_types.find_or_load(namespace, name, :definition)
|
||||
end
|
||||
|
||||
def find_or_load(namespace, name, type)
|
||||
method = "find_#{type}"
|
||||
namespace = namespace.downcase
|
||||
name = name.downcase
|
||||
fullname = (namespace + "::" + name).sub(/^::/, '')
|
||||
|
||||
if name =~ /^::/
|
||||
names_to_try = [name.sub(/^::/, '')]
|
||||
else
|
||||
names_to_try = [fullname]
|
||||
|
||||
# Try to load the module init file if we're a qualified name
|
||||
names_to_try << fullname.split("::")[0] if fullname.include?("::")
|
||||
|
||||
# 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.
|
||||
names_to_try << name
|
||||
names_to_try.compact!
|
||||
end
|
||||
|
||||
until (result = known_resource_types.send(method, namespace, name)) or names_to_try.empty? do
|
||||
self.load(names_to_try.shift)
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
# Import our files.
|
||||
def import(file)
|
||||
if Puppet[:ignoreimport]
|
||||
return AST::ASTArray.new(:children => [])
|
||||
end
|
||||
# use a path relative to the file doing the importing
|
||||
if @lexer.file
|
||||
dir = @lexer.file.sub(%r{[^/]+$},'').sub(/\/$/, '')
|
||||
else
|
||||
dir = "."
|
||||
end
|
||||
if dir == ""
|
||||
dir = "."
|
||||
end
|
||||
result = ast AST::ASTArray
|
||||
|
||||
# We can't interpolate at this point since we don't have any
|
||||
# scopes set up. Warn the user if they use a variable reference
|
||||
raise "Got no file" unless file
|
||||
pat = file
|
||||
if pat.index("$")
|
||||
Puppet.warning(
|
||||
"The import of #{pat} contains a variable reference;" +
|
||||
" variables are not interpolated for imports " +
|
||||
"in file #{@lexer.file} at line #{@lexer.line}"
|
||||
)
|
||||
end
|
||||
files = Puppet::Parser::Files.find_manifests(pat, :cwd => dir, :environment => @environment)
|
||||
if files.size == 0
|
||||
raise Puppet::ImportError.new("No file(s) found for import " +
|
||||
"of '#{pat}'")
|
||||
end
|
||||
|
||||
files.collect { |file|
|
||||
parser = Puppet::Parser::Parser.new(@environment)
|
||||
parser.files = self.files
|
||||
Puppet.debug("importing '%s'" % file)
|
||||
|
||||
unless file =~ /^#{File::SEPARATOR}/
|
||||
file = File.join(dir, file)
|
||||
end
|
||||
begin
|
||||
parser.file = file
|
||||
rescue Puppet::AlreadyImportedError
|
||||
# This file has already been imported to just move on
|
||||
next
|
||||
end
|
||||
|
||||
# This will normally add code to the 'main' class.
|
||||
parser.parse
|
||||
}
|
||||
known_resource_types.loader.import(file, @lexer.file)
|
||||
end
|
||||
|
||||
def initialize(env)
|
||||
|
@ -203,69 +127,6 @@ class Puppet::Parser::Parser
|
|||
# Initialize or reset all of our variables.
|
||||
def initvars
|
||||
@lexer = Puppet::Parser::Lexer.new()
|
||||
@files = {}
|
||||
@loaded = []
|
||||
@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
|
||||
|
||||
# Utility method factored out of load
|
||||
def able_to_import?(classname,item,msg)
|
||||
unless @loaded.include?(item)
|
||||
begin
|
||||
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 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 known_resource_types.hostclass(classname) || known_resource_types.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
|
||||
|
@ -365,15 +226,6 @@ class Puppet::Parser::Parser
|
|||
require self.file
|
||||
end
|
||||
|
||||
# See if any of the files have changed.
|
||||
def reparse?
|
||||
if file = @files.detect { |name, file| file.changed? }
|
||||
return file[1].stamp
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
def string=(string)
|
||||
@lexer.string = string
|
||||
end
|
||||
|
|
|
@ -0,0 +1,144 @@
|
|||
require 'puppet/node/environment'
|
||||
|
||||
class Puppet::Parser::TypeLoader
|
||||
include Puppet::Node::Environment::Helper
|
||||
|
||||
class Helper < Hash
|
||||
include MonitorMixin
|
||||
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
|
||||
|
||||
# Import our files.
|
||||
def import(file, current_file = nil)
|
||||
return if Puppet[:ignoreimport]
|
||||
|
||||
# use a path relative to the file doing the importing
|
||||
if current_file
|
||||
dir = current_file.sub(%r{[^/]+$},'').sub(/\/$/, '')
|
||||
else
|
||||
dir = "."
|
||||
end
|
||||
if dir == ""
|
||||
dir = "."
|
||||
end
|
||||
|
||||
pat = file
|
||||
files = Puppet::Parser::Files.find_manifests(pat, :cwd => dir, :environment => environment)
|
||||
if files.size == 0
|
||||
raise Puppet::ImportError.new("No file(s) found for import of '#{pat}'")
|
||||
end
|
||||
|
||||
files.each do |file|
|
||||
unless file =~ /^#{File::SEPARATOR}/
|
||||
file = File.join(dir, file)
|
||||
end
|
||||
@imported[file] = true
|
||||
parse_file(file)
|
||||
end
|
||||
end
|
||||
|
||||
def imported?(file)
|
||||
@imported.has_key?(file)
|
||||
end
|
||||
|
||||
def known_resource_types
|
||||
environment.known_resource_types
|
||||
end
|
||||
|
||||
def initialize(env)
|
||||
self.environment = env
|
||||
@loaded = []
|
||||
@loading = Helper.new
|
||||
|
||||
@imported = {}
|
||||
end
|
||||
|
||||
def load_until(namespaces, name)
|
||||
return nil if name == "" # special-case main.
|
||||
name2files(namespaces, name).each do |filename|
|
||||
import_if_possible(filename) do
|
||||
import(filename)
|
||||
@loaded << filename
|
||||
end
|
||||
if result = yield(filename)
|
||||
Puppet.info "Automatically imported #{name} from #{filename}"
|
||||
return result
|
||||
end
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
def loaded?(name)
|
||||
@loaded.include?(name)
|
||||
end
|
||||
|
||||
def name2files(namespaces, name)
|
||||
return [name.sub(/^::/, '').gsub("::", File::SEPARATOR)] if name =~ /^::/
|
||||
|
||||
result = namespaces.inject([]) do |names_to_try, namespace|
|
||||
fullname = (namespace + "::" + name).sub(/^::/, '')
|
||||
|
||||
# Try to load the module init file if we're a qualified name
|
||||
if fullname.include?("::")
|
||||
names_to_try << fullname.split("::")[0]
|
||||
end
|
||||
|
||||
# Then the fully qualified name
|
||||
names_to_try << fullname
|
||||
end
|
||||
|
||||
# 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.
|
||||
result << name
|
||||
result.uniq.collect { |f| f.gsub("::", File::SEPARATOR) }
|
||||
end
|
||||
|
||||
def parse_file(file)
|
||||
Puppet.debug("importing '#{file}'")
|
||||
parser = Puppet::Parser::Parser.new(environment)
|
||||
parser.file = file
|
||||
parser.parse
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Utility method factored out of load for handling thread-safety.
|
||||
# This isn't tested in the specs, because that's basically impossible.
|
||||
def import_if_possible(file)
|
||||
return if @loaded.include?(file)
|
||||
begin
|
||||
case @loading.owner_of(file)
|
||||
when :this_thread
|
||||
return
|
||||
when :another_thread
|
||||
return import_if_possible(file)
|
||||
when :nobody
|
||||
yield
|
||||
end
|
||||
rescue Puppet::ImportError => detail
|
||||
# We couldn't load the item
|
||||
ensure
|
||||
@loading.done_with(file)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -55,6 +55,11 @@ class Puppet::Resource::TypeCollection
|
|||
instance
|
||||
end
|
||||
|
||||
def loader
|
||||
require 'puppet/parser/type_loader'
|
||||
@loader ||= Puppet::Parser::TypeLoader.new(environment)
|
||||
end
|
||||
|
||||
def node(name)
|
||||
name = munge_name(name)
|
||||
|
||||
|
@ -115,16 +120,30 @@ class Puppet::Resource::TypeCollection
|
|||
nil
|
||||
end
|
||||
|
||||
def find_or_load(namespaces, name, type)
|
||||
name = name.downcase
|
||||
namespaces = [namespaces] unless namespaces.is_a?(Array)
|
||||
namespaces = namespaces.collect { |ns| ns.downcase }
|
||||
|
||||
# This could be done in the load_until, but the knowledge seems to
|
||||
# belong here.
|
||||
if r = find(namespaces, name, type)
|
||||
return r
|
||||
end
|
||||
|
||||
return loader.load_until(namespaces, name) { find(namespaces, name, type) }
|
||||
end
|
||||
|
||||
def find_node(name)
|
||||
find("", name, :node)
|
||||
end
|
||||
|
||||
def find_hostclass(namespace, name)
|
||||
find(namespace, name, :hostclass)
|
||||
def find_hostclass(namespaces, name)
|
||||
find_or_load(namespaces, name, :hostclass)
|
||||
end
|
||||
|
||||
def find_definition(namespace, name)
|
||||
find(namespace, name, :definition)
|
||||
def find_definition(namespaces, name)
|
||||
find_or_load(namespaces, name, :definition)
|
||||
end
|
||||
|
||||
[:hostclasses, :nodes, :definitions].each do |m|
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
require File.dirname(__FILE__) + '/../../spec_helper'
|
||||
|
||||
require 'puppet_spec/files'
|
||||
require 'puppet/resource/type_collection'
|
||||
|
||||
describe Puppet::Resource::TypeCollection do
|
||||
describe "when autoloading from modules" do
|
||||
include PuppetSpec::Files
|
||||
|
||||
before do
|
||||
@dir = tmpfile("autoload_testing")
|
||||
Puppet[:modulepath] = @dir
|
||||
|
||||
FileUtils.mkdir_p @dir
|
||||
@code = Puppet::Resource::TypeCollection.new("env")
|
||||
Puppet::Node::Environment.new("env").stubs(:known_resource_types).returns @code
|
||||
end
|
||||
|
||||
# Setup a module.
|
||||
def mk_module(name, files = {})
|
||||
mdir = File.join(@dir, name)
|
||||
mandir = File.join(mdir, "manifests")
|
||||
FileUtils.mkdir_p mandir
|
||||
|
||||
defs = files.delete(:define)
|
||||
|
||||
Dir.chdir(mandir) do
|
||||
files.each do |file, classes|
|
||||
File.open("#{file}.pp", "w") do |f|
|
||||
classes.each { |klass|
|
||||
if defs
|
||||
f.puts "define #{klass} {}"
|
||||
else
|
||||
f.puts "class #{klass} {}"
|
||||
end
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "should return nil when a class can't be found or loaded" do
|
||||
@code.find_hostclass('', 'nosuchclass').should be_nil
|
||||
end
|
||||
|
||||
it "should load the module's init file first" do
|
||||
name = "simple"
|
||||
mk_module(name, :init => [name])
|
||||
|
||||
@code.find_hostclass("", name).name.should == name
|
||||
end
|
||||
|
||||
it "should load the module's init file even when searching from a different namespace" do
|
||||
name = "simple"
|
||||
mk_module(name, :init => [name])
|
||||
|
||||
@code.find_hostclass("other::ns", name).name.should == name
|
||||
end
|
||||
|
||||
it "should be able to load definitions from the module base file" do
|
||||
name = "simpdef"
|
||||
mk_module(name, :define => true, :init => [name])
|
||||
@code.find_definition("", name).name.should == name
|
||||
end
|
||||
|
||||
it "should be able to load qualified classes from the module base file" do
|
||||
modname = "both"
|
||||
name = "sub"
|
||||
mk_module(modname, :init => %w{both both::sub})
|
||||
|
||||
@code.find_hostclass("both", name).name.should == "both::sub"
|
||||
end
|
||||
|
||||
it "should be able load classes from a separate file" do
|
||||
modname = "separate"
|
||||
name = "sub"
|
||||
mk_module(modname, :init => %w{separate}, :sub => %w{separate::sub})
|
||||
@code.find_hostclass("separate", name).name.should == "separate::sub"
|
||||
end
|
||||
|
||||
it "should not fail when loading from a separate file if there is no module file" do
|
||||
modname = "alone"
|
||||
name = "sub"
|
||||
mk_module(modname, :sub => %w{alone::sub})
|
||||
lambda { @code.find_hostclass("alone", name) }.should_not raise_error
|
||||
end
|
||||
|
||||
it "should be able to load definitions from their own file" do
|
||||
name = "mymod"
|
||||
mk_module(name, :define => true, :mydefine => ["mymod::mydefine"])
|
||||
@code.find_definition("", "mymod::mydefine").name.should == "mymod::mydefine"
|
||||
end
|
||||
end
|
||||
end
|
|
@ -32,6 +32,13 @@ describe Puppet::Parser do
|
|||
parser = Puppet::Parser::Parser.new "development"
|
||||
parser.known_resource_types.should equal(rtc)
|
||||
end
|
||||
|
||||
it "should delegate importing to the known resource type loader" do
|
||||
parser = Puppet::Parser::Parser.new "development"
|
||||
parser.known_resource_types.loader.expects(:import).with("newfile", "current_file")
|
||||
parser.lexer.expects(:file).returns "current_file"
|
||||
parser.import("newfile")
|
||||
end
|
||||
|
||||
describe "when parsing files" do
|
||||
before do
|
||||
|
@ -327,91 +334,19 @@ describe Puppet::Parser do
|
|||
end
|
||||
|
||||
describe "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)
|
||||
it "should use the known resource types to check for them by name" do
|
||||
@parser.known_resource_types.stubs(:find_or_load).with("namespace","name",:definition).returns(:this_value)
|
||||
@parser.find_definition("namespace","name").should == :this_value
|
||||
end
|
||||
end
|
||||
|
||||
describe "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)
|
||||
it "should use the known resource types to check for them by name" do
|
||||
@parser.known_resource_types.stubs(:find_or_load).with("namespace","name",:hostclass).returns(:this_value)
|
||||
@parser.find_hostclass("namespace","name").should == :this_value
|
||||
end
|
||||
end
|
||||
|
||||
describe "when looking up names" do
|
||||
before :each do
|
||||
@known_resource_types = mock 'loaded code'
|
||||
@known_resource_types.stubs(:find_my_type).with('loaded_namespace', 'loaded_name').returns(true)
|
||||
@known_resource_types.stubs(:find_my_type).with('bogus_namespace', 'bogus_name' ).returns(false)
|
||||
@parser = Puppet::Parser::Parser.new "development"
|
||||
@parser.stubs(:known_resource_types).returns @known_resource_types
|
||||
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 all lowercase fully qualified name" do
|
||||
@known_resource_types.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 all lowercase namespace" do
|
||||
@known_resource_types.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 finally attempt to load them with the all lowercase unqualified name" do
|
||||
@known_resource_types.stubs(:find_my_type).with("foo_namespace","foo_name").returns(false,false,false,true,true)
|
||||
@parser.expects(:load).with("foo_namespace::foo_name").returns(false).then.raises(Exception)
|
||||
@parser.expects(:load).with("foo_namespace" ).returns(false).then.raises(Exception)
|
||||
@parser.expects(:load).with( "foo_name").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
|
||||
|
||||
it "should directly look for fully qualified classes" do
|
||||
@known_resource_types.stubs(:find_hostclass).with("foo_namespace","::foo_name").returns(false, true)
|
||||
@parser.expects(:load).with("foo_name").returns true
|
||||
@parser.find_or_load("foo_namespace","::foo_name",:hostclass)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "when loading classnames" do
|
||||
before :each do
|
||||
@known_resource_types = mock 'loaded code'
|
||||
@parser = Puppet::Parser::Parser.new "development"
|
||||
@parser.stubs(:known_resource_types).returns @known_resource_types
|
||||
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
|
||||
|
||||
describe "when parsing classes" do
|
||||
before :each do
|
||||
@krt = Puppet::Resource::TypeCollection.new("development")
|
||||
|
|
|
@ -0,0 +1,191 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
require File.dirname(__FILE__) + '/../../spec_helper'
|
||||
|
||||
require 'puppet/parser/type_loader'
|
||||
require 'puppet_spec/files'
|
||||
|
||||
describe Puppet::Parser::TypeLoader do
|
||||
include PuppetSpec::Files
|
||||
|
||||
before do
|
||||
@loader = Puppet::Parser::TypeLoader.new(:myenv)
|
||||
end
|
||||
|
||||
it "should support an environment" do
|
||||
loader = Puppet::Parser::TypeLoader.new(:myenv)
|
||||
loader.environment.name.should == :myenv
|
||||
end
|
||||
|
||||
it "should include the Environment Helper" do
|
||||
@loader.class.ancestors.should be_include(Puppet::Node::Environment::Helper)
|
||||
end
|
||||
|
||||
it "should delegate its known resource types to its environment" do
|
||||
@loader.known_resource_types.should be_instance_of(Puppet::Resource::TypeCollection)
|
||||
end
|
||||
|
||||
describe "when loading names from namespaces" do
|
||||
it "should do nothing if the name to import is an empty string" do
|
||||
@loader.expects(:name2files).never
|
||||
@loader.load_until(["foo"], "") { |f| false }.should be_nil
|
||||
end
|
||||
|
||||
it "should turn the provided namespaces and name into a list of files" do
|
||||
@loader.expects(:name2files).with(["foo"], "bar").returns []
|
||||
@loader.load_until(["foo"], "bar") { |f| false }
|
||||
end
|
||||
|
||||
it "should attempt to import each generated name" do
|
||||
@loader.expects(:name2files).returns %w{foo bar}
|
||||
@loader.expects(:import).with("foo")
|
||||
@loader.expects(:import).with("bar")
|
||||
@loader.load_until(["foo"], "bar") { |f| false }
|
||||
end
|
||||
|
||||
it "should yield after each import" do
|
||||
yielded = []
|
||||
@loader.expects(:name2files).returns %w{foo bar}
|
||||
@loader.expects(:import).with("foo")
|
||||
@loader.expects(:import).with("bar")
|
||||
@loader.load_until(["foo"], "bar") { |f| yielded << f; false }
|
||||
yielded.should == %w{foo bar}
|
||||
end
|
||||
|
||||
it "should stop importing when the yielded block returns true" do
|
||||
yielded = []
|
||||
@loader.expects(:name2files).returns %w{foo bar baz}
|
||||
@loader.expects(:import).with("foo")
|
||||
@loader.expects(:import).with("bar")
|
||||
@loader.expects(:import).with("baz").never
|
||||
@loader.load_until(["foo"], "bar") { |f| true if f == "bar" }
|
||||
end
|
||||
|
||||
it "should return the result of the block" do
|
||||
yielded = []
|
||||
@loader.expects(:name2files).returns %w{foo bar baz}
|
||||
@loader.expects(:import).with("foo")
|
||||
@loader.expects(:import).with("bar")
|
||||
@loader.expects(:import).with("baz").never
|
||||
@loader.load_until(["foo"], "bar") { |f| 10 if f == "bar" }.should == 10
|
||||
end
|
||||
|
||||
it "should return nil if the block never returns true" do
|
||||
@loader.expects(:name2files).returns %w{foo bar}
|
||||
@loader.expects(:import).with("foo")
|
||||
@loader.expects(:import).with("bar")
|
||||
@loader.load_until(["foo"], "bar") { |f| false }.should be_nil
|
||||
end
|
||||
|
||||
it "should know when a given name has been loaded" do
|
||||
@loader.expects(:name2files).returns %w{file}
|
||||
@loader.expects(:import).with("file")
|
||||
@loader.load_until(["foo"], "bar") { |f| true }
|
||||
@loader.should be_loaded("file")
|
||||
end
|
||||
end
|
||||
|
||||
describe "when mapping names to files" do
|
||||
{
|
||||
[["foo"], "::bar::baz"] => %w{bar/baz},
|
||||
[[""], "foo::bar"] => %w{foo foo/bar},
|
||||
[%w{foo}, "bar"] => %w{foo foo/bar bar},
|
||||
[%w{a b}, "bar"] => %w{a a/bar b b/bar bar},
|
||||
[%w{a::b::c}, "bar"] => %w{a a/b/c/bar bar},
|
||||
[%w{a::b}, "foo::bar"] => %w{a a/b/foo/bar foo/bar}
|
||||
}.each do |inputs, outputs|
|
||||
it "should produce #{outputs.inspect} from the #{inputs[0].inspect} namespace and #{inputs[1]} name" do
|
||||
@loader.name2files(*inputs).should == outputs
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "when importing" do
|
||||
before do
|
||||
Puppet::Parser::Files.stubs(:find_manifests).returns %w{file}
|
||||
@loader.stubs(:parse_file)
|
||||
end
|
||||
|
||||
it "should return immediately when imports are being ignored" do
|
||||
Puppet::Parser::Files.expects(:find_manifests).never
|
||||
Puppet[:ignoreimport] = true
|
||||
@loader.import("foo").should be_nil
|
||||
end
|
||||
|
||||
it "should find all manifests matching the file or pattern" do
|
||||
Puppet::Parser::Files.expects(:find_manifests).with { |pat, opts| pat == "myfile" }.returns %w{one}
|
||||
@loader.import("myfile")
|
||||
end
|
||||
|
||||
it "should use the directory of the current file if one is set" do
|
||||
Puppet::Parser::Files.expects(:find_manifests).with { |pat, opts| opts[:cwd] == "/current" }.returns %w{one}
|
||||
@loader.import("myfile", "/current/file")
|
||||
end
|
||||
|
||||
it "should pass the environment when looking for files" do
|
||||
Puppet::Parser::Files.expects(:find_manifests).with { |pat, opts| opts[:environment] == @loader.environment }.returns %w{one}
|
||||
@loader.import("myfile")
|
||||
end
|
||||
|
||||
it "should fail if no files are found" do
|
||||
Puppet::Parser::Files.expects(:find_manifests).returns []
|
||||
lambda { @loader.import("myfile") }.should raise_error(Puppet::ImportError)
|
||||
end
|
||||
|
||||
it "should parse each found file" do
|
||||
Puppet::Parser::Files.expects(:find_manifests).returns %w{/one}
|
||||
@loader.expects(:parse_file).with("/one")
|
||||
@loader.import("myfile")
|
||||
end
|
||||
|
||||
it "should make each file qualified before attempting to parse it" do
|
||||
Puppet::Parser::Files.expects(:find_manifests).returns %w{one}
|
||||
@loader.expects(:parse_file).with("/current/one")
|
||||
@loader.import("myfile", "/current/file")
|
||||
end
|
||||
|
||||
it "should know when a given file has been imported" do
|
||||
Puppet::Parser::Files.expects(:find_manifests).returns %w{/one}
|
||||
@loader.import("myfile")
|
||||
|
||||
@loader.should be_imported("/one")
|
||||
end
|
||||
|
||||
it "should not attempt to import files that have already been imported" do
|
||||
Puppet::Parser::Files.expects(:find_manifests).returns %w{/one}
|
||||
@loader.expects(:parse_file).once
|
||||
@loader.import("myfile")
|
||||
|
||||
# This will fail if it tries to reimport the file.
|
||||
@loader.import("myfile")
|
||||
end
|
||||
end
|
||||
|
||||
describe "when parsing a file" do
|
||||
before do
|
||||
@parser = Puppet::Parser::Parser.new(@loader.environment)
|
||||
@parser.stubs(:parse)
|
||||
@parser.stubs(:file=)
|
||||
Puppet::Parser::Parser.stubs(:new).with(@loader.environment).returns @parser
|
||||
end
|
||||
|
||||
it "should create a new parser instance for each file using the current environment" do
|
||||
Puppet::Parser::Parser.expects(:new).with(@loader.environment).returns @parser
|
||||
@loader.parse_file("/my/file")
|
||||
end
|
||||
|
||||
it "should assign the parser its file and parse" do
|
||||
@parser.expects(:file=).with("/my/file")
|
||||
@parser.expects(:parse)
|
||||
@loader.parse_file("/my/file")
|
||||
end
|
||||
end
|
||||
|
||||
it "should be able to add classes to the current resource type collection" do
|
||||
file = tmpfile("simple_file")
|
||||
File.open(file, "w") { |f| f.puts "class foo {}" }
|
||||
@loader.import(file)
|
||||
|
||||
@loader.known_resource_types.hostclass("foo").should be_instance_of(Puppet::Resource::Type)
|
||||
end
|
||||
end
|
|
@ -21,6 +21,10 @@ describe Puppet::Resource::TypeCollection do
|
|||
Puppet::Resource::TypeCollection.new("testing").environment.should equal(env)
|
||||
end
|
||||
|
||||
it "should create a 'loader' at initialization" do
|
||||
Puppet::Resource::TypeCollection.new("testing").loader.should be_instance_of(Puppet::Parser::TypeLoader)
|
||||
end
|
||||
|
||||
it "should be able to add a resource type" do
|
||||
Puppet::Resource::TypeCollection.new("env").should respond_to(:add)
|
||||
end
|
||||
|
@ -85,6 +89,51 @@ describe Puppet::Resource::TypeCollection do
|
|||
loader.node("node").should be_nil
|
||||
end
|
||||
|
||||
describe "when looking up names" do
|
||||
before do
|
||||
@type = Puppet::Resource::Type.new(:hostclass, "ns::klass")
|
||||
end
|
||||
|
||||
it "should support looking up with multiple namespaces" do
|
||||
@code.add @type
|
||||
@code.find_hostclass(%w{boo baz ns}, "klass").should equal(@type)
|
||||
end
|
||||
|
||||
it "should not attempt to import anything when the type is already defined" do
|
||||
@code.add @type
|
||||
@code.loader.expects(:import).never
|
||||
@code.find_hostclass(%w{ns}, "klass").should equal(@type)
|
||||
end
|
||||
|
||||
describe "that need to be loaded" do
|
||||
it "should use the loader to load the files" do
|
||||
@code.loader.expects(:load_until).with(["ns"], "klass")
|
||||
@code.find_or_load(["ns"], "klass", :hostclass)
|
||||
end
|
||||
|
||||
it "should downcase the name and downcase and array-fy the namespaces before passing to the loader" do
|
||||
@code.loader.expects(:load_until).with(["ns"], "klass")
|
||||
@code.find_or_load("Ns", "Klass", :hostclass)
|
||||
end
|
||||
|
||||
it "should attempt to find the type when the loader yields" do
|
||||
@code.loader.expects(:load_until).yields
|
||||
@code.expects(:find).with(["ns"], "klass", :hostclass).times(2).returns(false).then.returns(true)
|
||||
@code.find_or_load("ns", "klass", :hostclass)
|
||||
end
|
||||
|
||||
it "should return the result of 'load_until'" do
|
||||
@code.loader.expects(:load_until).returns "foo"
|
||||
@code.find_or_load("Ns", "Klass", :hostclass).should == "foo"
|
||||
end
|
||||
|
||||
it "should return nil if the name isn't found" do
|
||||
@code.stubs(:load_until).returns(nil)
|
||||
@code.find_or_load("Ns", "Klass", :hostclass).should be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
%w{hostclass node definition}.each do |data|
|
||||
before do
|
||||
@instance = Puppet::Resource::Type.new(data, "foo")
|
||||
|
@ -217,15 +266,15 @@ describe Puppet::Resource::TypeCollection do
|
|||
loader.find_node("bar")
|
||||
end
|
||||
|
||||
it "should use the generic 'find' method to find hostclasses" do
|
||||
it "should use the 'find_or_load' method to find hostclasses" do
|
||||
loader = Puppet::Resource::TypeCollection.new("env")
|
||||
loader.expects(:find).with("foo", "bar", :hostclass)
|
||||
loader.expects(:find_or_load).with("foo", "bar", :hostclass)
|
||||
loader.find_hostclass("foo", "bar")
|
||||
end
|
||||
|
||||
it "should use the generic 'find' method to find definitions" do
|
||||
it "should use the 'find_or_load' method to find definitions" do
|
||||
loader = Puppet::Resource::TypeCollection.new("env")
|
||||
loader.expects(:find).with("foo", "bar", :definition)
|
||||
loader.expects(:find_or_load).with("foo", "bar", :definition)
|
||||
loader.find_definition("foo", "bar")
|
||||
end
|
||||
|
||||
|
|
|
@ -655,65 +655,6 @@ file { "/tmp/yayness":
|
|||
}
|
||||
end
|
||||
|
||||
def test_module_import
|
||||
basedir = File.join(tmpdir(), "module-import")
|
||||
@@tmpfiles << basedir
|
||||
Dir.mkdir(basedir)
|
||||
modfiles = [ "init.pp", "mani1.pp", "mani2.pp",
|
||||
"sub/smani1.pp", "sub/smani2.pp" ]
|
||||
|
||||
modpath = File.join(basedir, "modules")
|
||||
Puppet[:modulepath] = modpath
|
||||
|
||||
modname = "amod"
|
||||
manipath = File::join(modpath, modname, Puppet::Module::MANIFESTS)
|
||||
FileUtils::mkdir_p(File::join(manipath, "sub"))
|
||||
targets = []
|
||||
modfiles.each do |fname|
|
||||
target = File::join(basedir, File::basename(fname, '.pp'))
|
||||
targets << target
|
||||
txt = %[ file { '#{target}': content => "#{fname}" } ]
|
||||
if fname == "init.pp"
|
||||
txt = %[import 'mani1' \nimport '#{modname}/mani2'\nimport '#{modname}/sub/*.pp'\n ] + txt
|
||||
end
|
||||
File::open(File::join(manipath, fname), "w") do |f|
|
||||
f.puts txt
|
||||
end
|
||||
end
|
||||
|
||||
manifest_texts = [ "import '#{modname}'",
|
||||
"import '#{modname}/init'",
|
||||
"import '#{modname}/init.pp'" ]
|
||||
|
||||
manifest = File.join(modpath, "manifest.pp")
|
||||
manifest_texts.each do |txt|
|
||||
File.open(manifest, "w") { |f| f.puts txt }
|
||||
|
||||
assert_nothing_raised {
|
||||
parser = mkparser
|
||||
parser.file = manifest
|
||||
parser.parse
|
||||
}
|
||||
assert_creates(manifest, *targets)
|
||||
end
|
||||
end
|
||||
|
||||
# #544
|
||||
def test_ignoreimports
|
||||
parser = mkparser
|
||||
|
||||
assert(! Puppet[:ignoreimport], ":ignoreimport defaulted to true")
|
||||
assert_raise(Puppet::ParseError, "Did not fail on missing import") do
|
||||
parser.parse("import 'nosuchfile'")
|
||||
end
|
||||
assert_nothing_raised("could not set :ignoreimport") do
|
||||
Puppet[:ignoreimport] = true
|
||||
end
|
||||
assert_nothing_raised("Parser did not follow :ignoreimports") do
|
||||
parser.parse("import 'nosuchfile'")
|
||||
end
|
||||
end
|
||||
|
||||
def test_multiple_imports_on_one_line
|
||||
one = tempfile
|
||||
two = tempfile
|
||||
|
@ -744,22 +685,6 @@ file { "/tmp/yayness":
|
|||
end
|
||||
end
|
||||
|
||||
# #588
|
||||
def test_globbing_with_directories
|
||||
dir = tempfile
|
||||
Dir.mkdir(dir)
|
||||
subdir = File.join(dir, "subdir")
|
||||
Dir.mkdir(subdir)
|
||||
file = File.join(dir, "file.pp")
|
||||
maker = tempfile
|
||||
File.open(file, "w") { |f| f.puts "file { '#{maker}': ensure => file }" }
|
||||
|
||||
parser = mkparser
|
||||
assert_nothing_raised("Globbing failed when it matched a directory") do
|
||||
parser.import("%s/*" % dir)
|
||||
end
|
||||
end
|
||||
|
||||
# #629 - undef keyword
|
||||
def test_undef
|
||||
parser = mkparser
|
||||
|
@ -805,128 +730,6 @@ file { "/tmp/yayness":
|
|||
end
|
||||
end
|
||||
|
||||
# Setup a module.
|
||||
def mk_module(name, files = {})
|
||||
mdir = File.join(@dir, name)
|
||||
mandir = File.join(mdir, "manifests")
|
||||
FileUtils.mkdir_p mandir
|
||||
|
||||
if defs = files[:define]
|
||||
files.delete(:define)
|
||||
end
|
||||
Dir.chdir(mandir) do
|
||||
files.each do |file, classes|
|
||||
File.open("%s.pp" % file, "w") do |f|
|
||||
classes.each { |klass|
|
||||
if defs
|
||||
f.puts "define %s {}" % klass
|
||||
else
|
||||
f.puts "class %s {}" % klass
|
||||
end
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# #596 - make sure classes and definitions load automatically if they're in modules, so we don't have to manually load each one.
|
||||
def test_module_autoloading
|
||||
@dir = tempfile
|
||||
Puppet[:modulepath] = @dir
|
||||
|
||||
FileUtils.mkdir_p @dir
|
||||
|
||||
parser = mkparser
|
||||
|
||||
# Make sure we fail like normal for actually missing classes
|
||||
assert_nil(parser.find_hostclass("", "nosuchclass"), "Did not return nil on missing classes")
|
||||
|
||||
# test the simple case -- the module class itself
|
||||
name = "simple"
|
||||
mk_module(name, :init => [name])
|
||||
|
||||
# Try to load the module automatically now
|
||||
klass = parser.find_hostclass("", name)
|
||||
assert_instance_of(Puppet::Resource::Type, klass, "Did not autoload class from module init file")
|
||||
assert_equal(name, klass.name, "Incorrect class was returned")
|
||||
|
||||
# Try loading the simple module when we're in something other than the base namespace.
|
||||
parser = mkparser
|
||||
klass = parser.find_hostclass("something::else", name)
|
||||
assert_instance_of(Puppet::Resource::Type, klass, "Did not autoload class from module init file")
|
||||
assert_equal(name, klass.name, "Incorrect class was returned")
|
||||
|
||||
# Now try it with a definition as the base file
|
||||
name = "simpdef"
|
||||
mk_module(name, :define => true, :init => [name])
|
||||
|
||||
klass = parser.find_definition("", name)
|
||||
assert_instance_of(Puppet::Resource::Type, klass, "Did not autoload class from module init file")
|
||||
assert_equal(name, klass.name, "Incorrect class was returned")
|
||||
|
||||
# Now try it with namespace classes where both classes are in the init file
|
||||
parser = mkparser
|
||||
modname = "both"
|
||||
name = "sub"
|
||||
mk_module(modname, :init => %w{both both::sub})
|
||||
|
||||
# First try it with a namespace
|
||||
klass = parser.find_hostclass("both", name)
|
||||
assert_instance_of(Puppet::Resource::Type, klass, "Did not autoload sub class from module init file with a namespace")
|
||||
assert_equal("both::sub", klass.name, "Incorrect class was returned")
|
||||
|
||||
# Now try it using the fully qualified name
|
||||
parser = mkparser
|
||||
klass = parser.find_hostclass("", "both::sub")
|
||||
assert_instance_of(Puppet::Resource::Type, klass, "Did not autoload sub class from module init file with no namespace")
|
||||
assert_equal("both::sub", klass.name, "Incorrect class was returned")
|
||||
|
||||
|
||||
# Now try it with the class in a different file
|
||||
parser = mkparser
|
||||
modname = "separate"
|
||||
name = "sub"
|
||||
mk_module(modname, :init => %w{separate}, :sub => %w{separate::sub})
|
||||
|
||||
# First try it with a namespace
|
||||
klass = parser.find_hostclass("separate", name)
|
||||
assert_instance_of(Puppet::Resource::Type, klass, "Did not autoload sub class from separate file with a namespace")
|
||||
assert_equal("separate::sub", klass.name, "Incorrect class was returned")
|
||||
|
||||
# Now try it using the fully qualified name
|
||||
parser = mkparser
|
||||
klass = parser.find_hostclass("", "separate::sub")
|
||||
assert_instance_of(Puppet::Resource::Type, klass, "Did not autoload sub class from separate file with no namespace")
|
||||
assert_equal("separate::sub", klass.name, "Incorrect class was returned")
|
||||
|
||||
# Now make sure we don't get a failure when there's no module file
|
||||
parser = mkparser
|
||||
modname = "alone"
|
||||
name = "sub"
|
||||
mk_module(modname, :sub => %w{alone::sub})
|
||||
|
||||
# First try it with a namespace
|
||||
assert_nothing_raised("Could not autoload file when module file is missing") do
|
||||
klass = parser.find_hostclass("alone", name)
|
||||
end
|
||||
assert_instance_of(Puppet::Resource::Type, klass, "Did not autoload sub class from alone file with a namespace")
|
||||
assert_equal("alone::sub", klass.name, "Incorrect class was returned")
|
||||
|
||||
# Now try it using the fully qualified name
|
||||
parser = mkparser
|
||||
klass = parser.find_hostclass("", "alone::sub")
|
||||
assert_instance_of(Puppet::Resource::Type, klass, "Did not autoload sub class from alone file with no namespace")
|
||||
assert_equal("alone::sub", klass.name, "Incorrect class was returned")
|
||||
|
||||
# and with the definition in its own file
|
||||
name = "mymod"
|
||||
mk_module(name, :define => true, :mydefine => ["mymod::mydefine"])
|
||||
|
||||
klass = parser.find_definition("", "mymod::mydefine")
|
||||
assert_instance_of(Puppet::Resource::Type, klass, "Did not autoload definition from its own file")
|
||||
assert_equal("mymod::mydefine", klass.name, "Incorrect definition was returned")
|
||||
end
|
||||
|
||||
# Make sure class, node, and define methods are case-insensitive
|
||||
def test_structure_case_insensitivity
|
||||
parser = mkparser
|
||||
|
|
Загрузка…
Ссылка в новой задаче