diff --git a/lib/puppet/network/handler/configuration.rb b/lib/puppet/network/handler/configuration.rb index 1dbb16370..05c86f22e 100644 --- a/lib/puppet/network/handler/configuration.rb +++ b/lib/puppet/network/handler/configuration.rb @@ -1,218 +1,218 @@ require 'openssl' require 'puppet' require 'puppet/parser/interpreter' require 'puppet/sslcertificates' require 'xmlrpc/server' require 'yaml' class Puppet::Network::Handler class Configuration < Handler desc "Puppet's configuration compilation interface. Passed a node name or other key, retrieves information about the node (using the ``node_source``) and returns a compiled configuration." include Puppet::Util attr_accessor :local @interface = XMLRPC::Service::Interface.new("configuration") { |iface| iface.add_method("string configuration(string)") iface.add_method("string version()") } # Compile a node's configuration. def configuration(key, client = nil, clientip = nil) # If we want to use the cert name as our key if Puppet[:node_name] == 'cert' and client key = client end # Note that this is reasonable, because either their node source should actually # know about the node, or they should be using the ``none`` node source, which # will always return data. unless node = node_handler.details(key) raise Puppet::Error, "Could not find node '%s'" % key end # Add any external data to the node. add_node_data(node) - return translate(compile(node)) + configuration = compile(node) + + return translate(configuration) end def initialize(options = {}) if options[:Local] @local = options[:Local] else @local = false end # Just store the options, rather than creating the interpreter # immediately. Mostly, this is so we can create the interpreter # on-demand, which is easier for testing. @options = options set_server_facts end # Are we running locally, or are our clients networked? def local? self.local end # Return the configuration version. def version(client = nil, clientip = nil) if client and node = node_handler.details(client) update_node_check(node) return interpreter.configuration_version(node) else # Just return something that will always result in a recompile, because # this is local. return (Time.now + 1000).to_i end end private # Add any extra data necessary to the node. def add_node_data(node) # Merge in our server-side facts, so they can be used during compilation. node.fact_merge(@server_facts) # Add any specified classes to the node's class list. if classes = @options[:Classes] classes.each do |klass| node.classes << klass end end end # Compile the actual configuration. def compile(node) # Pick the benchmark level. if local? level = :none else level = :notice end # Ask the interpreter to compile the configuration. str = "Compiled configuration for %s" % node.name if node.environment str += " in environment %s" % node.environment end config = nil benchmark(level, "Compiled configuration for %s" % node.name) do begin config = interpreter.compile(node) rescue Puppet::Error => detail if Puppet[:trace] puts detail.backtrace end Puppet.err detail raise XMLRPC::FaultException.new( 1, detail.to_s ) end end return config end # Create our interpreter object. def create_interpreter(options) args = {} # Allow specification of a code snippet or of a file if code = options[:Code] args[:Code] = code elsif options[:Manifest] args[:Manifest] = options[:Manifest] end args[:Local] = local? if options.include?(:UseNodes) args[:UseNodes] = options[:UseNodes] elsif @local args[:UseNodes] = false end # This is only used by the cfengine module, or if --loadclasses was # specified in +puppet+. if options.include?(:Classes) args[:Classes] = options[:Classes] end return Puppet::Parser::Interpreter.new(args) end # Create/return our interpreter. def interpreter unless defined?(@interpreter) and @interpreter @interpreter = create_interpreter(@options) end @interpreter end # Create a node handler instance for looking up our nodes. def node_handler unless defined?(@node_handler) @node_handler = Puppet::Network::Handler.handler(:node).create end @node_handler end # Initialize our server fact hash; we add these to each client, and they # won't change while we're running, so it's safe to cache the values. def set_server_facts @server_facts = {} # Add our server version to the fact list @server_facts["serverversion"] = Puppet.version.to_s # And then add the server name and IP {"servername" => "fqdn", "serverip" => "ipaddress" }.each do |var, fact| if value = Facter.value(fact) @server_facts[var] = value else Puppet.warning "Could not retrieve fact %s" % fact end end if @server_facts["servername"].nil? host = Facter.value(:hostname) if domain = Facter.value(:domain) @server_facts["servername"] = [host, domain].join(".") else @server_facts["servername"] = host end end end # Translate our configuration appropriately for sending back to a client. def translate(config) if local? config else CGI.escape(config.to_yaml(:UseBlock => true)) end end # Mark that the node has checked in. FIXME this needs to be moved into # the Node class, or somewhere that's got abstract backends. def update_node_check(node) if Puppet.features.rails? and Puppet[:storeconfigs] Puppet::Rails.connect host = Puppet::Rails::Host.find_or_create_by_name(node.name) host.last_freshcheck = Time.now host.save end end end end - -# $Id$ diff --git a/lib/puppet/network/handler/master.rb b/lib/puppet/network/handler/master.rb index ace383e9f..18f37ca4a 100644 --- a/lib/puppet/network/handler/master.rb +++ b/lib/puppet/network/handler/master.rb @@ -1,143 +1,154 @@ require 'openssl' require 'puppet' require 'puppet/parser/interpreter' require 'puppet/sslcertificates' require 'xmlrpc/server' require 'yaml' class Puppet::Network::Handler class MasterError < Puppet::Error; end class Master < Handler desc "Puppet's configuration interface. Used for all interactions related to generating client configurations." include Puppet::Util attr_accessor :ast attr_reader :ca @interface = XMLRPC::Service::Interface.new("puppetmaster") { |iface| iface.add_method("string getconfig(string)") iface.add_method("int freshness()") } # Tell a client whether there's a fresh config for it def freshness(client = nil, clientip = nil) client ||= Facter.value("hostname") config_handler.version(client, clientip) end def initialize(hash = {}) args = {} # Allow specification of a code snippet or of a file if code = hash[:Code] args[:Code] = code elsif man = hash[:Manifest] args[:Manifest] = man end if hash[:Local] @local = hash[:Local] else @local = false end args[:Local] = local? if hash.include?(:CA) and hash[:CA] @ca = Puppet::SSLCertificates::CA.new() else @ca = nil end Puppet.debug("Creating interpreter") if hash.include?(:UseNodes) args[:UseNodes] = hash[:UseNodes] elsif @local args[:UseNodes] = false end # This is only used by the cfengine module, or if --loadclasses was # specified in +puppet+. if hash.include?(:Classes) args[:Classes] = hash[:Classes] end @config_handler = Puppet::Network::Handler.handler(:configuration).new(args) end # Call our various handlers; this handler is getting deprecated. def getconfig(facts, format = "marshal", client = nil, clientip = nil) facts = decode_facts(facts) client, clientip = clientname(client, clientip, facts) # Pass the facts to the fact handler fact_handler.set(client, facts) # And get the configuration from the config handler - return config_handler.configuration(client) + config = config_handler.configuration(client) + + return translate(config.extract) end def local=(val) @local = val config_handler.local = val fact_handler.local = val end private # Manipulate the client name as appropriate. def clientname(name, ip, facts) # Always use the hostname from Facter. client = facts["hostname"] clientip = facts["ipaddress"] if Puppet[:node_name] == 'cert' if name client = name end if ip clientip = ip end end return client, clientip end def config_handler unless defined? @config_handler @config_handler = Puppet::Network::Handler.handler(:config).new :local => local? end @config_handler end # def decode_facts(facts) if @local # we don't need to do anything, since we should already # have raw objects Puppet.debug "Our client is local" else Puppet.debug "Our client is remote" begin facts = YAML.load(CGI.unescape(facts)) rescue => detail raise XMLRPC::FaultException.new( 1, "Could not rebuild facts" ) end end return facts end def fact_handler unless defined? @fact_handler @fact_handler = Puppet::Network::Handler.handler(:facts).new :local => local? end @fact_handler end + + # Translate our configuration appropriately for sending back to a client. + def translate(config) + if local? + config + else + CGI.escape(config.to_yaml(:UseBlock => true)) + end + end end end diff --git a/lib/puppet/node/configuration.rb b/lib/puppet/node/configuration.rb index e667007e9..4f93fdbe5 100644 --- a/lib/puppet/node/configuration.rb +++ b/lib/puppet/node/configuration.rb @@ -1,123 +1,123 @@ require 'puppet/external/gratr/digraph' # This class models a node configuration. It is the thing # meant to be passed from server to client, and it contains all # of the information in the configuration, including the resources # and the relationships between them. class Puppet::Node::Configuration < GRATR::Digraph - attr_accessor :name + attr_accessor :name, :version attr_reader :extraction_format # Add classes to our class list. def add_class(*classes) classes.each do |klass| @classes << klass end # Add the class names as tags, too. tag(*classes) end def classes @classes.dup end # Make sure we support the requested extraction format. def extraction_format=(value) unless respond_to?("extract_to_%s" % value) raise ArgumentError, "Invalid extraction format %s" % value end @extraction_format = value end # Turn our configuration graph into whatever the client is expecting. def extract send("extract_to_%s" % extraction_format) end # Create the traditional TransBuckets and TransObjects from our configuration # graph. This will hopefully be deprecated soon. def extract_to_transportable top = nil current = nil buckets = {} unless main = vertices.find { |res| res.type == "class" and res.title == :main } raise Puppet::DevError, "Could not find 'main' class; cannot generate configuration" end # Create a proc for examining edges, which we'll use to build our tree # of TransBuckets and TransObjects. bucket = nil edges = proc do |edge| # The sources are always non-builtins. source, target = edge.source, edge.target unless tmp = buckets[source.to_s] if tmp = buckets[source.to_s] = source.to_trans bucket = tmp else # This is because virtual resources return nil. If a virtual # container resource contains realized resources, we still need to get # to them. So, we keep a reference to the last valid bucket # we returned and use that if the container resource is virtual. end end bucket = tmp || bucket if child = target.to_trans unless bucket raise "No bucket created for %s" % source end bucket.push child # It's important that we keep a reference to any TransBuckets we've created, so # we don't create multiple buckets for children. unless target.builtin? buckets[target.to_s] = child end end end dfs(:start => main, :examine_edge => edges) unless main raise Puppet::DevError, "Could not find 'main' class; cannot generate configuration" end # Retrive the bucket for the top-level scope and set the appropriate metadata. unless result = buckets[main.to_s] raise Puppet::DevError, "Did not evaluate top scope" end result.classes = classes # Clear the cache to encourage the GC buckets.clear return result end def initialize(name) super() @name = name @extraction_format ||= :transportable @tags = [] @classes = [] end # Add a tag. def tag(*names) names.each do |name| name = name.to_s @tags << name unless @tags.include?(name) if name.include?("::") name.split("::").each do |sub| @tags << sub unless @tags.include?(sub) end end end nil end # Return the list of tags. def tags @tags.dup end end diff --git a/lib/puppet/parser/compile.rb b/lib/puppet/parser/compile.rb index 841e58d4d..cc9938e50 100644 --- a/lib/puppet/parser/compile.rb +++ b/lib/puppet/parser/compile.rb @@ -1,493 +1,494 @@ # Created by Luke A. Kanies on 2007-08-13. # Copyright (c) 2007. All rights reserved. require 'puppet/external/gratr/digraph' require 'puppet/external/gratr/import' require 'puppet/external/gratr/dot' require 'puppet/node' require 'puppet/node/configuration' require 'puppet/util/errors' # Maintain a graph of scopes, along with a bunch of data # about the individual configuration we're compiling. class Puppet::Parser::Compile include Puppet::Util include Puppet::Util::Errors attr_reader :topscope, :parser, :node, :facts, :collections, :configuration attr_writer :ast_nodes # Add a collection to the global list. def add_collection(coll) @collections << coll end # Do we use nodes found in the code, vs. the external node sources? def ast_nodes? defined?(@ast_nodes) and @ast_nodes end # Store the fact that we've evaluated a class, and store a reference to # the scope in which it was evaluated, so that we can look it up later. def class_set(name, scope) if existing = @class_scopes[name] if existing.nodescope? or scope.nodescope? raise Puppet::ParseError, "Cannot have classes, nodes, or definitions with the same name" else raise Puppet::DevError, "Somehow evaluated the same class twice" end end @class_scopes[name] = scope @configuration.add_class(name) unless name == "" end # Return the scope associated with a class. This is just here so # that subclasses can set their parent scopes to be the scope of # their parent class, and it's also used when looking up qualified # variables. def class_scope(klass) # They might pass in either the class or class name if klass.respond_to?(:classname) @class_scopes[klass.classname] else @class_scopes[klass] end end # Return a list of all of the defined classes. def classlist return @configuration.classes end # Compile our configuration. This mostly revolves around finding and evaluating classes. # This is the main entry into our configuration. def compile # Set the client's parameters into the top scope. set_node_parameters() evaluate_main() evaluate_ast_node() evaluate_node_classes() evaluate_generators() fail_on_unevaluated() finish() if Puppet[:storeconfigs] store() end - return @configuration.extract + return @configuration end # FIXME There are no tests for this. def delete_collection(coll) @collections.delete(coll) if @collections.include?(coll) end # FIXME There are no tests for this. def delete_resource(resource) @resource_table.delete(resource.ref) if @resource_table.include?(resource.ref) end # Return the node's environment. def environment unless defined? @environment if node.environment and node.environment != "" @environment = node.environment else @environment = nil end end @environment end # Evaluate all of the classes specified by the node. def evaluate_node_classes evaluate_classes(@node.classes, @topscope) end # Evaluate each specified class in turn. If there are any classes we can't # find, just tag the configuration and move on. This method really just # creates resource objects that point back to the classes, and then the # resources are themselves evaluated later in the process. def evaluate_classes(classes, scope) + unless scope.source + raise Puppet::DevError, "No source for scope passed to evaluate_classes" + end found = [] classes.each do |name| # If we can find the class, then make a resource that will evaluate it. if klass = scope.findclass(name) - unless scope.source - raise Puppet::DevError, "No source for %s" % scope.to_s - end # Create a resource to model this class, and then add it to the list # of resources. resource = Puppet::Parser::Resource.new(:type => "class", :title => klass.classname, :scope => scope, :source => scope.source) store_resource(scope, resource) @configuration.tag(klass.classname) found << name else Puppet.info "Could not find class %s for %s" % [name, node.name] @configuration.tag(name) end end found end # Return a resource by either its ref or its type and title. def findresource(string, name = nil) if name string = "%s[%s]" % [string.capitalize, name] end @resource_table[string] end # Set up our compile. We require a parser # and a node object; the parser is so we can look up classes # and AST nodes, and the node has all of the client's info, # like facts and environment. def initialize(node, parser, options = {}) @node = node @parser = parser options.each do |param, value| begin send(param.to_s + "=", value) rescue NoMethodError raise ArgumentError, "Compile objects do not accept %s" % param end end initvars() init_main() end # Create a new scope, with either a specified parent scope or # using the top scope. Adds an edge between the scope and # its parent to the graph. def newscope(parent, options = {}) parent ||= @topscope options[:compile] = self options[:parser] ||= self.parser scope = Puppet::Parser::Scope.new(options) @scope_graph.add_edge!(parent, scope) scope end # Find the parent of a given scope. Assumes scopes only ever have # one in edge, which will always be true. def parent(scope) if ary = @scope_graph.adjacent(scope, :direction => :in) and ary.length > 0 ary[0] else nil end end # Return any overrides for the given resource. def resource_overrides(resource) @resource_overrides[resource.ref] end # Return a list of all resources. def resources @resource_table.values end # Store a resource override. def store_override(override) override.override = true # If possible, merge the override in immediately. if resource = @resource_table[override.ref] resource.merge(override) else # Otherwise, store the override for later; these # get evaluated in Resource#finish. @resource_overrides[override.ref] << override end end # Store a resource in our resource table. def store_resource(scope, resource) # This might throw an exception verify_uniqueness(resource) # Store it in the global table. @resource_table[resource.ref] = resource # And in the resource graph. At some point, this might supercede # the global resource table, but the table is a lot faster # so it makes sense to maintain for now. @configuration.add_edge!(scope.resource, resource) end private # If ast nodes are enabled, then see if we can find and evaluate one. def evaluate_ast_node return unless ast_nodes? # Now see if we can find the node. astnode = nil @node.names.each do |name| break if astnode = @parser.nodes[name.to_s.downcase] end unless astnode astnode = @parser.nodes["default"] end unless astnode raise Puppet::ParseError, "Could not find default node or by name with '%s'" % node.names.join(", ") end astnode.safeevaluate :scope => topscope end # Evaluate our collections and return true if anything returned an object. # The 'true' is used to continue a loop, so it's important. def evaluate_collections return false if @collections.empty? found_something = false exceptwrap do @collections.each do |collection| if collection.evaluate found_something = true end end end return found_something end # Make sure all of our resources have been evaluated into native resources. # We return true if any resources have, so that we know to continue the # evaluate_generators loop. def evaluate_definitions exceptwrap do if ary = unevaluated_resources ary.each do |resource| resource.evaluate end # If we evaluated, let the loop know. return true else return false end end end # Iterate over collections and resources until we're sure that the whole # compile is evaluated. This is necessary because both collections # and defined resources can generate new resources, which themselves could # be defined resources. def evaluate_generators count = 0 loop do done = true # Call collections first, then definitions. done = false if evaluate_collections done = false if evaluate_definitions break if done if count > 1000 raise Puppet::ParseError, "Somehow looped more than 1000 times while evaluating host configuration" end end end # Find and evaluate our main object, if possible. def evaluate_main @main = @parser.findclass("", "") || @parser.newclass("") @topscope.source = @main @main_resource = Puppet::Parser::Resource.new(:type => "class", :title => :main, :scope => @topscope, :source => @main) @topscope.resource = @main_resource @configuration.add_vertex!(@main_resource) @resource_table["Class[main]"] = @main_resource end # Make sure the entire configuration is evaluated. def fail_on_unevaluated fail_on_unevaluated_overrides fail_on_unevaluated_resource_collections end # If there are any resource overrides remaining, then we could # not find the resource they were supposed to override, so we # want to throw an exception. def fail_on_unevaluated_overrides remaining = [] @resource_overrides.each do |name, overrides| remaining += overrides end unless remaining.empty? fail Puppet::ParseError, "Could not find object(s) %s" % remaining.collect { |o| o.ref }.join(", ") end end # Make sure we don't have any remaining collections that specifically # look for resources, because we want to consider those to be # parse errors. def fail_on_unevaluated_resource_collections remaining = [] @collections.each do |coll| # We're only interested in the 'resource' collections, # which result from direct calls of 'realize'. Anything # else is allowed not to return resources. # Collect all of them, so we have a useful error. if r = coll.resources if r.is_a?(Array) remaining += r else remaining << r end end end unless remaining.empty? raise Puppet::ParseError, "Failed to realize virtual resources %s" % remaining.join(', ') end end # Make sure all of our resources and such have done any last work # necessary. def finish @resource_table.each { |name, resource| resource.finish if resource.respond_to?(:finish) } end # Initialize the top-level scope, class, and resource. def init_main # Create our initial scope and a resource that will evaluate main. @topscope = Puppet::Parser::Scope.new(:compile => self, :parser => self.parser) @scope_graph.add_vertex!(@topscope) end # Set up all of our internal variables. def initvars # The table for storing class singletons. This will only actually # be used by top scopes and node scopes. @class_scopes = {} # The table for all defined resources. @resource_table = {} # The list of objects that will available for export. @exported_resources = {} # The list of overrides. This is used to cache overrides on objects # that don't exist yet. We store an array of each override. @resource_overrides = Hash.new do |overs, ref| overs[ref] = [] end # The list of collections that have been created. This is a global list, # but they each refer back to the scope that created them. @collections = [] # A list of tags we've generated; most class names. @tags = [] # A graph for maintaining scope relationships. @scope_graph = GRATR::Digraph.new @scope_graph.add_vertex!(@topscope) # For maintaining the relationship between scopes and their resources. @configuration = Puppet::Node::Configuration.new(@node.name) + @configuration.version = @parser.version end # Set the node's parameters into the top-scope as variables. def set_node_parameters node.parameters.each do |param, value| @topscope.setvar(param, value) end end # Store the configuration into the database. def store unless Puppet.features.rails? raise Puppet::Error, "storeconfigs is enabled but rails is unavailable" end unless ActiveRecord::Base.connected? Puppet::Rails.connect end # We used to have hooks here for forking and saving, but I don't # think it's worth retaining at this point. store_to_active_record(@node, @resource_table.values) end # Do the actual storage. def store_to_active_record(node, resources) begin # We store all of the objects, even the collectable ones benchmark(:info, "Stored configuration for #{node.name}") do Puppet::Rails::Host.transaction do Puppet::Rails::Host.store(node, resources) end end rescue => detail if Puppet[:trace] puts detail.backtrace end Puppet.err "Could not store configs: %s" % detail.to_s end end # Return an array of all of the unevaluated resources. These will be definitions, # which need to get evaluated into native resources. def unevaluated_resources ary = @resource_table.find_all do |name, object| ! object.builtin? and ! object.evaluated? end.collect { |name, object| object } if ary.empty? return nil else return ary end end # Verify that the given resource isn't defined elsewhere. def verify_uniqueness(resource) # Short-curcuit the common case, unless existing_resource = @resource_table[resource.ref] return true end if typeclass = Puppet::Type.type(resource.type) and ! typeclass.isomorphic? Puppet.info "Allowing duplicate %s" % typeclass.name return true end # Either it's a defined type, which are never # isomorphic, or it's a non-isomorphic type, so # we should throw an exception. msg = "Duplicate definition: %s is already defined" % resource.ref if existing_resource.file and existing_resource.line msg << " in file %s at line %s" % [existing_resource.file, existing_resource.line] end if resource.line or resource.file msg << "; cannot redefine" end raise Puppet::ParseError.new(msg) end end diff --git a/spec/unit/parser/compile.rb b/spec/unit/parser/compile.rb index d22b63545..f6b3c9b3f 100755 --- a/spec/unit/parser/compile.rb +++ b/spec/unit/parser/compile.rb @@ -1,48 +1,52 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../spec_helper' describe Puppet::Parser::Compile, " when compiling" do before do - @node = mock 'node' - @parser = mock 'parser' + @node = stub 'node', :name => 'mynode' + @parser = stub 'parser', :version => "1.0" @compile = Puppet::Parser::Compile.new(@node, @parser) end def compile_methods [:set_node_parameters, :evaluate_main, :evaluate_ast_node, :evaluate_node_classes, :evaluate_generators, :fail_on_unevaluated, :finish, :store, :extract] end # Stub all of the main compile methods except the ones we're specifically interested in. def compile_stub(*except) (compile_methods - except).each { |m| @compile.stubs(m) } end it "should set node parameters as variables in the top scope" do params = {"a" => "b", "c" => "d"} @node.stubs(:parameters).returns(params) compile_stub(:set_node_parameters) @compile.compile @compile.topscope.lookupvar("a").should == "b" @compile.topscope.lookupvar("c").should == "d" end it "should evaluate any existing classes named in the node" do classes = %w{one two three four} main = stub 'main' - one = stub 'one' - one.expects(:safeevaluate).with(:scope => @compile.topscope) - three = stub 'three' - three.expects(:safeevaluate).with(:scope => @compile.topscope) + one = stub 'one', :classname => "one" + three = stub 'three', :classname => "three" @node.stubs(:name).returns("whatever") @compile.parser.expects(:findclass).with("", "").returns(main) @compile.parser.expects(:findclass).with("", "one").returns(one) @compile.parser.expects(:findclass).with("", "two").returns(nil) @compile.parser.expects(:findclass).with("", "three").returns(three) @compile.parser.expects(:findclass).with("", "four").returns(nil) @node.stubs(:classes).returns(classes) - compile_stub(:evaluate_node_classes) - @compile.compile + @compile.send :evaluate_main + @compile.send :evaluate_node_classes + + # Now make sure we've created the appropriate resources. + @compile.resources.find { |r| r.to_s == "Class[one]" }.should be_an_instance_of(Puppet::Parser::Resource) + @compile.resources.find { |r| r.to_s == "Class[three]" }.should be_an_instance_of(Puppet::Parser::Resource) + @compile.resources.find { |r| r.to_s == "Class[two]" }.should be_nil + @compile.resources.find { |r| r.to_s == "Class[four]" }.should be_nil end end diff --git a/test/language/ast.rb b/test/language/ast.rb index f62b2340c..dbc1d04ed 100755 --- a/test/language/ast.rb +++ b/test/language/ast.rb @@ -1,190 +1,188 @@ #!/usr/bin/env ruby $:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/ require 'puppettest' require 'puppet/parser/interpreter' require 'puppet/parser/parser' require 'puppettest/resourcetesting' require 'puppettest/parsertesting' require 'puppettest/support/collection' class TestAST < Test::Unit::TestCase include PuppetTest::ParserTesting include PuppetTest::ResourceTesting include PuppetTest::Support::Collection def test_if astif = nil astelse = nil fakeelse = FakeAST.new(:else) faketest = FakeAST.new(true) fakeif = FakeAST.new(:if) assert_nothing_raised { astelse = AST::Else.new(:statements => fakeelse) } assert_nothing_raised { astif = AST::IfStatement.new( :test => faketest, :statements => fakeif, :else => astelse ) } # We initialized it to true, so we should get that first ret = nil assert_nothing_raised { ret = astif.evaluate(:scope => "yay") } assert_equal(:if, ret) # Now set it to false and check that faketest.evaluate = false assert_nothing_raised { ret = astif.evaluate(:scope => "yay") } assert_equal(:else, ret) end # Make sure our override object behaves "correctly" def test_override scope = mkscope ref = nil assert_nothing_raised do ref = resourceoverride("file", "/yayness", "owner" => "blah", "group" => "boo") end Puppet::Parser::Resource.expects(:new).with { |o| o.is_a?(Hash) }.returns(:override) scope.compile.expects(:store_override).with(:override) ret = nil assert_nothing_raised do ret = ref.evaluate :scope => scope end assert_equal(:override, ret, "Did not return override") end # make sure our resourcedefaults ast object works correctly. def test_resourcedefaults - interp, scope, source = mkclassframing + scope = mkscope # Now make some defaults for files args = {:source => "/yay/ness", :group => "yayness"} assert_nothing_raised do obj = defaultobj "file", args obj.evaluate :scope => scope end hash = nil assert_nothing_raised do hash = scope.lookupdefaults("file") end hash.each do |name, value| assert_instance_of(Symbol, name) # params always convert assert_instance_of(Puppet::Parser::Resource::Param, value) end args.each do |name, value| assert(hash[name], "Did not get default %s" % name) assert_equal(value, hash[name].value) end end def test_node scope = mkscope parser = scope.compile.parser # Define a base node basenode = parser.newnode "basenode", :code => AST::ASTArray.new(:children => [ resourcedef("file", "/tmp/base", "owner" => "root") ]) # Now define a subnode nodes = parser.newnode ["mynode", "othernode"], :code => AST::ASTArray.new(:children => [ resourcedef("file", "/tmp/mynode", "owner" => "root"), resourcedef("file", "/tmp/basenode", "owner" => "daemon") ]) assert_instance_of(Array, nodes) # Make sure we can find them all. %w{mynode othernode}.each do |node| assert(parser.nodes[node], "Could not find %s" % node) end mynode = parser.nodes["mynode"] # Now try evaluating the node assert_nothing_raised do mynode.evaluate :scope => scope, :resource => scope.resource end # Make sure that we can find each of the files myfile = scope.findresource "File[/tmp/mynode]" assert(myfile, "Could not find file from node") assert_equal("root", myfile[:owner]) basefile = scope.findresource "File[/tmp/basenode]" assert(basefile, "Could not find file from base node") assert_equal("daemon", basefile[:owner]) # Now make sure we can evaluate nodes with parents child = parser.newnode(%w{child}, :parent => "basenode").shift newscope = mkscope :parser => parser assert_nothing_raised do child.evaluate :scope => newscope, :resource => scope.resource end assert(newscope.findresource("File[/tmp/base]"), "Could not find base resource") end def test_collection scope = mkscope coll = nil assert_nothing_raised do coll = AST::Collection.new(:type => "file", :form => :virtual) end assert_instance_of(AST::Collection, coll) ret = nil assert_nothing_raised do ret = coll.evaluate :scope => scope end assert_instance_of(Puppet::Parser::Collector, ret) # Now make sure we get it back from the scope colls = scope.compile.instance_variable_get("@collections") assert_equal([ret], colls, "Did not store collector in config's collection list") end def test_virtual_collexp - @interp, @scope, @source = mkclassframing + scope = mkscope # make a resource resource = mkresource(:type => "file", :title => "/tmp/testing", - :params => {:owner => "root", :group => "bin", :mode => "644"}) + :scope => scope, :params => {:owner => "root", :group => "bin", :mode => "644"}) run_collection_queries(:virtual) do |string, result, query| code = nil assert_nothing_raised do - str, code = query.evaluate :scope => @scope + str, code = query.evaluate :scope => scope end assert_instance_of(Proc, code) assert_nothing_raised do assert_equal(result, code.call(resource), "'#{string}' failed") end end end end - -# $Id$ diff --git a/test/language/compile.rb b/test/language/compile.rb index 0d9c9e32a..ceec3c346 100755 --- a/test/language/compile.rb +++ b/test/language/compile.rb @@ -1,669 +1,668 @@ #!/usr/bin/env ruby $:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/ require 'mocha' require 'puppettest' require 'puppettest/parsertesting' require 'puppet/parser/compile' # Test our compile object. class TestCompile < Test::Unit::TestCase include PuppetTest include PuppetTest::ParserTesting Compile = Puppet::Parser::Compile 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' + @parser = stub 'parser', :version => "1.0" end def mkcompile(options = {}) if node = options[:node] options.delete(:node) else node = mknode end @compile = Compile.new(node, mkparser, options) end def test_initialize compile = nil node = stub 'node', :name => "foo" - parser = mock 'parser' + parser = stub 'parser', :version => "1.0" assert_nothing_raised("Could not init compile with all required options") do compile = Compile.new(node, parser) end assert_equal(node, compile.node, "Did not set node correctly") assert_equal(parser, compile.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 compile with extra options") do compile = Compile.new(node, parser, :ast_nodes => false) end assert_equal(false, compile.ast_nodes?, "Did not set ast_nodes? correctly") end def test_initvars compile = mkcompile [:class_scopes, :resource_table, :exported_resources, :resource_overrides].each do |table| assert_instance_of(Hash, compile.send(:instance_variable_get, "@#{table}"), "Did not set %s table correctly" % table) end assert_instance_of(Scope, compile.topscope, "Did not create a topscope") graph = compile.instance_variable_get("@scope_graph") assert_instance_of(GRATR::Digraph, graph, "Did not create scope graph") assert(graph.vertex?(compile.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") compile = mkcompile compile.configuration.expects(:tag).with("myname") assert_nothing_raised("Could not set class") do compile.class_set "myname", "myscope" end # First try to retrieve it by name. assert_equal("myscope", compile.class_scope("myname"), "Could not retrieve class scope by name") # Then by object assert_equal("myscope", compile.class_scope(klass), "Could not retrieve class scope by object") end def test_classlist compile = mkcompile compile.class_set "", "empty" compile.class_set "one", "yep" compile.class_set "two", "nope" # Make sure our class list is correct assert_equal(%w{one two}.sort, compile.classlist.sort, "Did not get correct class list") end # Make sure collections get added to our internal array def test_add_collection compile = mkcompile assert_nothing_raised("Could not add collection") do compile.add_collection "nope" end assert_equal(%w{nope}, compile.instance_variable_get("@collections"), "Did not add collection") end # Make sure we create a graph of scopes. def test_newscope compile = mkcompile graph = compile.instance_variable_get("@scope_graph") assert_instance_of(Scope, compile.topscope, "Did not create top scope") assert_instance_of(GRATR::Digraph, graph, "Did not create graph") assert(graph.vertex?(compile.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 = compile.newscope(compile.topscope) end assert_instance_of(Scope, subscope, "Did not create subscope") assert(graph.edge?(compile.topscope, subscope), "An edge between top scope and subscope was not added") # Make sure a scope can find its parent. assert(compile.parent(subscope), "Could not look up parent scope on compile") assert_equal(compile.topscope.object_id, compile.parent(subscope).object_id, "Did not get correct parent scope from compile") assert_equal(compile.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 = compile.newscope(subscope, :level => 5) end assert_equal(5, another.level, "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(compile.parent(another), "Could not look up parent scope of second subscope on compile") assert_equal(subscope.object_id, compile.parent(another).object_id, "Did not get correct parent scope of second subscope from compile") 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, compile.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 compile = mkcompile - compile.instance_variable_get("@configuration").expects(:extract).returns(:config) [:set_node_parameters, :evaluate_main, :evaluate_ast_node, :evaluate_node_classes, :evaluate_generators, :fail_on_unevaluated, :finish].each do |method| compile.expects(method) end - assert_equal(:config, compile.compile, "Did not return the results of the extraction") + assert_instance_of(Puppet::Node::Configuration, compile.compile, "Did not return the configuration") end # Test setting the node's parameters into the top scope. def test_set_node_parameters compile = mkcompile @node.parameters = {"a" => "b", "c" => "d"} scope = compile.topscope @node.parameters.each do |param, value| scope.expects(:setvar).with(param, value) end assert_nothing_raised("Could not call 'set_node_parameters'") do compile.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 compile = mkcompile main = mock 'main_class' compile.topscope.expects(:source=).with(main) @parser.expects(:findclass).with("", "").returns(main) assert_nothing_raised("Could not call evaluate_main") do compile.send(:evaluate_main) end assert(compile.resources.find { |r| r.to_s == "Class[main]" }, "Did not create a 'main' resource") 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 compile = mkcompile :ast_nodes => false compile.expects(:ast_nodes?).returns(false) compile.parser.expects(:nodes).never assert_nothing_raised("Could not call evaluate_ast_node when ast nodes are disabled") do compile.send(:evaluate_ast_node) end # Now try it with them enabled, but no node found. nodes = mock 'node_hash' compile = mkcompile :ast_nodes => true compile.expects(:ast_nodes?).returns(true) compile.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 compile.send(:evaluate_ast_node) end # Finally, make sure it works dandily when we have a node nodes = mock 'hash' compile = mkcompile :ast_nodes => true compile.expects(:ast_nodes?).returns(true) compile.parser.expects(:nodes).returns(nodes).times(3) node = mock 'node' node.expects(:safeevaluate).with(:scope => compile.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 compile.send(:evaluate_ast_node) end # Lastly, check when we actually find the default. nodes = mock 'hash' compile = mkcompile :ast_nodes => true compile.expects(:ast_nodes?).returns(true) compile.parser.expects(:nodes).returns(nodes).times(4) node = mock 'node' node.expects(:safeevaluate).with(:scope => compile.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 compile.send(:evaluate_ast_node) end end def test_evaluate_node_classes compile = mkcompile @node.classes = %w{one two three four} compile.expects(:evaluate_classes).with(%w{one two three four}, compile.topscope) assert_nothing_raised("could not call evaluate_node_classes") do compile.send(:evaluate_node_classes) end end def test_evaluate_classes compile = mkcompile compile.parser.expects(:findclass).with("", "").returns(stub('main', :classname => "")) compile.send :evaluate_main classes = { "one" => stub('class one', :classname => "one"), "three" => stub('class three', :classname => "three") } classes.each do |name, obj| compile.parser.expects(:findclass).with("", name).returns(obj) end %w{two four}.each do |name| compile.parser.expects(:findclass).with("", name).returns(nil) end %w{one two three four}.each do |name| compile.configuration.expects(:tag).with(name) end result = nil assert_nothing_raised("could not call evaluate_node_classes") do result = compile.send(:evaluate_classes, %w{one two three four}, compile.topscope) end %w{one three}.each do |found| assert(compile.resources.find { |r| r.to_s == "Class[#{found}]" }, "Did not create a class resource for %s" % found) end assert_equal(%w{one three}, result, "Did not return the list of evaluated classes") end def test_evaluate_collections compile = mkcompile colls = [] # Make sure we return false when there's nothing there. assert(! compile.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) } compile.instance_variable_set("@collections", colls) assert(! compile.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(compile.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(compile.send(:evaluate_collections), "Did not return true when both collections evaluated true") end def test_unevaluated_resources compile = mkcompile resources = {} compile.instance_variable_set("@resource_table", resources) # First test it when the table is empty assert_nil(compile.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(compile.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(compile.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"]], compile.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 = compile.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 compile = mkcompile compile.expects(:unevaluated_resources).returns(nil) assert_nothing_raised("Could not test for unevaluated resources") do assert(! compile.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 compile = mkcompile compile.expects(:unevaluated_resources).returns(resources) assert_nothing_raised("Could not test for unevaluated resources") do assert(compile.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 compile = mkcompile compile.expects(:evaluate_definitions).returns(false) compile.expects(:evaluate_collections).returns(false) assert_nothing_raised("Could not call :eval_iterate") do compile.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. #compile = mkcompile #compile.expects(:evaluate_collections).returns(true).returns(false) #compile.expects(:evaluate_definitions).returns(false) #compile.send(:eval_iterate) end def test_store compile = mkcompile Puppet.features.expects(:rails?).returns(true) Puppet::Rails.expects(:connect) node = mock 'node' resource_table = mock 'resources' resource_table.expects(:values).returns(:resources) compile.instance_variable_set("@node", node) compile.instance_variable_set("@resource_table", resource_table) compile.expects(:store_to_active_record).with(node, :resources) compile.send(:store) end def test_store_to_active_record compile = mkcompile node = mock 'node' node.expects(:name).returns("myname") Puppet::Rails::Host.stubs(:transaction).yields Puppet::Rails::Host.expects(:store).with(node, :resources) compile.send(:store_to_active_record, node, :resources) end # Make sure that 'finish' gets called on all of our resources. def test_finish compile = mkcompile table = compile.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 compile.send(:finish) end def test_verify_uniqueness compile = mkcompile resources = compile.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 compile.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 compile.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 compile.send(:verify_uniqueness, conflict) end end def test_store_resource # Run once when there's no conflict compile = mkcompile table = compile.instance_variable_get("@resource_table") resource = mock("resource") resource.expects(:ref).returns("yay") compile.expects(:verify_uniqueness).with(resource) scope = stub("scope", :resource => mock('resource')) compile.configuration.expects(:add_edge!).with(scope.resource, resource) assert_nothing_raised("Could not store resource") do compile.store_resource(scope, resource) end assert_equal(resource, table["yay"], "Did not store resource in table") # Now for conflicts compile = mkcompile table = compile.instance_variable_get("@resource_table") resource = mock("resource") compile.expects(:verify_uniqueness).with(resource).raises(ArgumentError) assert_raise(ArgumentError, "Did not raise uniqueness exception") do compile.store_resource(scope, resource) end assert(table.empty?, "Conflicting resource was stored in table") end def test_fail_on_unevaluated compile = mkcompile compile.expects(:fail_on_unevaluated_overrides) compile.expects(:fail_on_unevaluated_resource_collections) compile.send :fail_on_unevaluated end def test_store_override # First test the case when the resource is not present. compile = mkcompile overrides = compile.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 compile.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 = compile.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 compile.store_override(override) end end def test_resource_overrides compile = mkcompile overrides = compile.instance_variable_get("@resource_overrides") overrides[:test] = :yay resource = mock 'resource' resource.expects(:ref).returns(:test) assert_equal(:yay, compile.resource_overrides(resource), "Did not return overrides from table") end def test_fail_on_unevaluated_resource_collections compile = mkcompile collections = compile.instance_variable_get("@collections") # Make sure we're fine when the list is empty assert_nothing_raised("Failed when no collections were present") do compile.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 compile.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 compile.send :fail_on_unevaluated_resource_collections end end def test_fail_on_unevaluated_overrides compile = mkcompile overrides = compile.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 compile.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 compile.send :fail_on_unevaluated_overrides end end def test_find_resource compile = mkcompile resources = compile.instance_variable_get("@resource_table") assert_nothing_raised("Could not call findresource when the resource table was empty") do assert_nil(compile.findresource("yay", "foo"), "Returned a non-existent resource") assert_nil(compile.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, compile.findresource("foo", "bar"), "Returned a non-existent resource") assert_equal(:yay, compile.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 compile = mkcompile node = stub :nodescope? => true klass = stub :nodescope? => false compile.class_set("one", node) assert_raise(Puppet::ParseError, "Did not fail when replacing node with class") do compile.class_set("one", klass) end # and class then node compile = mkcompile node = stub :nodescope? => true klass = stub :nodescope? => false compile.class_set("two", klass) assert_raise(Puppet::ParseError, "Did not fail when replacing node with class") do compile.class_set("two", node) end end end diff --git a/test/language/functions.rb b/test/language/functions.rb index efa506218..395577cb6 100755 --- a/test/language/functions.rb +++ b/test/language/functions.rb @@ -1,537 +1,535 @@ #!/usr/bin/env ruby $:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/ require 'puppet' require 'puppet/parser/parser' require 'puppet/network/client' require 'puppettest' require 'puppettest/resourcetesting' class TestLangFunctions < Test::Unit::TestCase include PuppetTest::ParserTesting include PuppetTest::ResourceTesting def test_functions assert_raise(Puppet::ParseError) do Puppet::Parser::AST::Function.new( :name => "fakefunction", :arguments => AST::ASTArray.new( :children => [nameobj("avalue")] ) ) end assert_nothing_raised do Puppet::Parser::Functions.newfunction(:fakefunction, :type => :rvalue) do |input| return "output %s" % input[0] end end func = nil assert_nothing_raised do func = Puppet::Parser::AST::Function.new( :name => "fakefunction", :ftype => :rvalue, :arguments => AST::ASTArray.new( :children => [nameobj("avalue")] ) ) end scope = mkscope val = nil assert_nothing_raised do val = func.evaluate(:scope => scope) end assert_equal("output avalue", val) end def test_taggedfunction scope = mkscope tags = [] scope.resource.stubs(:tags).returns(tags) tags << "yayness" {"yayness" => true, "booness" => false}.each do |tag, retval| func = taggedobj(tag, :rvalue) val = nil assert_nothing_raised do val = func.evaluate(:scope => scope) end assert_equal(retval, val, "'tagged' returned %s for %s" % [val, tag]) end end def test_failfunction func = nil assert_nothing_raised do func = Puppet::Parser::AST::Function.new( :name => "fail", :ftype => :statement, :arguments => AST::ASTArray.new( :children => [stringobj("this is a failure"), stringobj("and another")] ) ) end scope = mkscope val = nil assert_raise(Puppet::ParseError) do val = func.evaluate(:scope => scope) end end def test_multipletemplates Dir.mkdir(Puppet[:templatedir]) onep = File.join(Puppet[:templatedir], "one") twop = File.join(Puppet[:templatedir], "two") File.open(onep, "w") do |f| f.puts "template <%= one %>" end File.open(twop, "w") do |f| f.puts "template <%= two %>" end func = nil assert_nothing_raised do func = Puppet::Parser::AST::Function.new( :name => "template", :ftype => :rvalue, :arguments => AST::ASTArray.new( :children => [stringobj("one"), stringobj("two")] ) ) end ast = varobj("output", func) scope = mkscope assert_raise(Puppet::ParseError) do ast.evaluate(:scope => scope) end scope.setvar("one", "One") assert_raise(Puppet::ParseError) do ast.evaluate(:scope => scope) end scope.setvar("two", "Two") assert_nothing_raised do ast.evaluate(:scope => scope) end assert_equal("template One\ntemplate Two\n", scope.lookupvar("output"), "Templates were not handled correctly") end # Now make sure we can fully qualify files, and specify just one def test_singletemplates template = tempfile() File.open(template, "w") do |f| f.puts "template <%= yayness %>" end func = nil assert_nothing_raised do func = Puppet::Parser::AST::Function.new( :name => "template", :ftype => :rvalue, :arguments => AST::ASTArray.new( :children => [stringobj(template)] ) ) end ast = varobj("output", func) scope = mkscope assert_raise(Puppet::ParseError) do ast.evaluate(:scope => scope) end scope.setvar("yayness", "this is yayness") assert_nothing_raised do ast.evaluate(:scope => scope) end assert_equal("template this is yayness\n", scope.lookupvar("output"), "Templates were not handled correctly") end def test_tempatefunction_cannot_see_scopes template = tempfile() File.open(template, "w") do |f| f.puts "<%= lookupvar('myvar') %>" end func = nil assert_nothing_raised do func = Puppet::Parser::AST::Function.new( :name => "template", :ftype => :rvalue, :arguments => AST::ASTArray.new( :children => [stringobj(template)] ) ) end ast = varobj("output", func) scope = mkscope scope.setvar("myvar", "this is yayness") assert_raise(Puppet::ParseError) do ast.evaluate(:scope => scope) end end def test_template_reparses template = tempfile() File.open(template, "w") do |f| f.puts "original text" end manifest = tempfile() file = tempfile() File.open(manifest, "w") do |f| f.puts %{file { "#{file}": content => template("#{template}") }} end interp = Puppet::Parser::Interpreter.new( :Manifest => manifest, :UseNodes => false ) node = mknode node.stubs(:environment).returns("yay") Puppet[:environment] = "yay" - version = interp.compile(node) - - objects = nil + configuration = nil assert_nothing_raised { - objects = interp.compile(node) + configuration = interp.compile(node) } - fileobj = objects[0] + version = configuration.version + + fileobj = configuration.vertices.find { |r| r.title == file } + assert(fileobj, "File was not in configuration") assert_equal("original text\n", fileobj["content"], "Template did not work") Puppet[:filetimeout] = -5 # Have to sleep because one second is the fs's time granularity. sleep(1) # Now modify the template File.open(template, "w") do |f| f.puts "new text" end - assert_nothing_raised { - objects = interp.compile(node) - } - newversion = interp.compile(node) + newversion = interp.compile(node).version assert(version != newversion, "Parse date did not change") end def test_template_defined_vars template = tempfile() File.open(template, "w") do |f| f.puts "template <%= yayness %>" end func = nil assert_nothing_raised do func = Puppet::Parser::AST::Function.new( :name => "template", :ftype => :rvalue, :arguments => AST::ASTArray.new( :children => [stringobj(template)] ) ) end ast = varobj("output", func) { "" => "", false => "false", }.each do |string, value| scope = mkscope assert_raise(Puppet::ParseError) do ast.evaluate(:scope => scope) end scope.setvar("yayness", string) assert_equal(string, scope.lookupvar("yayness", false)) assert_nothing_raised("An empty string was not a valid variable value") do ast.evaluate(:scope => scope) end assert_equal("template #{value}\n", scope.lookupvar("output"), "%s did not get evaluated correctly" % string.inspect) end end def test_autoloading_functions assert_equal(false, Puppet::Parser::Functions.function(:autofunc), "Got told autofunc already exists") dir = tempfile() $: << dir newpath = File.join(dir, "puppet", "parser", "functions") FileUtils.mkdir_p(newpath) File.open(File.join(newpath, "autofunc.rb"), "w") { |f| f.puts %{ Puppet::Parser::Functions.newfunction(:autofunc, :type => :rvalue) do |vals| Puppet.wanring vals.inspect end } } obj = nil assert_nothing_raised { obj = Puppet::Parser::Functions.function(:autofunc) } assert(obj, "Did not autoload function") assert(Puppet::Parser::Scope.method_defined?(:function_autofunc), "Did not set function correctly") end def test_realize scope = mkscope parser = scope.compile.parser # Make a definition parser.newdefine("mytype") [%w{file /tmp/virtual}, %w{mytype yay}].each do |type, title| # Make a virtual resource virtual = mkresource(:type => type, :title => title, :virtual => true, :params => {}, :scope => scope) scope.compile.store_resource(scope, virtual) ref = Puppet::Parser::Resource::Reference.new( :type => type, :title => title, :scope => scope ) # Now call the realize function assert_nothing_raised do scope.function_realize(ref) end # Make sure it created a collection assert_equal(1, scope.compile.collections.length, "Did not set collection") assert_nothing_raised do scope.compile.collections.each do |coll| coll.evaluate end end scope.compile.collections.clear # Now make sure the virtual resource is no longer virtual assert(! virtual.virtual?, "Did not make virtual resource real") end # Make sure we puke on any resource that doesn't exist none = Puppet::Parser::Resource::Reference.new( :type => "file", :title => "/tmp/nosuchfile", :scope => scope ) # The function works assert_nothing_raised do scope.function_realize(none.to_s) end # Make sure it created a collection assert_equal(1, scope.compile.collections.length, "Did not set collection") # And the collection has our resource in it assert_equal([none.to_s], scope.compile.collections[0].resources, "Did not set resources in collection") end def test_defined scope = mkscope parser = scope.compile.parser parser.newclass("yayness") parser.newdefine("rahness") assert_nothing_raised do assert(scope.function_defined("yayness"), "yayness class was not considered defined") assert(scope.function_defined("rahness"), "rahness definition was not considered defined") assert(scope.function_defined("service"), "service type was not considered defined") assert(! scope.function_defined("fakness"), "fakeness was considered defined") end # Now make sure any match in a list will work assert(scope.function_defined(["booness", "yayness", "fakeness"]), "A single answer was not sufficient to return true") # and make sure multiple falses are still false assert(! scope.function_defined(%w{no otherno stillno}), "Multiple falses were somehow true") # Now make sure we can test resources scope.compile.store_resource(scope, mkresource(:type => "file", :title => "/tmp/rahness", :scope => scope, :source => scope.source, :params => {:owner => "root"})) yep = Puppet::Parser::Resource::Reference.new(:type => "file", :title => "/tmp/rahness") nope = Puppet::Parser::Resource::Reference.new(:type => "file", :title => "/tmp/fooness") assert(scope.function_defined([yep]), "valid resource was not considered defined") assert(! scope.function_defined([nope]), "invalid resource was considered defined") end def test_search parser = mkparser scope = mkscope(:parser => parser) fun = parser.newdefine("yay::ness") foo = parser.newdefine("foo::bar") search = Puppet::Parser::Functions.function(:search) assert_nothing_raised do scope.function_search(["foo", "yay"]) end ffun = ffoo = nil assert_nothing_raised("Search path change did not work") do ffun = scope.finddefine("ness") ffoo = scope.finddefine('bar') end assert(ffun, "Could not find definition in 'fun' namespace") assert(ffoo, "Could not find definition in 'foo' namespace") end def test_include scope = mkscope parser = scope.compile.parser assert_raise(Puppet::ParseError, "did not throw error on missing class") do scope.function_include("nosuchclass") end parser.newclass("myclass") assert_nothing_raised do scope.function_include "myclass" end assert(scope.compile.resources.find { |r| r.to_s == "Class[myclass]" }, "class was not evaluated") # Now try multiple classes at once classes = %w{one two three}.each { |c| parser.newclass(c) } assert_nothing_raised do scope.function_include classes end classes.each do |c| assert(scope.compile.resources.find { |r| r.to_s == "Class[#{c}]" }, "class %s was not evaluated" % c) end # Now try a scoped class parser.newclass("os::redhat") assert_nothing_raised("Could not include qualified class name") do scope.function_include("os::redhat") end end def test_file parser = mkparser scope = mkscope(:parser => parser) file1 = tempfile file2 = tempfile file3 = tempfile File.open(file2, "w") { |f| f.puts "yaytest" } val = nil assert_nothing_raised("Failed to call file with one arg") do val = scope.function_file([file2]) end assert_equal("yaytest\n", val, "file() failed") assert_nothing_raised("Failed to call file with two args") do val = scope.function_file([file1, file2]) end assert_equal("yaytest\n", val, "file() failed") assert_raise(Puppet::ParseError, "did not fail when files are missing") do val = scope.function_file([file1, file3]) end end def test_generate command = tempfile sh = %x{which sh} File.open(command, "w") do |f| f.puts %{#!#{sh} if [ -n "$1" ]; then echo "yay-$1" else echo yay fi } end File.chmod(0755, command) assert_equal("yay\n", %x{#{command}}, "command did not work") assert_equal("yay-foo\n", %x{#{command} foo}, "command did not work") scope = mkscope parser = scope.compile.parser val = nil assert_nothing_raised("Could not call generator with no args") do val = scope.function_generate([command]) end assert_equal("yay\n", val, "generator returned wrong results") assert_nothing_raised("Could not call generator with args") do val = scope.function_generate([command, "foo"]) end assert_equal("yay-foo\n", val, "generator returned wrong results") assert_raise(Puppet::ParseError, "Did not fail with an unqualified path") do val = scope.function_generate([File.basename(command), "foo"]) end assert_raise(Puppet::ParseError, "Did not fail when command failed") do val = scope.function_generate([%x{which touch}.chomp, "/this/dir/does/not/exist"]) end fake = File.join(File.dirname(command), "..") dir = File.dirname(command) dirname = File.basename(dir) bad = File.join(dir, "..", dirname, File.basename(command)) assert_raise(Puppet::ParseError, "Did not fail when command failed") do val = scope.function_generate([bad]) end end end # $Id$ diff --git a/test/language/parser.rb b/test/language/parser.rb index 36e058b11..ddf35e3e3 100755 --- a/test/language/parser.rb +++ b/test/language/parser.rb @@ -1,1197 +1,1197 @@ #!/usr/bin/env ruby $:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/ require 'mocha' require 'puppet' require 'puppet/parser/parser' require 'puppettest' class TestParser < Test::Unit::TestCase include PuppetTest::ParserTesting def setup super Puppet[:parseonly] = true #@lexer = Puppet::Parser::Lexer.new() end def test_each_file textfiles { |file| parser = mkparser Puppet.debug("parsing %s" % file) if __FILE__ == $0 assert_nothing_raised() { parser.file = file parser.parse } Puppet::Type.eachtype { |type| type.each { |obj| assert(obj.file, "File is not set on %s" % obj.ref) assert(obj.name, "Name is not set on %s" % obj.ref) assert(obj.line, "Line is not set on %s" % obj.ref) } } Puppet::Type.allclear } end def test_failers failers { |file| parser = mkparser Puppet.debug("parsing failer %s" % file) if __FILE__ == $0 assert_raise(Puppet::ParseError, "Did not fail while parsing %s" % file) { parser.file = file ast = parser.parse config = mkconfig(parser) config.compile #ast.classes[""].evaluate :scope => config.topscope } Puppet::Type.allclear } end def test_arrayrvalues parser = mkparser ret = nil file = tempfile() assert_nothing_raised { parser.string = "file { \"#{file}\": mode => [755, 640] }" } assert_nothing_raised { ret = parser.parse } end def mkmanifest(file) name = File.join(tmpdir, "file%s" % rand(100)) @@tmpfiles << name File.open(file, "w") { |f| f.puts "file { \"%s\": ensure => file, mode => 755 }\n" % name } end def test_importglobbing basedir = File.join(tmpdir(), "importesting") @@tmpfiles << basedir Dir.mkdir(basedir) subdir = "subdir" Dir.mkdir(File.join(basedir, subdir)) manifest = File.join(basedir, "manifest") File.open(manifest, "w") { |f| f.puts "import \"%s/*\"" % subdir } 4.times { |i| path = File.join(basedir, subdir, "subfile%s" % i) mkmanifest(path) } assert_nothing_raised("Could not parse multiple files") { parser = mkparser parser.file = manifest parser.parse } end def test_nonexistent_import basedir = File.join(tmpdir(), "importesting") @@tmpfiles << basedir Dir.mkdir(basedir) manifest = File.join(basedir, "manifest") File.open(manifest, "w") do |f| f.puts "import \" no such file \"" end assert_raise(Puppet::ParseError) { parser = mkparser parser.file = manifest parser.parse } end def test_trailingcomma path = tempfile() str = %{file { "#{path}": ensure => file, } } parser = mkparser parser.string = str assert_nothing_raised("Could not parse trailing comma") { parser.parse } end def test_importedclasses imported = tempfile() importer = tempfile() made = tempfile() File.open(imported, "w") do |f| f.puts %{class foo { file { "#{made}": ensure => file }}} end File.open(importer, "w") do |f| f.puts %{import "#{imported}"\ninclude foo} end parser = mkparser parser.file = importer # Make sure it parses fine assert_nothing_raised { parser.parse } # Now make sure it actually does the work assert_creates(importer, made) end # Make sure fully qualified and unqualified files can be imported def test_fqfilesandlocalfiles dir = tempfile() Dir.mkdir(dir) importer = File.join(dir, "site.pp") fullfile = File.join(dir, "full.pp") localfile = File.join(dir, "local.pp") files = [] File.open(importer, "w") do |f| f.puts %{import "#{fullfile}"\ninclude full\nimport "local.pp"\ninclude local} end fullmaker = tempfile() files << fullmaker File.open(fullfile, "w") do |f| f.puts %{class full { file { "#{fullmaker}": ensure => file }}} end localmaker = tempfile() files << localmaker File.open(localfile, "w") do |f| f.puts %{class local { file { "#{localmaker}": ensure => file }}} end parser = mkparser parser.file = importer # Make sure it parses assert_nothing_raised { parser.parse } # Now make sure it actually does the work assert_creates(importer, *files) end # Make sure the parser adds '.pp' when necessary def test_addingpp dir = tempfile() Dir.mkdir(dir) importer = File.join(dir, "site.pp") localfile = File.join(dir, "local.pp") files = [] File.open(importer, "w") do |f| f.puts %{import "local"\ninclude local} end file = tempfile() files << file File.open(localfile, "w") do |f| f.puts %{class local { file { "#{file}": ensure => file }}} end parser = mkparser parser.file = importer assert_nothing_raised { parser.parse } end # Make sure that file importing changes file relative names. def test_changingrelativenames dir = tempfile() Dir.mkdir(dir) Dir.mkdir(File.join(dir, "subdir")) top = File.join(dir, "site.pp") subone = File.join(dir, "subdir/subone") subtwo = File.join(dir, "subdir/subtwo") files = [] file = tempfile() files << file File.open(subone + ".pp", "w") do |f| f.puts %{class one { file { "#{file}": ensure => file }}} end otherfile = tempfile() files << otherfile File.open(subtwo + ".pp", "w") do |f| f.puts %{import "subone"\n class two inherits one { file { "#{otherfile}": ensure => file } }} end File.open(top, "w") do |f| f.puts %{import "subdir/subtwo"} end parser = mkparser parser.file = top assert_nothing_raised { parser.parse } end # Defaults are purely syntactical, so it doesn't make sense to be able to # collect them. def test_uncollectabledefaults string = "@Port { protocols => tcp }" assert_raise(Puppet::ParseError) { mkparser.parse(string) } end # Verify that we can parse collections def test_collecting text = "Port <| |>" parser = mkparser parser.string = text ret = nil assert_nothing_raised { ret = parser.parse } ret.classes[""].code.each do |obj| assert_instance_of(AST::Collection, obj) end end def test_emptyfile file = tempfile() File.open(file, "w") do |f| f.puts %{} end parser = mkparser parser.file = file assert_nothing_raised { parser.parse } end def test_multiple_nodes_named file = tempfile() other = tempfile() File.open(file, "w") do |f| f.puts %{ node nodeA, nodeB { file { "#{other}": ensure => file } } } end parser = mkparser parser.file = file ast = nil assert_nothing_raised { ast = parser.parse } end def test_emptyarrays str = %{$var = []\n} parser = mkparser parser.string = str # Make sure it parses fine assert_nothing_raised { parser.parse } end # Make sure function names aren't reserved words. def test_functionnamecollision str = %{tag yayness tag(rahness) file { "/tmp/yayness": tag => "rahness", ensure => exists } } parser = mkparser parser.string = str # Make sure it parses fine assert_nothing_raised { parser.parse } end def test_metaparams_in_definition_prototypes parser = mkparser assert_raise(Puppet::ParseError) { parser.parse %{define mydef($schedule) {}} } assert_nothing_raised { parser.parse %{define adef($schedule = false) {}} parser.parse %{define mydef($schedule = daily) {}} } end def test_parsingif parser = mkparser exec = proc do |val| %{exec { "/bin/echo #{val}": logoutput => true }} end str1 = %{if true { #{exec.call("true")} }} ret = nil assert_nothing_raised { ret = parser.parse(str1).classes[""].code[0] } assert_instance_of(Puppet::Parser::AST::IfStatement, ret) parser = mkparser str2 = %{if true { #{exec.call("true")} } else { #{exec.call("false")} }} assert_nothing_raised { ret = parser.parse(str2).classes[""].code[0] } assert_instance_of(Puppet::Parser::AST::IfStatement, ret) assert_instance_of(Puppet::Parser::AST::Else, ret.else) end def test_hostclass parser = mkparser assert_nothing_raised { parser.parse %{class myclass { class other {} }} } assert(parser.classes["myclass"], "Could not find myclass") assert(parser.classes["myclass::other"], "Could not find myclass::other") assert_nothing_raised { parser.parse "class base {} class container { class deep::sub inherits base {} }" } sub = parser.classes["container::deep::sub"] assert(sub, "Could not find sub") # Now try it with a parent class being a fq class assert_nothing_raised { parser.parse "class container::one inherits container::deep::sub {}" } sub = parser.classes["container::one"] assert(sub, "Could not find one") assert_equal("container::deep::sub", sub.parentclass) # Finally, try including a qualified class assert_nothing_raised("Could not include fully qualified class") { parser.parse "include container::deep::sub" } end def test_topnamespace parser = mkparser # Make sure we put the top-level code into a class called "" in # the "" namespace assert_nothing_raised do out = parser.parse "" assert_instance_of(Puppet::Parser::Parser::ASTSet, out) assert_nil(parser.classes[""], "Got a 'main' class when we had no code") end # Now try something a touch more complicated parser.initvars assert_nothing_raised do out = parser.parse "Exec { path => '/usr/bin:/usr/sbin' }" assert_instance_of(Puppet::Parser::Parser::ASTSet, out) assert_equal("", parser.classes[""].classname) assert_equal("", parser.classes[""].namespace) end end # Make sure virtual and exported resources work appropriately. def test_virtualresources tests = [:virtual] if Puppet.features.rails? Puppet[:storeconfigs] = true tests << :exported end tests.each do |form| parser = mkparser if form == :virtual at = "@" else at = "@@" end check = proc do |res, msg| if res.is_a?(Puppet::Parser::Resource) txt = res.ref else txt = res.class end # Real resources get marked virtual when exported if form == :virtual or res.is_a?(Puppet::Parser::Resource) assert(res.virtual, "#{msg} #{at}#{txt} is not virtual") end if form == :virtual assert(! res.exported, "#{msg} #{at}#{txt} is exported") else assert(res.exported, "#{msg} #{at}#{txt} is not exported") end end ret = nil assert_nothing_raised do ret = parser.parse("#{at}file { '/tmp/testing': owner => root }") end assert_instance_of(AST::ASTArray, ret.classes[""].code) resdef = ret.classes[""].code[0] assert_instance_of(AST::ResourceDef, resdef) assert_equal("/tmp/testing", resdef.title.value) # We always get an astarray back, so... check.call(resdef, "simple resource") # Now let's try it with multiple resources in the same spec assert_nothing_raised do ret = parser.parse("#{at}file { ['/tmp/1', '/tmp/2']: owner => root }") end ret.classes[""].each do |res| assert_instance_of(AST::ResourceDef, res) check.call(res, "multiresource") end # Now evaluate these scope = mkscope klass = parser.newclass "" scope.source = klass assert_nothing_raised do ret.classes[""].evaluate :scope => scope, :resource => Puppet::Parser::Resource.new(:type => "mydefine", :title => 'whatever', :scope => scope, :source => scope.source) end # Make sure we can find both of them %w{/tmp/1 /tmp/2}.each do |title| res = scope.findresource("File[#{title}]") assert(res, "Could not find %s" % title) check.call(res, "found multiresource") end end end def test_collections tests = [:virtual] if Puppet.features.rails? Puppet[:storeconfigs] = true tests << :exported end tests.each do |form| parser = mkparser if form == :virtual arrow = "<||>" else arrow = "<<||>>" end ret = nil assert_nothing_raised do ret = parser.parse("File #{arrow}") end coll = ret.classes[""].code[0] assert_instance_of(AST::Collection, coll) assert_equal(form, coll.form) end end def test_collectionexpressions %w{== !=}.each do |oper| str = "File <| title #{oper} '/tmp/testing' |>" parser = mkparser res = nil assert_nothing_raised do res = parser.parse(str).classes[""].code[0] end assert_instance_of(AST::Collection, res) query = res.query assert_instance_of(AST::CollExpr, query) assert_equal(:virtual, query.form) assert_equal("title", query.test1.value) assert_equal("/tmp/testing", query.test2.value) assert_equal(oper, query.oper) end end def test_collectionstatements %w{and or}.each do |joiner| str = "File <| title == '/tmp/testing' #{joiner} owner == root |>" parser = mkparser res = nil assert_nothing_raised do res = parser.parse(str).classes[""].code[0] end assert_instance_of(AST::Collection, res) query = res.query assert_instance_of(AST::CollExpr, query) assert_equal(joiner, query.oper) assert_instance_of(AST::CollExpr, query.test1) assert_instance_of(AST::CollExpr, query.test2) end end def test_collectionstatements_with_parens [ "(title == '/tmp/testing' and owner == root) or owner == wheel", "(title == '/tmp/testing')" ].each do |test| str = "File <| #{test} |>" parser = mkparser res = nil assert_nothing_raised("Could not parse '#{test}'") do res = parser.parse(str).classes[""].code[0] end assert_instance_of(AST::Collection, res) query = res.query assert_instance_of(AST::CollExpr, query) #assert_equal(joiner, query.oper) #assert_instance_of(AST::CollExpr, query.test1) #assert_instance_of(AST::CollExpr, query.test2) end end # We've had problems with files other than site.pp importing into main. def test_importing_into_main top = tempfile() other = tempfile() File.open(top, "w") do |f| f.puts "import '#{other}'" end file = tempfile() File.open(other, "w") do |f| f.puts "file { '#{file}': ensure => present }" end interp = mkinterp :Manifest => top, :UseNodes => false code = nil assert_nothing_raised do - code = interp.compile(mknode).flatten + code = interp.compile(mknode).extract.flatten end assert(code.length == 1, "Did not get the file") assert_instance_of(Puppet::TransObject, code[0]) end def test_fully_qualified_definitions parser = mkparser assert_nothing_raised("Could not parse fully-qualified definition") { parser.parse %{define one::two { }} } assert(parser.definitions["one::two"], "Could not find one::two with no namespace") # Now try using the definition assert_nothing_raised("Could not parse fully-qualified definition usage") { parser.parse %{one::two { yayness: }} } end # #524 def test_functions_with_no_arguments parser = mkparser assert_nothing_raised("Could not parse statement function with no args") { parser.parse %{tag()} } assert_nothing_raised("Could not parse rvalue function with no args") { parser.parse %{$testing = template()} } end def test_module_import basedir = File.join(tmpdir(), "module-import") @@tmpfiles << basedir Dir.mkdir(basedir) modfiles = [ "init.pp", "mani1.pp", "mani2.pp", "sub/smani1.pp", "sub/smani2.pp" ] modpath = File.join(basedir, "modules") Puppet[:modulepath] = modpath modname = "amod" manipath = File::join(modpath, modname, Puppet::Module::MANIFESTS) FileUtils::mkdir_p(File::join(manipath, "sub")) targets = [] modfiles.each do |fname| target = File::join(basedir, File::basename(fname, '.pp')) targets << target txt = %[ file { '#{target}': content => "#{fname}" } ] if fname == "init.pp" txt = %[import 'mani1' \nimport '#{modname}/mani2'\nimport '#{modname}/sub/*.pp'\n ] + txt end File::open(File::join(manipath, fname), "w") do |f| f.puts txt end end manifest_texts = [ "import '#{modname}'", "import '#{modname}/init'", "import '#{modname}/init.pp'" ] manifest = File.join(modpath, "manifest.pp") manifest_texts.each do |txt| File.open(manifest, "w") { |f| f.puts txt } assert_nothing_raised { parser = mkparser parser.file = manifest parser.parse } assert_creates(manifest, *targets) end end # #544 def test_ignoreimports parser = mkparser assert(! Puppet[:ignoreimport], ":ignoreimport defaulted to true") assert_raise(Puppet::ParseError, "Did not fail on missing import") do parser.parse("import 'nosuchfile'") end assert_nothing_raised("could not set :ignoreimport") do Puppet[:ignoreimport] = true end assert_nothing_raised("Parser did not follow :ignoreimports") do parser.parse("import 'nosuchfile'") end end def test_multiple_imports_on_one_line one = tempfile two = tempfile base = tempfile File.open(one, "w") { |f| f.puts "$var = value" } File.open(two, "w") { |f| f.puts "$var = value" } File.open(base, "w") { |f| f.puts "import '#{one}', '#{two}'" } parser = mkparser parser.file = base # Importing is logged at debug time. Puppet::Util::Log.level = :debug assert_nothing_raised("Parser could not import multiple files at once") do parser.parse end [one, two].each do |file| assert(@logs.detect { |l| l.message =~ /importing '#{file}'/}, "did not import %s" % file) end end def test_cannot_assign_qualified_variables parser = mkparser assert_raise(Puppet::ParseError, "successfully assigned a qualified variable") do parser.parse("$one::two = yay") end end # #588 def test_globbing_with_directories dir = tempfile Dir.mkdir(dir) subdir = File.join(dir, "subdir") Dir.mkdir(subdir) file = File.join(dir, "file.pp") maker = tempfile File.open(file, "w") { |f| f.puts "file { '#{maker}': ensure => file }" } parser = mkparser assert_nothing_raised("Globbing failed when it matched a directory") do parser.import("%s/*" % dir) end end # #629 - undef keyword def test_undef parser = mkparser result = nil assert_nothing_raised("Could not parse assignment to undef") { result = parser.parse %{$variable = undef} } main = result.classes[""].code children = main.children assert_instance_of(AST::VarDef, main.children[0]) assert_instance_of(AST::Undef, main.children[0].value) end # Prompted by #729 -- parsing should not modify the interpreter. def test_parse parser = mkparser str = "file { '/tmp/yay': ensure => file }\nclass yay {}\nnode foo {}\ndefine bar {}\n" result = nil assert_nothing_raised("Could not parse") do result = parser.parse(str) end assert_instance_of(Puppet::Parser::Parser::ASTSet, result, "Did not get a ASTSet back from parsing") assert_instance_of(AST::HostClass, result.classes["yay"], "Did not create 'yay' class") assert_instance_of(AST::HostClass, result.classes[""], "Did not create main class") assert_instance_of(AST::Definition, result.definitions["bar"], "Did not create 'bar' definition") assert_instance_of(AST::Node, result.nodes["foo"], "Did not create 'foo' node") end # Make sure our node gets added to the node table. def test_newnode parser = mkparser # First just try calling it directly assert_nothing_raised { parser.newnode("mynode", :code => :yay) } assert_equal(:yay, parser.nodes["mynode"].code) # Now make sure that trying to redefine it throws an error. assert_raise(Puppet::ParseError) { parser.newnode("mynode", {}) } # Now try one with no code assert_nothing_raised { parser.newnode("simplenode", :parent => :foo) } # Now define the parent node parser.newnode(:foo) # And make sure we get things back correctly assert_equal(:foo, parser.nodes["simplenode"].parentclass) assert_nil(parser.nodes["simplenode"].code) # Now make sure that trying to redefine it throws an error. assert_raise(Puppet::ParseError) { parser.newnode("mynode", {}) } # Test multiple names names = ["one", "two", "three"] assert_nothing_raised { parser.newnode(names, {:code => :yay, :parent => :foo}) } names.each do |name| assert_equal(:yay, parser.nodes[name].code) assert_equal(:foo, parser.nodes[name].parentclass) # Now make sure that trying to redefine it throws an error. assert_raise(Puppet::ParseError) { parser.newnode(name, {}) } end end def test_newdefine parser = mkparser assert_nothing_raised { parser.newdefine("mydefine", :code => :yay, :arguments => ["a", stringobj("b")]) } mydefine = parser.definitions["mydefine"] assert(mydefine, "Could not find definition") assert_equal("", mydefine.namespace) assert_equal("mydefine", mydefine.classname) assert_raise(Puppet::ParseError) do parser.newdefine("mydefine", :code => :yay, :arguments => ["a", stringobj("b")]) end # Now define the same thing in a different scope assert_nothing_raised { parser.newdefine("other::mydefine", :code => :other, :arguments => ["a", stringobj("b")]) } other = parser.definitions["other::mydefine"] assert(other, "Could not find definition") assert(parser.definitions["other::mydefine"], "Could not find other::mydefine") assert_equal(:other, other.code) assert_equal("other", other.namespace) assert_equal("other::mydefine", other.classname) end def test_newclass scope = mkscope parser = scope.compile.parser mkcode = proc do |ary| classes = ary.collect do |string| AST::FlatString.new(:value => string) end AST::ASTArray.new(:children => classes) end # First make sure that code is being appended code = mkcode.call(%w{original code}) klass = nil assert_nothing_raised { klass = parser.newclass("myclass", :code => code) } assert(klass, "Did not return class") assert(parser.classes["myclass"], "Could not find definition") assert_equal("myclass", parser.classes["myclass"].classname) assert_equal(%w{original code}, parser.classes["myclass"].code.evaluate(:scope => scope)) # Newclass behaves differently than the others -- it just appends # the code to the existing class. code = mkcode.call(%w{something new}) assert_nothing_raised do klass = parser.newclass("myclass", :code => code) end assert(klass, "Did not return class when appending") assert_equal(%w{original code something new}, parser.classes["myclass"].code.evaluate(:scope => scope)) # Now create the same class name in a different scope assert_nothing_raised { klass = parser.newclass("other::myclass", :code => mkcode.call(%w{something diff})) } assert(klass, "Did not return class") other = parser.classes["other::myclass"] assert(other, "Could not find class") assert_equal("other::myclass", other.classname) assert_equal("other::myclass", other.namespace) assert_equal(%w{something diff}, other.code.evaluate(:scope => scope)) # Make sure newclass deals correctly with nodes with no code klass = parser.newclass("nocode") assert(klass, "Did not return class") assert_nothing_raised do klass = parser.newclass("nocode", :code => mkcode.call(%w{yay test})) end assert(klass, "Did not return class with no code") assert_equal(%w{yay test}, parser.classes["nocode"].code.evaluate(:scope => scope)) # Then try merging something into nothing parser.newclass("nocode2", :code => mkcode.call(%w{foo test})) assert(klass, "Did not return class with no code") assert_nothing_raised do klass = parser.newclass("nocode2") end assert(klass, "Did not return class with no code") assert_equal(%w{foo test}, parser.classes["nocode2"].code.evaluate(:scope => scope)) # And lastly, nothing and nothing klass = parser.newclass("nocode3") assert(klass, "Did not return class with no code") assert_nothing_raised do klass = parser.newclass("nocode3") end assert(klass, "Did not return class with no code") assert_nil(parser.classes["nocode3"].code) end # Make sure you can't have classes and defines with the same name in the # same scope. def test_classes_beat_defines parser = mkparser assert_nothing_raised { parser.newclass("yay::funtest") } assert_raise(Puppet::ParseError) do parser.newdefine("yay::funtest") end assert_nothing_raised { parser.newdefine("yay::yaytest") } assert_raise(Puppet::ParseError) do parser.newclass("yay::yaytest") end end def test_namesplit parser = mkparser assert_nothing_raised do {"base::sub" => %w{base sub}, "main" => ["", "main"], "one::two::three::four" => ["one::two::three", "four"], }.each do |name, ary| result = parser.namesplit(name) assert_equal(ary, result, "%s split to %s" % [name, result]) end end end # Now make sure we get appropriate behaviour with parent class conflicts. def test_newclass_parentage parser = mkparser parser.newclass("base1") parser.newclass("one::two::three") # First create it with no parentclass. assert_nothing_raised { parser.newclass("sub") } assert(parser.classes["sub"], "Could not find definition") assert_nil(parser.classes["sub"].parentclass) # Make sure we can't set the parent class to ourself. assert_raise(Puppet::ParseError) { parser.newclass("sub", :parent => "sub") } # Now create another one, with a parentclass. assert_nothing_raised { parser.newclass("sub", :parent => "base1") } # Make sure we get the right parent class, and make sure it's not an object. assert_equal("base1", parser.classes["sub"].parentclass) # Now make sure we get a failure if we try to conflict. assert_raise(Puppet::ParseError) { parser.newclass("sub", :parent => "one::two::three") } # Make sure that failure didn't screw us up in any way. assert_equal("base1", parser.classes["sub"].parentclass) # But make sure we can create a class with a fq parent assert_nothing_raised { parser.newclass("another", :parent => "one::two::three") } assert_equal("one::two::three", parser.classes["another"].parentclass) end def test_fqfind parser = mkparser table = {} # Define a bunch of things. %w{a c a::b a::b::c a::c a::b::c::d a::b::c::d::e::f c::d}.each do |string| table[string] = string end check = proc do |namespace, hash| hash.each do |thing, result| assert_equal(result, parser.fqfind(namespace, thing, table), "Could not find %s in %s" % [thing, namespace]) end end # Now let's do some test lookups. # First do something really simple check.call "a", "b" => "a::b", "b::c" => "a::b::c", "d" => nil, "::c" => "c" check.call "a::b", "c" => "a::b::c", "b" => "a::b", "a" => "a" check.call "a::b::c::d::e", "c" => "a::b::c", "::c" => "c", "c::d" => "a::b::c::d", "::c::d" => "c::d" check.call "", "a" => "a", "a::c" => "a::c" end # Setup a module. def mk_module(name, files = {}) mdir = File.join(@dir, name) mandir = File.join(mdir, "manifests") FileUtils.mkdir_p mandir if defs = files[:define] files.delete(:define) end Dir.chdir(mandir) do files.each do |file, classes| File.open("%s.pp" % file, "w") do |f| classes.each { |klass| if defs f.puts "define %s {}" % klass else f.puts "class %s {}" % klass end } end end end end # #596 - make sure classes and definitions load automatically if they're in modules, so we don't have to manually load each one. def test_module_autoloading @dir = tempfile Puppet[:modulepath] = @dir FileUtils.mkdir_p @dir parser = mkparser # Make sure we fail like normal for actually missing classes assert_nil(parser.findclass("", "nosuchclass"), "Did not return nil on missing classes") # test the simple case -- the module class itself name = "simple" mk_module(name, :init => [name]) # Try to load the module automatically now klass = parser.findclass("", name) assert_instance_of(AST::HostClass, klass, "Did not autoload class from module init file") assert_equal(name, klass.classname, "Incorrect class was returned") # Try loading the simple module when we're in something other than the base namespace. parser = mkparser klass = parser.findclass("something::else", name) assert_instance_of(AST::HostClass, klass, "Did not autoload class from module init file") assert_equal(name, klass.classname, "Incorrect class was returned") # Now try it with a definition as the base file name = "simpdef" mk_module(name, :define => true, :init => [name]) klass = parser.finddefine("", name) assert_instance_of(AST::Definition, klass, "Did not autoload class from module init file") assert_equal(name, klass.classname, "Incorrect class was returned") # Now try it with namespace classes where both classes are in the init file parser = mkparser modname = "both" name = "sub" mk_module(modname, :init => %w{both both::sub}) # First try it with a namespace klass = parser.findclass("both", name) assert_instance_of(AST::HostClass, klass, "Did not autoload sub class from module init file with a namespace") assert_equal("both::sub", klass.classname, "Incorrect class was returned") # Now try it using the fully qualified name parser = mkparser klass = parser.findclass("", "both::sub") assert_instance_of(AST::HostClass, klass, "Did not autoload sub class from module init file with no namespace") assert_equal("both::sub", klass.classname, "Incorrect class was returned") # Now try it with the class in a different file parser = mkparser modname = "separate" name = "sub" mk_module(modname, :init => %w{separate}, :sub => %w{separate::sub}) # First try it with a namespace klass = parser.findclass("separate", name) assert_instance_of(AST::HostClass, klass, "Did not autoload sub class from separate file with a namespace") assert_equal("separate::sub", klass.classname, "Incorrect class was returned") # Now try it using the fully qualified name parser = mkparser klass = parser.findclass("", "separate::sub") assert_instance_of(AST::HostClass, klass, "Did not autoload sub class from separate file with no namespace") assert_equal("separate::sub", klass.classname, "Incorrect class was returned") # Now make sure we don't get a failure when there's no module file parser = mkparser modname = "alone" name = "sub" mk_module(modname, :sub => %w{alone::sub}) # First try it with a namespace assert_nothing_raised("Could not autoload file when module file is missing") do klass = parser.findclass("alone", name) end assert_instance_of(AST::HostClass, klass, "Did not autoload sub class from alone file with a namespace") assert_equal("alone::sub", klass.classname, "Incorrect class was returned") # Now try it using the fully qualified name parser = mkparser klass = parser.findclass("", "alone::sub") assert_instance_of(AST::HostClass, klass, "Did not autoload sub class from alone file with no namespace") assert_equal("alone::sub", klass.classname, "Incorrect class was returned") end # Make sure class, node, and define methods are case-insensitive def test_structure_case_insensitivity parser = mkparser result = nil assert_nothing_raised do result = parser.newclass "Yayness" end assert_equal(result, parser.findclass("", "yayNess")) assert_nothing_raised do result = parser.newdefine "FunTest" end assert_equal(result, parser.finddefine("", "fUntEst"), "%s was not matched" % "fUntEst") end end # $Id$ diff --git a/test/language/scope.rb b/test/language/scope.rb index 213226241..b317fa4c8 100755 --- a/test/language/scope.rb +++ b/test/language/scope.rb @@ -1,518 +1,518 @@ #!/usr/bin/env ruby $:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/ require 'mocha' require 'puppettest' require 'puppettest/parsertesting' require 'puppettest/resourcetesting' # so, what kind of things do we want to test? # we don't need to test function, since we're confident in the # library tests. We do, however, need to test how things are actually # working in the language. # so really, we want to do things like test that our ast is correct # and test whether we've got things in the right scopes class TestScope < Test::Unit::TestCase include PuppetTest::ParserTesting include PuppetTest::ResourceTesting def to_ary(hash) hash.collect { |key,value| [key,value] } end def test_variables config = mkconfig topscope = config.topscope midscope = config.newscope(topscope) botscope = config.newscope(midscope) scopes = {:top => topscope, :mid => midscope, :bot => botscope} # Set a variable in the top and make sure all three can get it topscope.setvar("first", "topval") scopes.each do |name, scope| assert_equal("topval", scope.lookupvar("first", false), "Could not find var in %s" % name) end # Now set a var in the midscope and make sure the mid and bottom can see it but not the top midscope.setvar("second", "midval") assert_equal(:undefined, scopes[:top].lookupvar("second", false), "Found child var in top scope") [:mid, :bot].each do |name| assert_equal("midval", scopes[name].lookupvar("second", false), "Could not find var in %s" % name) end # And set something in the bottom, and make sure we only find it there. botscope.setvar("third", "botval") [:top, :mid].each do |name| assert_equal(:undefined, scopes[name].lookupvar("third", false), "Found child var in top scope") end assert_equal("botval", scopes[:bot].lookupvar("third", false), "Could not find var in bottom scope") end def test_lookupvar parser = mkparser scope = mkscope :parser => parser # first do the plain lookups assert_equal("", scope.lookupvar("var"), "scope did not default to string") assert_equal("", scope.lookupvar("var", true), "scope ignored usestring setting") assert_equal(:undefined, scope.lookupvar("var", false), "scope ignored usestring setting when false") # Now set the var scope.setvar("var", "yep") assert_equal("yep", scope.lookupvar("var"), "did not retrieve value correctly") # Now test the parent lookups subscope = mkscope :parser => parser subscope.parent = scope assert_equal("", subscope.lookupvar("nope"), "scope did not default to string with parent") assert_equal("", subscope.lookupvar("nope", true), "scope ignored usestring setting with parent") assert_equal(:undefined, subscope.lookupvar("nope", false), "scope ignored usestring setting when false with parent") assert_equal("yep", subscope.lookupvar("var"), "did not retrieve value correctly from parent") # Now override the value in the subscope subscope.setvar("var", "sub") assert_equal("sub", subscope.lookupvar("var"), "did not retrieve overridden value correctly") # Make sure we punt when the var is qualified. Specify the usestring value, so we know it propagates. scope.expects(:lookup_qualified_var).with("one::two", false).returns(:punted) assert_equal(:punted, scope.lookupvar("one::two", false), "did not return the value of lookup_qualified_var") end def test_lookup_qualified_var parser = mkparser scope = mkscope :parser => parser scopes = {} classes = ["", "one", "one::two", "one::two::three"].each do |name| klass = parser.newclass(name) Puppet::Parser::Resource.new(:type => "class", :title => name, :scope => scope, :source => mock('source')).evaluate scopes[name] = scope.compile.class_scope(klass) end classes.each do |name| var = [name, "var"].join("::") scopes[name].expects(:lookupvar).with("var", false).returns(name) assert_equal(name, scope.send(:lookup_qualified_var, var, false), "did not get correct value from lookupvar") end end def test_declarative # set to declarative top = mkscope sub = mkscope(:parent => top) assert_nothing_raised { top.setvar("test","value") } assert_raise(Puppet::ParseError) { top.setvar("test","other") } assert_nothing_raised { sub.setvar("test","later") } assert_raise(Puppet::ParseError) { top.setvar("test","yeehaw") } end def test_setdefaults config = mkconfig scope = config.topscope defaults = scope.instance_variable_get("@defaults") # First the case where there are no defaults and we pass a single param param = stub :name => "myparam", :file => "f", :line => "l" scope.setdefaults(:mytype, param) assert_equal({"myparam" => param}, defaults[:mytype], "Did not set default correctly") # Now the case where we pass in multiple parameters param1 = stub :name => "one", :file => "f", :line => "l" param2 = stub :name => "two", :file => "f", :line => "l" scope.setdefaults(:newtype, [param1, param2]) assert_equal({"one" => param1, "two" => param2}, defaults[:newtype], "Did not set multiple defaults correctly") # And the case where there's actually a conflict. Use the first default for this. newparam = stub :name => "myparam", :file => "f", :line => "l" assert_raise(Puppet::ParseError, "Allowed resetting of defaults") do scope.setdefaults(:mytype, param) end assert_equal({"myparam" => param}, defaults[:mytype], "Replaced default even though there was a failure") end def test_lookupdefaults config = mkconfig top = config.topscope # Make a subscope sub = config.newscope(top) topdefs = top.instance_variable_get("@defaults") subdefs = sub.instance_variable_get("@defaults") # First add some defaults to our top scope topdefs[:t1] = {:p1 => :p2, :p3 => :p4} topdefs[:t2] = {:p5 => :p6} # Then the sub scope subdefs[:t1] = {:p1 => :p7, :p8 => :p9} subdefs[:t2] = {:p5 => :p10, :p11 => :p12} # Now make sure we get the correct list back result = nil assert_nothing_raised("Could not get defaults") do result = sub.lookupdefaults(:t1) end assert_equal(:p9, result[:p8], "Did not get child defaults") assert_equal(:p4, result[:p3], "Did not override parent defaults with child default") assert_equal(:p7, result[:p1], "Did not get parent defaults") end def test_parent config = mkconfig top = config.topscope # Make a subscope sub = config.newscope(top) assert_equal(top, sub.parent, "Did not find parent scope correctly") assert_equal(top, sub.parent, "Did not find parent scope on second call") end def test_strinterp # Make and evaluate our classes so the qualified lookups work parser = mkparser klass = parser.newclass("") scope = mkscope(:parser => parser) Puppet::Parser::Resource.new(:type => "class", :title => :main, :scope => scope, :source => mock('source')).evaluate assert_nothing_raised { scope.setvar("test","value") } scopes = {"" => scope} %w{one one::two one::two::three}.each do |name| klass = parser.newclass(name) Puppet::Parser::Resource.new(:type => "class", :title => name, :scope => scope, :source => mock('source')).evaluate scopes[name] = scope.compile.class_scope(klass) scopes[name].setvar("test", "value-%s" % name.sub(/.+::/,'')) end assert_equal("value", scope.lookupvar("::test"), "did not look up qualified value correctly") tests = { "string ${test}" => "string value", "string ${one::two::three::test}" => "string value-three", "string $one::two::three::test" => "string value-three", "string ${one::two::test}" => "string value-two", "string $one::two::test" => "string value-two", "string ${one::test}" => "string value-one", "string $one::test" => "string value-one", "string ${::test}" => "string value", "string $::test" => "string value", "string ${test} ${test} ${test}" => "string value value value", "string $test ${test} $test" => "string value value value", "string \\$test" => "string $test", '\\$test string' => "$test string", '$test string' => "value string", 'a testing $' => "a testing $", 'a testing \$' => "a testing $", "an escaped \\\n carriage return" => "an escaped carriage return", '\$' => "$", '\s' => "\s", '\t' => "\t", '\n' => "\n" } tests.each do |input, output| assert_nothing_raised("Failed to scan %s" % input.inspect) do assert_equal(output, scope.strinterp(input), 'did not parserret %s correctly' % input.inspect) end end logs = [] Puppet::Util::Log.close Puppet::Util::Log.newdestination(logs) # #523 %w{d f h l w z}.each do |l| string = "\\" + l assert_nothing_raised do assert_equal(string, scope.strinterp(string), 'did not parserret %s correctly' % string) end assert(logs.detect { |m| m.message =~ /Unrecognised escape/ }, "Did not get warning about escape sequence with %s" % string) logs.clear end end def test_tagfunction scope = mkscope resource = mock 'resource' scope.resource = resource resource.expects(:tag).with("yayness", "booness") scope.function_tag(%w{yayness booness}) end def test_includefunction parser = mkparser scope = mkscope :parser => parser myclass = parser.newclass "myclass" otherclass = parser.newclass "otherclass" function = Puppet::Parser::AST::Function.new( :name => "include", :ftype => :statement, :arguments => AST::ASTArray.new( :children => [nameobj("myclass"), nameobj("otherclass")] ) ) assert_nothing_raised do function.evaluate :scope => scope end scope.compile.send(:evaluate_generators) [myclass, otherclass].each do |klass| assert(scope.compile.class_scope(klass), "%s was not set" % klass.classname) end end def test_definedfunction parser = mkparser %w{one two}.each do |name| parser.newdefine name end scope = mkscope :parser => parser assert_nothing_raised { %w{one two file user}.each do |type| assert(scope.function_defined([type]), "Class #{type} was not considered defined") end assert(!scope.function_defined(["nopeness"]), "Class 'nopeness' was incorrectly considered defined") } end # Make sure we know what we consider to be truth. def test_truth assert_equal(true, Puppet::Parser::Scope.true?("a string"), "Strings not considered true") assert_equal(true, Puppet::Parser::Scope.true?(true), "True considered true") assert_equal(false, Puppet::Parser::Scope.true?(""), "Empty strings considered true") assert_equal(false, Puppet::Parser::Scope.true?(false), "false considered true") assert_equal(false, Puppet::Parser::Scope.true?(:undef), "undef considered true") end if defined? ActiveRecord # Verify that we recursively mark as exported the results of collectable # components. def test_exportedcomponents config = mkconfig parser = config.parser # Create a default source config.topscope.source = parser.newclass "", "" # And a scope resource scope_res = stub 'scope_resource', :virtual? => true, :exported? => false, :tags => [] config.topscope.resource = scope_res args = AST::ASTArray.new( :file => tempfile(), :line => rand(100), :children => [nameobj("arg")] ) # Create a top-level component parser.newdefine "one", :arguments => [%w{arg}], :code => AST::ASTArray.new( :children => [ resourcedef("file", "/tmp", {"owner" => varref("arg")}) ] ) # And a component that calls it parser.newdefine "two", :arguments => [%w{arg}], :code => AST::ASTArray.new( :children => [ resourcedef("one", "ptest", {"arg" => varref("arg")}) ] ) # And then a third component that calls the second parser.newdefine "three", :arguments => [%w{arg}], :code => AST::ASTArray.new( :children => [ resourcedef("two", "yay", {"arg" => varref("arg")}) ] ) # lastly, create an object that calls our third component obj = resourcedef("three", "boo", {"arg" => "parentfoo"}) # And mark it as exported obj.exported = true # And then evaluate it obj.evaluate :scope => config.topscope # And run the loop. config.send(:evaluate_generators) %w{file}.each do |type| objects = config.resources.find_all { |r| r.type == type and r.exported } assert(!objects.empty?, "Did not get an exported %s" % type) end end # Verify that we can both store and collect an object in the same # run, whether it's in the same scope as a collection or a different # scope. def test_storeandcollect Puppet[:storeconfigs] = true Puppet::Rails.init sleep 1 children = [] file = tempfile() File.open(file, "w") { |f| f.puts " class yay { @@host { myhost: ip => \"192.168.0.2\" } } include yay @@host { puppet: ip => \"192.168.0.3\" } Host <<||>>" } interp = nil assert_nothing_raised { interp = Puppet::Parser::Interpreter.new( :Manifest => file, :UseNodes => false, :ForkSave => false ) } - objects = nil + config = nil # We run it twice because we want to make sure there's no conflict # if we pull it up from the database. node = mknode node.parameters = {"hostname" => node.name} 2.times { |i| assert_nothing_raised { - objects = interp.compile(node) + config = interp.compile(node) } - flat = objects.flatten + flat = config.extract.flatten %w{puppet myhost}.each do |name| assert(flat.find{|o| o.name == name }, "Did not find #{name}") end } end else $stderr.puts "No ActiveRecord -- skipping collection tests" end def test_namespaces - parser, scope, source = mkclassframing + scope = mkscope assert_equal([""], scope.namespaces, "Started out with incorrect namespaces") assert_nothing_raised { scope.add_namespace("fun::test") } assert_equal(["fun::test"], scope.namespaces, "Did not add namespace correctly") assert_nothing_raised { scope.add_namespace("yay::test") } assert_equal(["fun::test", "yay::test"], scope.namespaces, "Did not add extra namespace correctly") end def test_findclass_and_finddefine parser = mkparser # Make sure our scope calls the parser findclass method with # the right namespaces scope = mkscope :parser => parser parser.metaclass.send(:attr_accessor, :last) methods = [:findclass, :finddefine] methods.each do |m| parser.meta_def(m) do |namespace, name| @checked ||= [] @checked << [namespace, name] # Only return a value on the last call. if @last == namespace ret = @checked.dup @checked.clear return ret else return nil end end end test = proc do |should| parser.last = scope.namespaces[-1] methods.each do |method| result = scope.send(method, "testing") assert_equal(should, result, "did not get correct value from %s with namespaces %s" % [method, scope.namespaces.inspect]) end end # Start with the empty namespace assert_nothing_raised { test.call([["", "testing"]]) } # Now add a namespace scope.add_namespace("a") assert_nothing_raised { test.call([["a", "testing"]]) } # And another scope.add_namespace("b") assert_nothing_raised { test.call([["a", "testing"], ["b", "testing"]]) } end # #629 - undef should be "" or :undef def test_lookupvar_with_undef scope = mkscope scope.setvar("testing", :undef) assert_equal(:undef, scope.lookupvar("testing", false), "undef was not returned as :undef when not string") assert_equal("", scope.lookupvar("testing", true), "undef was not returned as '' when string") end end # $Id$ diff --git a/test/lib/puppettest/parsertesting.rb b/test/lib/puppettest/parsertesting.rb index 9e90bbdd6..b8fd8bd83 100644 --- a/test/lib/puppettest/parsertesting.rb +++ b/test/lib/puppettest/parsertesting.rb @@ -1,408 +1,408 @@ require 'puppettest' require 'puppet/rails' module PuppetTest::ParserTesting include PuppetTest AST = Puppet::Parser::AST Compile = Puppet::Parser::Compile # A fake class that we can use for testing evaluation. class FakeAST attr_writer :evaluate def evaluated? defined? @evaluated and @evaluated end def evaluate(*args) @evaluated = true return @evaluate end def initialize(val = nil) if val @evaluate = val end end def reset @evaluated = nil end def safeevaluate(*args) evaluate() end end def astarray(*args) AST::ASTArray.new( :children => args ) end def mkconfig(parser = nil) require 'puppet/network/handler/node' parser ||= mkparser node = mknode return Compile.new(node, parser) end def mknode(name = nil) require 'puppet/node' name ||= "nodename" Puppet::Network::Handler.handler(:node) Puppet::Node.new(name) end def mkinterp(args = {}) args[:Code] ||= "" unless args.include?(:Manifest) args[:Local] ||= true Puppet::Parser::Interpreter.new(args) end def mkparser Puppet::Parser::Parser.new() end def mkscope(hash = {}) hash[:parser] ||= mkparser config ||= mkconfig(hash[:parser]) config.topscope.source = (hash[:parser].findclass("", "") || hash[:parser].newclass("")) unless config.topscope.source raise "Could not find source for scope" end # Make the 'main' stuff config.send(:evaluate_main) config.topscope end def classobj(name, hash = {}) hash[:file] ||= __FILE__ hash[:line] ||= __LINE__ hash[:type] ||= name AST::HostClass.new(hash) end def tagobj(*names) args = {} newnames = names.collect do |name| if name.is_a? AST name else nameobj(name) end end args[:type] = astarray(*newnames) assert_nothing_raised("Could not create tag %s" % names.inspect) { return AST::Tag.new(args) } end def resourcedef(type, title, params) unless title.is_a?(AST) title = stringobj(title) end assert_nothing_raised("Could not create %s %s" % [type, title]) { return AST::ResourceDef.new( :file => __FILE__, :line => __LINE__, :title => title, :type => type, :params => resourceinst(params) ) } end def virt_resourcedef(*args) res = resourcedef(*args) res.virtual = true res end def resourceoverride(type, title, params) assert_nothing_raised("Could not create %s %s" % [type, name]) { return AST::ResourceOverride.new( :file => __FILE__, :line => __LINE__, :object => resourceref(type, title), :type => type, :params => resourceinst(params) ) } end def resourceref(type, title) assert_nothing_raised("Could not create %s %s" % [type, title]) { return AST::ResourceRef.new( :file => __FILE__, :line => __LINE__, :type => type, :title => stringobj(title) ) } end def fileobj(path, hash = {"owner" => "root"}) assert_nothing_raised("Could not create file %s" % path) { return resourcedef("file", path, hash) } end def nameobj(name) assert_nothing_raised("Could not create name %s" % name) { return AST::Name.new( :file => tempfile(), :line => rand(100), :value => name ) } end def typeobj(name) assert_nothing_raised("Could not create type %s" % name) { return AST::Type.new( :file => tempfile(), :line => rand(100), :value => name ) } end def nodedef(name) assert_nothing_raised("Could not create node %s" % name) { return AST::NodeDef.new( :file => tempfile(), :line => rand(100), :names => nameobj(name), :code => AST::ASTArray.new( :children => [ varobj("%svar" % name, "%svalue" % name), fileobj("/%s" % name) ] ) ) } end def resourceinst(hash) assert_nothing_raised("Could not create resource instance") { params = hash.collect { |param, value| resourceparam(param, value) } return AST::ResourceInst.new( :file => tempfile(), :line => rand(100), :children => params ) } end def resourceparam(param, value) # Allow them to pass non-strings in if value.is_a?(String) value = stringobj(value) end assert_nothing_raised("Could not create param %s" % param) { return AST::ResourceParam.new( :file => tempfile(), :line => rand(100), :param => param, :value => value ) } end def stringobj(value) AST::String.new( :file => tempfile(), :line => rand(100), :value => value ) end def varobj(name, value) unless value.is_a? AST value = stringobj(value) end assert_nothing_raised("Could not create %s code" % name) { return AST::VarDef.new( :file => tempfile(), :line => rand(100), :name => nameobj(name), :value => value ) } end def varref(name) assert_nothing_raised("Could not create %s variable" % name) { return AST::Variable.new( :file => __FILE__, :line => __LINE__, :value => name ) } end def argobj(name, value) assert_nothing_raised("Could not create %s compargument" % name) { return AST::CompArgument.new( :children => [nameobj(name), stringobj(value)] ) } end def defaultobj(type, params) pary = [] params.each { |p,v| pary << AST::ResourceParam.new( :file => __FILE__, :line => __LINE__, :param => p, :value => stringobj(v) ) } past = AST::ASTArray.new( :file => __FILE__, :line => __LINE__, :children => pary ) assert_nothing_raised("Could not create defaults for %s" % type) { return AST::ResourceDefaults.new( :file => __FILE__, :line => __LINE__, :type => type, :params => past ) } end def taggedobj(name, ftype = :statement) functionobj("tagged", name, ftype) end def functionobj(function, name, ftype = :statement) func = nil assert_nothing_raised do func = Puppet::Parser::AST::Function.new( :name => function, :ftype => ftype, :arguments => AST::ASTArray.new( :children => [nameobj(name)] ) ) end return func end # This assumes no nodes def assert_creates(manifest, *files) interp = nil assert_nothing_raised { interp = Puppet::Parser::Interpreter.new( :Manifest => manifest, :UseNodes => false ) } config = nil assert_nothing_raised { config = interp.compile(mknode) } comp = nil assert_nothing_raised { - comp = config.to_type + comp = config.extract.to_type } assert_apply(comp) files.each do |file| assert(FileTest.exists?(file), "Did not create %s" % file) end end def mk_transobject(file = "/etc/passwd") obj = nil assert_nothing_raised { obj = Puppet::TransObject.new("file", file) obj["owner"] = "root" obj["mode"] = "644" } return obj end def mk_transbucket(*resources) bucket = nil assert_nothing_raised { bucket = Puppet::TransBucket.new bucket.name = "yayname" bucket.type = "yaytype" } resources.each { |o| bucket << o } return bucket end # Make a tree of resources, yielding if desired def mk_transtree(depth = 4, width = 2) top = nil assert_nothing_raised { top = Puppet::TransBucket.new top.name = "top" top.type = "bucket" } bucket = top file = tempfile() depth.times do |i| resources = [] width.times do |j| path = tempfile + i.to_s obj = Puppet::TransObject.new("file", path) obj["owner"] = "root" obj["mode"] = "644" # Yield, if they want if block_given? yield(obj, i, j) end resources << obj end newbucket = mk_transbucket(*resources) bucket.push newbucket bucket = newbucket end return top end # Take a list of AST resources, evaluate them, and return the results def assert_evaluate(children) top = nil assert_nothing_raised("Could not create top object") { top = AST::ASTArray.new( :children => children ) } trans = nil scope = nil assert_nothing_raised { scope = Puppet::Parser::Scope.new() trans = scope.evaluate(:ast => top) } return trans end end diff --git a/test/lib/puppettest/resourcetesting.rb b/test/lib/puppettest/resourcetesting.rb index 54154d504..325602c22 100644 --- a/test/lib/puppettest/resourcetesting.rb +++ b/test/lib/puppettest/resourcetesting.rb @@ -1,61 +1,46 @@ module PuppetTest::ResourceTesting Parser = Puppet::Parser AST = Puppet::Parser::AST - def mkclassframing(parser = nil) - parser ||= mkparser - - parser.newdefine("resource", :arguments => [%w{one}, %w{two value}, %w{three}]) - parser.newclass("") - source = parser.newclass("base") - parser.newclass("sub1", :parent => "base") - parser.newclass("sub2", :parent => "base") - parser.newclass("other") - - config = mkconfig(:parser => parser) - config.topscope.source = source - - return parser, config.topscope, source - end def mkevaltest(parser = nil) parser ||= mkparser @parser.newdefine("evaltest", :arguments => [%w{one}, ["two", stringobj("755")]], :code => resourcedef("file", "/tmp", "owner" => varref("one"), "mode" => varref("two")) ) end def mkresource(args = {}) args[:source] ||= "source" args[:scope] ||= mkscope {:type => "resource", :title => "testing", :source => "source", :scope => "scope"}.each do |param, value| args[param] ||= value end params = args[:params] || {:one => "yay", :three => "rah"} if args[:params] == :none args.delete(:params) else args[:params] = paramify args[:source], params end Parser::Resource.new(args) end def param(name, value, source) Parser::Resource::Param.new(:name => name, :value => value, :source => source) end def paramify(source, hash) hash.collect do |name, value| Parser::Resource::Param.new( :name => name, :value => value, :source => source ) end end end # $Id$