3
require File.dirname(__FILE__) + '/../../spec_helper'
5
describe Puppet::Parser::Compiler do
7
@node = Puppet::Node.new "testnode"
8
@parser = Puppet::Parser::Parser.new :environment => "development"
10
@scope_resource = stub 'scope_resource', :builtin? => true, :finish => nil, :ref => 'Class[main]'
11
@scope = stub 'scope', :resource => @scope_resource, :source => mock("source")
12
@compiler = Puppet::Parser::Compiler.new(@node, @parser)
15
describe Puppet::Parser::Compiler do
17
it "should be able to store references to class scopes" do
18
lambda { @compiler.class_set "myname", "myscope" }.should_not raise_error
21
it "should be able to retrieve class scopes by name" do
22
@compiler.class_set "myname", "myscope"
23
@compiler.class_scope("myname").should == "myscope"
26
it "should be able to retrieve class scopes by object" do
27
klass = mock 'ast_class'
28
klass.expects(:classname).returns("myname")
29
@compiler.class_set "myname", "myscope"
30
@compiler.class_scope(klass).should == "myscope"
33
it "should be able to return a class list containing all set classes" do
34
@compiler.class_set "", "empty"
35
@compiler.class_set "one", "yep"
36
@compiler.class_set "two", "nope"
38
@compiler.classlist.sort.should == %w{one two}.sort
42
describe Puppet::Parser::Compiler, " when initializing" do
44
it "should set its node attribute" do
45
@compiler.node.should equal(@node)
48
it "should set its parser attribute" do
49
@compiler.parser.should equal(@parser)
52
it "should detect when ast nodes are absent" do
53
@compiler.ast_nodes?.should be_false
56
it "should detect when ast nodes are present" do
57
@parser.nodes["testing"] = "yay"
58
@compiler.ast_nodes?.should be_true
62
describe Puppet::Parser::Compiler, "when managing scopes" do
64
it "should create a top scope" do
65
@compiler.topscope.should be_instance_of(Puppet::Parser::Scope)
68
it "should be able to create new scopes" do
69
@compiler.newscope(@compiler.topscope).should be_instance_of(Puppet::Parser::Scope)
72
it "should correctly set the level of newly created scopes" do
73
@compiler.newscope(@compiler.topscope, :level => 5).level.should == 5
76
it "should set the parent scope of the new scope to be the passed-in parent" do
78
newscope = @compiler.newscope(scope)
80
@compiler.parent(newscope).should equal(scope)
84
describe Puppet::Parser::Compiler, " when compiling" do
87
[:set_node_parameters, :evaluate_main, :evaluate_ast_node, :evaluate_node_classes, :evaluate_generators, :fail_on_unevaluated,
88
:finish, :store, :extract]
91
# Stub all of the main compile methods except the ones we're specifically interested in.
92
def compile_stub(*except)
93
(compile_methods - except).each { |m| @compiler.stubs(m) }
96
it "should set node parameters as variables in the top scope" do
97
params = {"a" => "b", "c" => "d"}
98
@node.stubs(:parameters).returns(params)
99
compile_stub(:set_node_parameters)
101
@compiler.topscope.lookupvar("a").should == "b"
102
@compiler.topscope.lookupvar("c").should == "d"
105
it "should evaluate any existing classes named in the node" do
106
classes = %w{one two three four}
108
one = stub 'one', :classname => "one"
109
three = stub 'three', :classname => "three"
110
@node.stubs(:name).returns("whatever")
111
@node.stubs(:classes).returns(classes)
113
@compiler.expects(:evaluate_classes).with(classes, @compiler.topscope)
114
@compiler.class.publicize_methods(:evaluate_node_classes) { @compiler.evaluate_node_classes }
117
it "should enable ast_nodes if the parser has any nodes" do
118
@parser.expects(:nodes).returns(:one => :yay)
119
@compiler.ast_nodes?.should be_true
122
it "should disable ast_nodes if the parser has no nodes" do
123
@parser.expects(:nodes).returns({})
124
@compiler.ast_nodes?.should be_false
127
it "should evaluate the main class if it exists" do
128
compile_stub(:evaluate_main)
129
main_class = mock 'main_class'
130
main_class.expects(:evaluate_code).with { |r| r.is_a?(Puppet::Parser::Resource) }
131
@compiler.topscope.expects(:source=).with(main_class)
132
@parser.stubs(:findclass).with("", "").returns(main_class)
137
it "should evaluate any node classes" do
138
@node.stubs(:classes).returns(%w{one two three four})
139
@compiler.expects(:evaluate_classes).with(%w{one two three four}, @compiler.topscope)
140
@compiler.send(:evaluate_node_classes)
143
it "should evaluate all added collections" do
145
# And when the collections fail to evaluate.
146
colls << mock("coll1-false")
147
colls << mock("coll2-false")
148
colls.each { |c| c.expects(:evaluate).returns(false) }
150
@compiler.add_collection(colls[0])
151
@compiler.add_collection(colls[1])
153
compile_stub(:evaluate_generators)
157
it "should ignore builtin resources" do
158
resource = stub 'builtin', :ref => "File[testing]", :builtin? => true
160
@compiler.add_resource(@scope, resource)
161
resource.expects(:evaluate).never
166
it "should evaluate unevaluated resources" do
167
resource = stub 'notevaluated', :ref => "File[testing]", :builtin? => false, :evaluated? => false, :virtual? => false
168
@compiler.add_resource(@scope, resource)
170
# We have to now mark the resource as evaluated
171
resource.expects(:evaluate).with { |*whatever| resource.stubs(:evaluated?).returns true }
176
it "should not evaluate already-evaluated resources" do
177
resource = stub 'already_evaluated', :ref => "File[testing]", :builtin? => false, :evaluated? => true, :virtual? => false
178
@compiler.add_resource(@scope, resource)
179
resource.expects(:evaluate).never
184
it "should evaluate unevaluated resources created by evaluating other resources" do
185
resource = stub 'notevaluated', :ref => "File[testing]", :builtin? => false, :evaluated? => false, :virtual? => false
186
@compiler.add_resource(@scope, resource)
188
resource2 = stub 'created', :ref => "File[other]", :builtin? => false, :evaluated? => false, :virtual? => false
190
# We have to now mark the resource as evaluated
191
resource.expects(:evaluate).with { |*whatever| resource.stubs(:evaluated?).returns(true); @compiler.add_resource(@scope, resource2) }
192
resource2.expects(:evaluate).with { |*whatever| resource2.stubs(:evaluated?).returns(true) }
198
it "should call finish() on all resources" do
199
# Add a resource that does respond to :finish
200
resource = Puppet::Parser::Resource.new :scope => @scope, :type => "file", :title => "finish"
201
resource.expects(:finish)
203
@compiler.add_resource(@scope, resource)
205
# And one that does not
206
dnf = stub "dnf", :ref => "File[dnf]"
208
@compiler.add_resource(@scope, dnf)
210
@compiler.send(:finish)
213
it "should add resources that do not conflict with existing resources" do
214
resource = stub "noconflict", :ref => "File[yay]"
215
@compiler.add_resource(@scope, resource)
217
@compiler.catalog.should be_vertex(resource)
220
it "should fail to add resources that conflict with existing resources" do
221
type = stub 'faketype', :isomorphic? => true, :name => "mytype"
222
Puppet::Type.stubs(:type).with("mytype").returns(type)
224
resource1 = stub "iso1conflict", :ref => "Mytype[yay]", :type => "mytype", :file => "eh", :line => 0
225
resource2 = stub "iso2conflict", :ref => "Mytype[yay]", :type => "mytype", :file => "eh", :line => 0
227
@compiler.add_resource(@scope, resource1)
228
lambda { @compiler.add_resource(@scope, resource2) }.should raise_error(ArgumentError)
231
it "should have a method for looking up resources" do
232
resource = stub 'resource', :ref => "Yay[foo]"
233
@compiler.add_resource(@scope, resource)
234
@compiler.findresource("Yay[foo]").should equal(resource)
237
it "should be able to look resources up by type and title" do
238
resource = stub 'resource', :ref => "Yay[foo]"
239
@compiler.add_resource(@scope, resource)
240
@compiler.findresource("Yay", "foo").should equal(resource)
243
it "should not evaluate virtual defined resources" do
244
resource = stub 'notevaluated', :ref => "File[testing]", :builtin? => false, :evaluated? => false, :virtual? => true
245
@compiler.add_resource(@scope, resource)
247
resource.expects(:evaluate).never
253
describe Puppet::Parser::Compiler, " when evaluating collections" do
255
it "should evaluate each collection" do
257
coll = mock 'coll%s' % i
258
@compiler.add_collection(coll)
260
# This is the hard part -- we have to emulate the fact that
261
# collections delete themselves if they are done evaluating.
262
coll.expects(:evaluate).with do
263
@compiler.delete_collection(coll)
267
@compiler.class.publicize_methods(:evaluate_collections) { @compiler.evaluate_collections }
270
it "should not fail when there are unevaluated resource collections that do not refer to specific resources" do
271
coll = stub 'coll', :evaluate => false
272
coll.expects(:resources).returns(nil)
274
@compiler.add_collection(coll)
276
lambda { @compiler.compile }.should_not raise_error
279
it "should fail when there are unevaluated resource collections that refer to a specific resource" do
280
coll = stub 'coll', :evaluate => false
281
coll.expects(:resources).returns(:something)
283
@compiler.add_collection(coll)
285
lambda { @compiler.compile }.should raise_error(Puppet::ParseError)
288
it "should fail when there are unevaluated resource collections that refer to multiple specific resources" do
289
coll = stub 'coll', :evaluate => false
290
coll.expects(:resources).returns([:one, :two])
292
@compiler.add_collection(coll)
294
lambda { @compiler.compile }.should raise_error(Puppet::ParseError)
298
describe Puppet::Parser::Compiler, "when told to evaluate missing classes" do
300
it "should fail if there's no source listed for the scope" do
301
scope = stub 'scope', :source => nil
302
proc { @compiler.evaluate_classes(%w{one two}, scope) }.should raise_error(Puppet::DevError)
305
it "should tag the catalog with the name of each not-found class" do
306
@compiler.catalog.expects(:tag).with("notfound")
307
@scope.expects(:findclass).with("notfound").returns(nil)
308
@compiler.evaluate_classes(%w{notfound}, @scope)
312
describe Puppet::Parser::Compiler, " when evaluating found classes" do
315
@class = stub 'class', :classname => "my::class"
316
@scope.stubs(:findclass).with("myclass").returns(@class)
318
@resource = stub 'resource', :ref => "Class[myclass]"
321
it "should evaluate each class" do
322
@compiler.catalog.stubs(:tag)
324
@class.expects(:evaluate).with(@scope)
326
@compiler.evaluate_classes(%w{myclass}, @scope)
329
it "should not evaluate the resources created for found classes unless asked" do
330
@compiler.catalog.stubs(:tag)
332
@resource.expects(:evaluate).never
334
@class.expects(:evaluate).returns(@resource)
336
@compiler.evaluate_classes(%w{myclass}, @scope)
339
it "should immediately evaluate the resources created for found classes when asked" do
340
@compiler.catalog.stubs(:tag)
342
@resource.expects(:evaluate)
343
@class.expects(:evaluate).returns(@resource)
345
@compiler.evaluate_classes(%w{myclass}, @scope, false)
348
it "should skip classes that have already been evaluated" do
349
@compiler.catalog.stubs(:tag)
351
@compiler.expects(:class_scope).with(@class).returns("something")
353
@compiler.expects(:add_resource).never
355
@resource.expects(:evaluate).never
357
Puppet::Parser::Resource.expects(:new).never
358
@compiler.evaluate_classes(%w{myclass}, @scope, false)
361
it "should return the list of found classes" do
362
@compiler.catalog.stubs(:tag)
364
@compiler.stubs(:add_resource)
365
@scope.stubs(:findclass).with("notfound").returns(nil)
367
Puppet::Parser::Resource.stubs(:new).returns(@resource)
368
@class.stubs :evaluate
369
@compiler.evaluate_classes(%w{myclass notfound}, @scope).should == %w{myclass}
373
describe Puppet::Parser::Compiler, " when evaluating AST nodes with no AST nodes present" do
375
it "should do nothing" do
376
@compiler.expects(:ast_nodes?).returns(false)
377
@compiler.parser.expects(:nodes).never
378
Puppet::Parser::Resource.expects(:new).never
380
@compiler.send(:evaluate_ast_node)
384
describe Puppet::Parser::Compiler, " when evaluating AST nodes with AST nodes present" do
387
@nodes = mock 'node_hash'
388
@compiler.stubs(:ast_nodes?).returns(true)
389
@compiler.parser.stubs(:nodes).returns(@nodes)
391
# Set some names for our test
392
@node.stubs(:names).returns(%w{a b c})
393
@nodes.stubs(:[]).with("a").returns(nil)
394
@nodes.stubs(:[]).with("b").returns(nil)
395
@nodes.stubs(:[]).with("c").returns(nil)
397
# It should check this last, of course.
398
@nodes.stubs(:[]).with("default").returns(nil)
401
it "should fail if the named node cannot be found" do
402
proc { @compiler.send(:evaluate_ast_node) }.should raise_error(Puppet::ParseError)
405
it "should evaluate the first node class matching the node name" do
406
node_class = stub 'node', :classname => "c", :evaluate_code => nil
407
@nodes.stubs(:[]).with("c").returns(node_class)
409
node_resource = stub 'node resource', :ref => "Node[c]", :evaluate => nil
410
node_class.expects(:evaluate).returns(node_resource)
415
it "should match the default node if no matching node can be found" do
416
node_class = stub 'node', :classname => "default", :evaluate_code => nil
417
@nodes.stubs(:[]).with("default").returns(node_class)
419
node_resource = stub 'node resource', :ref => "Node[default]", :evaluate => nil
420
node_class.expects(:evaluate).returns(node_resource)
425
it "should evaluate the node resource immediately rather than using lazy evaluation" do
426
node_class = stub 'node', :classname => "c"
427
@nodes.stubs(:[]).with("c").returns(node_class)
429
node_resource = stub 'node resource', :ref => "Node[c]"
430
node_class.expects(:evaluate).returns(node_resource)
432
node_resource.expects(:evaluate)
434
@compiler.send(:evaluate_ast_node)
437
it "should set the node's scope as the top scope" do
438
node_resource = stub 'node resource', :ref => "Node[c]", :evaluate => nil
439
node_class = stub 'node', :classname => "c", :evaluate => node_resource
441
@nodes.stubs(:[]).with("c").returns(node_class)
443
# The #evaluate method normally does this.
444
scope = stub 'scope', :source => "mysource"
445
@compiler.class_set(node_class.classname, scope)
446
node_resource.stubs(:evaluate)
450
@compiler.topscope.should equal(scope)
454
describe Puppet::Parser::Compiler, "when storing compiled resources" do
456
it "should store the resources" do
457
Puppet.features.expects(:rails?).returns(true)
458
Puppet::Rails.expects(:connect)
460
@compiler.catalog.expects(:vertices).returns(:resources)
462
@compiler.expects(:store_to_active_record).with(@node, :resources)
463
@compiler.send(:store)
466
it "should store to active_record" do
467
@node.expects(:name).returns("myname")
468
Puppet::Rails::Host.stubs(:transaction).yields
469
Puppet::Rails::Host.expects(:store).with(@node, :resources)
470
@compiler.send(:store_to_active_record, @node, :resources)
474
describe Puppet::Parser::Compiler, "when managing resource overrides" do
477
@override = stub 'override', :ref => "My[ref]"
478
@resource = stub 'resource', :ref => "My[ref]", :builtin? => true
481
it "should be able to store overrides" do
482
lambda { @compiler.add_override(@override) }.should_not raise_error
485
it "should apply overrides to the appropriate resources" do
486
@compiler.add_resource(@scope, @resource)
487
@resource.expects(:merge).with(@override)
489
@compiler.add_override(@override)
494
it "should accept overrides before the related resource has been created" do
495
@resource.expects(:merge).with(@override)
497
# First store the override
498
@compiler.add_override(@override)
501
@compiler.add_resource(@scope, @resource)
503
# And compile, so they get resolved
507
it "should fail if the compile is finished and resource overrides have not been applied" do
508
@compiler.add_override(@override)
510
lambda { @compiler.compile }.should raise_error(Puppet::ParseError)
514
# #620 - Nodes and classes should conflict, else classes don't get evaluated
515
describe Puppet::Parser::Compiler, "when evaluating nodes and classes with the same name (#620)" do
518
@node = stub :nodescope? => true
519
@class = stub :nodescope? => false
522
it "should fail if a node already exists with the same name as the class being evaluated" do
523
@compiler.class_set("one", @node)
524
lambda { @compiler.class_set("one", @class) }.should raise_error(Puppet::ParseError)
527
it "should fail if a class already exists with the same name as the node being evaluated" do
528
@compiler.class_set("one", @class)
529
lambda { @compiler.class_set("one", @node) }.should raise_error(Puppet::ParseError)