Fixed #1030 - class and definition evaluation has been significantly

refactored, fixing this problem and making the whole interplay
between the classes, definitions, and nodes, and the Compile class much
cleaner.
This commit is contained in:
Luke Kanies 2008-02-11 17:24:02 -06:00
Родитель 3b740ff7a6
Коммит 6a4cf6c978
8 изменённых файлов: 265 добавлений и 329 удалений

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

@ -1,6 +1,11 @@
Exec resources must now have unique names. This is easily accomplished
by just specifying a unique name with whatever (unique or otherwise)
command you need.
Fixed #1030 - class and definition evaluation has been significantly
refactored, fixing this problem and making the whole interplay
between the classes, definitions, and nodes, and the Compile class much
cleaner.
Exec resources must now have unique names. This is easily accomplished by
just specifying a unique name with whatever (unique or otherwise) command
you need.
Fixed #989 -- missing CRL files are correctly ignored, and the
value should be set to 'false' to explicitly not look for these

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

@ -395,6 +395,11 @@ class Puppet::Node::Catalog < Puppet::PGraph
nil
end
# Does our tag list include this tag?
def tagged?(tag)
@tags.include?(tag)
end
# Return the list of tags.
def tags
@tags.dup

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

@ -24,7 +24,12 @@ class Puppet::Parser::AST::Definition < Puppet::Parser::AST::Branch
# Create a resource that knows how to evaluate our actual code.
def evaluate(scope)
resource = Puppet::Parser::Resource.new(:type => "class", :title => klass.classname, :scope => scope, :source => scope.source)
# Do nothing if the resource already exists; this provides the singleton nature classes need.
return if scope.catalog.resource(:class, self.classname)
resource = Puppet::Parser::Resource.new(:type => "class", :title => self.classname, :scope => scope, :source => scope.source)
scope.catalog.tag(*resource.tags)
scope.compile.store_resource(scope, resource)
@ -91,11 +96,12 @@ class Puppet::Parser::AST::Definition < Puppet::Parser::AST::Branch
# Hunt down our class object.
def parentobj
if @parentclass
return nil unless @parentclass
# Cache our result, since it should never change.
unless defined?(@parentobj)
unless tmp = find_parentclass
parsefail "Could not find %s %s" % [self.class.name, @parentclass]
parsefail "Could not find %s parent %s" % [self.class.name, @parentclass]
end
if tmp == self
@ -105,9 +111,6 @@ class Puppet::Parser::AST::Definition < Puppet::Parser::AST::Branch
@parentobj = tmp
end
@parentobj
else
nil
end
end
# Create a new subscope in which to evaluate our code.

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

@ -18,6 +18,15 @@ class Puppet::Parser::AST::HostClass < Puppet::Parser::AST::Definition
end
end
# Make sure our parent class has been evaluated, if we have one.
def evaluate(scope)
if parentclass and ! scope.catalog.resource(:class, parentclass)
resource = parentobj.evaluate(scope)
end
super
end
# Evaluate the code associated with this class.
def evaluate_code(resource)
scope = resource.scope
@ -58,11 +67,6 @@ class Puppet::Parser::AST::HostClass < Puppet::Parser::AST::Definition
end
end
def initialize(options)
@parentclass = nil
super
end
def parent_scope(scope, klass)
if s = scope.compile.class_scope(klass)
return s

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

@ -115,16 +115,11 @@ class Puppet::Parser::Compile
if klass = scope.findclass(name)
found << name and next if class_scope(klass)
# Create a resource to model this class, and then add it to the list
# of resources.
resource = Puppet::Parser::Resource.new(:type => "class", :title => klass.classname, :scope => scope, :source => scope.source)
store_resource(scope, resource)
resource = klass.evaluate(scope)
# If they've disabled lazy evaluation (which the :include function does),
# then evaluate our resource immediately.
resource.evaluate unless lazy_evaluate
@catalog.tag(klass.classname)
found << name
else
Puppet.info "Could not find class %s for %s" % [name, node.name]
@ -412,9 +407,6 @@ class Puppet::Parser::Compile
# but they each refer back to the scope that created them.
@collections = []
# A list of tags we've generated; most class names.
@tags = []
# A graph for maintaining scope relationships.
@scope_graph = Puppet::SimpleGraph.new

131
spec/unit/parser/ast/hostclass.rb Executable file
Просмотреть файл

@ -0,0 +1,131 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../../spec_helper'
module HostClassTesting
def setup
@node = Puppet::Node.new "testnode"
@parser = Puppet::Parser::Parser.new :environment => "development"
@scope_resource = stub 'scope_resource', :builtin? => true
@compile = Puppet::Parser::Compile.new(@node, @parser)
@scope = @compile.topscope
end
end
describe Puppet::Parser::AST::HostClass, "when evaluating" do
include HostClassTesting
before do
@top = @parser.newclass "top"
@middle = @parser.newclass "middle", :parent => "top"
end
it "should create a resource that references itself" do
@top.evaluate(@scope)
@compile.catalog.resource(:class, "top").should be_instance_of(Puppet::Parser::Resource)
end
it "should evaluate the parent class if one exists" do
@middle.evaluate(@scope)
@compile.catalog.resource(:class, "top").should be_instance_of(Puppet::Parser::Resource)
end
it "should fail to evaluate if a parent class is defined but cannot be found" do
othertop = @parser.newclass "something", :parent => "yay"
lambda { othertop.evaluate(@scope) }.should raise_error(Puppet::ParseError)
end
it "should not create a new resource if one already exists" do
@compile.catalog.expects(:resource).with(:class, "top").returns("something")
@compile.catalog.expects(:add_resource).never
@top.evaluate(@scope)
end
it "should not create a new parent resource if one already exists and it has a parent class" do
@top.evaluate(@scope)
top_resource = @compile.catalog.resource(:class, "top")
@middle.evaluate(@scope)
@compile.catalog.resource(:class, "top").should equal(top_resource)
end
# #795 - tag before evaluation.
it "should tag the catalog with the resource tags when it is evaluated" do
@middle.evaluate(@scope)
@compile.catalog.should be_tagged("middle")
end
it "should tag the catalog with the parent class tags when it is evaluated" do
@middle.evaluate(@scope)
@compile.catalog.should be_tagged("top")
end
end
describe Puppet::Parser::AST::HostClass, "when evaluating code" do
include HostClassTesting
before do
@top_resource = stub "top_resource"
@top = @parser.newclass "top", :code => @top_resource
@middle_resource = stub "middle_resource"
@middle = @parser.newclass "top::middle", :parent => "top", :code => @middle_resource
end
it "should set its namespace to its fully qualified name" do
@middle.namespace.should == "top::middle"
end
it "should evaluate the code referred to by the class" do
@top_resource.expects(:safeevaluate)
resource = @top.evaluate(@scope)
@top.evaluate_code(resource)
end
it "should evaluate the parent class's code if it has a parent" do
@top_resource.expects(:safeevaluate)
@middle_resource.expects(:safeevaluate)
resource = @middle.evaluate(@scope)
@middle.evaluate_code(resource)
end
it "should not evaluate the parent class's code if the parent has already been evaluated" do
@top_resource.stubs(:safeevaluate)
resource = @top.evaluate(@scope)
@top.evaluate_code(resource)
@top_resource.expects(:safeevaluate).never
@middle_resource.stubs(:safeevaluate)
resource = @middle.evaluate(@scope)
@middle.evaluate_code(resource)
end
it "should use the parent class's scope as its parent scope" do
@top_resource.stubs(:safeevaluate)
@middle_resource.stubs(:safeevaluate)
resource = @middle.evaluate(@scope)
@middle.evaluate_code(resource)
@compile.class_scope(@middle).parent.should equal(@compile.class_scope(@top))
end
it "should add the parent class's namespace to its namespace search path" do
@top_resource.stubs(:safeevaluate)
@middle_resource.stubs(:safeevaluate)
resource = @middle.evaluate(@scope)
@middle.evaluate_code(resource)
@compile.class_scope(@middle).namespaces.should be_include(@top.namespace)
end
end

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

@ -13,6 +13,78 @@ module CompileTesting
end
end
describe Puppet::Parser::Compile do
include CompileTesting
it "should be able to store references to class scopes" do
lambda { @compile.class_set "myname", "myscope" }.should_not raise_error
end
it "should be able to retrieve class scopes by name" do
@compile.class_set "myname", "myscope"
@compile.class_scope("myname").should == "myscope"
end
it "should be able to retrieve class scopes by object" do
klass = mock 'ast_class'
klass.expects(:classname).returns("myname")
@compile.class_set "myname", "myscope"
@compile.class_scope(klass).should == "myscope"
end
it "should be able to return a class list containing all set classes" do
@compile.class_set "", "empty"
@compile.class_set "one", "yep"
@compile.class_set "two", "nope"
@compile.classlist.sort.should == %w{one two}.sort
end
end
describe Puppet::Parser::Compile, " when initializing" do
include CompileTesting
it "should set its node attribute" do
@compile.node.should equal(@node)
end
it "should set its parser attribute" do
@compile.parser.should equal(@parser)
end
it "should detect when ast nodes are absent" do
@compile.ast_nodes?.should be_false
end
it "should detect when ast nodes are present" do
@parser.nodes["testing"] = "yay"
@compile.ast_nodes?.should be_true
end
end
describe Puppet::Parser::Compile, "when managing scopes" do
include CompileTesting
it "should create a top scope" do
@compile.topscope.should be_instance_of(Puppet::Parser::Scope)
end
it "should be able to create new scopes" do
@compile.newscope(@compile.topscope).should be_instance_of(Puppet::Parser::Scope)
end
it "should correctly set the level of newly created scopes" do
@compile.newscope(@compile.topscope, :level => 5).level.should == 5
end
it "should set the parent scope of the new scope to be the passed-in parent" do
scope = mock 'scope'
newscope = @compile.newscope(scope)
@compile.parent(newscope).should equal(scope)
end
end
describe Puppet::Parser::Compile, " when compiling" do
include CompileTesting
@ -174,21 +246,6 @@ describe Puppet::Parser::Compile, " when compiling" do
end
end
describe Puppet::Parser::Compile, " when evaluating classes" do
include CompileTesting
it "should fail if there's no source listed for the scope" do
scope = stub 'scope', :source => nil
proc { @compile.evaluate_classes(%w{one two}, scope) }.should raise_error(Puppet::DevError)
end
it "should tag the catalog with the name of each not-found class" do
@compile.catalog.expects(:tag).with("notfound")
@scope.expects(:findclass).with("notfound").returns(nil)
@compile.evaluate_classes(%w{notfound}, @scope)
end
end
describe Puppet::Parser::Compile, " when evaluating collections" do
include CompileTesting
@ -235,6 +292,20 @@ describe Puppet::Parser::Compile, " when evaluating collections" do
end
end
describe Puppet::Parser::Compile, "when told to evaluate missing classes" do
include CompileTesting
it "should fail if there's no source listed for the scope" do
scope = stub 'scope', :source => nil
proc { @compile.evaluate_classes(%w{one two}, scope) }.should raise_error(Puppet::DevError)
end
it "should tag the catalog with the name of each not-found class" do
@compile.catalog.expects(:tag).with("notfound")
@scope.expects(:findclass).with("notfound").returns(nil)
@compile.evaluate_classes(%w{notfound}, @scope)
end
end
describe Puppet::Parser::Compile, " when evaluating found classes" do
include CompileTesting
@ -243,53 +314,33 @@ describe Puppet::Parser::Compile, " when evaluating found classes" do
@class = stub 'class', :classname => "my::class"
@scope.stubs(:findclass).with("myclass").returns(@class)
@resource = stub 'resource', :ref => 'Class[myclass]'
@resource = stub 'resource', :ref => "Class[myclass]"
end
it "should create a resource for each found class" do
it "should evaluate each class" do
@compile.catalog.stubs(:tag)
@compile.stubs :store_resource
@class.expects(:evaluate).with(@scope)
Puppet::Parser::Resource.expects(:new).with(:scope => @scope, :source => @scope.source, :title => "my::class", :type => "class").returns(@resource)
@compile.evaluate_classes(%w{myclass}, @scope)
end
it "should store each created resource in the compile" do
@compile.catalog.stubs(:tag)
@compile.expects(:store_resource).with(@scope, @resource)
Puppet::Parser::Resource.stubs(:new).returns(@resource)
@compile.evaluate_classes(%w{myclass}, @scope)
end
it "should tag the catalog with the fully-qualified name of each found class" do
@compile.catalog.expects(:tag).with("my::class")
@compile.stubs(:store_resource)
Puppet::Parser::Resource.stubs(:new).returns(@resource)
@compile.evaluate_classes(%w{myclass}, @scope)
end
it "should not evaluate the resources created for found classes unless asked" do
@compile.catalog.stubs(:tag)
@compile.stubs(:store_resource)
@resource.expects(:evaluate).never
Puppet::Parser::Resource.stubs(:new).returns(@resource)
@class.expects(:evaluate).returns(@resource)
@compile.evaluate_classes(%w{myclass}, @scope)
end
it "should immediately evaluate the resources created for found classes when asked" do
@compile.catalog.stubs(:tag)
@compile.stubs(:store_resource)
@resource.expects(:evaluate)
@class.expects(:evaluate).returns(@resource)
Puppet::Parser::Resource.stubs(:new).returns(@resource)
@compile.evaluate_classes(%w{myclass}, @scope, false)
end
@ -313,6 +364,7 @@ describe Puppet::Parser::Compile, " when evaluating found classes" do
@scope.stubs(:findclass).with("notfound").returns(nil)
Puppet::Parser::Resource.stubs(:new).returns(@resource)
@class.stubs :evaluate
@compile.evaluate_classes(%w{myclass notfound}, @scope).should == %w{myclass}
end
end
@ -411,78 +463,6 @@ describe Puppet::Parser::Compile, " when evaluating AST nodes with AST nodes pre
end
end
describe Puppet::Parser::Compile, " when initializing" do
include CompileTesting
it "should set its node attribute" do
@compile.node.should equal(@node)
end
it "should set its parser attribute" do
@compile.parser.should equal(@parser)
end
it "should detect when ast nodes are absent" do
@compile.ast_nodes?.should be_false
end
it "should detect when ast nodes are present" do
@parser.nodes["testing"] = "yay"
@compile.ast_nodes?.should be_true
end
end
describe Puppet::Parser::Compile do
include CompileTesting
it "should be able to store references to class scopes" do
lambda { @compile.class_set "myname", "myscope" }.should_not raise_error
end
it "should be able to retrieve class scopes by name" do
@compile.class_set "myname", "myscope"
@compile.class_scope("myname").should == "myscope"
end
it "should be able to retrieve class scopes by object" do
klass = mock 'ast_class'
klass.expects(:classname).returns("myname")
@compile.class_set "myname", "myscope"
@compile.class_scope(klass).should == "myscope"
end
it "should be able to return a class list containing all set classes" do
@compile.class_set "", "empty"
@compile.class_set "one", "yep"
@compile.class_set "two", "nope"
@compile.classlist.sort.should == %w{one two}.sort
end
end
describe Puppet::Parser::Compile, "when managing scopes" do
include CompileTesting
it "should create a top scope" do
@compile.topscope.should be_instance_of(Puppet::Parser::Scope)
end
it "should be able to create new scopes" do
@compile.newscope(@compile.topscope).should be_instance_of(Puppet::Parser::Scope)
end
it "should correctly set the level of newly created scopes" do
@compile.newscope(@compile.topscope, :level => 5).level.should == 5
end
it "should set the parent scope of the new scope to be the passed-in parent" do
scope = mock 'scope'
newscope = @compile.newscope(scope)
@compile.parent(newscope).should equal(scope)
end
end
describe Puppet::Parser::Compile, "when storing compiled resources" do
include CompileTesting

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

@ -1,184 +0,0 @@
#!/usr/bin/env ruby
#
# Created by Luke A. Kanies on 2006-02-20.
# Copyright (c) 2006. All rights reserved.
require File.dirname(__FILE__) + '/../../lib/puppettest'
require 'puppettest'
require 'puppettest/parsertesting'
require 'puppettest/resourcetesting'
require 'mocha'
class TestASTHostClass < Test::Unit::TestCase
include PuppetTest
include PuppetTest::ParserTesting
include PuppetTest::ResourceTesting
AST = Puppet::Parser::AST
def test_hostclass
scope = mkscope
parser = scope.compile.parser
# Create the class we're testing, first with no parent
klass = parser.newclass "first",
:code => AST::ASTArray.new(
:children => [resourcedef("file", "/tmp",
"owner" => "nobody", "mode" => "755")]
)
resource = Puppet::Parser::Resource.new(:type => "class", :title => "first", :scope => scope)
assert_nothing_raised do
klass.evaluate_code(resource)
end
# Then try it again
assert_nothing_raised do
klass.evaluate_code(resource)
end
assert(scope.compile.class_scope(klass), "Class was not considered evaluated")
tmp = scope.findresource("File[/tmp]")
assert(tmp, "Could not find file /tmp")
assert_equal("nobody", tmp[:owner])
assert_equal("755", tmp[:mode])
# Now create a couple more classes.
newbase = parser.newclass "newbase",
:code => AST::ASTArray.new(
:children => [resourcedef("file", "/tmp/other",
"owner" => "nobody", "mode" => "644")]
)
newsub = parser.newclass "newsub",
:parent => "newbase",
:code => AST::ASTArray.new(
:children => [resourcedef("file", "/tmp/yay",
"owner" => "nobody", "mode" => "755"),
resourceoverride("file", "/tmp/other",
"owner" => "daemon")
]
)
# Override a different variable in the top scope.
moresub = parser.newclass "moresub",
:parent => "newbase",
:code => AST::ASTArray.new(
:children => [resourceoverride("file", "/tmp/other",
"mode" => "755")]
)
assert_nothing_raised do
newsub.evaluate_code(resource)
end
assert_nothing_raised do
moresub.evaluate_code(resource)
end
assert(scope.compile.class_scope(newbase), "Did not eval newbase")
assert(scope.compile.class_scope(newsub), "Did not eval newsub")
yay = scope.findresource("File[/tmp/yay]")
assert(yay, "Did not find file /tmp/yay")
assert_equal("nobody", yay[:owner])
assert_equal("755", yay[:mode])
other = scope.findresource("File[/tmp/other]")
assert(other, "Did not find file /tmp/other")
assert_equal("daemon", other[:owner])
assert_equal("755", other[:mode])
end
# Make sure that classes set their namespaces to themselves. This
# way they start looking for definitions in their own namespace.
def test_hostclass_namespace
scope = mkscope
parser = scope.compile.parser
# Create a new class
klass = nil
assert_nothing_raised do
klass = parser.newclass "funtest"
end
# Now define a definition in that namespace
define = nil
assert_nothing_raised do
define = parser.newdefine "funtest::mydefine"
end
assert_equal("funtest", klass.namespace,
"component namespace was not set in the class")
assert_equal("funtest", define.namespace,
"component namespace was not set in the definition")
newscope = klass.subscope(scope, mock("resource"))
assert_equal(["funtest"], newscope.namespaces,
"Scope did not inherit namespace")
# Now make sure we can find the define
assert(newscope.finddefine("mydefine"),
"Could not find definition in my enclosing class")
end
# Make sure that our scope is a subscope of the parentclass's scope.
# At the same time, make sure definitions in the parent class can be
# found within the subclass (#517).
def test_parent_scope_from_parentclass
scope = mkscope
parser = scope.compile.parser
source = parser.newclass ""
parser.newclass("base")
fun = parser.newdefine("base::fun")
parser.newclass("middle", :parent => "base")
parser.newclass("sub", :parent => "middle")
scope = mkscope :parser => parser
ret = nil
assert_nothing_raised do
ret = scope.compile.evaluate_classes(["sub"], scope)
end
scope.compile.send(:evaluate_generators)
subscope = scope.compile.class_scope(scope.findclass("sub"))
assert(subscope, "could not find sub scope")
mscope = scope.compile.class_scope(scope.findclass("middle"))
assert(mscope, "could not find middle scope")
pscope = scope.compile.class_scope(scope.findclass("base"))
assert(pscope, "could not find parent scope")
assert(pscope == mscope.parent, "parent scope of middle was not set correctly")
assert(mscope == subscope.parent, "parent scope of sub was not set correctly")
result = mscope.finddefine("fun")
assert(result, "could not find parent-defined definition from middle")
assert(fun == result, "found incorrect parent-defined definition from middle")
result = subscope.finddefine("fun")
assert(result, "could not find parent-defined definition from sub")
assert(fun == result, "found incorrect parent-defined definition from sub")
end
# #795 - make sure the subclass's tags get set before we
# evaluate the parent class, so we can be sure that the parent
# class can switch based on the sub classes.
def test_tags_set_before_parent_is_evaluated
scope = mkscope
parser = scope.compile.parser
base = parser.newclass "base"
sub = parser.newclass "sub", :parent => "base"
base.expects(:evaluate_code).with do |*args|
assert(scope.catalog.tags.include?("sub"), "Did not tag with sub class name before evaluating base class")
base.evaluate_code(*args)
true
end
sub.evaluate_code scope.resource
end
end