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:
Luke Kanies 2010-04-28 21:52:06 -07:00 коммит произвёл test branch
Родитель f66095d35b
Коммит d61a69a0e5
10 изменённых файлов: 1280 добавлений и 1183 удалений

Просмотреть файл

@ -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