diff options
| author | Luke Kanies <luke@madstop.com> | 2008-02-11 17:24:02 -0600 |
|---|---|---|
| committer | Luke Kanies <luke@madstop.com> | 2008-02-11 17:24:02 -0600 |
| commit | 6a4cf6c978e8c8aebba4ed0f16d3de7bb31a0ce0 (patch) | |
| tree | df96556dd073aa5d0c23c735a2456da8f144f6b9 | |
| parent | 3b740ff7a6ab7127ec5e4935782c33245687c429 (diff) | |
| download | puppet-6a4cf6c978e8c8aebba4ed0f16d3de7bb31a0ce0.tar.gz puppet-6a4cf6c978e8c8aebba4ed0f16d3de7bb31a0ce0.tar.xz puppet-6a4cf6c978e8c8aebba4ed0f16d3de7bb31a0ce0.zip | |
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.
| -rw-r--r-- | CHANGELOG | 11 | ||||
| -rw-r--r-- | lib/puppet/node/catalog.rb | 5 | ||||
| -rw-r--r-- | lib/puppet/parser/ast/definition.rb | 31 | ||||
| -rw-r--r-- | lib/puppet/parser/ast/hostclass.rb | 14 | ||||
| -rw-r--r-- | lib/puppet/parser/compile.rb | 10 | ||||
| -rwxr-xr-x | spec/unit/parser/ast/hostclass.rb | 131 | ||||
| -rwxr-xr-x | spec/unit/parser/compile.rb | 206 | ||||
| -rwxr-xr-x | test/language/ast/hostclass.rb | 184 |
8 files changed, 264 insertions, 328 deletions
@@ -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 diff --git a/lib/puppet/node/catalog.rb b/lib/puppet/node/catalog.rb index 03187cc94..f93d2786d 100644 --- a/lib/puppet/node/catalog.rb +++ b/lib/puppet/node/catalog.rb @@ -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 diff --git a/lib/puppet/parser/ast/definition.rb b/lib/puppet/parser/ast/definition.rb index bf57942d7..e3f6414c3 100644 --- a/lib/puppet/parser/ast/definition.rb +++ b/lib/puppet/parser/ast/definition.rb @@ -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,23 +96,21 @@ class Puppet::Parser::AST::Definition < Puppet::Parser::AST::Branch # Hunt down our class object. def parentobj - if @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] - end + return nil unless @parentclass - if tmp == self - parsefail "Parent classes must have dissimilar names" - end + # Cache our result, since it should never change. + unless defined?(@parentobj) + unless tmp = find_parentclass + parsefail "Could not find %s parent %s" % [self.class.name, @parentclass] + end - @parentobj = tmp + if tmp == self + parsefail "Parent classes must have dissimilar names" end - @parentobj - else - nil + + @parentobj = tmp end + @parentobj end # Create a new subscope in which to evaluate our code. diff --git a/lib/puppet/parser/ast/hostclass.rb b/lib/puppet/parser/ast/hostclass.rb index 251d5eba6..4f2d00f0c 100644 --- a/lib/puppet/parser/ast/hostclass.rb +++ b/lib/puppet/parser/ast/hostclass.rb @@ -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 diff --git a/lib/puppet/parser/compile.rb b/lib/puppet/parser/compile.rb index 46ce1cb9b..bceead271 100644 --- a/lib/puppet/parser/compile.rb +++ b/lib/puppet/parser/compile.rb @@ -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 diff --git a/spec/unit/parser/ast/hostclass.rb b/spec/unit/parser/ast/hostclass.rb new file mode 100755 index 000000000..b1e8a48ea --- /dev/null +++ b/spec/unit/parser/ast/hostclass.rb @@ -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 diff --git a/spec/unit/parser/compile.rb b/spec/unit/parser/compile.rb index d495ac343..ff205f7a5 100755 --- a/spec/unit/parser/compile.rb +++ b/spec/unit/parser/compile.rb @@ -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]' - end - - it "should create a resource for each found class" do - @compile.catalog.stubs(:tag) - - @compile.stubs :store_resource - - Puppet::Parser::Resource.expects(:new).with(:scope => @scope, :source => @scope.source, :title => "my::class", :type => "class").returns(@resource) - @compile.evaluate_classes(%w{myclass}, @scope) + @resource = stub 'resource', :ref => "Class[myclass]" end - it "should store each created resource in the compile" do + it "should evaluate each class" 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") + @class.expects(:evaluate).with(@scope) - @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 diff --git a/test/language/ast/hostclass.rb b/test/language/ast/hostclass.rb deleted file mode 100755 index 7697317a6..000000000 --- a/test/language/ast/hostclass.rb +++ /dev/null @@ -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 |
