summaryrefslogtreecommitdiff
path: root/spec/unit/parser/functions/lookup_spec.rb
blob: 54ea554233645e02c1c5941b2c10dc7e3dce8be4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
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