require 'spec_helper' require 'puppet/pops' require 'puppet_spec/compiler' module Puppet::Pops module Types describe 'the type mismatch describer' do include PuppetSpec::Compiler it 'will report a mismatch between a hash and a struct with details' do code = <<-CODE function f(Hash[String,String] $h) { $h['a'] } f({'a' => 'a', 'b' => 23}) CODE expect { eval_and_collect_notices(code) }.to raise_error(Puppet::Error, /expects a Hash\[String, String\] value, got Struct\[\{'a' => String, 'b' => Integer\}\]/) end it 'will report a mismatch between a array and tuple with details' do code = <<-CODE function f(Array[String] $h) { $h[0] } f(['a', 23]) CODE expect { eval_and_collect_notices(code) }.to raise_error(Puppet::Error, /expects an Array\[String\] value, got Tuple\[String, Integer\]/) end it 'will not report details for a mismatch between an array and a struct' do code = <<-CODE function f(Array[String] $h) { $h[0] } f({'a' => 'a string', 'b' => 23}) CODE expect { eval_and_collect_notices(code) }.to raise_error(Puppet::Error, /expects an Array value, got Struct/) end it 'will not report details for a mismatch between a hash and a tuple' do code = <<-CODE function f(Hash[String,String] $h) { $h['a'] } f(['a', 23]) CODE expect { eval_and_collect_notices(code) }.to raise_error(Puppet::Error, /expects a Hash value, got Tuple/) end it 'will include the aliased type when reporting a mismatch that involves an alias' do code = <<-CODE type UnprivilegedPort = Integer[1024,65537] function check_port(UnprivilegedPort $port) {} check_port(34) CODE expect { eval_and_collect_notices(code) }.to raise_error(Puppet::Error, /parameter 'port' expects an UnprivilegedPort = Integer\[1024, 65537\] value, got Integer\[34, 34\]/) end it 'will include the aliased type when reporting a mismatch that involves an alias nested in another type' do code = <<-CODE type UnprivilegedPort = Integer[1024,65537] type PortMap = Hash[UnprivilegedPort,String] function check_port(PortMap $ports) {} check_port({ 34 => 'some service'}) CODE expect { eval_and_collect_notices(code) }.to(raise_error(Puppet::Error, /parameter 'ports' expects a PortMap = Hash\[UnprivilegedPort = Integer\[1024, 65537\], String\] value, got Hash\[Integer\[34, 34\], String\[12, 12\]\]/)) end it 'will not include the aliased type more than once when reporting a mismatch that involves an alias that is self recursive' do code = <<-CODE type Tree = Hash[String,Tree] function check_tree(Tree $tree) {} check_tree({ 'x' => {'y' => {32 => 'n'}}}) CODE expect { eval_and_collect_notices(code) }.to(raise_error(Puppet::Error, /parameter 'tree' expects a Tree = Hash\[String, Tree\] value, got Struct\[\{'x' => Struct\[\{'y' => Hash\[Integer, String\]\}\]\}\]/)) end it 'will use type normalization' do code = <<-CODE type EVariants = Variant[Enum[a,b],Enum[b,c],Enum[c,d]] function check_enums(EVariants $evars) {} check_enums('n') CODE expect { eval_and_collect_notices(code) }.to(raise_error(Puppet::Error, /parameter 'evars' expects a match for EVariants = Enum\['a', 'b', 'c', 'd'\], got 'n'/)) end context 'when reporting a mismatch between' do let(:parser) { TypeParser.new } let(:subject) { TypeMismatchDescriber.singleton } context 'hash and struct' do it 'reports a size mismatch when hash has unlimited size' do expected = parser.parse('Struct[{a=>Integer,b=>Integer}]') actual = parser.parse('Hash[String,Integer]') expect(subject.describe_mismatch('', expected, actual)).to eq('expected size to be 2, got unlimited') end it 'reports a size mismatch when hash has specified but incorrect size' do expected = parser.parse('Struct[{a=>Integer,b=>Integer}]') actual = parser.parse('Hash[String,Integer,1,1]') expect(subject.describe_mismatch('', expected, actual)).to eq('expected size to be 2, got 1') end it 'reports a full type mismatch when size is correct but hash value type is incorrect' do expected = parser.parse('Struct[{a=>Integer,b=>String}]') actual = parser.parse('Hash[String,Integer,2,2]') expect(subject.describe_mismatch('', expected, actual)).to eq("expected a Struct[{'a' => Integer, 'b' => String}] value, got Hash[String, Integer]") end end end context 'when using present tense' do let(:parser) { TypeParser.new } let(:subject) { TypeMismatchDescriber.singleton } it 'reports a missing parameter as "has no parameter"' do t = parser.parse('Struct[{a=>String}]') expect { subject.validate_parameters('v', t, {'a'=>'a','b'=>'b'}, false, :present) }.to raise_error(Puppet::Error, "v: has no parameter named 'b'") end it 'reports a missing value as "expects a value"' do t = parser.parse('Struct[{a=>String,b=>String}]') expect { subject.validate_parameters('v', t, {'a'=>'a'}, false, :present) }.to raise_error(Puppet::Error, "v: expects a value for parameter 'b'") end it 'reports a missing block as "expects a block"' do callable = parser.parse('Callable[String,String,Callable]') args_tuple = parser.parse('Tuple[String,String]') dispatch = Functions::Dispatch.new(callable, 'foo', ['a','b'], 'block', nil, nil, false) expect(subject.describe_signatures('function', [dispatch], args_tuple, :present)).to eq("'function' expects a block") end it 'reports an unexpected block as "does not expect a block"' do callable = parser.parse('Callable[String,String]') args_tuple = parser.parse('Tuple[String,String,Callable]') dispatch = Functions::Dispatch.new(callable, 'foo', ['a','b'], nil, nil, nil, false) expect(subject.describe_signatures('function', [dispatch], args_tuple, :present)).to eq("'function' does not expect a block") end end context 'when using past tense' do let(:parser) { TypeParser.new } let(:subject) { TypeMismatchDescriber.singleton } it 'reports a missing parameter as "did not have a parameter"' do t = parser.parse('Struct[{a=>String}]') expect { subject.validate_parameters('v', t, {'a'=>'a','b'=>'b'}, false, :past) }.to raise_error(Puppet::Error, "v: did not have a parameter named 'b'") end it 'reports a missing value as "expected a value"' do t = parser.parse('Struct[{a=>String,b=>String}]') expect { subject.validate_parameters('v', t, {'a'=>'a'}, false, :past) }.to raise_error(Puppet::Error, "v: expected a value for parameter 'b'") end it 'reports a missing block as "expected a block"' do callable = parser.parse('Callable[String,String,Callable]') args_tuple = parser.parse('Tuple[String,String]') dispatch = Functions::Dispatch.new(callable, 'foo', ['a','b'], 'block', nil, nil, false) expect(subject.describe_signatures('function', [dispatch], args_tuple, :past)).to eq("'function' expected a block") end it 'reports an unexpected block as "did not expect a block"' do callable = parser.parse('Callable[String,String]') args_tuple = parser.parse('Tuple[String,String,Callable]') dispatch = Functions::Dispatch.new(callable, 'foo', ['a','b'], nil, nil, nil, false) expect(subject.describe_signatures('function', [dispatch], args_tuple, :past)).to eq("'function' did not expect a block") end end end end end