diff --git a/lib/puppet/parser/ast/definition.rb b/lib/puppet/parser/ast/definition.rb index e3f6414c3..992bb1f5e 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.store_resource(scope, resource) + scope.compile.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/resource.rb b/lib/puppet/parser/ast/resource.rb index 606beb537..2dadf9ed6 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. # At some point, we need to switch all of this to return # objects instead of storing them like this. - scope.compile.store_resource(scope, obj) + scope.compile.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 d15f68608..db0986a8e 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.store_override(obj) + scope.compile.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 b8165a84f..efd64a320 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) end return result end # Collect just virtual objects, from our local compile. def collect_virtual(exported = false) if exported method = :exported? else method = :virtual? end scope.compile.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.store_resource(scope, resource) + scope.compile.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/compile.rb index bceead271..f68796843 100644 --- a/lib/puppet/parser/compile.rb +++ b/lib/puppet/parser/compile.rb @@ -1,469 +1,469 @@ # 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 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. # 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 end end initvars() init_main() end # Create a new scope, with either a specified parent scope or # using the top scope. Adds an edge between the scope and # its parent to the graph. def newscope(parent, options = {}) parent ||= topscope options[:compile] = self options[:parser] ||= self.parser scope = Puppet::Parser::Scope.new(options) @scope_graph.add_edge!(parent, scope) scope end # Find the parent of a given scope. Assumes scopes only ever have # one in edge, which will always be true. def parent(scope) if ary = @scope_graph.adjacent(scope, :direction => :in) and ary.length > 0 ary[0] else nil end end # Return any overrides for the given resource. def resource_overrides(resource) @resource_overrides[resource.ref] end # Return a list of all resources. def resources @catalog.vertices end - # Store a resource override. - def store_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 store_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 - # 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 = Puppet::Parser::Resource.new(:type => "node", :title => astnode.classname, :scope => topscope, :source => topscope.source) - store_resource(topscope, resource) + add_resource(topscope, resource) @catalog.tag(astnode.classname) 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) @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/spec/unit/parser/collector.rb b/spec/unit/parser/collector.rb index 9b5eab1f4..aedbe1b9a 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) @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 @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) @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]) @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]) @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]) @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]) @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]) @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]) @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]) @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) @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([]) 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]) @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]) @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([]) @scope.stubs(:findresource).returns(nil) - @compile.stubs(:store_resource) + @compile.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([]) @scope.stubs(:findresource).returns(nil) - @compile.expects(:store_resource).with(@scope, resource) + @compile.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([]) @scope.stubs(:findresource).returns(nil) - @compile.stubs(:store_resource) + @compile.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([]) @scope.stubs(:findresource).returns(inmemory) - @compile.stubs(:store_resource) + @compile.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([]) @scope.stubs(:findresource).returns(inmemory) - @compile.stubs(:store_resource) + @compile.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) @resource_type = "Mytype" @equery = nil @vquery = proc { |r| true } @collector = Puppet::Parser::Collector.new(@scope, @resource_type, @equery, @vquery, :exported) @compile.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/compile.rb index ff205f7a5..65e7befca 100755 --- a/spec/unit/parser/compile.rb +++ b/spec/unit/parser/compile.rb @@ -1,546 +1,546 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../spec_helper' module CompileTesting 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) end end describe Puppet::Parser::Compile do include CompileTesting it "should be able to store references to class scopes" do lambda { @compile.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" 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" 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" @compile.classlist.sort.should == %w{one two}.sort end end describe Puppet::Parser::Compile, " when initializing" do include CompileTesting it "should set its node attribute" do @compile.node.should equal(@node) end it "should set its parser attribute" do @compile.parser.should equal(@parser) end it "should detect when ast nodes are absent" do @compile.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 end end describe Puppet::Parser::Compile, "when managing scopes" do include CompileTesting it "should create a top scope" do @compile.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) end it "should correctly set the level of newly created scopes" do @compile.newscope(@compile.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) @compile.parent(newscope).should equal(scope) end end describe Puppet::Parser::Compile, " when compiling" do include CompileTesting def compile_methods [:set_node_parameters, :evaluate_main, :evaluate_ast_node, :evaluate_node_classes, :evaluate_generators, :fail_on_unevaluated, :finish, :store, :extract] end # Stub all of the main compile methods except the ones we're specifically interested in. def compile_stub(*except) (compile_methods - except).each { |m| @compile.stubs(m) } end it "should set node parameters as variables in the top scope" do params = {"a" => "b", "c" => "d"} @node.stubs(:parameters).returns(params) compile_stub(:set_node_parameters) @compile.compile @compile.topscope.lookupvar("a").should == "b" @compile.topscope.lookupvar("c").should == "d" end it "should evaluate any existing classes named in the node" do classes = %w{one two three four} main = stub 'main' one = stub 'one', :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 } 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 end it "should disable ast_nodes if the parser has no nodes" do @parser.expects(:nodes).returns({}) @compile.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) @parser.stubs(:findclass).with("", "").returns(main_class) @compile.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) 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]) compile_stub(:evaluate_generators) @compile.compile end it "should ignore builtin resources" do resource = stub 'builtin', :ref => "File[testing]", :builtin? => true - @compile.store_resource(@scope, resource) + @compile.add_resource(@scope, resource) resource.expects(:evaluate).never @compile.compile end it "should evaluate unevaluated resources" do resource = stub 'notevaluated', :ref => "File[testing]", :builtin? => false, :evaluated? => false - @compile.store_resource(@scope, resource) + @compile.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 end it "should not evaluate already-evaluated resources" do resource = stub 'already_evaluated', :ref => "File[testing]", :builtin? => false, :evaluated? => true - @compile.store_resource(@scope, resource) + @compile.add_resource(@scope, resource) resource.expects(:evaluate).never @compile.compile end it "should evaluate unevaluated resources created by evaluating other resources" do resource = stub 'notevaluated', :ref => "File[testing]", :builtin? => false, :evaluated? => false - @compile.store_resource(@scope, resource) + @compile.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.store_resource(@scope, resource2) } + resource.expects(:evaluate).with { |*whatever| resource.stubs(:evaluated?).returns(true); @compile.add_resource(@scope, resource2) } resource2.expects(:evaluate).with { |*whatever| resource2.stubs(:evaluated?).returns(true) } @compile.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.store_resource(@scope, resource) + @compile.add_resource(@scope, resource) # And one that does not dnf = stub "dnf", :ref => "File[dnf]" - @compile.store_resource(@scope, dnf) + @compile.add_resource(@scope, dnf) @compile.send(:finish) end it "should add resources that do not conflict with existing resources" do resource = stub "noconflict", :ref => "File[yay]" - @compile.store_resource(@scope, resource) + @compile.add_resource(@scope, resource) @compile.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.store_resource(@scope, resource1) - lambda { @compile.store_resource(@scope, resource2) }.should raise_error(ArgumentError) + @compile.add_resource(@scope, resource1) + lambda { @compile.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.store_resource(@scope, resource) + @compile.add_resource(@scope, resource) @compile.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.store_resource(@scope, resource) + @compile.add_resource(@scope, resource) @compile.findresource("Yay", "foo").should equal(resource) end end describe Puppet::Parser::Compile, " when evaluating collections" do include CompileTesting it "should evaluate each collection" do 2.times { |i| coll = mock 'coll%s' % i @compile.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) end } @compile.class.publicize_methods(:evaluate_collections) { @compile.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) lambda { @compile.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) lambda { @compile.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) lambda { @compile.compile }.should raise_error(Puppet::ParseError) end end describe Puppet::Parser::Compile, "when told to evaluate missing classes" do include CompileTesting 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) end it "should tag the catalog with the name of each not-found class" do @compile.catalog.expects(:tag).with("notfound") @scope.expects(:findclass).with("notfound").returns(nil) @compile.evaluate_classes(%w{notfound}, @scope) end end describe Puppet::Parser::Compile, " when evaluating found classes" do include CompileTesting 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) @class.expects(:evaluate).with(@scope) @compile.evaluate_classes(%w{myclass}, @scope) end it "should not evaluate the resources created for found classes unless asked" do @compile.catalog.stubs(:tag) @resource.expects(:evaluate).never @class.expects(:evaluate).returns(@resource) @compile.evaluate_classes(%w{myclass}, @scope) end it "should immediately evaluate the resources created for found classes when asked" do @compile.catalog.stubs(:tag) @resource.expects(:evaluate) @class.expects(:evaluate).returns(@resource) @compile.evaluate_classes(%w{myclass}, @scope, false) end it "should skip classes that have already been evaluated" do @compile.catalog.stubs(:tag) @compile.expects(:class_scope).with(@class).returns("something") - @compile.expects(:store_resource).never + @compile.expects(:add_resource).never @resource.expects(:evaluate).never Puppet::Parser::Resource.expects(:new).never @compile.evaluate_classes(%w{myclass}, @scope, false) end it "should return the list of found classes" do @compile.catalog.stubs(:tag) - @compile.stubs(:store_resource) + @compile.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} end end describe Puppet::Parser::Compile, " when evaluating AST nodes with no AST nodes present" do include CompileTesting it "should do nothing" do @compile.expects(:ast_nodes?).returns(false) @compile.parser.expects(:nodes).never Puppet::Parser::Resource.expects(:new).never @compile.send(:evaluate_ast_node) end end describe Puppet::Parser::Compile, " when evaluating AST nodes with AST nodes present" do include CompileTesting before do @nodes = mock 'node_hash' @compile.stubs(:ast_nodes?).returns(true) @compile.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) end it "should create a resource for the first node class matching the node name" do node_class = stub 'node', :classname => "c" @nodes.stubs(:[]).with("c").returns(node_class) node_resource = stub 'node resource', :ref => "Node[c]", :evaluate => nil Puppet::Parser::Resource.expects(:new).with { |args| args[:title] == "c" and args[:type] == "node" }.returns(node_resource) @compile.send(:evaluate_ast_node) end it "should match the default node if no matching node can be found" do node_class = stub 'node', :classname => "default" @nodes.stubs(:[]).with("default").returns(node_class) node_resource = stub 'node resource', :ref => "Node[default]", :evaluate => nil Puppet::Parser::Resource.expects(:new).with { |args| args[:title] == "default" and args[:type] == "node" }.returns(node_resource) @compile.send(:evaluate_ast_node) end it "should tag the catalog with the found node name" do node_class = stub 'node', :classname => "c" @nodes.stubs(:[]).with("c").returns(node_class) node_resource = stub 'node resource', :ref => "Node[c]", :evaluate => nil Puppet::Parser::Resource.stubs(:new).returns(node_resource) @compile.catalog.expects(:tag).with("c") @compile.send(:evaluate_ast_node) 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]" Puppet::Parser::Resource.stubs(:new).returns(node_resource) node_resource.expects(:evaluate) @compile.send(:evaluate_ast_node) end it "should set the node's scope as the top scope" do node_class = stub 'node', :classname => "c" @nodes.stubs(:[]).with("c").returns(node_class) node_resource = stub 'node resource', :ref => "Node[c]" Puppet::Parser::Resource.stubs(:new).returns(node_resource) # The #evaluate method normally does this. @compile.class_set(node_class.classname, :my_node_scope) node_resource.stubs(:evaluate) @compile.send(:evaluate_ast_node) @compile.topscope.should == :my_node_scope end end describe Puppet::Parser::Compile, "when storing compiled resources" do include CompileTesting it "should store the resources" do Puppet.features.expects(:rails?).returns(true) Puppet::Rails.expects(:connect) @compile.catalog.expects(:vertices).returns(:resources) @compile.expects(:store_to_active_record).with(@node, :resources) @compile.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) end end describe Puppet::Parser::Compile, "when managing resource overrides" do include CompileTesting 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.store_override(@override) }.should_not raise_error + lambda { @compile.add_override(@override) }.should_not raise_error end it "should apply overrides to the appropriate resources" do - @compile.store_resource(@scope, @resource) + @compile.add_resource(@scope, @resource) @resource.expects(:merge).with(@override) - @compile.store_override(@override) + @compile.add_override(@override) @compile.compile end it "should accept overrides before the related resource has been created" do @resource.expects(:merge).with(@override) # First store the override - @compile.store_override(@override) + @compile.add_override(@override) # Then the resource - @compile.store_resource(@scope, @resource) + @compile.add_resource(@scope, @resource) # And compile, so they get resolved @compile.compile end it "should fail if the compile is finished and resource overrides have not been applied" do - @compile.store_override(@override) + @compile.add_override(@override) lambda { @compile.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 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) 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) end end diff --git a/test/language/ast.rb b/test/language/ast.rb index 9b1c1c1dc..9b8c74cfb 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(:store_override).with(:override) + scope.compile.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") 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/functions.rb b/test/language/functions.rb index edb39e9a7..b8f7d4313 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") 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 # Make a definition parser.newdefine("mytype") [%w{file /tmp/virtual}, %w{mytype yay}].each do |type, title| # Make a virtual resource virtual = mkresource(:type => type, :title => title, :virtual => true, :params => {}, :scope => scope) - scope.compile.store_resource(scope, virtual) + scope.compile.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, "Did not set collection") assert_nothing_raised do scope.compile.collections.each do |coll| coll.evaluate end end scope.compile.collections.clear # Now make sure the virtual resource is no longer virtual assert(! virtual.virtual?, "Did not make virtual resource real") end # Make sure we puke on any resource that doesn't exist none = Puppet::Parser::Resource::Reference.new( :type => "file", :title => "/tmp/nosuchfile", :scope => scope ) # The function works assert_nothing_raised do scope.function_realize(none.to_s) end # Make sure it created a collection assert_equal(1, scope.compile.collections.length, "Did not set collection") # And the collection has our resource in it assert_equal([none.to_s], scope.compile.collections[0].resources, "Did not set resources in collection") end def test_defined scope = mkscope parser = scope.compile.parser parser.newclass("yayness") parser.newdefine("rahness") assert_nothing_raised do assert(scope.function_defined("yayness"), "yayness class was not considered defined") assert(scope.function_defined("rahness"), "rahness definition was not considered defined") assert(scope.function_defined("service"), "service type was not considered defined") assert(! scope.function_defined("fakness"), "fakeness was considered defined") end # Now make sure any match in a list will work assert(scope.function_defined(["booness", "yayness", "fakeness"]), "A single answer was not sufficient to return true") # and make sure multiple falses are still false assert(! scope.function_defined(%w{no otherno stillno}), "Multiple falses were somehow true") # Now make sure we can test resources - scope.compile.store_resource(scope, mkresource(:type => "file", :title => "/tmp/rahness", + scope.compile.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 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}) 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 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