diff options
| author | Luke Kanies <luke@puppetlabs.com> | 2010-05-10 23:50:05 -0700 |
|---|---|---|
| committer | test branch <puppet-dev@googlegroups.com> | 2010-02-17 06:50:53 -0800 |
| commit | 2c153b12921d67354ea0968ad81590a124ac8b75 (patch) | |
| tree | e1667858786446a5a56306823663960c2ec4905b /spec/unit/parser | |
| parent | 052f98fdac20af4593372ecedaa13af97664a482 (diff) | |
Fixing #448 - relationships have their own syntax
You can now specify relationships directly in the language:
File[/foo] -> Service[bar]
Specifies a normal dependency while:
File[/foo] ~> Service[bar]
Specifies a subscription.
You can also do relationship chaining, specifying multiple
relationships on a single line:
File[/foo] -> Package[baz] -> Service[bar]
Note that while it's confusing, you don't have to have all
of the arrows be the same direction:
File[/foo] -> Service[bar] <~ Package[baz]
This can provide some succinctness at the cost of readability.
You can also specify full resources, rather than just
resource refs:
file { "/foo": ensure => present } -> package { bar: ensure => installed }
But wait! There's more! You can also specify a subscription on either side
of the relationship marker:
yumrepo { foo: .... }
package { bar: provider => yum, ... }
Yumrepo <| |> -> Package <| provider == yum |>
This, finally, provides easy many to many relationships in Puppet, but it also opens
the door to massive dependency cycles. This last feature is a very powerful stick,
and you can considerably hurt yourself with it.
Signed-off-by: Luke Kanies <luke@puppetlabs.com>
Diffstat (limited to 'spec/unit/parser')
| -rw-r--r-- | spec/unit/parser/ast/relationship.rb | 88 | ||||
| -rwxr-xr-x | spec/unit/parser/compiler.rb | 11 | ||||
| -rwxr-xr-x | spec/unit/parser/lexer.rb | 4 | ||||
| -rw-r--r-- | spec/unit/parser/relationship.rb | 70 |
4 files changed, 172 insertions, 1 deletions
diff --git a/spec/unit/parser/ast/relationship.rb b/spec/unit/parser/ast/relationship.rb new file mode 100644 index 000000000..acb46e4ce --- /dev/null +++ b/spec/unit/parser/ast/relationship.rb @@ -0,0 +1,88 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../../spec_helper' + +describe Puppet::Parser::AST::Relationship do + before do + @class = Puppet::Parser::AST::Relationship + end + + it "should set its 'left' and 'right' arguments accordingly" do + dep = @class.new(:left, :right, '->') + dep.left.should == :left + dep.right.should == :right + end + + it "should set its arrow to whatever arrow is passed" do + @class.new(:left, :right, '->').arrow.should == '->' + end + + it "should set its type to :relationship if the relationship type is '<-'" do + @class.new(:left, :right, '<-').type.should == :relationship + end + + it "should set its type to :relationship if the relationship type is '->'" do + @class.new(:left, :right, '->').type.should == :relationship + end + + it "should set its type to :subscription if the relationship type is '~>'" do + @class.new(:left, :right, '~>').type.should == :subscription + end + + it "should set its type to :subscription if the relationship type is '<~'" do + @class.new(:left, :right, '<~').type.should == :subscription + end + + it "should set its line and file if provided" do + dep = @class.new(:left, :right, '->', :line => 50, :file => "/foo") + dep.line.should == 50 + dep.file.should == "/foo" + end + + describe "when evaluating" do + before do + @compiler = Puppet::Parser::Compiler.new(Puppet::Node.new("foo")) + @scope = Puppet::Parser::Scope.new(:compiler => @compiler) + end + + it "should create a relationship with the evaluated source and target and add it to the scope" do + source = stub 'source', :safeevaluate => :left + target = stub 'target', :safeevaluate => :right + @class.new(source, target, '->').evaluate(@scope) + @compiler.relationships[0].source.should == :left + @compiler.relationships[0].target.should == :right + end + + describe "a chained relationship" do + before do + @left = stub 'left', :safeevaluate => :left + @middle = stub 'middle', :safeevaluate => :middle + @right = stub 'right', :safeevaluate => :right + @first = @class.new(@left, @middle, '->') + @second = @class.new(@first, @right, '->') + end + + it "should evaluate the relationship to the left" do + @first.expects(:evaluate).with(@scope).returns Puppet::Parser::Relationship.new(:left, :right, :relationship) + + @second.evaluate(@scope) + end + + it "should use the right side of the left relationship as its source" do + @second.evaluate(@scope) + + @compiler.relationships[0].source.should == :left + @compiler.relationships[0].target.should == :middle + @compiler.relationships[1].source.should == :middle + @compiler.relationships[1].target.should == :right + end + + it "should only evaluate a given AST node once" do + @left.expects(:safeevaluate).once.returns :left + @middle.expects(:safeevaluate).once.returns :middle + @right.expects(:safeevaluate).once.returns :right + @second.evaluate(@scope) + end + end + end +end diff --git a/spec/unit/parser/compiler.rb b/spec/unit/parser/compiler.rb index 6fd4d1fb5..c36113ff6 100755 --- a/spec/unit/parser/compiler.rb +++ b/spec/unit/parser/compiler.rb @@ -134,7 +134,7 @@ describe Puppet::Parser::Compiler do def compile_methods [:set_node_parameters, :evaluate_main, :evaluate_ast_node, :evaluate_node_classes, :evaluate_generators, :fail_on_unevaluated, - :finish, :store, :extract] + :finish, :store, :extract, :evaluate_relationships] end # Stub all of the main compile methods except the ones we're specifically interested in. @@ -394,6 +394,15 @@ describe Puppet::Parser::Compiler do end end + describe "when evaluating relationships" do + it "should evaluate each relationship with its catalog" do + dep = stub 'dep' + dep.expects(:evaluate).with(@compiler.catalog) + @compiler.add_relationship dep + @compiler.evaluate_relationships + end + end + describe "when told to evaluate missing classes" do it "should fail if there's no source listed for the scope" do diff --git a/spec/unit/parser/lexer.rb b/spec/unit/parser/lexer.rb index 2e58ef485..b1524f1e0 100755 --- a/spec/unit/parser/lexer.rb +++ b/spec/unit/parser/lexer.rb @@ -174,6 +174,10 @@ describe Puppet::Parser::Lexer::TOKENS do :RSHIFT => '>>', :MATCH => '=~', :NOMATCH => '!~', + :IN_EDGE => '->', + :OUT_EDGE => '<-', + :IN_EDGE_SUB => '~>', + :OUT_EDGE_SUB => '<~', }.each do |name, string| it "should have a token named #{name.to_s}" do Puppet::Parser::Lexer::TOKENS[name].should_not be_nil diff --git a/spec/unit/parser/relationship.rb b/spec/unit/parser/relationship.rb new file mode 100644 index 000000000..bd4ff5632 --- /dev/null +++ b/spec/unit/parser/relationship.rb @@ -0,0 +1,70 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +require 'puppet/parser/relationship' + +describe Puppet::Parser::Relationship do + before do + @source = Puppet::Resource.new(:mytype, "source") + @target = Puppet::Resource.new(:mytype, "target") + @dep = Puppet::Parser::Relationship.new(@source, @target, :relationship) + end + + describe "when evaluating" do + before do + @catalog = Puppet::Resource::Catalog.new + @catalog.add_resource(@source) + @catalog.add_resource(@target) + end + + it "should fail if the source resource cannot be found" do + @catalog = Puppet::Resource::Catalog.new + @catalog.add_resource @target + lambda { @dep.evaluate(@catalog) }.should raise_error(ArgumentError) + end + + it "should fail if the target resource cannot be found" do + @catalog = Puppet::Resource::Catalog.new + @catalog.add_resource @source + lambda { @dep.evaluate(@catalog) }.should raise_error(ArgumentError) + end + + it "should add the target as a 'before' value if the type is 'relationship'" do + @dep.type = :relationship + @dep.evaluate(@catalog) + @source[:before].should be_include("Mytype[target]") + end + + it "should add the target as a 'notify' value if the type is 'subscription'" do + @dep.type = :subscription + @dep.evaluate(@catalog) + @source[:notify].should be_include("Mytype[target]") + end + + it "should supplement rather than clobber existing relationship values" do + @source[:before] = "File[/bar]" + @dep.evaluate(@catalog) + @source[:before].should be_include("Mytype[target]") + @source[:before].should be_include("File[/bar]") + end + + it "should use the collected retargets if the target is a Collector" do + orig_target = @target + @target = Puppet::Parser::Collector.new(stub("scope"), :file, "equery", "vquery", :virtual) + @target.collected[:foo] = @target + @dep.evaluate(@catalog) + + @source[:before].should be_include("Mytype[target]") + end + + it "should use the collected resources if the source is a Collector" do + orig_source = @source + @source = Puppet::Parser::Collector.new(stub("scope"), :file, "equery", "vquery", :virtual) + @source.collected[:foo] = @source + @dep.evaluate(@catalog) + + orig_source[:before].should be_include("Mytype[target]") + end + end +end |
