diff options
Diffstat (limited to 'spec/integration/parser')
-rw-r--r-- | spec/integration/parser/catalog_spec.rb | 20 | ||||
-rw-r--r-- | spec/integration/parser/class_spec.rb | 37 | ||||
-rwxr-xr-x | spec/integration/parser/collector_spec.rb | 309 | ||||
-rwxr-xr-x | spec/integration/parser/compiler_spec.rb | 774 | ||||
-rw-r--r-- | spec/integration/parser/conditionals_spec.rb | 117 | ||||
-rw-r--r-- | spec/integration/parser/future_compiler_spec.rb | 396 | ||||
-rw-r--r-- | spec/integration/parser/node_spec.rb | 185 | ||||
-rw-r--r-- | spec/integration/parser/resource_expressions_spec.rb | 286 | ||||
-rwxr-xr-x | spec/integration/parser/ruby_manifest_spec.rb | 4 | ||||
-rw-r--r-- | spec/integration/parser/scope_spec.rb | 245 |
10 files changed, 1744 insertions, 629 deletions
diff --git a/spec/integration/parser/catalog_spec.rb b/spec/integration/parser/catalog_spec.rb index e37eb591a..39aeb394e 100644 --- a/spec/integration/parser/catalog_spec.rb +++ b/spec/integration/parser/catalog_spec.rb @@ -75,6 +75,14 @@ describe "A catalog" do expect(resources_in(agent_catalog)).to_not include(*exported_resources) end end + end + + describe 'using classic parser' do + before :each do + Puppet[:parser] = 'current' + end + it_behaves_like 'when compiled' do + end it "compiles resource creation from appended array as two separate resources" do # moved here from acceptance test "jeff_append_to_array.rb" @@ -92,14 +100,6 @@ describe "A catalog" do end end - describe 'using classic parser' do - before :each do - Puppet[:parser] = 'current' - end - it_behaves_like 'when compiled' do - end - end - describe 'using future parser' do before :each do Puppet[:parser] = 'future' @@ -113,9 +113,9 @@ describe "A catalog" do end def master_and_agent_catalogs_for(manifest) - master_catalog = Puppet::Resource::Catalog::Compiler.new.filter(compile_to_catalog(manifest)) + compiler = Puppet::Resource::Catalog::Compiler.new + master_catalog = compiler.filter(compile_to_catalog(manifest)) agent_catalog = Puppet::Resource::Catalog.convert_from(:pson, master_catalog.render(:pson)) - [master_catalog, agent_catalog] end diff --git a/spec/integration/parser/class_spec.rb b/spec/integration/parser/class_spec.rb new file mode 100644 index 000000000..9f63eb083 --- /dev/null +++ b/spec/integration/parser/class_spec.rb @@ -0,0 +1,37 @@ +require 'spec_helper' +require 'puppet_spec/language' + +describe "Class expressions" do + extend PuppetSpec::Language + + before :each do + Puppet[:parser] = 'future' + end + + produces( + "class hi { }" => '!defined(Class[Hi])', + + "class hi { } include hi" => 'defined(Class[Hi])', + "include(hi) class hi { }" => 'defined(Class[Hi])', + + "class hi { } class { hi: }" => 'defined(Class[Hi])', + "class { hi: } class hi { }" => 'defined(Class[Hi])', + + "class bye { } class hi inherits bye { } include hi" => 'defined(Class[Hi]) and defined(Class[Bye])') + + produces(<<-EXAMPLE => 'defined(Notify[foo]) and defined(Notify[bar]) and !defined(Notify[foo::bar])') + class bar { notify { 'bar': } } + class foo::bar { notify { 'foo::bar': } } + class foo inherits bar { notify { 'foo': } } + + include foo + EXAMPLE + + produces(<<-EXAMPLE => 'defined(Notify[foo]) and defined(Notify[bar]) and !defined(Notify[foo::bar])') + class bar { notify { 'bar': } } + class foo::bar { notify { 'foo::bar': } } + class foo inherits ::bar { notify { 'foo': } } + + include foo + EXAMPLE +end diff --git a/spec/integration/parser/collector_spec.rb b/spec/integration/parser/collector_spec.rb index 49ce74583..55ac66a5b 100755 --- a/spec/integration/parser/collector_spec.rb +++ b/spec/integration/parser/collector_spec.rb @@ -14,104 +14,263 @@ describe Puppet::Parser::Collector do messages.should include(*expected_messages) end - it "matches on title" do - expect_the_message_to_be(["the message"], <<-MANIFEST) - @notify { "testing": message => "the message" } + shared_examples_for "virtual resource collection" do + it "matches everything when no query given" do + expect_the_message_to_be(["the other message", "the message"], <<-MANIFEST) + @notify { "testing": message => "the message" } + @notify { "other": message => "the other message" } - Notify <| title == "testing" |> - MANIFEST - end + Notify <| |> + MANIFEST + end - it "matches on other parameters" do - expect_the_message_to_be(["the message"], <<-MANIFEST) - @notify { "testing": message => "the message" } - @notify { "other testing": message => "the wrong message" } + it "matches regular resources " do + expect_the_message_to_be(["changed", "changed"], <<-MANIFEST) + notify { "testing": message => "the message" } + notify { "other": message => "the other message" } - Notify <| message == "the message" |> - MANIFEST - end + Notify <| |> { message => "changed" } + MANIFEST + end - it "allows criteria to be combined with 'and'" do - expect_the_message_to_be(["the message"], <<-MANIFEST) - @notify { "testing": message => "the message" } - @notify { "other": message => "the message" } + it "matches on tags" do + expect_the_message_to_be(["wanted"], <<-MANIFEST) + @notify { "testing": tag => ["one"], message => "wanted" } + @notify { "other": tag => ["two"], message => "unwanted" } - Notify <| title == "testing" and message == "the message" |> - MANIFEST - end + Notify <| tag == one |> + MANIFEST + end - it "allows criteria to be combined with 'or'" do - expect_the_message_to_be(["the message", "other message"], <<-MANIFEST) - @notify { "testing": message => "the message" } - @notify { "other": message => "other message" } - @notify { "yet another": message => "different message" } + it "matches on title" do + expect_the_message_to_be(["the message"], <<-MANIFEST) + @notify { "testing": message => "the message" } - Notify <| title == "testing" or message == "other message" |> - MANIFEST - end + Notify <| title == "testing" |> + MANIFEST + end - it "allows criteria to be combined with 'or'" do - expect_the_message_to_be(["the message", "other message"], <<-MANIFEST) - @notify { "testing": message => "the message" } - @notify { "other": message => "other message" } - @notify { "yet another": message => "different message" } + it "matches on other parameters" do + expect_the_message_to_be(["the message"], <<-MANIFEST) + @notify { "testing": message => "the message" } + @notify { "other testing": message => "the wrong message" } - Notify <| title == "testing" or message == "other message" |> - MANIFEST - end + Notify <| message == "the message" |> + MANIFEST + end - it "allows criteria to be grouped with parens" do - expect_the_message_to_be(["the message", "different message"], <<-MANIFEST) - @notify { "testing": message => "different message", withpath => true } - @notify { "other": message => "the message" } - @notify { "yet another": message => "the message", withpath => true } + it "matches against elements of an array valued parameter" do + expect_the_message_to_be([["the", "message"]], <<-MANIFEST) + @notify { "testing": message => ["the", "message"] } + @notify { "other testing": message => ["not", "here"] } - Notify <| (title == "testing" or message == "the message") and withpath == true |> - MANIFEST - end + Notify <| message == "message" |> + MANIFEST + end - it "does not do anything if nothing matches" do - expect_the_message_to_be([], <<-MANIFEST) - @notify { "testing": message => "different message" } + it "allows criteria to be combined with 'and'" do + expect_the_message_to_be(["the message"], <<-MANIFEST) + @notify { "testing": message => "the message" } + @notify { "other": message => "the message" } - Notify <| title == "does not exist" |> - MANIFEST - end + Notify <| title == "testing" and message == "the message" |> + MANIFEST + end - it "excludes items with inequalities" do - expect_the_message_to_be(["good message"], <<-MANIFEST) - @notify { "testing": message => "good message" } - @notify { "the wrong one": message => "bad message" } + it "allows criteria to be combined with 'or'" do + expect_the_message_to_be(["the message", "other message"], <<-MANIFEST) + @notify { "testing": message => "the message" } + @notify { "other": message => "other message" } + @notify { "yet another": message => "different message" } - Notify <| title != "the wrong one" |> - MANIFEST - end + Notify <| title == "testing" or message == "other message" |> + MANIFEST + end - context "issue #10963" do - it "collects with override when inside a class" do - expect_the_message_to_be(["overridden message"], <<-MANIFEST) - @notify { "testing": message => "original message" } + it "allows criteria to be combined with 'or'" do + expect_the_message_to_be(["the message", "other message"], <<-MANIFEST) + @notify { "testing": message => "the message" } + @notify { "other": message => "other message" } + @notify { "yet another": message => "different message" } - include collector_test - class collector_test { - Notify <| |> { - message => "overridden message" - } - } + Notify <| title == "testing" or message == "other message" |> + MANIFEST + end + + it "allows criteria to be grouped with parens" do + expect_the_message_to_be(["the message", "different message"], <<-MANIFEST) + @notify { "testing": message => "different message", withpath => true } + @notify { "other": message => "the message" } + @notify { "yet another": message => "the message", withpath => true } + + Notify <| (title == "testing" or message == "the message") and withpath == true |> MANIFEST end - it "collects with override when inside a define" do - expect_the_message_to_be(["overridden message"], <<-MANIFEST) - @notify { "testing": message => "original message" } + it "does not do anything if nothing matches" do + expect_the_message_to_be([], <<-MANIFEST) + @notify { "testing": message => "different message" } + + Notify <| title == "does not exist" |> + MANIFEST + end + + it "excludes items with inequalities" do + expect_the_message_to_be(["good message"], <<-MANIFEST) + @notify { "testing": message => "good message" } + @notify { "the wrong one": message => "bad message" } + + Notify <| title != "the wrong one" |> + MANIFEST + end + + it "does not exclude resources with unequal arrays" do + expect_the_message_to_be(["message", ["not this message", "or this one"]], <<-MANIFEST) + @notify { "testing": message => "message" } + @notify { "the wrong one": message => ["not this message", "or this one"] } + + Notify <| message != "not this message" |> + MANIFEST + end + + it "does not exclude tags with inequalities" do + expect_the_message_to_be(["wanted message", "the way it works"], <<-MANIFEST) + @notify { "testing": tag => ["wanted"], message => "wanted message" } + @notify { "other": tag => ["why"], message => "the way it works" } + + Notify <| tag != "why" |> + MANIFEST + end + + it "does not collect classes" do + node = Puppet::Node.new('the node') + expect do + catalog = compile_to_catalog(<<-MANIFEST, node) + class theclass { + @notify { "testing": message => "good message" } + } + Class <| |> + MANIFEST + end.to raise_error(/Classes cannot be collected/) + end + + context "overrides" do + it "modifies an existing array" do + expect_the_message_to_be([["original message", "extra message"]], <<-MANIFEST) + @notify { "testing": message => ["original message"] } - collector_test { testing: } - define collector_test() { Notify <| |> { - message => "overridden message" + message +> "extra message" } - } - MANIFEST + MANIFEST + end + + it "converts a scalar to an array" do + expect_the_message_to_be([["original message", "extra message"]], <<-MANIFEST) + @notify { "testing": message => "original message" } + + Notify <| |> { + message +> "extra message" + } + MANIFEST + end + + it "collects with override when inside a class (#10963)" do + expect_the_message_to_be(["overridden message"], <<-MANIFEST) + @notify { "testing": message => "original message" } + + include collector_test + class collector_test { + Notify <| |> { + message => "overridden message" + } + } + MANIFEST + end + + it "collects with override when inside a define (#10963)" do + expect_the_message_to_be(["overridden message"], <<-MANIFEST) + @notify { "testing": message => "original message" } + + collector_test { testing: } + define collector_test() { + Notify <| |> { + message => "overridden message" + } + } + MANIFEST + end + + # Catches regression in implemented behavior, this is not to be taken as this is the wanted behavior + # but it has been this way for a long time. + it "collects and overrides user defined resources immediately (before queue is evaluated)" do + expect_the_message_to_be(["overridden"], <<-MANIFEST) + define foo($message) { + notify { "testing": message => $message } + } + foo { test: message => 'given' } + Foo <| |> { message => 'overridden' } + MANIFEST + end + + # Catches regression in implemented behavior, this is not to be taken as this is the wanted behavior + # but it has been this way for a long time. + it "collects and overrides user defined resources immediately (virtual resources not queued)" do + expect_the_message_to_be(["overridden"], <<-MANIFEST) + define foo($message) { + @notify { "testing": message => $message } + } + foo { test: message => 'given' } + Notify <| |> # must be collected or the assertion does not find it + Foo <| |> { message => 'overridden' } + MANIFEST + end + + # Catches regression in implemented behavior, this is not to be taken as this is the wanted behavior + # but it has been this way for a long time. + # Note difference from none +> case where the override takes effect + it "collects and overrides user defined resources with +>" do + expect_the_message_to_be([["given", "overridden"]], <<-MANIFEST) + define foo($message) { + notify { "$name": message => $message } + } + foo { test: message => ['given'] } + Notify <| |> { message +> ['overridden'] } + MANIFEST + end + + it "collects and overrides virtual resources multiple times using multiple collects" do + expect_the_message_to_be(["overridden2"], <<-MANIFEST) + @notify { "testing": message => "original" } + Notify <| |> { message => 'overridden1' } + Notify <| |> { message => 'overridden2' } + MANIFEST + end + + it "collects and overrides non virtual resources multiple times using multiple collects" do + expect_the_message_to_be(["overridden2"], <<-MANIFEST) + notify { "testing": message => "original" } + Notify <| |> { message => 'overridden1' } + Notify <| |> { message => 'overridden2' } + MANIFEST + end + end end + + describe "in the current parser" do + before :each do + Puppet[:parser] = 'current' + end + + it_behaves_like "virtual resource collection" + end + + describe "in the future parser" do + before :each do + Puppet[:parser] = 'future' + end + + it_behaves_like "virtual resource collection" + end end diff --git a/spec/integration/parser/compiler_spec.rb b/spec/integration/parser/compiler_spec.rb index f10ce1adc..e6069d834 100755 --- a/spec/integration/parser/compiler_spec.rb +++ b/spec/integration/parser/compiler_spec.rb @@ -15,499 +15,511 @@ describe "Puppet::Parser::Compiler" do @scope = stub 'scope', :resource => @scope_resource, :source => mock("source") end - after do - Puppet.settings.clear - end - - # shared because tests are invoked both for classic and future parser - # - shared_examples_for "the compiler" do - it "should be able to determine the configuration version from a local version control repository" do - pending("Bug #14071 about semantics of Puppet::Util::Execute on Windows", :if => Puppet.features.microsoft_windows?) do - # This should always work, because we should always be - # in the puppet repo when we run this. - version = %x{git rev-parse HEAD}.chomp + it "should be able to determine the configuration version from a local version control repository" do + pending("Bug #14071 about semantics of Puppet::Util::Execute on Windows", :if => Puppet.features.microsoft_windows?) do + # This should always work, because we should always be + # in the puppet repo when we run this. + version = %x{git rev-parse HEAD}.chomp - Puppet.settings[:config_version] = 'git rev-parse HEAD' + Puppet.settings[:config_version] = 'git rev-parse HEAD' - @parser = Puppet::Parser::ParserFactory.parser "development" - @compiler = Puppet::Parser::Compiler.new(@node) + @parser = Puppet::Parser::ParserFactory.parser "development" + @compiler = Puppet::Parser::Compiler.new(@node) - @compiler.catalog.version.should == version - end + @compiler.catalog.version.should == version end + end + + it "should not create duplicate resources when a class is referenced both directly and indirectly by the node classifier (4792)" do + Puppet[:code] = <<-PP + class foo + { + notify { foo_notify: } + include bar + } + class bar + { + notify { bar_notify: } + } + PP + + @node.stubs(:classes).returns(['foo', 'bar']) + + catalog = Puppet::Parser::Compiler.compile(@node) + + catalog.resource("Notify[foo_notify]").should_not be_nil + catalog.resource("Notify[bar_notify]").should_not be_nil + end - it "should not create duplicate resources when a class is referenced both directly and indirectly by the node classifier (4792)" do + describe "when resolving class references" do + it "should favor local scope, even if there's an included class in topscope" do Puppet[:code] = <<-PP - class foo - { - notify { foo_notify: } - include bar + class experiment { + class baz { + } + notify {"x" : require => Class[Baz] } } - class bar - { - notify { bar_notify: } + class baz { } + include baz + include experiment + include experiment::baz PP - @node.stubs(:classes).returns(['foo', 'bar']) + catalog = Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode")) - catalog = Puppet::Parser::Compiler.compile(@node) + notify_resource = catalog.resource( "Notify[x]" ) - catalog.resource("Notify[foo_notify]").should_not be_nil - catalog.resource("Notify[bar_notify]").should_not be_nil + notify_resource[:require].title.should == "Experiment::Baz" end - describe "when resolving class references" do - it "should favor local scope, even if there's an included class in topscope" do - Puppet[:code] = <<-PP - class experiment { - class baz { - } - notify {"x" : require => Class[Baz] } - } + it "should favor local scope, even if there's an unincluded class in topscope" do + Puppet[:code] = <<-PP + class experiment { class baz { } - include baz - include experiment - include experiment::baz - PP - - catalog = Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode")) + notify {"x" : require => Class[Baz] } + } + class baz { + } + include experiment + include experiment::baz + PP - notify_resource = catalog.resource( "Notify[x]" ) + catalog = Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode")) - notify_resource[:require].title.should == "Experiment::Baz" - end + notify_resource = catalog.resource( "Notify[x]" ) - it "should favor local scope, even if there's an unincluded class in topscope" do - Puppet[:code] = <<-PP - class experiment { - class baz { + notify_resource[:require].title.should == "Experiment::Baz" + end + end + describe "(ticket #13349) when explicitly specifying top scope" do + ["class {'::bar::baz':}", "include ::bar::baz"].each do |include| + describe "with #{include}" do + it "should find the top level class" do + Puppet[:code] = <<-MANIFEST + class { 'foo::test': } + class foo::test { + #{include} } - notify {"x" : require => Class[Baz] } - } - class baz { - } - include experiment - include experiment::baz - PP - - catalog = Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode")) + class bar::baz { + notify { 'good!': } + } + class foo::bar::baz { + notify { 'bad!': } + } + MANIFEST - notify_resource = catalog.resource( "Notify[x]" ) + catalog = Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode")) - notify_resource[:require].title.should == "Experiment::Baz" - end - end - describe "(ticket #13349) when explicitly specifying top scope" do - ["class {'::bar::baz':}", "include ::bar::baz"].each do |include| - describe "with #{include}" do - it "should find the top level class" do - Puppet[:code] = <<-MANIFEST - class { 'foo::test': } - class foo::test { - #{include} - } - class bar::baz { - notify { 'good!': } - } - class foo::bar::baz { - notify { 'bad!': } - } - MANIFEST - - catalog = Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode")) - - catalog.resource("Class[Bar::Baz]").should_not be_nil - catalog.resource("Notify[good!]").should_not be_nil - catalog.resource("Class[Foo::Bar::Baz]").should be_nil - catalog.resource("Notify[bad!]").should be_nil - end + catalog.resource("Class[Bar::Baz]").should_not be_nil + catalog.resource("Notify[good!]").should_not be_nil + catalog.resource("Class[Foo::Bar::Baz]").should be_nil + catalog.resource("Notify[bad!]").should be_nil end end end + end - it "should recompute the version after input files are re-parsed" do - Puppet[:code] = 'class foo { }' - Time.stubs(:now).returns(1) - node = Puppet::Node.new('mynode') - Puppet::Parser::Compiler.compile(node).version.should == 1 - Time.stubs(:now).returns(2) - Puppet::Parser::Compiler.compile(node).version.should == 1 # no change because files didn't change - Puppet::Resource::TypeCollection.any_instance.stubs(:stale?).returns(true).then.returns(false) # pretend change - Puppet::Parser::Compiler.compile(node).version.should == 2 - end - - ['class', 'define', 'node'].each do |thing| - it "should not allow '#{thing}' inside evaluated conditional constructs" do - Puppet[:code] = <<-PP - if true { - #{thing} foo { - } - notify { decoy: } - } - PP - - begin - Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode")) - raise "compilation should have raised Puppet::Error" - rescue Puppet::Error => e - e.message.should =~ /at line 2/ - end - end - end + it "should recompute the version after input files are re-parsed" do + Puppet[:code] = 'class foo { }' + Time.stubs(:now).returns(1) + node = Puppet::Node.new('mynode') + Puppet::Parser::Compiler.compile(node).version.should == 1 + Time.stubs(:now).returns(2) + Puppet::Parser::Compiler.compile(node).version.should == 1 # no change because files didn't change + Puppet::Resource::TypeCollection.any_instance.stubs(:stale?).returns(true).then.returns(false) # pretend change + Puppet::Parser::Compiler.compile(node).version.should == 2 + end - it "should not allow classes inside unevaluated conditional constructs" do + ['class', 'define', 'node'].each do |thing| + it "should not allow '#{thing}' inside evaluated conditional constructs" do Puppet[:code] = <<-PP - if false { - class foo { + if true { + #{thing} foo { } + notify { decoy: } } PP - lambda { Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode")) }.should raise_error(Puppet::Error) + begin + Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode")) + raise "compilation should have raised Puppet::Error" + rescue Puppet::Error => e + e.message.should =~ /at line 2/ + end end + end - describe "when defining relationships" do - def extract_name(ref) - ref.sub(/File\[(\w+)\]/, '\1') - end + it "should not allow classes inside unevaluated conditional constructs" do + Puppet[:code] = <<-PP + if false { + class foo { + } + } + PP - let(:node) { Puppet::Node.new('mynode') } - let(:code) do - <<-MANIFEST - file { [a,b,c]: - mode => 0644, - } - file { [d,e]: - mode => 0755, - } - MANIFEST - end - let(:expected_relationships) { [] } - let(:expected_subscriptions) { [] } + lambda { Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode")) }.should raise_error(Puppet::Error) + end - before :each do - Puppet[:code] = code - end + describe "when defining relationships" do + def extract_name(ref) + ref.sub(/File\[(\w+)\]/, '\1') + end + + let(:node) { Puppet::Node.new('mynode') } + let(:code) do + <<-MANIFEST + file { [a,b,c]: + mode => '0644', + } + file { [d,e]: + mode => '0755', + } + MANIFEST + end + let(:expected_relationships) { [] } + let(:expected_subscriptions) { [] } - after :each do - catalog = Puppet::Parser::Compiler.compile(node) + before :each do + Puppet[:code] = code + end - resources = catalog.resources.select { |res| res.type == 'File' } + after :each do + catalog = Puppet::Parser::Compiler.compile(node) - actual_relationships, actual_subscriptions = [:before, :notify].map do |relation| - resources.map do |res| - dependents = Array(res[relation]) - dependents.map { |ref| [res.title, extract_name(ref)] } - end.inject(&:concat) - end + resources = catalog.resources.select { |res| res.type == 'File' } - actual_relationships.should =~ expected_relationships - actual_subscriptions.should =~ expected_subscriptions + actual_relationships, actual_subscriptions = [:before, :notify].map do |relation| + resources.map do |res| + dependents = Array(res[relation]) + dependents.map { |ref| [res.title, extract_name(ref)] } + end.inject(&:concat) end - it "should create a relationship" do - code << "File[a] -> File[b]" + actual_relationships.should =~ expected_relationships + actual_subscriptions.should =~ expected_subscriptions + end - expected_relationships << ['a','b'] - end + it "should create a relationship" do + code << "File[a] -> File[b]" - it "should create a subscription" do - code << "File[a] ~> File[b]" + expected_relationships << ['a','b'] + end - expected_subscriptions << ['a', 'b'] - end + it "should create a subscription" do + code << "File[a] ~> File[b]" - it "should create relationships using title arrays" do - code << "File[a,b] -> File[c,d]" + expected_subscriptions << ['a', 'b'] + end - expected_relationships.concat [ - ['a', 'c'], - ['b', 'c'], - ['a', 'd'], - ['b', 'd'], - ] - end + it "should create relationships using title arrays" do + code << "File[a,b] -> File[c,d]" - it "should create relationships using collection expressions" do - code << "File <| mode == 0644 |> -> File <| mode == 0755 |>" - - expected_relationships.concat [ - ['a', 'd'], - ['b', 'd'], - ['c', 'd'], - ['a', 'e'], - ['b', 'e'], - ['c', 'e'], - ] - end + expected_relationships.concat [ + ['a', 'c'], + ['b', 'c'], + ['a', 'd'], + ['b', 'd'], + ] + end - it "should create relationships using resource names" do - code << "'File[a]' -> 'File[b]'" + it "should create relationships using collection expressions" do + code << "File <| mode == 0644 |> -> File <| mode == 0755 |>" + + expected_relationships.concat [ + ['a', 'd'], + ['b', 'd'], + ['c', 'd'], + ['a', 'e'], + ['b', 'e'], + ['c', 'e'], + ] + end - expected_relationships << ['a', 'b'] - end + it "should create relationships using resource names" do + code << "'File[a]' -> 'File[b]'" - it "should create relationships using variables" do - code << <<-MANIFEST - $var = File[a] - $var -> File[b] - MANIFEST + expected_relationships << ['a', 'b'] + end - expected_relationships << ['a', 'b'] - end + it "should create relationships using variables" do + code << <<-MANIFEST + $var = File[a] + $var -> File[b] + MANIFEST - it "should create relationships using case statements" do - code << <<-MANIFEST - $var = 10 - case $var { - 10: { - file { s1: } - } - 12: { - file { s2: } - } + expected_relationships << ['a', 'b'] + end + + it "should create relationships using case statements" do + code << <<-MANIFEST + $var = 10 + case $var { + 10: { + file { s1: } } - -> - case $var + 2 { - 10: { - file { t1: } - } - 12: { - file { t2: } - } + 12: { + file { s2: } } - MANIFEST + } + -> + case $var + 2 { + 10: { + file { t1: } + } + 12: { + file { t2: } + } + } + MANIFEST - expected_relationships << ['s1', 't2'] - end + expected_relationships << ['s1', 't2'] + end - it "should create relationships using array members" do - code << <<-MANIFEST - $var = [ [ [ File[a], File[b] ] ] ] - $var[0][0][0] -> $var[0][0][1] - MANIFEST + it "should create relationships using array members" do + code << <<-MANIFEST + $var = [ [ [ File[a], File[b] ] ] ] + $var[0][0][0] -> $var[0][0][1] + MANIFEST - expected_relationships << ['a', 'b'] - end + expected_relationships << ['a', 'b'] + end - it "should create relationships using hash members" do - code << <<-MANIFEST - $var = {'foo' => {'bar' => {'source' => File[a], 'target' => File[b]}}} - $var[foo][bar][source] -> $var[foo][bar][target] - MANIFEST + it "should create relationships using hash members" do + code << <<-MANIFEST + $var = {'foo' => {'bar' => {'source' => File[a], 'target' => File[b]}}} + $var[foo][bar][source] -> $var[foo][bar][target] + MANIFEST - expected_relationships << ['a', 'b'] - end + expected_relationships << ['a', 'b'] + end - it "should create relationships using resource declarations" do - code << "file { l: } -> file { r: }" + it "should create relationships using resource declarations" do + code << "file { l: } -> file { r: }" - expected_relationships << ['l', 'r'] - end + expected_relationships << ['l', 'r'] + end - it "should chain relationships" do - code << "File[a] -> File[b] ~> File[c] <- File[d] <~ File[e]" + it "should chain relationships" do + code << "File[a] -> File[b] ~> File[c] <- File[d] <~ File[e]" - expected_relationships << ['a', 'b'] << ['d', 'c'] - expected_subscriptions << ['b', 'c'] << ['e', 'd'] - end + expected_relationships << ['a', 'b'] << ['d', 'c'] + expected_subscriptions << ['b', 'c'] << ['e', 'd'] end + end - context 'when working with immutable node data' do - context 'and have opted in to immutable_node_data' do - before :each do - Puppet[:immutable_node_data] = true - end + context 'when working with immutable node data' do + context 'and have opted in to immutable_node_data' do + before :each do + Puppet[:immutable_node_data] = true + end - def node_with_facts(facts) - Puppet[:facts_terminus] = :memory - Puppet::Node::Facts.indirection.save(Puppet::Node::Facts.new("testing", facts)) - node = Puppet::Node.new("testing") - node.fact_merge - node - end + def node_with_facts(facts) + Puppet[:facts_terminus] = :memory + Puppet::Node::Facts.indirection.save(Puppet::Node::Facts.new("testing", facts)) + node = Puppet::Node.new("testing") + node.fact_merge + node + end - matcher :fail_compile_with do |node, message_regex| - match do |manifest| - @error = nil - begin - compile_to_catalog(manifest, node) - false - rescue Puppet::Error => e - @error = e - message_regex.match(e.message) - end + matcher :fail_compile_with do |node, message_regex| + match do |manifest| + @error = nil + begin + PuppetSpec::Compiler.compile_to_catalog(manifest, node) + false + rescue Puppet::Error => e + @error = e + message_regex.match(e.message) end + end - failure_message_for_should do - if @error - "failed with #{@error}\n#{@error.backtrace}" - else - "did not fail" - end + failure_message_for_should do + if @error + "failed with #{@error}\n#{@error.backtrace}" + else + "did not fail" end end + end - it 'should make $facts available' do - node = node_with_facts('the_facts' => 'straight') + it 'should make $facts available' do + node = node_with_facts('the_facts' => 'straight') - catalog = compile_to_catalog(<<-MANIFEST, node) - notify { 'test': message => $facts[the_facts] } - MANIFEST + catalog = compile_to_catalog(<<-MANIFEST, node) + notify { 'test': message => $facts[the_facts] } + MANIFEST - catalog.resource("Notify[test]")[:message].should == "straight" - end + catalog.resource("Notify[test]")[:message].should == "straight" + end - it 'should make $facts reserved' do - node = node_with_facts('the_facts' => 'straight') + it 'should make $facts reserved' do + node = node_with_facts('the_facts' => 'straight') - expect('$facts = {}').to fail_compile_with(node, /assign to a reserved variable name: 'facts'/) - expect('class a { $facts = {} } include a').to fail_compile_with(node, /assign to a reserved variable name: 'facts'/) - end + expect('$facts = {}').to fail_compile_with(node, /assign to a reserved variable name: 'facts'/) + expect('class a { $facts = {} } include a').to fail_compile_with(node, /assign to a reserved variable name: 'facts'/) + end - it 'should make $facts immutable' do - node = node_with_facts('string' => 'value', 'array' => ['string'], 'hash' => { 'a' => 'string' }, 'number' => 1, 'boolean' => true) + it 'should make $facts immutable' do + node = node_with_facts('string' => 'value', 'array' => ['string'], 'hash' => { 'a' => 'string' }, 'number' => 1, 'boolean' => true) - expect('$i=inline_template("<% @facts[%q{new}] = 2 %>")').to fail_compile_with(node, /frozen Hash/i) - expect('$i=inline_template("<% @facts[%q{string}].chop! %>")').to fail_compile_with(node, /frozen String/i) + expect('$i=inline_template("<% @facts[%q{new}] = 2 %>")').to fail_compile_with(node, /frozen Hash/i) + expect('$i=inline_template("<% @facts[%q{string}].chop! %>")').to fail_compile_with(node, /frozen String/i) - expect('$i=inline_template("<% @facts[%q{array}][0].chop! %>")').to fail_compile_with(node, /frozen String/i) - expect('$i=inline_template("<% @facts[%q{array}][1] = 2 %>")').to fail_compile_with(node, /frozen Array/i) + expect('$i=inline_template("<% @facts[%q{array}][0].chop! %>")').to fail_compile_with(node, /frozen String/i) + expect('$i=inline_template("<% @facts[%q{array}][1] = 2 %>")').to fail_compile_with(node, /frozen Array/i) - expect('$i=inline_template("<% @facts[%q{hash}][%q{a}].chop! %>")').to fail_compile_with(node, /frozen String/i) - expect('$i=inline_template("<% @facts[%q{hash}][%q{b}] = 2 %>")').to fail_compile_with(node, /frozen Hash/i) - end + expect('$i=inline_template("<% @facts[%q{hash}][%q{a}].chop! %>")').to fail_compile_with(node, /frozen String/i) + expect('$i=inline_template("<% @facts[%q{hash}][%q{b}] = 2 %>")').to fail_compile_with(node, /frozen Hash/i) + end - it 'should make $facts available even if there are no facts' do - Puppet[:facts_terminus] = :memory - node = Puppet::Node.new("testing2") - node.fact_merge + it 'should make $facts available even if there are no facts' do + Puppet[:facts_terminus] = :memory + node = Puppet::Node.new("testing2") + node.fact_merge - catalog = compile_to_catalog(<<-MANIFEST, node) - notify { 'test': message => $facts } - MANIFEST + catalog = compile_to_catalog(<<-MANIFEST, node) + notify { 'test': message => $facts } + MANIFEST - expect(catalog).to have_resource("Notify[test]").with_parameter(:message, {}) - end + expect(catalog).to have_resource("Notify[test]").with_parameter(:message, {}) end + end - context 'and have not opted in to immutable_node_data' do - before :each do - Puppet[:immutable_node_data] = false - end + context 'and have not opted in to immutable_node_data' do + before :each do + Puppet[:immutable_node_data] = false + end - it 'should not make $facts available' do - Puppet[:facts_terminus] = :memory - facts = Puppet::Node::Facts.new("testing", 'the_facts' => 'straight') - Puppet::Node::Facts.indirection.save(facts) - node = Puppet::Node.new("testing") - node.fact_merge + it 'should not make $facts available' do + Puppet[:facts_terminus] = :memory + facts = Puppet::Node::Facts.new("testing", 'the_facts' => 'straight') + Puppet::Node::Facts.indirection.save(facts) + node = Puppet::Node.new("testing") + node.fact_merge - catalog = compile_to_catalog(<<-MANIFEST, node) - notify { 'test': message => "An $facts space" } - MANIFEST + catalog = compile_to_catalog(<<-MANIFEST, node) + notify { 'test': message => "An $facts space" } + MANIFEST - catalog.resource("Notify[test]")[:message].should == "An space" - end + catalog.resource("Notify[test]")[:message].should == "An space" end end + end - context 'when working with the trusted data hash' do - context 'and have opted in to trusted_node_data' do - before :each do - Puppet[:trusted_node_data] = true - end - - it 'should make $trusted available' do - node = Puppet::Node.new("testing") - node.trusted_data = { "data" => "value" } + context 'when working with the trusted data hash' do + context 'and have opted in to trusted_node_data' do + before :each do + Puppet[:trusted_node_data] = true + end - catalog = compile_to_catalog(<<-MANIFEST, node) - notify { 'test': message => $trusted[data] } - MANIFEST + it 'should make $trusted available' do + node = Puppet::Node.new("testing") + node.trusted_data = { "data" => "value" } - catalog.resource("Notify[test]")[:message].should == "value" - end + catalog = compile_to_catalog(<<-MANIFEST, node) + notify { 'test': message => $trusted[data] } + MANIFEST - it 'should not allow assignment to $trusted' do - node = Puppet::Node.new("testing") - node.trusted_data = { "data" => "value" } - - expect do - catalog = compile_to_catalog(<<-MANIFEST, node) - $trusted = 'changed' - notify { 'test': message => $trusted == 'changed' } - MANIFEST - catalog.resource("Notify[test]")[:message].should == true - end.to raise_error(Puppet::Error, /Attempt to assign to a reserved variable name: 'trusted'/) - end + catalog.resource("Notify[test]")[:message].should == "value" + end - it 'should not allow addition to $trusted hash' do - node = Puppet::Node.new("testing") - node.trusted_data = { "data" => "value" } - - expect do - catalog = compile_to_catalog(<<-MANIFEST, node) - $trusted['extra'] = 'added' - notify { 'test': message => $trusted['extra'] == 'added' } - MANIFEST - catalog.resource("Notify[test]")[:message].should == true - # different errors depending on regular or future parser - end.to raise_error(Puppet::Error, /(can't modify frozen [hH]ash)|(Illegal attempt to assign)/) - end + it 'should not allow assignment to $trusted' do + node = Puppet::Node.new("testing") + node.trusted_data = { "data" => "value" } - it 'should not allow addition to $trusted hash via Ruby inline template' do - node = Puppet::Node.new("testing") - node.trusted_data = { "data" => "value" } - - expect do - catalog = compile_to_catalog(<<-MANIFEST, node) - $dummy = inline_template("<% @trusted['extra'] = 'added' %> lol") - notify { 'test': message => $trusted['extra'] == 'added' } - MANIFEST - catalog.resource("Notify[test]")[:message].should == true - end.to raise_error(Puppet::Error, /can't modify frozen [hH]ash/) - end + expect do + catalog = compile_to_catalog(<<-MANIFEST, node) + $trusted = 'changed' + notify { 'test': message => $trusted == 'changed' } + MANIFEST + catalog.resource("Notify[test]")[:message].should == true + end.to raise_error(Puppet::Error, /Attempt to assign to a reserved variable name: 'trusted'/) end - context 'and have not opted in to trusted_node_data' do - before :each do - Puppet[:trusted_node_data] = false - end - - it 'should not make $trusted available' do - node = Puppet::Node.new("testing") - node.trusted_data = { "data" => "value" } + it 'should not allow addition to $trusted hash' do + node = Puppet::Node.new("testing") + node.trusted_data = { "data" => "value" } + expect do catalog = compile_to_catalog(<<-MANIFEST, node) - notify { 'test': message => $trusted == undef } + $trusted['extra'] = 'added' + notify { 'test': message => $trusted['extra'] == 'added' } MANIFEST - catalog.resource("Notify[test]")[:message].should == true - end + # different errors depending on regular or future parser + end.to raise_error(Puppet::Error, /(can't modify frozen [hH]ash)|(Illegal attempt to assign)/) + end - it 'should allow assignment to $trusted' do - node = Puppet::Node.new("testing") + it 'should not allow addition to $trusted hash via Ruby inline template' do + node = Puppet::Node.new("testing") + node.trusted_data = { "data" => "value" } + expect do catalog = compile_to_catalog(<<-MANIFEST, node) - $trusted = 'changed' - notify { 'test': message => $trusted == 'changed' } + $dummy = inline_template("<% @trusted['extra'] = 'added' %> lol") + notify { 'test': message => $trusted['extra'] == 'added' } MANIFEST - catalog.resource("Notify[test]")[:message].should == true - end + end.to raise_error(Puppet::Error, /can't modify frozen [hH]ash/) end end - end - describe 'using classic parser' do - before :each do - Puppet[:parser] = 'current' + context 'and have not opted in to trusted_node_data' do + before :each do + Puppet[:trusted_node_data] = false + end + + it 'should not make $trusted available' do + node = Puppet::Node.new("testing") + node.trusted_data = { "data" => "value" } + + catalog = compile_to_catalog(<<-MANIFEST, node) + notify { 'test': message => $trusted == undef } + MANIFEST + + catalog.resource("Notify[test]")[:message].should == true + end + + it 'should allow assignment to $trusted' do + node = Puppet::Node.new("testing") + + catalog = compile_to_catalog(<<-MANIFEST, node) + $trusted = 'changed' + notify { 'test': message => $trusted == 'changed' } + MANIFEST + + catalog.resource("Notify[test]")[:message].should == true + end end - it_behaves_like 'the compiler' do + end + + context 'when evaluating collection' do + it 'matches on container inherited tags' do + Puppet[:code] = <<-MANIFEST + class xport_test { + tag 'foo_bar' + @notify { 'nbr1': + message => 'explicitly tagged', + tag => 'foo_bar' + } + + @notify { 'nbr2': + message => 'implicitly tagged' + } + + Notify <| tag == 'foo_bar' |> { + message => 'overridden' + } + } + include xport_test + MANIFEST + + catalog = Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode")) + + expect(catalog).to have_resource("Notify[nbr1]").with_parameter(:message, 'overridden') + expect(catalog).to have_resource("Notify[nbr2]").with_parameter(:message, 'overridden') end end end diff --git a/spec/integration/parser/conditionals_spec.rb b/spec/integration/parser/conditionals_spec.rb new file mode 100644 index 000000000..82d950a06 --- /dev/null +++ b/spec/integration/parser/conditionals_spec.rb @@ -0,0 +1,117 @@ +require 'spec_helper' +require 'puppet_spec/compiler' +require 'matchers/resource' + +describe "Evaluation of Conditionals" do + include PuppetSpec::Compiler + include Matchers::Resource + + shared_examples_for "a catalog built with conditionals" do + it "evaluates an if block correctly" do + catalog = compile_to_catalog(<<-CODE) + if( 1 == 1) { + notify { 'if': } + } elsif(2 == 2) { + notify { 'elsif': } + } else { + notify { 'else': } + } + CODE + expect(catalog).to have_resource("Notify[if]") + end + + it "evaluates elsif block" do + catalog = compile_to_catalog(<<-CODE) + if( 1 == 3) { + notify { 'if': } + } elsif(2 == 2) { + notify { 'elsif': } + } else { + notify { 'else': } + } + CODE + expect(catalog).to have_resource("Notify[elsif]") + end + + it "reaches the else clause if no expressions match" do + catalog = compile_to_catalog(<<-CODE) + if( 1 == 2) { + notify { 'if': } + } elsif(2 == 3) { + notify { 'elsif': } + } else { + notify { 'else': } + } + CODE + expect(catalog).to have_resource("Notify[else]") + end + + it "evalutes false to false" do + catalog = compile_to_catalog(<<-CODE) + if false { + } else { + notify { 'false': } + } + CODE + expect(catalog).to have_resource("Notify[false]") + end + + it "evaluates the string 'false' as true" do + catalog = compile_to_catalog(<<-CODE) + if 'false' { + notify { 'true': } + } else { + notify { 'false': } + } + CODE + expect(catalog).to have_resource("Notify[true]") + end + + it "evaluates undefined variables as false" do + catalog = compile_to_catalog(<<-CODE) + if $undef_var { + } else { + notify { 'undef': } + } + CODE + expect(catalog).to have_resource("Notify[undef]") + end + end + + context "current parser" do + before(:each) do + Puppet[:parser] = 'current' + end + + it_behaves_like "a catalog built with conditionals" + + it "evaluates empty string as false" do + catalog = compile_to_catalog(<<-CODE) + if '' { + notify { 'true': } + } else { + notify { 'empty': } + } + CODE + expect(catalog).to have_resource("Notify[empty]") + end + end + + context "future parser" do + before(:each) do + Puppet[:parser] = 'future' + end + it_behaves_like "a catalog built with conditionals" + + it "evaluates empty string as true" do + catalog = compile_to_catalog(<<-CODE) + if '' { + notify { 'true': } + } else { + notify { 'empty': } + } + CODE + expect(catalog).to have_resource("Notify[true]") + end + end +end diff --git a/spec/integration/parser/future_compiler_spec.rb b/spec/integration/parser/future_compiler_spec.rb index 9d4d98776..d0fcfcdec 100644 --- a/spec/integration/parser/future_compiler_spec.rb +++ b/spec/integration/parser/future_compiler_spec.rb @@ -61,13 +61,73 @@ describe "Puppet::Parser::Compiler" do expect(catalog).to have_resource("Notify[check_me]").with_parameter(:message, "evoe") end + it 'Applies defaults from dynamic scopes (3x and future with reverted PUP-867)' do + catalog = compile_to_catalog(<<-CODE) + class a { + Notify { message => "defaulted" } + include b + notify { bye: } + } + class b { notify { hi: } } + + include a + CODE + expect(catalog).to have_resource("Notify[hi]").with_parameter(:message, "defaulted") + expect(catalog).to have_resource("Notify[bye]").with_parameter(:message, "defaulted") + end + + it 'gets default from inherited class (PUP-867)' do + catalog = compile_to_catalog(<<-CODE) + class a { + Notify { message => "defaulted" } + include c + notify { bye: } + } + class b { Notify { message => "inherited" } } + class c inherits b { notify { hi: } } + + include a + CODE + + expect(catalog).to have_resource("Notify[hi]").with_parameter(:message, "inherited") + expect(catalog).to have_resource("Notify[bye]").with_parameter(:message, "defaulted") + end + + it 'looks up default parameter values from inherited class (PUP-2532)' do + catalog = compile_to_catalog(<<-CODE) + class a { + Notify { message => "defaulted" } + include c + notify { bye: } + } + class b { Notify { message => "inherited" } } + class c inherits b { notify { hi: } } + + include a + notify {hi_test: message => Notify[hi][message] } + notify {bye_test: message => Notify[bye][message] } + CODE + + expect(catalog).to have_resource("Notify[hi_test]").with_parameter(:message, "inherited") + expect(catalog).to have_resource("Notify[bye_test]").with_parameter(:message, "defaulted") + end + + it 'does not allow override of class parameters using a resource override expression' do + expect do + compile_to_catalog(<<-CODE) + Class[a] { x => 2} + CODE + end.to raise_error(/Resource Override can only.*got: Class\[a\].*/) + end + describe "when resolving class references" do - it "should favor local scope, even if there's an included class in topscope" do + it "should not favor local scope (with class included in topscope)" do catalog = compile_to_catalog(<<-PP) class experiment { class baz { } notify {"x" : require => Class[Baz] } + notify {"y" : require => Class[Experiment::Baz] } } class baz { } @@ -76,15 +136,17 @@ describe "Puppet::Parser::Compiler" do include experiment::baz PP - expect(catalog).to have_resource("Notify[x]").with_parameter(:require, be_resource("Class[Experiment::Baz]")) + expect(catalog).to have_resource("Notify[x]").with_parameter(:require, be_resource("Class[Baz]")) + expect(catalog).to have_resource("Notify[y]").with_parameter(:require, be_resource("Class[Experiment::Baz]")) end - it "should favor local scope, even if there's an unincluded class in topscope" do + it "should not favor local scope, (with class not included in topscope)" do catalog = compile_to_catalog(<<-PP) class experiment { class baz { } notify {"x" : require => Class[Baz] } + notify {"y" : require => Class[Experiment::Baz] } } class baz { } @@ -92,7 +154,8 @@ describe "Puppet::Parser::Compiler" do include experiment::baz PP - expect(catalog).to have_resource("Notify[x]").with_parameter(:require, be_resource("Class[Experiment::Baz]")) + expect(catalog).to have_resource("Notify[x]").with_parameter(:require, be_resource("Class[Baz]")) + expect(catalog).to have_resource("Notify[y]").with_parameter(:require, be_resource("Class[Experiment::Baz]")) end end @@ -167,10 +230,10 @@ describe "Puppet::Parser::Compiler" do def assert_creates_relationships(relationship_code, expectations) base_manifest = <<-MANIFEST file { [a,b,c]: - mode => 0644, + mode => '0644', } file { [d,e]: - mode => 0755, + mode => '0755', } MANIFEST catalog = compile_to_catalog(base_manifest + relationship_code) @@ -301,12 +364,13 @@ describe "Puppet::Parser::Compiler" do end it 'a missing variable as default value becomes undef' do + # strict variables not on catalog = compile_to_catalog(<<-MANIFEST) - class a ($b=$x) { notify {$b: message=>'meh'} } + class a ($b=$x) { notify {test: message=>"yes ${undef == $b}" } } include a MANIFEST - expect(catalog).to have_resource("Notify[undef]").with_parameter(:message, "meh") + expect(catalog).to have_resource("Notify[test]").with_parameter(:message, "yes true") end end @@ -366,5 +430,321 @@ describe "Puppet::Parser::Compiler" do end end end + + context 'when using typed parameters in definition' do + it 'accepts type compliant arguments' do + catalog = compile_to_catalog(<<-MANIFEST) + define foo(String $x) { } + foo { 'test': x =>'say friend' } + MANIFEST + expect(catalog).to have_resource("Foo[test]").with_parameter(:x, 'say friend') + end + + it 'accepts anything when parameters are untyped' do + expect do + catalog = compile_to_catalog(<<-MANIFEST) + define foo($a, $b, $c) { } + foo { 'test': a => String, b=>10, c=>undef } + MANIFEST + end.to_not raise_error() + end + + it 'denies non type compliant arguments' do + expect do + catalog = compile_to_catalog(<<-MANIFEST) + define foo(Integer $x) { } + foo { 'test': x =>'say friend' } + MANIFEST + end.to raise_error(/type Integer, got String/) + end + + it 'denies non type compliant default argument' do + expect do + catalog = compile_to_catalog(<<-MANIFEST) + define foo(Integer $x = 'pow') { } + foo { 'test': } + MANIFEST + end.to raise_error(/type Integer, got String/) + end + + it 'accepts a Resource as a Type' do + catalog = compile_to_catalog(<<-MANIFEST) + define foo(Type[Bar] $x) { + notify { 'test': message => $x[text] } + } + define bar($text) { } + bar { 'joke': text => 'knock knock' } + foo { 'test': x => Bar[joke] } + MANIFEST + expect(catalog).to have_resource("Notify[test]").with_parameter(:message, 'knock knock') + end + end + + context 'when using typed parameters in class' do + it 'accepts type compliant arguments' do + catalog = compile_to_catalog(<<-MANIFEST) + class foo(String $x) { } + class { 'foo': x =>'say friend' } + MANIFEST + expect(catalog).to have_resource("Class[Foo]").with_parameter(:x, 'say friend') + end + + it 'accepts anything when parameters are untyped' do + expect do + catalog = compile_to_catalog(<<-MANIFEST) + class foo($a, $b, $c) { } + class { 'foo': a => String, b=>10, c=>undef } + MANIFEST + end.to_not raise_error() + end + + it 'denies non type compliant arguments' do + expect do + catalog = compile_to_catalog(<<-MANIFEST) + class foo(Integer $x) { } + class { 'foo': x =>'say friend' } + MANIFEST + end.to raise_error(/type Integer, got String/) + end + + it 'denies non type compliant default argument' do + expect do + catalog = compile_to_catalog(<<-MANIFEST) + class foo(Integer $x = 'pow') { } + class { 'foo': } + MANIFEST + end.to raise_error(/type Integer, got String/) + end + + it 'accepts a Resource as a Type' do + catalog = compile_to_catalog(<<-MANIFEST) + class foo(Type[Bar] $x) { + notify { 'test': message => $x[text] } + } + define bar($text) { } + bar { 'joke': text => 'knock knock' } + class { 'foo': x => Bar[joke] } + MANIFEST + expect(catalog).to have_resource("Notify[test]").with_parameter(:message, 'knock knock') + end + end + + context 'when using typed parameters in lambdas' do + it 'accepts type compliant arguments' do + catalog = compile_to_catalog(<<-MANIFEST) + with('value') |String $x| { notify { "$x": } } + MANIFEST + expect(catalog).to have_resource("Notify[value]") + end + + it 'handles an array as a single argument' do + catalog = compile_to_catalog(<<-MANIFEST) + with(['value', 'second']) |$x| { notify { "${x[0]} ${x[1]}": } } + MANIFEST + expect(catalog).to have_resource("Notify[value second]") + end + + it 'denies when missing required arguments' do + expect do + compile_to_catalog(<<-MANIFEST) + with(1) |$x, $y| { } + MANIFEST + end.to raise_error(/Parameter \$y is required but no value was given/m) + end + + it 'accepts anything when parameters are untyped' do + catalog = compile_to_catalog(<<-MANIFEST) + ['value', 1, true, undef].each |$x| { notify { "value: $x": } } + MANIFEST + + expect(catalog).to have_resource("Notify[value: value]") + expect(catalog).to have_resource("Notify[value: 1]") + expect(catalog).to have_resource("Notify[value: true]") + expect(catalog).to have_resource("Notify[value: ]") + end + + it 'accepts type-compliant, slurped arguments' do + catalog = compile_to_catalog(<<-MANIFEST) + with(1, 2) |Integer *$x| { notify { "${$x[0] + $x[1]}": } } + MANIFEST + expect(catalog).to have_resource("Notify[3]") + end + + it 'denies non-type-compliant arguments' do + expect do + compile_to_catalog(<<-MANIFEST) + with(1) |String $x| { } + MANIFEST + end.to raise_error(/expected.*String.*actual.*Integer/m) + end + + it 'denies non-type-compliant, slurped arguments' do + expect do + compile_to_catalog(<<-MANIFEST) + with(1, "hello") |Integer *$x| { } + MANIFEST + end.to raise_error(/called with mis-matched arguments.*expected.*Integer.*actual.*Integer, String/m) + end + + it 'denies non-type-compliant default argument' do + expect do + compile_to_catalog(<<-MANIFEST) + with(1) |$x, String $defaulted = 1| { notify { "${$x + $defaulted}": }} + MANIFEST + end.to raise_error(/expected.*Any.*String.*actual.*Integer.*Integer/m) + end + + it 'raises an error when a default argument value is an incorrect type and there are no arguments passed' do + expect do + compile_to_catalog(<<-MANIFEST) + with() |String $defaulted = 1| {} + MANIFEST + end.to raise_error(/expected.*String.*actual.*Integer/m) + end + + it 'raises an error when the default argument for a slurped parameter is an incorrect type' do + expect do + compile_to_catalog(<<-MANIFEST) + with() |String *$defaulted = 1| {} + MANIFEST + end.to raise_error(/expected.*String.*actual.*Integer/m) + end + + it 'allows using an array as the default slurped value' do + catalog = compile_to_catalog(<<-MANIFEST) + with() |String *$defaulted = [hi]| { notify { $defaulted[0]: } } + MANIFEST + + expect(catalog).to have_resource('Notify[hi]') + end + + it 'allows using a value of the type as the default slurped value' do + catalog = compile_to_catalog(<<-MANIFEST) + with() |String *$defaulted = hi| { notify { $defaulted[0]: } } + MANIFEST + + expect(catalog).to have_resource('Notify[hi]') + end + + it 'allows specifying the type of a slurped parameter as an array' do + catalog = compile_to_catalog(<<-MANIFEST) + with() |Array[String] *$defaulted = hi| { notify { $defaulted[0]: } } + MANIFEST + + expect(catalog).to have_resource('Notify[hi]') + end + + it 'raises an error when the number of default values does not match the parameter\'s size specification' do + expect do + compile_to_catalog(<<-MANIFEST) + with() |Array[String, 2] *$defaulted = hi| { } + MANIFEST + end.to raise_error(/expected.*arg count \{2,\}.*actual.*arg count \{1\}/m) + end + + it 'raises an error when the number of passed values does not match the parameter\'s size specification' do + expect do + compile_to_catalog(<<-MANIFEST) + with(hi) |Array[String, 2] *$passed| { } + MANIFEST + end.to raise_error(/expected.*arg count \{2,\}.*actual.*arg count \{1\}/m) + end + + it 'matches when the number of arguments passed for a slurp parameter match the size specification' do + catalog = compile_to_catalog(<<-MANIFEST) + with(hi, bye) |Array[String, 2] *$passed| { + $passed.each |$n| { notify { $n: } } + } + MANIFEST + + expect(catalog).to have_resource('Notify[hi]') + expect(catalog).to have_resource('Notify[bye]') + end + + it 'raises an error when the number of allowed slurp parameters exceeds the size constraint' do + expect do + compile_to_catalog(<<-MANIFEST) + with(hi, bye) |Array[String, 1, 1] *$passed| { } + MANIFEST + end.to raise_error(/expected.*arg count \{1\}.*actual.*arg count \{2\}/m) + end + + it 'allows passing slurped arrays by specifying an array of arrays' do + catalog = compile_to_catalog(<<-MANIFEST) + with([hi], [bye]) |Array[Array[String, 1, 1]] *$passed| { + notify { $passed[0][0]: } + notify { $passed[1][0]: } + } + MANIFEST + + expect(catalog).to have_resource('Notify[hi]') + expect(catalog).to have_resource('Notify[bye]') + end + + it 'raises an error when a required argument follows an optional one' do + expect do + compile_to_catalog(<<-MANIFEST) + with() |$y = first, $x, Array[String, 1] *$passed = bye| {} + MANIFEST + end.to raise_error(/Parameter \$x is required/) + end + + it 'raises an error when the minimum size of a slurped argument makes it required and it follows an optional argument' do + expect do + compile_to_catalog(<<-MANIFEST) + with() |$x = first, Array[String, 1] *$passed| {} + MANIFEST + end.to raise_error(/Parameter \$passed is required/) + end + + it 'allows slurped arguments with a minimum size of 0 after an optional argument' do + catalog = compile_to_catalog(<<-MANIFEST) + with() |$x = first, Array[String, 0] *$passed| { + notify { $x: } + } + MANIFEST + + expect(catalog).to have_resource('Notify[first]') + end + + it 'accepts a Resource as a Type' do + catalog = compile_to_catalog(<<-MANIFEST) + define bar($text) { } + bar { 'joke': text => 'knock knock' } + + with(Bar[joke]) |Type[Bar] $joke| { notify { "${joke[text]}": } } + MANIFEST + expect(catalog).to have_resource("Notify[knock knock]") + end + end end + + context 'when evaluating collection' do + it 'matches on container inherited tags' do + Puppet[:code] = <<-MANIFEST + class xport_test { + tag('foo_bar') + @notify { 'nbr1': + message => 'explicitly tagged', + tag => 'foo_bar' + } + + @notify { 'nbr2': + message => 'implicitly tagged' + } + + Notify <| tag == 'foo_bar' |> { + message => 'overridden' + } + } + include xport_test + MANIFEST + + catalog = Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode")) + + expect(catalog).to have_resource("Notify[nbr1]").with_parameter(:message, 'overridden') + expect(catalog).to have_resource("Notify[nbr2]").with_parameter(:message, 'overridden') + end + end + end diff --git a/spec/integration/parser/node_spec.rb b/spec/integration/parser/node_spec.rb new file mode 100644 index 000000000..7ce58f152 --- /dev/null +++ b/spec/integration/parser/node_spec.rb @@ -0,0 +1,185 @@ +require 'spec_helper' +require 'puppet_spec/compiler' +require 'matchers/resource' + +describe 'node statements' do + include PuppetSpec::Compiler + include Matchers::Resource + + shared_examples_for 'nodes' do + it 'selects a node where the name is just a number' do + # Future parser doesn't allow a number in this position + catalog = compile_to_catalog(<<-MANIFEST, Puppet::Node.new("5")) + node 5 { notify { 'matched': } } + MANIFEST + + expect(catalog).to have_resource('Notify[matched]') + end + + it 'selects the node with a matching name' do + catalog = compile_to_catalog(<<-MANIFEST, Puppet::Node.new("nodename")) + node noden {} + node nodename { notify { matched: } } + node name {} + MANIFEST + + expect(catalog).to have_resource('Notify[matched]') + end + + it 'prefers a node with a literal name over one with a regex' do + catalog = compile_to_catalog(<<-MANIFEST, Puppet::Node.new("nodename")) + node /noden.me/ { notify { ignored: } } + node nodename { notify { matched: } } + MANIFEST + + expect(catalog).to have_resource('Notify[matched]') + end + + it 'selects a node where one of the names matches' do + catalog = compile_to_catalog(<<-MANIFEST, Puppet::Node.new("nodename")) + node different, nodename, other { notify { matched: } } + MANIFEST + + expect(catalog).to have_resource('Notify[matched]') + end + + it 'arbitrarily selects one of the matching nodes' do + catalog = compile_to_catalog(<<-MANIFEST, Puppet::Node.new("nodename")) + node /not/ { notify { 'is not matched': } } + node /name.*/ { notify { 'could be matched': } } + node /na.e/ { notify { 'could also be matched': } } + MANIFEST + + expect([catalog.resource('Notify[could be matched]'), catalog.resource('Notify[could also be matched]')].compact).to_not be_empty + end + + it 'selects a node where one of the names matches with a mixture of literals and regex' do + catalog = compile_to_catalog(<<-MANIFEST, Puppet::Node.new("nodename")) + node different, /name/, other { notify { matched: } } + MANIFEST + + expect(catalog).to have_resource('Notify[matched]') + end + + it 'errors when two nodes with regexes collide after some regex syntax is removed' do + expect do + compile_to_catalog(<<-MANIFEST) + node /a.*(c)?/ { } + node 'a.c' { } + MANIFEST + end.to raise_error(Puppet::Error, /Node 'a.c' is already defined/) + end + + it 'provides captures from the regex in the node body' do + catalog = compile_to_catalog(<<-MANIFEST, Puppet::Node.new("nodename")) + node /(.*)/ { notify { "$1": } } + MANIFEST + + expect(catalog).to have_resource('Notify[nodename]') + end + + it 'selects the node with the matching regex' do + catalog = compile_to_catalog(<<-MANIFEST, Puppet::Node.new("nodename")) + node /node.*/ { notify { matched: } } + MANIFEST + + expect(catalog).to have_resource('Notify[matched]') + end + + it 'selects a node that is a literal string' do + catalog = compile_to_catalog(<<-MANIFEST, Puppet::Node.new("node.name")) + node 'node.name' { notify { matched: } } + MANIFEST + + expect(catalog).to have_resource('Notify[matched]') + end + + it 'selects a node that is a prefix of the agent name' do + Puppet[:strict_hostname_checking] = false + catalog = compile_to_catalog(<<-MANIFEST, Puppet::Node.new("node.name.com")) + node 'node.name' { notify { matched: } } + MANIFEST + + expect(catalog).to have_resource('Notify[matched]') + end + + it 'does not treat regex symbols as a regex inside a string literal' do + catalog = compile_to_catalog(<<-MANIFEST, Puppet::Node.new("nodexname")) + node 'node.name' { notify { 'not matched': } } + node 'nodexname' { notify { 'matched': } } + MANIFEST + + expect(catalog).to have_resource('Notify[matched]') + end + + it 'errors when two nodes have the same name' do + expect do + compile_to_catalog(<<-MANIFEST) + node name { } + node 'name' { } + MANIFEST + end.to raise_error(Puppet::Error, /Node 'name' is already defined/) + end + end + + describe 'using classic parser' do + before :each do + Puppet[:parser] = 'current' + end + + it_behaves_like 'nodes' + + it 'includes the inherited nodes of the matching node' do + catalog = compile_to_catalog(<<-MANIFEST, Puppet::Node.new("nodename")) + node notmatched1 { notify { inherited: } } + node nodename inherits notmatched1 { notify { matched: } } + node notmatched2 { notify { ignored: } } + MANIFEST + + expect(catalog).to have_resource('Notify[matched]') + expect(catalog).to have_resource('Notify[inherited]') + end + + it 'raises deprecation warning for node inheritance for 3x parser' do + Puppet.expects(:warning).at_least_once + Puppet.expects(:warning).with(regexp_matches(/Deprecation notice\: Node inheritance is not supported in Puppet >= 4\.0\.0/)) + + catalog = compile_to_catalog(<<-MANIFEST, Puppet::Node.new("1.2.3.4")) + node default {} + node '1.2.3.4' inherits default { } + MANIFEST + end + end + + describe 'using future parser' do + before :each do + Puppet[:parser] = 'future' + end + + it_behaves_like 'nodes' + + it 'is unable to parse a name that is an invalid number' do + expect do + compile_to_catalog('node 5name {} ') + end.to raise_error(Puppet::Error, /Illegal number/) + end + + it 'parses a node name that is dotted numbers' do + catalog = compile_to_catalog(<<-MANIFEST, Puppet::Node.new("1.2.3.4")) + node 1.2.3.4 { notify { matched: } } + MANIFEST + + expect(catalog).to have_resource('Notify[matched]') + end + + it 'raises error for node inheritance' do + expect do + compile_to_catalog(<<-MANIFEST, Puppet::Node.new("nodename")) + node default {} + node nodename inherits default { } + MANIFEST + end.to raise_error(/Node inheritance is not supported in Puppet >= 4\.0\.0/) + end + + end +end diff --git a/spec/integration/parser/resource_expressions_spec.rb b/spec/integration/parser/resource_expressions_spec.rb new file mode 100644 index 000000000..c753f5a91 --- /dev/null +++ b/spec/integration/parser/resource_expressions_spec.rb @@ -0,0 +1,286 @@ +require 'spec_helper' +require 'puppet_spec/language' + +describe "Puppet resource expressions" do + extend PuppetSpec::Language + + describe "future parser" do + before :each do + Puppet[:parser] = 'future' + end + + produces( + "$a = notify + $b = example + $c = { message => hello } + @@Resource[$a] { + $b: + * => $c + } + realize(Resource[$a, $b]) + " => "Notify[example][message] == 'hello'") + + + context "resource titles" do + produces( + "notify { thing: }" => "defined(Notify[thing])", + "$x = thing notify { $x: }" => "defined(Notify[thing])", + + "notify { [thing]: }" => "defined(Notify[thing])", + "$x = [thing] notify { $x: }" => "defined(Notify[thing])", + + "notify { [[nested, array]]: }" => "defined(Notify[nested]) and defined(Notify[array])", + "$x = [[nested, array]] notify { $x: }" => "defined(Notify[nested]) and defined(Notify[array])", + + "notify { []: }" => [], # this asserts nothing added + "$x = [] notify { $x: }" => [], # this asserts nothing added + + "notify { default: }" => "!defined(Notify['default'])", # nothing created because this is just a local default + "$x = default notify { $x: }" => "!defined(Notify['default'])") + + fails( + "notify { '': }" => /Empty string title/, + "$x = '' notify { $x: }" => /Empty string title/, + + "notify { 1: }" => /Illegal title type.*Expected String, got Integer/, + "$x = 1 notify { $x: }" => /Illegal title type.*Expected String, got Integer/, + + "notify { [1]: }" => /Illegal title type.*Expected String, got Integer/, + "$x = [1] notify { $x: }" => /Illegal title type.*Expected String, got Integer/, + + "notify { 3.0: }" => /Illegal title type.*Expected String, got Float/, + "$x = 3.0 notify { $x: }" => /Illegal title type.*Expected String, got Float/, + + "notify { [3.0]: }" => /Illegal title type.*Expected String, got Float/, + "$x = [3.0] notify { $x: }" => /Illegal title type.*Expected String, got Float/, + + "notify { true: }" => /Illegal title type.*Expected String, got Boolean/, + "$x = true notify { $x: }" => /Illegal title type.*Expected String, got Boolean/, + + "notify { [true]: }" => /Illegal title type.*Expected String, got Boolean/, + "$x = [true] notify { $x: }" => /Illegal title type.*Expected String, got Boolean/, + + "notify { [false]: }" => /Illegal title type.*Expected String, got Boolean/, + "$x = [false] notify { $x: }" => /Illegal title type.*Expected String, got Boolean/, + + "notify { undef: }" => /Missing title.*undef/, + "$x = undef notify { $x: }" => /Missing title.*undef/, + + "notify { [undef]: }" => /Missing title.*undef/, + "$x = [undef] notify { $x: }" => /Missing title.*undef/, + + "notify { {nested => hash}: }" => /Illegal title type.*Expected String, got Hash/, + "$x = {nested => hash} notify { $x: }" => /Illegal title type.*Expected String, got Hash/, + + "notify { [{nested => hash}]: }" => /Illegal title type.*Expected String, got Hash/, + "$x = [{nested => hash}] notify { $x: }" => /Illegal title type.*Expected String, got Hash/, + + "notify { /regexp/: }" => /Illegal title type.*Expected String, got Regexp/, + "$x = /regexp/ notify { $x: }" => /Illegal title type.*Expected String, got Regexp/, + + "notify { [/regexp/]: }" => /Illegal title type.*Expected String, got Regexp/, + "$x = [/regexp/] notify { $x: }" => /Illegal title type.*Expected String, got Regexp/, + + "notify { [dupe, dupe]: }" => /The title 'dupe' has already been used/, + "notify { dupe:; dupe: }" => /The title 'dupe' has already been used/, + "notify { [dupe]:; dupe: }" => /The title 'dupe' has already been used/, + "notify { [default, default]:}" => /The title 'default' has already been used/, + "notify { default:; default:}" => /The title 'default' has already been used/, + "notify { [default]:; default:}" => /The title 'default' has already been used/) + end + + context "type names" do + produces( "notify { testing: }" => "defined(Notify[testing])") + produces( "$a = notify; Resource[$a] { testing: }" => "defined(Notify[testing])") + produces( "Resource['notify'] { testing: }" => "defined(Notify[testing])") + produces( "Resource[sprintf('%s', 'notify')] { testing: }" => "defined(Notify[testing])") + produces( "$a = ify; Resource[\"not$a\"] { testing: }" => "defined(Notify[testing])") + + produces( "Notify { testing: }" => "defined(Notify[testing])") + produces( "Resource[Notify] { testing: }" => "defined(Notify[testing])") + produces( "Resource['Notify'] { testing: }" => "defined(Notify[testing])") + + produces( "class a { notify { testing: } } class { a: }" => "defined(Notify[testing])") + produces( "class a { notify { testing: } } Class { a: }" => "defined(Notify[testing])") + produces( "class a { notify { testing: } } Resource['class'] { a: }" => "defined(Notify[testing])") + + produces( "define a::b { notify { testing: } } a::b { title: }" => "defined(Notify[testing])") + produces( "define a::b { notify { testing: } } A::B { title: }" => "defined(Notify[testing])") + produces( "define a::b { notify { testing: } } Resource['a::b'] { title: }" => "defined(Notify[testing])") + + fails( "'class' { a: }" => /Illegal Resource Type expression.*got String/) + fails( "'' { testing: }" => /Illegal Resource Type expression.*got String/) + fails( "1 { testing: }" => /Illegal Resource Type expression.*got Integer/) + fails( "3.0 { testing: }" => /Illegal Resource Type expression.*got Float/) + fails( "true { testing: }" => /Illegal Resource Type expression.*got Boolean/) + fails( "'not correct' { testing: }" => /Illegal Resource Type expression.*got String/) + + fails( "Notify[hi] { testing: }" => /Illegal Resource Type expression.*got Notify\['hi'\]/) + fails( "[Notify, File] { testing: }" => /Illegal Resource Type expression.*got Array\[Type\[Resource\]\]/) + + fails( "define a::b { notify { testing: } } 'a::b' { title: }" => /Illegal Resource Type expression.*got String/) + + fails( "Does::Not::Exist { title: }" => /Invalid resource type does::not::exist/) + end + + context "local defaults" do + produces( + "notify { example:; default: message => defaulted }" => "Notify[example][message] == 'defaulted'", + "notify { example: message => specific; default: message => defaulted }" => "Notify[example][message] == 'specific'", + "notify { example: message => undef; default: message => defaulted }" => "Notify[example][message] == undef", + "notify { [example, other]: ; default: message => defaulted }" => "Notify[example][message] == 'defaulted' and Notify[other][message] == 'defaulted'", + "notify { [example, default]: message => set; other: }" => "Notify[example][message] == 'set' and Notify[other][message] == 'set'") + end + + context "order of evaluation" do + fails("notify { hi: message => value; bye: message => Notify[hi][message] }" => /Resource not found: Notify\['hi'\]/) + + produces("notify { hi: message => (notify { param: message => set }); bye: message => Notify[param][message] }" => "defined(Notify[hi]) and Notify[bye][message] == 'set'") + fails("notify { bye: message => Notify[param][message]; hi: message => (notify { param: message => set }) }" => /Resource not found: Notify\['param'\]/) + end + + context "parameters" do + produces( + "notify { title: message => set }" => "Notify[title][message] == 'set'", + "$x = set notify { title: message => $x }" => "Notify[title][message] == 'set'", + + "notify { title: *=> { message => set } }" => "Notify[title][message] == 'set'", + + "$x = { message => set } notify { title: * => $x }" => "Notify[title][message] == 'set'", + + # picks up defaults + "$x = { owner => the_x } + $y = { mode => '0666' } + $t = '/tmp/x' + file { + default: + * => $x; + $t: + path => '/somewhere', + * => $y }" => "File[$t][mode] == '0666' and File[$t][owner] == 'the_x' and File[$t][path] == '/somewhere'", + + # explicit wins over default - no error + "$x = { owner => the_x, mode => '0777' } + $y = { mode => '0666' } + $t = '/tmp/x' + file { + default: + * => $x; + $t: + path => '/somewhere', + * => $y }" => "File[$t][mode] == '0666' and File[$t][owner] == 'the_x' and File[$t][path] == '/somewhere'") + + fails("notify { title: unknown => value }" => /Invalid parameter unknown/) + + # this really needs to be a better error message. + fails("notify { title: * => { hash => value }, message => oops }" => /Invalid parameter hash/) + + # should this be a better error message? + fails("notify { title: message => oops, * => { hash => value } }" => /Invalid parameter hash/) + + fails("notify { title: * => { unknown => value } }" => /Invalid parameter unknown/) + fails(" + $x = { mode => '0666' } + $y = { owner => the_y } + $t = '/tmp/x' + file { $t: + * => $x, + * => $y }" => /Unfolding of attributes from Hash can only be used once per resource body/) + end + + context "virtual" do + produces( + "@notify { example: }" => "!defined(Notify[example])", + + "@notify { example: } + realize(Notify[example])" => "defined(Notify[example])", + + "@notify { virtual: message => set } + notify { real: + message => Notify[virtual][message] }" => "Notify[real][message] == 'set'") + end + + context "exported" do + produces( + "@@notify { example: }" => "!defined(Notify[example])", + "@@notify { example: } realize(Notify[example])" => "defined(Notify[example])", + "@@notify { exported: message => set } notify { real: message => Notify[exported][message] }" => "Notify[real][message] == 'set'") + end + end + + describe "current parser" do + before :each do + Puppet[:parser] = 'current' + end + + produces( + "notify { thing: }" => ["Notify[thing]"], + "$x = thing notify { $x: }" => ["Notify[thing]"], + + "notify { [thing]: }" => ["Notify[thing]"], + "$x = [thing] notify { $x: }" => ["Notify[thing]"], + + "notify { [[nested, array]]: }" => ["Notify[nested]", "Notify[array]"], + "$x = [[nested, array]] notify { $x: }" => ["Notify[nested]", "Notify[array]"], + + # deprecate? + "notify { 1: }" => ["Notify[1]"], + "$x = 1 notify { $x: }" => ["Notify[1]"], + + # deprecate? + "notify { [1]: }" => ["Notify[1]"], + "$x = [1] notify { $x: }" => ["Notify[1]"], + + # deprecate? + "notify { 3.0: }" => ["Notify[3.0]"], + "$x = 3.0 notify { $x: }" => ["Notify[3.0]"], + + # deprecate? + "notify { [3.0]: }" => ["Notify[3.0]"], + "$x = [3.0] notify { $x: }" => ["Notify[3.0]"]) + + # :( + fails( "notify { true: }" => /Syntax error/) + produces("$x = true notify { $x: }" => ["Notify[true]"]) + + # this makes no sense given the [false] case + produces( + "notify { [true]: }" => ["Notify[true]"], + "$x = [true] notify { $x: }" => ["Notify[true]"]) + + # *sigh* + fails( + "notify { false: }" => /Syntax error/, + "$x = false notify { $x: }" => /No title provided and :notify is not a valid resource reference/, + + "notify { [false]: }" => /No title provided and :notify is not a valid resource reference/, + "$x = [false] notify { $x: }" => /No title provided and :notify is not a valid resource reference/) + + # works for variable value, not for literal. deprecate? + fails("notify { undef: }" => /Syntax error/) + produces( + "$x = undef notify { $x: }" => ["Notify[undef]"], + + # deprecate? + "notify { [undef]: }" => ["Notify[undef]"], + "$x = [undef] notify { $x: }" => ["Notify[undef]"]) + + fails("notify { {nested => hash}: }" => /Syntax error/) + #produces("$x = {nested => hash} notify { $x: }" => ["Notify[{nested => hash}]"]) #it is created, but isn't possible to reference the resource. deprecate? + #produces("notify { [{nested => hash}]: }" => ["Notify[{nested => hash}]"]) #it is created, but isn't possible to reference the resource. deprecate? + #produces("$x = [{nested => hash}] notify { $x: }" => ["Notify[{nested => hash}]"]) #it is created, but isn't possible to reference the resource. deprecate? + + fails( + "notify { /regexp/: }" => /Syntax error/, + "$x = /regexp/ notify { $x: }" => /Syntax error/, + + "notify { [/regexp/]: }" => /Syntax error/, + "$x = [/regexp/] notify { $x: }" => /Syntax error/, + + "notify { default: }" => /Syntax error/, + "$x = default notify { $x: }" => /Syntax error/, + + "notify { [default]: }" => /Syntax error/, + "$x = [default] notify { $x: }" => /Syntax error/) + end +end diff --git a/spec/integration/parser/ruby_manifest_spec.rb b/spec/integration/parser/ruby_manifest_spec.rb index d0bd5f0e7..29e9c6379 100755 --- a/spec/integration/parser/ruby_manifest_spec.rb +++ b/spec/integration/parser/ruby_manifest_spec.rb @@ -11,10 +11,6 @@ describe "Pure ruby manifests" do @test_dir = tmpdir('ruby_manifest_test') end - after do - Puppet.settings.clear - end - def write_file(name, contents) path = File.join(@test_dir, name) File.open(path, "w") { |f| f.write(contents) } diff --git a/spec/integration/parser/scope_spec.rb b/spec/integration/parser/scope_spec.rb index 1fd84f421..c10caa7f0 100644 --- a/spec/integration/parser/scope_spec.rb +++ b/spec/integration/parser/scope_spec.rb @@ -39,23 +39,27 @@ describe "Two step scoping for variables" do Puppet[:parser] = 'future' end - describe "using plussignment to change in a new scope" do - it "does not change a string in the parent scope" do - # Expects to be able to concatenate string using += + describe "using unsupported operators" do + it "issues an error for +=" do expect do - catalog = compile_to_catalog(<<-MANIFEST, Puppet::Node.new('the node')) - $var = "top_msg" - class override { - $var += "override" - include foo - } - class foo { - notify { 'something': message => $var, } + catalog = compile_to_catalog(<<-MANIFEST) + $var = ["top_msg"] + node default { + $var += ["override"] } + MANIFEST + end.to raise_error(/The operator '\+=' is no longer supported/) + end - include override + it "issues an error for -=" do + expect do + catalog = compile_to_catalog(<<-MANIFEST) + $var = ["top_msg"] + node default { + $var -= ["top_msg"] + } MANIFEST - end.to raise_error(/The value 'top_msg' cannot be converted to Numeric/) + end.to raise_error(/The operator '-=' is no longer supported/) end end @@ -184,54 +188,6 @@ describe "Two step scoping for variables" do end describe "when using shadowing and inheritance" do - it "finds value define in the inherited node" do - expect_the_message_to_be('parent_msg') do <<-MANIFEST - $var = "top_msg" - node parent { - $var = "parent_msg" - } - node default inherits parent { - include foo - } - class foo { - notify { 'something': message => $var, } - } - MANIFEST - end - end - - it "finds top scope when the class is included before the node defines the var" do - expect_the_message_to_be('top_msg') do <<-MANIFEST - $var = "top_msg" - node parent { - include foo - } - node default inherits parent { - $var = "default_msg" - } - class foo { - notify { 'something': message => $var, } - } - MANIFEST - end - end - - it "finds top scope when the class is included before the node defines the var" do - expect_the_message_to_be('top_msg') do <<-MANIFEST - $var = "top_msg" - node parent { - include foo - } - node default inherits parent { - $var = "default_msg" - } - class foo { - notify { 'something': message => $var, } - } - MANIFEST - end - end - it "finds values in its local scope" do expect_the_message_to_be('local_msg') do <<-MANIFEST node default { @@ -363,7 +319,7 @@ describe "Two step scoping for variables" do $var = "inner baz" } - class bar inherits baz { + class bar inherits foo::baz { notify { 'something': message => $var, } } } @@ -651,80 +607,6 @@ describe "Two step scoping for variables" do end end - describe "using plussignment to change in a new scope" do - - it "does not change an array in the parent scope" do - expect_the_message_to_be('top_msg') do <<-MANIFEST - $var = ["top_msg"] - class override { - $var += ["override"] - include foo - } - class foo { - notify { 'something': message => $var, } - } - - include override - MANIFEST - end - end - - it "concatenates two arrays" do - expect_the_message_to_be(['top_msg', 'override']) do <<-MANIFEST - $var = ["top_msg"] - class override { - $var += ["override"] - notify { 'something': message => $var, } - } - - include override - MANIFEST - end - end - - it "leaves an array of arrays unflattened" do - expect_the_message_to_be([['top_msg'], ['override']]) do <<-MANIFEST - $var = [["top_msg"]] - class override { - $var += [["override"]] - notify { 'something': message => $var, } - } - - include override - MANIFEST - end - end - - it "does not change a hash in the parent scope" do - expect_the_message_to_be({"key"=>"top_msg"}) do <<-MANIFEST - $var = { "key" => "top_msg" } - class override { - $var += { "other" => "override" } - include foo - } - class foo { - notify { 'something': message => $var, } - } - - include override - MANIFEST - end - end - - it "replaces a value of a key in the hash instead of merging the values" do - expect_the_message_to_be({"key"=>"override"}) do <<-MANIFEST - $var = { "key" => "top_msg" } - class override { - $var += { "key" => "override" } - notify { 'something': message => $var, } - } - - include override - MANIFEST - end - end - end - describe "when using an enc" do it "places enc parameters in top scope" do enc_node = Puppet::Node.new("the node", { :parameters => { "var" => 'from_enc' } }) @@ -757,20 +639,16 @@ describe "Two step scoping for variables" do end end - it "evaluates enc classes in the node scope when there is a matching node" do - enc_node = Puppet::Node.new("the_node", { :classes => ['foo'] }) - - expect_the_message_to_be('from matching node', enc_node) do <<-MANIFEST - node inherited { - $var = "from inherited" - } + it "overrides enc variables from a node scope var" do + enc_node = Puppet::Node.new("the_node", { :classes => ['foo'], :parameters => { 'enc_var' => 'Set from ENC.' } }) - node the_node inherits inherited { - $var = "from matching node" + expect_the_message_to_be('ENC overridden in node', enc_node) do <<-MANIFEST + node the_node { + $enc_var = "ENC overridden in node" } class foo { - notify { 'something': message => $var, } + notify { 'something': message => $enc_var, } } MANIFEST end @@ -782,7 +660,74 @@ describe "Two step scoping for variables" do before :each do Puppet[:parser] = 'current' end - it_behaves_like 'the scope' do + + it_behaves_like 'the scope' + + it "finds value define in the inherited node" do + expect_the_message_to_be('parent_msg') do <<-MANIFEST + $var = "top_msg" + node parent { + $var = "parent_msg" + } + node default inherits parent { + include foo + } + class foo { + notify { 'something': message => $var, } + } + MANIFEST + end + end + + it "finds top scope when the class is included before the node defines the var" do + expect_the_message_to_be('top_msg') do <<-MANIFEST + $var = "top_msg" + node parent { + include foo + } + node default inherits parent { + $var = "default_msg" + } + class foo { + notify { 'something': message => $var, } + } + MANIFEST + end + end + + it "finds top scope when the class is included before the node defines the var" do + expect_the_message_to_be('top_msg') do <<-MANIFEST + $var = "top_msg" + node parent { + include foo + } + node default inherits parent { + $var = "default_msg" + } + class foo { + notify { 'something': message => $var, } + } + MANIFEST + end + end + + it "evaluates enc classes in the node scope when there is a matching node" do + enc_node = Puppet::Node.new("the_node", { :classes => ['foo'] }) + + expect_the_message_to_be('from matching node', enc_node) do <<-MANIFEST + node inherited { + $var = "from inherited" + } + + node the_node inherits inherited { + $var = "from matching node" + } + + class foo { + notify { 'something': message => $var, } + } + MANIFEST + end end end @@ -790,9 +735,7 @@ describe "Two step scoping for variables" do before :each do Puppet[:parser] = 'future' end - it_behaves_like 'the scope' do - end - end + it_behaves_like 'the scope' + end end - |