summaryrefslogtreecommitdiffstats
path: root/spec/unit/parser
diff options
context:
space:
mode:
authorLuke Kanies <luke@puppetlabs.com>2010-05-10 23:50:05 -0700
committertest branch <puppet-dev@googlegroups.com>2010-02-17 06:50:53 -0800
commit2c153b12921d67354ea0968ad81590a124ac8b75 (patch)
treee1667858786446a5a56306823663960c2ec4905b /spec/unit/parser
parent052f98fdac20af4593372ecedaa13af97664a482 (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.rb88
-rwxr-xr-xspec/unit/parser/compiler.rb11
-rwxr-xr-xspec/unit/parser/lexer.rb4
-rw-r--r--spec/unit/parser/relationship.rb70
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