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