diff options
Diffstat (limited to 'spec/unit')
-rwxr-xr-x | spec/unit/parser/compile.rb | 755 |
1 files changed, 755 insertions, 0 deletions
diff --git a/spec/unit/parser/compile.rb b/spec/unit/parser/compile.rb new file mode 100755 index 000000000..409d4ca1d --- /dev/null +++ b/spec/unit/parser/compile.rb @@ -0,0 +1,755 @@ +#!/usr/bin/env ruby + +$:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/ + +require 'mocha' +require 'puppettest' +require 'puppettest/parsertesting' +require 'puppet/parser/configuration' + +# Test our configuration object. +class TestConfiguration < Test::Unit::TestCase + include PuppetTest + include PuppetTest::ParserTesting + + Config = Puppet::Parser::Configuration + Scope = Puppet::Parser::Scope + Node = Puppet::Network::Handler.handler(:node) + SimpleNode = Puppet::Node + + def mknode(name = "foo") + @node = SimpleNode.new(name) + end + + def mkparser + # This should mock an interpreter + @parser = mock 'parser' + end + + def mkconfig(options = {}) + if node = options[:node] + options.delete(:node) + else + node = mknode + end + @config = Config.new(node, mkparser, options) + end + + def test_initialize + config = nil + assert_nothing_raised("Could not init config with all required options") do + config = Config.new("foo", "parser") + end + + assert_equal("foo", config.node, "Did not set node correctly") + assert_equal("parser", config.parser, "Did not set parser correctly") + + # We're not testing here whether we call initvars, because it's too difficult to + # mock. + + # Now try it with some options + assert_nothing_raised("Could not init config with extra options") do + config = Config.new("foo", "parser", :ast_nodes => false) + end + + assert_equal(false, config.ast_nodes?, "Did not set ast_nodes? correctly") + end + + def test_initvars + config = mkconfig + [:class_scopes, :resource_table, :exported_resources, :resource_overrides].each do |table| + assert_instance_of(Hash, config.send(:instance_variable_get, "@#{table}"), "Did not set %s table correctly" % table) + end + assert_instance_of(Scope, config.topscope, "Did not create a topscope") + graph = config.instance_variable_get("@scope_graph") + assert_instance_of(GRATR::Digraph, graph, "Did not create scope graph") + assert(graph.vertex?(config.topscope), "Did not add top scope as a vertex in the graph") + end + + # Make sure we store and can retrieve references to classes and their scopes. + def test_class_set_and_class_scope + klass = mock 'ast_class' + klass.expects(:classname).returns("myname") + + config = mkconfig + config.expects(:tag).with("myname") + + assert_nothing_raised("Could not set class") do + config.class_set "myname", "myscope" + end + # First try to retrieve it by name. + assert_equal("myscope", config.class_scope("myname"), "Could not retrieve class scope by name") + + # Then by object + assert_equal("myscope", config.class_scope(klass), "Could not retrieve class scope by object") + end + + def test_classlist + config = mkconfig + + config.class_set "", "empty" + config.class_set "one", "yep" + config.class_set "two", "nope" + + # Make sure our class list is correct + assert_equal(%w{one two}.sort, config.classlist.sort, "Did not get correct class list") + end + + # Make sure collections get added to our internal array + def test_add_collection + config = mkconfig + assert_nothing_raised("Could not add collection") do + config.add_collection "nope" + end + assert_equal(%w{nope}, config.instance_variable_get("@collections"), "Did not add collection") + end + + # Make sure we create a graph of scopes. + def test_newscope + config = mkconfig + graph = config.instance_variable_get("@scope_graph") + assert_instance_of(Scope, config.topscope, "Did not create top scope") + assert_instance_of(GRATR::Digraph, graph, "Did not create graph") + + assert(graph.vertex?(config.topscope), "The top scope is not a vertex in the graph") + + # Now that we've got the top scope, create a new, subscope + subscope = nil + assert_nothing_raised("Could not create subscope") do + subscope = config.newscope(config.topscope) + end + assert_instance_of(Scope, subscope, "Did not create subscope") + assert(graph.edge?(config.topscope, subscope), "An edge between top scope and subscope was not added") + + # Make sure a scope can find its parent. + assert(config.parent(subscope), "Could not look up parent scope on configuration") + assert_equal(config.topscope.object_id, config.parent(subscope).object_id, "Did not get correct parent scope from configuration") + assert_equal(config.topscope.object_id, subscope.parent.object_id, "Scope did not correctly retrieve its parent scope") + + # Now create another, this time specifying options + another = nil + assert_nothing_raised("Could not create subscope") do + another = config.newscope(subscope, :name => "testing") + end + assert_equal("testing", another.name, "did not set scope option correctly") + assert_instance_of(Scope, another, "Did not create second subscope") + assert(graph.edge?(subscope, another), "An edge between parent scope and second subscope was not added") + + # Make sure it can find its parent. + assert(config.parent(another), "Could not look up parent scope of second subscope on configuration") + assert_equal(subscope.object_id, config.parent(another).object_id, "Did not get correct parent scope of second subscope from configuration") + assert_equal(subscope.object_id, another.parent.object_id, "Second subscope did not correctly retrieve its parent scope") + + # And make sure both scopes show up in the right order in the search path + assert_equal([another.object_id, subscope.object_id, config.topscope.object_id], another.scope_path.collect { |p| p.object_id }, + "Did not get correct scope path") + end + + # The heart of the action. + def test_compile + config = mkconfig + [:set_node_parameters, :evaluate_main, :evaluate_ast_node, :evaluate_classes, :evaluate_generators, :fail_on_unevaluated, :finish].each do |method| + config.expects(method) + end + config.expects(:extract).returns(:config) + assert_equal(:config, config.compile, "Did not return the results of the extraction") + end + + # Test setting the node's parameters into the top scope. + def test_set_node_parameters + config = mkconfig + @node.parameters = {"a" => "b", "c" => "d"} + scope = config.topscope + @node.parameters.each do |param, value| + scope.expects(:setvar).with(param, value) + end + + assert_nothing_raised("Could not call 'set_node_parameters'") do + config.send(:set_node_parameters) + end + end + + # Test that we can evaluate the main class, which is the one named "" in namespace + # "". + def test_evaluate_main + config = mkconfig + main = mock 'main_class' + config.topscope.expects(:source=).with(main) + main.expects(:safeevaluate).with(:scope => config.topscope, :nosubscope => true) + @parser.expects(:findclass).with("", "").returns(main) + + assert_nothing_raised("Could not call evaluate_main") do + config.send(:evaluate_main) + end + end + + # Make sure we either don't look for nodes, or that we find and evaluate the right object. + def test_evaluate_ast_node + # First try it with ast_nodes disabled + config = mkconfig :ast_nodes => false + config.expects(:ast_nodes?).returns(false) + config.parser.expects(:nodes).never + + assert_nothing_raised("Could not call evaluate_ast_node when ast nodes are disabled") do + config.send(:evaluate_ast_node) + end + + # Now try it with them enabled, but no node found. + nodes = mock 'node_hash' + config = mkconfig :ast_nodes => true + config.expects(:ast_nodes?).returns(true) + config.parser.expects(:nodes).returns(nodes).times(4) + + # Set some names for our test + @node.names = %w{a b c} + nodes.expects(:[]).with("a").returns(nil) + nodes.expects(:[]).with("b").returns(nil) + nodes.expects(:[]).with("c").returns(nil) + + # It should check this last, of course. + nodes.expects(:[]).with("default").returns(nil) + + # And make sure the lack of a node throws an exception + assert_raise(Puppet::ParseError, "Did not fail when we couldn't find an ast node") do + config.send(:evaluate_ast_node) + end + + # Finally, make sure it works dandily when we have a node + nodes = mock 'hash' + config = mkconfig :ast_nodes => true + config.expects(:ast_nodes?).returns(true) + config.parser.expects(:nodes).returns(nodes).times(3) + + node = mock 'node' + node.expects(:safeevaluate).with(:scope => config.topscope) + # Set some names for our test + @node.names = %w{a b c} + nodes.expects(:[]).with("a").returns(nil) + nodes.expects(:[]).with("b").returns(nil) + nodes.expects(:[]).with("c").returns(node) + nodes.expects(:[]).with("default").never + + # And make sure the lack of a node throws an exception + assert_nothing_raised("Failed when a node was found") do + config.send(:evaluate_ast_node) + end + + # Lastly, check when we actually find the default. + nodes = mock 'hash' + config = mkconfig :ast_nodes => true + config.expects(:ast_nodes?).returns(true) + config.parser.expects(:nodes).returns(nodes).times(4) + + node = mock 'node' + node.expects(:safeevaluate).with(:scope => config.topscope) + # Set some names for our test + @node.names = %w{a b c} + nodes.expects(:[]).with("a").returns(nil) + nodes.expects(:[]).with("b").returns(nil) + nodes.expects(:[]).with("c").returns(nil) + nodes.expects(:[]).with("default").returns(node) + + # And make sure the lack of a node throws an exception + assert_nothing_raised("Failed when a node was found") do + config.send(:evaluate_ast_node) + end + end + + # Make sure our config object handles tags appropriately. + def test_tags + config = mkconfig + config.send(:tag, "one") + assert_equal(%w{one}, config.send(:tags), "Did not add tag") + + config.send(:tag, "two", "three") + assert_equal(%w{one two three}, config.send(:tags), "Did not add new tags") + + config.send(:tag, "two") + assert_equal(%w{one two three}, config.send(:tags), "Allowed duplicate tag") + end + + def test_evaluate_classes + config = mkconfig + classes = { + "one" => mock('class one'), + "three" => mock('class three') + } + + classes.each do |name, obj| + config.parser.expects(:findclass).with("", name).returns(obj) + obj.expects(:safeevaluate).with(:scope => config.topscope) + end + %w{two four}.each do |name| + config.parser.expects(:findclass).with("", name).returns(nil) + end + + config.expects(:tag).with("two") + config.expects(:tag).with("four") + + @node.classes = %w{one two three four} + result = nil + assert_nothing_raised("could not call evaluate_classes") do + result = config.send(:evaluate_classes) + end + assert_equal(%w{one three}, result, "Did not return the list of evaluated classes") + end + + def test_evaluate_collections + config = mkconfig + + colls = [] + + # Make sure we return false when there's nothing there. + assert(! config.send(:evaluate_collections), "Returned true when there were no collections") + + # And when the collections fail to evaluate. + colls << mock("coll1-false") + colls << mock("coll2-false") + colls.each { |c| c.expects(:evaluate).returns(false) } + + config.instance_variable_set("@collections", colls) + assert(! config.send(:evaluate_collections), "Returned true when collections both evaluated nothing") + + # Now have one of the colls evaluate + colls.clear + colls << mock("coll1-one-true") + colls << mock("coll2-one-true") + colls[0].expects(:evaluate).returns(true) + colls[1].expects(:evaluate).returns(false) + assert(config.send(:evaluate_collections), "Did not return true when one collection evaluated true") + + # And have them both eval true + colls.clear + colls << mock("coll1-both-true") + colls << mock("coll2-both-true") + colls[0].expects(:evaluate).returns(true) + colls[1].expects(:evaluate).returns(true) + assert(config.send(:evaluate_collections), "Did not return true when both collections evaluated true") + end + + def test_unevaluated_resources + config = mkconfig + resources = {} + config.instance_variable_set("@resource_table", resources) + + # First test it when the table is empty + assert_nil(config.send(:unevaluated_resources), "Somehow found unevaluated resources in an empty table") + + # Then add a builtin resources + resources["one"] = mock("builtin only") + resources["one"].expects(:builtin?).returns(true) + assert_nil(config.send(:unevaluated_resources), "Considered a builtin resource unevaluated") + + # And do both builtin and non-builtin but already evaluated + resources.clear + resources["one"] = mock("builtin (with eval)") + resources["one"].expects(:builtin?).returns(true) + resources["two"] = mock("evaled (with builtin)") + resources["two"].expects(:builtin?).returns(false) + resources["two"].expects(:evaluated?).returns(true) + assert_nil(config.send(:unevaluated_resources), "Considered either a builtin or evaluated resource unevaluated") + + # Now a single unevaluated resource. + resources.clear + resources["one"] = mock("unevaluated") + resources["one"].expects(:builtin?).returns(false) + resources["one"].expects(:evaluated?).returns(false) + assert_equal([resources["one"]], config.send(:unevaluated_resources), "Did not find unevaluated resource") + + # With two uneval'ed resources, and an eval'ed one thrown in + resources.clear + resources["one"] = mock("unevaluated one") + resources["one"].expects(:builtin?).returns(false) + resources["one"].expects(:evaluated?).returns(false) + resources["two"] = mock("unevaluated two") + resources["two"].expects(:builtin?).returns(false) + resources["two"].expects(:evaluated?).returns(false) + resources["three"] = mock("evaluated") + resources["three"].expects(:builtin?).returns(false) + resources["three"].expects(:evaluated?).returns(true) + + result = config.send(:unevaluated_resources) + %w{one two}.each do |name| + assert(result.include?(resources[name]), "Did not find %s in the unevaluated list" % name) + end + end + + def test_evaluate_definitions + # First try the case where there's nothing to return + config = mkconfig + config.expects(:unevaluated_resources).returns(nil) + + assert_nothing_raised("Could not test for unevaluated resources") do + assert(! config.send(:evaluate_definitions), "evaluate_definitions returned true when no resources were evaluated") + end + + # Now try it with resources left to evaluate + resources = [] + res1 = mock("resource1") + res1.expects(:evaluate) + res2 = mock("resource2") + res2.expects(:evaluate) + resources << res1 << res2 + config = mkconfig + config.expects(:unevaluated_resources).returns(resources) + + assert_nothing_raised("Could not test for unevaluated resources") do + assert(config.send(:evaluate_definitions), "evaluate_definitions returned false when resources were evaluated") + end + end + + def test_evaluate_generators + # First try the case where we have nothing to do + config = mkconfig + config.expects(:evaluate_definitions).returns(false) + config.expects(:evaluate_collections).returns(false) + + assert_nothing_raised("Could not call :eval_iterate") do + config.send(:evaluate_generators) + end + + # FIXME I could not get this test to work, but the code is short + # enough that I'm ok with it. + # It's important that collections are evaluated before definitions, + # so make sure that's the case by verifying that collections get tested + # twice but definitions only once. + #config = mkconfig + #config.expects(:evaluate_collections).returns(true).returns(false) + #config.expects(:evaluate_definitions).returns(false) + #config.send(:eval_iterate) + end + + def test_store + config = mkconfig + Puppet.features.expects(:rails?).returns(true) + Puppet::Rails.expects(:connect) + + node = mock 'node' + resource_table = mock 'resources' + resource_table.expects(:values).returns(:resources) + config.instance_variable_set("@node", node) + config.instance_variable_set("@resource_table", resource_table) + config.expects(:store_to_active_record).with(node, :resources) + config.send(:store) + end + + def test_store_to_active_record + config = mkconfig + node = mock 'node' + node.expects(:name).returns("myname") + Puppet::Rails::Host.stubs(:transaction).yields + Puppet::Rails::Host.expects(:store).with(node, :resources) + config.send(:store_to_active_record, node, :resources) + end + + # Make sure that 'finish' gets called on all of our resources. + def test_finish + config = mkconfig + table = config.instance_variable_get("@resource_table") + + # Add a resource that does respond to :finish + yep = mock("finisher") + yep.expects(:respond_to?).with(:finish).returns(true) + yep.expects(:finish) + table["yep"] = yep + + # And one that does not + dnf = mock("dnf") + dnf.expects(:respond_to?).with(:finish).returns(false) + table["dnf"] = dnf + + config.send(:finish) + end + + def test_extract + config = mkconfig + config.expects(:extraction_format).returns(:whatever) + config.expects(:extract_to_whatever).returns(:result) + assert_equal(:result, config.send(:extract), "Did not return extraction result as the method result") + end + + # We want to make sure that the scope and resource graphs translate correctly + def test_extract_to_transportable_simple + # Start with a really simple graph -- one scope, one resource. + config = mkconfig + resources = config.instance_variable_get("@resource_graph") + scopes = config.instance_variable_get("@scope_graph") + + # Get rid of the topscope + scopes.vertices.each { |v| scopes.remove_vertex!(v) } + + bucket = [] + scope = mock("scope") + bucket.expects(:copy_type_and_name).with(scope) + scope.expects(:to_trans).returns(bucket) + scopes.add_vertex! scope + + # The topscope is the key to picking out the top of the graph. + config.instance_variable_set("@topscope", scope) + + resource = mock "resource" + resource.expects(:to_trans).returns(:resource) + resources.add_edge! scope, resource + + result = nil + assert_nothing_raised("Could not extract transportable configuration") do + result = config.send :extract_to_transportable + end + assert_equal([:resource], result, "Did not translate simple configuration correctly") + end + + def test_extract_to_transportable_complex + # Now try it with a more complicated graph -- a three tier graph, each tier + # having a scope and a resource. + config = mkconfig + resources = config.instance_variable_get("@resource_graph") + scopes = config.instance_variable_get("@scope_graph") + + # Get rid of the topscope + scopes.vertices.each { |v| scopes.remove_vertex!(v) } + + fakebucket = Class.new(Array) do + attr_accessor :name + def initialize(n) + @name = n + end + end + + # Create our scopes. + top = mock("top") + topbucket = fakebucket.new "top" + topbucket.expects(:copy_type_and_name).with(top) + top.stubs(:copy_type_and_name) + top.expects(:to_trans).returns(topbucket) + # The topscope is the key to picking out the top of the graph. + config.instance_variable_set("@topscope", top) + middle = mock("middle") + middle.expects(:to_trans).returns(fakebucket.new("middle")) + scopes.add_edge! top, middle + bottom = mock("bottom") + bottom.expects(:to_trans).returns(fakebucket.new("bottom")) + scopes.add_edge! middle, bottom + + topres = mock "topres" + topres.expects(:to_trans).returns(:topres) + resources.add_edge! top, topres + + midres = mock "midres" + midres.expects(:to_trans).returns(:midres) + resources.add_edge! middle, midres + + botres = mock "botres" + botres.expects(:to_trans).returns(:botres) + resources.add_edge! bottom, botres + + result = nil + assert_nothing_raised("Could not extract transportable configuration") do + result = config.send :extract_to_transportable + end + assert_equal([[[:botres], :midres], :topres], result, "Did not translate medium configuration correctly") + end + + def test_verify_uniqueness + config = mkconfig + + resources = config.instance_variable_get("@resource_table") + resource = mock("noconflict") + resource.expects(:ref).returns("File[yay]") + assert_nothing_raised("Raised an exception when there should have been no conflict") do + config.send(:verify_uniqueness, resource) + end + + # Now try the case where our type is isomorphic + resources["thing"] = true + + isoconflict = mock("isoconflict") + isoconflict.expects(:ref).returns("thing") + isoconflict.expects(:type).returns("testtype") + faketype = mock("faketype") + faketype.expects(:isomorphic?).returns(false) + faketype.expects(:name).returns("whatever") + Puppet::Type.expects(:type).with("testtype").returns(faketype) + assert_nothing_raised("Raised an exception when was a conflict in non-isomorphic types") do + config.send(:verify_uniqueness, isoconflict) + end + + # Now test for when we actually have an exception + initial = mock("initial") + resources["thing"] = initial + initial.expects(:file).returns(false) + + conflict = mock("conflict") + conflict.expects(:ref).returns("thing").times(2) + conflict.expects(:type).returns("conflict") + conflict.expects(:file).returns(false) + conflict.expects(:line).returns(false) + + faketype = mock("faketype") + faketype.expects(:isomorphic?).returns(true) + Puppet::Type.expects(:type).with("conflict").returns(faketype) + assert_raise(Puppet::ParseError, "Did not fail when two isomorphic resources conflicted") do + config.send(:verify_uniqueness, conflict) + end + end + + def test_store_resource + # Run once when there's no conflict + config = mkconfig + table = config.instance_variable_get("@resource_table") + resource = mock("resource") + resource.expects(:ref).returns("yay") + config.expects(:verify_uniqueness).with(resource) + scope = mock("scope") + + graph = config.instance_variable_get("@resource_graph") + graph.expects(:add_edge!).with(scope, resource) + + assert_nothing_raised("Could not store resource") do + config.store_resource(scope, resource) + end + assert_equal(resource, table["yay"], "Did not store resource in table") + + # Now for conflicts + config = mkconfig + table = config.instance_variable_get("@resource_table") + resource = mock("resource") + config.expects(:verify_uniqueness).with(resource).raises(ArgumentError) + + assert_raise(ArgumentError, "Did not raise uniqueness exception") do + config.store_resource(scope, resource) + end + assert(table.empty?, "Conflicting resource was stored in table") + end + + def test_fail_on_unevaluated + config = mkconfig + config.expects(:fail_on_unevaluated_overrides) + config.expects(:fail_on_unevaluated_resource_collections) + config.send :fail_on_unevaluated + end + + def test_store_override + # First test the case when the resource is not present. + config = mkconfig + overrides = config.instance_variable_get("@resource_overrides") + override = Object.new + override.expects(:ref).returns(:myref).times(2) + override.expects(:override=).with(true) + + assert_nothing_raised("Could not call store_override") do + config.store_override(override) + end + assert_instance_of(Array, overrides[:myref], "Overrides table is not a hash of arrays") + assert_equal(override, overrides[:myref][0], "Did not store override in appropriately named array") + + # And when the resource already exists. + resource = mock 'resource' + resources = config.instance_variable_get("@resource_table") + resources[:resref] = resource + + override = mock 'override' + resource.expects(:merge).with(override) + override.expects(:override=).with(true) + override.expects(:ref).returns(:resref) + assert_nothing_raised("Could not call store_override when the resource already exists.") do + config.store_override(override) + end + end + + def test_resource_overrides + config = mkconfig + overrides = config.instance_variable_get("@resource_overrides") + overrides[:test] = :yay + resource = mock 'resource' + resource.expects(:ref).returns(:test) + + assert_equal(:yay, config.resource_overrides(resource), "Did not return overrides from table") + end + + def test_fail_on_unevaluated_resource_collections + config = mkconfig + collections = config.instance_variable_get("@collections") + + # Make sure we're fine when the list is empty + assert_nothing_raised("Failed when no collections were present") do + config.send :fail_on_unevaluated_resource_collections + end + + # And that we're fine when we've got collections but with no resources + collections << mock('coll') + collections[0].expects(:resources).returns(nil) + assert_nothing_raised("Failed when no resource collections were present") do + config.send :fail_on_unevaluated_resource_collections + end + + # But that we do fail when we've got resource collections left. + collections.clear + + # return both an array and a string, because that's tested internally + collections << mock('coll returns one') + collections[0].expects(:resources).returns(:something) + + collections << mock('coll returns many') + collections[1].expects(:resources).returns([:one, :two]) + + assert_raise(Puppet::ParseError, "Did not fail on unevaluated resource collections") do + config.send :fail_on_unevaluated_resource_collections + end + end + + def test_fail_on_unevaluated_overrides + config = mkconfig + overrides = config.instance_variable_get("@resource_overrides") + + # Make sure we're fine when the list is empty + assert_nothing_raised("Failed when no collections were present") do + config.send :fail_on_unevaluated_overrides + end + + # But that we fail if there are any overrides left in the table. + overrides[:yay] = [] + overrides[:foo] = [] + overrides[:bar] = [mock("override")] + overrides[:bar][0].expects(:ref).returns("yay") + assert_raise(Puppet::ParseError, "Failed to fail when overrides remain") do + config.send :fail_on_unevaluated_overrides + end + end + + def test_find_resource + config = mkconfig + resources = config.instance_variable_get("@resource_table") + + assert_nothing_raised("Could not call findresource when the resource table was empty") do + assert_nil(config.findresource("yay", "foo"), "Returned a non-existent resource") + assert_nil(config.findresource("yay[foo]"), "Returned a non-existent resource") + end + + resources["Foo[bar]"] = :yay + assert_nothing_raised("Could not call findresource when the resource table was not empty") do + assert_equal(:yay, config.findresource("foo", "bar"), "Returned a non-existent resource") + assert_equal(:yay, config.findresource("Foo[bar]"), "Returned a non-existent resource") + end + end + + # #620 - Nodes and classes should conflict, else classes don't get evaluated + def test_nodes_and_classes_name_conflict + # Test node then class + config = mkconfig + node = stub :nodescope? => true + klass = stub :nodescope? => false + config.class_set("one", node) + assert_raise(Puppet::ParseError, "Did not fail when replacing node with class") do + config.class_set("one", klass) + end + + # and class then node + config = mkconfig + node = stub :nodescope? => true + klass = stub :nodescope? => false + config.class_set("two", klass) + assert_raise(Puppet::ParseError, "Did not fail when replacing node with class") do + config.class_set("two", node) + end + end +end |