diff --git a/lib/puppet/parser/ast.rb b/lib/puppet/parser/ast.rb index bf60c75a0..b7e324687 100644 --- a/lib/puppet/parser/ast.rb +++ b/lib/puppet/parser/ast.rb @@ -1,131 +1,130 @@ # the parent class for all of our syntactical objects require 'puppet' require 'puppet/util/autoload' # The base class for all of the objects that make up the parse trees. # Handles things like file name, line #, and also does the initialization # for all of the parameters of all of the child objects. class Puppet::Parser::AST # Do this so I don't have to type the full path in all of the subclasses AST = Puppet::Parser::AST include Puppet::Util::Errors include Puppet::Util::MethodHelper include Puppet::Util::Docs attr_accessor :parent, :scope, :file, :line, :pos def inspect "( #{self.class} #{self.to_s} #{@children.inspect} )" end # don't fetch lexer comment by default def use_docs self.class.use_docs end # allow our subclass to specify they want documentation class << self attr_accessor :use_docs def associates_doc self.use_docs = true end end # Evaluate the current object. Just a stub method, since the subclass # should override this method. def evaluate(*options) - raise Puppet::DevError, "Did not override #evaluate in #{self.class}" end # Throw a parse error. def parsefail(message) self.fail(Puppet::ParseError, message) end # Wrap a statemp in a reusable way so we always throw a parse error. def parsewrap exceptwrap :type => Puppet::ParseError do yield end end # The version of the evaluate method that should be called, because it # correctly handles errors. It is critical to use this method because # it can enable you to catch the error where it happens, rather than # much higher up the stack. def safeevaluate(*options) # We duplicate code here, rather than using exceptwrap, because this # is called so many times during parsing. begin return self.evaluate(*options) rescue Puppet::Error => detail raise adderrorcontext(detail) rescue => detail error = Puppet::ParseError.new(detail.to_s, nil, nil, detail) # We can't use self.fail here because it always expects strings, # not exceptions. raise adderrorcontext(error, detail) end end # Initialize the object. Requires a hash as the argument, and # takes each of the parameters of the hash and calls the settor # method for them. This is probably pretty inefficient and should # likely be changed at some point. def initialize(args) set_options(args) end # evaluate ourselves, and match def evaluate_match(value, scope) obj = self.safeevaluate(scope) obj = obj.downcase if obj.respond_to?(:downcase) value = value.downcase if value.respond_to?(:downcase) obj = Puppet::Parser::Scope.number?(obj) || obj value = Puppet::Parser::Scope.number?(value) || value # "" == undef for case/selector/if obj == value or (obj == "" and value == :undef) or (obj == :undef and value == "") end end # And include all of the AST subclasses. require 'puppet/parser/ast/arithmetic_operator' require 'puppet/parser/ast/astarray' require 'puppet/parser/ast/asthash' require 'puppet/parser/ast/boolean_operator' require 'puppet/parser/ast/branch' require 'puppet/parser/ast/caseopt' require 'puppet/parser/ast/casestatement' require 'puppet/parser/ast/collection' require 'puppet/parser/ast/collexpr' require 'puppet/parser/ast/comparison_operator' require 'puppet/parser/ast/definition' require 'puppet/parser/ast/else' require 'puppet/parser/ast/function' require 'puppet/parser/ast/hostclass' require 'puppet/parser/ast/ifstatement' require 'puppet/parser/ast/in_operator' require 'puppet/parser/ast/lambda' require 'puppet/parser/ast/leaf' require 'puppet/parser/ast/match_operator' require 'puppet/parser/ast/method_call' require 'puppet/parser/ast/minus' require 'puppet/parser/ast/node' require 'puppet/parser/ast/nop' require 'puppet/parser/ast/not' require 'puppet/parser/ast/relationship' require 'puppet/parser/ast/resource' require 'puppet/parser/ast/resource_defaults' require 'puppet/parser/ast/resource_instance' require 'puppet/parser/ast/resource_override' require 'puppet/parser/ast/resource_reference' require 'puppet/parser/ast/resourceparam' require 'puppet/parser/ast/selector' require 'puppet/parser/ast/tag' require 'puppet/parser/ast/vardef' require 'puppet/parser/code_merger' diff --git a/lib/puppet/parser/ast/block_expression.rb b/lib/puppet/parser/ast/block_expression.rb index ac6173c39..b6b7e66b4 100644 --- a/lib/puppet/parser/ast/block_expression.rb +++ b/lib/puppet/parser/ast/block_expression.rb @@ -1,45 +1,40 @@ require 'puppet/parser/ast/branch' class Puppet::Parser::AST class BlockExpression < Branch include Enumerable # Evaluate contained expressions, produce result of the last def evaluate(scope) result = nil @children.each do |child| - # Skip things that respond to :instantiate (classes, nodes, - # and definitions), because they have already been - # instantiated. - if !child.respond_to?(:instantiate) - result = child.safeevaluate(scope) - end + result = child.safeevaluate(scope) end result end # Return a child by index. def [](index) @children[index] end def push(*ary) ary.each { |child| #Puppet.debug "adding %s(%s) of type %s to %s" % # [child, child.object_id, child.class.to_s.sub(/.+::/,''), # self.object_id] @children.push(child) } self end def sequence_with(other) Puppet::Parser::AST::BlockExpression.new(:children => self.children + other.children) end def to_s "[" + @children.collect { |c| c.to_s }.join(', ') + "]" end end end diff --git a/lib/puppet/parser/code_merger.rb b/lib/puppet/parser/code_merger.rb index 62d72dd05..e8913b88b 100644 --- a/lib/puppet/parser/code_merger.rb +++ b/lib/puppet/parser/code_merger.rb @@ -1,13 +1,13 @@ class Puppet::Parser::CodeMerger # Concatenates the logic in the array of parse results into one parse result # @return Puppet::Parser::AST::BlockExpression # def concatenate(parse_results) children = parse_results.select {|x| !x.nil? && x.code}.reduce([]) do |memo, parsed_class| memo + parsed_class.code.children end Puppet::Parser::AST::BlockExpression.new(:children => children) end -end \ No newline at end of file +end diff --git a/lib/puppet/parser/parser_factory.rb b/lib/puppet/parser/parser_factory.rb index d4239afe6..576bc8755 100644 --- a/lib/puppet/parser/parser_factory.rb +++ b/lib/puppet/parser/parser_factory.rb @@ -1,89 +1,88 @@ module Puppet; end module Puppet::Parser # The ParserFactory makes selection of parser possible. # Currently, it is possible to switch between two different parsers: # * classic_parser, the parser in 3.1 # * eparser, the Expression Based Parser # class ParserFactory # Produces a parser instance for the given environment def self.parser(environment) case Puppet[:parser] when 'future' if Puppet[:evaluator] == 'future' evaluating_parser(environment) else eparser(environment) end else classic_parser(environment) end end # Creates an instance of the classic parser. # def self.classic_parser(environment) require 'puppet/parser' Puppet::Parser::Parser.new(environment) end # Returns an instance of an EvaluatingParser def self.evaluating_parser(file_watcher) # Since RGen is optional, test that it is installed @@asserted ||= false assert_rgen_installed() unless @@asserted @@asserted = true require 'puppet/parser/e4_parser_adapter' require 'puppet/pops/parser/code_merger' E4ParserAdapter.new(file_watcher) end # Creates an instance of the expression based parser 'eparser' # def self.eparser(environment) # Since RGen is optional, test that it is installed @@asserted ||= false assert_rgen_installed() unless @@asserted @@asserted = true require 'puppet/parser' require 'puppet/parser/e_parser_adapter' EParserAdapter.new(Puppet::Parser::Parser.new(environment)) end private def self.assert_rgen_installed begin require 'rgen/metamodel_builder' rescue LoadError raise Puppet::DevError.new("The gem 'rgen' version >= 0.6.1 is required when using the setting '--parser future'. Please install 'rgen'.") end # Since RGen is optional, there is nothing specifying its version. # It is not installed in any controlled way, so not possible to use gems to check (it may be installed some other way). # Instead check that "eContainer, and eContainingFeature" has been installed. require 'puppet/pops' begin litstring = Puppet::Pops::Model::LiteralString.new(); container = Puppet::Pops::Model::ArithmeticExpression.new(); container.left_expr = litstring raise "no eContainer" if litstring.eContainer() != container raise "no eContainingFeature" if litstring.eContainingFeature() != :left_expr rescue raise Puppet::DevError.new("The gem 'rgen' version >= 0.6.1 is required when using '--parser future'. An older version is installed, please update.") end end def self.code_merger - case Puppet[:parser] - when 'future' + if Puppet[:parser] == 'future' && Puppet[:evaluator] == 'future' Puppet::Pops::Parser::CodeMerger.new else Puppet::Parser::CodeMerger.new end end end end diff --git a/lib/puppet/pops/parser/code_merger.rb b/lib/puppet/pops/parser/code_merger.rb index 8634b329d..79e1328cd 100644 --- a/lib/puppet/pops/parser/code_merger.rb +++ b/lib/puppet/pops/parser/code_merger.rb @@ -1,17 +1,17 @@ class Puppet::Pops::Parser::CodeMerger # Concatenates the logic in the array of parse results into one parse result. # @return Puppet::Parser::AST::BlockExpression # def concatenate(parse_results) # this is a bit brute force as the result is already 3x ast with wrapped 4x content # this could be combined in a more elegant way, but it is only used to process a handful of files # at the beginning of a puppet run. TODO: Revisit for Puppet 4x when there is no 3x ast at the top. # children = parse_results.select {|x| !x.nil? && x.code}.reduce([]) do |memo, parsed_class| memo << parsed_class.code end - main = Puppet::Parser::AST::BlockExpression.new(:children => children) + Puppet::Parser::AST::BlockExpression.new(:children => children) end -end \ No newline at end of file +end diff --git a/spec/fixtures/integration/node/environment/sitedir/01_b.pp b/spec/fixtures/integration/node/environment/sitedir/01_b.pp index 4dce5eb83..25e339b4c 100644 --- a/spec/fixtures/integration/node/environment/sitedir/01_b.pp +++ b/spec/fixtures/integration/node/environment/sitedir/01_b.pp @@ -1,2 +1,6 @@ class b {} -$b = $a # error if $a not set in strict mode \ No newline at end of file + +# if the files are evaluated in the wrong order, the file 'b' has a reference +# to $a (set in file 'a') and with strict variable lookup should raise an error +# and fail this test. +$b = $a # error if $a not set in strict mode diff --git a/spec/fixtures/integration/node/environment/sitedir/04_include.pp b/spec/fixtures/integration/node/environment/sitedir/04_include.pp new file mode 100644 index 000000000..293067a7f --- /dev/null +++ b/spec/fixtures/integration/node/environment/sitedir/04_include.pp @@ -0,0 +1,2 @@ +include a, b +notify { "variables": message => "a: $a, b: $b" } diff --git a/spec/integration/node/environment_spec.rb b/spec/integration/node/environment_spec.rb index 105c70944..d193367ee 100755 --- a/spec/integration/node/environment_spec.rb +++ b/spec/integration/node/environment_spec.rb @@ -1,106 +1,109 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet_spec/files' require 'puppet_spec/scope' +require 'matchers/resource' describe Puppet::Node::Environment do include PuppetSpec::Files + include Matchers::Resource def a_module_in(name, dir) Dir.mkdir(dir) moddir = File.join(dir, name) Dir.mkdir(moddir) moddir end it "should be able to return each module from its environment with the environment, name, and path set correctly" do base = tmpfile("env_modules") Dir.mkdir(base) dirs = [] mods = {} %w{1 2}.each do |num| dir = File.join(base, "dir#{num}") dirs << dir mods["mod#{num}"] = a_module_in("mod#{num}", dir) end environment = Puppet::Node::Environment.create(:foo, dirs, '') environment.modules.each do |mod| mod.environment.should == environment mod.path.should == mods[mod.name] end end it "should not yield the same module from different module paths" do base = tmpfile("env_modules") Dir.mkdir(base) dirs = [] %w{1 2}.each do |num| dir = File.join(base, "dir#{num}") dirs << dir a_module_in("mod", dir) end environment = Puppet::Node::Environment.create(:foo, dirs, '') mods = environment.modules mods.length.should == 1 mods[0].path.should == File.join(base, "dir1", "mod") end - shared_examples_for "the environment's initial import" do - include PuppetSpec::Scope - - let(:node) { Puppet::Node.new('testnode') } - + shared_examples_for "the environment's initial import" do |settings| it "a manifest referring to a directory invokes parsing of all its files in sorted order" do - # fixture has three files 00_a.pp, 01_b.pp, and 02_c.pp. The 'b' file depends on 'a' - # being evaluated first. The 'c' file is empty (to ensure empty things do not break the directory import). - # if the files are evaluated in the wrong order, the file 'b' has a reference to $a (set in file 'a') - # and with strict variable lookup should raise an error and fail this test. + settings.each do |name, value| + Puppet[name] = value + end + + # fixture has three files 00_a.pp, 01_b.pp, and 02_c.pp. The 'b' file + # depends on 'a' being evaluated first. The 'c' file is empty (to ensure + # empty things do not break the directory import). # dirname = my_fixture('sitedir') - # Set the manifest to the directory to make it parse and combine them when compiling - Puppet[:manifest] = dirname - # include the classes that are in the fixture files - node.stubs(:classes).returns(['a', 'b']) + # Set the manifest to the directory to make it parse and combine them when compiling + node = Puppet::Node.new('testnode', + :environment => Puppet::Node::Environment.create(:testing, [], dirname)) - # compile, to make the initial_import in the environment take place the correct way catalog = Puppet::Parser::Compiler.compile(node) - class_a = catalog.resource('Class[a]') - class_b = catalog.resource('Class[b]') - expect(class_a).to_not be_nil - expect(class_b).to_not be_nil + + expect(catalog).to have_resource('Class[a]') + expect(catalog).to have_resource('Class[b]') + expect(catalog).to have_resource('Notify[variables]').with_parameter(:message, "a: 10, b: 10") end end describe 'using classic parser' do - before :each do - Puppet[:parser] = 'current' - # fixture uses variables that are set in a particular order (this ensures that files are parsed - # and combined in the right order or an error will be raised if 'b' is evaluated before 'a'). - Puppet[:strict_variables] = true - end - it_behaves_like "the environment's initial import" do - end + it_behaves_like "the environment's initial import", + :parser => 'current', + # fixture uses variables that are set in a particular order (this ensures + # that files are parsed and combined in the right order or an error will + # be raised if 'b' is evaluated before 'a'). + :strict_variables => true end + describe 'using future parser' do - before :each do - Puppet[:parser] = 'future' - # Turned off because currently future parser turns on the binder which causes lookup of facts - # that are uninitialized and it will fail with errors for 'osfamily' etc. - # This can be turned back on when the binder is taken out of the equation. - # Puppet[:strict_variables] = true - end - it_behaves_like "the environment's initial import" do + it_behaves_like "the environment's initial import", + :parser => 'future', + :evaluator => 'future', + # Turned off because currently future parser turns on the binder which + # causes lookup of facts that are uninitialized and it will fail with + # errors for 'osfamily' etc. This can be turned back on when the binder + # is taken out of the equation. + :strict_variables => false + + context 'and evaluator current' do + it_behaves_like "the environment's initial import", + :parser => 'future', + :evaluator => 'current', + :strict_variables => false end -end - + end end diff --git a/spec/lib/matchers/resource.rb b/spec/lib/matchers/resource.rb new file mode 100644 index 000000000..3964a3e9e --- /dev/null +++ b/spec/lib/matchers/resource.rb @@ -0,0 +1,35 @@ +module Matchers; module Resource + extend RSpec::Matchers::DSL + + matcher :have_resource do |expected_resource| + @params = {} + + match do |actual_catalog| + @mismatch = "" + if resource = actual_catalog.resource(expected_resource) + matched = true + failures = [] + @params.each do |name, value| + if resource[name] != value + matched = false + failures << "expected #{name} to be '#{value}' but was '#{resource[name]}'" + end + end + @mismatch = failures.join("\n") + + matched + else + @mismatch = "expected #{@actual.to_dot} to include #{@expected[0]}" + false + end + end + + chain :with_parameter do |name, value| + @params[name] = value + end + + def failure_message_for_should + @mismatch + end + end +end; end diff --git a/spec/unit/transaction/additional_resource_generator_spec.rb b/spec/unit/transaction/additional_resource_generator_spec.rb index 6983ba354..27dc3cbeb 100644 --- a/spec/unit/transaction/additional_resource_generator_spec.rb +++ b/spec/unit/transaction/additional_resource_generator_spec.rb @@ -1,419 +1,411 @@ require 'spec_helper' require 'puppet/transaction' require 'puppet_spec/compiler' require 'matchers/relationship_graph_matchers' require 'matchers/include_in_order' +require 'matchers/resource' describe Puppet::Transaction::AdditionalResourceGenerator do include PuppetSpec::Compiler include PuppetSpec::Files include RelationshipGraphMatchers + include Matchers::Resource let(:prioritizer) { Puppet::Graph::SequentialPrioritizer.new } def find_vertex(graph, type, title) graph.vertices.find {|v| v.type == type and v.title == title} end Puppet::Type.newtype(:generator) do include PuppetSpec::Compiler newparam(:name) do isnamevar end newparam(:kind) do defaultto :eval_generate newvalues(:eval_generate, :generate) end newparam(:code) def respond_to?(method_name) method_name == self[:kind] || super end def eval_generate eval_code end def generate eval_code end def eval_code if self[:code] compile_to_ral(self[:code]).resources.select { |r| r.ref =~ /Notify/ } else [] end end end context "when applying eval_generate" do it "should add the generated resources to the catalog" do catalog = compile_to_ral(<<-MANIFEST) generator { thing: code => 'notify { hello: }' } MANIFEST eval_generate_resources_in(catalog, relationship_graph_for(catalog), 'Generator[thing]') expect(catalog).to have_resource('Notify[hello]') end it "should add a sentinel whit for the resource" do graph = relationships_after_eval_generating(<<-MANIFEST, 'Generator[thing]') generator { thing: code => 'notify { hello: }' } MANIFEST find_vertex(graph, :whit, "completed_thing").must be_a(Puppet::Type.type(:whit)) end it "should replace dependencies on the resource with dependencies on the sentinel" do graph = relationships_after_eval_generating(<<-MANIFEST, 'Generator[thing]') generator { thing: code => 'notify { hello: }' } notify { last: require => Generator['thing'] } MANIFEST expect(graph).to enforce_order_with_edge( 'Whit[completed_thing]', 'Notify[last]') end it "should add an edge from the nearest ancestor to the generated resource" do graph = relationships_after_eval_generating(<<-MANIFEST, 'Generator[thing]') generator { thing: code => 'notify { hello: } notify { goodbye: }' } MANIFEST expect(graph).to enforce_order_with_edge( 'Generator[thing]', 'Notify[hello]') expect(graph).to enforce_order_with_edge( 'Generator[thing]', 'Notify[goodbye]') end it "should add an edge from each generated resource to the sentinel" do graph = relationships_after_eval_generating(<<-MANIFEST, 'Generator[thing]') generator { thing: code => 'notify { hello: } notify { goodbye: }' } MANIFEST expect(graph).to enforce_order_with_edge( 'Notify[hello]', 'Whit[completed_thing]') expect(graph).to enforce_order_with_edge( 'Notify[goodbye]', 'Whit[completed_thing]') end it "should add an edge from the resource to the sentinel" do graph = relationships_after_eval_generating(<<-MANIFEST, 'Generator[thing]') generator { thing: code => 'notify { hello: }' } MANIFEST expect(graph).to enforce_order_with_edge( 'Generator[thing]', 'Whit[completed_thing]') end it "should contain the generated resources in the same container as the generator" do catalog = compile_to_ral(<<-MANIFEST) class container { generator { thing: code => 'notify { hello: }' } } include container MANIFEST eval_generate_resources_in(catalog, relationship_graph_for(catalog), 'Generator[thing]') expect(catalog).to contain_resources_equally('Generator[thing]', 'Notify[hello]') end it "should return false if an error occurred when generating resources" do catalog = compile_to_ral(<<-MANIFEST) generator { thing: code => 'fail("not a good generation")' } MANIFEST generator = Puppet::Transaction::AdditionalResourceGenerator.new(catalog, relationship_graph_for(catalog), prioritizer) expect(generator.eval_generate(catalog.resource('Generator[thing]'))). to eq(false) end it "should return true if resources were generated" do catalog = compile_to_ral(<<-MANIFEST) generator { thing: code => 'notify { hello: }' } MANIFEST generator = Puppet::Transaction::AdditionalResourceGenerator.new(catalog, relationship_graph_for(catalog), prioritizer) expect(generator.eval_generate(catalog.resource('Generator[thing]'))). to eq(true) end it "should not add a sentinel if no resources are generated" do catalog = compile_to_ral(<<-MANIFEST) generator { thing: } MANIFEST relationship_graph = relationship_graph_for(catalog) generator = Puppet::Transaction::AdditionalResourceGenerator.new(catalog, relationship_graph, prioritizer) expect(generator.eval_generate(catalog.resource('Generator[thing]'))). to eq(false) expect(find_vertex(relationship_graph, :whit, "completed_thing")).to be_nil end it "orders generated resources with the generator" do graph = relationships_after_eval_generating(<<-MANIFEST, 'Generator[thing]') notify { before: } generator { thing: code => 'notify { hello: }' } notify { after: } MANIFEST expect(order_resources_traversed_in(graph)).to( include_in_order("Notify[before]", "Generator[thing]", "Notify[hello]", "Notify[after]")) end it "orders the generator in manifest order with dependencies" do graph = relationships_after_eval_generating(<<-MANIFEST, 'Generator[thing]') notify { before: } generator { thing: code => 'notify { hello: } notify { goodbye: }' } notify { third: require => Generator['thing'] } notify { after: } MANIFEST expect(order_resources_traversed_in(graph)).to( include_in_order("Notify[before]", "Generator[thing]", "Notify[hello]", "Notify[goodbye]", "Notify[third]", "Notify[after]")) end it "duplicate generated resources are made dependent on the generator" do graph = relationships_after_eval_generating(<<-MANIFEST, 'Generator[thing]') notify { before: } notify { hello: } generator { thing: code => 'notify { before: }' } notify { third: require => Generator['thing'] } notify { after: } MANIFEST expect(order_resources_traversed_in(graph)).to( include_in_order("Notify[hello]", "Generator[thing]", "Notify[before]", "Notify[third]", "Notify[after]")) end it "preserves dependencies on duplicate generated resources" do graph = relationships_after_eval_generating(<<-MANIFEST, 'Generator[thing]') notify { before: } generator { thing: code => 'notify { hello: } notify { before: }', require => 'Notify[before]' } notify { third: require => Generator['thing'] } notify { after: } MANIFEST expect(order_resources_traversed_in(graph)).to( include_in_order("Notify[before]", "Generator[thing]", "Notify[hello]", "Notify[third]", "Notify[after]")) end def relationships_after_eval_generating(manifest, resource_to_generate) catalog = compile_to_ral(manifest) relationship_graph = relationship_graph_for(catalog) eval_generate_resources_in(catalog, relationship_graph, resource_to_generate) relationship_graph end def eval_generate_resources_in(catalog, relationship_graph, resource_to_generate) generator = Puppet::Transaction::AdditionalResourceGenerator.new(catalog, relationship_graph, prioritizer) generator.eval_generate(catalog.resource(resource_to_generate)) end end context "when applying generate" do it "should add the generated resources to the catalog" do catalog = compile_to_ral(<<-MANIFEST) generator { thing: kind => generate, code => 'notify { hello: }' } MANIFEST generate_resources_in(catalog, relationship_graph_for(catalog), 'Generator[thing]') expect(catalog).to have_resource('Notify[hello]') end it "should contain the generated resources in the same container as the generator" do catalog = compile_to_ral(<<-MANIFEST) class container { generator { thing: kind => generate, code => 'notify { hello: }' } } include container MANIFEST generate_resources_in(catalog, relationship_graph_for(catalog), 'Generator[thing]') expect(catalog).to contain_resources_equally('Generator[thing]', 'Notify[hello]') end it "should add an edge from the nearest ancestor to the generated resource" do graph = relationships_after_generating(<<-MANIFEST, 'Generator[thing]') generator { thing: kind => generate, code => 'notify { hello: } notify { goodbye: }' } MANIFEST expect(graph).to enforce_order_with_edge( 'Generator[thing]', 'Notify[hello]') expect(graph).to enforce_order_with_edge( 'Generator[thing]', 'Notify[goodbye]') end it "orders generated resources with the generator" do graph = relationships_after_generating(<<-MANIFEST, 'Generator[thing]') notify { before: } generator { thing: kind => generate, code => 'notify { hello: }' } notify { after: } MANIFEST expect(order_resources_traversed_in(graph)).to( include_in_order("Notify[before]", "Generator[thing]", "Notify[hello]", "Notify[after]")) end it "duplicate generated resources are made dependent on the generator" do graph = relationships_after_generating(<<-MANIFEST, 'Generator[thing]') notify { before: } notify { hello: } generator { thing: kind => generate, code => 'notify { before: }' } notify { third: require => Generator['thing'] } notify { after: } MANIFEST expect(order_resources_traversed_in(graph)).to( include_in_order("Notify[hello]", "Generator[thing]", "Notify[before]", "Notify[third]", "Notify[after]")) end it "preserves dependencies on duplicate generated resources" do graph = relationships_after_generating(<<-MANIFEST, 'Generator[thing]') notify { before: } generator { thing: kind => generate, code => 'notify { hello: } notify { before: }', require => 'Notify[before]' } notify { third: require => Generator['thing'] } notify { after: } MANIFEST expect(order_resources_traversed_in(graph)).to( include_in_order("Notify[before]", "Generator[thing]", "Notify[hello]", "Notify[third]", "Notify[after]")) end it "orders the generator in manifest order with dependencies" do graph = relationships_after_generating(<<-MANIFEST, 'Generator[thing]') notify { before: } generator { thing: kind => generate, code => 'notify { hello: } notify { goodbye: }' } notify { third: require => Generator['thing'] } notify { after: } MANIFEST expect(order_resources_traversed_in(graph)).to( include_in_order("Notify[before]", "Generator[thing]", "Notify[hello]", "Notify[goodbye]", "Notify[third]", "Notify[after]")) end def relationships_after_generating(manifest, resource_to_generate) catalog = compile_to_ral(manifest) relationship_graph = relationship_graph_for(catalog) generate_resources_in(catalog, relationship_graph, resource_to_generate) relationship_graph end def generate_resources_in(catalog, relationship_graph, resource_to_generate) generator = Puppet::Transaction::AdditionalResourceGenerator.new(catalog, relationship_graph, prioritizer) generator.generate_additional_resources(catalog.resource(resource_to_generate)) end end def relationship_graph_for(catalog) relationship_graph = Puppet::Graph::RelationshipGraph.new(prioritizer) relationship_graph.populate_from(catalog) relationship_graph end def order_resources_traversed_in(relationships) order_seen = [] relationships.traverse { |resource| order_seen << resource.ref } order_seen end RSpec::Matchers.define :contain_resources_equally do |*resource_refs| match do |catalog| @containers = resource_refs.collect do |resource_ref| catalog.container_of(catalog.resource(resource_ref)).ref end @containers.all? { |resource_ref| resource_ref == @containers[0] } end def failure_message_for_should "expected #{@expected.join(', ')} to all be contained in the same resource but the containment was #{@expected.zip(@containers).collect { |(res, container)| res + ' => ' + container }.join(', ')}" end end end - -RSpec::Matchers.define :have_resource do |expected_resource| - match do |actual_catalog| - actual_catalog.resource(expected_resource) - end - - def failure_message_for_should - "expected #{@actual.to_dot} to include #{@expected[0]}" - end -end