summaryrefslogtreecommitdiff
path: root/spec/unit/pops/evaluator/evaluating_parser_spec.rb
diff options
context:
space:
mode:
Diffstat (limited to 'spec/unit/pops/evaluator/evaluating_parser_spec.rb')
-rw-r--r--spec/unit/pops/evaluator/evaluating_parser_spec.rb280
1 files changed, 250 insertions, 30 deletions
diff --git a/spec/unit/pops/evaluator/evaluating_parser_spec.rb b/spec/unit/pops/evaluator/evaluating_parser_spec.rb
index 210b55b20..5e80e4076 100644
--- a/spec/unit/pops/evaluator/evaluating_parser_spec.rb
+++ b/spec/unit/pops/evaluator/evaluating_parser_spec.rb
@@ -2,6 +2,7 @@ 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'
@@ -16,18 +17,29 @@ describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do
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
+ # 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'
- Puppet[:evaluator] = 'future'
+
# Puppetx cannot be loaded until the correct parser has been set (injector is turned off otherwise)
require 'puppetx'
+
+ # 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) { Puppet::Pops::Parser::EvaluatingParser::Transitional.new }
- let(:node) { 'node.example.com' }
- let(:scope) { s = create_test_scope_for_node(node); s }
+ let(:parser) { @parser }
+ let(:scope) { @scope }
types = Puppet::Pops::Types::TypeFactory
context "When evaluator evaluates literals" do
@@ -206,7 +218,7 @@ describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do
"'a' !~ 'b.*'" => true,
'$x = a; a =~ "$x.*"' => true,
"a =~ Pattern['a.*']" => true,
- "a =~ Regexp['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,
@@ -240,6 +252,7 @@ describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do
"/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,
@@ -278,7 +291,20 @@ describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do
end
{
- 'Object' => ['Data', 'Scalar', 'Numeric', 'Integer', 'Float', 'Boolean', 'String', 'Pattern', 'Collection',
+ "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)
@@ -364,11 +390,21 @@ describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do
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
@@ -380,6 +416,10 @@ describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do
"'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)
@@ -395,8 +435,6 @@ describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do
"$a = 5; $a" => 5,
"$a = 5; $b = 6; $a" => 5,
"$a = $b = 5; $a == $b" => true,
- "$a = [1,2,3]; [x].map |$x| { $a += x; $a }" => [[1,2,3,'x']],
- "$a = [a,x,c]; [x].map |$x| { $a -= x; $a }" => [['a','c']],
}.each do |source, result|
it "should parse and evaluate the expression '#{source}' to #{result}" do
parser.evaluate_string(scope, source, __FILE__).should == result
@@ -404,13 +442,11 @@ describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do
end
{
- "[a,b,c] = [1,2,3]; $a == 1 and $b == 2 and $c == 3" => :error,
- "[a,b,c] = {b=>2,c=>3,a=>1}; $a == 1 and $b == 2 and $c == 3" => :error,
- "$a = [1,2,3]; [x].collect |$x| { [a] += x; $a }" => :error,
- "$a = [a,x,c]; [x].collect |$x| { [a] -= x; $a }" => :error,
+ "[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 #{result}" do
- expect { parser.evaluate_string(scope, source, __FILE__)}.to raise_error(Puppet::ParseError)
+ 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
@@ -458,6 +494,9 @@ describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do
"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
@@ -467,16 +506,42 @@ describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do
{
"2 ? { 1 => no, 2 => yes}" => 'yes',
- "3 ? { 1 => no, 2 => no}" => nil,
"3 ? { 1 => no, 2 => no, default => yes }" => 'yes',
- "3 ? { 1 => no, default => yes, 3 => no }" => '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
@@ -501,6 +566,8 @@ describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do
"[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
@@ -651,24 +718,82 @@ describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do
adapted_parser = Puppet::Parser::E4ParserAdapter.new
adapted_parser.file = __FILE__
ast = adapted_parser.parse(source)
- scope.known_resource_types.import_ast(ast, '')
- ast.code.safeevaluate(scope).should == 'chocolate'
+ 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
+ # 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/,
@@ -676,6 +801,7 @@ describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do
# 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)
@@ -713,7 +839,7 @@ describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do
"!! true" => true,
"!! false" => false,
"! 'x'" => false,
- "! ''" => true,
+ "! ''" => false,
"! undef" => true,
"! [a]" => false,
"! []" => false,
@@ -744,12 +870,15 @@ describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do
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,
@@ -771,6 +900,69 @@ describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do
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
end
context "When evaluator performs string interpolation" do
@@ -971,7 +1163,7 @@ describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do
expect { parser.evaluate_string(scope, src)}.to raise_error(/Cannot parse invalid JSON string/)
end
- it "parses interpolated heredoc epression" do
+ it "parses interpolated heredoc expression" do
src = <<-CODE
$name = 'Fjodor'
@("END")
@@ -983,12 +1175,6 @@ describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do
end
context "Handles Deprecations and Discontinuations" do
- around(:each) do |example|
- Puppet.override({:loaders => Puppet::Pops::Loaders.new(Puppet::Node::Environment.create(:testing, []))}, 'test') do
- example.run
- end
- end
-
it 'of import statements' do
source = "\nimport foo"
# Error references position 5 at the opening '{'
@@ -1002,7 +1188,8 @@ describe 'Puppet::Pops::Evaluator::EvaluatorImpl' 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.parse_string(source, nil) }.to raise_error(/Expression is not valid as a resource.*line 1:5/)
+ 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
@@ -1058,6 +1245,39 @@ describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do
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