diff --git a/lib/puppet/parser/ast/collection.rb b/lib/puppet/parser/ast/collection.rb index 31f508929..9e795a33c 100644 --- a/lib/puppet/parser/ast/collection.rb +++ b/lib/puppet/parser/ast/collection.rb @@ -1,26 +1,26 @@ require 'puppet' require 'puppet/parser/ast/branch' require 'puppet/parser/collector' # An object that collects stored objects from the central cache and returns # them to the current host, yo. class Puppet::Parser::AST class Collection < AST::Branch attr_accessor :type, :query, :form # We return an object that does a late-binding evaluation. def evaluate(scope) if self.query str, code = self.query.safeevaluate scope else str = code = nil end newcoll = Puppet::Parser::Collector.new(scope, @type, str, code, self.form) - scope.compile.add_collection(newcoll) + scope.compiler.add_collection(newcoll) newcoll end end end diff --git a/lib/puppet/parser/ast/definition.rb b/lib/puppet/parser/ast/definition.rb index 992bb1f5e..b4a90016a 100644 --- a/lib/puppet/parser/ast/definition.rb +++ b/lib/puppet/parser/ast/definition.rb @@ -1,204 +1,204 @@ require 'puppet/parser/ast/branch' require 'puppet/util/warnings' # The AST class for defined types, which is also the base class # nodes and classes. class Puppet::Parser::AST::Definition < Puppet::Parser::AST::Branch include Puppet::Util::Warnings class << self attr_accessor :name end # The class name @name = :definition attr_accessor :classname, :arguments, :code, :scope, :keyword attr_accessor :exported, :namespace, :parser, :virtual, :name attr_reader :parentclass def child_of?(klass) false end # Create a resource that knows how to evaluate our actual code. def evaluate(scope) # Do nothing if the resource already exists; this provides the singleton nature classes need. return if scope.catalog.resource(:class, self.classname) resource = Puppet::Parser::Resource.new(:type => "class", :title => self.classname, :scope => scope, :source => scope.source) scope.catalog.tag(*resource.tags) - scope.compile.add_resource(scope, resource) + scope.compiler.add_resource(scope, resource) return resource end # Now evaluate the code associated with this class or definition. def evaluate_code(resource) # Create a new scope. scope = subscope(resource.scope, resource) set_resource_parameters(scope, resource) if self.code return self.code.safeevaluate(scope) else return nil end end def initialize(hash = {}) @arguments = nil @parentclass = nil super # Convert the arguments to a hash for ease of later use. if @arguments unless @arguments.is_a? Array @arguments = [@arguments] end oldargs = @arguments @arguments = {} oldargs.each do |arg, val| @arguments[arg] = val end else @arguments = {} end # Deal with metaparams in the argument list. @arguments.each do |arg, defvalue| next unless Puppet::Type.metaparamclass(arg) if defvalue warnonce "%s is a metaparam; this value will inherit to all contained resources" % arg else raise Puppet::ParseError, "%s is a metaparameter; please choose another parameter name in the %s definition" % [arg, self.classname] end end end def find_parentclass @parser.findclass(namespace, parentclass) end # Set our parent class, with a little check to avoid some potential # weirdness. def parentclass=(name) if name == self.classname parsefail "Parent classes must have dissimilar names" end @parentclass = name end # Hunt down our class object. def parentobj return nil unless @parentclass # Cache our result, since it should never change. unless defined?(@parentobj) unless tmp = find_parentclass parsefail "Could not find %s parent %s" % [self.class.name, @parentclass] end if tmp == self parsefail "Parent classes must have dissimilar names" end @parentobj = tmp end @parentobj end # Create a new subscope in which to evaluate our code. def subscope(scope, resource) args = { :resource => resource, :keyword => self.keyword, :namespace => self.namespace, :source => self } oldscope = scope scope = scope.newscope(args) scope.source = self return scope end def to_s classname end # Check whether a given argument is valid. Searches up through # any parent classes that might exist. def validattr?(param) param = param.to_s if @arguments.include?(param) # It's a valid arg for us return true elsif param == "name" return true # elsif defined? @parentclass and @parentclass # # Else, check any existing parent # if parent = @scope.lookuptype(@parentclass) and parent != [] # return parent.validarg?(param) # elsif builtin = Puppet::Type.type(@parentclass) # return builtin.validattr?(param) # else # raise Puppet::Error, "Could not find parent class %s" % # @parentclass # end elsif Puppet::Type.metaparam?(param) return true else # Or just return false return false end end private # Set any arguments passed by the resource as variables in the scope. def set_resource_parameters(scope, resource) args = symbolize_options(resource.to_hash || {}) # Verify that all required arguments are either present or # have been provided with defaults. if self.arguments self.arguments.each { |arg, default| arg = arg.to_sym unless args.include?(arg) if defined? default and ! default.nil? default = default.safeevaluate scope args[arg] = default #Puppet.debug "Got default %s for %s in %s" % # [default.inspect, arg.inspect, @name.inspect] else parsefail "Must pass %s to %s of type %s" % [arg, resource.title, @classname] end end } end # Set each of the provided arguments as variables in the # definition's scope. args.each { |arg,value| unless validattr?(arg) parsefail "%s does not accept attribute %s" % [@classname, arg] end exceptwrap do scope.setvar(arg.to_s, args[arg]) end } scope.setvar("title", resource.title) unless args.include? :title scope.setvar("name", resource.name) unless args.include? :name end end diff --git a/lib/puppet/parser/ast/hostclass.rb b/lib/puppet/parser/ast/hostclass.rb index 4f2d00f0c..f49016526 100644 --- a/lib/puppet/parser/ast/hostclass.rb +++ b/lib/puppet/parser/ast/hostclass.rb @@ -1,77 +1,77 @@ require 'puppet/parser/ast/definition' # The code associated with a class. This is different from definitions # in that each class is a singleton -- only one will exist for a given # node. class Puppet::Parser::AST::HostClass < Puppet::Parser::AST::Definition @name = :class # Are we a child of the passed class? Do a recursive search up our # parentage tree to figure it out. def child_of?(klass) return false unless self.parentclass if klass == self.parentobj return true else return self.parentobj.child_of?(klass) end end # Make sure our parent class has been evaluated, if we have one. def evaluate(scope) if parentclass and ! scope.catalog.resource(:class, parentclass) resource = parentobj.evaluate(scope) end super end # Evaluate the code associated with this class. def evaluate_code(resource) scope = resource.scope # Verify that we haven't already been evaluated. This is # what provides the singleton aspect. - if existing_scope = scope.compile.class_scope(self) + if existing_scope = scope.compiler.class_scope(self) Puppet.debug "Class '%s' already evaluated; not evaluating again" % (classname == "" ? "main" : classname) return nil end pnames = nil if pklass = self.parentobj pklass.evaluate_code(resource) scope = parent_scope(scope, pklass) pnames = scope.namespaces end # Don't create a subscope for the top-level class, since it already # has its own scope. scope = subscope(scope, resource) unless resource.title == :main if pnames pnames.each do |ns| scope.add_namespace(ns) end end # Set the class before we do anything else, so that it's set # during the evaluation and can be inspected. - scope.compile.class_set(self.classname, scope) + scope.compiler.class_set(self.classname, scope) # Now evaluate our code, yo. if self.code return self.code.safeevaluate(scope) else return nil end end def parent_scope(scope, klass) - if s = scope.compile.class_scope(klass) + if s = scope.compiler.class_scope(klass) return s else raise Puppet::DevError, "Could not find scope for %s" % klass.classname end end end diff --git a/lib/puppet/parser/ast/node.rb b/lib/puppet/parser/ast/node.rb index 7ff7a18e1..8cebac8a8 100644 --- a/lib/puppet/parser/ast/node.rb +++ b/lib/puppet/parser/ast/node.rb @@ -1,57 +1,57 @@ require 'puppet/parser/ast/hostclass' # The specific code associated with a host. Nodes are annoyingly unlike # other objects. That's just the way it is, at least for now. class Puppet::Parser::AST::Node < Puppet::Parser::AST::HostClass @name = :node # Evaluate the code associated with our node definition. def evaluate_code(resource) scope = resource.scope # We don't have to worry about the declarativeness of node parentage, # because the entry point is always a single node definition. if parent = self.parentobj scope = parent.evaluate_code(resource) end scope = scope.newscope( :resource => resource, :keyword => @keyword, :source => self, :namespace => "" # nodes are always in "" ) # Mark our node name as a class, too, but strip it of the domain # name. Make the mark before we evaluate the code, so that it is # marked within the code itself. - scope.compile.class_set(self.classname, scope) + scope.compiler.class_set(self.classname, scope) # And then evaluate our code if we have any @code.safeevaluate(scope) if self.code return scope end def initialize(options) @parentclass = nil super # Do some validation on the node name if @name =~ /[^-\w.]/ raise Puppet::ParseError, "Invalid node name %s" % @name end end # Make sure node scopes are marked as such. def subscope(*args) scope = super scope.nodescope = true end private # Search for the object matching our parent class. def find_parentclass @parser.findnode(parentclass) end end diff --git a/lib/puppet/parser/ast/resource.rb b/lib/puppet/parser/ast/resource.rb index 2dadf9ed6..8a60522a3 100644 --- a/lib/puppet/parser/ast/resource.rb +++ b/lib/puppet/parser/ast/resource.rb @@ -1,73 +1,73 @@ require 'puppet/parser/ast/resource_reference' # Any normal puppet resource declaration. Can point to a definition or a # builtin type. class Puppet::Parser::AST class Resource < AST::ResourceReference attr_accessor :title, :type, :exported, :virtual attr_reader :params # Does not actually return an object; instead sets an object # in the current scope. def evaluate(scope) # Evaluate all of the specified params. paramobjects = @params.collect { |param| param.safeevaluate(scope) } objtitles = @title.safeevaluate(scope) # it's easier to always use an array, even for only one name unless objtitles.is_a?(Array) objtitles = [objtitles] end objtype = qualified_type(scope) # This is where our implicit iteration takes place; if someone # passed an array as the name, then we act just like the called us # many times. objtitles.collect { |objtitle| exceptwrap :type => Puppet::ParseError do exp = self.exported || scope.resource.exported? # We want virtual to be true if exported is true. We can't # just set :virtual => self.virtual in the initialization, # because sometimes the :virtual attribute is set *after* # :exported, in which case it clobbers :exported if :exported # is true. Argh, this was a very tough one to track down. virt = self.virtual || scope.resource.virtual? || exp obj = Puppet::Parser::Resource.new( :type => objtype, :title => objtitle, :params => paramobjects, :file => self.file, :line => self.line, :exported => exp, :virtual => virt, :source => scope.source, :scope => scope ) - # And then store the resource in the compile. + # And then store the resource in the compiler. # At some point, we need to switch all of this to return # objects instead of storing them like this. - scope.compile.add_resource(scope, obj) + scope.compiler.add_resource(scope, obj) obj end }.reject { |obj| obj.nil? } end # Set the parameters for our object. def params=(params) if params.is_a?(AST::ASTArray) @params = params else @params = AST::ASTArray.new( :line => params.line, :file => params.file, :children => [params] ) end end end end diff --git a/lib/puppet/parser/ast/resource_override.rb b/lib/puppet/parser/ast/resource_override.rb index db0986a8e..f9464acda 100644 --- a/lib/puppet/parser/ast/resource_override.rb +++ b/lib/puppet/parser/ast/resource_override.rb @@ -1,58 +1,58 @@ require 'puppet/parser/ast/resource' class Puppet::Parser::AST # Set a parameter on a resource specification created somewhere else in the # configuration. The object is responsible for verifying that this is allowed. class ResourceOverride < Resource attr_accessor :object attr_reader :params # Iterate across all of our children. def each [@object,@params].flatten.each { |param| #Puppet.debug("yielding param %s" % param) yield param } end # Does not actually return an object; instead sets an object # in the current scope. def evaluate(scope) # Get our object reference. object = @object.safeevaluate(scope) hash = {} # Evaluate all of the specified params. params = @params.collect { |param| param.safeevaluate(scope) } # Now we just create a normal resource, but we call a very different # method on the scope. obj = Puppet::Parser::Resource.new( :type => object.type, :title => object.title, :params => params, :file => @file, :line => @line, :source => scope.source, :scope => scope ) # Now we tell the scope that it's an override, and it behaves as # necessary. - scope.compile.add_override(obj) + scope.compiler.add_override(obj) obj end # Create our ResourceDef. Handles type checking for us. def initialize(hash) @checked = false super #self.typecheck(@type.value) end end end diff --git a/lib/puppet/parser/collector.rb b/lib/puppet/parser/collector.rb index efd64a320..e0c37cd35 100644 --- a/lib/puppet/parser/collector.rb +++ b/lib/puppet/parser/collector.rb @@ -1,167 +1,167 @@ # An object that collects stored objects from the central cache and returns # them to the current host, yo. class Puppet::Parser::Collector attr_accessor :type, :scope, :vquery, :equery, :form, :resources # Call the collection method, mark all of the returned objects as non-virtual, # and then delete this object from the list of collections to evaluate. def evaluate if self.resources if objects = collect_resources and ! objects.empty? return objects else return false end else method = "collect_#{@form.to_s}" objects = send(method).each do |obj| obj.virtual = false end if objects.empty? return false else return objects end end end def initialize(scope, type, equery, vquery, form) @scope = scope # Canonize the type @type = Puppet::ResourceReference.new(type, "whatever").type @equery = equery @vquery = vquery raise(ArgumentError, "Invalid query form %s" % form) unless [:exported, :virtual].include?(form) @form = form end private # Create our active record query. def build_active_record_query Puppet::Rails.init unless ActiveRecord::Base.connected? raise Puppet::DevError, "Cannot collect resources for a nil host" unless @scope.host host = Puppet::Rails::Host.find_by_name(@scope.host) query = {:include => {:param_values => :param_name}} search = "(exported=? AND restype=?)" values = [true, @type] search += " AND (?)" and values << @equery if @equery # We're going to collect objects from rails, but we don't want any # objects from this host. search = ("host_id != ? AND %s" % search) and values.unshift(host.id) if host query[:conditions] = [search, *values] return query end # Collect exported objects. def collect_exported # First get everything from the export table. Just reuse our # collect_virtual method but tell it to use 'exported? for the test. resources = collect_virtual(true).reject { |r| ! r.virtual? } count = resources.length query = build_active_record_query # Now look them up in the rails db. When we support attribute comparison # and such, we'll need to vary the conditions, but this works with no # attributes, anyway. time = Puppet::Util.thinmark do Puppet::Rails::Resource.find(:all, @type, true, query).each do |obj| if resource = exported_resource(obj) count += 1 resources << resource end end end scope.debug("Collected %s %s resource%s in %.2f seconds" % [count, @type, count == 1 ? "" : "s", time]) return resources end def collect_resources unless @resources.is_a?(Array) @resources = [@resources] end method = "collect_#{form.to_s}_resources" send(method) end def collect_exported_resources raise Puppet::ParseError, "realize() is not yet implemented for exported resources" end # Collect resources directly; this is the result of using 'realize', # which specifies resources, rather than using a normal collection. def collect_virtual_resources return [] unless defined?(@resources) and ! @resources.empty? result = @resources.dup.collect do |ref| if res = @scope.findresource(ref.to_s) @resources.delete(ref) res end end.reject { |r| r.nil? }.each do |res| res.virtual = false end # If there are no more resources to find, delete this from the list # of collections. if @resources.empty? - @scope.compile.delete_collection(self) + @scope.compiler.delete_collection(self) end return result end - # Collect just virtual objects, from our local compile. + # Collect just virtual objects, from our local compiler. def collect_virtual(exported = false) if exported method = :exported? else method = :virtual? end - scope.compile.resources.find_all do |resource| + scope.compiler.resources.find_all do |resource| resource.type == @type and resource.send(method) and match?(resource) end end # Seek a specific exported resource. def exported_resource(obj) if existing = @scope.findresource(obj.restype, obj.title) # Next see if we've already collected this resource return nil if existing.rails_id == obj.id # This is the one we've already collected raise Puppet::ParseError, "Exported resource %s cannot override local resource" % [obj.ref] end resource = obj.to_resource(self.scope) resource.exported = false - scope.compile.add_resource(scope, resource) + scope.compiler.add_resource(scope, resource) return resource end # Does the resource match our tests? We don't yet support tests, # so it's always true at the moment. def match?(resource) if self.vquery return self.vquery.call(resource) else return true end end end diff --git a/lib/puppet/parser/compile.rb b/lib/puppet/parser/compiler.rb similarity index 97% rename from lib/puppet/parser/compile.rb rename to lib/puppet/parser/compiler.rb index 2415fd5e8..27860487a 100644 --- a/lib/puppet/parser/compile.rb +++ b/lib/puppet/parser/compiler.rb @@ -1,467 +1,467 @@ # Created by Luke A. Kanies on 2007-08-13. # Copyright (c) 2007. All rights reserved. require 'puppet/node' require 'puppet/node/catalog' require 'puppet/util/errors' # Maintain a graph of scopes, along with a bunch of data # about the individual catalog we're compiling. -class Puppet::Parser::Compile +class Puppet::Parser::Compiler include Puppet::Util include Puppet::Util::Errors attr_reader :parser, :node, :facts, :collections, :catalog, :node_scope # Add a collection to the global list. def add_collection(coll) @collections << coll end # Store a resource override. def add_override(override) # If possible, merge the override in immediately. if resource = @catalog.resource(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 add_resource(scope, resource) @catalog.add_resource(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. @catalog.add_edge!(scope.resource, resource) end # Do we use nodes found in the code, vs. the external node sources? def ast_nodes? parser.nodes.length > 0 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 @catalog.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 @catalog.classes end - # Compile our catalog. This mostly revolves around finding and evaluating classes. + # Compiler our catalog. This mostly revolves around finding and evaluating classes. # This is the main entry into our catalog. def compile # Set the client's parameters into the top scope. set_node_parameters() evaluate_main() evaluate_ast_node() evaluate_node_classes() evaluate_generators() finish() fail_on_unevaluated() if Puppet[:storeconfigs] store() end return @catalog end # LAK:FIXME There are no tests for this. def delete_collection(coll) @collections.delete(coll) if @collections.include?(coll) 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 catalog 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, lazy_evaluate = true) 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) found << name and next if class_scope(klass) resource = klass.evaluate(scope) # If they've disabled lazy evaluation (which the :include function does), # then evaluate our resource immediately. resource.evaluate unless lazy_evaluate found << name else Puppet.info "Could not find class %s for %s" % [name, node.name] @catalog.tag(name) end end found end # Return a resource by either its ref or its type and title. def findresource(*args) @catalog.resource(*args) 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 + raise ArgumentError, "Compiler 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[:compiler] = 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 @catalog.vertices end # The top scope is usually the top-level scope, but if we're using AST nodes, # then it is instead the node's scope. def topscope node_scope || @topscope 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 ||= @parser.nodes["default"]) raise Puppet::ParseError, "Could not find default node or by name with '%s'" % node.names.join(", ") end # Create a resource to model this node, and then add it to the list # of resources. resource = astnode.evaluate(topscope) resource.evaluate # Now set the node scope appropriately, so that :topscope can # behave differently. @node_scope = class_scope(astnode) 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 # We have to iterate over a dup of the array because # collections can delete themselves from the list, which # changes its length and causes some collections to get missed. @collections.dup.each do |collection| found_something = true if collection.evaluate 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 count += 1 if count > 1000 raise Puppet::ParseError, "Somehow looped more than 1000 times while evaluating host catalog" 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 @catalog.add_resource(@main_resource) @main_resource.evaluate end # Make sure the entire catalog 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 @catalog.resources.each do |name| resource = @catalog.resource(name) # Add in any resource overrides. if overrides = resource_overrides(resource) overrides.each do |over| resource.merge(over) end # Remove the overrides, so that the configuration knows there # are none left. overrides.clear end resource.finish if resource.respond_to?(:finish) end 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) + @topscope = Puppet::Parser::Scope.new(:compiler => 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 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 graph for maintaining scope relationships. @scope_graph = Puppet::SimpleGraph.new # For maintaining the relationship between scopes and their resources. @catalog = Puppet::Node::Catalog.new(@node.name) @catalog.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 catalog 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, @catalog.vertices) 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 catalog 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 = @catalog.vertices.reject { |resource| resource.builtin? or resource.evaluated? } if ary.empty? return nil else return ary end end end diff --git a/lib/puppet/parser/functions.rb b/lib/puppet/parser/functions.rb index 34b38b809..e0b60e161 100644 --- a/lib/puppet/parser/functions.rb +++ b/lib/puppet/parser/functions.rb @@ -1,306 +1,306 @@ require 'puppet/util/autoload' require 'puppet/parser/scope' module Puppet::Parser module Functions # A module for managing parser functions. Each specified function # becomes an instance method on the Scope class. class << self include Puppet::Util end def self.autoloader unless defined? @autoloader @autoloader = Puppet::Util::Autoload.new(self, "puppet/parser/functions", :wrap => false ) end @autoloader end # Create a new function type. def self.newfunction(name, options = {}, &block) @functions ||= {} name = symbolize(name) if @functions.include? name raise Puppet::DevError, "Function %s already defined" % name end # We want to use a separate, hidden module, because we don't want # people to be able to call them directly. unless defined? FCollection eval("module FCollection; end") end ftype = options[:type] || :statement unless ftype == :statement or ftype == :rvalue raise Puppet::DevError, "Invalid statement type %s" % ftype.inspect end fname = "function_" + name.to_s Puppet::Parser::Scope.send(:define_method, fname, &block) # Someday we'll support specifying an arity, but for now, nope #@functions[name] = {:arity => arity, :type => ftype} @functions[name] = {:type => ftype, :name => fname} if options[:doc] @functions[name][:doc] = options[:doc] end end # Determine if a given name is a function def self.function(name) name = symbolize(name) unless @functions.include? name autoloader.load(name) end if @functions.include? name return @functions[name][:name] else return false end end def self.functiondocs autoloader.loadall ret = "" @functions.sort { |a,b| a[0].to_s <=> b[0].to_s }.each do |name, hash| #ret += "%s\n%s\n" % [name, hash[:type]] ret += "%s\n%s\n" % [name, "-" * name.to_s.length] if hash[:doc] ret += hash[:doc].gsub(/\n\s*/, ' ') else ret += "Undocumented.\n" end ret += "\n\n- **Type**: %s\n\n" % hash[:type] end return ret end def self.functions @functions.keys end # Determine if a given function returns a value or not. def self.rvalue?(name) name = symbolize(name) if @functions.include? name case @functions[name][:type] when :statement: return false when :rvalue: return true end else return false end end # Include the specified classes newfunction(:include, :doc => "Evaluate one or more classes.") do |vals| vals = [vals] unless vals.is_a?(Array) # The 'false' disables lazy evaluation. - klasses = compile.evaluate_classes(vals, self, false) + klasses = compiler.evaluate_classes(vals, self, false) missing = vals.find_all do |klass| ! klasses.include?(klass) end unless missing.empty? # Throw an error if we didn't evaluate all of the classes. str = "Could not find class" if missing.length > 1 str += "es" end str += " " + missing.join(", ") if n = namespaces and ! n.empty? and n != [""] str += " in namespaces %s" % @namespaces.join(", ") end self.fail Puppet::ParseError, str end end # Tag the current scope with each passed name newfunction(:tag, :doc => "Add the specified tags to the containing class or definition. All contained objects will then acquire that tag, also. ") do |vals| self.resource.tag(*vals) end # Test whether a given tag is set. This functions as a big OR -- if any of the # specified tags are unset, we return false. newfunction(:tagged, :type => :rvalue, :doc => "A boolean function that tells you whether the current container is tagged with the specified tags. The tags are ANDed, so that all of the specified tags must be included for the function to return true.") do |vals| - configtags = compile.catalog.tags + configtags = compiler.catalog.tags resourcetags = resource.tags retval = true vals.each do |val| unless configtags.include?(val) or resourcetags.include?(val) retval = false break end end return retval end # Test whether a given class or definition is defined newfunction(:defined, :type => :rvalue, :doc => "Determine whether a given type is defined, either as a native type or a defined type, or whether a class is defined. This is useful for checking whether a class is defined and only including it if it is. This function can also test whether a resource has been defined, using resource references (e.g., ``if defined(File['/tmp/myfile'] { ... }``). This function is unfortunately dependent on the parse order of the configuration when testing whether a resource is defined.") do |vals| result = false vals.each do |val| case val when String: # For some reason, it doesn't want me to return from here. if Puppet::Type.type(val) or finddefine(val) or findclass(val) result = true break end when Puppet::Parser::Resource::Reference: if findresource(val.to_s) result = true break end else raise ArgumentError, "Invalid argument of type %s to 'defined'" % val.class end end result end newfunction(:fail, :doc => "Fail with a parse error.") do |vals| vals = vals.collect { |s| s.to_s }.join(" ") if vals.is_a? Array raise Puppet::ParseError, vals.to_s end # Runs a newfunction to create a function for each of the log levels Puppet::Util::Log.levels.each do |level| newfunction(level, :doc => "Log a message on the server at level #{level.to_s}.") do |vals| send(level, vals.join(" ")) end end newfunction(:template, :type => :rvalue, :doc => "Evaluate a template and return its value. See `the templating docs `_ for more information. Note that if multiple templates are specified, their output is all concatenated and returned as the output of the function. ") do |vals| require 'erb' vals.collect do |file| # Use a wrapper, so the template can't get access to the full # Scope object. debug "Retrieving template %s" % file wrapper = Puppet::Parser::TemplateWrapper.new(self, file) begin wrapper.result() rescue => detail raise Puppet::ParseError, "Failed to parse template %s: %s" % [file, detail] end end.join("") end # This is just syntactic sugar for a collection, although it will generally # be a good bit faster. newfunction(:realize, :doc => "Make a virtual object real. This is useful when you want to know the name of the virtual object and don't want to bother with a full collection. It is slightly faster than a collection, and, of course, is a bit shorter. You must pass the object using a reference; e.g.: ``realize User[luke]``." ) do |vals| coll = Puppet::Parser::Collector.new(self, :nomatter, nil, nil, :virtual) vals = [vals] unless vals.is_a?(Array) coll.resources = vals - compile.add_collection(coll) + compiler.add_collection(coll) end newfunction(:search, :doc => "Add another namespace for this class to search. This allows you to create classes with sets of definitions and add those classes to another class's search path.") do |vals| vals.each do |val| add_namespace(val) end end newfunction(:file, :type => :rvalue, :doc => "Return the contents of a file. Multiple files can be passed, and the first file that exists will be read in.") do |vals| ret = nil vals.each do |file| unless file =~ /^#{File::SEPARATOR}/ raise Puppet::ParseError, "Files must be fully qualified" end if FileTest.exists?(file) ret = File.read(file) break end end if ret ret else raise Puppet::ParseError, "Could not find any files from %s" % vals.join(", ") end end newfunction(:generate, :type => :rvalue, :doc => "Calls an external command and returns the results of the command. Any arguments are passed to the external command as arguments. If the generator does not exit with return code of 0, the generator is considered to have failed and a parse error is thrown. Generators can only have file separators, alphanumerics, dashes, and periods in them. This function will attempt to protect you from malicious generator calls (e.g., those with '..' in them), but it can never be entirely safe. No subshell is used to execute generators, so all shell metacharacters are passed directly to the generator.") do |args| unless args[0] =~ /^#{File::SEPARATOR}/ raise Puppet::ParseError, "Generators must be fully qualified" end unless args[0] =~ /^[-#{File::SEPARATOR}\w.]+$/ raise Puppet::ParseError, "Generators can only contain alphanumerics, file separators, and dashes" end if args[0] =~ /\.\./ raise Puppet::ParseError, "Can not use generators with '..' in them." end begin output = Puppet::Util.execute(args) rescue Puppet::ExecutionFailure => detail raise Puppet::ParseError, "Failed to execute generator %s: %s" % [args[0], detail] end output end end end diff --git a/lib/puppet/parser/interpreter.rb b/lib/puppet/parser/interpreter.rb index e29e19944..1d93193dd 100644 --- a/lib/puppet/parser/interpreter.rb +++ b/lib/puppet/parser/interpreter.rb @@ -1,90 +1,90 @@ require 'puppet' require 'timeout' require 'puppet/rails' require 'puppet/util/methodhelper' require 'puppet/parser/parser' -require 'puppet/parser/compile' +require 'puppet/parser/compiler' require 'puppet/parser/scope' # The interpreter is a very simple entry-point class that # manages the existence of the parser (e.g., replacing it # when files are reparsed). You can feed it a node and # get the node's catalog back. class Puppet::Parser::Interpreter include Puppet::Util attr_accessor :usenodes include Puppet::Util::Errors # Determine the configuration version for a given node's environment. def configuration_version(node) parser(node.environment).version end # evaluate our whole tree def compile(node) raise Puppet::ParseError, "Could not parse configuration; cannot compile" unless env_parser = parser(node.environment) - return Puppet::Parser::Compile.new(node, env_parser).compile + return Puppet::Parser::Compiler.new(node, env_parser).compile end # create our interpreter def initialize # The class won't always be defined during testing. if Puppet[:storeconfigs] if Puppet.features.rails? Puppet::Rails.init else raise Puppet::Error, "Rails is missing; cannot store configurations" end end @parsers = {} end private # Create a new parser object and pre-parse the configuration. def create_parser(environment) begin parser = Puppet::Parser::Parser.new(:environment => environment) if code = Puppet.settings.value(:code, environment) and code != "" parser.string = code else file = Puppet.settings.value(:manifest, environment) parser.file = file end parser.parse return parser rescue => detail msg = "Could not parse" if environment and environment != "" msg += " for environment %s" % environment end msg += ": %s" % detail.to_s error = Puppet::Error.new(msg) error.set_backtrace(detail.backtrace) raise error end end # Return the parser for a specific environment. def parser(environment) if ! @parsers[environment] or @parsers[environment].reparse? # This will throw an exception if it does not succeed. We only # want to get rid of the old parser if we successfully create a new # one. begin tmp = create_parser(environment) @parsers[environment].clear if @parsers[environment] @parsers[environment] = tmp rescue => detail # If a parser already exists, than assume that we logged the # exception elsewhere and reuse the parser. If one doesn't # exist, then reraise. raise detail unless @parsers[environment] end end @parsers[environment] end end diff --git a/lib/puppet/parser/scope.rb b/lib/puppet/parser/scope.rb index 81d4ac71a..a6e43e7b3 100644 --- a/lib/puppet/parser/scope.rb +++ b/lib/puppet/parser/scope.rb @@ -1,337 +1,337 @@ # The scope class, which handles storing and retrieving variables and types and # such. require 'puppet/parser/parser' require 'puppet/parser/templatewrapper' require 'puppet/transportable' require 'strscan' class Puppet::Parser::Scope require 'puppet/parser/resource' AST = Puppet::Parser::AST Puppet::Util.logmethods(self) include Enumerable include Puppet::Util::Errors attr_accessor :parent, :level, :parser, :source, :resource attr_accessor :base, :keyword, :nodescope - attr_accessor :top, :translated, :compile + attr_accessor :top, :translated, :compiler # A demeterific shortcut to the catalog. def catalog - compile.catalog + compiler.catalog end # Proxy accessors def host - @compile.node.name + @compiler.node.name end def interpreter - @compile.interpreter + @compiler.interpreter end # Is the value true? This allows us to control the definition of truth # in one place. def self.true?(value) if value == false or value == "" or value == :undef return false else return true end end # Add to our list of namespaces. def add_namespace(ns) return false if @namespaces.include?(ns) if @namespaces == [""] @namespaces = [ns] else @namespaces << ns end end # Are we the top scope? def topscope? @level == 1 end def findclass(name) @namespaces.each do |namespace| if r = parser.findclass(namespace, name) return r end end return nil end def finddefine(name) @namespaces.each do |namespace| if r = parser.finddefine(namespace, name) return r end end return nil end def findresource(string, name = nil) - compile.findresource(string, name) + compiler.findresource(string, name) end # Initialize our new scope. Defaults to having no parent. def initialize(hash = {}) if hash.include?(:namespace) if n = hash[:namespace] @namespaces = [n] end hash.delete(:namespace) else @namespaces = [""] end hash.each { |name, val| method = name.to_s + "=" if self.respond_to? method self.send(method, val) else raise Puppet::DevError, "Invalid scope argument %s" % name end } @tags = [] # The symbol table for this scope. This is where we store variables. @symtable = {} # All of the defaults set for types. It's a hash of hashes, # with the first key being the type, then the second key being # the parameter. @defaults = Hash.new { |dhash,type| dhash[type] = {} } end # Collect all of the defaults set at any higher scopes. # This is a different type of lookup because it's additive -- # it collects all of the defaults, with defaults in closer scopes # overriding those in later scopes. def lookupdefaults(type) values = {} # first collect the values from the parents unless parent.nil? parent.lookupdefaults(type).each { |var,value| values[var] = value } end # then override them with any current values # this should probably be done differently if @defaults.include?(type) @defaults[type].each { |var,value| values[var] = value } end #Puppet.debug "Got defaults for %s: %s" % # [type,values.inspect] return values end # Look up a defined type. def lookuptype(name) finddefine(name) || findclass(name) end def lookup_qualified_var(name, usestring) parts = name.split(/::/) shortname = parts.pop klassname = parts.join("::") klass = findclass(klassname) unless klass raise Puppet::ParseError, "Could not find class %s" % klassname end - unless kscope = compile.class_scope(klass) + unless kscope = compiler.class_scope(klass) raise Puppet::ParseError, "Class %s has not been evaluated so its variables cannot be referenced" % klass.classname end return kscope.lookupvar(shortname, usestring) end private :lookup_qualified_var # Look up a variable. The simplest value search we do. Default to returning # an empty string for missing values, but support returning a constant. def lookupvar(name, usestring = true) # If the variable is qualified, then find the specified scope and look the variable up there instead. if name =~ /::/ return lookup_qualified_var(name, usestring) end # We can't use "if @symtable[name]" here because the value might be false if @symtable.include?(name) if usestring and @symtable[name] == :undef return "" else return @symtable[name] end elsif self.parent return parent.lookupvar(name, usestring) elsif usestring return "" else return :undefined end end def namespaces @namespaces.dup end # Create a new scope and set these options. def newscope(options = {}) - compile.newscope(self, options) + compiler.newscope(self, options) end # Is this class for a node? This is used to make sure that # nodes and classes with the same name conflict (#620), which # is required because of how often the names are used throughout # the system, including on the client. def nodescope? self.nodescope end # We probably shouldn't cache this value... But it's a lot faster # than doing lots of queries. def parent unless defined?(@parent) - @parent = compile.parent(self) + @parent = compiler.parent(self) end @parent end # Return the list of scopes up to the top scope, ordered with our own first. # This is used for looking up variables and defaults. def scope_path if parent [self, parent.scope_path].flatten.compact else [self] end end # Set defaults for a type. The typename should already be downcased, # so that the syntax is isolated. We don't do any kind of type-checking # here; instead we let the resource do it when the defaults are used. def setdefaults(type, params) table = @defaults[type] # if we got a single param, it'll be in its own array params = [params] unless params.is_a?(Array) params.each { |param| #Puppet.debug "Default for %s is %s => %s" % # [type,ary[0].inspect,ary[1].inspect] if table.include?(param.name) raise Puppet::ParseError.new("Default already defined for %s { %s }; cannot redefine" % [type, param.name], param.line, param.file) end table[param.name] = param } end # Set a variable in the current scope. This will override settings # in scopes above, but will not allow variables in the current scope # to be reassigned. def setvar(name,value, file = nil, line = nil) #Puppet.debug "Setting %s to '%s' at level %s" % # [name.inspect,value,self.level] if @symtable.include?(name) error = Puppet::ParseError.new("Cannot reassign variable %s" % name) if file error.file = file end if line error.line = line end raise error end @symtable[name] = value end # Return an interpolated string. def strinterp(string, file = nil, line = nil) # Most strings won't have variables in them. ss = StringScanner.new(string) out = "" while not ss.eos? if ss.scan(/^\$\{((\w*::)*\w+)\}|^\$((\w*::)*\w+)/) # If it matches the backslash, then just retun the dollar sign. if ss.matched == '\\$' out << '$' else # look the variable up out << lookupvar(ss[1] || ss[3]).to_s || "" end elsif ss.scan(/^\\(.)/) # Puppet.debug("Got escape: pos:%d; m:%s" % [ss.pos, ss.matched]) case ss[1] when 'n' out << "\n" when 't' out << "\t" when 's' out << " " when '\\' out << '\\' when '$' out << '$' else str = "Unrecognised escape sequence '#{ss.matched}'" if file str += " in file %s" % file end if line str += " at line %s" % line end Puppet.warning str out << ss.matched end elsif ss.scan(/^\$/) out << '$' elsif ss.scan(/^\\\n/) # an escaped carriage return next else tmp = ss.scan(/[^\\$]+/) # Puppet.debug("Got other: pos:%d; m:%s" % [ss.pos, tmp]) unless tmp error = Puppet::ParseError.new("Could not parse string %s" % string.inspect) {:file= => file, :line= => line}.each do |m,v| error.send(m, v) if v end raise error end out << tmp end end return out end # Return the tags associated with this scope. It's basically # just our parents' tags, plus our type. We don't cache this value # because our parent tags might change between calls. def tags resource.tags end # Used mainly for logging def to_s "Scope(%s)" % @resource.to_s end # Undefine a variable; only used for testing. def unsetvar(var) if @symtable.include?(var) @symtable.delete(var) end end end diff --git a/lib/puppet/parser/templatewrapper.rb b/lib/puppet/parser/templatewrapper.rb index 13823d483..7a8f74156 100644 --- a/lib/puppet/parser/templatewrapper.rb +++ b/lib/puppet/parser/templatewrapper.rb @@ -1,53 +1,53 @@ # A simple wrapper for templates, so they don't have full access to # the scope objects. class Puppet::Parser::TemplateWrapper attr_accessor :scope, :file include Puppet::Util Puppet::Util.logmethods(self) def initialize(scope, file) @scope = scope - @file = Puppet::Module::find_template(file, @scope.compile.environment) + @file = Puppet::Module::find_template(file, @scope.compiler.environment) unless FileTest.exists?(@file) raise Puppet::ParseError, "Could not find template %s" % file end # We'll only ever not have a parser in testing, but, eh. if @scope.parser @scope.parser.watch_file(@file) end end # Ruby treats variables like methods, so we can cheat here and # trap missing vars like they were missing methods. def method_missing(name, *args) # We have to tell lookupvar to return :undefined to us when # appropriate; otherwise it converts to "". value = @scope.lookupvar(name.to_s, false) if value != :undefined return value else # Just throw an error immediately, instead of searching for # other missingmethod things or whatever. raise Puppet::ParseError, "Could not find value for '%s'" % name end end def result result = nil benchmark(:debug, "Interpolated template #{@file}") do template = ERB.new(File.read(@file), 0, "-") result = template.result(binding) end result end def to_s "template[%s]" % @file end end diff --git a/spec/unit/node/catalog.rb b/spec/unit/node/catalog.rb index 3833890f7..93bbd0ad2 100755 --- a/spec/unit/node/catalog.rb +++ b/spec/unit/node/catalog.rb @@ -1,797 +1,797 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../spec_helper' describe Puppet::Node::Catalog, " when compiling" do it "should accept tags" do config = Puppet::Node::Catalog.new("mynode") config.tag("one") config.tags.should == %w{one} end it "should accept multiple tags at once" do config = Puppet::Node::Catalog.new("mynode") config.tag("one", "two") config.tags.should == %w{one two} end it "should convert all tags to strings" do config = Puppet::Node::Catalog.new("mynode") config.tag("one", :two) config.tags.should == %w{one two} end it "should tag with both the qualified name and the split name" do config = Puppet::Node::Catalog.new("mynode") config.tag("one::two") config.tags.include?("one").should be_true config.tags.include?("one::two").should be_true end it "should accept classes" do config = Puppet::Node::Catalog.new("mynode") config.add_class("one") config.classes.should == %w{one} config.add_class("two", "three") config.classes.should == %w{one two three} end it "should tag itself with passed class names" do config = Puppet::Node::Catalog.new("mynode") config.add_class("one") config.tags.should == %w{one} end end describe Puppet::Node::Catalog, " when extracting" do it "should return extraction result as the method result" do config = Puppet::Node::Catalog.new("mynode") config.expects(:extraction_format).returns(:whatever) config.expects(:extract_to_whatever).returns(:result) config.extract.should == :result end end describe Puppet::Node::Catalog, " when extracting transobjects" do def mkscope @parser = Puppet::Parser::Parser.new :Code => "" @node = Puppet::Node.new("mynode") - @compile = Puppet::Parser::Compile.new(@node, @parser) + @compiler = Puppet::Parser::Compiler.new(@node, @parser) # XXX This is ridiculous. - @compile.send(:evaluate_main) - @scope = @compile.topscope + @compiler.send(:evaluate_main) + @scope = @compiler.topscope end def mkresource(type, name) Puppet::Parser::Resource.new(:type => type, :title => name, :source => @source, :scope => @scope) end it "should always create a TransBucket for the 'main' class" do config = Puppet::Node::Catalog.new("mynode") @scope = mkscope @source = mock 'source' main = mkresource("class", :main) config.add_vertex!(main) bucket = mock 'bucket' bucket.expects(:classes=).with(config.classes) main.stubs(:builtin?).returns(false) main.expects(:to_transbucket).returns(bucket) config.extract_to_transportable.should equal(bucket) end # This isn't really a spec-style test, but I don't know how better to do it. it "should transform the resource graph into a tree of TransBuckets and TransObjects" do config = Puppet::Node::Catalog.new("mynode") @scope = mkscope @source = mock 'source' defined = mkresource("class", :main) builtin = mkresource("file", "/yay") config.add_edge!(defined, builtin) bucket = [] bucket.expects(:classes=).with(config.classes) defined.stubs(:builtin?).returns(false) defined.expects(:to_transbucket).returns(bucket) builtin.expects(:to_transobject).returns(:builtin) config.extract_to_transportable.should == [:builtin] end # Now try it with a more complicated graph -- a three tier graph, each tier it "should transform arbitrarily deep graphs into isomorphic trees" do config = Puppet::Node::Catalog.new("mynode") @scope = mkscope @scope.stubs(:tags).returns([]) @source = mock 'source' # Create our scopes. top = mkresource "class", :main topbucket = [] topbucket.expects(:classes=).with([]) top.expects(:to_trans).returns(topbucket) topres = mkresource "file", "/top" topres.expects(:to_trans).returns(:topres) config.add_edge! top, topres middle = mkresource "class", "middle" middle.expects(:to_trans).returns([]) config.add_edge! top, middle midres = mkresource "file", "/mid" midres.expects(:to_trans).returns(:midres) config.add_edge! middle, midres bottom = mkresource "class", "bottom" bottom.expects(:to_trans).returns([]) config.add_edge! middle, bottom botres = mkresource "file", "/bot" botres.expects(:to_trans).returns(:botres) config.add_edge! bottom, botres toparray = config.extract_to_transportable # This is annoying; it should look like: # [[[:botres], :midres], :topres] # but we can't guarantee sort order. toparray.include?(:topres).should be_true midarray = toparray.find { |t| t.is_a?(Array) } midarray.include?(:midres).should be_true botarray = midarray.find { |t| t.is_a?(Array) } botarray.include?(:botres).should be_true end end describe Puppet::Node::Catalog, " when converting to a transobject catalog" do class TestResource attr_accessor :name, :virtual, :builtin def initialize(name, options = {}) @name = name options.each { |p,v| send(p.to_s + "=", v) } end def ref if builtin? "File[%s]" % name else "Class[%s]" % name end end def virtual? virtual end def builtin? builtin end def to_transobject Puppet::TransObject.new(name, builtin? ? "file" : "class") end end before do @original = Puppet::Node::Catalog.new("mynode") @original.tag(*%w{one two three}) @original.add_class *%w{four five six} @top = TestResource.new 'top' @topobject = TestResource.new 'topobject', :builtin => true @virtual = TestResource.new 'virtual', :virtual => true @virtualobject = TestResource.new 'virtualobject', :builtin => true, :virtual => true @middle = TestResource.new 'middle' @middleobject = TestResource.new 'middleobject', :builtin => true @bottom = TestResource.new 'bottom' @bottomobject = TestResource.new 'bottomobject', :builtin => true @resources = [@top, @topobject, @middle, @middleobject, @bottom, @bottomobject] @original.add_edge!(@top, @topobject) @original.add_edge!(@top, @virtual) @original.add_edge!(@virtual, @virtualobject) @original.add_edge!(@top, @middle) @original.add_edge!(@middle, @middleobject) @original.add_edge!(@middle, @bottom) @original.add_edge!(@bottom, @bottomobject) @catalog = @original.to_transportable end it "should add all resources as TransObjects" do @resources.each { |resource| @catalog.resource(resource.ref).should be_instance_of(Puppet::TransObject) } end it "should not extract defined virtual resources" do @catalog.vertices.find { |v| v.name == "virtual" }.should be_nil end it "should not extract builtin virtual resources" do @catalog.vertices.find { |v| v.name == "virtualobject" }.should be_nil end it "should copy the tag list to the new catalog" do @catalog.tags.sort.should == @original.tags.sort end it "should copy the class list to the new catalog" do @catalog.classes.should == @original.classes end it "should duplicate the original edges" do @original.edges.each do |edge| next if edge.source.virtual? or edge.target.virtual? source = @catalog.resource(edge.source.ref) target = @catalog.resource(edge.target.ref) source.should_not be_nil target.should_not be_nil @catalog.edge?(source, target).should be_true end end it "should set itself as the catalog for each converted resource" do @catalog.vertices.each { |v| v.catalog.object_id.should equal(@catalog.object_id) } end end describe Puppet::Node::Catalog, " when converting to a RAL catalog" do before do @original = Puppet::Node::Catalog.new("mynode") @original.tag(*%w{one two three}) @original.add_class *%w{four five six} @top = Puppet::TransObject.new 'top', "class" @topobject = Puppet::TransObject.new '/topobject', "file" @middle = Puppet::TransObject.new 'middle', "class" @middleobject = Puppet::TransObject.new '/middleobject', "file" @bottom = Puppet::TransObject.new 'bottom', "class" @bottomobject = Puppet::TransObject.new '/bottomobject', "file" @resources = [@top, @topobject, @middle, @middleobject, @bottom, @bottomobject] @original.add_resource(*@resources) @original.add_edge!(@top, @topobject) @original.add_edge!(@top, @middle) @original.add_edge!(@middle, @middleobject) @original.add_edge!(@middle, @bottom) @original.add_edge!(@bottom, @bottomobject) @catalog = @original.to_ral end it "should add all resources as RAL instances" do @resources.each { |resource| @catalog.resource(resource.ref).should be_instance_of(Puppet::Type) } end it "should copy the tag list to the new catalog" do @catalog.tags.sort.should == @original.tags.sort end it "should copy the class list to the new catalog" do @catalog.classes.should == @original.classes end it "should duplicate the original edges" do @original.edges.each do |edge| @catalog.edge?(@catalog.resource(edge.source.ref), @catalog.resource(edge.target.ref)).should be_true end end it "should set itself as the catalog for each converted resource" do @catalog.vertices.each { |v| v.catalog.object_id.should equal(@catalog.object_id) } end # This tests #931. it "should not lose track of resources whose names vary" do changer = Puppet::TransObject.new 'changer', 'test' config = Puppet::Node::Catalog.new('test') config.add_resource(changer) config.add_resource(@top) config.add_edge!(@top, changer) resource = stub 'resource', :name => "changer2", :title => "changer2", :ref => "Test[changer2]", :catalog= => nil, :remove => nil changer.expects(:to_type).returns(resource) newconfig = nil Puppet::Type.allclear proc { @catalog = config.to_ral }.should_not raise_error @catalog.resource("Test[changer2]").should equal(resource) end after do # Remove all resource instances. @catalog.clear(true) end end describe Puppet::Node::Catalog, " when functioning as a resource container" do before do @catalog = Puppet::Node::Catalog.new("host") @one = stub 'resource1', :ref => "Me[one]", :catalog= => nil @two = stub 'resource2', :ref => "Me[two]", :catalog= => nil @dupe = stub 'resource3', :ref => "Me[one]", :catalog= => nil end it "should provide a method to add one or more resources" do @catalog.add_resource @one, @two @catalog.resource(@one.ref).should equal(@one) @catalog.resource(@two.ref).should equal(@two) end it "should set itself as the resource's catalog if it is not a relationship graph" do @one.expects(:catalog=).with(@catalog) @catalog.add_resource @one end it "should not set itself as the resource's catalog if it is a relationship graph" do @one.expects(:catalog=).never @catalog.is_relationship_graph = true @catalog.add_resource @one end it "should make all vertices available by resource reference" do @catalog.add_resource(@one) @catalog.resource(@one.ref).should equal(@one) @catalog.vertices.find { |r| r.ref == @one.ref }.should equal(@one) end it "should canonize how resources are referred to during retrieval when both type and title are provided" do @catalog.add_resource(@one) @catalog.resource("me", "one").should equal(@one) end it "should canonize how resources are referred to during retrieval when just the title is provided" do @catalog.add_resource(@one) @catalog.resource("me[one]", nil).should equal(@one) end it "should not allow two resources with the same resource reference" do @catalog.add_resource(@one) proc { @catalog.add_resource(@dupe) }.should raise_error(ArgumentError) end it "should not store objects that do not respond to :ref" do proc { @catalog.add_resource("thing") }.should raise_error(ArgumentError) end it "should remove all resources when asked" do @catalog.add_resource @one @catalog.add_resource @two @one.expects :remove @two.expects :remove @catalog.clear(true) end it "should support a mechanism for finishing resources" do @one.expects :finish @two.expects :finish @catalog.add_resource @one @catalog.add_resource @two @catalog.finalize end it "should make default resources when finalizing" do @catalog.expects(:make_default_resources) @catalog.finalize end it "should add default resources to the catalog upon creation" do @catalog.make_default_resources @catalog.resource(:schedule, "daily").should_not be_nil end it "should optionally support an initialization block and should finalize after such blocks" do @one.expects :finish @two.expects :finish config = Puppet::Node::Catalog.new("host") do |conf| conf.add_resource @one conf.add_resource @two end end it "should inform the resource that it is the resource's catalog" do @one.expects(:catalog=).with(@catalog) @catalog.add_resource @one end it "should be able to find resources by reference" do @catalog.add_resource @one @catalog.resource(@one.ref).should equal(@one) end it "should be able to find resources by reference or by type/title tuple" do @catalog.add_resource @one @catalog.resource("me", "one").should equal(@one) end it "should have a mechanism for removing resources" do @catalog.add_resource @one @one.expects :remove @catalog.remove_resource(@one) @catalog.resource(@one.ref).should be_nil @catalog.vertex?(@one).should be_false end it "should have a method for creating aliases for resources" do @catalog.add_resource @one @catalog.alias(@one, "other") @catalog.resource("me", "other").should equal(@one) end # This test is the same as the previous, but the behaviour should be explicit. it "should alias using the class name from the resource reference, not the resource class name" do @catalog.add_resource @one @catalog.alias(@one, "other") @catalog.resource("me", "other").should equal(@one) end it "should fail to add an alias if the aliased name already exists" do @catalog.add_resource @one proc { @catalog.alias @two, "one" }.should raise_error(ArgumentError) end it "should remove resource aliases when the target resource is removed" do @catalog.add_resource @one @catalog.alias(@one, "other") @one.expects :remove @catalog.remove_resource(@one) @catalog.resource("me", "other").should be_nil end after do Puppet::Type.allclear end end module ApplyingCatalogs def setup @catalog = Puppet::Node::Catalog.new("host") @catalog.retrieval_duration = Time.now @transaction = mock 'transaction' Puppet::Transaction.stubs(:new).returns(@transaction) @transaction.stubs(:evaluate) @transaction.stubs(:cleanup) @transaction.stubs(:addtimes) end end describe Puppet::Node::Catalog, " when applying" do include ApplyingCatalogs it "should create and evaluate a transaction" do @transaction.expects(:evaluate) @catalog.apply end it "should provide the catalog time to the transaction" do @transaction.expects(:addtimes).with do |arg| arg[:config_retrieval].should be_instance_of(Time) true end @catalog.apply end it "should clean up the transaction" do @transaction.expects :cleanup @catalog.apply end it "should return the transaction" do @catalog.apply.should equal(@transaction) end it "should yield the transaction if a block is provided" do @catalog.apply do |trans| trans.should equal(@transaction) end end it "should default to not being a host catalog" do @catalog.host_config.should be_nil end it "should pass supplied tags on to the transaction" do @transaction.expects(:tags=).with(%w{one two}) @catalog.apply(:tags => %w{one two}) end it "should set ignoreschedules on the transaction if specified in apply()" do @transaction.expects(:ignoreschedules=).with(true) @catalog.apply(:ignoreschedules => true) end end describe Puppet::Node::Catalog, " when applying host catalogs" do include ApplyingCatalogs # super() doesn't work in the setup method for some reason before do @catalog.host_config = true end it "should send a report if reporting is enabled" do Puppet[:report] = true @transaction.expects :send_report @transaction.stubs :any_failed? => false @catalog.apply end it "should send a report if report summaries are enabled" do Puppet[:summarize] = true @transaction.expects :send_report @transaction.stubs :any_failed? => false @catalog.apply end it "should initialize the state database before applying a catalog" do Puppet::Util::Storage.expects(:load) # Short-circuit the apply, so we know we're loading before the transaction Puppet::Transaction.expects(:new).raises ArgumentError proc { @catalog.apply }.should raise_error(ArgumentError) end it "should sync the state database after applying" do Puppet::Util::Storage.expects(:store) @transaction.stubs :any_failed? => false @catalog.apply end after { Puppet.settings.clear } end describe Puppet::Node::Catalog, " when applying non-host catalogs" do include ApplyingCatalogs before do @catalog.host_config = false end it "should never send reports" do Puppet[:report] = true Puppet[:summarize] = true @transaction.expects(:send_report).never @catalog.apply end it "should never modify the state database" do Puppet::Util::Storage.expects(:load).never Puppet::Util::Storage.expects(:store).never @catalog.apply end after { Puppet.settings.clear } end describe Puppet::Node::Catalog, " when creating a relationship graph" do before do @catalog = Puppet::Node::Catalog.new("host") @compone = Puppet::Type::Component.create :name => "one" @comptwo = Puppet::Type::Component.create :name => "two", :require => ["class", "one"] @file = Puppet::Type.type(:file) @one = @file.create :path => "/one" @two = @file.create :path => "/two" @catalog.add_edge! @compone, @one @catalog.add_edge! @comptwo, @two @three = @file.create :path => "/three" @four = @file.create :path => "/four", :require => ["file", "/three"] @five = @file.create :path => "/five" @catalog.add_resource @compone, @comptwo, @one, @two, @three, @four, @five @relationships = @catalog.relationship_graph end it "should fail when trying to create a relationship graph for a relationship graph" do proc { @relationships.relationship_graph }.should raise_error(Puppet::DevError) end it "should be able to create a relationship graph" do @relationships.should be_instance_of(Puppet::Node::Catalog) end it "should copy its host_config setting to the relationship graph" do config = Puppet::Node::Catalog.new config.host_config = true config.relationship_graph.host_config.should be_true end it "should not have any components" do @relationships.vertices.find { |r| r.instance_of?(Puppet::Type::Component) }.should be_nil end it "should have all non-component resources from the catalog" do # The failures print out too much info, so i just do a class comparison @relationships.vertex?(@five).should be_true end it "should have all resource relationships set as edges" do @relationships.edge?(@three, @four).should be_true end it "should copy component relationships to all contained resources" do @relationships.edge?(@one, @two).should be_true end it "should get removed when the catalog is cleaned up" do @relationships.expects(:clear).with(false) @catalog.clear @catalog.instance_variable_get("@relationship_graph").should be_nil end it "should create a new relationship graph after clearing the old one" do @relationships.expects(:clear).with(false) @catalog.clear @catalog.relationship_graph.should be_instance_of(Puppet::Node::Catalog) end it "should look up resources in the relationship graph if not found in the main catalog" do five = stub 'five', :ref => "File[five]", :catalog= => nil @relationships.add_resource five @catalog.resource(five.ref).should equal(five) end it "should provide a method to create additional resources that also registers the resource" do args = {:name => "/yay", :ensure => :file} resource = stub 'file', :ref => "File[/yay]", :catalog= => @catalog Puppet::Type.type(:file).expects(:create).with(args).returns(resource) @catalog.create_resource :file, args @catalog.resource("File[/yay]").should equal(resource) end it "should provide a mechanism for creating implicit resources" do args = {:name => "/yay", :ensure => :file} resource = stub 'file', :ref => "File[/yay]", :catalog= => @catalog Puppet::Type.type(:file).expects(:create).with(args).returns(resource) resource.expects(:implicit=).with(true) @catalog.create_implicit_resource :file, args @catalog.resource("File[/yay]").should equal(resource) end it "should add implicit resources to the relationship graph if there is one" do args = {:name => "/yay", :ensure => :file} resource = stub 'file', :ref => "File[/yay]", :catalog= => @catalog resource.expects(:implicit=).with(true) Puppet::Type.type(:file).expects(:create).with(args).returns(resource) # build the graph relgraph = @catalog.relationship_graph @catalog.create_implicit_resource :file, args relgraph.resource("File[/yay]").should equal(resource) end it "should remove resources created mid-transaction" do args = {:name => "/yay", :ensure => :file} resource = stub 'file', :ref => "File[/yay]", :catalog= => @catalog @transaction = mock 'transaction' Puppet::Transaction.stubs(:new).returns(@transaction) @transaction.stubs(:evaluate) @transaction.stubs(:cleanup) @transaction.stubs(:addtimes) Puppet::Type.type(:file).expects(:create).with(args).returns(resource) resource.expects :remove @catalog.apply do |trans| @catalog.create_resource :file, args @catalog.resource("File[/yay]").should equal(resource) end @catalog.resource("File[/yay]").should be_nil end it "should remove resources from the relationship graph if it exists" do @catalog.remove_resource(@one) @catalog.relationship_graph.vertex?(@one).should be_false end after do Puppet::Type.allclear end end describe Puppet::Node::Catalog, " when writing dot files" do before do @catalog = Puppet::Node::Catalog.new("host") @name = :test @file = File.join(Puppet[:graphdir], @name.to_s + ".dot") end it "should only write when it is a host catalog" do File.expects(:open).with(@file).never @catalog.host_config = false Puppet[:graph] = true @catalog.write_graph(@name) end it "should only write when graphing is enabled" do File.expects(:open).with(@file).never @catalog.host_config = true Puppet[:graph] = false @catalog.write_graph(@name) end it "should write a dot file based on the passed name" do File.expects(:open).with(@file, "w").yields(stub("file", :puts => nil)) @catalog.expects(:to_dot).with("name" => @name.to_s.capitalize) @catalog.host_config = true Puppet[:graph] = true @catalog.write_graph(@name) end after do Puppet.settings.clear end end describe Puppet::Node::Catalog, " when indirecting" do before do @indirection = mock 'indirection' Puppet::Indirector::Indirection.clear_cache end it "should redirect to the indirection for retrieval" do Puppet::Node::Catalog.stubs(:indirection).returns(@indirection) @indirection.expects(:find).with(:myconfig) Puppet::Node::Catalog.find(:myconfig) end it "should default to the 'compiler' terminus" do Puppet::Node::Catalog.indirection.terminus_class.should == :compiler end after do mocha_verify Puppet::Indirector::Indirection.clear_cache end end describe Puppet::Node::Catalog, " when converting to yaml" do before do @catalog = Puppet::Node::Catalog.new("me") @catalog.add_edge!("one", "two") end it "should be able to be dumped to yaml" do YAML.dump(@catalog).should be_instance_of(String) end end describe Puppet::Node::Catalog, " when converting from yaml" do before do @catalog = Puppet::Node::Catalog.new("me") @catalog.add_edge!("one", "two") text = YAML.dump(@catalog) @newcatalog = YAML.load(text) end it "should get converted back to a catalog" do @newcatalog.should be_instance_of(Puppet::Node::Catalog) end it "should have all vertices" do @newcatalog.vertex?("one").should be_true @newcatalog.vertex?("two").should be_true end it "should have all edges" do @newcatalog.edge?("one", "two").should be_true end end diff --git a/spec/unit/parser/ast/definition.rb b/spec/unit/parser/ast/definition.rb index fae2851e3..ba80894e8 100755 --- a/spec/unit/parser/ast/definition.rb +++ b/spec/unit/parser/ast/definition.rb @@ -1,47 +1,47 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../../spec_helper' describe Puppet::Parser::AST::Definition, "when initializing" do end describe Puppet::Parser::AST::Definition, "when evaluating" do before do @type = Puppet::Parser::Resource @parser = Puppet::Parser::Parser.new :Code => "" @source = @parser.newclass "" @definition = @parser.newdefine "mydefine" @node = Puppet::Node.new("yaynode") - @compile = Puppet::Parser::Compile.new(@node, @parser) - @scope = @compile.topscope + @compiler = Puppet::Parser::Compiler.new(@node, @parser) + @scope = @compiler.topscope @resource = Puppet::Parser::Resource.new(:type => "mydefine", :title => "myresource", :scope => @scope, :source => @source) end it "should create a new scope" do scope = nil code = mock 'code' code.expects(:safeevaluate).with do |scope| scope.object_id.should_not == @scope.object_id true end @definition.stubs(:code).returns(code) @definition.evaluate_code(@resource) end # it "should copy its namespace to the scope" # # it "should mark the scope virtual if the resource is virtual" # # it "should mark the scope exported if the resource is exported" # # it "should set the resource's parameters as variables in the scope" # # it "should set the resource's title as a variable in the scope" # # it "should copy the resource's title in a 'name' variable in the scope" # # it "should not copy the resource's title as the name if 'name' is one of the resource parameters" # # it "should evaluate the associated code with the new scope" end diff --git a/spec/unit/parser/ast/hostclass.rb b/spec/unit/parser/ast/hostclass.rb index b1e8a48ea..422861857 100755 --- a/spec/unit/parser/ast/hostclass.rb +++ b/spec/unit/parser/ast/hostclass.rb @@ -1,131 +1,131 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../../spec_helper' module HostClassTesting def setup @node = Puppet::Node.new "testnode" @parser = Puppet::Parser::Parser.new :environment => "development" @scope_resource = stub 'scope_resource', :builtin? => true - @compile = Puppet::Parser::Compile.new(@node, @parser) + @compiler = Puppet::Parser::Compiler.new(@node, @parser) - @scope = @compile.topscope + @scope = @compiler.topscope end end describe Puppet::Parser::AST::HostClass, "when evaluating" do include HostClassTesting before do @top = @parser.newclass "top" @middle = @parser.newclass "middle", :parent => "top" end it "should create a resource that references itself" do @top.evaluate(@scope) - @compile.catalog.resource(:class, "top").should be_instance_of(Puppet::Parser::Resource) + @compiler.catalog.resource(:class, "top").should be_instance_of(Puppet::Parser::Resource) end it "should evaluate the parent class if one exists" do @middle.evaluate(@scope) - @compile.catalog.resource(:class, "top").should be_instance_of(Puppet::Parser::Resource) + @compiler.catalog.resource(:class, "top").should be_instance_of(Puppet::Parser::Resource) end it "should fail to evaluate if a parent class is defined but cannot be found" do othertop = @parser.newclass "something", :parent => "yay" lambda { othertop.evaluate(@scope) }.should raise_error(Puppet::ParseError) end it "should not create a new resource if one already exists" do - @compile.catalog.expects(:resource).with(:class, "top").returns("something") - @compile.catalog.expects(:add_resource).never + @compiler.catalog.expects(:resource).with(:class, "top").returns("something") + @compiler.catalog.expects(:add_resource).never @top.evaluate(@scope) end it "should not create a new parent resource if one already exists and it has a parent class" do @top.evaluate(@scope) - top_resource = @compile.catalog.resource(:class, "top") + top_resource = @compiler.catalog.resource(:class, "top") @middle.evaluate(@scope) - @compile.catalog.resource(:class, "top").should equal(top_resource) + @compiler.catalog.resource(:class, "top").should equal(top_resource) end # #795 - tag before evaluation. it "should tag the catalog with the resource tags when it is evaluated" do @middle.evaluate(@scope) - @compile.catalog.should be_tagged("middle") + @compiler.catalog.should be_tagged("middle") end it "should tag the catalog with the parent class tags when it is evaluated" do @middle.evaluate(@scope) - @compile.catalog.should be_tagged("top") + @compiler.catalog.should be_tagged("top") end end describe Puppet::Parser::AST::HostClass, "when evaluating code" do include HostClassTesting before do @top_resource = stub "top_resource" @top = @parser.newclass "top", :code => @top_resource @middle_resource = stub "middle_resource" @middle = @parser.newclass "top::middle", :parent => "top", :code => @middle_resource end it "should set its namespace to its fully qualified name" do @middle.namespace.should == "top::middle" end it "should evaluate the code referred to by the class" do @top_resource.expects(:safeevaluate) resource = @top.evaluate(@scope) @top.evaluate_code(resource) end it "should evaluate the parent class's code if it has a parent" do @top_resource.expects(:safeevaluate) @middle_resource.expects(:safeevaluate) resource = @middle.evaluate(@scope) @middle.evaluate_code(resource) end it "should not evaluate the parent class's code if the parent has already been evaluated" do @top_resource.stubs(:safeevaluate) resource = @top.evaluate(@scope) @top.evaluate_code(resource) @top_resource.expects(:safeevaluate).never @middle_resource.stubs(:safeevaluate) resource = @middle.evaluate(@scope) @middle.evaluate_code(resource) end it "should use the parent class's scope as its parent scope" do @top_resource.stubs(:safeevaluate) @middle_resource.stubs(:safeevaluate) resource = @middle.evaluate(@scope) @middle.evaluate_code(resource) - @compile.class_scope(@middle).parent.should equal(@compile.class_scope(@top)) + @compiler.class_scope(@middle).parent.should equal(@compiler.class_scope(@top)) end it "should add the parent class's namespace to its namespace search path" do @top_resource.stubs(:safeevaluate) @middle_resource.stubs(:safeevaluate) resource = @middle.evaluate(@scope) @middle.evaluate_code(resource) - @compile.class_scope(@middle).namespaces.should be_include(@top.namespace) + @compiler.class_scope(@middle).namespaces.should be_include(@top.namespace) end end diff --git a/spec/unit/parser/collector.rb b/spec/unit/parser/collector.rb index aedbe1b9a..e1ceb23ed 100755 --- a/spec/unit/parser/collector.rb +++ b/spec/unit/parser/collector.rb @@ -1,417 +1,417 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../spec_helper' require 'puppet/parser/collector' describe Puppet::Parser::Collector, "when initializing" do before do @scope = mock 'scope' @resource_type = 'resource_type' @form = :exported @vquery = mock 'vquery' @equery = mock 'equery' @collector = Puppet::Parser::Collector.new(@scope, @resource_type, @equery, @vquery, @form) end it "should require a scope" do @collector.scope.should equal(@scope) end it "should require a resource type" do @collector.type.should == 'Resource_type' end it "should only accept :virtual or :exported as the collector form" do proc { @collector = Puppet::Parser::Collector.new(@scope, @resource_type, @vquery, @equery, :other) }.should raise_error(ArgumentError) end it "should accept an optional virtual query" do @collector.vquery.should equal(@vquery) end it "should accept an optional exported query" do @collector.equery.should equal(@equery) end it "should canonize the type name" do @collector = Puppet::Parser::Collector.new(@scope, "resource::type", @equery, @vquery, @form) @collector.type.should == "Resource::Type" end end describe Puppet::Parser::Collector, "when collecting specific virtual resources" do before do @scope = mock 'scope' @resource_type = mock 'resource_type' @vquery = mock 'vquery' @equery = mock 'equery' @collector = Puppet::Parser::Collector.new(@scope, @resource_type, @equery, @vquery, :virtual) end it "should not fail when it does not find any resources to collect" do @collector.resources = ["File[virtual1]", "File[virtual2]"] @scope.stubs(:findresource).returns(false) proc { @collector.evaluate }.should_not raise_error end it "should mark matched resources as non-virtual" do @collector.resources = ["File[virtual1]", "File[virtual2]"] one = mock 'one' one.expects(:virtual=).with(false) @scope.stubs(:findresource).with("File[virtual1]").returns(one) @scope.stubs(:findresource).with("File[virtual2]").returns(nil) @collector.evaluate end it "should return matched resources" do @collector.resources = ["File[virtual1]", "File[virtual2]"] one = mock 'one' one.stubs(:virtual=) @scope.stubs(:findresource).with("File[virtual1]").returns(one) @scope.stubs(:findresource).with("File[virtual2]").returns(nil) @collector.evaluate.should == [one] end it "should delete itself from the compile's collection list if it has found all of its resources" do @collector.resources = ["File[virtual1]"] one = mock 'one' one.stubs(:virtual=) - @compile.expects(:delete_collection).with(@collector) - @scope.expects(:compile).returns(@compile) + @compiler.expects(:delete_collection).with(@collector) + @scope.expects(:compiler).returns(@compiler) @scope.stubs(:findresource).with("File[virtual1]").returns(one) @collector.evaluate end it "should not delete itself from the compile's collection list if it has unfound resources" do @collector.resources = ["File[virtual1]"] one = mock 'one' one.stubs(:virtual=) - @compile.expects(:delete_collection).never + @compiler.expects(:delete_collection).never @scope.stubs(:findresource).with("File[virtual1]").returns(nil) @collector.evaluate end end describe Puppet::Parser::Collector, "when collecting virtual resources" do before do @scope = mock 'scope' - @compile = mock 'compile' - @scope.stubs(:compile).returns(@compile) + @compiler = mock 'compile' + @scope.stubs(:compiler).returns(@compiler) @resource_type = "Mytype" @vquery = proc { |res| true } @collector = Puppet::Parser::Collector.new(@scope, @resource_type, nil, @vquery, :virtual) end it "should find all resources matching the vquery" do one = stub 'one', :type => "Mytype", :virtual? => true two = stub 'two', :type => "Mytype", :virtual? => true one.stubs(:virtual=) two.stubs(:virtual=) - @compile.expects(:resources).returns([one, two]) + @compiler.expects(:resources).returns([one, two]) @collector.evaluate.should == [one, two] end it "should mark all matched resources as non-virtual" do one = stub 'one', :type => "Mytype", :virtual? => true one.expects(:virtual=).with(false) - @compile.expects(:resources).returns([one]) + @compiler.expects(:resources).returns([one]) @collector.evaluate end it "should return matched resources" do one = stub 'one', :type => "Mytype", :virtual? => true two = stub 'two', :type => "Mytype", :virtual? => true one.stubs(:virtual=) two.stubs(:virtual=) - @compile.expects(:resources).returns([one, two]) + @compiler.expects(:resources).returns([one, two]) @collector.evaluate.should == [one, two] end it "should return all resources of the correct type if there is no virtual query" do one = stub 'one', :type => "Mytype", :virtual? => true two = stub 'two', :type => "Mytype", :virtual? => true one.expects(:virtual=).with(false) two.expects(:virtual=).with(false) - @compile.expects(:resources).returns([one, two]) + @compiler.expects(:resources).returns([one, two]) @collector = Puppet::Parser::Collector.new(@scope, @resource_type, nil, nil, :virtual) @collector.evaluate.should == [one, two] end it "should not return or mark resources of a different type" do one = stub 'one', :type => "Mytype", :virtual? => true two = stub 'two', :type => :other, :virtual? => true one.expects(:virtual=).with(false) two.expects(:virtual=).never - @compile.expects(:resources).returns([one, two]) + @compiler.expects(:resources).returns([one, two]) @collector.evaluate.should == [one] end it "should not return or mark non-virtual resources" do one = stub 'one', :type => "Mytype", :virtual? => false two = stub 'two', :type => :other, :virtual? => false one.expects(:virtual=).never two.expects(:virtual=).never - @compile.expects(:resources).returns([one, two]) + @compiler.expects(:resources).returns([one, two]) @collector.evaluate.should be_false end it "should not return or mark non-matching resources" do @collector.vquery = proc { |res| res.name == :one } one = stub 'one', :name => :one, :type => "Mytype", :virtual? => true two = stub 'two', :name => :two, :type => "Mytype", :virtual? => true one.expects(:virtual=).with(false) two.expects(:virtual=).never - @compile.expects(:resources).returns([one, two]) + @compiler.expects(:resources).returns([one, two]) @collector.evaluate.should == [one] end end describe Puppet::Parser::Collector, "when collecting exported resources" do confine Puppet.features.rails? => "Cannot test Rails integration without ActiveRecord" before do @scope = stub 'scope', :host => "myhost", :debug => nil - @compile = mock 'compile' - @scope.stubs(:compile).returns(@compile) + @compiler = mock 'compile' + @scope.stubs(:compiler).returns(@compiler) @resource_type = "Mytype" @equery = "test = true" @vquery = proc { |r| true } @collector = Puppet::Parser::Collector.new(@scope, @resource_type, @equery, @vquery, :exported) end # Stub most of our interface to Rails. def stub_rails(everything = false) ActiveRecord::Base.stubs(:connected?).returns(false) Puppet::Rails.stubs(:init) if everything Puppet::Rails::Host.stubs(:find_by_name).returns(nil) Puppet::Rails::Resource.stubs(:find).returns([]) end end it "should use initialize the Rails support if ActiveRecord is not connected" do - @compile.stubs(:resources).returns([]) + @compiler.stubs(:resources).returns([]) ActiveRecord::Base.expects(:connected?).returns(false) Puppet::Rails.expects(:init) Puppet::Rails::Host.stubs(:find_by_name).returns(nil) Puppet::Rails::Resource.stubs(:find).returns([]) @collector.evaluate end it "should return all matching resources from the current compile" do stub_rails(true) one = stub 'one', :type => "Mytype", :virtual? => true, :exported? => true two = stub 'two', :type => "Mytype", :virtual? => true, :exported? => true one.stubs(:exported=) one.stubs(:virtual=) two.stubs(:exported=) two.stubs(:virtual=) - @compile.expects(:resources).returns([one, two]) + @compiler.expects(:resources).returns([one, two]) @collector.evaluate.should == [one, two] end it "should mark all returned resources as not virtual" do stub_rails(true) one = stub 'one', :type => "Mytype", :virtual? => true, :exported? => true one.stubs(:exported=) one.expects(:virtual=).with(false) - @compile.expects(:resources).returns([one]) + @compiler.expects(:resources).returns([one]) @collector.evaluate.should == [one] end it "should convert all found resources into parser resources" do stub_rails() Puppet::Rails::Host.stubs(:find_by_name).returns(nil) one = stub 'one', :restype => "Mytype", :title => "one", :virtual? => true, :exported? => true Puppet::Rails::Resource.stubs(:find).returns([one]) resource = mock 'resource' one.expects(:to_resource).with(@scope).returns(resource) resource.stubs(:exported=) resource.stubs(:virtual=) - @compile.stubs(:resources).returns([]) + @compiler.stubs(:resources).returns([]) @scope.stubs(:findresource).returns(nil) - @compile.stubs(:add_resource) + @compiler.stubs(:add_resource) @collector.evaluate.should == [resource] end it "should store converted resources in the compile's resource list" do stub_rails() Puppet::Rails::Host.stubs(:find_by_name).returns(nil) one = stub 'one', :restype => "Mytype", :title => "one", :virtual? => true, :exported? => true Puppet::Rails::Resource.stubs(:find).returns([one]) resource = mock 'resource' one.expects(:to_resource).with(@scope).returns(resource) resource.stubs(:exported=) resource.stubs(:virtual=) - @compile.stubs(:resources).returns([]) + @compiler.stubs(:resources).returns([]) @scope.stubs(:findresource).returns(nil) - @compile.expects(:add_resource).with(@scope, resource) + @compiler.expects(:add_resource).with(@scope, resource) @collector.evaluate.should == [resource] end # This way one host doesn't store another host's resources as exported. it "should mark resources collected from the database as not exported" do stub_rails() Puppet::Rails::Host.stubs(:find_by_name).returns(nil) one = stub 'one', :restype => "Mytype", :title => "one", :virtual? => true, :exported? => true Puppet::Rails::Resource.stubs(:find).returns([one]) resource = mock 'resource' one.expects(:to_resource).with(@scope).returns(resource) resource.expects(:exported=).with(false) resource.stubs(:virtual=) - @compile.stubs(:resources).returns([]) + @compiler.stubs(:resources).returns([]) @scope.stubs(:findresource).returns(nil) - @compile.stubs(:add_resource) + @compiler.stubs(:add_resource) @collector.evaluate end it "should fail if an equivalent resource already exists in the compile" do stub_rails() Puppet::Rails::Host.stubs(:find_by_name).returns(nil) rails = stub 'one', :restype => "Mytype", :title => "one", :virtual? => true, :exported? => true, :id => 1, :ref => "yay" inmemory = stub 'one', :type => "Mytype", :virtual? => true, :exported? => true, :rails_id => 2 Puppet::Rails::Resource.stubs(:find).returns([rails]) resource = mock 'resource' - @compile.stubs(:resources).returns([]) + @compiler.stubs(:resources).returns([]) @scope.stubs(:findresource).returns(inmemory) - @compile.stubs(:add_resource) + @compiler.stubs(:add_resource) proc { @collector.evaluate }.should raise_error(Puppet::ParseError) end it "should ignore exported resources that match already-collected resources" do stub_rails() Puppet::Rails::Host.stubs(:find_by_name).returns(nil) rails = stub 'one', :restype => "Mytype", :title => "one", :virtual? => true, :exported? => true, :id => 1, :ref => "yay" inmemory = stub 'one', :type => "Mytype", :virtual? => true, :exported? => true, :rails_id => 1 Puppet::Rails::Resource.stubs(:find).returns([rails]) resource = mock 'resource' - @compile.stubs(:resources).returns([]) + @compiler.stubs(:resources).returns([]) @scope.stubs(:findresource).returns(inmemory) - @compile.stubs(:add_resource) + @compiler.stubs(:add_resource) proc { @collector.evaluate }.should_not raise_error(Puppet::ParseError) end end describe Puppet::Parser::Collector, "when building its ActiveRecord query for collecting exported resources" do confine Puppet.features.rails? => "Cannot test Rails integration without ActiveRecord" before do @scope = stub 'scope', :host => "myhost", :debug => nil - @compile = mock 'compile' - @scope.stubs(:compile).returns(@compile) + @compiler = mock 'compile' + @scope.stubs(:compiler).returns(@compiler) @resource_type = "Mytype" @equery = nil @vquery = proc { |r| true } @collector = Puppet::Parser::Collector.new(@scope, @resource_type, @equery, @vquery, :exported) - @compile.stubs(:resources).returns([]) + @compiler.stubs(:resources).returns([]) ActiveRecord::Base.stubs(:connected?).returns(false) Puppet::Rails.stubs(:init) Puppet::Rails::Host.stubs(:find_by_name).returns(nil) Puppet::Rails::Resource.stubs(:find).returns([]) end it "should exclude all resources from the host if ActiveRecord contains information for this host" do @host = mock 'host' @host.stubs(:id).returns 5 Puppet::Rails::Host.expects(:find_by_name).with(@scope.host).returns(@host) Puppet::Rails::Resource.stubs(:find).with { |*arguments| options = arguments[3] options[:conditions][0] =~ /^host_id != \?/ and options[:conditions][1] == 5 }.returns([]) @collector.evaluate end it "should return parameter names and parameter values when querying ActiveRecord" do Puppet::Rails::Resource.stubs(:find).with { |*arguments| options = arguments[3] options[:include] == {:param_values => :param_name} }.returns([]) @collector.evaluate end it "should only search for exported resources with the matching type" do Puppet::Rails::Resource.stubs(:find).with { |*arguments| options = arguments[3] options[:conditions][0].include?("(exported=? AND restype=?)") and options[:conditions][1] == true and options[:conditions][2] == "Mytype" }.returns([]) end it "should include the export query if one is provided" do @collector = Puppet::Parser::Collector.new(@scope, @resource_type, "test = true", @vquery, :exported) Puppet::Rails::Resource.stubs(:find).with { |*arguments| options = arguments[3] options[:conditions][0].include?("test = true") }.returns([]) end end diff --git a/spec/unit/parser/compile.rb b/spec/unit/parser/compiler.rb similarity index 62% rename from spec/unit/parser/compile.rb rename to spec/unit/parser/compiler.rb index 3903f6879..d3039996f 100755 --- a/spec/unit/parser/compile.rb +++ b/spec/unit/parser/compiler.rb @@ -1,535 +1,535 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../spec_helper' -module CompileTesting +module CompilerTesting def setup @node = Puppet::Node.new "testnode" @parser = Puppet::Parser::Parser.new :environment => "development" @scope_resource = stub 'scope_resource', :builtin? => true @scope = stub 'scope', :resource => @scope_resource, :source => mock("source") - @compile = Puppet::Parser::Compile.new(@node, @parser) + @compiler = Puppet::Parser::Compiler.new(@node, @parser) end end -describe Puppet::Parser::Compile do - include CompileTesting +describe Puppet::Parser::Compiler do + include CompilerTesting it "should be able to store references to class scopes" do - lambda { @compile.class_set "myname", "myscope" }.should_not raise_error + lambda { @compiler.class_set "myname", "myscope" }.should_not raise_error end it "should be able to retrieve class scopes by name" do - @compile.class_set "myname", "myscope" - @compile.class_scope("myname").should == "myscope" + @compiler.class_set "myname", "myscope" + @compiler.class_scope("myname").should == "myscope" end it "should be able to retrieve class scopes by object" do klass = mock 'ast_class' klass.expects(:classname).returns("myname") - @compile.class_set "myname", "myscope" - @compile.class_scope(klass).should == "myscope" + @compiler.class_set "myname", "myscope" + @compiler.class_scope(klass).should == "myscope" end it "should be able to return a class list containing all set classes" do - @compile.class_set "", "empty" - @compile.class_set "one", "yep" - @compile.class_set "two", "nope" + @compiler.class_set "", "empty" + @compiler.class_set "one", "yep" + @compiler.class_set "two", "nope" - @compile.classlist.sort.should == %w{one two}.sort + @compiler.classlist.sort.should == %w{one two}.sort end end -describe Puppet::Parser::Compile, " when initializing" do - include CompileTesting +describe Puppet::Parser::Compiler, " when initializing" do + include CompilerTesting it "should set its node attribute" do - @compile.node.should equal(@node) + @compiler.node.should equal(@node) end it "should set its parser attribute" do - @compile.parser.should equal(@parser) + @compiler.parser.should equal(@parser) end it "should detect when ast nodes are absent" do - @compile.ast_nodes?.should be_false + @compiler.ast_nodes?.should be_false end it "should detect when ast nodes are present" do @parser.nodes["testing"] = "yay" - @compile.ast_nodes?.should be_true + @compiler.ast_nodes?.should be_true end end -describe Puppet::Parser::Compile, "when managing scopes" do - include CompileTesting +describe Puppet::Parser::Compiler, "when managing scopes" do + include CompilerTesting it "should create a top scope" do - @compile.topscope.should be_instance_of(Puppet::Parser::Scope) + @compiler.topscope.should be_instance_of(Puppet::Parser::Scope) end it "should be able to create new scopes" do - @compile.newscope(@compile.topscope).should be_instance_of(Puppet::Parser::Scope) + @compiler.newscope(@compiler.topscope).should be_instance_of(Puppet::Parser::Scope) end it "should correctly set the level of newly created scopes" do - @compile.newscope(@compile.topscope, :level => 5).level.should == 5 + @compiler.newscope(@compiler.topscope, :level => 5).level.should == 5 end it "should set the parent scope of the new scope to be the passed-in parent" do scope = mock 'scope' - newscope = @compile.newscope(scope) + newscope = @compiler.newscope(scope) - @compile.parent(newscope).should equal(scope) + @compiler.parent(newscope).should equal(scope) end end -describe Puppet::Parser::Compile, " when compiling" do - include CompileTesting +describe Puppet::Parser::Compiler, " when compiling" do + include CompilerTesting 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) } + (compile_methods - except).each { |m| @compiler.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" + @compiler.compile + @compiler.topscope.lookupvar("a").should == "b" + @compiler.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', :classname => "one" three = stub 'three', :classname => "three" @node.stubs(:name).returns("whatever") @node.stubs(:classes).returns(classes) - @compile.expects(:evaluate_classes).with(classes, @compile.topscope) - @compile.class.publicize_methods(:evaluate_node_classes) { @compile.evaluate_node_classes } + @compiler.expects(:evaluate_classes).with(classes, @compiler.topscope) + @compiler.class.publicize_methods(:evaluate_node_classes) { @compiler.evaluate_node_classes } end it "should enable ast_nodes if the parser has any nodes" do @parser.expects(:nodes).returns(:one => :yay) - @compile.ast_nodes?.should be_true + @compiler.ast_nodes?.should be_true end it "should disable ast_nodes if the parser has no nodes" do @parser.expects(:nodes).returns({}) - @compile.ast_nodes?.should be_false + @compiler.ast_nodes?.should be_false end it "should evaluate the main class if it exists" do compile_stub(:evaluate_main) main_class = mock 'main_class' main_class.expects(:evaluate_code).with { |r| r.is_a?(Puppet::Parser::Resource) } - @compile.topscope.expects(:source=).with(main_class) + @compiler.topscope.expects(:source=).with(main_class) @parser.stubs(:findclass).with("", "").returns(main_class) - @compile.compile + @compiler.compile end it "should evaluate any node classes" do @node.stubs(:classes).returns(%w{one two three four}) - @compile.expects(:evaluate_classes).with(%w{one two three four}, @compile.topscope) - @compile.send(:evaluate_node_classes) + @compiler.expects(:evaluate_classes).with(%w{one two three four}, @compiler.topscope) + @compiler.send(:evaluate_node_classes) end it "should evaluate all added collections" do colls = [] # And when the collections fail to evaluate. colls << mock("coll1-false") colls << mock("coll2-false") colls.each { |c| c.expects(:evaluate).returns(false) } - @compile.add_collection(colls[0]) - @compile.add_collection(colls[1]) + @compiler.add_collection(colls[0]) + @compiler.add_collection(colls[1]) compile_stub(:evaluate_generators) - @compile.compile + @compiler.compile end it "should ignore builtin resources" do resource = stub 'builtin', :ref => "File[testing]", :builtin? => true - @compile.add_resource(@scope, resource) + @compiler.add_resource(@scope, resource) resource.expects(:evaluate).never - @compile.compile + @compiler.compile end it "should evaluate unevaluated resources" do resource = stub 'notevaluated', :ref => "File[testing]", :builtin? => false, :evaluated? => false - @compile.add_resource(@scope, resource) + @compiler.add_resource(@scope, resource) # We have to now mark the resource as evaluated resource.expects(:evaluate).with { |*whatever| resource.stubs(:evaluated?).returns true } - @compile.compile + @compiler.compile end it "should not evaluate already-evaluated resources" do resource = stub 'already_evaluated', :ref => "File[testing]", :builtin? => false, :evaluated? => true - @compile.add_resource(@scope, resource) + @compiler.add_resource(@scope, resource) resource.expects(:evaluate).never - @compile.compile + @compiler.compile end it "should evaluate unevaluated resources created by evaluating other resources" do resource = stub 'notevaluated', :ref => "File[testing]", :builtin? => false, :evaluated? => false - @compile.add_resource(@scope, resource) + @compiler.add_resource(@scope, resource) resource2 = stub 'created', :ref => "File[other]", :builtin? => false, :evaluated? => false # We have to now mark the resource as evaluated - resource.expects(:evaluate).with { |*whatever| resource.stubs(:evaluated?).returns(true); @compile.add_resource(@scope, resource2) } + resource.expects(:evaluate).with { |*whatever| resource.stubs(:evaluated?).returns(true); @compiler.add_resource(@scope, resource2) } resource2.expects(:evaluate).with { |*whatever| resource2.stubs(:evaluated?).returns(true) } - @compile.compile + @compiler.compile end it "should call finish() on all resources" do # Add a resource that does respond to :finish resource = Puppet::Parser::Resource.new :scope => @scope, :type => "file", :title => "finish" resource.expects(:finish) - @compile.add_resource(@scope, resource) + @compiler.add_resource(@scope, resource) # And one that does not dnf = stub "dnf", :ref => "File[dnf]" - @compile.add_resource(@scope, dnf) + @compiler.add_resource(@scope, dnf) - @compile.send(:finish) + @compiler.send(:finish) end it "should add resources that do not conflict with existing resources" do resource = stub "noconflict", :ref => "File[yay]" - @compile.add_resource(@scope, resource) + @compiler.add_resource(@scope, resource) - @compile.catalog.should be_vertex(resource) + @compiler.catalog.should be_vertex(resource) end it "should fail to add resources that conflict with existing resources" do type = stub 'faketype', :isomorphic? => true, :name => "mytype" Puppet::Type.stubs(:type).with("mytype").returns(type) resource1 = stub "iso1conflict", :ref => "Mytype[yay]", :type => "mytype", :file => "eh", :line => 0 resource2 = stub "iso2conflict", :ref => "Mytype[yay]", :type => "mytype", :file => "eh", :line => 0 - @compile.add_resource(@scope, resource1) - lambda { @compile.add_resource(@scope, resource2) }.should raise_error(ArgumentError) + @compiler.add_resource(@scope, resource1) + lambda { @compiler.add_resource(@scope, resource2) }.should raise_error(ArgumentError) end it "should have a method for looking up resources" do resource = stub 'resource', :ref => "Yay[foo]" - @compile.add_resource(@scope, resource) - @compile.findresource("Yay[foo]").should equal(resource) + @compiler.add_resource(@scope, resource) + @compiler.findresource("Yay[foo]").should equal(resource) end it "should be able to look resources up by type and title" do resource = stub 'resource', :ref => "Yay[foo]" - @compile.add_resource(@scope, resource) - @compile.findresource("Yay", "foo").should equal(resource) + @compiler.add_resource(@scope, resource) + @compiler.findresource("Yay", "foo").should equal(resource) end end -describe Puppet::Parser::Compile, " when evaluating collections" do - include CompileTesting +describe Puppet::Parser::Compiler, " when evaluating collections" do + include CompilerTesting it "should evaluate each collection" do 2.times { |i| coll = mock 'coll%s' % i - @compile.add_collection(coll) + @compiler.add_collection(coll) # This is the hard part -- we have to emulate the fact that # collections delete themselves if they are done evaluating. coll.expects(:evaluate).with do - @compile.delete_collection(coll) + @compiler.delete_collection(coll) end } - @compile.class.publicize_methods(:evaluate_collections) { @compile.evaluate_collections } + @compiler.class.publicize_methods(:evaluate_collections) { @compiler.evaluate_collections } end it "should not fail when there are unevaluated resource collections that do not refer to specific resources" do coll = stub 'coll', :evaluate => false coll.expects(:resources).returns(nil) - @compile.add_collection(coll) + @compiler.add_collection(coll) - lambda { @compile.compile }.should_not raise_error + lambda { @compiler.compile }.should_not raise_error end it "should fail when there are unevaluated resource collections that refer to a specific resource" do coll = stub 'coll', :evaluate => false coll.expects(:resources).returns(:something) - @compile.add_collection(coll) + @compiler.add_collection(coll) - lambda { @compile.compile }.should raise_error(Puppet::ParseError) + lambda { @compiler.compile }.should raise_error(Puppet::ParseError) end it "should fail when there are unevaluated resource collections that refer to multiple specific resources" do coll = stub 'coll', :evaluate => false coll.expects(:resources).returns([:one, :two]) - @compile.add_collection(coll) + @compiler.add_collection(coll) - lambda { @compile.compile }.should raise_error(Puppet::ParseError) + lambda { @compiler.compile }.should raise_error(Puppet::ParseError) end end -describe Puppet::Parser::Compile, "when told to evaluate missing classes" do - include CompileTesting +describe Puppet::Parser::Compiler, "when told to evaluate missing classes" do + include CompilerTesting it "should fail if there's no source listed for the scope" do scope = stub 'scope', :source => nil - proc { @compile.evaluate_classes(%w{one two}, scope) }.should raise_error(Puppet::DevError) + proc { @compiler.evaluate_classes(%w{one two}, scope) }.should raise_error(Puppet::DevError) end it "should tag the catalog with the name of each not-found class" do - @compile.catalog.expects(:tag).with("notfound") + @compiler.catalog.expects(:tag).with("notfound") @scope.expects(:findclass).with("notfound").returns(nil) - @compile.evaluate_classes(%w{notfound}, @scope) + @compiler.evaluate_classes(%w{notfound}, @scope) end end -describe Puppet::Parser::Compile, " when evaluating found classes" do - include CompileTesting +describe Puppet::Parser::Compiler, " when evaluating found classes" do + include CompilerTesting before do @class = stub 'class', :classname => "my::class" @scope.stubs(:findclass).with("myclass").returns(@class) @resource = stub 'resource', :ref => "Class[myclass]" end it "should evaluate each class" do - @compile.catalog.stubs(:tag) + @compiler.catalog.stubs(:tag) @class.expects(:evaluate).with(@scope) - @compile.evaluate_classes(%w{myclass}, @scope) + @compiler.evaluate_classes(%w{myclass}, @scope) end it "should not evaluate the resources created for found classes unless asked" do - @compile.catalog.stubs(:tag) + @compiler.catalog.stubs(:tag) @resource.expects(:evaluate).never @class.expects(:evaluate).returns(@resource) - @compile.evaluate_classes(%w{myclass}, @scope) + @compiler.evaluate_classes(%w{myclass}, @scope) end it "should immediately evaluate the resources created for found classes when asked" do - @compile.catalog.stubs(:tag) + @compiler.catalog.stubs(:tag) @resource.expects(:evaluate) @class.expects(:evaluate).returns(@resource) - @compile.evaluate_classes(%w{myclass}, @scope, false) + @compiler.evaluate_classes(%w{myclass}, @scope, false) end it "should skip classes that have already been evaluated" do - @compile.catalog.stubs(:tag) + @compiler.catalog.stubs(:tag) - @compile.expects(:class_scope).with(@class).returns("something") + @compiler.expects(:class_scope).with(@class).returns("something") - @compile.expects(:add_resource).never + @compiler.expects(:add_resource).never @resource.expects(:evaluate).never Puppet::Parser::Resource.expects(:new).never - @compile.evaluate_classes(%w{myclass}, @scope, false) + @compiler.evaluate_classes(%w{myclass}, @scope, false) end it "should return the list of found classes" do - @compile.catalog.stubs(:tag) + @compiler.catalog.stubs(:tag) - @compile.stubs(:add_resource) + @compiler.stubs(:add_resource) @scope.stubs(:findclass).with("notfound").returns(nil) Puppet::Parser::Resource.stubs(:new).returns(@resource) @class.stubs :evaluate - @compile.evaluate_classes(%w{myclass notfound}, @scope).should == %w{myclass} + @compiler.evaluate_classes(%w{myclass notfound}, @scope).should == %w{myclass} end end -describe Puppet::Parser::Compile, " when evaluating AST nodes with no AST nodes present" do - include CompileTesting +describe Puppet::Parser::Compiler, " when evaluating AST nodes with no AST nodes present" do + include CompilerTesting it "should do nothing" do - @compile.expects(:ast_nodes?).returns(false) - @compile.parser.expects(:nodes).never + @compiler.expects(:ast_nodes?).returns(false) + @compiler.parser.expects(:nodes).never Puppet::Parser::Resource.expects(:new).never - @compile.send(:evaluate_ast_node) + @compiler.send(:evaluate_ast_node) end end -describe Puppet::Parser::Compile, " when evaluating AST nodes with AST nodes present" do - include CompileTesting +describe Puppet::Parser::Compiler, " when evaluating AST nodes with AST nodes present" do + include CompilerTesting before do @nodes = mock 'node_hash' - @compile.stubs(:ast_nodes?).returns(true) - @compile.parser.stubs(:nodes).returns(@nodes) + @compiler.stubs(:ast_nodes?).returns(true) + @compiler.parser.stubs(:nodes).returns(@nodes) # Set some names for our test @node.stubs(:names).returns(%w{a b c}) @nodes.stubs(:[]).with("a").returns(nil) @nodes.stubs(:[]).with("b").returns(nil) @nodes.stubs(:[]).with("c").returns(nil) # It should check this last, of course. @nodes.stubs(:[]).with("default").returns(nil) end it "should fail if the named node cannot be found" do - proc { @compile.send(:evaluate_ast_node) }.should raise_error(Puppet::ParseError) + proc { @compiler.send(:evaluate_ast_node) }.should raise_error(Puppet::ParseError) end it "should evaluate the first node class matching the node name" do node_class = stub 'node', :classname => "c", :evaluate_code => nil @nodes.stubs(:[]).with("c").returns(node_class) node_resource = stub 'node resource', :ref => "Node[c]", :evaluate => nil node_class.expects(:evaluate).returns(node_resource) - @compile.compile + @compiler.compile end it "should match the default node if no matching node can be found" do node_class = stub 'node', :classname => "default", :evaluate_code => nil @nodes.stubs(:[]).with("default").returns(node_class) node_resource = stub 'node resource', :ref => "Node[default]", :evaluate => nil node_class.expects(:evaluate).returns(node_resource) - @compile.compile + @compiler.compile end it "should evaluate the node resource immediately rather than using lazy evaluation" do node_class = stub 'node', :classname => "c" @nodes.stubs(:[]).with("c").returns(node_class) node_resource = stub 'node resource', :ref => "Node[c]" node_class.expects(:evaluate).returns(node_resource) node_resource.expects(:evaluate) - @compile.send(:evaluate_ast_node) + @compiler.send(:evaluate_ast_node) end it "should set the node's scope as the top scope" do node_resource = stub 'node resource', :ref => "Node[c]", :evaluate => nil node_class = stub 'node', :classname => "c", :evaluate => node_resource @nodes.stubs(:[]).with("c").returns(node_class) # The #evaluate method normally does this. scope = stub 'scope', :source => "mysource" - @compile.class_set(node_class.classname, scope) + @compiler.class_set(node_class.classname, scope) node_resource.stubs(:evaluate) - @compile.compile + @compiler.compile - @compile.topscope.should equal(scope) + @compiler.topscope.should equal(scope) end end -describe Puppet::Parser::Compile, "when storing compiled resources" do - include CompileTesting +describe Puppet::Parser::Compiler, "when storing compiled resources" do + include CompilerTesting it "should store the resources" do Puppet.features.expects(:rails?).returns(true) Puppet::Rails.expects(:connect) - @compile.catalog.expects(:vertices).returns(:resources) + @compiler.catalog.expects(:vertices).returns(:resources) - @compile.expects(:store_to_active_record).with(@node, :resources) - @compile.send(:store) + @compiler.expects(:store_to_active_record).with(@node, :resources) + @compiler.send(:store) end it "should store to active_record" do @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) + @compiler.send(:store_to_active_record, @node, :resources) end end -describe Puppet::Parser::Compile, "when managing resource overrides" do - include CompileTesting +describe Puppet::Parser::Compiler, "when managing resource overrides" do + include CompilerTesting before do @override = stub 'override', :ref => "My[ref]" @resource = stub 'resource', :ref => "My[ref]", :builtin? => true end it "should be able to store overrides" do - lambda { @compile.add_override(@override) }.should_not raise_error + lambda { @compiler.add_override(@override) }.should_not raise_error end it "should apply overrides to the appropriate resources" do - @compile.add_resource(@scope, @resource) + @compiler.add_resource(@scope, @resource) @resource.expects(:merge).with(@override) - @compile.add_override(@override) + @compiler.add_override(@override) - @compile.compile + @compiler.compile end it "should accept overrides before the related resource has been created" do @resource.expects(:merge).with(@override) # First store the override - @compile.add_override(@override) + @compiler.add_override(@override) # Then the resource - @compile.add_resource(@scope, @resource) + @compiler.add_resource(@scope, @resource) # And compile, so they get resolved - @compile.compile + @compiler.compile end it "should fail if the compile is finished and resource overrides have not been applied" do - @compile.add_override(@override) + @compiler.add_override(@override) - lambda { @compile.compile }.should raise_error(Puppet::ParseError) + lambda { @compiler.compile }.should raise_error(Puppet::ParseError) end end # #620 - Nodes and classes should conflict, else classes don't get evaluated -describe Puppet::Parser::Compile, "when evaluating nodes and classes with the same name (#620)" do - include CompileTesting +describe Puppet::Parser::Compiler, "when evaluating nodes and classes with the same name (#620)" do + include CompilerTesting before do @node = stub :nodescope? => true @class = stub :nodescope? => false end it "should fail if a node already exists with the same name as the class being evaluated" do - @compile.class_set("one", @node) - lambda { @compile.class_set("one", @class) }.should raise_error(Puppet::ParseError) + @compiler.class_set("one", @node) + lambda { @compiler.class_set("one", @class) }.should raise_error(Puppet::ParseError) end it "should fail if a class already exists with the same name as the node being evaluated" do - @compile.class_set("one", @class) - lambda { @compile.class_set("one", @node) }.should raise_error(Puppet::ParseError) + @compiler.class_set("one", @class) + lambda { @compiler.class_set("one", @node) }.should raise_error(Puppet::ParseError) end end diff --git a/spec/unit/parser/interpreter.rb b/spec/unit/parser/interpreter.rb index ed30ced93..7885f0542 100755 --- a/spec/unit/parser/interpreter.rb +++ b/spec/unit/parser/interpreter.rb @@ -1,148 +1,148 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../spec_helper' describe Puppet::Parser::Interpreter, " when creating parser instances" do before do @interp = Puppet::Parser::Interpreter.new @parser = mock('parser') end it "should create a parser with code if there is code defined in the :code setting" do Puppet.settings.stubs(:value).with(:code, :myenv).returns("mycode") @parser.expects(:string=).with("mycode") @parser.expects(:parse) Puppet::Parser::Parser.expects(:new).with(:environment => :myenv).returns(@parser) @interp.send(:create_parser, :myenv).object_id.should equal(@parser.object_id) end it "should create a parser with the main manifest when the code setting is an empty string" do Puppet.settings.stubs(:value).with(:code, :myenv).returns("") Puppet.settings.stubs(:value).with(:manifest, :myenv).returns("/my/file") @parser.expects(:parse) @parser.expects(:file=).with("/my/file") Puppet::Parser::Parser.expects(:new).with(:environment => :myenv).returns(@parser) @interp.send(:create_parser, :myenv).should equal(@parser) end it "should return nothing when new parsers fail" do Puppet::Parser::Parser.expects(:new).with(:environment => :myenv).raises(ArgumentError) proc { @interp.send(:create_parser, :myenv) }.should raise_error(Puppet::Error) end it "should create parsers with environment-appropriate manifests" do # Set our per-environment values. We can't just stub :value, because # it's called by too much of the rest of the code. text = "[env1]\nmanifest = /t/env1.pp\n[env2]\nmanifest = /t/env2.pp" file = mock 'file' file.stubs(:changed?).returns(true) file.stubs(:file).returns("/whatever") Puppet.settings.stubs(:read_file).with(file).returns(text) Puppet.settings.parse(file) parser1 = mock 'parser1' Puppet::Parser::Parser.expects(:new).with(:environment => :env1).returns(parser1) parser1.expects(:file=).with("/t/env1.pp") parser1.expects(:parse) @interp.send(:create_parser, :env1) parser2 = mock 'parser2' Puppet::Parser::Parser.expects(:new).with(:environment => :env2).returns(parser2) parser2.expects(:file=).with("/t/env2.pp") parser2.expects(:parse) @interp.send(:create_parser, :env2) end end describe Puppet::Parser::Interpreter, " when managing parser instances" do before do @interp = Puppet::Parser::Interpreter.new @parser = mock('parser') end it "should use the same parser when the parser does not need reparsing" do @interp.expects(:create_parser).with(:myenv).returns(@parser) @interp.send(:parser, :myenv).should equal(@parser) @parser.expects(:reparse?).returns(false) @interp.send(:parser, :myenv).should equal(@parser) end it "should create a new parser when reparse is true" do oldparser = mock('oldparser') newparser = mock('newparser') oldparser.expects(:reparse?).returns(true) oldparser.expects(:clear) @interp.expects(:create_parser).with(:myenv).returns(oldparser) @interp.send(:parser, :myenv).should equal(oldparser) @interp.expects(:create_parser).with(:myenv).returns(newparser) @interp.send(:parser, :myenv).should equal(newparser) end it "should fail intelligently if a parser cannot be created and one does not already exist" do @interp.expects(:create_parser).with(:myenv).raises(ArgumentError) proc { @interp.send(:parser, :myenv) }.should raise_error(ArgumentError) end it "should keep the old parser if a new parser cannot be created" do # Get the first parser in the hash. @interp.expects(:create_parser).with(:myenv).returns(@parser) @interp.send(:parser, :myenv).should equal(@parser) # Have it indicate something has changed @parser.expects(:reparse?).returns(true) # But fail to create a new parser @interp.expects(:create_parser).with(:myenv).raises(ArgumentError) # And make sure we still get the old valid parser @interp.send(:parser, :myenv).should equal(@parser) end it "should use different parsers for different environments" do # get one for the first env @interp.expects(:create_parser).with(:first_env).returns(@parser) @interp.send(:parser, :first_env).should equal(@parser) other_parser = mock('otherparser') @interp.expects(:create_parser).with(:second_env).returns(other_parser) @interp.send(:parser, :second_env).should equal(other_parser) end end describe Puppet::Parser::Interpreter, " when compiling catalog" do before do @interp = Puppet::Parser::Interpreter.new @node = stub 'node', :environment => :myenv - @compile = mock 'compile' + @compiler = mock 'compile' @parser = mock 'parser' end it "should create a compile with the node and parser" do - @compile.expects(:compile).returns(:config) + @compiler.expects(:compile).returns(:config) @interp.expects(:parser).with(:myenv).returns(@parser) - Puppet::Parser::Compile.expects(:new).with(@node, @parser).returns(@compile) + Puppet::Parser::Compiler.expects(:new).with(@node, @parser).returns(@compiler) @interp.compile(@node) end it "should fail intelligently when no parser can be found" do @interp.expects(:parser).with(:myenv).returns(nil) proc { @interp.compile(@node) }.should raise_error(Puppet::ParseError) end end describe Puppet::Parser::Interpreter, " when returning catalog version" do before do @interp = Puppet::Parser::Interpreter.new end it "should ask the appropriate parser for the catalog version" do node = mock 'node' node.expects(:environment).returns(:myenv) parser = mock 'parser' parser.expects(:version).returns(:myvers) @interp.expects(:parser).with(:myenv).returns(parser) @interp.configuration_version(node).should equal(:myvers) end end diff --git a/spec/unit/parser/resource.rb b/spec/unit/parser/resource.rb index 099cfd05e..a5a49e2a6 100755 --- a/spec/unit/parser/resource.rb +++ b/spec/unit/parser/resource.rb @@ -1,149 +1,149 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../spec_helper' # LAK: FIXME This is just new tests for resources; I have # not moved all tests over yet. describe Puppet::Parser::Resource, " when evaluating" do before do @type = Puppet::Parser::Resource @parser = Puppet::Parser::Parser.new :Code => "" @source = @parser.newclass "" @definition = @parser.newdefine "mydefine" @class = @parser.newclass "myclass" @nodedef = @parser.newnode("mynode")[0] @node = Puppet::Node.new("yaynode") - @compile = Puppet::Parser::Compile.new(@node, @parser) - @scope = @compile.topscope + @compiler = Puppet::Parser::Compiler.new(@node, @parser) + @scope = @compiler.topscope end it "should evaluate the associated AST definition" do res = @type.new(:type => "mydefine", :title => "whatever", :scope => @scope, :source => @source) @definition.expects(:evaluate_code).with(res) res.evaluate end it "should evaluate the associated AST class" do res = @type.new(:type => "class", :title => "myclass", :scope => @scope, :source => @source) @class.expects(:evaluate_code).with(res) res.evaluate end it "should evaluate the associated AST node" do res = @type.new(:type => "node", :title => "mynode", :scope => @scope, :source => @source) @nodedef.expects(:evaluate_code).with(res) res.evaluate end end describe Puppet::Parser::Resource, " when finishing" do before do @parser = Puppet::Parser::Parser.new :Code => "" @source = @parser.newclass "" @definition = @parser.newdefine "mydefine" @class = @parser.newclass "myclass" @nodedef = @parser.newnode("mynode")[0] @node = Puppet::Node.new("yaynode") - @compile = Puppet::Parser::Compile.new(@node, @parser) - @scope = @compile.topscope + @compiler = Puppet::Parser::Compiler.new(@node, @parser) + @scope = @compiler.topscope @resource = Puppet::Parser::Resource.new(:type => "mydefine", :title => "whatever", :scope => @scope, :source => @source) end it "should copy metaparams from its scope" do @scope.setvar("noop", "true") @resource.class.publicize_methods(:add_metaparams) { @resource.add_metaparams } @resource["noop"].should == "true" end it "should not copy metaparams that it already has" do @resource.class.publicize_methods(:set_parameter) { @resource.set_parameter("noop", "false") } @scope.setvar("noop", "true") @resource.class.publicize_methods(:add_metaparams) { @resource.add_metaparams } @resource["noop"].should == "false" end it "should stack relationship metaparams from its container if it already has them" do @resource.class.publicize_methods(:set_parameter) { @resource.set_parameter("require", "resource") } @scope.setvar("require", "container") @resource.class.publicize_methods(:add_metaparams) { @resource.add_metaparams } @resource["require"].sort.should == %w{container resource} end it "should flatten the array resulting from stacking relationship metaparams" do @resource.class.publicize_methods(:set_parameter) { @resource.set_parameter("require", ["resource1", "resource2"]) } @scope.setvar("require", %w{container1 container2}) @resource.class.publicize_methods(:add_metaparams) { @resource.add_metaparams } @resource["require"].sort.should == %w{container1 container2 resource1 resource2} end it "should add any tags from the scope resource" do scope_resource = stub 'scope_resource', :tags => %w{one two} @scope.stubs(:resource).returns(scope_resource) @resource.class.publicize_methods(:add_scope_tags) { @resource.add_scope_tags } @resource.tags.should be_include("one") @resource.tags.should be_include("two") end end describe Puppet::Parser::Resource, "when being tagged" do before do @scope_resource = stub 'scope_resource', :tags => %w{srone srtwo} @scope = stub 'scope', :resource => @scope_resource @resource = Puppet::Parser::Resource.new(:type => "file", :title => "yay", :scope => @scope, :source => mock('source')) end it "should get tagged with the resource type" do @resource.tags.should be_include("file") end it "should get tagged with the title" do @resource.tags.should be_include("yay") end it "should get tagged with each name in the title if the title is a qualified class name" do resource = Puppet::Parser::Resource.new(:type => "file", :title => "one::two", :scope => @scope, :source => mock('source')) resource.tags.should be_include("one") resource.tags.should be_include("two") end it "should get tagged with each name in the type if the type is a qualified class name" do resource = Puppet::Parser::Resource.new(:type => "one::two", :title => "whatever", :scope => @scope, :source => mock('source')) resource.tags.should be_include("one") resource.tags.should be_include("two") end it "should not get tagged with non-alphanumeric titles" do resource = Puppet::Parser::Resource.new(:type => "file", :title => "this is a test", :scope => @scope, :source => mock('source')) resource.tags.should_not be_include("this is a test") end it "should fail on tags containing '*' characters" do lambda { @resource.tag("bad*tag") }.should raise_error(Puppet::ParseError) end it "should fail on tags starting with '-' characters" do lambda { @resource.tag("-badtag") }.should raise_error(Puppet::ParseError) end it "should fail on tags containing ' ' characters" do lambda { @resource.tag("bad tag") }.should raise_error(Puppet::ParseError) end it "should allow alpha tags" do lambda { @resource.tag("good_tag") }.should_not raise_error(Puppet::ParseError) end end diff --git a/spec/unit/parser/resource/reference.rb b/spec/unit/parser/resource/reference.rb index e7385f796..147f772d1 100755 --- a/spec/unit/parser/resource/reference.rb +++ b/spec/unit/parser/resource/reference.rb @@ -1,75 +1,75 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../../spec_helper' describe Puppet::Parser::Resource::Reference do before do @type = Puppet::Parser::Resource::Reference end it "should require a type" do proc { @type.new(:title => "yay") }.should raise_error(Puppet::DevError) end it "should require a title" do proc { @type.new(:type => "file") }.should raise_error(Puppet::DevError) end it "should know when it refers to a builtin type" do ref = @type.new(:type => "file", :title => "/tmp/yay") ref.builtin?.should be_true ref.builtintype.should equal(Puppet::Type.type(:file)) end it "should return a downcased relationship-style resource reference for defined types" do ref = @type.new(:type => "file", :title => "/tmp/yay") ref.to_ref.should == ["file", "/tmp/yay"] end it "should return a capitalized relationship-style resource reference for defined types" do ref = @type.new(:type => "whatever", :title => "/tmp/yay") ref.to_ref.should == ["Whatever", "/tmp/yay"] end it "should return a resource reference string when asked" do ref = @type.new(:type => "file", :title => "/tmp/yay") ref.to_s.should == "File[/tmp/yay]" end it "should canonize resource references" do ref = @type.new(:type => "foo::bar", :title => "/tmp/yay") ref.to_s.should == "Foo::Bar[/tmp/yay]" end end describe Puppet::Parser::Resource::Reference, " when modeling defined types" do before do @type = Puppet::Parser::Resource::Reference @parser = Puppet::Parser::Parser.new :Code => "" @definition = @parser.newdefine "mydefine" @class = @parser.newclass "myclass" @nodedef = @parser.newnode("mynode")[0] @node = Puppet::Node.new("yaynode") - @compile = Puppet::Parser::Compile.new(@node, @parser) + @compiler = Puppet::Parser::Compiler.new(@node, @parser) end it "should be able to find defined types" do - ref = @type.new(:type => "mydefine", :title => "/tmp/yay", :scope => @compile.topscope) + ref = @type.new(:type => "mydefine", :title => "/tmp/yay", :scope => @compiler.topscope) ref.builtin?.should be_false ref.definedtype.should equal(@definition) end it "should be able to find classes" do - ref = @type.new(:type => "class", :title => "myclass", :scope => @compile.topscope) + ref = @type.new(:type => "class", :title => "myclass", :scope => @compiler.topscope) ref.builtin?.should be_false ref.definedtype.should equal(@class) end it "should be able to find nodes" do - ref = @type.new(:type => "node", :title => "mynode", :scope => @compile.topscope) + ref = @type.new(:type => "node", :title => "mynode", :scope => @compiler.topscope) ref.builtin?.should be_false ref.definedtype.object_id.should == @nodedef.object_id end end diff --git a/test/language/ast.rb b/test/language/ast.rb index 9b8c74cfb..8c0f31aba 100755 --- a/test/language/ast.rb +++ b/test/language/ast.rb @@ -1,138 +1,138 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../lib/puppettest' 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("yay") } assert_equal(:if, ret) # Now set it to false and check that faketest.evaluate = false assert_nothing_raised { ret = astif.evaluate("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(:add_override).with(:override) + scope.compiler.expects(:add_override).with(:override) ret = nil assert_nothing_raised do ret = ref.evaluate scope end assert_equal(:override, ret, "Did not return override") end # make sure our resourcedefaults ast object works correctly. def test_resourcedefaults 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 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_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 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") + colls = scope.compiler.instance_variable_get("@collections") assert_equal([ret], colls, "Did not store collector in config's collection list") end def test_virtual_collexp scope = mkscope # make a resource resource = mkresource(:type => "file", :title => "/tmp/testing", :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 end assert_instance_of(Proc, code) assert_nothing_raised do assert_equal(result, code.call(resource), "'#{string}' failed") end end end end diff --git a/test/language/ast/definition.rb b/test/language/ast/definition.rb index d4e4bd185..1585c5b1d 100755 --- a/test/language/ast/definition.rb +++ b/test/language/ast/definition.rb @@ -1,166 +1,166 @@ #!/usr/bin/env ruby # # Created by Luke A. Kanies on 2006-02-20. # Copyright (c) 2006. All rights reserved. require File.dirname(__FILE__) + '/../../lib/puppettest' require 'puppettest' require 'mocha' require 'puppettest/parsertesting' require 'puppettest/resourcetesting' class TestASTDefinition < Test::Unit::TestCase include PuppetTest include PuppetTest::ParserTesting include PuppetTest::ResourceTesting AST = Puppet::Parser::AST def test_initialize parser = mkparser # Create a new definition klass = parser.newdefine "yayness", :arguments => [["owner", stringobj("nobody")], %w{mode}], :code => AST::ASTArray.new( :children => [resourcedef("file", "/tmp/$name", "owner" => varref("owner"), "mode" => varref("mode"))] ) # Test validattr? a couple different ways [:owner, "owner", :schedule, "schedule"].each do |var| assert(klass.validattr?(var), "%s was not considered valid" % var.inspect) end [:random, "random"].each do |var| assert(! klass.validattr?(var), "%s was considered valid" % var.inspect) end end def test_evaluate parser = mkparser - config = mkcompile + config = mkcompiler config.send(:evaluate_main) scope = config.topscope klass = parser.newdefine "yayness", :arguments => [["owner", stringobj("nobody")], %w{mode}], :code => AST::ASTArray.new( :children => [resourcedef("file", "/tmp/$name", "owner" => varref("owner"), "mode" => varref("mode"))] ) resource = Puppet::Parser::Resource.new( :title => "first", :type => "yayness", :exported => false, :virtual => false, :scope => scope, :source => scope.source ) resource.send(:set_parameter, "name", "first") resource.send(:set_parameter, "mode", "755") resource.stubs(:title) assert_nothing_raised do klass.evaluate_code(resource) end firstobj = config.findresource("File[/tmp/first]") assert(firstobj, "Did not create /tmp/first obj") assert_equal("File", firstobj.type) assert_equal("/tmp/first", firstobj.title) assert_equal("nobody", firstobj[:owner]) assert_equal("755", firstobj[:mode]) # Make sure we can't evaluate it with the same args assert_raise(Puppet::ParseError) do klass.evaluate_code(resource) end # Now create another with different args resource2 = Puppet::Parser::Resource.new( :title => "second", :type => "yayness", :exported => false, :virtual => false, :scope => scope, :source => scope.source ) resource2.send(:set_parameter, "name", "second") resource2.send(:set_parameter, "mode", "755") resource2.send(:set_parameter, "owner", "daemon") assert_nothing_raised do klass.evaluate_code(resource2) end secondobj = config.findresource("File[/tmp/second]") assert(secondobj, "Did not create /tmp/second obj") assert_equal("File", secondobj.type) assert_equal("/tmp/second", secondobj.title) assert_equal("daemon", secondobj[:owner]) assert_equal("755", secondobj[:mode]) end # #539 - definitions should support both names and titles def test_names_and_titles parser = mkparser scope = mkscope :parser => parser [ {:name => "one", :title => "two"}, {:title => "mytitle"} ].each_with_index do |hash, i| # Create a definition that uses both name and title. Put this # inside the loop so the subscope expectations work. klass = parser.newdefine "yayness%s" % i resource = Puppet::Parser::Resource.new( :title => hash[:title], :type => "yayness%s" % i, :exported => false, :virtual => false, :scope => scope, :source => scope.source ) subscope = klass.subscope(scope, resource) klass.expects(:subscope).returns(subscope) if hash[:name] resource.stubs(:to_hash).returns({:name => hash[:name]}) end assert_nothing_raised("Could not evaluate definition with %s" % hash.inspect) do klass.evaluate_code(resource) end name = hash[:name] || hash[:title] title = hash[:title] assert_equal(name, subscope.lookupvar("name"), "Name did not get set correctly") assert_equal(title, subscope.lookupvar("title"), "title did not get set correctly") [:name, :title].each do |param| val = resource.send(param) assert(subscope.tags.include?(val), "Scope was not tagged with %s '%s'" % [param, val]) end end end # Testing the root cause of #615. We should be using the fqname for the type, instead # of just the short name. def test_fully_qualified_types parser = mkparser klass = parser.newclass("one::two") assert_equal("one::two", klass.classname, "Class did not get fully qualified class name") end end diff --git a/test/language/ast/node.rb b/test/language/ast/node.rb index df732480d..a90f05adf 100755 --- a/test/language/ast/node.rb +++ b/test/language/ast/node.rb @@ -1,68 +1,68 @@ #!/usr/bin/env ruby # # Created by Luke A. Kanies on 2008-02-09. # Copyright (c) 2008. All rights reserved. require File.dirname(__FILE__) + '/../../lib/puppettest' require 'puppettest' require 'mocha' require 'puppettest/parsertesting' require 'puppettest/resourcetesting' class TestASTNode < Test::Unit::TestCase include PuppetTest include PuppetTest::ParserTesting include PuppetTest::ResourceTesting AST = Puppet::Parser::AST def test_node scope = mkscope - parser = scope.compile.parser + parser = scope.compiler.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_code 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_code newscope.resource end assert(newscope.findresource("File[/tmp/base]"), "Could not find base resource") end end diff --git a/test/language/ast/resource.rb b/test/language/ast/resource.rb index aff1dba16..97541d92f 100755 --- a/test/language/ast/resource.rb +++ b/test/language/ast/resource.rb @@ -1,58 +1,58 @@ #!/usr/bin/env ruby # # Created by Luke A. Kanies on 2007-07-8. # Copyright (c) 2007. All rights reserved. require File.dirname(__FILE__) + '/../../lib/puppettest' require 'puppettest' require 'puppettest/parsertesting' class TestASTResource< Test::Unit::TestCase include PuppetTest include PuppetTest::ParserTesting AST = Puppet::Parser::AST def setup super @scope = mkscope - @parser = @scope.compile.parser + @parser = @scope.compiler.parser end def newdef(type, title, params = nil) params ||= AST::ASTArray.new(:children => []) AST::Resource.new(:type => type, :title => AST::String.new(:value => title), :params => params) end # Related to #806, make sure resources always look up the full path to the resource. def test_scoped_types @parser.newdefine "one" @parser.newdefine "one::two" @parser.newdefine "three" twoscope = @scope.newscope(:namespace => "one") twoscope.resource = @scope.resource assert(twoscope.finddefine("two"), "Could not find 'two' definition") title = "title" # First try a qualified type assert_equal("One::Two", newdef("two", title).evaluate(twoscope)[0].type, "Defined type was not made fully qualified") # Then try a type that does not need to be qualified assert_equal("One", newdef("one", title).evaluate(twoscope)[0].type, "Unqualified defined type was not handled correctly") # Then an unqualified type from within the one namespace assert_equal("Three", newdef("three", title).evaluate(twoscope)[0].type, "Defined type was not made fully qualified") # Then a builtin type assert_equal("File", newdef("file", title).evaluate(twoscope)[0].type, "Builtin type was not handled correctly") # Now try a type that does not exist, which should throw an error. assert_raise(Puppet::ParseError, "Did not fail on a missing type in a resource reference") do newdef("nosuchtype", title).evaluate(twoscope) end end end diff --git a/test/language/ast/resource_reference.rb b/test/language/ast/resource_reference.rb index 9de3391d9..1f554d90f 100755 --- a/test/language/ast/resource_reference.rb +++ b/test/language/ast/resource_reference.rb @@ -1,105 +1,105 @@ #!/usr/bin/env ruby # # Created by Luke A. Kanies on 2007-07-8. # Copyright (c) 2007. All rights reserved. require File.dirname(__FILE__) + '/../../lib/puppettest' require 'puppettest' require 'puppettest/parsertesting' class TestASTResourceReference < Test::Unit::TestCase include PuppetTest include PuppetTest::ParserTesting AST = Puppet::Parser::AST def newref(type, title) AST::ResourceReference.new(:type => type, :title => AST::String.new(:value => title)) end def setup super @scope = mkscope - @parser = @scope.compile.parser + @parser = @scope.compiler.parser end def test_evaluate @parser.newdefine "one::two" @parser.newdefine "one-two" [%w{File /tmp/yay}, %w{One::Two three}, %w{One-two three}].each do |type, title| ref = newref(type, title) evaled = nil assert_nothing_raised("Could not evaluate resource ref") do evaled = ref.evaluate(@scope) end assert_equal(type, evaled.type, "Type did not translate correctly") assert_equal(title, evaled.title, "Title did not translate correctly") end end def test_finding_classes_for_reference @parser.newclass "one" ref = newref("Class", "one") evaled = nil assert_nothing_raised("Could not evaluate resource ref") do evaled = ref.evaluate(@scope) end assert_equal("Class", evaled.type, "Did not set type to 'class'") assert_equal("one", evaled.title, "Did not look up class corectly") end # Related to #706, make sure resource references correctly translate to qualified types. def test_scoped_references @parser.newdefine "one" @parser.newdefine "one::two" @parser.newdefine "three" twoscope = @scope.newscope(:namespace => "one") assert(twoscope.finddefine("two"), "Could not find 'two' definition") title = "title" # First try a qualified type assert_equal("One::Two", newref("two", title).evaluate(twoscope).type, "Defined type was not made fully qualified") # Then try a type that does not need to be qualified assert_equal("One", newref("one", title).evaluate(twoscope).type, "Unqualified defined type was not handled correctly") # Then an unqualified type from within the one namespace assert_equal("Three", newref("three", title).evaluate(twoscope).type, "Defined type was not made fully qualified") # Then a builtin type assert_equal("File", newref("file", title).evaluate(twoscope).type, "Builtin type was not handled correctly") # Now try a type that does not exist, which should throw an error. assert_raise(Puppet::ParseError, "Did not fail on a missing type in a resource reference") do newref("nosuchtype", title).evaluate(twoscope) end # Now run the same tests, but with the classes @parser.newclass "four" @parser.newclass "one::five" # First try an unqualified type assert_equal("four", newref("class", "four").evaluate(twoscope).title, "Unqualified class was not found") # Then a qualified class assert_equal("one::five", newref("class", "five").evaluate(twoscope).title, "Class was not made fully qualified") # Then try a type that does not need to be qualified assert_equal("four", newref("class", "four").evaluate(twoscope).title, "Unqualified class was not handled correctly") # Now try a type that does not exist, which should throw an error. assert_raise(Puppet::ParseError, "Did not fail on a missing type in a resource reference") do newref("class", "nosuchclass").evaluate(twoscope) end end end diff --git a/test/language/functions.rb b/test/language/functions.rb index b8f7d4313..a5d52d7ac 100755 --- a/test/language/functions.rb +++ b/test/language/functions.rb @@ -1,513 +1,513 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../lib/puppettest' 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) end assert_equal("output avalue", val) end def test_taggedfunction scope = mkscope scope.resource.tag("yayness") # Make sure the ast stuff does what it's supposed to {"yayness" => true, "booness" => false}.each do |tag, retval| func = taggedobj(tag, :rvalue) val = nil assert_nothing_raised do val = func.evaluate(scope) end assert_equal(retval, val, "'tagged' returned %s for %s" % [val, tag]) end # Now make sure we correctly get tags. scope.resource.tag("resourcetag") assert(scope.function_tagged("resourcetag"), "tagged function did not catch resource tags") - scope.compile.catalog.tag("configtag") + scope.compiler.catalog.tag("configtag") assert(scope.function_tagged("configtag"), "tagged function did not catch catalog tags") 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) 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) end scope.setvar("one", "One") assert_raise(Puppet::ParseError) do ast.evaluate(scope) end scope.setvar("two", "Two") assert_nothing_raised do ast.evaluate(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) end scope.setvar("yayness", "this is yayness") assert_nothing_raised do ast.evaluate(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) end end def test_template_reparses template = tempfile() File.open(template, "w") do |f| f.puts "original text" end file = tempfile() Puppet[:code] = %{file { "#{file}": content => template("#{template}") }} Puppet[:environments] = "yay" interp = Puppet::Parser::Interpreter.new node = mknode node.stubs(:environment).returns("yay") Puppet[:environment] = "yay" catalog = nil assert_nothing_raised { catalog = interp.compile(node) } version = catalog.version fileobj = catalog.vertices.find { |r| r.title == file } assert(fileobj, "File was not in catalog") 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 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) 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) 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 + parser = scope.compiler.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.add_resource(scope, virtual) + scope.compiler.add_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, + assert_equal(1, scope.compiler.collections.length, "Did not set collection") assert_nothing_raised do - scope.compile.collections.each do |coll| coll.evaluate end + scope.compiler.collections.each do |coll| coll.evaluate end end - scope.compile.collections.clear + scope.compiler.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, + assert_equal(1, scope.compiler.collections.length, "Did not set collection") # And the collection has our resource in it - assert_equal([none.to_s], scope.compile.collections[0].resources, + assert_equal([none.to_s], scope.compiler.collections[0].resources, "Did not set resources in collection") end def test_defined scope = mkscope - parser = scope.compile.parser + parser = scope.compiler.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.add_resource(scope, mkresource(:type => "file", :title => "/tmp/rahness", + scope.compiler.add_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 + parser = scope.compiler.parser assert_raise(Puppet::ParseError, "did not throw error on missing class") do scope.function_include("nosuchclass") end parser.newclass("myclass") - scope.compile.expects(:evaluate_classes).with(%w{myclass otherclass}, scope, false).returns(%w{myclass otherclass}) + scope.compiler.expects(:evaluate_classes).with(%w{myclass otherclass}, scope, false).returns(%w{myclass otherclass}) assert_nothing_raised do scope.function_include(["myclass", "otherclass"]) 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 + parser = scope.compiler.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 diff --git a/test/language/parser.rb b/test/language/parser.rb index 3df4d0bb8..2a0e9c02d 100755 --- a/test/language/parser.rb +++ b/test/language/parser.rb @@ -1,1204 +1,1204 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../lib/puppettest' require 'mocha' require 'puppet' require 'puppet/parser/parser' require 'puppettest' require 'puppettest/support/utils' class TestParser < Test::Unit::TestCase include PuppetTest include PuppetTest::ParserTesting include PuppetTest::Support::Utils 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 = mkcompile(parser) + config = mkcompiler(parser) config.compile #ast.classes[""].evaluate 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::Resource, 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::Resource, res) check.call(res, "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 Puppet[:manifest] = top interp = Puppet::Parser::Interpreter.new code = nil assert_nothing_raised do 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 # #774 def test_fully_qualified_collection_statement parser = mkparser assert_nothing_raised("Could not parse fully qualified collection statement") { parser.parse %{Foo::Bar <||>} } 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| Puppet::Type.allclear 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 + parser = scope.compiler.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)) # 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)) # 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)) # 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)) # 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)) # 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 def test_manifests_with_multiple_environments parser = mkparser :environment => "something" # We use an exception to cut short the processing to simplify our stubbing #Puppet::Module.expects(:find_manifests).with("test", {:cwd => ".", :environment => "something"}).raises(Puppet::ParseError) Puppet::Module.expects(:find_manifests).with("test", {:cwd => ".", :environment => "something"}).returns([]) assert_raise(Puppet::ImportError) do parser.import("test") end end end diff --git a/test/language/resource.rb b/test/language/resource.rb index dbb1ab9f9..608e7c995 100755 --- a/test/language/resource.rb +++ b/test/language/resource.rb @@ -1,456 +1,456 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../lib/puppettest' require 'puppettest' require 'puppettest/resourcetesting' class TestResource < PuppetTest::TestCase include PuppetTest include PuppetTest::ParserTesting include PuppetTest::ResourceTesting Parser = Puppet::Parser AST = Parser::AST Resource = Puppet::Parser::Resource Reference = Puppet::Parser::Resource::Reference def setup super Puppet[:trace] = false end def teardown mocha_verify end def test_initialize args = {:type => "resource", :title => "testing", :scope => mkscope} # Check our arg requirements args.each do |name, value| try = args.dup try.delete(name) assert_raise(ArgumentError, "Did not fail when %s was missing" % name) do Parser::Resource.new(try) end end res = nil assert_nothing_raised do res = Parser::Resource.new(args) end ref = res.instance_variable_get("@ref") assert_equal("Resource", ref.type, "did not set resource type") assert_equal("testing", ref.title, "did not set resource title") end def test_merge res = mkresource other = mkresource # First try the case where the resource is not allowed to override res.source = "source1" other.source = "source2" other.source.expects(:child_of?).with("source1").returns(false) assert_raise(Puppet::ParseError, "Allowed unrelated resources to override") do res.merge(other) end # Next try it when the sources are equal. res.source = "source3" other.source = res.source other.source.expects(:child_of?).with("source3").never params = {:a => :b, :c => :d} other.expects(:params).returns(params) res.expects(:override_parameter).with(:b) res.expects(:override_parameter).with(:d) res.merge(other) # And then parentage is involved other = mkresource res.source = "source3" other.source = "source4" other.source.expects(:child_of?).with("source3").returns(true) params = {:a => :b, :c => :d} other.expects(:params).returns(params) res.expects(:override_parameter).with(:b) res.expects(:override_parameter).with(:d) res.merge(other) end # the [] method def test_array_accessors res = mkresource params = res.instance_variable_get("@params") assert_nil(res[:missing], "Found a missing parameter somehow") params[:something] = stub(:value => "yay") assert_equal("yay", res[:something], "Did not correctly call value on the parameter") res.expects(:title).returns(:mytitle) assert_equal(:mytitle, res[:title], "Did not call title when asked for it as a param") end # Make sure any defaults stored in the scope get added to our resource. def test_add_defaults res = mkresource params = res.instance_variable_get("@params") params[:a] = :b res.scope.expects(:lookupdefaults).with(res.type).returns(:a => :replaced, :c => :d) res.expects(:debug) res.send(:add_defaults) assert_equal(:d, params[:c], "Did not set default") assert_equal(:b, params[:a], "Replaced parameter with default") end def test_finish res = mkresource res.expects(:add_defaults) res.expects(:add_metaparams) res.expects(:validate) res.finish end # Make sure we paramcheck our params def test_validate res = mkresource params = res.instance_variable_get("@params") params[:one] = :two params[:three] = :four res.expects(:paramcheck).with(:one) res.expects(:paramcheck).with(:three) res.send(:validate) end def test_override_parameter res = mkresource params = res.instance_variable_get("@params") # There are three cases, with the second having two options: # No existing parameter. param = stub(:name => "myparam") res.send(:override_parameter, param) assert_equal(param, params["myparam"], "Override was not added to param list") # An existing parameter that we can override. source = stub(:child_of? => true) # Start out without addition params["param2"] = stub(:source => :whatever) param = stub(:name => "param2", :source => source, :add => false) res.send(:override_parameter, param) assert_equal(param, params["param2"], "Override was not added to param list") # Try with addition. params["param2"] = stub(:value => :a, :source => :whatever) param = stub(:name => "param2", :source => source, :add => true, :value => :b) param.expects(:value=).with([:a, :b]) res.send(:override_parameter, param) assert_equal(param, params["param2"], "Override was not added to param list") # And finally, make sure we throw an exception when the sources aren't related source = stub(:child_of? => false) params["param2"] = stub(:source => :whatever, :file => :f, :line => :l) old = params["param2"] param = stub(:name => "param2", :source => source, :file => :f, :line => :l) assert_raise(Puppet::ParseError, "Did not fail when params conflicted") do res.send(:override_parameter, param) end assert_equal(old, params["param2"], "Param was replaced irrespective of conflict") end def test_set_parameter res = mkresource params = res.instance_variable_get("@params") # First test the simple case: It's already a parameter param = mock('param') param.expects(:is_a?).with(Resource::Param).returns(true) param.expects(:name).returns("pname") res.send(:set_parameter, param) assert_equal(param, params["pname"], "Parameter was not added to hash") # Now the case where there's no value but it's not a param param = mock('param') param.expects(:is_a?).with(Resource::Param).returns(false) assert_raise(ArgumentError, "Did not fail when a non-param was passed") do res.send(:set_parameter, param) end # and the case where a value is passed in param = stub :name => "pname", :value => "whatever" Resource::Param.expects(:new).with(:name => "pname", :value => "myvalue", :source => res.source).returns(param) res.send(:set_parameter, "pname", "myvalue") assert_equal(param, params["pname"], "Did not put param in hash") end def test_paramcheck # There are three cases here: # It's a valid parameter res = mkresource ref = mock('ref') res.instance_variable_set("@ref", ref) klass = mock("class") ref.expects(:typeclass).returns(klass).times(4) klass.expects(:validattr?).with("good").returns(true) assert(res.send(:paramcheck, :good), "Did not allow valid param") # It's name or title klass.expects(:validattr?).with("name").returns(false) assert(res.send(:paramcheck, :name), "Did not allow name") klass.expects(:validattr?).with("title").returns(false) assert(res.send(:paramcheck, :title), "Did not allow title") # It's not actually allowed klass.expects(:validattr?).with("other").returns(false) res.expects(:fail) ref.expects(:type) res.send(:paramcheck, :other) end def test_to_transobject # First try translating a builtin resource. Make sure we use some references # and arrays, to make sure they translate correctly. source = mock("source") scope = mkscope scope.stubs(:tags).returns([]) refs = [] 4.times { |i| refs << Puppet::Parser::Resource::Reference.new(:title => "file%s" % i, :type => "file") } res = Parser::Resource.new :type => "file", :title => "/tmp", :source => source, :scope => scope, :params => paramify(source, :owner => "nobody", :group => %w{you me}, :require => refs[0], :ignore => %w{svn}, :subscribe => [refs[1], refs[2]], :notify => [refs[3]]) obj = nil assert_nothing_raised do obj = res.to_trans end assert_instance_of(Puppet::TransObject, obj) assert_equal(obj.type, res.type.downcase) assert_equal(obj.name, res.title) # TransObjects use strings, resources use symbols assert_equal("nobody", obj["owner"], "Single-value string was not passed correctly") assert_equal(%w{you me}, obj["group"], "Array of strings was not passed correctly") assert_equal("svn", obj["ignore"], "Array with single string was not turned into single value") assert_equal(["file", refs[0].title], obj["require"], "Resource reference was not passed correctly") assert_equal([["file", refs[1].title], ["file", refs[2].title]], obj["subscribe"], "Array of resource references was not passed correctly") assert_equal(["file", refs[3].title], obj["notify"], "Array with single resource reference was not turned into single value") end # FIXME This isn't a great test, but I need to move on. def test_to_transbucket bucket = mock("transbucket") source = mock("source") scope = mkscope res = Parser::Resource.new :type => "mydefine", :title => "yay", :source => source, :scope => scope result = res.to_trans assert_equal("yay", result.name, "did not set bucket name correctly") assert_equal("Mydefine", result.type, "did not set bucket type correctly") end def test_evaluate # First try the most common case, we're not a builtin type. res = mkresource ref = res.instance_variable_get("@ref") type = mock("type") ref.expects(:definedtype).returns(type) res.expects(:finish) res.scope = mock("scope") type.expects(:evaluate_code).with(res) res.evaluate end def test_proxymethods res = Parser::Resource.new :type => "evaltest", :title => "yay", :source => mock("source"), :scope => mkscope assert_equal("Evaltest", res.type) assert_equal("yay", res.title) assert_equal(false, res.builtin?) end def test_reference_conversion # First try it as a normal string ref = Parser::Resource::Reference.new(:type => "file", :title => "/tmp/ref1") # Now create an obj that uses it res = mkresource :type => "file", :title => "/tmp/resource", :params => {:require => ref} res.scope = mkscope trans = nil assert_nothing_raised do trans = res.to_trans end assert_instance_of(Array, trans["require"]) assert_equal(["file", "/tmp/ref1"], trans["require"]) # Now try it when using an array of references. two = Parser::Resource::Reference.new(:type => "file", :title => "/tmp/ref2") res = mkresource :type => "file", :title => "/tmp/resource2", :params => {:require => [ref, two]} res.scope = mkscope trans = nil assert_nothing_raised do trans = res.to_trans end assert_instance_of(Array, trans["require"][0]) trans["require"].each do |val| assert_instance_of(Array, val) assert_equal("file", val[0]) assert(val[1] =~ /\/tmp\/ref[0-9]/, "Was %s instead of the file name" % val[1]) end end # This is a bit of a weird one -- the user should not actually know # that components exist, so we want references to act like they're not # builtin def test_components_are_not_builtin ref = Parser::Resource::Reference.new(:type => "component", :title => "yay") assert_nil(ref.builtintype, "Definition was considered builtin") end # The second part of #539 - make sure resources pass the arguments # correctly. def test_title_with_definitions parser = mkparser define = parser.newdefine "yayness", :code => resourcedef("file", "/tmp", "owner" => varref("name"), "mode" => varref("title")) klass = parser.findclass("", "") should = {:name => :owner, :title => :mode} [ {:name => "one", :title => "two"}, {:title => "three"}, ].each do |hash| - config = mkcompile parser + config = mkcompiler parser args = {:type => "yayness", :title => hash[:title], :source => klass, :scope => config.topscope} if hash[:name] args[:params] = {:name => hash[:name]} else args[:params] = {} # override the defaults end res = nil assert_nothing_raised("Could not create res with %s" % hash.inspect) do res = mkresource(args) end assert_nothing_raised("Could not eval res with %s" % hash.inspect) do res.evaluate end made = config.topscope.findresource("File[/tmp]") assert(made, "Did not create resource with %s" % hash.inspect) should.each do |orig, param| assert_equal(hash[orig] || hash[:title], made[param], "%s was not set correctly with %s" % [param, hash.inspect]) end end end # part of #629 -- the undef keyword. Make sure 'undef' params get skipped. def test_undef_and_to_hash res = mkresource :type => "file", :title => "/tmp/testing", :source => mock("source"), :scope => mkscope, :params => {:owner => :undef, :mode => "755"} hash = nil assert_nothing_raised("Could not convert resource with undef to hash") do hash = res.to_hash end assert_nil(hash[:owner], "got a value for an undef parameter") end # #643 - Make sure virtual defines result in virtual resources def test_virtual_defines parser = mkparser define = parser.newdefine("yayness", :code => resourcedef("file", varref("name"), "mode" => "644")) - config = mkcompile(parser) + config = mkcompiler(parser) res = mkresource :type => "yayness", :title => "foo", :params => {}, :scope => config.topscope res.virtual = true result = nil assert_nothing_raised("Could not evaluate defined resource") do result = res.evaluate end scope = res.scope newres = scope.findresource("File[foo]") assert(newres, "Could not find resource") assert(newres.virtual?, "Virtual defined resource generated non-virtual resources") # Now try it with exported resources res = mkresource :type => "yayness", :title => "bar", :params => {}, :scope => config.topscope res.exported = true result = nil assert_nothing_raised("Could not evaluate exported resource") do result = res.evaluate end scope = res.scope newres = scope.findresource("File[bar]") assert(newres, "Could not find resource") assert(newres.exported?, "Exported defined resource generated non-exported resources") assert(newres.virtual?, "Exported defined resource generated non-virtual resources") end # Make sure tags behave appropriately. def test_tags scope_resource = stub 'scope_resource', :tags => %w{srone srtwo} scope = stub 'scope', :resource => scope_resource resource = Puppet::Parser::Resource.new(:type => "file", :title => "yay", :scope => scope, :source => mock('source')) # Make sure we get the type and title %w{yay file}.each do |tag| assert(resource.tags.include?(tag), "Did not tag resource with %s" % tag) end # make sure we can only set legal tags ["an invalid tag", "-anotherinvalid", "bad*tag"].each do |tag| assert_raise(Puppet::ParseError, "Tag #{tag} was considered valid") do resource.tag tag end end # make sure good tags make it through. tags = %w{good-tag yaytag GoodTag another_tag a ab A} tags.each do |tag| assert_nothing_raised("Tag #{tag} was considered invalid") do resource.tag tag end end # make sure we get each of them. ptags = resource.tags tags.each do |tag| assert(ptags.include?(tag.downcase), "missing #{tag}") end end end diff --git a/test/language/scope.rb b/test/language/scope.rb index b35687e66..9c0e583e4 100755 --- a/test/language/scope.rb +++ b/test/language/scope.rb @@ -1,510 +1,510 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../lib/puppettest' 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 = mkcompile + config = mkcompiler 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) + scopes[name] = scope.compiler.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 = mkcompile + config = mkcompiler 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 = mkcompile + config = mkcompiler 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 = mkcompile + config = mkcompiler 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] = scope.compiler.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 end - scope.compile.send(:evaluate_generators) + scope.compiler.send(:evaluate_generators) [myclass, otherclass].each do |klass| - assert(scope.compile.class_scope(klass), + assert(scope.compiler.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 = mkcompile + config = mkcompiler 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 => [], :builtin? => true, :type => "eh", :title => "bee" 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 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 = [] Puppet[:code] = " 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 } 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 { config = interp.compile(node) } 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 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 diff --git a/test/lib/puppettest/parsertesting.rb b/test/lib/puppettest/parsertesting.rb index 36bb68a77..1a08ecbae 100644 --- a/test/lib/puppettest/parsertesting.rb +++ b/test/lib/puppettest/parsertesting.rb @@ -1,406 +1,406 @@ require 'puppettest' require 'puppet/rails' module PuppetTest::ParserTesting include PuppetTest AST = Puppet::Parser::AST - Compile = Puppet::Parser::Compile + Compiler = Puppet::Parser::Compiler # 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 mkcompile(parser = nil) + def mkcompiler(parser = nil) parser ||= mkparser node = mknode - return Compile.new(node, parser) + return Compiler.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 Puppet::Parser::Interpreter.new end def mkparser(args = {}) Puppet::Parser::Parser.new(args) end def mkscope(hash = {}) hash[:parser] ||= mkparser - compile ||= mkcompile(hash[:parser]) - compile.topscope.source = (hash[:parser].findclass("", "") || hash[:parser].newclass("")) + compiler ||= mkcompiler(hash[:parser]) + compiler.topscope.source = (hash[:parser].findclass("", "") || hash[:parser].newclass("")) - unless compile.topscope.source + unless compiler.topscope.source raise "Could not find source for scope" end # Make the 'main' stuff - compile.send(:evaluate_main) - compile.topscope + compiler.send(:evaluate_main) + compiler.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::Resource.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::ResourceReference.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::ResourceInstance.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 oldmanifest = Puppet[:manifest] Puppet[:manifest] = manifest assert_nothing_raised { interp = Puppet::Parser::Interpreter.new } trans = nil assert_nothing_raised { trans = interp.compile(mknode) } config = nil assert_nothing_raised { config = trans.extract.to_catalog } config.apply files.each do |file| assert(FileTest.exists?(file), "Did not create %s" % file) end ensure Puppet[:manifest] = oldmanifest 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