diff --git a/spec/integration/environments/default_manifest_spec.rb b/spec/integration/environments/default_manifest_spec.rb index 543afc087..7a4c68bd0 100644 --- a/spec/integration/environments/default_manifest_spec.rb +++ b/spec/integration/environments/default_manifest_spec.rb @@ -1,263 +1,250 @@ require 'spec_helper' module EnvironmentsDefaultManifestsSpec describe "default manifests" do - shared_examples_for "puppet with default_manifest settings" do + context "puppet with default_manifest settings" do let(:confdir) { Puppet[:confdir] } let(:environmentpath) { File.expand_path("envdir", confdir) } context "relative default" do let(:testingdir) { File.join(environmentpath, "testing") } before(:each) do FileUtils.mkdir_p(testingdir) end it "reads manifest from ./manifest of a basic directory environment" do manifestsdir = File.join(testingdir, "manifests") FileUtils.mkdir_p(manifestsdir) File.open(File.join(manifestsdir, "site.pp"), "w") do |f| f.puts("notify { 'ManifestFromRelativeDefault': }") end File.open(File.join(confdir, "puppet.conf"), "w") do |f| f.puts("environmentpath=#{environmentpath}") end expect(a_catalog_compiled_for_environment('testing')).to( include_resource('Notify[ManifestFromRelativeDefault]') ) end end context "set absolute" do let(:testingdir) { File.join(environmentpath, "testing") } before(:each) do FileUtils.mkdir_p(testingdir) end it "reads manifest from an absolute default_manifest" do manifestsdir = File.expand_path("manifests", confdir) FileUtils.mkdir_p(manifestsdir) File.open(File.join(confdir, "puppet.conf"), "w") do |f| f.puts(<<-EOF) environmentpath=#{environmentpath} default_manifest=#{manifestsdir} EOF end File.open(File.join(manifestsdir, "site.pp"), "w") do |f| f.puts("notify { 'ManifestFromAbsoluteDefaultManifest': }") end expect(a_catalog_compiled_for_environment('testing')).to( include_resource('Notify[ManifestFromAbsoluteDefaultManifest]') ) end it "reads manifest from directory environment manifest when environment.conf manifest set" do default_manifestsdir = File.expand_path("manifests", confdir) File.open(File.join(confdir, "puppet.conf"), "w") do |f| f.puts(<<-EOF) environmentpath=#{environmentpath} default_manifest=#{default_manifestsdir} EOF end manifestsdir = File.join(testingdir, "special_manifests") FileUtils.mkdir_p(manifestsdir) File.open(File.join(manifestsdir, "site.pp"), "w") do |f| f.puts("notify { 'ManifestFromEnvironmentConfManifest': }") end File.open(File.join(testingdir, "environment.conf"), "w") do |f| f.puts("manifest=./special_manifests") end expect(a_catalog_compiled_for_environment('testing')).to( include_resource('Notify[ManifestFromEnvironmentConfManifest]') ) expect(Puppet[:default_manifest]).to eq(default_manifestsdir) end it "ignores manifests in the local ./manifests if default_manifest specifies another directory" do default_manifestsdir = File.expand_path("manifests", confdir) FileUtils.mkdir_p(default_manifestsdir) File.open(File.join(confdir, "puppet.conf"), "w") do |f| f.puts(<<-EOF) environmentpath=#{environmentpath} default_manifest=#{default_manifestsdir} EOF end File.open(File.join(default_manifestsdir, "site.pp"), "w") do |f| f.puts("notify { 'ManifestFromAbsoluteDefaultManifest': }") end implicit_manifestsdir = File.join(testingdir, "manifests") FileUtils.mkdir_p(implicit_manifestsdir) File.open(File.join(implicit_manifestsdir, "site.pp"), "w") do |f| f.puts("notify { 'ManifestFromImplicitRelativeEnvironmentManifestDirectory': }") end expect(a_catalog_compiled_for_environment('testing')).to( include_resource('Notify[ManifestFromAbsoluteDefaultManifest]') ) end end context "with disable_per_environment_manifest true" do let(:manifestsdir) { File.expand_path("manifests", confdir) } let(:testingdir) { File.join(environmentpath, "testing") } before(:each) do FileUtils.mkdir_p(testingdir) end before(:each) do FileUtils.mkdir_p(manifestsdir) File.open(File.join(confdir, "puppet.conf"), "w") do |f| f.puts(<<-EOF) environmentpath=#{environmentpath} default_manifest=#{manifestsdir} disable_per_environment_manifest=true EOF end File.open(File.join(manifestsdir, "site.pp"), "w") do |f| f.puts("notify { 'ManifestFromAbsoluteDefaultManifest': }") end end it "reads manifest from the default manifest setting" do expect(a_catalog_compiled_for_environment('testing')).to( include_resource('Notify[ManifestFromAbsoluteDefaultManifest]') ) end it "refuses to compile if environment.conf specifies a different manifest" do File.open(File.join(testingdir, "environment.conf"), "w") do |f| f.puts("manifest=./special_manifests") end expect { a_catalog_compiled_for_environment('testing') }.to( raise_error(Puppet::Error, /disable_per_environment_manifest.*environment.conf.*manifest.*conflict/) ) end it "reads manifest from default_manifest setting when environment.conf has manifest set if setting equals default_manifest setting" do File.open(File.join(testingdir, "environment.conf"), "w") do |f| f.puts("manifest=#{manifestsdir}") end expect(a_catalog_compiled_for_environment('testing')).to( include_resource('Notify[ManifestFromAbsoluteDefaultManifest]') ) end it "logs errors if environment.conf specifies a different manifest" do File.open(File.join(testingdir, "environment.conf"), "w") do |f| f.puts("manifest=./special_manifests") end Puppet.initialize_settings expect(Puppet[:environmentpath]).to eq(environmentpath) environment = Puppet.lookup(:environments).get('testing') expect(environment.manifest).to eq(manifestsdir) expect(@logs.first.to_s).to match(%r{disable_per_environment_manifest.*is true, but.*environment.*at #{testingdir}.*has.*environment.conf.*manifest.*#{testingdir}/special_manifests}) end it "raises an error if default_manifest is not absolute" do File.open(File.join(confdir, "puppet.conf"), "w") do |f| f.puts(<<-EOF) environmentpath=#{environmentpath} default_manifest=./relative disable_per_environment_manifest=true EOF end expect { Puppet.initialize_settings }.to raise_error(Puppet::Settings::ValidationError, /default_manifest.*must be.*absolute.*when.*disable_per_environment_manifest.*true/) end end context "in legacy environments" do let(:environmentpath) { '' } let(:manifestsdir) { File.expand_path("default_manifests", confdir) } let(:legacy_manifestsdir) { File.expand_path('manifests', confdir) } before(:each) do FileUtils.mkdir_p(manifestsdir) File.open(File.join(confdir, "puppet.conf"), "w") do |f| f.puts(<<-EOF) default_manifest=#{manifestsdir} disable_per_environment_manifest=true manifest=#{legacy_manifestsdir} EOF end File.open(File.join(manifestsdir, "site.pp"), "w") do |f| f.puts("notify { 'ManifestFromAbsoluteDefaultManifest': }") end end it "has no effect on compilation" do FileUtils.mkdir_p(legacy_manifestsdir) File.open(File.join(legacy_manifestsdir, "site.pp"), "w") do |f| f.puts("notify { 'ManifestFromLegacy': }") end expect(a_catalog_compiled_for_environment('testing')).to( include_resource('Notify[ManifestFromLegacy]') ) end end end - describe 'using future parser' do - before :each do - Puppet[:parser] = 'future' - end - it_behaves_like 'puppet with default_manifest settings' - end - - describe 'using current parser' do - before :each do - Puppet[:parser] = 'current' - end - it_behaves_like 'puppet with default_manifest settings' - end RSpec::Matchers.define :include_resource do |expected| match do |actual| actual.resources.map(&:ref).include?(expected) end def failure_message_for_should "expected #{@actual.resources.map(&:ref)} to include #{expected}" end def failure_message_for_should_not "expected #{@actual.resources.map(&:ref)} not to include #{expected}" end end def a_catalog_compiled_for_environment(envname) Puppet.initialize_settings expect(Puppet[:environmentpath]).to eq(environmentpath) node = Puppet::Node.new('testnode', :environment => 'testing') expect(node.environment).to eq(Puppet.lookup(:environments).get('testing')) Puppet::Parser::Compiler.compile(node) end end end diff --git a/spec/integration/parser/catalog_spec.rb b/spec/integration/parser/catalog_spec.rb index 39aeb394e..96e3f5bbf 100644 --- a/spec/integration/parser/catalog_spec.rb +++ b/spec/integration/parser/catalog_spec.rb @@ -1,125 +1,94 @@ require 'spec_helper' require 'matchers/include_in_order' require 'puppet_spec/compiler' require 'puppet/indirector/catalog/compiler' describe "A catalog" do include PuppetSpec::Compiler - shared_examples_for "when compiled" do + context "when compiled" do context "when transmitted to the agent" do it "preserves the order in which the resources are added to the catalog" do resources_in_declaration_order = ["Class[First]", "Second[position]", "Class[Third]", "Fourth[position]"] master_catalog, agent_catalog = master_and_agent_catalogs_for(<<-EOM) define fourth() { } class third { } define second() { fourth { "position": } } class first { second { "position": } class { "third": } } include first EOM expect(resources_in(master_catalog)). to include_in_order(*resources_in_declaration_order) expect(resources_in(agent_catalog)). to include_in_order(*resources_in_declaration_order) end it "does not contain unrealized, virtual resources" do virtual_resources = ["Unrealized[unreal]", "Class[Unreal]"] master_catalog, agent_catalog = master_and_agent_catalogs_for(<<-EOM) class unreal { } define unrealized() { } class real { @unrealized { "unreal": } @class { "unreal": } } include real EOM expect(resources_in(master_catalog)).to_not include(*virtual_resources) expect(resources_in(agent_catalog)).to_not include(*virtual_resources) end it "does not contain unrealized, exported resources" do exported_resources = ["Unrealized[unreal]", "Class[Unreal]"] master_catalog, agent_catalog = master_and_agent_catalogs_for(<<-EOM) class unreal { } define unrealized() { } class real { @@unrealized { "unreal": } @@class { "unreal": } } include real EOM expect(resources_in(master_catalog)).to_not include(*exported_resources) 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" - master_catalog = master_catalog_for(<<-EOM) - class parent { - $arr1 = [ "parent array element" ] - } - class parent::child inherits parent { - $arr1 += ["child array element"] - notify { $arr1: } - } - include parent::child - EOM - expect(resources_in(master_catalog)).to include('Notify[parent array element]', 'Notify[child array element]') - end - end - - describe 'using future parser' do - before :each do - Puppet[:parser] = 'future' - end - it_behaves_like 'when compiled' do - end - end - def master_catalog_for(manifest) master_catalog = Puppet::Resource::Catalog::Compiler.new.filter(compile_to_catalog(manifest)) end def master_and_agent_catalogs_for(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 def resources_in(catalog) catalog.resources.map(&:ref) end end diff --git a/spec/integration/parser/class_spec.rb b/spec/integration/parser/class_spec.rb index 9f63eb083..52edccfaa 100644 --- a/spec/integration/parser/class_spec.rb +++ b/spec/integration/parser/class_spec.rb @@ -1,37 +1,33 @@ 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 cfb4f0265..31ed0bf2f 100755 --- a/spec/integration/parser/collector_spec.rb +++ b/spec/integration/parser/collector_spec.rb @@ -1,288 +1,273 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet_spec/compiler' require 'puppet/parser/collector' describe Puppet::Parser::Collector do include PuppetSpec::Compiler def expect_the_message_to_be(expected_messages, code, node = Puppet::Node.new('the node')) catalog = compile_to_catalog(code, node) messages = catalog.resources.find_all { |resource| resource.type == 'Notify' }. collect { |notify| notify[:message] } messages.should include(*expected_messages) end - shared_examples_for "virtual resource collection" do + context "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 <| |> MANIFEST end 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 => "changed" } MANIFEST end 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 <| tag == one |> MANIFEST end it "matches on title" do expect_the_message_to_be(["the message"], <<-MANIFEST) @notify { "testing": message => "the message" } Notify <| title == "testing" |> 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" } Notify <| message == "the message" |> MANIFEST end 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 <| message == "message" |> 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" } Notify <| title == "testing" and message == "the message" |> 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" } Notify <| title == "testing" or message == "other message" |> 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" } 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 "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 it "does not collect resources that don't exist" do node = Puppet::Node.new('the node') expect do catalog = compile_to_catalog(<<-MANIFEST, node) class theclass { @notify { "testing": message => "good message" } } SomeResource <| |> MANIFEST end.to raise_error(/Resource type someresource doesn't exist/) 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"] } Notify <| |> { message +> "extra message" } 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/conditionals_spec.rb b/spec/integration/parser/conditionals_spec.rb index 82d950a06..70d2def2a 100644 --- a/spec/integration/parser/conditionals_spec.rb +++ b/spec/integration/parser/conditionals_spec.rb @@ -1,117 +1,92 @@ 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 + context "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/node_spec.rb b/spec/integration/parser/node_spec.rb index 7ce58f152..bf349e279 100644 --- a/spec/integration/parser/node_spec.rb +++ b/spec/integration/parser/node_spec.rb @@ -1,185 +1,149 @@ 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 + context '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 index bac1f77ad..b2a279611 100644 --- a/spec/integration/parser/resource_expressions_spec.rb +++ b/spec/integration/parser/resource_expressions_spec.rb @@ -1,299 +1,217 @@ 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 + 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])", - 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 { [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 { [[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 { []: }" => [], # 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'])") + "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/, + 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 { [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 { [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 { [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 { [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 { [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 { [{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 { [/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 + "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])") + 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( "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( "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])") + 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( "'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( "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( "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 + 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 "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'\]/) + 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 + 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'", + 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'", + "notify { title: *=> { message => set } }" => "Notify[title][message] == 'set'", - "$x = { message => set } notify { title: * => $x }" => "Notify[title][message] == 'set'", + "$x = { message => set } notify { title: * => $x }" => "Notify[title][message] == 'set'", - # picks up defaults - "$x = { owner => the_x } + # 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' } + # 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/) + 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/) + # 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/) + # 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(" + 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 + end - context "virtual" do - produces( - "@notify { example: }" => "!defined(Notify[example])", + context "virtual" do + produces( + "@notify { example: }" => "!defined(Notify[example])", - "@notify { example: } + "@notify { example: } realize(Notify[example])" => "defined(Notify[example])", - "@notify { virtual: message => set } + "@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 - - context "explicit undefs" do - # PUP-3505 - produces(" + 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 + + context "explicit undefs" do + # PUP-3505 + produces(" $x = 10 define foo($x = undef) { notify { example: message => \"'$x'\" } } foo {'blah': x => undef } " => "Notify[example][message] == \"''\"") - 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/unit/functions/each_spec.rb b/spec/unit/functions/each_spec.rb index d6651cf5a..b8d3289ac 100644 --- a/spec/unit/functions/each_spec.rb +++ b/spec/unit/functions/each_spec.rb @@ -1,111 +1,107 @@ require 'puppet' require 'spec_helper' require 'puppet_spec/compiler' require 'shared_behaviours/iterative_functions' describe 'the each method' do include PuppetSpec::Compiler - before :each do - Puppet[:parser] = 'future' - end - context "should be callable as" do it 'each on an array selecting each value' do catalog = compile_to_catalog(<<-MANIFEST) $a = [1,2,3] $a.each |$v| { file { "/file_$v": ensure => present } } MANIFEST catalog.resource(:file, "/file_1")['ensure'].should == 'present' catalog.resource(:file, "/file_2")['ensure'].should == 'present' catalog.resource(:file, "/file_3")['ensure'].should == 'present' end it 'each on an array selecting each value - function call style' do catalog = compile_to_catalog(<<-MANIFEST) $a = [1,2,3] each ($a) |$index, $v| { file { "/file_$v": ensure => present } } MANIFEST catalog.resource(:file, "/file_1")['ensure'].should == 'present' catalog.resource(:file, "/file_2")['ensure'].should == 'present' catalog.resource(:file, "/file_3")['ensure'].should == 'present' end it 'each on an array with index' do catalog = compile_to_catalog(<<-MANIFEST) $a = [present, absent, present] $a.each |$k,$v| { file { "/file_${$k+1}": ensure => $v } } MANIFEST catalog.resource(:file, "/file_1")['ensure'].should == 'present' catalog.resource(:file, "/file_2")['ensure'].should == 'absent' catalog.resource(:file, "/file_3")['ensure'].should == 'present' end it 'each on a hash selecting entries' do catalog = compile_to_catalog(<<-MANIFEST) $a = {'a'=>'present','b'=>'absent','c'=>'present'} $a.each |$e| { file { "/file_${e[0]}": ensure => $e[1] } } MANIFEST catalog.resource(:file, "/file_a")['ensure'].should == 'present' catalog.resource(:file, "/file_b")['ensure'].should == 'absent' catalog.resource(:file, "/file_c")['ensure'].should == 'present' end it 'each on a hash selecting key and value' do catalog = compile_to_catalog(<<-MANIFEST) $a = {'a'=>present,'b'=>absent,'c'=>present} $a.each |$k, $v| { file { "/file_$k": ensure => $v } } MANIFEST catalog.resource(:file, "/file_a")['ensure'].should == 'present' catalog.resource(:file, "/file_b")['ensure'].should == 'absent' catalog.resource(:file, "/file_c")['ensure'].should == 'present' end it 'each on a hash selecting key and value (using captures-last parameter)' do catalog = compile_to_catalog(<<-MANIFEST) $a = {'a'=>present,'b'=>absent,'c'=>present} $a.each |*$kv| { file { "/file_${kv[0]}": ensure => $kv[1] } } MANIFEST catalog.resource(:file, "/file_a")['ensure'].should == 'present' catalog.resource(:file, "/file_b")['ensure'].should == 'absent' catalog.resource(:file, "/file_c")['ensure'].should == 'present' end end context "should produce receiver" do it 'each checking produced value using single expression' do catalog = compile_to_catalog(<<-MANIFEST) $a = [1, 3, 2] $b = $a.each |$x| { "unwanted" } file { "/file_${b[1]}": ensure => present } MANIFEST catalog.resource(:file, "/file_3")['ensure'].should == 'present' end end it_should_behave_like 'all iterative functions argument checks', 'each' it_should_behave_like 'all iterative functions hash handling', 'each' end diff --git a/spec/unit/functions/epp_spec.rb b/spec/unit/functions/epp_spec.rb index da58748b3..4586d664c 100644 --- a/spec/unit/functions/epp_spec.rb +++ b/spec/unit/functions/epp_spec.rb @@ -1,154 +1,146 @@ require 'spec_helper' describe "the epp function" do include PuppetSpec::Files - before :all do - Puppet::Parser::Functions.autoloader.loadall - end - - before :each do - Puppet[:parser] = 'future' - end - let :node do Puppet::Node.new('localhost') end let :compiler do Puppet::Parser::Compiler.new(node) end let :scope do Puppet::Parser::Scope.new(compiler) end context "when accessing scope variables as $ variables" do it "looks up the value from the scope" do scope["what"] = "are belong" eval_template("all your base <%= $what %> to us").should == "all your base are belong to us" end it "get nil accessing a variable that does not exist" do eval_template("<%= $kryptonite == undef %>").should == "true" end it "get nil accessing a variable that is undef" do scope['undef_var'] = nil eval_template("<%= $undef_var == undef %>").should == "true" end it "gets shadowed variable if args are given" do scope['phantom'] = 'of the opera' eval_template_with_args("<%= $phantom == dragos %>", 'phantom' => 'dragos').should == "true" end it "can use values from the enclosing scope for defaults" do scope['phantom'] = 'of the opera' eval_template("<%- |$phantom = $phantom| -%><%= $phantom %>").should == "of the opera" end it "uses the default value if the given value is undef/nil" do eval_template_with_args("<%- |$phantom = 'inside your mind'| -%><%= $phantom %>", 'phantom' => nil).should == "inside your mind" end it "gets shadowed variable if args are given and parameters are specified" do scope['x'] = 'wrong one' eval_template_with_args("<%- |$x| -%><%= $x == correct %>", 'x' => 'correct').should == "true" end it "raises an error if required variable is not given" do scope['x'] = 'wrong one' expect do eval_template_with_args("<%-| $x |-%><%= $x == correct %>", 'y' => 'correct') end.to raise_error(/no value given for required parameters x/) end it "raises an error if too many arguments are given" do scope['x'] = 'wrong one' expect do eval_template_with_args("<%-| $x |-%><%= $x == correct %>", 'x' => 'correct', 'y' => 'surplus') end.to raise_error(/Too many arguments: 2 for 1/) end end context "when given an empty template" do it "allows the template file to be empty" do expect(eval_template("")).to eq("") end it "allows the template to have empty body after parameters" do expect(eval_template_with_args("<%-|$x|%>", 'x'=>1)).to eq("") end end context "when using typed parameters" do it "allows a passed value that matches the parameter's type" do expect(eval_template_with_args("<%-|String $x|-%><%= $x == correct %>", 'x' => 'correct')).to eq("true") end it "does not allow slurped parameters" do expect do eval_template_with_args("<%-|*$x|-%><%= $x %>", 'x' => 'incorrect') end.to raise_error(/'captures rest' - not supported in an Epp Template/) end it "raises an error when the passed value does not match the parameter's type" do expect do eval_template_with_args("<%-|Integer $x|-%><%= $x %>", 'x' => 'incorrect') end.to raise_error(/expected.*Integer.*actual.*String/m) end it "raises an error when the default value does not match the parameter's type" do expect do eval_template("<%-|Integer $x = 'nope'|-%><%= $x %>") end.to raise_error(/expected.*Integer.*actual.*String/m) end it "allows an parameter to default to undef" do expect(eval_template("<%-|Optional[Integer] $x = undef|-%><%= $x == undef %>")).to eq("true") end end # although never a problem with epp it "is not interfered with by having a variable named 'string' (#14093)" do scope['string'] = "this output should not be seen" eval_template("some text that is static").should == "some text that is static" end it "has access to a variable named 'string' (#14093)" do scope['string'] = "the string value" eval_template("string was: <%= $string %>").should == "string was: the string value" end describe 'when loading from modules' do include PuppetSpec::Files it 'an epp template is found' do modules_dir = dir_containing('modules', { 'testmodule' => { 'templates' => { 'the_x.epp' => 'The x is <%= $x %>' } }}) Puppet.override({:current_environment => (env = Puppet::Node::Environment.create(:testload, [ modules_dir ]))}, "test") do node.environment = env expect(epp_function.call(scope, 'testmodule/the_x.epp', { 'x' => '3'} )).to eql("The x is 3") end end end def eval_template_with_args(content, args_hash) file_path = tmpdir('epp_spec_content') filename = File.join(file_path, "template.epp") File.open(filename, "w+") { |f| f.write(content) } Puppet::Parser::Files.stubs(:find_template).returns(filename) epp_function.call(scope, 'template', args_hash) end def eval_template(content) file_path = tmpdir('epp_spec_content') filename = File.join(file_path, "template.epp") File.open(filename, "w+") { |f| f.write(content) } Puppet::Parser::Files.stubs(:find_template).returns(filename) epp_function.call(scope, 'template') end def epp_function() epp_func = scope.compiler.loaders.public_environment_loader.load(:function, 'epp') end end diff --git a/spec/unit/functions/filter_spec.rb b/spec/unit/functions/filter_spec.rb index e904c6751..b2aecc938 100644 --- a/spec/unit/functions/filter_spec.rb +++ b/spec/unit/functions/filter_spec.rb @@ -1,131 +1,127 @@ require 'puppet' require 'spec_helper' require 'puppet_spec/compiler' require 'matchers/resource' require 'shared_behaviours/iterative_functions' describe 'the filter method' do include PuppetSpec::Compiler include Matchers::Resource - before :each do - Puppet[:parser] = 'future' - end - it 'should filter on an array (all berries)' do catalog = compile_to_catalog(<<-MANIFEST) $a = ['strawberry','blueberry','orange'] $a.filter |$x|{ $x =~ /berry$/}.each |$v|{ file { "/file_$v": ensure => present } } MANIFEST expect(catalog).to have_resource("File[/file_strawberry]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_blueberry]").with_parameter(:ensure, 'present') end it 'should filter on enumerable type (Integer)' do catalog = compile_to_catalog(<<-MANIFEST) $a = Integer[1,10] $a.filter |$x|{ $x % 3 == 0}.each |$v|{ file { "/file_$v": ensure => present } } MANIFEST expect(catalog).to have_resource("File[/file_3]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_6]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_9]").with_parameter(:ensure, 'present') end it 'should filter on enumerable type (Integer) using two args index/value' do catalog = compile_to_catalog(<<-MANIFEST) $a = Integer[10,18] $a.filter |$i, $x|{ $i % 3 == 0}.each |$v|{ file { "/file_$v": ensure => present } } MANIFEST expect(catalog).to have_resource("File[/file_10]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_13]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_16]").with_parameter(:ensure, 'present') end it 'should produce an array when acting on an array' do catalog = compile_to_catalog(<<-MANIFEST) $a = ['strawberry','blueberry','orange'] $b = $a.filter |$x|{ $x =~ /berry$/} file { "/file_${b[0]}": ensure => present } file { "/file_${b[1]}": ensure => present } MANIFEST expect(catalog).to have_resource("File[/file_strawberry]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_blueberry]").with_parameter(:ensure, 'present') end it 'can filter array using index and value' do catalog = compile_to_catalog(<<-MANIFEST) $a = ['strawberry','blueberry','orange'] $b = $a.filter |$index, $x|{ $index == 0 or $index ==2} file { "/file_${b[0]}": ensure => present } file { "/file_${b[1]}": ensure => present } MANIFEST expect(catalog).to have_resource("File[/file_strawberry]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_orange]").with_parameter(:ensure, 'present') end it 'can filter array using index and value (using captures-rest)' do catalog = compile_to_catalog(<<-MANIFEST) $a = ['strawberry','blueberry','orange'] $b = $a.filter |*$ix|{ $ix[0] == 0 or $ix[0] ==2} file { "/file_${b[0]}": ensure => present } file { "/file_${b[1]}": ensure => present } MANIFEST expect(catalog).to have_resource("File[/file_strawberry]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_orange]").with_parameter(:ensure, 'present') end it 'filters on a hash (all berries) by key' do catalog = compile_to_catalog(<<-MANIFEST) $a = {'strawberry'=>'red','blueberry'=>'blue','orange'=>'orange'} $a.filter |$x|{ $x[0] =~ /berry$/}.each |$v|{ file { "/file_${v[0]}": ensure => present } } MANIFEST expect(catalog).to have_resource("File[/file_strawberry]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_blueberry]").with_parameter(:ensure, 'present') end it 'should produce a hash when acting on a hash' do catalog = compile_to_catalog(<<-MANIFEST) $a = {'strawberry'=>'red','blueberry'=>'blue','orange'=>'orange'} $b = $a.filter |$x|{ $x[0] =~ /berry$/} file { "/file_${b['strawberry']}": ensure => present } file { "/file_${b['blueberry']}": ensure => present } file { "/file_${b['orange']}": ensure => present } MANIFEST expect(catalog).to have_resource("File[/file_red]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_blue]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_]").with_parameter(:ensure, 'present') end it 'filters on a hash (all berries) by value' do catalog = compile_to_catalog(<<-MANIFEST) $a = {'strawb'=>'red berry','blueb'=>'blue berry','orange'=>'orange fruit'} $a.filter |$x|{ $x[1] =~ /berry$/}.each |$v|{ file { "/file_${v[0]}": ensure => present } } MANIFEST expect(catalog).to have_resource("File[/file_strawb]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_blueb]").with_parameter(:ensure, 'present') end it_should_behave_like 'all iterative functions argument checks', 'filter' it_should_behave_like 'all iterative functions hash handling', 'filter' end diff --git a/spec/unit/functions/inline_epp_spec.rb b/spec/unit/functions/inline_epp_spec.rb index 36328c2dc..f428e3895 100644 --- a/spec/unit/functions/inline_epp_spec.rb +++ b/spec/unit/functions/inline_epp_spec.rb @@ -1,97 +1,89 @@ require 'spec_helper' describe "the inline_epp function" do include PuppetSpec::Files - before :all do - Puppet::Parser::Functions.autoloader.loadall - end - - before :each do - Puppet[:parser] = 'future' - end - let :node do Puppet::Node.new('localhost') end let :compiler do Puppet::Parser::Compiler.new(node) end let :scope do Puppet::Parser::Scope.new(compiler) end context "when accessing scope variables as $ variables" do it "looks up the value from the scope" do scope["what"] = "are belong" eval_template("all your base <%= $what %> to us").should == "all your base are belong to us" end it "get nil accessing a variable that does not exist" do eval_template("<%= $kryptonite == undef %>").should == "true" end it "get nil accessing a variable that is undef" do scope['undef_var'] = :undef eval_template("<%= $undef_var == undef %>").should == "true" end it "gets shadowed variable if args are given" do scope['phantom'] = 'of the opera' eval_template_with_args("<%= $phantom == dragos %>", 'phantom' => 'dragos').should == "true" end it "gets shadowed variable if args are given and parameters are specified" do scope['x'] = 'wrong one' eval_template_with_args("<%-| $x |-%><%= $x == correct %>", 'x' => 'correct').should == "true" end it "raises an error if required variable is not given" do scope['x'] = 'wrong one' expect { eval_template_with_args("<%-| $x |-%><%= $x == correct %>", 'y' => 'correct') }.to raise_error(/no value given for required parameters x/) end it "raises an error if too many arguments are given" do scope['x'] = 'wrong one' expect { eval_template_with_args("<%-| $x |-%><%= $x == correct %>", 'x' => 'correct', 'y' => 'surplus') }.to raise_error(/Too many arguments: 2 for 1/) end end context "when given an empty template" do it "allows the template file to be empty" do expect(eval_template("")).to eq("") end it "allows the template to have empty body after parameters" do expect(eval_template_with_args("<%-|$x|%>", 'x'=>1)).to eq("") end end it "renders a block expression" do eval_template_with_args("<%= { $y = $x $x + 1} %>", 'x' => 2).should == "3" end # although never a problem with epp it "is not interfered with by having a variable named 'string' (#14093)" do scope['string'] = "this output should not be seen" eval_template("some text that is static").should == "some text that is static" end it "has access to a variable named 'string' (#14093)" do scope['string'] = "the string value" eval_template("string was: <%= $string %>").should == "string was: the string value" end def eval_template_with_args(content, args_hash) epp_function.call(scope, content, args_hash) end def eval_template(content) epp_function.call(scope, content) end def epp_function() epp_func = scope.compiler.loaders.public_environment_loader.load(:function, 'inline_epp') end end diff --git a/spec/unit/functions/map_spec.rb b/spec/unit/functions/map_spec.rb index e1b09cf24..addce2558 100644 --- a/spec/unit/functions/map_spec.rb +++ b/spec/unit/functions/map_spec.rb @@ -1,169 +1,165 @@ require 'puppet' require 'spec_helper' require 'puppet_spec/compiler' require 'matchers/resource' require 'shared_behaviours/iterative_functions' describe 'the map method' do include PuppetSpec::Compiler include Matchers::Resource - before :each do - Puppet[:parser] = "future" - end - context "using future parser" do it 'map on an array (multiplying each value by 2)' do catalog = compile_to_catalog(<<-MANIFEST) $a = [1,2,3] $a.map |$x|{ $x*2}.each |$v|{ file { "/file_$v": ensure => present } } MANIFEST expect(catalog).to have_resource("File[/file_2]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_4]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_6]").with_parameter(:ensure, 'present') end it 'map on an enumerable type (multiplying each value by 2)' do catalog = compile_to_catalog(<<-MANIFEST) $a = Integer[1,3] $a.map |$x|{ $x*2}.each |$v|{ file { "/file_$v": ensure => present } } MANIFEST expect(catalog).to have_resource("File[/file_2]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_4]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_6]").with_parameter(:ensure, 'present') end it 'map on an integer (multiply each by 3)' do catalog = compile_to_catalog(<<-MANIFEST) 3.map |$x|{ $x*3}.each |$v|{ file { "/file_$v": ensure => present } } MANIFEST expect(catalog).to have_resource("File[/file_0]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_3]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_6]").with_parameter(:ensure, 'present') end it 'map on a string' do catalog = compile_to_catalog(<<-MANIFEST) $a = {a=>x, b=>y} "ab".map |$x|{$a[$x]}.each |$v|{ file { "/file_$v": ensure => present } } MANIFEST expect(catalog).to have_resource("File[/file_x]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_y]").with_parameter(:ensure, 'present') end it 'map on an array (multiplying value by 10 in even index position)' do catalog = compile_to_catalog(<<-MANIFEST) $a = [1,2,3] $a.map |$i, $x|{ if $i % 2 == 0 {$x} else {$x*10}}.each |$v|{ file { "/file_$v": ensure => present } } MANIFEST expect(catalog).to have_resource("File[/file_1]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_20]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_3]").with_parameter(:ensure, 'present') end it 'map on a hash selecting keys' do catalog = compile_to_catalog(<<-MANIFEST) $a = {'a'=>1,'b'=>2,'c'=>3} $a.map |$x|{ $x[0]}.each |$k|{ file { "/file_$k": ensure => present } } MANIFEST expect(catalog).to have_resource("File[/file_a]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_b]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_c]").with_parameter(:ensure, 'present') end it 'map on a hash selecting keys - using two block parameters' do catalog = compile_to_catalog(<<-MANIFEST) $a = {'a'=>1,'b'=>2,'c'=>3} $a.map |$k,$v|{ file { "/file_$k": ensure => present } } MANIFEST expect(catalog).to have_resource("File[/file_a]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_b]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_c]").with_parameter(:ensure, 'present') end it 'map on a hash using captures-last parameter' do catalog = compile_to_catalog(<<-MANIFEST) $a = {'a'=>present,'b'=>absent,'c'=>present} $a.map |*$kv|{ file { "/file_${kv[0]}": ensure => $kv[1] } } MANIFEST expect(catalog).to have_resource("File[/file_a]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_b]").with_parameter(:ensure, 'absent') expect(catalog).to have_resource("File[/file_c]").with_parameter(:ensure, 'present') end it 'each on a hash selecting value' do catalog = compile_to_catalog(<<-MANIFEST) $a = {'a'=>1,'b'=>2,'c'=>3} $a.map |$x|{ $x[1]}.each |$k|{ file { "/file_$k": ensure => present } } MANIFEST expect(catalog).to have_resource("File[/file_1]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_2]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_3]").with_parameter(:ensure, 'present') end it 'each on a hash selecting value - using two block parameters' do catalog = compile_to_catalog(<<-MANIFEST) $a = {'a'=>1,'b'=>2,'c'=>3} $a.map |$k,$v|{ file { "/file_$v": ensure => present } } MANIFEST expect(catalog).to have_resource("File[/file_1]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_2]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_3]").with_parameter(:ensure, 'present') end context "handles data type corner cases" do it "map gets values that are false" do catalog = compile_to_catalog(<<-MANIFEST) $a = [false,false] $a.map |$x| { $x }.each |$i, $v| { file { "/file_$i.$v": ensure => present } } MANIFEST expect(catalog).to have_resource("File[/file_0.false]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_1.false]").with_parameter(:ensure, 'present') end it "map gets values that are nil" do Puppet::Parser::Functions.newfunction(:nil_array, :type => :rvalue) do |args| [nil] end catalog = compile_to_catalog(<<-MANIFEST) $a = nil_array() $a.map |$x| { $x }.each |$i, $v| { file { "/file_$i.$v": ensure => present } } MANIFEST expect(catalog).to have_resource("File[/file_0.]").with_parameter(:ensure, 'present') end end it_should_behave_like 'all iterative functions argument checks', 'map' it_should_behave_like 'all iterative functions hash handling', 'map' end end diff --git a/spec/unit/functions/reduce_spec.rb b/spec/unit/functions/reduce_spec.rb index 032f6ccc4..44868660f 100644 --- a/spec/unit/functions/reduce_spec.rb +++ b/spec/unit/functions/reduce_spec.rb @@ -1,96 +1,85 @@ require 'puppet' require 'spec_helper' require 'puppet_spec/compiler' require 'matchers/resource' require 'shared_behaviours/iterative_functions' describe 'the reduce method' do include PuppetSpec::Compiler include Matchers::Resource - before :all do - # enable switching back - @saved_parser = Puppet[:parser] - # These tests only work with future parser - end - after :all do - # switch back to original - Puppet[:parser] = @saved_parser - end - before :each do node = Puppet::Node.new("floppy", :environment => 'production') @compiler = Puppet::Parser::Compiler.new(node) @scope = Puppet::Parser::Scope.new(@compiler) @topscope = @scope.compiler.topscope @scope.parent = @topscope - Puppet[:parser] = 'future' end context "should be callable as" do it 'reduce on an array' do catalog = compile_to_catalog(<<-MANIFEST) $a = [1,2,3] $b = $a.reduce |$memo, $x| { $memo + $x } file { "/file_$b": ensure => present } MANIFEST expect(catalog).to have_resource("File[/file_6]").with_parameter(:ensure, 'present') end it 'reduce on an array with captures rest in lambda' do catalog = compile_to_catalog(<<-MANIFEST) $a = [1,2,3] $b = $a.reduce |*$mx| { $mx[0] + $mx[1] } file { "/file_$b": ensure => present } MANIFEST expect(catalog).to have_resource("File[/file_6]").with_parameter(:ensure, 'present') end it 'reduce on enumerable type' do catalog = compile_to_catalog(<<-MANIFEST) $a = Integer[1,3] $b = $a.reduce |$memo, $x| { $memo + $x } file { "/file_$b": ensure => present } MANIFEST expect(catalog).to have_resource("File[/file_6]").with_parameter(:ensure, 'present') end it 'reduce on an array with start value' do catalog = compile_to_catalog(<<-MANIFEST) $a = [1,2,3] $b = $a.reduce(4) |$memo, $x| { $memo + $x } file { "/file_$b": ensure => present } MANIFEST expect(catalog).to have_resource("File[/file_10]").with_parameter(:ensure, 'present') end it 'reduce on a hash' do catalog = compile_to_catalog(<<-MANIFEST) $a = {a=>1, b=>2, c=>3} $start = [ignored, 4] $b = $a.reduce |$memo, $x| {['sum', $memo[1] + $x[1]] } file { "/file_${$b[0]}_${$b[1]}": ensure => present } MANIFEST expect(catalog).to have_resource("File[/file_sum_6]").with_parameter(:ensure, 'present') end it 'reduce on a hash with start value' do catalog = compile_to_catalog(<<-MANIFEST) $a = {a=>1, b=>2, c=>3} $start = ['ignored', 4] $b = $a.reduce($start) |$memo, $x| { ['sum', $memo[1] + $x[1]] } file { "/file_${$b[0]}_${$b[1]}": ensure => present } MANIFEST expect(catalog).to have_resource("File[/file_sum_10]").with_parameter(:ensure, 'present') end end it_should_behave_like 'all iterative functions argument checks', 'reduce' end diff --git a/spec/unit/functions/slice_spec.rb b/spec/unit/functions/slice_spec.rb index 945cae5c7..f900f9300 100644 --- a/spec/unit/functions/slice_spec.rb +++ b/spec/unit/functions/slice_spec.rb @@ -1,148 +1,136 @@ require 'puppet' require 'spec_helper' require 'puppet_spec/compiler' require 'matchers/resource' describe 'methods' do include PuppetSpec::Compiler include Matchers::Resource - before :all do - # enable switching back - @saved_parser = Puppet[:parser] - # These tests only work with future parser - Puppet[:parser] = 'future' - end - after :all do - # switch back to original - Puppet[:parser] = @saved_parser - end - before :each do node = Puppet::Node.new("floppy", :environment => 'production') @compiler = Puppet::Parser::Compiler.new(node) @scope = Puppet::Parser::Scope.new(@compiler) @topscope = @scope.compiler.topscope @scope.parent = @topscope - Puppet[:parser] = 'future' end context "should be callable on array as" do it 'slice with explicit parameters' do catalog = compile_to_catalog(<<-MANIFEST) $a = [1, present, 2, absent, 3, present] $a.slice(2) |$k,$v| { file { "/file_${$k}": ensure => $v } } MANIFEST expect(catalog).to have_resource("File[/file_1]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_2]").with_parameter(:ensure, 'absent') expect(catalog).to have_resource("File[/file_3]").with_parameter(:ensure, 'present') end it 'slice with captures last' do catalog = compile_to_catalog(<<-MANIFEST) $a = [1, present, 2, absent, 3, present] $a.slice(2) |*$kv| { file { "/file_${$kv[0]}": ensure => $kv[1] } } MANIFEST expect(catalog).to have_resource("File[/file_1]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_2]").with_parameter(:ensure, 'absent') expect(catalog).to have_resource("File[/file_3]").with_parameter(:ensure, 'present') end it 'slice with one parameter' do catalog = compile_to_catalog(<<-MANIFEST) $a = [1, present, 2, absent, 3, present] $a.slice(2) |$k| { file { "/file_${$k[0]}": ensure => $k[1] } } MANIFEST expect(catalog).to have_resource("File[/file_1]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_2]").with_parameter(:ensure, 'absent') expect(catalog).to have_resource("File[/file_3]").with_parameter(:ensure, 'present') end it 'slice with shorter last slice' do catalog = compile_to_catalog(<<-MANIFEST) $a = [1, present, 2, present, 3, absent] $a.slice(4) |$a, $b, $c, $d| { file { "/file_$a.$c": ensure => $b } } MANIFEST expect(catalog).to have_resource("File[/file_1.2]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_3.]").with_parameter(:ensure, 'absent') end end context "should be callable on hash as" do it 'slice with explicit parameters, missing are empty' do catalog = compile_to_catalog(<<-MANIFEST) $a = {1=>present, 2=>present, 3=>absent} $a.slice(2) |$a,$b| { file { "/file_${a[0]}.${b[0]}": ensure => $a[1] } } MANIFEST expect(catalog).to have_resource("File[/file_1.2]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_3.]").with_parameter(:ensure, 'absent') end end context "should be callable on enumerable types as" do it 'slice with integer range' do catalog = compile_to_catalog(<<-MANIFEST) $a = Integer[1,4] $a.slice(2) |$a,$b| { file { "/file_${a}.${b}": ensure => present } } MANIFEST expect(catalog).to have_resource("File[/file_1.2]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_3.4]").with_parameter(:ensure, 'present') end it 'slice with integer' do catalog = compile_to_catalog(<<-MANIFEST) 4.slice(2) |$a,$b| { file { "/file_${a}.${b}": ensure => present } } MANIFEST expect(catalog).to have_resource("File[/file_0.1]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_2.3]").with_parameter(:ensure, 'present') end it 'slice with string' do catalog = compile_to_catalog(<<-MANIFEST) 'abcd'.slice(2) |$a,$b| { file { "/file_${a}.${b}": ensure => present } } MANIFEST expect(catalog).to have_resource("File[/file_a.b]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_c.d]").with_parameter(:ensure, 'present') end end context "when called without a block" do it "should produce an array with the result" do catalog = compile_to_catalog(<<-MANIFEST) $a = [1, present, 2, absent, 3, present] $a.slice(2).each |$k| { file { "/file_${$k[0]}": ensure => $k[1] } } MANIFEST expect(catalog).to have_resource("File[/file_1]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_2]").with_parameter(:ensure, 'absent') expect(catalog).to have_resource("File[/file_3]").with_parameter(:ensure, 'present') end end end diff --git a/spec/unit/functions/with_spec.rb b/spec/unit/functions/with_spec.rb index 952b14412..9b3f9b1a9 100644 --- a/spec/unit/functions/with_spec.rb +++ b/spec/unit/functions/with_spec.rb @@ -1,35 +1,31 @@ require 'spec_helper' require 'puppet_spec/compiler' require 'matchers/resource' describe 'the with function' do include PuppetSpec::Compiler include Matchers::Resource - before :each do - Puppet[:parser] = 'future' - end - it 'calls a lambda passing no arguments' do expect(compile_to_catalog("with() || { notify { testing: } }")).to have_resource('Notify[testing]') end it 'calls a lambda passing a single argument' do expect(compile_to_catalog('with(1) |$x| { notify { "testing$x": } }')).to have_resource('Notify[testing1]') end it 'calls a lambda passing more than one argument' do expect(compile_to_catalog('with(1, 2) |*$x| { notify { "testing${x[0]}, ${x[1]}": } }')).to have_resource('Notify[testing1, 2]') end it 'passes a type reference to a lambda' do expect(compile_to_catalog('notify { test: message => "data" } with(Notify[test]) |$x| { notify { "${x[message]}": } }')).to have_resource('Notify[data]') end it 'errors when not given enough arguments for the lambda' do expect do compile_to_catalog('with(1) |$x, $y| { }') end.to raise_error(/Parameter \$y is required but no value was given/m) end end diff --git a/spec/unit/functions4_spec.rb b/spec/unit/functions4_spec.rb index 8bbe855a1..1f4b1aab6 100644 --- a/spec/unit/functions4_spec.rb +++ b/spec/unit/functions4_spec.rb @@ -1,670 +1,663 @@ require 'spec_helper' require 'puppet/pops' require 'puppet/loaders' require 'puppet_spec/pops' require 'puppet_spec/scope' module FunctionAPISpecModule class TestDuck end class TestFunctionLoader < Puppet::Pops::Loader::StaticLoader def initialize @functions = {} end def add_function(name, function) typed_name = Puppet::Pops::Loader::Loader::TypedName.new(:function, name) entry = Puppet::Pops::Loader::Loader::NamedEntry.new(typed_name, function, __FILE__) @functions[typed_name] = entry end # override StaticLoader def load_constant(typed_name) @functions[typed_name] end end end describe 'the 4x function api' do include FunctionAPISpecModule include PuppetSpec::Pops include PuppetSpec::Scope let(:loader) { FunctionAPISpecModule::TestFunctionLoader.new } it 'allows a simple function to be created without dispatch declaration' do f = Puppet::Functions.create_function('min') do def min(x,y) x <= y ? x : y end end # the produced result is a Class inheriting from Function expect(f.class).to be(Class) expect(f.superclass).to be(Puppet::Functions::Function) # and this class had the given name (not a real Ruby class name) expect(f.name).to eql('min') end it 'refuses to create functions that are not based on the Function class' do expect do Puppet::Functions.create_function('testing', Object) {} end.to raise_error(ArgumentError, 'Functions must be based on Puppet::Pops::Functions::Function. Got Object') end it 'a function without arguments can be defined and called without dispatch declaration' do f = create_noargs_function_class() func = f.new(:closure_scope, :loader) expect(func.call({})).to eql(10) end it 'an error is raised when calling a no arguments function with arguments' do f = create_noargs_function_class() func = f.new(:closure_scope, :loader) expect{func.call({}, 'surprise')}.to raise_error(ArgumentError, "function 'test' called with mis-matched arguments expected: test() - arg count {0} actual: test(String) - arg count {1}") end it 'a simple function can be called' do f = create_min_function_class() # TODO: Bogus parameters, not yet used func = f.new(:closure_scope, :loader) expect(func.is_a?(Puppet::Functions::Function)).to be_true expect(func.call({}, 10,20)).to eql(10) end it 'an error is raised if called with too few arguments' do f = create_min_function_class() # TODO: Bogus parameters, not yet used func = f.new(:closure_scope, :loader) expect(func.is_a?(Puppet::Functions::Function)).to be_true signature = if RUBY_VERSION =~ /^1\.8/ 'Any{2}' else 'Any x, Any y' end expect do func.call({}, 10) end.to raise_error(ArgumentError, "function 'min' called with mis-matched arguments expected: min(#{signature}) - arg count {2} actual: min(Integer) - arg count {1}") end it 'an error is raised if called with too many arguments' do f = create_min_function_class() # TODO: Bogus parameters, not yet used func = f.new(:closure_scope, :loader) expect(func.is_a?(Puppet::Functions::Function)).to be_true signature = if RUBY_VERSION =~ /^1\.8/ 'Any{2}' else 'Any x, Any y' end expect do func.call({}, 10, 10, 10) end.to raise_error(ArgumentError, Regexp.new(Regexp.escape( "function 'min' called with mis-matched arguments expected: min(#{signature}) - arg count {2} actual: min(Integer, Integer, Integer) - arg count {3}"))) end it 'an error is raised if simple function-name and method are not matched' do expect do f = create_badly_named_method_function_class() end.to raise_error(ArgumentError, /Function Creation Error, cannot create a default dispatcher for function 'mix', no method with this name found/) end it 'the implementation separates dispatchers for different functions' do # this tests that meta programming / construction puts class attributes in the correct class f1 = create_min_function_class() f2 = create_max_function_class() d1 = f1.dispatcher d2 = f2.dispatcher expect(d1).to_not eql(d2) expect(d1.dispatchers[0]).to_not eql(d2.dispatchers[0]) end context 'when using regular dispatch' do it 'a function can be created using dispatch and called' do f = create_min_function_class_using_dispatch() func = f.new(:closure_scope, :loader) expect(func.call({}, 3,4)).to eql(3) end it 'an error is raised with reference to given parameter names when called with mis-matched arguments' do f = create_min_function_class_using_dispatch() # TODO: Bogus parameters, not yet used func = f.new(:closure_scope, :loader) expect(func.is_a?(Puppet::Functions::Function)).to be_true expect do func.call({}, 10, 10, 10) end.to raise_error(ArgumentError, Regexp.new(Regexp.escape( "function 'min' called with mis-matched arguments expected: min(Numeric a, Numeric b) - arg count {2} actual: min(Integer, Integer, Integer) - arg count {3}"))) end it 'an error includes optional indicators and count for last element' do f = create_function_with_optionals_and_varargs() # TODO: Bogus parameters, not yet used func = f.new(:closure_scope, :loader) expect(func.is_a?(Puppet::Functions::Function)).to be_true signature = if RUBY_VERSION =~ /^1\.8/ 'Any{2,}' else 'Any x, Any y, Any a?, Any b?, Any c{0,}' end expect do func.call({}, 10) end.to raise_error(ArgumentError, "function 'min' called with mis-matched arguments expected: min(#{signature}) - arg count {2,} actual: min(Integer) - arg count {1}") end it 'an error includes optional indicators and count for last element when defined via dispatch' do f = create_function_with_optionals_and_varargs_via_dispatch() # TODO: Bogus parameters, not yet used func = f.new(:closure_scope, :loader) expect(func.is_a?(Puppet::Functions::Function)).to be_true expect do func.call({}, 10) end.to raise_error(ArgumentError, "function 'min' called with mis-matched arguments expected: min(Numeric x, Numeric y, Numeric a?, Numeric b?, Numeric c{0,}) - arg count {2,} actual: min(Integer) - arg count {1}") end it 'a function can be created using dispatch and called' do f = create_min_function_class_disptaching_to_two_methods() func = f.new(:closure_scope, :loader) expect(func.call({}, 3,4)).to eql(3) expect(func.call({}, 'Apple', 'Banana')).to eql('Apple') end it 'an error is raised with reference to multiple methods when called with mis-matched arguments' do f = create_min_function_class_disptaching_to_two_methods() # TODO: Bogus parameters, not yet used func = f.new(:closure_scope, :loader) expect(func.is_a?(Puppet::Functions::Function)).to be_true expect do func.call({}, 10, 10, 10) end.to raise_error(ArgumentError, "function 'min' called with mis-matched arguments expected one of: min(Numeric a, Numeric b) - arg count {2} min(String s1, String s2) - arg count {2} actual: min(Integer, Integer, Integer) - arg count {3}") end context 'can use injection' do before :all do injector = Puppet::Pops::Binder::Injector.create('test') do bind.name('a_string').to('evoe') bind.name('an_int').to(42) end Puppet.push_context({:injector => injector}, "injector for testing function API") end after :all do Puppet.pop_context() end it 'attributes can be injected' do f1 = create_function_with_class_injection() f = f1.new(:closure_scope, :loader) expect(f.test_attr2()).to eql("evoe") expect(f.serial().produce(nil)).to eql(42) expect(f.test_attr().class.name).to eql("FunctionAPISpecModule::TestDuck") end it 'parameters can be injected and woven with regular dispatch' do f1 = create_function_with_param_injection_regular() f = f1.new(:closure_scope, :loader) expect(f.call(nil, 10, 20)).to eql("evoe! 10, and 20 < 42 = true") expect(f.call(nil, 50, 20)).to eql("evoe! 50, and 20 < 42 = false") end end context 'when requesting a type' do it 'responds with a Callable for a single signature' do tf = Puppet::Pops::Types::TypeFactory fc = create_min_function_class_using_dispatch() t = fc.dispatcher.to_type expect(t.class).to be(Puppet::Pops::Types::PCallableType) expect(t.param_types.class).to be(Puppet::Pops::Types::PTupleType) expect(t.param_types.types).to eql([tf.numeric(), tf.numeric()]) expect(t.block_type).to be_nil end it 'responds with a Variant[Callable...] for multiple signatures' do tf = Puppet::Pops::Types::TypeFactory fc = create_min_function_class_disptaching_to_two_methods() t = fc.dispatcher.to_type expect(t.class).to be(Puppet::Pops::Types::PVariantType) expect(t.types.size).to eql(2) t1 = t.types[0] expect(t1.param_types.class).to be(Puppet::Pops::Types::PTupleType) expect(t1.param_types.types).to eql([tf.numeric(), tf.numeric()]) expect(t1.block_type).to be_nil t2 = t.types[1] expect(t2.param_types.class).to be(Puppet::Pops::Types::PTupleType) expect(t2.param_types.types).to eql([tf.string(), tf.string()]) expect(t2.block_type).to be_nil end end context 'supports lambdas' do it 'such that, a required block can be defined and given as an argument' do # use a Function as callable the_callable = create_min_function_class().new(:closure_scope, :loader) the_function = create_function_with_required_block_all_defaults().new(:closure_scope, :loader) result = the_function.call({}, 10, the_callable) expect(result).to be(the_callable) end it 'such that, a missing required block when called raises an error' do # use a Function as callable the_function = create_function_with_required_block_all_defaults().new(:closure_scope, :loader) expect do the_function.call({}, 10) end.to raise_error(ArgumentError, "function 'test' called with mis-matched arguments expected: test(Integer x, Callable block) - arg count {2} actual: test(Integer) - arg count {1}") end it 'such that, an optional block can be defined and given as an argument' do # use a Function as callable the_callable = create_min_function_class().new(:closure_scope, :loader) the_function = create_function_with_optional_block_all_defaults().new(:closure_scope, :loader) result = the_function.call({}, 10, the_callable) expect(result).to be(the_callable) end it 'such that, an optional block can be omitted when called and gets the value nil' do # use a Function as callable the_function = create_function_with_optional_block_all_defaults().new(:closure_scope, :loader) expect(the_function.call({}, 10)).to be_nil end end context 'provides signature information' do it 'about capture rest (varargs)' do fc = create_function_with_optionals_and_varargs signatures = fc.signatures expect(signatures.size).to eql(1) signature = signatures[0] expect(signature.last_captures_rest?).to be_true end it 'about optional and required parameters' do fc = create_function_with_optionals_and_varargs signature = fc.signatures[0] expect(signature.args_range).to eql( [2, Puppet::Pops::Types::INFINITY ] ) expect(signature.infinity?(signature.args_range[1])).to be_true end it 'about block not being allowed' do fc = create_function_with_optionals_and_varargs signature = fc.signatures[0] expect(signature.block_range).to eql( [ 0, 0 ] ) expect(signature.block_type).to be_nil end it 'about required block' do fc = create_function_with_required_block_all_defaults signature = fc.signatures[0] expect(signature.block_range).to eql( [ 1, 1 ] ) expect(signature.block_type).to_not be_nil end it 'about optional block' do fc = create_function_with_optional_block_all_defaults signature = fc.signatures[0] expect(signature.block_range).to eql( [ 0, 1 ] ) expect(signature.block_type).to_not be_nil end it 'about the type' do fc = create_function_with_optional_block_all_defaults signature = fc.signatures[0] expect(signature.type.class).to be(Puppet::Pops::Types::PCallableType) end # conditional on Ruby 1.8.7 which does not do parameter introspection if Method.method_defined?(:parameters) it 'about parameter names obtained from ruby introspection' do fc = create_min_function_class signature = fc.signatures[0] expect(signature.parameter_names).to eql(['x', 'y']) end end it 'about parameter names specified with dispatch' do fc = create_min_function_class_using_dispatch signature = fc.signatures[0] expect(signature.parameter_names).to eql(['a', 'b']) end it 'about block_name when it is *not* given in the definition' do # neither type, nor name fc = create_function_with_required_block_all_defaults signature = fc.signatures[0] expect(signature.block_name).to eql('block') # no name given, only type fc = create_function_with_required_block_given_type signature = fc.signatures[0] expect(signature.block_name).to eql('block') end it 'about block_name when it *is* given in the definition' do # neither type, nor name fc = create_function_with_required_block_default_type signature = fc.signatures[0] expect(signature.block_name).to eql('the_block') # no name given, only type fc = create_function_with_required_block_fully_specified signature = fc.signatures[0] expect(signature.block_name).to eql('the_block') end end context 'supports calling other functions' do before(:all) do Puppet.push_context( {:loaders => Puppet::Pops::Loaders.new(Puppet::Node::Environment.create(:testing, []))}) end after(:all) do Puppet.pop_context() end it 'such that, other functions are callable by name' do fc = Puppet::Functions.create_function(:test) do def test() # Call a function available in the puppet system call_function('assert_type', 'Integer', 10) end end # initiate the function the same way the loader initiates it f = fc.new(:closure_scope, Puppet.lookup(:loaders).puppet_system_loader) expect(f.call({})).to eql(10) end it 'such that, calling a non existing function raises an error' do fc = Puppet::Functions.create_function(:test) do def test() # Call a function not available in the puppet system call_function('no_such_function', 'Integer', 'hello') end end # initiate the function the same way the loader initiates it f = fc.new(:closure_scope, Puppet.lookup(:loaders).puppet_system_loader) expect{f.call({})}.to raise_error(ArgumentError, "Function test(): cannot call function 'no_such_function' - not found") end end context 'supports calling ruby functions with lambda from puppet' do before(:all) do Puppet.push_context( {:loaders => Puppet::Pops::Loaders.new(Puppet::Node::Environment.create(:testing, []))}) end after(:all) do Puppet.pop_context() end before(:each) do Puppet[:strict_variables] = true - - # These must be set since the is 3x logic that triggers on these even if the tests are explicit - # about selection of parser and evaluator - # - Puppet[:parser] = 'future' - # Plugins Configuration cannot be loaded until the correct parser has been set (injector is turned off otherwise) - require 'puppet/plugins' end let(:parser) { Puppet::Pops::Parser::EvaluatingParser.new } let(:node) { 'node.example.com' } let(:scope) { s = create_test_scope_for_node(node); s } it 'function with required block can be called' do # construct ruby function to call fc = Puppet::Functions.create_function('testing::test') do dispatch :test do param 'Integer', 'x' # block called 'the_block', and using "all_callables" required_block_param #(all_callables(), 'the_block') end def test(x, block) # call the block with x block.call(closure_scope, x) end end # add the function to the loader (as if it had been loaded from somewhere) the_loader = loader() f = fc.new({}, the_loader) loader.add_function('testing::test', f) # evaluate a puppet call source = "testing::test(10) |$x| { $x+1 }" program = parser.parse_string(source, __FILE__) Puppet::Pops::Adapters::LoaderAdapter.adapt(program.model).loader = the_loader expect(parser.evaluate(scope, program)).to eql(11) end end end def create_noargs_function_class f = Puppet::Functions.create_function('test') do def test() 10 end end end def create_min_function_class f = Puppet::Functions.create_function('min') do def min(x,y) x <= y ? x : y end end end def create_max_function_class f = Puppet::Functions.create_function('max') do def max(x,y) x >= y ? x : y end end end def create_badly_named_method_function_class f = Puppet::Functions.create_function('mix') do def mix_up(x,y) x <= y ? x : y end end end def create_min_function_class_using_dispatch f = Puppet::Functions.create_function('min') do dispatch :min do param 'Numeric', 'a' param 'Numeric', 'b' end def min(x,y) x <= y ? x : y end end end def create_min_function_class_disptaching_to_two_methods f = Puppet::Functions.create_function('min') do dispatch :min do param 'Numeric', 'a' param 'Numeric', 'b' end dispatch :min_s do param 'String', 's1' param 'String', 's2' end def min(x,y) x <= y ? x : y end def min_s(x,y) cmp = (x.downcase <=> y.downcase) cmp <= 0 ? x : y end end end def create_function_with_optionals_and_varargs f = Puppet::Functions.create_function('min') do def min(x,y,a=1, b=1, *c) x <= y ? x : y end end end def create_function_with_optionals_and_varargs_via_dispatch f = Puppet::Functions.create_function('min') do dispatch :min do param 'Numeric', 'x' param 'Numeric', 'y' param 'Numeric', 'a' param 'Numeric', 'b' param 'Numeric', 'c' arg_count 2, :default end def min(x,y,a=1, b=1, *c) x <= y ? x : y end end end def create_function_with_class_injection f = Puppet::Functions.create_function('test', Puppet::Functions::InternalFunction) do attr_injected Puppet::Pops::Types::TypeFactory.type_of(FunctionAPISpecModule::TestDuck), :test_attr attr_injected Puppet::Pops::Types::TypeFactory.string(), :test_attr2, "a_string" attr_injected_producer Puppet::Pops::Types::TypeFactory.integer(), :serial, "an_int" def test(x,y,a=1, b=1, *c) x <= y ? x : y end end end def create_function_with_param_injection_regular f = Puppet::Functions.create_function('test', Puppet::Functions::InternalFunction) do attr_injected Puppet::Pops::Types::TypeFactory.type_of(FunctionAPISpecModule::TestDuck), :test_attr attr_injected Puppet::Pops::Types::TypeFactory.string(), :test_attr2, "a_string" attr_injected_producer Puppet::Pops::Types::TypeFactory.integer(), :serial, "an_int" dispatch :test do injected_param Puppet::Pops::Types::TypeFactory.string, 'x', 'a_string' injected_producer_param Puppet::Pops::Types::TypeFactory.integer, 'y', 'an_int' param 'Scalar', 'a' param 'Scalar', 'b' end def test(x,y,a,b) y_produced = y.produce(nil) "#{x}! #{a}, and #{b} < #{y_produced} = #{ !!(a < y_produced && b < y_produced)}" end end end def create_function_with_required_block_all_defaults f = Puppet::Functions.create_function('test') do dispatch :test do param 'Integer', 'x' # use defaults, any callable, name is 'block' required_block_param end def test(x, block) # returns the block to make it easy to test what it got when called block end end end def create_function_with_required_block_default_type f = Puppet::Functions.create_function('test') do dispatch :test do param 'Integer', 'x' # use defaults, any callable, name is 'block' required_block_param 'the_block' end def test(x, block) # returns the block to make it easy to test what it got when called block end end end def create_function_with_required_block_given_type f = Puppet::Functions.create_function('test') do dispatch :test do param 'Integer', 'x' required_block_param end def test(x, block) # returns the block to make it easy to test what it got when called block end end end def create_function_with_required_block_fully_specified f = Puppet::Functions.create_function('test') do dispatch :test do param 'Integer', 'x' # use defaults, any callable, name is 'block' required_block_param('Callable', 'the_block') end def test(x, block) # returns the block to make it easy to test what it got when called block end end end def create_function_with_optional_block_all_defaults f = Puppet::Functions.create_function('test') do dispatch :test do param 'Integer', 'x' # use defaults, any callable, name is 'block' optional_block_param end def test(x, block=nil) # returns the block to make it easy to test what it got when called # a default of nil must be used or the call will fail with a missing parameter block end end end end diff --git a/spec/unit/node/environment_spec.rb b/spec/unit/node/environment_spec.rb index d4e7e0fc4..d2d27ad58 100755 --- a/spec/unit/node/environment_spec.rb +++ b/spec/unit/node/environment_spec.rb @@ -1,604 +1,590 @@ #! /usr/bin/env ruby require 'spec_helper' require 'tmpdir' require 'puppet/node/environment' require 'puppet/util/execution' require 'puppet_spec/modules' require 'puppet/parser/parser_factory' describe Puppet::Node::Environment do let(:env) { Puppet::Node::Environment.new("testing") } include PuppetSpec::Files after do Puppet::Node::Environment.clear end - shared_examples_for 'the environment' do + context 'the environment' do it "should convert an environment to string when converting to YAML" do env.to_yaml.should match(/--- testing/) end it "should use the filetimeout for the ttl for the module list" do Puppet::Node::Environment.attr_ttl(:modules).should == Integer(Puppet[:filetimeout]) end it "should use the default environment if no name is provided while initializing an environment" do Puppet[:environment] = "one" Puppet::Node::Environment.new.name.should == :one end it "should treat environment instances as singletons" do Puppet::Node::Environment.new("one").should equal(Puppet::Node::Environment.new("one")) end it "should treat an environment specified as names or strings as equivalent" do Puppet::Node::Environment.new(:one).should equal(Puppet::Node::Environment.new("one")) end it "should return its name when converted to a string" do Puppet::Node::Environment.new(:one).to_s.should == "one" end it "should just return any provided environment if an environment is provided as the name" do one = Puppet::Node::Environment.new(:one) Puppet::Node::Environment.new(one).should equal(one) end describe "equality" do it "works as a hash key" do base = Puppet::Node::Environment.create(:first, ["modules"], "manifests") same = Puppet::Node::Environment.create(:first, ["modules"], "manifests") different = Puppet::Node::Environment.create(:first, ["different"], "manifests") hash = {} hash[base] = "base env" hash[same] = "same env" hash[different] = "different env" expect(hash[base]).to eq("same env") expect(hash[different]).to eq("different env") expect(hash).to have(2).item end it "is equal when name, modules, and manifests are the same" do base = Puppet::Node::Environment.create(:base, ["modules"], "manifests") different_name = Puppet::Node::Environment.create(:different, base.full_modulepath, base.manifest) expect(base).to_not eq("not an environment") expect(base).to eq(base) expect(base.hash).to eq(base.hash) expect(base.override_with(:modulepath => ["different"])).to_not eq(base) expect(base.override_with(:modulepath => ["different"]).hash).to_not eq(base.hash) expect(base.override_with(:manifest => "different")).to_not eq(base) expect(base.override_with(:manifest => "different").hash).to_not eq(base.hash) expect(different_name).to_not eq(base) expect(different_name.hash).to_not eq(base.hash) end end describe "overriding an existing environment" do let(:original_path) { [tmpdir('original')] } let(:new_path) { [tmpdir('new')] } let(:environment) { Puppet::Node::Environment.create(:overridden, original_path, 'orig.pp', '/config/script') } it "overrides modulepath" do overridden = environment.override_with(:modulepath => new_path) expect(overridden).to_not be_equal(environment) expect(overridden.name).to eq(:overridden) expect(overridden.manifest).to eq(File.expand_path('orig.pp')) expect(overridden.modulepath).to eq(new_path) expect(overridden.config_version).to eq('/config/script') end it "overrides manifest" do overridden = environment.override_with(:manifest => 'new.pp') expect(overridden).to_not be_equal(environment) expect(overridden.name).to eq(:overridden) expect(overridden.manifest).to eq(File.expand_path('new.pp')) expect(overridden.modulepath).to eq(original_path) expect(overridden.config_version).to eq('/config/script') end it "overrides config_version" do overridden = environment.override_with(:config_version => '/new/script') expect(overridden).to_not be_equal(environment) expect(overridden.name).to eq(:overridden) expect(overridden.manifest).to eq(File.expand_path('orig.pp')) expect(overridden.modulepath).to eq(original_path) expect(overridden.config_version).to eq('/new/script') end end describe "watching a file" do let(:filename) { "filename" } it "accepts a File" do file = tmpfile(filename) env.known_resource_types.expects(:watch_file).with(file.to_s) env.watch_file(file) end it "accepts a String" do env.known_resource_types.expects(:watch_file).with(filename) env.watch_file(filename) end end describe "when managing known resource types" do before do @collection = Puppet::Resource::TypeCollection.new(env) env.stubs(:perform_initial_import).returns(Puppet::Parser::AST::Hostclass.new('')) end it "should create a resource type collection if none exists" do Puppet::Resource::TypeCollection.expects(:new).with(env).returns @collection env.known_resource_types.should equal(@collection) end it "should reuse any existing resource type collection" do env.known_resource_types.should equal(env.known_resource_types) end it "should perform the initial import when creating a new collection" do env.expects(:perform_initial_import).returns(Puppet::Parser::AST::Hostclass.new('')) env.known_resource_types end it "should return the same collection even if stale if it's the same thread" do Puppet::Resource::TypeCollection.stubs(:new).returns @collection env.known_resource_types.stubs(:stale?).returns true env.known_resource_types.should equal(@collection) end it "should generate a new TypeCollection if the current one requires reparsing" do old_type_collection = env.known_resource_types old_type_collection.stubs(:require_reparse?).returns true env.check_for_reparse new_type_collection = env.known_resource_types new_type_collection.should be_a Puppet::Resource::TypeCollection new_type_collection.should_not equal(old_type_collection) end end it "should validate the modulepath directories" do real_file = tmpdir('moduledir') path = %W[/one /two #{real_file}].join(File::PATH_SEPARATOR) Puppet[:modulepath] = path env.modulepath.should == [real_file] end it "should prefix the value of the 'PUPPETLIB' environment variable to the module path if present" do first_puppetlib = tmpdir('puppetlib1') second_puppetlib = tmpdir('puppetlib2') first_moduledir = tmpdir('moduledir1') second_moduledir = tmpdir('moduledir2') Puppet::Util.withenv("PUPPETLIB" => [first_puppetlib, second_puppetlib].join(File::PATH_SEPARATOR)) do Puppet[:modulepath] = [first_moduledir, second_moduledir].join(File::PATH_SEPARATOR) env.modulepath.should == [first_puppetlib, second_puppetlib, first_moduledir, second_moduledir] end end it "does not register validation_errors when not using directory environments" do expect(Puppet::Node::Environment.create(:directory, [], '/some/non/default/manifest.pp').validation_errors).to be_empty end describe "when operating in the context of directory environments" do before(:each) do Puppet[:environmentpath] = "$confdir/environments" Puppet[:default_manifest] = "/default/manifests/site.pp" end it "has no validation errors when disable_per_environment_manifest is false" do expect(Puppet::Node::Environment.create(:directory, [], '/some/non/default/manifest.pp').validation_errors).to be_empty end context "when disable_per_environment_manifest is true" do let(:config) { mock('config') } let(:global_modulepath) { ["/global/modulepath"] } let(:envconf) { Puppet::Settings::EnvironmentConf.new("/some/direnv", config, global_modulepath) } before(:each) do Puppet[:disable_per_environment_manifest] = true end def assert_manifest_conflict(expectation, envconf_manifest_value) config.expects(:setting).with(:manifest).returns( mock('setting', :value => envconf_manifest_value) ) environment = Puppet::Node::Environment.create(:directory, [], '/default/manifests/site.pp') loader = Puppet::Environments::Static.new(environment) loader.stubs(:get_conf).returns(envconf) Puppet.override(:environments => loader) do if expectation expect(environment.validation_errors).to have_matching_element(/The 'disable_per_environment_manifest' setting is true.*and the.*environment.*conflicts/) else expect(environment.validation_errors).to be_empty end end end it "has conflicting_manifest_settings when environment.conf manifest was set" do assert_manifest_conflict(true, '/some/envconf/manifest/site.pp') end it "does not have conflicting_manifest_settings when environment.conf manifest is empty" do assert_manifest_conflict(false, '') end it "does not have conflicting_manifest_settings when environment.conf manifest is nil" do assert_manifest_conflict(false, nil) end it "does not have conflicting_manifest_settings when environment.conf manifest is an exact, uninterpolated match of default_manifest" do assert_manifest_conflict(false, '/default/manifests/site.pp') end end end describe "when modeling a specific environment" do it "should have a method for returning the environment name" do Puppet::Node::Environment.new("testing").name.should == :testing end it "should provide an array-like accessor method for returning any environment-specific setting" do env.should respond_to(:[]) end it "obtains its core values from the puppet settings instance as a legacy env" do Puppet.settings.parse_config(<<-CONF) [testing] manifest = /some/manifest modulepath = /some/modulepath config_version = /some/script CONF env = Puppet::Node::Environment.new("testing") expect(env.full_modulepath).to eq([File.expand_path('/some/modulepath')]) expect(env.manifest).to eq(File.expand_path('/some/manifest')) expect(env.config_version).to eq('/some/script') end it "should ask the Puppet settings instance for the setting qualified with the environment name" do Puppet.settings.parse_config(<<-CONF) [testing] server = myval CONF env[:server].should == "myval" end it "should be able to return an individual module that exists in its module path" do env.stubs(:modules).returns [Puppet::Module.new('one', "/one", mock("env"))] mod = env.module('one') mod.should be_a(Puppet::Module) mod.name.should == 'one' end it "should not return a module if the module doesn't exist" do env.stubs(:modules).returns [Puppet::Module.new('one', "/one", mock("env"))] env.module('two').should be_nil end it "should return nil if asked for a module that does not exist in its path" do modpath = tmpdir('modpath') env = Puppet::Node::Environment.create(:testing, [modpath]) env.module("one").should be_nil end describe "module data" do before do dir = tmpdir("deep_path") @first = File.join(dir, "first") @second = File.join(dir, "second") Puppet[:modulepath] = "#{@first}#{File::PATH_SEPARATOR}#{@second}" FileUtils.mkdir_p(@first) FileUtils.mkdir_p(@second) end describe "#modules_by_path" do it "should return an empty list if there are no modules" do env.modules_by_path.should == { @first => [], @second => [] } end it "should include modules even if they exist in multiple dirs in the modulepath" do modpath1 = File.join(@first, "foo") FileUtils.mkdir_p(modpath1) modpath2 = File.join(@second, "foo") FileUtils.mkdir_p(modpath2) env.modules_by_path.should == { @first => [Puppet::Module.new('foo', modpath1, env)], @second => [Puppet::Module.new('foo', modpath2, env)] } end it "should ignore modules with invalid names" do PuppetSpec::Modules.generate_files('foo', @first) PuppetSpec::Modules.generate_files('foo2', @first) PuppetSpec::Modules.generate_files('foo-bar', @first) PuppetSpec::Modules.generate_files('foo_bar', @first) PuppetSpec::Modules.generate_files('foo=bar', @first) PuppetSpec::Modules.generate_files('foo bar', @first) PuppetSpec::Modules.generate_files('foo.bar', @first) PuppetSpec::Modules.generate_files('-foo', @first) PuppetSpec::Modules.generate_files('foo-', @first) PuppetSpec::Modules.generate_files('foo--bar', @first) env.modules_by_path[@first].collect{|mod| mod.name}.sort.should == %w{foo foo-bar foo2 foo_bar} end end describe "#module_requirements" do it "should return a list of what modules depend on other modules" do PuppetSpec::Modules.create( 'foo', @first, :metadata => { :author => 'puppetlabs', :dependencies => [{ 'name' => 'puppetlabs/bar', "version_requirement" => ">= 1.0.0" }] } ) PuppetSpec::Modules.create( 'bar', @second, :metadata => { :author => 'puppetlabs', :dependencies => [{ 'name' => 'puppetlabs/foo', "version_requirement" => "<= 2.0.0" }] } ) PuppetSpec::Modules.create( 'baz', @first, :metadata => { :author => 'puppetlabs', :dependencies => [{ 'name' => 'puppetlabs-bar', "version_requirement" => "3.0.0" }] } ) PuppetSpec::Modules.create( 'alpha', @first, :metadata => { :author => 'puppetlabs', :dependencies => [{ 'name' => 'puppetlabs/bar', "version_requirement" => "~3.0.0" }] } ) env.module_requirements.should == { 'puppetlabs/alpha' => [], 'puppetlabs/foo' => [ { "name" => "puppetlabs/bar", "version" => "9.9.9", "version_requirement" => "<= 2.0.0" } ], 'puppetlabs/bar' => [ { "name" => "puppetlabs/alpha", "version" => "9.9.9", "version_requirement" => "~3.0.0" }, { "name" => "puppetlabs/baz", "version" => "9.9.9", "version_requirement" => "3.0.0" }, { "name" => "puppetlabs/foo", "version" => "9.9.9", "version_requirement" => ">= 1.0.0" } ], 'puppetlabs/baz' => [] } end end describe ".module_by_forge_name" do it "should find modules by forge_name" do mod = PuppetSpec::Modules.create( 'baz', @first, :metadata => {:author => 'puppetlabs'}, :environment => env ) env.module_by_forge_name('puppetlabs/baz').should == mod end it "should not find modules with same name by the wrong author" do mod = PuppetSpec::Modules.create( 'baz', @first, :metadata => {:author => 'sneakylabs'}, :environment => env ) env.module_by_forge_name('puppetlabs/baz').should == nil end it "should return nil when the module can't be found" do env.module_by_forge_name('ima/nothere').should be_nil end end describe ".modules" do it "should return an empty list if there are no modules" do env.modules.should == [] end it "should return a module named for every directory in each module path" do %w{foo bar}.each do |mod_name| PuppetSpec::Modules.generate_files(mod_name, @first) end %w{bee baz}.each do |mod_name| PuppetSpec::Modules.generate_files(mod_name, @second) end env.modules.collect{|mod| mod.name}.sort.should == %w{foo bar bee baz}.sort end it "should remove duplicates" do PuppetSpec::Modules.generate_files('foo', @first) PuppetSpec::Modules.generate_files('foo', @second) env.modules.collect{|mod| mod.name}.sort.should == %w{foo} end it "should ignore modules with invalid names" do PuppetSpec::Modules.generate_files('foo', @first) PuppetSpec::Modules.generate_files('foo2', @first) PuppetSpec::Modules.generate_files('foo-bar', @first) PuppetSpec::Modules.generate_files('foo_bar', @first) PuppetSpec::Modules.generate_files('foo=bar', @first) PuppetSpec::Modules.generate_files('foo bar', @first) env.modules.collect{|mod| mod.name}.sort.should == %w{foo foo-bar foo2 foo_bar} end it "should create modules with the correct environment" do PuppetSpec::Modules.generate_files('foo', @first) env.modules.each {|mod| mod.environment.should == env } end it "should log an exception if a module contains invalid metadata" do PuppetSpec::Modules.generate_files( 'foo', @first, :metadata => { :author => 'puppetlabs' # missing source, version, etc } ) Puppet.expects(:log_exception).with(is_a(Puppet::Module::MissingMetadata)) env.modules end end end end describe "when performing initial import" do def parser_and_environment(name) env = Puppet::Node::Environment.new(name) parser = Puppet::Parser::ParserFactory.parser(env) Puppet::Parser::ParserFactory.stubs(:parser).returns(parser) [parser, env] end it "should set the parser's string to the 'code' setting and parse if code is available" do Puppet[:code] = "my code" parser, env = parser_and_environment('testing') parser.expects(:string=).with "my code" parser.expects(:parse) env.instance_eval { perform_initial_import } end it "should set the parser's file to the 'manifest' setting and parse if no code is available and the manifest is available" do filename = tmpfile('myfile') Puppet[:manifest] = filename parser, env = parser_and_environment('testing') parser.expects(:file=).with filename parser.expects(:parse) env.instance_eval { perform_initial_import } end it "should pass the manifest file to the parser even if it does not exist on disk" do filename = tmpfile('myfile') Puppet[:code] = "" Puppet[:manifest] = filename parser, env = parser_and_environment('testing') parser.expects(:file=).with(filename).once parser.expects(:parse).once env.instance_eval { perform_initial_import } end it "should fail helpfully if there is an error importing" do Puppet::FileSystem.stubs(:exist?).returns true parser, env = parser_and_environment('testing') parser.expects(:file=).once parser.expects(:parse).raises ArgumentError expect do env.known_resource_types end.to raise_error(Puppet::Error) end it "should not do anything if the ignore_import settings is set" do Puppet[:ignoreimport] = true parser, env = parser_and_environment('testing') parser.expects(:string=).never parser.expects(:file=).never parser.expects(:parse).never env.instance_eval { perform_initial_import } end it "should mark the type collection as needing a reparse when there is an error parsing" do parser, env = parser_and_environment('testing') parser.expects(:parse).raises Puppet::ParseError.new("Syntax error at ...") expect do env.known_resource_types end.to raise_error(Puppet::Error, /Syntax error at .../) env.known_resource_types.require_reparse?.should be_true end end end - describe 'with classic parser' do - before :each do - Puppet[:parser] = 'current' - end - it_behaves_like 'the environment' - end - - describe 'with future parser' do - before :each do - Puppet[:parser] = 'future' - end - it_behaves_like 'the environment' - end - describe '#current' do it 'should return the current context' do env = Puppet::Node::Environment.new(:test) Puppet::Context.any_instance.expects(:lookup).with(:current_environment).returns(env) Puppet.expects(:deprecation_warning).once Puppet::Node::Environment.current.should equal(env) end end end diff --git a/spec/unit/parser/functions/contain_spec.rb b/spec/unit/parser/functions/contain_spec.rb index 0f338c7e0..af4017745 100644 --- a/spec/unit/parser/functions/contain_spec.rb +++ b/spec/unit/parser/functions/contain_spec.rb @@ -1,236 +1,233 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet_spec/compiler' require 'puppet/parser/functions' require 'matchers/containment_matchers' require 'matchers/resource' require 'matchers/include_in_order' require 'unit/parser/functions/shared' describe 'The "contain" function' do include PuppetSpec::Compiler include ContainmentMatchers include Matchers::Resource + before(:each) do + compiler = Puppet::Parser::Compiler.new(Puppet::Node.new("foo")) + @scope = Puppet::Parser::Scope.new(compiler) + end + it "includes the class" do catalog = compile_to_catalog(<<-MANIFEST) class contained { notify { "contained": } } class container { contain contained } include container MANIFEST expect(catalog.classes).to include("contained") end it "includes the class when using a fully qualified anchored name" do catalog = compile_to_catalog(<<-MANIFEST) class contained { notify { "contained": } } class container { contain ::contained } include container MANIFEST expect(catalog.classes).to include("contained") end it "ensures that the edge is with the correct class" do catalog = compile_to_catalog(<<-MANIFEST) class outer { class named { } contain outer::named } class named { } include named include outer MANIFEST expect(catalog).to have_resource("Class[Named]") expect(catalog).to have_resource("Class[Outer]") expect(catalog).to have_resource("Class[Outer::Named]") expect(catalog).to contain_class("outer::named").in("outer") end it "makes the class contained in the current class" do catalog = compile_to_catalog(<<-MANIFEST) class contained { notify { "contained": } } class container { contain contained } include container MANIFEST expect(catalog).to contain_class("contained").in("container") end it "can contain multiple classes" do catalog = compile_to_catalog(<<-MANIFEST) class a { notify { "a": } } class b { notify { "b": } } class container { contain a, b } include container MANIFEST expect(catalog).to contain_class("a").in("container") expect(catalog).to contain_class("b").in("container") end context "when containing a class in multiple classes" do it "creates a catalog with all containment edges" do catalog = compile_to_catalog(<<-MANIFEST) class contained { notify { "contained": } } class container { contain contained } class another { contain contained } include container include another MANIFEST expect(catalog).to contain_class("contained").in("container") expect(catalog).to contain_class("contained").in("another") end it "and there are no dependencies applies successfully" do manifest = <<-MANIFEST class contained { notify { "contained": } } class container { contain contained } class another { contain contained } include container include another MANIFEST expect { apply_compiled_manifest(manifest) }.not_to raise_error end it "and there are explicit dependencies on the containing class causes a dependency cycle" do manifest = <<-MANIFEST class contained { notify { "contained": } } class container { contain contained } class another { contain contained } include container include another Class["container"] -> Class["another"] MANIFEST expect { apply_compiled_manifest(manifest) }.to raise_error( Puppet::Error, /Found 1 dependency cycle/ ) end end it "does not create duplicate edges" do catalog = compile_to_catalog(<<-MANIFEST) class contained { notify { "contained": } } class container { contain contained contain contained } include container MANIFEST contained = catalog.resource("Class", "contained") container = catalog.resource("Class", "container") expect(catalog.edges_between(container, contained)).to have(1).item end context "when a containing class has a dependency order" do it "the contained class is applied in that order" do catalog = compile_to_relationship_graph(<<-MANIFEST) class contained { notify { "contained": } } class container { contain contained } class first { notify { "first": } } class last { notify { "last": } } include container, first, last Class["first"] -> Class["container"] -> Class["last"] MANIFEST expect(order_resources_traversed_in(catalog)).to include_in_order( "Notify[first]", "Notify[contained]", "Notify[last]" ) end end - describe "When the future parser is in use" do - require 'puppet/pops' - before(:each) do - Puppet[:parser] = 'future' - compiler = Puppet::Parser::Compiler.new(Puppet::Node.new("foo")) - @scope = Puppet::Parser::Scope.new(compiler) - end + it_should_behave_like 'all functions transforming relative to absolute names', :function_contain + it_should_behave_like 'an inclusion function, regardless of the type of class reference,', :contain - it_should_behave_like 'all functions transforming relative to absolute names', :function_contain - it_should_behave_like 'an inclusion function, regardless of the type of class reference,', :contain - end end diff --git a/spec/unit/parser/functions/defined_spec.rb b/spec/unit/parser/functions/defined_spec.rb index edbf29863..91590254f 100755 --- a/spec/unit/parser/functions/defined_spec.rb +++ b/spec/unit/parser/functions/defined_spec.rb @@ -1,114 +1,106 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/pops' describe "the 'defined' function" do - before :all do - Puppet::Parser::Functions.autoloader.loadall - end before :each do @compiler = Puppet::Parser::Compiler.new(Puppet::Node.new("foo")) @scope = Puppet::Parser::Scope.new(@compiler) end it "exists" do expect(Puppet::Parser::Functions.function("defined")).to be_eql("function_defined") end it "is true when the name is defined as a class" do @scope.known_resource_types.add Puppet::Resource::Type.new(:hostclass, "yayness") expect(@scope.function_defined(["yayness"])).to be_true end it "is true when the name is defined as a definition" do @scope.known_resource_types.add Puppet::Resource::Type.new(:definition, "yayness") expect(@scope.function_defined(["yayness"])).to be_true end it "is true when the name is defined as a builtin type" do expect(@scope.function_defined(["file"])).to be_true end it "is true when any of the provided names are defined" do @scope.known_resource_types.add Puppet::Resource::Type.new(:definition, "yayness") expect(@scope.function_defined(["meh", "yayness", "booness"])).to be_true end it "is false when a single given name is not defined" do expect(@scope.function_defined(["meh"])).to be_false end it "is false when none of the names are defined" do expect(@scope.function_defined(["meh", "yayness", "booness"])).to be_false end it "is true when a resource reference is provided and the resource is in the catalog" do resource = Puppet::Resource.new("file", "/my/file") @compiler.add_resource(@scope, resource) expect(@scope.function_defined([resource])).to be_true end context "with string variable references" do it "is true when variable exists in scope" do @scope['x'] = 'something' expect(@scope.function_defined(['$x'])).to be_true end it "is true when at least one variable exists in scope" do @scope['x'] = 'something' expect(@scope.function_defined(['$y', '$x', '$z'])).to be_true end it "is false when variable does not exist in scope" do expect(@scope.function_defined(['$x'])).to be_false end end - context "with future parser" do - before(:each) do - Puppet[:parser] = 'future' - end - - it "is true when a future resource type reference is provided, and the resource is in the catalog" do - resource = Puppet::Resource.new("file", "/my/file") - @compiler.add_resource(@scope, resource) + it "is true when a future resource type reference is provided, and the resource is in the catalog" do + resource = Puppet::Resource.new("file", "/my/file") + @compiler.add_resource(@scope, resource) - resource_type = Puppet::Pops::Types::TypeFactory.resource('file', '/my/file') - expect(@scope.function_defined([resource_type])).to be_true - end + resource_type = Puppet::Pops::Types::TypeFactory.resource('file', '/my/file') + expect(@scope.function_defined([resource_type])).to be_true + end - it "raises an argument error if you ask if Resource is defined" do - resource_type = Puppet::Pops::Types::TypeFactory.resource - expect { @scope.function_defined([resource_type]) }.to raise_error(ArgumentError, /reference to all.*type/) - end + it "raises an argument error if you ask if Resource is defined" do + resource_type = Puppet::Pops::Types::TypeFactory.resource + expect { @scope.function_defined([resource_type]) }.to raise_error(ArgumentError, /reference to all.*type/) + end - it "is true if referencing a built in type" do - resource_type = Puppet::Pops::Types::TypeFactory.resource('file') - expect(@scope.function_defined([resource_type])).to be_true - end + it "is true if referencing a built in type" do + resource_type = Puppet::Pops::Types::TypeFactory.resource('file') + expect(@scope.function_defined([resource_type])).to be_true + end - it "is true if referencing a defined type" do - @scope.known_resource_types.add Puppet::Resource::Type.new(:definition, "yayness") - resource_type = Puppet::Pops::Types::TypeFactory.resource('yayness') - expect(@scope.function_defined([resource_type])).to be_true - end + it "is true if referencing a defined type" do + @scope.known_resource_types.add Puppet::Resource::Type.new(:definition, "yayness") + resource_type = Puppet::Pops::Types::TypeFactory.resource('yayness') + expect(@scope.function_defined([resource_type])).to be_true + end - it "is false if referencing an undefined type" do - resource_type = Puppet::Pops::Types::TypeFactory.resource('barbershops') - expect(@scope.function_defined([resource_type])).to be_false - end + it "is false if referencing an undefined type" do + resource_type = Puppet::Pops::Types::TypeFactory.resource('barbershops') + expect(@scope.function_defined([resource_type])).to be_false + end - it "is true when a future class reference type is provided" do - @scope.known_resource_types.add Puppet::Resource::Type.new(:hostclass, "cowabunga") + it "is true when a future class reference type is provided" do + @scope.known_resource_types.add Puppet::Resource::Type.new(:hostclass, "cowabunga") - class_type = Puppet::Pops::Types::TypeFactory.host_class("cowabunga") - expect(@scope.function_defined([class_type])).to be_true - end + class_type = Puppet::Pops::Types::TypeFactory.host_class("cowabunga") + expect(@scope.function_defined([class_type])).to be_true + end - it "raises an argument error if you ask if Class is defined" do - class_type = Puppet::Pops::Types::TypeFactory.host_class - expect { @scope.function_defined([class_type]) }.to raise_error(ArgumentError, /reference to all.*class/) - end + it "raises an argument error if you ask if Class is defined" do + class_type = Puppet::Pops::Types::TypeFactory.host_class + expect { @scope.function_defined([class_type]) }.to raise_error(ArgumentError, /reference to all.*class/) end + end diff --git a/spec/unit/parser/functions/include_spec.rb b/spec/unit/parser/functions/include_spec.rb index 26e114e6e..8e4548025 100755 --- a/spec/unit/parser/functions/include_spec.rb +++ b/spec/unit/parser/functions/include_spec.rb @@ -1,66 +1,55 @@ #! /usr/bin/env ruby require 'spec_helper' require 'unit/parser/functions/shared' +require 'puppet_spec/compiler' describe "the 'include' function" do - before :all do - Puppet::Parser::Functions.autoloader.loadall - end + include PuppetSpec::Compiler before :each do - Puppet[:parser] = 'future' @compiler = Puppet::Parser::Compiler.new(Puppet::Node.new("foo")) @scope = Puppet::Parser::Scope.new(@compiler) end it "should exist" do Puppet::Parser::Functions.function("include").should == "function_include" end it "should include a single class" do inc = "::foo" @compiler.expects(:evaluate_classes).with {|klasses,parser,lazy| klasses == [inc]}.returns([inc]) @scope.function_include(["foo"]) end it "should include multiple classes" do inc = ["::foo","::bar"] @compiler.expects(:evaluate_classes).with {|klasses,parser,lazy| klasses == inc}.returns(inc) @scope.function_include(["foo","bar"]) end it "should include multiple classes passed in an array" do inc = ["::foo","::bar"] @compiler.expects(:evaluate_classes).with {|klasses,parser,lazy| klasses == inc}.returns(inc) @scope.function_include([["foo","bar"]]) end it "should flatten nested arrays" do inc = ["::foo","::bar","::baz"] @compiler.expects(:evaluate_classes).with {|klasses,parser,lazy| klasses == inc}.returns(inc) @scope.function_include([["foo","bar"],"baz"]) end it "should not lazily evaluate the included class" do @compiler.expects(:evaluate_classes).with {|klasses,parser,lazy| lazy == false}.returns("foo") @scope.function_include(["foo"]) end it "should raise if the class is not found" do @scope.stubs(:source).returns(true) expect { @scope.function_include(["nosuchclass"]) }.to raise_error(Puppet::Error) end - describe "When the future parser is in use" do - require 'puppet/pops' - require 'puppet_spec/compiler' - include PuppetSpec::Compiler - - before(:each) do - Puppet[:parser] = 'future' - end + it_should_behave_like 'all functions transforming relative to absolute names', :function_include + it_should_behave_like 'an inclusion function, regardless of the type of class reference,', :include - it_should_behave_like 'all functions transforming relative to absolute names', :function_include - it_should_behave_like 'an inclusion function, regardless of the type of class reference,', :include - end end diff --git a/spec/unit/parser/functions/lookup_spec.rb b/spec/unit/parser/functions/lookup_spec.rb index 54ea55423..1ea977246 100644 --- a/spec/unit/parser/functions/lookup_spec.rb +++ b/spec/unit/parser/functions/lookup_spec.rb @@ -1,147 +1,142 @@ require 'spec_helper' require 'puppet/pops' require 'stringio' require 'puppet_spec/scope' describe "lookup function" do include PuppetSpec::Scope - before(:each) do - Puppet[:binder] = true - Puppet[:parser] = 'future' - end - it "must be called with at least a name to lookup" do scope = scope_with_injections_from(bound(bindings)) expect do scope.function_lookup([]) end.to raise_error(ArgumentError, /Wrong number of arguments/) end it "looks up a value that exists" do scope = scope_with_injections_from(bound(bind_single("a_value", "something"))) expect(scope.function_lookup(['a_value'])).to eq('something') end it "searches for first found if given several names" do scope = scope_with_injections_from(bound(bind_single("a_value", "something"))) expect(scope.function_lookup([['b_value', 'a_value', 'c_value']])).to eq('something') end it "override wins over bound" do scope = scope_with_injections_from(bound(bind_single("a_value", "something"))) options = {:override => { 'a_value' => 'something_overridden' }} expect(scope.function_lookup(['a_value', options])).to eq('something_overridden') end it "extra option is used if nothing is found" do scope = scope_with_injections_from(bound(bind_single("another_value", "something"))) options = {:extra => { 'a_value' => 'something_extra' }} expect(scope.function_lookup(['a_value', options])).to eq('something_extra') end it "hiera is called to lookup if value is not bound" do Puppet::Parser::Scope.any_instance.stubs(:function_hiera).returns('from_hiera') scope = scope_with_injections_from(bound(bind_single("another_value", "something"))) expect(scope.function_lookup(['a_value'])).to eq('from_hiera') end it "returns nil when the requested value is not bound and undef is accepted" do scope = scope_with_injections_from(bound(bind_single("a_value", "something"))) expect(scope.function_lookup(['not_bound_value',{'accept_undef' => true}])).to eq(nil) end it "fails if the requested value is not bound and undef is not allowed" do scope = scope_with_injections_from(bound(bind_single("a_value", "something"))) expect do scope.function_lookup(['not_bound_value']) end.to raise_error(/did not find a value for the name 'not_bound_value'/) end it "returns the given default value when the requested value is not bound" do scope = scope_with_injections_from(bound(bind_single("a_value", "something"))) expect(scope.function_lookup(['not_bound_value','String', 'cigar'])).to eq('cigar') end it "accepts values given in a hash of options" do scope = scope_with_injections_from(bound(bind_single("a_value", "something"))) expect(scope.function_lookup(['not_bound_value',{'type' => 'String', 'default' => 'cigar'}])).to eq('cigar') end it "raises an error when the bound type is not assignable to the requested type" do scope = scope_with_injections_from(bound(bind_single("a_value", "something"))) expect do scope.function_lookup(['a_value', 'Integer']) end.to raise_error(ArgumentError, /incompatible type, expected: Integer, got: String/) end it "returns the value if the bound type is assignable to the requested type" do typed_bindings = bindings typed_bindings.bind().string().name("a_value").to("something") scope = scope_with_injections_from(bound(typed_bindings)) expect(scope.function_lookup(['a_value', 'Data'])).to eq("something") end it "yields to a given lambda and returns the result" do scope = scope_with_injections_from(bound(bind_single("a_value", "something"))) expect(scope.function_lookup(['a_value', ast_lambda(scope, '|$x|{something_else}')])).to eq('something_else') end it "fails if given lambda produces undef" do scope = scope_with_injections_from(bound(bind_single("a_value", "something"))) expect do scope.function_lookup(['a_value', ast_lambda(scope, '|$x|{undef}')]) end.to raise_error(/did not find a value for the name 'a_value'/) end it "yields name and result to a given lambda" do scope = scope_with_injections_from(bound(bind_single("a_value", "something"))) expect(scope.function_lookup(['a_value', ast_lambda(scope, '|$name, $result|{[$name, $result]}')])).to eq(['a_value', 'something']) end it "yields name and result and default to a given lambda" do scope = scope_with_injections_from(bound(bind_single("a_value", "something"))) expect(scope.function_lookup(['a_value', {'default' => 'cigar'}, ast_lambda(scope, '|$name, $result, $d|{[$name, $result, $d]}')])).to eq(['a_value', 'something', 'cigar']) end it "yields to a given lambda and returns the result when giving name and type" do scope = scope_with_injections_from(bound(bind_single("a_value", "something"))) expect(scope.function_lookup(['a_value', 'String', ast_lambda(scope, '|$x|{something_else}')])).to eq('something_else') end it "yields :undef when value is not found and using a lambda" do scope = scope_with_injections_from(bound(bind_single("a_value", "something"))) expect(scope.function_lookup(['not_bound_value', ast_lambda(scope, '|$x|{ if $x == undef {good} else {bad}}')])).to eq('good') end def scope_with_injections_from(binder) injector = Puppet::Pops::Binder::Injector.new(binder) scope = create_test_scope_for_node('testing') scope.compiler.injector = injector scope end def bindings Puppet::Pops::Binder::BindingsFactory.named_bindings("testing") end def bind_single(name, value) local_bindings = Puppet::Pops::Binder::BindingsFactory.named_bindings("testing") local_bindings.bind().name(name).to(value) local_bindings end def bound(local_bindings) layered_bindings = Puppet::Pops::Binder::BindingsFactory.layered_bindings(Puppet::Pops::Binder::BindingsFactory.named_layer('test layer', local_bindings.model)) Puppet::Pops::Binder::Binder.new(layered_bindings) end def ast_lambda(scope, puppet_source) puppet_source = "fake_func() " + puppet_source evaluator = Puppet::Pops::Parser::EvaluatingParser.new() model = evaluator.parse_string(puppet_source, __FILE__).current evaluator.closure(model.body.lambda, scope) end end diff --git a/spec/unit/parser/functions/require_spec.rb b/spec/unit/parser/functions/require_spec.rb index 9d0c8a663..bccd923b9 100755 --- a/spec/unit/parser/functions/require_spec.rb +++ b/spec/unit/parser/functions/require_spec.rb @@ -1,75 +1,68 @@ #! /usr/bin/env ruby require 'spec_helper' require 'unit/parser/functions/shared' +require 'puppet_spec/compiler' describe "the require function" do - before :all do - Puppet::Parser::Functions.autoloader.loadall - end + include PuppetSpec::Compiler +# before :all do +# Puppet::Parser::Functions.autoloader.loadall +# end before :each do @catalog = stub 'catalog' node = Puppet::Node.new('localhost') compiler = Puppet::Parser::Compiler.new(node) @scope = Puppet::Parser::Scope.new(compiler) @scope.stubs(:findresource) @klass = stub 'class', :name => "myclass" @scope.stubs(:find_hostclass).returns(@klass) @resource = Puppet::Parser::Resource.new(:file, "/my/file", :scope => @scope, :source => "source") @scope.stubs(:resource).returns @resource end it "should exist" do Puppet::Parser::Functions.function("require").should == "function_require" end it "should delegate to the 'include' puppet function" do @scope.compiler.expects(:evaluate_classes).with(["::myclass"], @scope, false) @scope.function_require(["myclass"]) end it "should set the 'require' parameter on the resource to a resource reference" do @scope.compiler.stubs(:evaluate_classes) @scope.function_require(["myclass"]) @resource["require"].should be_instance_of(Array) @resource["require"][0].should be_instance_of(Puppet::Resource) end it "should lookup the absolute class path" do @scope.compiler.stubs(:evaluate_classes) @scope.expects(:find_hostclass).with("::myclass").returns(@klass) @klass.expects(:name).returns("myclass") @scope.function_require(["myclass"]) end it "should append the required class to the require parameter" do @scope.compiler.stubs(:evaluate_classes) one = Puppet::Resource.new(:file, "/one") @resource[:require] = one @scope.function_require(["myclass"]) @resource[:require].should be_include(one) @resource[:require].detect { |r| r.to_s == "Class[Myclass]" }.should be_instance_of(Puppet::Resource) end - describe "When the future parser is in use" do - require 'puppet/pops' - require 'puppet_spec/compiler' - include PuppetSpec::Compiler - - before(:each) do - Puppet[:parser] = 'future' - end + it_should_behave_like 'all functions transforming relative to absolute names', :function_require + it_should_behave_like 'an inclusion function, regardless of the type of class reference,', :require - it_should_behave_like 'all functions transforming relative to absolute names', :function_require - it_should_behave_like 'an inclusion function, regardless of the type of class reference,', :require - end end diff --git a/spec/unit/pops/evaluator/evaluating_parser_spec.rb b/spec/unit/pops/evaluator/evaluating_parser_spec.rb index ea40ef4e7..125c6cf88 100644 --- a/spec/unit/pops/evaluator/evaluating_parser_spec.rb +++ b/spec/unit/pops/evaluator/evaluating_parser_spec.rb @@ -1,1337 +1,1332 @@ require 'spec_helper' require 'puppet/pops' require 'puppet/pops/evaluator/evaluator_impl' require 'puppet/loaders' require 'puppet_spec/pops' require 'puppet_spec/scope' require 'puppet/parser/e4_parser_adapter' # relative to this spec file (./) does not work as this file is loaded by rspec #require File.join(File.dirname(__FILE__), '/evaluator_rspec_helper') describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do include PuppetSpec::Pops include PuppetSpec::Scope before(:each) do Puppet[:strict_variables] = true - # These must be set since the 3x logic switches some behaviors on these even if the tests explicitly - # use the 4x parser and evaluator. - # - Puppet[:parser] = 'future' - # Plugins Configuration cannot be loaded until the correct parser has been set (injector is turned off otherwise) require 'puppet/plugins' # Tests needs a known configuration of node/scope/compiler since it parses and evaluates # snippets as the compiler will evaluate them, butwithout the overhead of compiling a complete # catalog for each tested expression. # @parser = Puppet::Pops::Parser::EvaluatingParser.new @node = Puppet::Node.new('node.example.com') @node.environment = Puppet::Node::Environment.create(:testing, []) @compiler = Puppet::Parser::Compiler.new(@node) @scope = Puppet::Parser::Scope.new(@compiler) @scope.source = Puppet::Resource::Type.new(:node, 'node.example.com') @scope.parent = @compiler.topscope end let(:parser) { @parser } let(:scope) { @scope } types = Puppet::Pops::Types::TypeFactory context "When evaluator evaluates literals" do { "1" => 1, "010" => 8, "0x10" => 16, "3.14" => 3.14, "0.314e1" => 3.14, "31.4e-1" => 3.14, "'1'" => '1', "'banana'" => 'banana', '"banana"' => 'banana', "banana" => 'banana', "banana::split" => 'banana::split', "false" => false, "true" => true, "Array" => types.array_of_data(), "/.*/" => /.*/ }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end end context "When the evaluator evaluates Lists and Hashes" do { "[]" => [], "[1,2,3]" => [1,2,3], "[1,[2.0, 2.1, [2.2]],[3.0, 3.1]]" => [1,[2.0, 2.1, [2.2]],[3.0, 3.1]], "[2 + 2]" => [4], "[1,2,3] == [1,2,3]" => true, "[1,2,3] != [2,3,4]" => true, "[1,2,3] == [2,2,3]" => false, "[1,2,3] != [1,2,3]" => false, "[1,2,3][2]" => 3, "[1,2,3] + [4,5]" => [1,2,3,4,5], "[1,2,3] + [[4,5]]" => [1,2,3,[4,5]], "[1,2,3] + 4" => [1,2,3,4], "[1,2,3] << [4,5]" => [1,2,3,[4,5]], "[1,2,3] << {'a' => 1, 'b'=>2}" => [1,2,3,{'a' => 1, 'b'=>2}], "[1,2,3] << 4" => [1,2,3,4], "[1,2,3,4] - [2,3]" => [1,4], "[1,2,3,4] - [2,5]" => [1,3,4], "[1,2,3,4] - 2" => [1,3,4], "[1,2,3,[2],4] - 2" => [1,3,[2],4], "[1,2,3,[2,3],4] - [[2,3]]" => [1,2,3,4], "[1,2,3,3,2,4,2,3] - [2,3]" => [1,4], "[1,2,3,['a',1],['b',2]] - {'a' => 1, 'b'=>2}" => [1,2,3], "[1,2,3,{'a'=>1,'b'=>2}] - [{'a' => 1, 'b'=>2}]" => [1,2,3], }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end { "[1,2,3] + {'a' => 1, 'b'=>2}" => [1,2,3,['a',1],['b',2]], }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do # This test must be done with match_array since the order of the hash # is undefined and Ruby 1.8.7 and 1.9.3 produce different results. expect(parser.evaluate_string(scope, source, __FILE__)).to match_array(result) end end { "[1,2,3][a]" => :error, }.each do |source, result| it "should parse and raise error for '#{source}'" do expect { parser.evaluate_string(scope, source, __FILE__) }.to raise_error(Puppet::ParseError) end end { "{}" => {}, "{'a'=>1,'b'=>2}" => {'a'=>1,'b'=>2}, "{'a'=>1,'b'=>{'x'=>2.1,'y'=>2.2}}" => {'a'=>1,'b'=>{'x'=>2.1,'y'=>2.2}}, "{'a'=> 2 + 2}" => {'a'=> 4}, "{'a'=> 1, 'b'=>2} == {'a'=> 1, 'b'=>2}" => true, "{'a'=> 1, 'b'=>2} != {'x'=> 1, 'b'=>2}" => true, "{'a'=> 1, 'b'=>2} == {'a'=> 2, 'b'=>3}" => false, "{'a'=> 1, 'b'=>2} != {'a'=> 1, 'b'=>2}" => false, "{a => 1, b => 2}[b]" => 2, "{2+2 => sum, b => 2}[4]" => 'sum', "{'a'=>1, 'b'=>2} + {'c'=>3}" => {'a'=>1,'b'=>2,'c'=>3}, "{'a'=>1, 'b'=>2} + {'b'=>3}" => {'a'=>1,'b'=>3}, "{'a'=>1, 'b'=>2} + ['c', 3, 'b', 3]" => {'a'=>1,'b'=>3, 'c'=>3}, "{'a'=>1, 'b'=>2} + [['c', 3], ['b', 3]]" => {'a'=>1,'b'=>3, 'c'=>3}, "{'a'=>1, 'b'=>2} - {'b' => 3}" => {'a'=>1}, "{'a'=>1, 'b'=>2, 'c'=>3} - ['b', 'c']" => {'a'=>1}, "{'a'=>1, 'b'=>2, 'c'=>3} - 'c'" => {'a'=>1, 'b'=>2}, }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end { "{'a' => 1, 'b'=>2} << 1" => :error, }.each do |source, result| it "should parse and raise error for '#{source}'" do expect { parser.evaluate_string(scope, source, __FILE__) }.to raise_error(Puppet::ParseError) end end end context "When the evaluator perform comparisons" do { "'a' == 'a'" => true, "'a' == 'b'" => false, "'a' != 'a'" => false, "'a' != 'b'" => true, "'a' < 'b' " => true, "'a' < 'a' " => false, "'b' < 'a' " => false, "'a' <= 'b'" => true, "'a' <= 'a'" => true, "'b' <= 'a'" => false, "'a' > 'b' " => false, "'a' > 'a' " => false, "'b' > 'a' " => true, "'a' >= 'b'" => false, "'a' >= 'a'" => true, "'b' >= 'a'" => true, "'a' == 'A'" => true, "'a' != 'A'" => false, "'a' > 'A'" => false, "'a' >= 'A'" => true, "'A' < 'a'" => false, "'A' <= 'a'" => true, "1 == 1" => true, "1 == 2" => false, "1 != 1" => false, "1 != 2" => true, "1 < 2 " => true, "1 < 1 " => false, "2 < 1 " => false, "1 <= 2" => true, "1 <= 1" => true, "2 <= 1" => false, "1 > 2 " => false, "1 > 1 " => false, "2 > 1 " => true, "1 >= 2" => false, "1 >= 1" => true, "2 >= 1" => true, "1 == 1.0 " => true, "1 < 1.1 " => true, "1.0 == 1 " => true, "1.0 < 2 " => true, "'1.0' < 'a'" => true, "'1.0' < '' " => false, "'1.0' < ' '" => false, "'a' > '1.0'" => true, "/.*/ == /.*/ " => true, "/.*/ != /a.*/" => true, "true == true " => true, "false == false" => true, "true == false" => false, }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end { "a > 1" => /String > Integer/, "a >= 1" => /String >= Integer/, "a < 1" => /String < Integer/, "a <= 1" => /String <= Integer/, "1 > a" => /Integer > String/, "1 >= a" => /Integer >= String/, "1 < a" => /Integer < String/, "1 <= a" => /Integer <= String/, }.each do | source, error| it "should not allow comparison of String and Number '#{source}'" do expect { parser.evaluate_string(scope, source, __FILE__)}.to raise_error(error) end end { "'a' =~ /.*/" => true, "'a' =~ '.*'" => true, "/.*/ != /a.*/" => true, "'a' !~ /b.*/" => true, "'a' !~ 'b.*'" => true, '$x = a; a =~ "$x.*"' => true, "a =~ Pattern['a.*']" => true, "a =~ Regexp['a.*']" => false, # String is not subtype of Regexp. PUP-957 "$x = /a.*/ a =~ $x" => true, "$x = Pattern['a.*'] a =~ $x" => true, "1 =~ Integer" => true, "1 !~ Integer" => false, "[1,2,3] =~ Array[Integer[1,10]]" => true, }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end { "666 =~ /6/" => :error, "[a] =~ /a/" => :error, "{a=>1} =~ /a/" => :error, "/a/ =~ /a/" => :error, "Array =~ /A/" => :error, }.each do |source, result| it "should parse and raise error for '#{source}'" do expect { parser.evaluate_string(scope, source, __FILE__) }.to raise_error(Puppet::ParseError) end end { "1 in [1,2,3]" => true, "4 in [1,2,3]" => false, "a in {x=>1, a=>2}" => true, "z in {x=>1, a=>2}" => false, "ana in bananas" => true, "xxx in bananas" => false, "/ana/ in bananas" => true, "/xxx/ in bananas" => false, "ANA in bananas" => false, # ANA is a type, not a String "String[1] in bananas" => false, # Philosophically true though :-) "'ANA' in bananas" => true, "ana in 'BANANAS'" => true, "/ana/ in 'BANANAS'" => false, "/ANA/ in 'BANANAS'" => true, "xxx in 'BANANAS'" => false, "[2,3] in [1,[2,3],4]" => true, "[2,4] in [1,[2,3],4]" => false, "[a,b] in ['A',['A','B'],'C']" => true, "[x,y] in ['A',['A','B'],'C']" => false, "a in {a=>1}" => true, "x in {a=>1}" => false, "'A' in {a=>1}" => true, "'X' in {a=>1}" => false, "a in {'A'=>1}" => true, "x in {'A'=>1}" => false, "/xxx/ in {'aaaxxxbbb'=>1}" => true, "/yyy/ in {'aaaxxxbbb'=>1}" => false, "15 in [1, 0xf]" => true, "15 in [1, '0xf']" => false, "'15' in [1, 0xf]" => false, "15 in [1, 115]" => false, "1 in [11, '111']" => false, "'1' in [11, '111']" => false, "Array[Integer] in [2, 3]" => false, "Array[Integer] in [2, [3, 4]]" => true, "Array[Integer] in [2, [a, 4]]" => false, "Integer in { 2 =>'a'}" => true, "Integer[5,10] in [1,5,3]" => true, "Integer[5,10] in [1,2,3]" => false, "Integer in {'a'=>'a'}" => false, "Integer in {'a'=>1}" => false, }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end { "if /(ana)/ in bananas {$1}" => 'ana', "if /(xyz)/ in bananas {$1} else {$1}" => nil, "$a = bananas =~ /(ana)/; $b = /(xyz)/ in bananas; $1" => 'ana', "$a = xyz =~ /(xyz)/; $b = /(ana)/ in bananas; $1" => 'ana', "if /p/ in [pineapple, bananas] {$0}" => 'p', "if /b/ in [pineapple, bananas] {$0}" => 'b', }.each do |source, result| it "sets match variables for a regexp search using in such that '#{source}' produces '#{result}'" do parser.evaluate_string(scope, source, __FILE__).should == result end end { 'Any' => ['Data', 'Scalar', 'Numeric', 'Integer', 'Float', 'Boolean', 'String', 'Pattern', 'Collection', 'Array', 'Hash', 'CatalogEntry', 'Resource', 'Class', 'Undef', 'File', 'NotYetKnownResourceType'], # Note, Data > Collection is false (so not included) 'Data' => ['Scalar', 'Numeric', 'Integer', 'Float', 'Boolean', 'String', 'Pattern', 'Array', 'Hash',], 'Scalar' => ['Numeric', 'Integer', 'Float', 'Boolean', 'String', 'Pattern'], 'Numeric' => ['Integer', 'Float'], 'CatalogEntry' => ['Class', 'Resource', 'File', 'NotYetKnownResourceType'], 'Integer[1,10]' => ['Integer[2,3]'], }.each do |general, specials| specials.each do |special | it "should compute that #{general} > #{special}" do parser.evaluate_string(scope, "#{general} > #{special}", __FILE__).should == true end it "should compute that #{special} < #{general}" do parser.evaluate_string(scope, "#{special} < #{general}", __FILE__).should == true end it "should compute that #{general} != #{special}" do parser.evaluate_string(scope, "#{special} != #{general}", __FILE__).should == true end end end { 'Integer[1,10] > Integer[2,3]' => true, 'Integer[1,10] == Integer[2,3]' => false, 'Integer[1,10] > Integer[0,5]' => false, 'Integer[1,10] > Integer[1,10]' => false, 'Integer[1,10] >= Integer[1,10]' => true, 'Integer[1,10] == Integer[1,10]' => true, }.each do |source, result| it "should parse and evaluate the integer range comparison expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end end context "When the evaluator performs arithmetic" do context "on Integers" do { "2+2" => 4, "2 + 2" => 4, "7 - 3" => 4, "6 * 3" => 18, "6 / 3" => 2, "6 % 3" => 0, "10 % 3" => 1, "-(6/3)" => -2, "-6/3 " => -2, "8 >> 1" => 4, "8 << 1" => 16, }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end context "on Floats" do { "2.2 + 2.2" => 4.4, "7.7 - 3.3" => 4.4, "6.1 * 3.1" => 18.91, "6.6 / 3.3" => 2.0, "-(6.0/3.0)" => -2.0, "-6.0/3.0 " => -2.0, }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end { "3.14 << 2" => :error, "3.14 >> 2" => :error, "6.6 % 3.3" => 0.0, "10.0 % 3.0" => 1.0, }.each do |source, result| it "should parse and raise error for '#{source}'" do expect { parser.evaluate_string(scope, source, __FILE__) }.to raise_error(Puppet::ParseError) end end end context "on strings requiring boxing to Numeric" do { "'2' + '2'" => 4, "'-2' + '2'" => 0, "'- 2' + '2'" => 0, '"-\t 2" + "2"' => 0, "'+2' + '2'" => 4, "'+ 2' + '2'" => 4, "'2.2' + '2.2'" => 4.4, "'-2.2' + '2.2'" => 0.0, "'0xF7' + '010'" => 0xFF, "'0xF7' + '0x8'" => 0xFF, "'0367' + '010'" => 0xFF, "'012.3' + '010'" => 20.3, "'-0x2' + '0x4'" => 2, "'+0x2' + '0x4'" => 6, "'-02' + '04'" => 2, "'+02' + '04'" => 6, }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end { "'0888' + '010'" => :error, "'0xWTF' + '010'" => :error, "'0x12.3' + '010'" => :error, "'0x12.3' + '010'" => :error, '"-\n 2" + "2"' => :error, '"-\v 2" + "2"' => :error, '"-2\n" + "2"' => :error, '"-2\n " + "2"' => :error, }.each do |source, result| it "should parse and raise error for '#{source}'" do expect { parser.evaluate_string(scope, source, __FILE__) }.to raise_error(Puppet::ParseError) end end end end end # arithmetic context "When the evaluator evaluates assignment" do { "$a = 5" => 5, "$a = 5; $a" => 5, "$a = 5; $b = 6; $a" => 5, "$a = $b = 5; $a == $b" => true, }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end { "[a,b,c] = [1,2,3]" => /attempt to assign to 'an Array Expression'/, "[a,b,c] = {b=>2,c=>3,a=>1}" => /attempt to assign to 'an Array Expression'/, }.each do |source, result| it "should parse and evaluate the expression '#{source}' to error with #{result}" do expect { parser.evaluate_string(scope, source, __FILE__)}.to raise_error(Puppet::ParseError, result) end end end context "When the evaluator evaluates conditionals" do { "if true {5}" => 5, "if false {5}" => nil, "if false {2} else {5}" => 5, "if false {2} elsif true {5}" => 5, "if false {2} elsif false {5}" => nil, "unless false {5}" => 5, "unless true {5}" => nil, "unless true {2} else {5}" => 5, "$a = if true {5} $a" => 5, "$a = if false {5} $a" => nil, "$a = if false {2} else {5} $a" => 5, "$a = if false {2} elsif true {5} $a" => 5, "$a = if false {2} elsif false {5} $a" => nil, "$a = unless false {5} $a" => 5, "$a = unless true {5} $a" => nil, "$a = unless true {2} else {5} $a" => 5, }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end { "case 1 { 1 : { yes } }" => 'yes', "case 2 { 1,2,3 : { yes} }" => 'yes', "case 2 { 1,3 : { no } 2: { yes} }" => 'yes', "case 2 { 1,3 : { no } 5: { no } default: { yes }}" => 'yes', "case 2 { 1,3 : { no } 5: { no } }" => nil, "case 'banana' { 1,3 : { no } /.*ana.*/: { yes } }" => 'yes', "case 'banana' { /.*(ana).*/: { $1 } }" => 'ana', "case [1] { Array : { yes } }" => 'yes', "case [1] { Array[String] : { no } Array[Integer]: { yes } }" => 'yes', "case 1 { Integer : { yes } Type[Integer] : { no } }" => 'yes', "case Integer { Integer : { no } Type[Integer] : { yes } }" => 'yes', # supports unfold "case ringo { *[paul, john, ringo, george] : { 'beatle' } }" => 'beatle', }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end { "2 ? { 1 => no, 2 => yes}" => 'yes', "3 ? { 1 => no, 2 => no, default => yes }" => 'yes', "3 ? { 1 => no, default => yes, 3 => no }" => 'no', "3 ? { 1 => no, 3 => no, default => yes }" => 'no', "4 ? { 1 => no, default => yes, 3 => no }" => 'yes', "4 ? { 1 => no, 3 => no, default => yes }" => 'yes', "'banana' ? { /.*(ana).*/ => $1 }" => 'ana', "[2] ? { Array[String] => yes, Array => yes}" => 'yes', "ringo ? *[paul, john, ringo, george] => 'beatle'" => 'beatle', }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end it 'fails if a selector does not match' do expect{parser.evaluate_string(scope, "2 ? 3 => 4")}.to raise_error(/No matching entry for selector parameter with value '2'/) end end context "When evaluator evaluated unfold" do { "*[1,2,3]" => [1,2,3], "*1" => [1], "*'a'" => ['a'] }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end it "should parse and evaluate the expression '*{a=>10, b=>20} to [['a',10],['b',20]]" do result = parser.evaluate_string(scope, '*{a=>10, b=>20}', __FILE__) expect(result).to include(['a', 10]) expect(result).to include(['b', 20]) end end context "When evaluator performs [] operations" do { "[1,2,3][0]" => 1, "[1,2,3][2]" => 3, "[1,2,3][3]" => nil, "[1,2,3][-1]" => 3, "[1,2,3][-2]" => 2, "[1,2,3][-4]" => nil, "[1,2,3,4][0,2]" => [1,2], "[1,2,3,4][1,3]" => [2,3,4], "[1,2,3,4][-2,2]" => [3,4], "[1,2,3,4][-3,2]" => [2,3], "[1,2,3,4][3,5]" => [4], "[1,2,3,4][5,2]" => [], "[1,2,3,4][0,-1]" => [1,2,3,4], "[1,2,3,4][0,-2]" => [1,2,3], "[1,2,3,4][0,-4]" => [1], "[1,2,3,4][0,-5]" => [], "[1,2,3,4][-5,2]" => [1], "[1,2,3,4][-5,-3]" => [1,2], "[1,2,3,4][-6,-3]" => [1,2], "[1,2,3,4][2,-3]" => [], "[1,*[2,3],4]" => [1,2,3,4], "[1,*[2,3],4][1]" => 2, }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end { "{a=>1, b=>2, c=>3}[a]" => 1, "{a=>1, b=>2, c=>3}[c]" => 3, "{a=>1, b=>2, c=>3}[x]" => nil, "{a=>1, b=>2, c=>3}[c,b]" => [3,2], "{a=>1, b=>2, c=>3}[a,b,c]" => [1,2,3], "{a=>{b=>{c=>'it works'}}}[a][b][c]" => 'it works', "$a = {undef => 10} $a[free_lunch]" => nil, "$a = {undef => 10} $a[undef]" => 10, "$a = {undef => 10} $a[$a[free_lunch]]" => 10, "$a = {} $a[free_lunch] == undef" => true, }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end { "'abc'[0]" => 'a', "'abc'[2]" => 'c', "'abc'[-1]" => 'c', "'abc'[-2]" => 'b', "'abc'[-3]" => 'a', "'abc'[-4]" => '', "'abc'[3]" => '', "abc[0]" => 'a', "abc[2]" => 'c', "abc[-1]" => 'c', "abc[-2]" => 'b', "abc[-3]" => 'a', "abc[-4]" => '', "abc[3]" => '', "'abcd'[0,2]" => 'ab', "'abcd'[1,3]" => 'bcd', "'abcd'[-2,2]" => 'cd', "'abcd'[-3,2]" => 'bc', "'abcd'[3,5]" => 'd', "'abcd'[5,2]" => '', "'abcd'[0,-1]" => 'abcd', "'abcd'[0,-2]" => 'abc', "'abcd'[0,-4]" => 'a', "'abcd'[0,-5]" => '', "'abcd'[-5,2]" => 'a', "'abcd'[-5,-3]" => 'ab', "'abcd'[-6,-3]" => 'ab', "'abcd'[2,-3]" => '', }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end # Type operations (full set tested by tests covering type calculator) { "Array[Integer]" => types.array_of(types.integer), "Array[Integer,1]" => types.constrain_size(types.array_of(types.integer),1, :default), "Array[Integer,1,2]" => types.constrain_size(types.array_of(types.integer),1, 2), "Array[Integer,Integer[1,2]]" => types.constrain_size(types.array_of(types.integer),1, 2), "Array[Integer,Integer[1]]" => types.constrain_size(types.array_of(types.integer),1, :default), "Hash[Integer,Integer]" => types.hash_of(types.integer, types.integer), "Hash[Integer,Integer,1]" => types.constrain_size(types.hash_of(types.integer, types.integer),1, :default), "Hash[Integer,Integer,1,2]" => types.constrain_size(types.hash_of(types.integer, types.integer),1, 2), "Hash[Integer,Integer,Integer[1,2]]" => types.constrain_size(types.hash_of(types.integer, types.integer),1, 2), "Hash[Integer,Integer,Integer[1]]" => types.constrain_size(types.hash_of(types.integer, types.integer),1, :default), "Resource[File]" => types.resource('File'), "Resource['File']" => types.resource(types.resource('File')), "File[foo]" => types.resource('file', 'foo'), "File[foo, bar]" => [types.resource('file', 'foo'), types.resource('file', 'bar')], "Pattern[a, /b/, Pattern[c], Regexp[d]]" => types.pattern('a', 'b', 'c', 'd'), "String[1,2]" => types.constrain_size(types.string,1, 2), "String[Integer[1,2]]" => types.constrain_size(types.string,1, 2), "String[Integer[1]]" => types.constrain_size(types.string,1, :default), }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end # LHS where [] not supported, and missing key(s) { "Array[]" => :error, "'abc'[]" => :error, "Resource[]" => :error, "File[]" => :error, "String[]" => :error, "1[]" => :error, "3.14[]" => :error, "/.*/[]" => :error, "$a=[1] $a[]" => :error, }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do expect { parser.evaluate_string(scope, source, __FILE__)}.to raise_error(/Syntax error/) end end # Errors when wrong number/type of keys are used { "Array[0]" => 'Array-Type[] arguments must be types. Got Fixnum', "Hash[0]" => 'Hash-Type[] arguments must be types. Got Fixnum', "Hash[Integer, 0]" => 'Hash-Type[] arguments must be types. Got Fixnum', "Array[Integer,1,2,3]" => 'Array-Type[] accepts 1 to 3 arguments. Got 4', "Array[Integer,String]" => "A Type's size constraint arguments must be a single Integer type, or 1-2 integers (or default). Got a String-Type", "Hash[Integer,String, 1,2,3]" => 'Hash-Type[] accepts 1 to 4 arguments. Got 5', "'abc'[x]" => "The value 'x' cannot be converted to Numeric", "'abc'[1.0]" => "A String[] cannot use Float where Integer is expected", "'abc'[1,2,3]" => "String supports [] with one or two arguments. Got 3", "Resource[0]" => 'First argument to Resource[] must be a resource type or a String. Got Integer', "Resource[a, 0]" => 'Error creating type specialization of a Resource-Type, Cannot use Integer where a resource title String is expected', "File[0]" => 'Error creating type specialization of a File-Type, Cannot use Integer where a resource title String is expected', "String[a]" => "A Type's size constraint arguments must be a single Integer type, or 1-2 integers (or default). Got a String", "Pattern[0]" => 'Error creating type specialization of a Pattern-Type, Cannot use Integer where String or Regexp or Pattern-Type or Regexp-Type is expected', "Regexp[0]" => 'Error creating type specialization of a Regexp-Type, Cannot use Integer where String or Regexp is expected', "Regexp[a,b]" => 'A Regexp-Type[] accepts 1 argument. Got 2', "true[0]" => "Operator '[]' is not applicable to a Boolean", "1[0]" => "Operator '[]' is not applicable to an Integer", "3.14[0]" => "Operator '[]' is not applicable to a Float", "/.*/[0]" => "Operator '[]' is not applicable to a Regexp", "[1][a]" => "The value 'a' cannot be converted to Numeric", "[1][0.0]" => "An Array[] cannot use Float where Integer is expected", "[1]['0.0']" => "An Array[] cannot use Float where Integer is expected", "[1,2][1, 0.0]" => "An Array[] cannot use Float where Integer is expected", "[1,2][1.0, -1]" => "An Array[] cannot use Float where Integer is expected", "[1,2][1, -1.0]" => "An Array[] cannot use Float where Integer is expected", }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do expect { parser.evaluate_string(scope, source, __FILE__)}.to raise_error(Regexp.new(Regexp.quote(result))) end end context "on catalog types" do it "[n] gets resource parameter [n]" do source = "notify { 'hello': message=>'yo'} Notify[hello][message]" parser.evaluate_string(scope, source, __FILE__).should == 'yo' end it "[n] gets class parameter [n]" do source = "class wonka($produces='chocolate'){ } include wonka Class[wonka][produces]" # This is more complicated since it needs to run like 3.x and do an import_ast adapted_parser = Puppet::Parser::E4ParserAdapter.new adapted_parser.file = __FILE__ ast = adapted_parser.parse(source) Puppet.override({:global_scope => scope}, "test") do scope.known_resource_types.import_ast(ast, '') ast.code.safeevaluate(scope).should == 'chocolate' end end # Resource default and override expressions and resource parameter access with [] { # Properties "notify { id: message=>explicit} Notify[id][message]" => "explicit", "Notify { message=>by_default} notify {foo:} Notify[foo][message]" => "by_default", "notify {foo:} Notify[foo]{message =>by_override} Notify[foo][message]" => "by_override", # Parameters "notify { id: withpath=>explicit} Notify[id][withpath]" => "explicit", "Notify { withpath=>by_default } notify { foo: } Notify[foo][withpath]" => "by_default", "notify {foo:} Notify[foo]{withpath=>by_override} Notify[foo][withpath]" => "by_override", # Metaparameters "notify { foo: tag => evoe} Notify[foo][tag]" => "evoe", # Does not produce the defaults for tag parameter (title, type or names of scopes) "notify { foo: } Notify[foo][tag]" => nil, # But a default may be specified on the type "Notify { tag=>by_default } notify { foo: } Notify[foo][tag]" => "by_default", "Notify { tag=>by_default } notify { foo: } Notify[foo]{ tag=>by_override } Notify[foo][tag]" => "by_override", }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end # Virtual and realized resource default and overridden resource parameter access with [] { # Properties "@notify { id: message=>explicit } Notify[id][message]" => "explicit", "@notify { id: message=>explicit } realize Notify[id] Notify[id][message]" => "explicit", "Notify { message=>by_default } @notify { id: } Notify[id][message]" => "by_default", "Notify { message=>by_default } @notify { id: tag=>thisone } Notify <| tag == thisone |>; Notify[id][message]" => "by_default", "@notify { id: } Notify[id]{message=>by_override} Notify[id][message]" => "by_override", # Parameters "@notify { id: withpath=>explicit } Notify[id][withpath]" => "explicit", "Notify { withpath=>by_default } @notify { id: } Notify[id][withpath]" => "by_default", "@notify { id: } realize Notify[id] Notify[id]{withpath=>by_override} Notify[id][withpath]" => "by_override", # Metaparameters "@notify { id: tag=>explicit } Notify[id][tag]" => "explicit", }.each do |source, result| it "parses and evaluates virtual and realized resources in the expression '#{source}' to #{result}" do expect(parser.evaluate_string(scope, source, __FILE__)).to eq(result) end end # Exported resource attributes { "@@notify { id: message=>explicit } Notify[id][message]" => "explicit", "@@notify { id: message=>explicit, tag=>thisone } Notify <<| tag == thisone |>> Notify[id][message]" => "explicit", }.each do |source, result| it "parses and evaluates exported resources in the expression '#{source}' to #{result}" do expect(parser.evaluate_string(scope, source, __FILE__)).to eq(result) end end # Resource default and override expressions and resource parameter access error conditions { "notify { xid: message=>explicit} Notify[id][message]" => /Resource not found/, "notify { id: message=>explicit} Notify[id][mustard]" => /does not have a parameter called 'mustard'/, # NOTE: these meta-esque parameters are not recognized as such "notify { id: message=>explicit} Notify[id][title]" => /does not have a parameter called 'title'/, "notify { id: message=>explicit} Notify[id]['type']" => /does not have a parameter called 'type'/, "notify { id: message=>explicit } Notify[id]{message=>override}" => /'message' is already set on Notify\[id\]/ }.each do |source, result| it "should parse '#{source}' and raise error matching #{result}" do expect { parser.evaluate_string(scope, source, __FILE__)}.to raise_error(result) end end context 'with errors' do { "Class['fail-whale']" => /Illegal name/, "Class[0]" => /An Integer cannot be used where a String is expected/, "Class[/.*/]" => /A Regexp cannot be used where a String is expected/, "Class[4.1415]" => /A Float cannot be used where a String is expected/, "Class[Integer]" => /An Integer-Type cannot be used where a String is expected/, "Class[File['tmp']]" => /A File\['tmp'\] Resource-Reference cannot be used where a String is expected/, }.each do | source, error_pattern| it "an error is flagged for '#{source}'" do expect { parser.evaluate_string(scope, source, __FILE__)}.to raise_error(error_pattern) end end end end # end [] operations end context "When the evaluator performs boolean operations" do { "true and true" => true, "false and true" => false, "true and false" => false, "false and false" => false, "true or true" => true, "false or true" => true, "true or false" => true, "false or false" => false, "! true" => false, "!! true" => true, "!! false" => false, "! 'x'" => false, "! ''" => false, "! undef" => true, "! [a]" => false, "! []" => false, "! {a=>1}" => false, "! {}" => false, "true and false and '0xwtf' + 1" => false, "false or true or '0xwtf' + 1" => true, }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end { "false || false || '0xwtf' + 1" => :error, }.each do |source, result| it "should parse and raise error for '#{source}'" do expect { parser.evaluate_string(scope, source, __FILE__) }.to raise_error(Puppet::ParseError) end end end context "When evaluator performs operations on literal undef" do it "computes non existing hash lookup as undef" do parser.evaluate_string(scope, "{a => 1}[b] == undef", __FILE__).should == true parser.evaluate_string(scope, "undef == {a => 1}[b]", __FILE__).should == true end end context "When evaluator performs calls" do let(:populate) do parser.evaluate_string(scope, "$a = 10 $b = [1,2,3]") end { 'sprintf( "x%iy", $a )' => "x10y", # unfolds 'sprintf( *["x%iy", $a] )' => "x10y", '"x%iy".sprintf( $a )' => "x10y", '$b.reduce |$memo,$x| { $memo + $x }' => 6, 'reduce($b) |$memo,$x| { $memo + $x }' => 6, }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do populate parser.evaluate_string(scope, source, __FILE__).should == result end end { '"value is ${a*2} yo"' => :error, }.each do |source, result| it "should parse and raise error for '#{source}'" do expect { parser.evaluate_string(scope, source, __FILE__) }.to raise_error(Puppet::ParseError) end end it "provides location information on error in unparenthesized call logic" do expect{parser.evaluate_string(scope, "include non_existing_class", __FILE__)}.to raise_error(Puppet::ParseError, /line 1\:1/) end it 'defaults can be given in a lambda and used only when arg is missing' do env_loader = @compiler.loaders.public_environment_loader fc = Puppet::Functions.create_function(:test) do dispatch :test do param 'Integer', 'count' required_block_param end def test(count, block) block.call({}, *[].fill(10, 0, count)) end end the_func = fc.new({}, env_loader) env_loader.add_entry(:function, 'test', the_func, __FILE__) expect(parser.evaluate_string(scope, "test(1) |$x, $y=20| { $x + $y}")).to eql(30) expect(parser.evaluate_string(scope, "test(2) |$x, $y=20| { $x + $y}")).to eql(20) end it 'a given undef does not select the default value' do env_loader = @compiler.loaders.public_environment_loader fc = Puppet::Functions.create_function(:test) do dispatch :test do param 'Any', 'lambda_arg' required_block_param end def test(lambda_arg, block) block.call({}, lambda_arg) end end the_func = fc.new({}, env_loader) env_loader.add_entry(:function, 'test', the_func, __FILE__) expect(parser.evaluate_string(scope, "test(undef) |$x=20| { $x == undef}")).to eql(true) end it 'a given undef is given as nil' do env_loader = @compiler.loaders.public_environment_loader fc = Puppet::Functions.create_function(:assert_no_undef) do dispatch :assert_no_undef do param 'Any', 'x' end def assert_no_undef(x) case x when Array return unless x.include?(:undef) when Hash return unless x.keys.include?(:undef) || x.values.include?(:undef) else return unless x == :undef end raise "contains :undef" end end the_func = fc.new({}, env_loader) env_loader.add_entry(:function, 'assert_no_undef', the_func, __FILE__) expect{parser.evaluate_string(scope, "assert_no_undef(undef)")}.to_not raise_error() expect{parser.evaluate_string(scope, "assert_no_undef([undef])")}.to_not raise_error() expect{parser.evaluate_string(scope, "assert_no_undef({undef => 1})")}.to_not raise_error() expect{parser.evaluate_string(scope, "assert_no_undef({1 => undef})")}.to_not raise_error() end context 'using the 3x function api' do it 'can call a 3x function' do Puppet::Parser::Functions.newfunction("bazinga", :type => :rvalue) { |args| args[0] } parser.evaluate_string(scope, "bazinga(42)", __FILE__).should == 42 end it 'maps :undef to empty string' do Puppet::Parser::Functions.newfunction("bazinga", :type => :rvalue) { |args| args[0] } parser.evaluate_string(scope, "$a = {} bazinga($a[nope])", __FILE__).should == '' parser.evaluate_string(scope, "bazinga(undef)", __FILE__).should == '' end it 'does not map :undef to empty string in arrays' do Puppet::Parser::Functions.newfunction("bazinga", :type => :rvalue) { |args| args[0][0] } parser.evaluate_string(scope, "$a = {} $b = [$a[nope]] bazinga($b)", __FILE__).should == :undef parser.evaluate_string(scope, "bazinga([undef])", __FILE__).should == :undef end it 'does not map :undef to empty string in hashes' do Puppet::Parser::Functions.newfunction("bazinga", :type => :rvalue) { |args| args[0]['a'] } parser.evaluate_string(scope, "$a = {} $b = {a => $a[nope]} bazinga($b)", __FILE__).should == :undef parser.evaluate_string(scope, "bazinga({a => undef})", __FILE__).should == :undef end end end context "When evaluator performs string interpolation" do let(:populate) do parser.evaluate_string(scope, "$a = 10 $b = [1,2,3]") end { '"value is $a yo"' => "value is 10 yo", '"value is \$a yo"' => "value is $a yo", '"value is ${a} yo"' => "value is 10 yo", '"value is \${a} yo"' => "value is ${a} yo", '"value is ${$a} yo"' => "value is 10 yo", '"value is ${$a*2} yo"' => "value is 20 yo", '"value is ${sprintf("x%iy",$a)} yo"' => "value is x10y yo", '"value is ${"x%iy".sprintf($a)} yo"' => "value is x10y yo", '"value is ${[1,2,3]} yo"' => "value is [1, 2, 3] yo", '"value is ${/.*/} yo"' => "value is /.*/ yo", '$x = undef "value is $x yo"' => "value is yo", '$x = default "value is $x yo"' => "value is default yo", '$x = Array[Integer] "value is $x yo"' => "value is Array[Integer] yo", '"value is ${Array[Integer]} yo"' => "value is Array[Integer] yo", }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do populate parser.evaluate_string(scope, source, __FILE__).should == result end end it "should parse and evaluate an interpolation of a hash" do source = '"value is ${{a=>1,b=>2}} yo"' # This test requires testing against two options because a hash to string # produces a result that is unordered hashstr = {'a' => 1, 'b' => 2}.to_s alt_results = ["value is {a => 1, b => 2} yo", "value is {b => 2, a => 1} yo" ] populate parse_result = parser.evaluate_string(scope, source, __FILE__) alt_results.include?(parse_result).should == true end it 'should accept a variable with leading underscore when used directly' do source = '$_x = 10; "$_x"' expect(parser.evaluate_string(scope, source, __FILE__)).to eql('10') end it 'should accept a variable with leading underscore when used as an expression' do source = '$_x = 10; "${_x}"' expect(parser.evaluate_string(scope, source, __FILE__)).to eql('10') end { '"value is ${a*2} yo"' => :error, }.each do |source, result| it "should parse and raise error for '#{source}'" do expect { parser.evaluate_string(scope, source, __FILE__) }.to raise_error(Puppet::ParseError) end end end context "When evaluating variables" do context "that are non existing an error is raised for" do it "unqualified variable" do expect { parser.evaluate_string(scope, "$quantum_gravity", __FILE__) }.to raise_error(/Unknown variable/) end it "qualified variable" do expect { parser.evaluate_string(scope, "$quantum_gravity::graviton", __FILE__) }.to raise_error(/Unknown variable/) end end it "a lex error should be raised for '$foo::::bar'" do expect { parser.evaluate_string(scope, "$foo::::bar") }.to raise_error(Puppet::LexError, /Illegal fully qualified name at line 1:7/) end { '$a = $0' => nil, '$a = $1' => nil, }.each do |source, value| it "it is ok to reference numeric unassigned variables '#{source}'" do parser.evaluate_string(scope, source, __FILE__).should == value end end { '$00 = 0' => /must be a decimal value/, '$0xf = 0' => /must be a decimal value/, '$0777 = 0' => /must be a decimal value/, '$123a = 0' => /must be a decimal value/, }.each do |source, error_pattern| it "should raise an error for '#{source}'" do expect { parser.evaluate_string(scope, source, __FILE__) }.to raise_error(error_pattern) end end context "an initial underscore in the last segment of a var name is allowed" do { '$_a = 1' => 1, '$__a = 1' => 1, }.each do |source, value| it "as in this example '#{source}'" do parser.evaluate_string(scope, source, __FILE__).should == value end end end end context "When evaluating relationships" do it 'should form a relation with File[a] -> File[b]' do source = "File[a] -> File[b]" parser.evaluate_string(scope, source, __FILE__) scope.compiler.should have_relationship(['File', 'a', '->', 'File', 'b']) end it 'should form a relation with resource -> resource' do source = "notify{a:} -> notify{b:}" parser.evaluate_string(scope, source, __FILE__) scope.compiler.should have_relationship(['Notify', 'a', '->', 'Notify', 'b']) end it 'should form a relation with [File[a], File[b]] -> [File[x], File[y]]' do source = "[File[a], File[b]] -> [File[x], File[y]]" parser.evaluate_string(scope, source, __FILE__) scope.compiler.should have_relationship(['File', 'a', '->', 'File', 'x']) scope.compiler.should have_relationship(['File', 'b', '->', 'File', 'x']) scope.compiler.should have_relationship(['File', 'a', '->', 'File', 'y']) scope.compiler.should have_relationship(['File', 'b', '->', 'File', 'y']) end it 'should tolerate (eliminate) duplicates in operands' do source = "[File[a], File[a]] -> File[x]" parser.evaluate_string(scope, source, __FILE__) scope.compiler.should have_relationship(['File', 'a', '->', 'File', 'x']) scope.compiler.relationships.size.should == 1 end it 'should form a relation with <-' do source = "File[a] <- File[b]" parser.evaluate_string(scope, source, __FILE__) scope.compiler.should have_relationship(['File', 'b', '->', 'File', 'a']) end it 'should form a relation with <-' do source = "File[a] <~ File[b]" parser.evaluate_string(scope, source, __FILE__) scope.compiler.should have_relationship(['File', 'b', '~>', 'File', 'a']) end end context "When evaluating heredoc" do it "evaluates plain heredoc" do src = "@(END)\nThis is\nheredoc text\nEND\n" parser.evaluate_string(scope, src).should == "This is\nheredoc text\n" end it "parses heredoc with margin" do src = [ "@(END)", " This is", " heredoc text", " | END", "" ].join("\n") parser.evaluate_string(scope, src).should == "This is\nheredoc text\n" end it "parses heredoc with margin and right newline trim" do src = [ "@(END)", " This is", " heredoc text", " |- END", "" ].join("\n") parser.evaluate_string(scope, src).should == "This is\nheredoc text" end it "parses escape specification" do src = <<-CODE @(END/t) Tex\\tt\\n |- END CODE parser.evaluate_string(scope, src).should == "Tex\tt\\n" end it "parses syntax checked specification" do src = <<-CODE @(END:json) ["foo", "bar"] |- END CODE parser.evaluate_string(scope, src).should == '["foo", "bar"]' end it "parses syntax checked specification with error and reports it" do src = <<-CODE @(END:json) ['foo', "bar"] |- END CODE expect { parser.evaluate_string(scope, src)}.to raise_error(/Cannot parse invalid JSON string/) end it "parses interpolated heredoc expression" do src = <<-CODE $name = 'Fjodor' @("END") Hello $name |- END CODE parser.evaluate_string(scope, src).should == "Hello Fjodor" end end context "Handles Deprecations and Discontinuations" do it 'of import statements' do source = "\nimport foo" # Error references position 5 at the opening '{' # Set file to nil to make it easier to match with line number (no file name in output) expect { parser.evaluate_string(scope, source) }.to raise_error(/'import' has been discontinued.*line 2:1/) end end context "Detailed Error messages are reported" do it 'for illegal type references' do source = '1+1 { "title": }' # Error references position 5 at the opening '{' # Set file to nil to make it easier to match with line number (no file name in output) expect { parser.evaluate_string(scope, source) }.to raise_error( /Illegal Resource Type expression, expected result to be a type name, or untitled Resource.*line 1:2/) end it 'for non r-value producing <| |>' do expect { parser.parse_string("$a = File <| |>", nil) }.to raise_error(/A Virtual Query does not produce a value at line 1:6/) end it 'for non r-value producing <<| |>>' do expect { parser.parse_string("$a = File <<| |>>", nil) }.to raise_error(/An Exported Query does not produce a value at line 1:6/) end it 'for non r-value producing define' do Puppet.expects(:err).with("Invalid use of expression. A 'define' expression does not produce a value at line 1:6") Puppet.expects(:err).with("Classes, definitions, and nodes may only appear at toplevel or inside other classes at line 1:6") expect { parser.parse_string("$a = define foo { }", nil) }.to raise_error(/2 errors/) end it 'for non r-value producing class' do Puppet.expects(:err).with("Invalid use of expression. A Host Class Definition does not produce a value at line 1:6") Puppet.expects(:err).with("Classes, definitions, and nodes may only appear at toplevel or inside other classes at line 1:6") expect { parser.parse_string("$a = class foo { }", nil) }.to raise_error(/2 errors/) end it 'for unclosed quote with indication of start position of string' do source = <<-SOURCE.gsub(/^ {6}/,'') $a = "xx yyy SOURCE # first char after opening " reported as being in error. expect { parser.parse_string(source) }.to raise_error(/Unclosed quote after '"' followed by 'xx\\nyy\.\.\.' at line 1:7/) end it 'for multiple errors with a summary exception' do Puppet.expects(:err).with("Invalid use of expression. A Node Definition does not produce a value at line 1:6") Puppet.expects(:err).with("Classes, definitions, and nodes may only appear at toplevel or inside other classes at line 1:6") expect { parser.parse_string("$a = node x { }",nil) }.to raise_error(/2 errors/) end it 'for a bad hostname' do expect { parser.parse_string("node 'macbook+owned+by+name' { }", nil) }.to raise_error(/The hostname 'macbook\+owned\+by\+name' contains illegal characters.*at line 1:6/) end it 'for a hostname with interpolation' do source = <<-SOURCE.gsub(/^ {6}/,'') $name = 'fred' node "macbook-owned-by$name" { } SOURCE expect { parser.parse_string(source, nil) }.to raise_error(/An interpolated expression is not allowed in a hostname of a node at line 2:23/) end end context 'does not leak variables' do it 'local variables are gone when lambda ends' do source = <<-SOURCE [1,2,3].each |$x| { $y = $x} $a = $y SOURCE expect do parser.evaluate_string(scope, source) end.to raise_error(/Unknown variable: 'y'/) end it 'lambda parameters are gone when lambda ends' do source = <<-SOURCE [1,2,3].each |$x| { $y = $x} $a = $x SOURCE expect do parser.evaluate_string(scope, source) end.to raise_error(/Unknown variable: 'x'/) end it 'does not leak match variables' do source = <<-SOURCE if 'xyz' =~ /(x)(y)(z)/ { notice $2 } case 'abc' { /(a)(b)(c)/ : { $x = $2 } } "-$x-$2-" SOURCE expect(parser.evaluate_string(scope, source)).to eq('-b--') end end matcher :have_relationship do |expected| calc = Puppet::Pops::Types::TypeCalculator.new match do |compiler| op_name = {'->' => :relationship, '~>' => :subscription} compiler.relationships.any? do | relation | relation.source.type == expected[0] && relation.source.title == expected[1] && relation.type == op_name[expected[2]] && relation.target.type == expected[3] && relation.target.title == expected[4] end end failure_message_for_should do |actual| "Relationship #{expected[0]}[#{expected[1]}] #{expected[2]} #{expected[3]}[#{expected[4]}] but was unknown to compiler" end end end diff --git a/spec/unit/pops/parser/parsing_typed_parameters_spec.rb b/spec/unit/pops/parser/parsing_typed_parameters_spec.rb index 678f6523c..a8d585cb6 100644 --- a/spec/unit/pops/parser/parsing_typed_parameters_spec.rb +++ b/spec/unit/pops/parser/parsing_typed_parameters_spec.rb @@ -1,72 +1,65 @@ require 'spec_helper' require 'puppet/pops' require 'puppet/pops/evaluator/evaluator_impl' require 'puppet_spec/pops' require 'puppet_spec/scope' require 'puppet/parser/e4_parser_adapter' # relative to this spec file (./) does not work as this file is loaded by rspec #require File.join(File.dirname(__FILE__), '/evaluator_rspec_helper') describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do include PuppetSpec::Pops include PuppetSpec::Scope - before(:each) do - - # These must be set since the is 3x logic that triggers on these even if the tests are explicit - # about selection of parser and evaluator - # - Puppet[:parser] = 'future' - end let(:parser) { Puppet::Pops::Parser::EvaluatingParser.new } context "captures-rest parameter" do it 'is allowed in lambda when placed last' do source = <<-CODE foo() |$a, *$b| { $a + $b[0] } CODE expect do parser.parse_string(source, __FILE__) end.to_not raise_error() end it 'allows a type annotation' do source = <<-CODE foo() |$a, Integer *$b| { $a + $b[0] } CODE expect do parser.parse_string(source, __FILE__) end.to_not raise_error() end it 'is not allowed in lambda except last' do source = <<-CODE foo() |*$a, $b| { $a + $b[0] } CODE expect do parser.parse_string(source, __FILE__) end.to raise_error(Puppet::ParseError, /Parameter \$a is not last, and has 'captures rest'/) end it 'is not allowed in define' do source = <<-CODE define foo(*$a) { } CODE expect do parser.parse_string(source, __FILE__) end.to raise_error(Puppet::ParseError, /Parameter \$a has 'captures rest' - not supported in a 'define'/) end it 'is not allowed in class' do source = <<-CODE class foo(*$a) { } CODE expect do parser.parse_string(source, __FILE__) end.to raise_error(Puppet::ParseError, /Parameter \$a has 'captures rest' - not supported in a Host Class Definition/) end end end