diff --git a/lib/puppet/parser/ast/component.rb b/lib/puppet/parser/ast/component.rb index 65f310212..17cfa9d61 100644 --- a/lib/puppet/parser/ast/component.rb +++ b/lib/puppet/parser/ast/component.rb @@ -1,226 +1,226 @@ require 'puppet/parser/ast/branch' class Puppet::Parser::AST # Evaluate the stored parse tree for a given component. This will # receive the arguments passed to the component and also the type and # name of the component. class Component < AST::Branch include Puppet::Util include Puppet::Util::Warnings include Puppet::Util::MethodHelper class << self attr_accessor :name end # The class name @name = :definition attr_accessor :classname, :arguments, :code, :scope, :keyword attr_accessor :exported, :namespace, :parser, :virtual # These are retrieved when looking up the superclass attr_accessor :name attr_reader :parentclass def child_of?(klass) false end - def evaluate(hash) + def evaluate_resource(hash) origscope = hash[:scope] title = hash[:title] args = symbolize_options(hash[:arguments] || {}) name = args[:name] || title exported = hash[:exported] virtual = hash[:virtual] pscope = origscope scope = subscope(pscope, title) if virtual or origscope.virtual? scope.virtual = true end if exported or origscope.exported? scope.exported = true end # Additionally, add a tag for whatever kind of class # we are if @classname != "" and ! @classname.nil? @classname.split(/::/).each { |tag| scope.tag(tag) } end [name, title].each do |str| unless str.nil? or str =~ /[^\w]/ or str == "" scope.tag(str) end end # define all of the arguments in our local scope if self.arguments # Verify that all required arguments are either present or # have been provided with defaults. self.arguments.each { |arg, default| arg = symbolize(arg) unless args.include?(arg) if defined? default and ! default.nil? default = default.safeevaluate :scope => 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,title,@classname] end end } end # Set each of the provided arguments as variables in the # component'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 } unless args.include? :title scope.setvar("title",title) end unless args.include? :name scope.setvar("name",name) end if self.code return self.code.safeevaluate(:scope => 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 elements" % arg else raise Puppet::ParseError, "%s is a metaparameter; please choose another name" % name 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 if @parentclass # Cache our result, since it should never change. unless defined?(@parentobj) unless tmp = find_parentclass parsefail "Could not find %s %s" % [self.class.name, @parentclass] end if tmp == self parsefail "Parent classes must have dissimilar names" end @parentobj = tmp end @parentobj else nil end end # Create a new subscope in which to evaluate our code. def subscope(scope, name = nil) args = { :type => self.classname, :keyword => self.keyword, :namespace => self.namespace } args[:name] = name if name 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 end end # $Id$ diff --git a/lib/puppet/parser/configuration.rb b/lib/puppet/parser/configuration.rb index 90812899a..ddfc3606f 100644 --- a/lib/puppet/parser/configuration.rb +++ b/lib/puppet/parser/configuration.rb @@ -1,525 +1,535 @@ # Created by Luke A. Kanies on 2007-08-13. # Copyright (c) 2007. All rights reserved. require 'puppet/external/gratr/digraph' require 'puppet/external/gratr/import' require 'puppet/external/gratr/dot' require 'puppet/util/errors' # Maintain a graph of scopes, along with a bunch of data # about the individual configuration we're compiling. class Puppet::Parser::Configuration include Puppet::Util include Puppet::Util::Errors - attr_reader :topscope, :parser, :node, :facts + attr_reader :topscope, :parser, :node, :facts, :collections attr_accessor :extraction_format attr_writer :ast_nodes # Add a collection to the global list. def add_collection(coll) @collections << coll end # Do we use nodes found in the code, vs. the external node sources? def ast_nodes? defined?(@ast_nodes) and @ast_nodes end # Store the fact that we've evaluated a class, and store a reference to # the scope in which it was evaluated, so that we can look it up later. def class_set(name, scope) + if existing = @class_scopes[name] + if existing.nodescope? or scope.nodescope? + raise Puppet::ParseError, "Cannot have classes, nodes, or definitions with the same name" + else + raise Puppet::DevError, "Somehow evaluated the same class twice" + end + end @class_scopes[name] = scope tag(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 @class_scopes.keys.reject { |k| k == "" } end # Compile our configuration. This mostly revolves around finding and evaluating classes. # This is the main entry into our configuration. def compile # Set the client's parameters into the top scope. set_node_parameters() evaluate_main() evaluate_ast_node() evaluate_classes() evaluate_generators() fail_on_unevaluated() finish() return extract() end # FIXME There are no tests for this. def delete_collection(coll) @collections.delete(coll) if @collections.include?(coll) end # FIXME There are no tests for this. def delete_resource(resource) @resource_table.delete(resource.ref) if @resource_table.include?(resource.ref) @resource_graph.remove_vertex!(resource) if @resource_graph.vertex?(resource) end # Evaluate each class in turn. If there are any classes we can't find, # just tag the configuration and move on. def evaluate_classes(classes = nil) classes ||= node.classes + found = [] classes.each do |name| if klass = @parser.findclass("", name) # This will result in class_set getting called, which # will in turn result in tags. Yay. klass.safeevaluate(:scope => topscope) + found << name else Puppet.info "Could not find class %s for %s" % [name, node.name] tag(name) end end + found end # Make sure we support the requested extraction format. def extraction_format=(value) unless respond_to?("extract_to_%s" % value) raise ArgumentError, "Invalid extraction format %s" % value end @extraction_format = value end # Return a resource by either its ref or its type and title. def findresource(string, name = nil) if name string = "%s[%s]" % [string.capitalize, name] end @resource_table[string] end # Set up our configuration. 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, "Configuration objects do not accept %s" % param end end @extraction_format ||= :transportable initvars() 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[:configuration] = self options[:parser] ||= self.parser scope = Puppet::Parser::Scope.new(options) @scope_graph.add_edge!(parent, scope) scope end # Find the parent of a given scope. Assumes scopes only ever have # one in edge, which will always be true. def parent(scope) if ary = @scope_graph.adjacent(scope, :direction => :in) and ary.length > 0 ary[0] else nil end end # Return any overrides for the given resource. def resource_overrides(resource) @resource_overrides[resource.ref] end # Return a list of all resources. def resources @resource_table.values end # Store a resource override. def store_override(override) override.override = true # If possible, merge the override in immediately. if resource = @resource_table[override.ref] resource.merge(override) else # Otherwise, store the override for later; these # get evaluated in Resource#finish. @resource_overrides[override.ref] << override end end # Store a resource in our resource table. def store_resource(scope, resource) # This might throw an exception verify_uniqueness(resource) # Store it in the global table. @resource_table[resource.ref] = resource # And in the resource graph. At some point, this might supercede # the global resource table, but the table is a lot faster # so it makes sense to maintain for now. @resource_graph.add_edge!(scope, resource) end private # If ast nodes are enabled, then see if we can find and evaluate one. def evaluate_ast_node return unless ast_nodes? # Now see if we can find the node. astnode = nil #nodes = @parser.nodes @node.names.each do |name| break if astnode = @parser.nodes[name.to_s.downcase] end unless astnode astnode = @parser.nodes["default"] end unless astnode raise Puppet::ParseError, "Could not find default node or by name with '%s'" % node.names.join(", ") end astnode.safeevaluate :scope => topscope end # Evaluate our collections and return true if anything returned an object. # The 'true' is used to continue a loop, so it's important. def evaluate_collections return false if @collections.empty? found_something = false exceptwrap do @collections.each do |collection| if collection.evaluate found_something = true end end end return found_something end # Make sure all of our resources have been evaluated into native resources. # We return true if any resources have, so that we know to continue the # evaluate_generators loop. def evaluate_definitions exceptwrap do if ary = unevaluated_resources ary.each do |resource| resource.evaluate end # If we evaluated, let the loop know. return true else return false end end end # Iterate over collections and resources until we're sure that the whole # configuration is evaluated. This is necessary because both collections # and defined resources can generate new resources, which themselves could # be defined resources. def evaluate_generators count = 0 loop do done = true # Call collections first, then definitions. done = false if evaluate_collections done = false if evaluate_definitions break if done if count > 1000 raise Puppet::ParseError, "Somehow looped more than 1000 times while evaluating host configuration" end end end # Find and evaluate our main object, if possible. def evaluate_main if klass = @parser.findclass("", "") # Set the source, so objects can tell where they were defined. topscope.source = klass klass.safeevaluate :scope => topscope, :nosubscope => true end end # Turn our configuration graph into whatever the client is expecting. def extract send("extract_to_%s" % extraction_format) end # Create the traditional TransBuckets and TransObjects from our configuration # graph. This will hopefully be deprecated soon. def extract_to_transportable top = nil current = nil buckets = {} # I'm *sure* there's a simple way to do this using a breadth-first search # or something, but I couldn't come up with, and this is both fast # and simple, so I'm not going to worry about it too much. @scope_graph.vertices.each do |scope| # For each scope, we need to create a TransBucket, and then # put all of the scope's resources into that bucket, translating # each resource into a TransObject. # Unless the bucket's already been created, make it now and add # it to the cache. unless bucket = buckets[scope] bucket = buckets[scope] = scope.to_trans end # First add any contained scopes @scope_graph.adjacent(scope, :direction => :out).each do |vertex| # If there's not already a bucket, then create and cache it. unless child_bucket = buckets[vertex] child_bucket = buckets[vertex] = vertex.to_trans end bucket.push child_bucket end # Then add the resources. if @resource_graph.vertex?(scope) @resource_graph.adjacent(scope, :direction => :out).each do |vertex| # Some resources don't get translated, e.g., virtual resources. if obj = vertex.to_trans bucket.push obj end end end end # Clear the cache to encourage the GC result = buckets[topscope] buckets.clear return result end # Make sure the entire configuration is evaluated. def fail_on_unevaluated fail_on_unevaluated_overrides fail_on_unevaluated_resource_collections end # If there are any resource overrides remaining, then we could # not find the resource they were supposed to override, so we # want to throw an exception. def fail_on_unevaluated_overrides remaining = [] @resource_overrides.each do |name, overrides| remaining += overrides end unless remaining.empty? fail Puppet::ParseError, "Could not find object(s) %s" % remaining.collect { |o| o.ref }.join(", ") end end # Make sure we don't have any remaining collections that specifically # look for resources, because we want to consider those to be # parse errors. def fail_on_unevaluated_resource_collections remaining = [] @collections.each do |coll| # We're only interested in the 'resource' collections, # which result from direct calls of 'realize'. Anything # else is allowed not to return resources. # Collect all of them, so we have a useful error. if r = coll.resources if r.is_a?(Array) remaining += r else remaining << r end end end unless remaining.empty? raise Puppet::ParseError, "Failed to realize virtual resources %s" % remaining.join(', ') end end # Make sure all of our resources and such have done any last work # necessary. def finish @resource_table.each { |name, resource| resource.finish if resource.respond_to?(:finish) } end # Set up all of our internal variables. def initvars # The table for storing class singletons. This will only actually # be used by top scopes and node scopes. @class_scopes = {} # The table for all defined resources. @resource_table = {} # The list of objects that will available for export. @exported_resources = {} # The list of overrides. This is used to cache overrides on objects # that don't exist yet. We store an array of each override. @resource_overrides = Hash.new do |overs, ref| overs[ref] = [] end # The list of collections that have been created. This is a global list, # but they each refer back to the scope that created them. @collections = [] # A list of tags we've generated; most class names. @tags = [] # Create our initial scope, our scope graph, and add the initial scope to the graph. @topscope = Puppet::Parser::Scope.new(:configuration => self, :type => "main", :name => "top", :parser => self.parser) # For maintaining scope relationships. @scope_graph = GRATR::Digraph.new @scope_graph.add_vertex!(@topscope) # For maintaining the relationship between scopes and their resources. @resource_graph = GRATR::Digraph.new end # Set the node's parameters into the top-scope as variables. def set_node_parameters node.parameters.each do |param, value| @topscope.setvar(param, value) end end # Store the configuration into the database. def store(options) 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(options) end # Do the actual storage. def store_to_active_record(options) begin # We store all of the objects, even the collectable ones benchmark(:info, "Stored configuration for #{options[:name]}") do Puppet::Rails::Host.transaction do Puppet::Rails::Host.store(options) end end rescue => detail if Puppet[:trace] puts detail.backtrace end Puppet.err "Could not store configs: %s" % detail.to_s end end # Add a tag. def tag(*names) names.each do |name| name = name.to_s @tags << name unless @tags.include?(name) end nil end # Return the list of tags. def tags @tags.dup end # Return an array of all of the unevaluated resources. These will be definitions, # which need to get evaluated into native resources. def unevaluated_resources ary = @resource_table.find_all do |name, object| ! object.builtin? and ! object.evaluated? end.collect { |name, object| object } if ary.empty? return nil else return ary end end # Verify that the given resource isn't defined elsewhere. def verify_uniqueness(resource) # Short-curcuit the common case, unless existing_resource = @resource_table[resource.ref] return true end if typeclass = Puppet::Type.type(resource.type) and ! typeclass.isomorphic? Puppet.info "Allowing duplicate %s" % typeclass.name return true end # Either it's a defined type, which are never # isomorphic, or it's a non-isomorphic type, so # we should throw an exception. msg = "Duplicate definition: %s is already defined" % resource.ref if existing_resource.file and existing_resource.line msg << " in file %s at line %s" % [existing_resource.file, existing_resource.line] end if resource.line or resource.file msg << "; cannot redefine" end raise Puppet::ParseError.new(msg) end end diff --git a/lib/puppet/parser/interpreter.rb b/lib/puppet/parser/interpreter.rb index cf3027a5a..54cd9b023 100644 --- a/lib/puppet/parser/interpreter.rb +++ b/lib/puppet/parser/interpreter.rb @@ -1,176 +1,144 @@ require 'puppet' require 'timeout' require 'puppet/rails' require 'puppet/util/methodhelper' require 'puppet/parser/parser' require 'puppet/parser/configuration' 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 configuration back. class Puppet::Parser::Interpreter include Puppet::Util attr_accessor :usenodes include Puppet::Util::Errors - # Create proxy methods, so the scopes can call the interpreter, since - # they don't have access to the parser. - def findclass(namespace, name) - raise "move findclass() out of the interpreter" - @parser.findclass(namespace, name) - end - - def finddefine(namespace, name) - raise "move finddefine() out of the interpreter" - @parser.finddefine(namespace, name) - end - # create our interpreter def initialize(hash) if @code = hash[:Code] @file = nil # to avoid warnings elsif ! @file = hash[:Manifest] devfail "You must provide code or a manifest" end if hash.include?(:UseNodes) @usenodes = hash[:UseNodes] else @usenodes = true end # By default, we only search for parsed nodes. @nodesource = :code @setup = false @local = hash[:Local] || false # 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 @files = [] # Create our parser object parsefiles end - # Pass these methods through to the parser. - [:newclass, :newdefine, :newnode].each do |name| - define_method(name) do |*args| - raise("move %s out of the interpreter" % name) - @parser.send(name, *args) - end - end - - # Add a new file to be checked when we're checking to see if we should be - # reparsed. - def newfile(*files) - raise "who uses newfile?" - files.each do |file| - unless file.is_a? Puppet::Util::LoadedFile - file = Puppet::Util::LoadedFile.new(file) - end - @files << file - end - end - def parsedate parsefiles() @parsedate end # evaluate our whole tree def compile(node) parsefiles() return Puppet::Parser::Configuration.new(node, @parser).compile end private # Check whether any of our files have changed. def checkfiles if @files.find { |f| f.changed? } @parsedate = Time.now.to_i end end # Parse the files, generating our parse tree. This automatically # reparses only if files are updated, so it's safe to call multiple # times. def parsefiles # First check whether there are updates to any non-puppet files # like templates. If we need to reparse, this will get quashed, # but it needs to be done first in case there's no reparse # but there are other file changes. checkfiles() # Check if the parser should reparse. if @file if defined? @parser if stamp = @parser.reparse? Puppet.notice "Reloading files" else return false end end unless FileTest.exists?(@file) # If we've already parsed, then we're ok. if findclass("", "") return else raise Puppet::Error, "Manifest %s must exist" % @file end end end # Create a new parser, just to keep things fresh. Don't replace our # current parser until we know weverything works. newparser = Puppet::Parser::Parser.new() if @code newparser.string = @code else newparser.file = @file end # Parsing stores all classes and defines and such in their # various tables, so we don't worry about the return. begin if @local newparser.parse else benchmark(:info, "Parsed manifest") do newparser.parse end end # We've gotten this far, so it's ok to swap the parsers. oldparser = @parser @parser = newparser if oldparser oldparser.clear end # Mark when we parsed, so we can check freshness @parsedate = Time.now.to_i rescue => detail if Puppet[:trace] puts detail.backtrace end Puppet.err "Could not parse; using old configuration: %s" % detail end end end # $Id$ diff --git a/lib/puppet/parser/parser_support.rb b/lib/puppet/parser/parser_support.rb index 6d069dc07..967508e56 100644 --- a/lib/puppet/parser/parser_support.rb +++ b/lib/puppet/parser/parser_support.rb @@ -1,449 +1,460 @@ # I pulled this into a separate file, because I got # tired of rebuilding the parser.rb file all the time. class Puppet::Parser::Parser require 'puppet/parser/functions' ASTSet = Struct.new(:classes, :definitions, :nodes) # Define an accessor method for each table. We hide the existence of # the struct. [:classes, :definitions, :nodes].each do |name| define_method(name) do @astset.send(name) end end AST = Puppet::Parser::AST attr_reader :file, :interp attr_accessor :files # Add context to a message; useful for error messages and such. def addcontext(message, obj = nil) obj ||= @lexer message += " on line %s" % obj.line if file = obj.file message += " in file %s" % file end return message end # Create an AST array out of all of the args def aryfy(*args) if args[0].instance_of?(AST::ASTArray) result = args.shift args.each { |arg| result.push arg } else result = ast AST::ASTArray, :children => args end return result end # Create an AST object, and automatically add the file and line information if # available. def ast(klass, hash = nil) hash ||= {} unless hash.include?(:line) hash[:line] = @lexer.line end unless hash.include?(:file) if file = @lexer.file hash[:file] = file end end return klass.new(hash) end # The fully qualifed name, with the full namespace. def classname(name) [@lexer.namespace, name].join("::").sub(/^::/, '') end def clear initvars end # Raise a Parse error. def error(message) if brace = @lexer.expected message += "; expected '%s'" end except = Puppet::ParseError.new(message) except.line = @lexer.line if @lexer.file except.file = @lexer.file end raise except end def file=(file) unless FileTest.exists?(file) unless file =~ /\.pp$/ file = file + ".pp" end unless FileTest.exists?(file) raise Puppet::Error, "Could not find file %s" % file end end if @files.detect { |f| f.file == file } raise Puppet::AlreadyImportedError.new("Import loop detected") else @files << Puppet::Util::LoadedFile.new(file) @lexer.file = file end end # Find a class definition, relative to the current namespace. def findclass(namespace, name) fqfind namespace, name, classes end # Find a component definition, relative to the current namespace. def finddefine(namespace, name) fqfind namespace, name, definitions end # This is only used when nodes are looking up the code for their # parent nodes. def findnode(name) fqfind "", name, nodes end # The recursive method used to actually look these objects up. def fqfind(namespace, name, table) namespace = namespace.downcase name = name.downcase if name =~ /^::/ or namespace == "" classname = name.sub(/^::/, '') unless table[classname] self.load(classname) end return table[classname] end ary = namespace.split("::") while ary.length > 0 newname = (ary + [name]).join("::").sub(/^::/, '') if obj = table[newname] or (self.load(newname) and obj = table[newname]) return obj end # Delete the second to last object, which reduces our namespace by one. ary.pop end # If we've gotten to this point without finding it, see if the name # exists at the top namespace if obj = table[name] or (self.load(name) and obj = table[name]) return obj end return nil end # Import our files. def import(file) if Puppet[:ignoreimport] return AST::ASTArray.new(:children => []) end # use a path relative to the file doing the importing if @lexer.file dir = @lexer.file.sub(%r{[^/]+$},'').sub(/\/$/, '') else dir = "." end if dir == "" dir = "." end result = ast AST::ASTArray # We can't interpolate at this point since we don't have any # scopes set up. Warn the user if they use a variable reference pat = file if pat.index("$") Puppet.warning( "The import of #{pat} contains a variable reference;" + " variables are not interpolated for imports " + "in file #{@lexer.file} at line #{@lexer.line}" ) end files = Puppet::Module::find_manifests(pat, dir) if files.size == 0 raise Puppet::ImportError.new("No file(s) found for import " + "of '#{pat}'") end files.collect { |file| parser = Puppet::Parser::Parser.new(@astset) parser.files = self.files Puppet.debug("importing '%s'" % file) unless file =~ /^#{File::SEPARATOR}/ file = File.join(dir, file) end begin parser.file = file rescue Puppet::AlreadyImportedError # This file has already been imported to just move on next end # This will normally add code to the 'main' class. parser.parse } end def initialize(astset = nil) initvars() if astset @astset = astset end end # Initialize or reset all of our variables. def initvars @lexer = Puppet::Parser::Lexer.new() @files = [] @loaded = [] # This is where we store our classes and definitions and nodes. # Clear each hash, just to help the GC a bit. if defined?(@astset) [:classes, :definitions, :nodes].each do |name| @astset.send(name).clear end end @astset = ASTSet.new({}, {}, {}) end # Try to load a class, since we could not find it. def load(classname) return false if classname == "" filename = classname.gsub("::", File::SEPARATOR) loaded = false # First try to load the top-level module mod = filename.scan(/^[\w-]+/).shift unless @loaded.include?(mod) @loaded << mod begin import(mod) Puppet.info "Autoloaded module %s" % mod loaded = true rescue Puppet::ImportError => detail # We couldn't load the module end end unless filename == mod and ! @loaded.include?(mod) @loaded << mod # Then the individual file begin import(filename) Puppet.info "Autoloaded file %s from module %s" % [filename, mod] loaded = true rescue Puppet::ImportError => detail # We couldn't load the file end end return loaded end # Split an fq name into a namespace and name def namesplit(fullname) ary = fullname.split("::") n = ary.pop || "" ns = ary.join("::") return ns, n end # Create a new class, or merge with an existing class. def newclass(name, options = {}) name = name.downcase if definitions.include?(name) raise Puppet::ParseError, "Cannot redefine class %s as a definition" % name end code = options[:code] parent = options[:parent] # If the class is already defined, then add code to it. if other = @astset.classes[name] # Make sure the parents match if parent and other.parentclass and (parent != other.parentclass) error("Class %s is already defined at %s:%s; cannot redefine" % [name, other.file, other.line]) end # This might be dangerous... if parent and ! other.parentclass other.parentclass = parent end # This might just be an empty, stub class. if code tmp = name if tmp == "" tmp = "main" end Puppet.debug addcontext("Adding code to %s" % tmp) # Else, add our code to it. if other.code and code other.code.children += code.children else other.code ||= code end end else # Define it anew. # Note we're doing something somewhat weird here -- we're setting # the class's namespace to its fully qualified name. This means # anything inside that class starts looking in that namespace first. args = {:namespace => name, :classname => name, :parser => self} args[:code] = code if code args[:parentclass] = parent if parent @astset.classes[name] = ast AST::HostClass, args end return @astset.classes[name] end # Create a new definition. def newdefine(name, options = {}) name = name.downcase if @astset.classes.include?(name) raise Puppet::ParseError, "Cannot redefine class %s as a definition" % name end # Make sure our definition doesn't already exist if other = @astset.definitions[name] error("%s is already defined at %s:%s; cannot redefine" % [name, other.file, other.line]) end ns, whatever = namesplit(name) args = { :namespace => ns, :arguments => options[:arguments], :code => options[:code], :parser => self, :classname => name } [:code, :arguments].each do |param| args[param] = options[param] if options[param] end @astset.definitions[name] = ast AST::Component, args end # Create a new node. Nodes are special, because they're stored in a global # table, not according to namespaces. def newnode(names, options = {}) names = [names] unless names.instance_of?(Array) names.collect do |name| name = name.to_s.downcase if other = @astset.nodes[name] error("Node %s is already defined at %s:%s; cannot redefine" % [other.name, other.file, other.line]) end name = name.to_s if name.is_a?(Symbol) args = { :name => name, :parser => self } if options[:code] args[:code] = options[:code] end if options[:parent] args[:parentclass] = options[:parent] end @astset.nodes[name] = ast(AST::Node, args) @astset.nodes[name].classname = name @astset.nodes[name] end end def on_error(token,value,stack) #on '%s' at '%s' in\n'%s'" % [token,value,stack] #error = "line %s: parse error after '%s'" % # [@lexer.line,@lexer.last] error = "Syntax error at '%s'" % [value] if brace = @lexer.expected error += "; expected '%s'" % brace end except = Puppet::ParseError.new(error) except.line = @lexer.line if @lexer.file except.file = @lexer.file end raise except end # how should I do error handling here? def parse(string = nil) if string self.string = string end begin main = yyparse(@lexer,:scan) rescue Racc::ParseError => except error = Puppet::ParseError.new(except) error.line = @lexer.line error.file = @lexer.file error.set_backtrace except.backtrace raise error rescue Puppet::ParseError => except except.line ||= @lexer.line except.file ||= @lexer.file raise except rescue Puppet::Error => except # and this is a framework error except.line ||= @lexer.line except.file ||= @lexer.file raise except rescue Puppet::DevError => except except.line ||= @lexer.line except.file ||= @lexer.file raise except rescue => except error = Puppet::DevError.new(except.message) error.line = @lexer.line error.file = @lexer.file error.set_backtrace except.backtrace raise error end if main # Store the results as the top-level class. newclass("", :code => main) end return @astset ensure @lexer.clear end # See if any of the files have changed. def reparse? if file = @files.detect { |file| file.changed? } return file.stamp else return false end end def string=(string) @lexer.string = string end + + # Add a new file to be checked when we're checking to see if we should be + # reparsed. + def watch_file(*files) + files.each do |file| + unless file.is_a? Puppet::Util::LoadedFile + file = Puppet::Util::LoadedFile.new(file) + end + @files << file + end + end end # $Id$ diff --git a/lib/puppet/parser/resource.rb b/lib/puppet/parser/resource.rb index eace88645..9d3e962f0 100644 --- a/lib/puppet/parser/resource.rb +++ b/lib/puppet/parser/resource.rb @@ -1,417 +1,416 @@ # A resource that we're managing. This handles making sure that only subclasses # can set parameters. class Puppet::Parser::Resource require 'puppet/parser/resource/param' require 'puppet/parser/resource/reference' ResParam = Struct.new :name, :value, :source, :line, :file include Puppet::Util include Puppet::Util::MethodHelper include Puppet::Util::Errors include Puppet::Util::Logging attr_accessor :source, :line, :file, :scope, :rails_id attr_accessor :virtual, :override, :translated attr_reader :exported, :evaluated, :params attr_writer :tags # Proxy a few methods to our @ref object. [:builtin?, :type, :title].each do |method| define_method(method) do @ref.send(method) end end # Set up some boolean test methods [:exported, :translated, :override, :virtual, :evaluated].each do |method| newmeth = (method.to_s + "?").intern define_method(newmeth) do self.send(method) end end def [](param) param = symbolize(param) if param == :title return self.title end if @params.has_key?(param) @params[param].value else nil end end def builtin=(bool) @ref.builtin = bool end # Retrieve the associated definition and evaluate it. def evaluate if klass = @ref.definedtype finish() scope.configuration.delete_resource(self) - return klass.evaluate(:scope => scope, + return klass.evaluate_resource(:scope => scope, :type => self.type, :title => self.title, :arguments => self.to_hash, - :scope => self.scope, :virtual => self.virtual, :exported => self.exported ) elsif builtin? devfail "Cannot evaluate a builtin type" else self.fail "Cannot find definition %s" % self.type end ensure @evaluated = true end # Mark this resource as both exported and virtual, # or remove the exported mark. def exported=(value) if value @virtual = true @exported = value else @exported = value end end # Do any finishing work on this object, called before evaluation or # before storage/translation. def finish add_overrides() add_defaults() add_metaparams() validate() end def initialize(options) # Set all of the options we can. options.each do |option, value| if respond_to?(option.to_s + "=") send(option.to_s + "=", value) options.delete(option) end end [:scope, :source].each do |attribute| unless self.send(attribute) raise ArgumentError, "Resources require a %s" % attribute end end # Set up our reference. if type = options[:type] and title = options[:title] options.delete(:type) options.delete(:title) else raise ArgumentError, "Resources require a type and title" end @ref = Reference.new(:type => type, :title => title, :scope => self.scope) @params = {} # Define all of the parameters if params = options[:params] options.delete(:params) params.each do |param| set_parameter(param) end end # Throw an exception if we've got any arguments left to set. unless options.empty? raise ArgumentError, "Resources do not accept %s" % options.keys.collect { |k| k.to_s }.join(", ") end end # Merge an override resource in. This will throw exceptions if # any overrides aren't allowed. def merge(resource) # Test the resource scope, to make sure the resource is even allowed # to override. unless self.source.object_id == resource.source.object_id || resource.source.child_of?(self.source) raise Puppet::ParseError.new("Only subclasses can override parameters", resource.line, resource.file) end # Some of these might fail, but they'll fail in the way we want. resource.params.each do |name, param| override_parameter(param) end end # Modify this resource in the Rails database. Poor design, yo. def modify_rails(db_resource) args = rails_args args.each do |param, value| db_resource[param] = value unless db_resource[param] == value end # Handle file specially if (self.file and (!db_resource.file or db_resource.file != self.file)) db_resource.file = self.file end updated_params = @params.inject({}) do |hash, ary| hash[ary[0].to_s] = ary[1] hash end db_resource.ar_hash_merge(db_resource.get_params_hash(db_resource.param_values), updated_params, :create => Proc.new { |name, parameter| parameter.to_rails(db_resource) }, :delete => Proc.new { |values| values.each { |value| db_resource.param_values.delete(value) } }, :modify => Proc.new { |db, mem| mem.modify_rails_values(db) }) updated_tags = tags.inject({}) { |hash, tag| hash[tag] = tag hash } db_resource.ar_hash_merge(db_resource.get_tag_hash(), updated_tags, :create => Proc.new { |name, tag| db_resource.add_resource_tag(name) }, :delete => Proc.new { |tag| db_resource.resource_tags.delete(tag) }, :modify => Proc.new { |db, mem| # nothing here }) end # This *significantly* reduces the number of calls to Puppet.[]. def paramcheck? unless defined? @@paramcheck @@paramcheck = Puppet[:paramcheck] end @@paramcheck end # A temporary occasion, until I get paths in the scopes figured out. def path to_s end # Return the short version of our name. def ref @ref.to_s end def tags unless defined? @tags @tags = scope.tags @tags << self.type end @tags end def to_hash @params.inject({}) do |hash, ary| param = ary[1] # Skip "undef" values. if param.value != :undef hash[param.name] = param.value end hash end end # Turn our parser resource into a Rails resource. def to_rails(host) args = rails_args db_resource = host.resources.build(args) # Handle file specially db_resource.file = self.file @params.each { |name, param| param.to_rails(db_resource) } tags.each { |tag| db_resource.add_resource_tag(tag) } return db_resource end def to_s self.ref end # Translate our object to a transportable object. def to_trans unless builtin? devfail "Tried to translate a non-builtin resource" end return nil if virtual? # Now convert to a transobject obj = Puppet::TransObject.new(@ref.title, @ref.type) to_hash.each do |p, v| if v.is_a?(Reference) v = v.to_ref elsif v.is_a?(Array) v = v.collect { |av| if av.is_a?(Reference) av = av.to_ref end av } end # If the value is an array with only one value, then # convert it to a single value. This is largely so that # the database interaction doesn't have to worry about # whether it returns an array or a string. obj[p.to_s] = if v.is_a?(Array) and v.length == 1 v[0] else v end end obj.file = self.file obj.line = self.line obj.tags = self.tags return obj end private # Add default values from our definition. def add_defaults scope.lookupdefaults(self.type).each do |name, param| unless @params.include?(name) self.debug "Adding default for %s" % name @params[name] = param end end end # Add any metaparams defined in our scope. This actually adds any metaparams # from any parent scope, and there's currently no way to turn that off. def add_metaparams Puppet::Type.eachmetaparam do |name| # Skip metaparams that we already have defined. next if @params[name] if val = scope.lookupvar(name.to_s, false) unless val == :undefined set_parameter(name, val) end end end end # Add any overrides for this object. def add_overrides if overrides = scope.configuration.resource_overrides(self) overrides.each do |over| self.merge(over) end # Remove the overrides, so that the configuration knows there # are none left. overrides.clear end end # Accept a parameter from an override. def override_parameter(param) # This can happen if the override is defining a new parameter, rather # than replacing an existing one. unless current = @params[param.name] @params[param.name] = param return end # The parameter is already set. See if they're allowed to override it. if param.source.child_of?(current.source) if param.add # Merge with previous value. param.value = [ current.value, param.value ].flatten end # Replace it, keeping all of its info. @params[param.name] = param else if Puppet[:trace] puts caller end msg = "Parameter '%s' is already set on %s" % [param.name, self.to_s] if current.source.to_s != "" msg += " by %s" % current.source end if current.file or current.line fields = [] fields << current.file if current.file fields << current.line.to_s if current.line msg += " at %s" % fields.join(":") end msg += "; cannot redefine" raise Puppet::ParseError.new(msg, param.line, param.file) end end # Verify that all passed parameters are valid. This throws an error if # there's a problem, so we don't have to worry about the return value. def paramcheck(param) param = param.to_s # Now make sure it's a valid argument to our class. These checks # are organized in order of commonhood -- most types, it's a valid # argument and paramcheck is enabled. if @ref.typeclass.validattr?(param) true elsif %w{name title}.include?(param) # always allow these true elsif paramcheck? self.fail Puppet::ParseError, "Invalid parameter '%s' for type '%s'" % [param, @ref.type] end end def rails_args return [:type, :title, :line, :exported].inject({}) do |hash, param| # 'type' isn't a valid column name, so we have to use another name. to = (param == :type) ? :restype : param if value = self.send(param) hash[to] = value end hash end end # Define a parameter in our resource. def set_parameter(param, value = nil) if value param = Puppet::Parser::Resource::Param.new( :name => param, :value => value, :source => self.source ) elsif ! param.is_a?(Puppet::Parser::Resource::Param) raise ArgumentError, "Must pass a parameter or all necessary values" end # And store it in our parameter hash. @params[param.name] = param end # Make sure the resource's parameters are all valid for the type. def validate @params.each do |name, param| # Make sure it's a valid parameter. paramcheck(name) end end end diff --git a/lib/puppet/parser/scope.rb b/lib/puppet/parser/scope.rb index 808362858..527ed4dcd 100644 --- a/lib/puppet/parser/scope.rb +++ b/lib/puppet/parser/scope.rb @@ -1,457 +1,457 @@ # 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 attr_accessor :name, :type, :base, :keyword attr_accessor :top, :translated, :exported, :virtual, :configuration # Proxy accessors def host - @configuration.host + @configuration.node.name end def interpreter @configuration.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 # Is the type a builtin type? def builtintype?(type) if typeklass = Puppet::Type.type(type) return typeklass else return false end end # Retrieve a given class scope from the configuration. def class_scope(klass) configuration.class_scope(klass) end # Are we the top scope? def topscope? @level == 1 end def exported? self.exported 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) configuration.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 = 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) configuration.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? defined?(@nodescope) and @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 = configuration.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 def resources @definedtable.values end # Store the fact that we've evaluated a given class. We use a hash # that gets inherited from the top scope down, rather than a global # hash. We store the object ID, not class name, so that we # can support multiple unrelated classes with the same name. def setclass(klass) if klass.is_a?(AST::HostClass) - unless klass.classname + unless name = klass.classname raise Puppet::DevError, "Got a %s with no fully qualified name" % klass.class end - @configuration.class_set(klass.classname, self) + @configuration.class_set(name, self) else raise Puppet::DevError, "Invalid class %s" % klass.inspect end if klass.is_a?(AST::Node) @nodescope = true end nil end # Add a new object to our object table and the global list, and do any necessary # checks. def setresource(resource) @configuration.store_resource(self, resource) # Mark the resource as virtual or exported, as necessary. if self.exported? resource.exported = true elsif self.virtual? resource.virtual = true end return resource end # Override a parameter in an existing object. If the object does not yet # exist, then cache the override in a global table, so it can be flushed # at the end. def setoverride(resource) @configuration.store_override(resource) 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 # Add a tag to our current list. These tags will be added to all # of the objects contained in this scope. def tag(*ary) ary.each { |tag| if tag.nil? or tag == "" puts caller Puppet.debug "got told to tag with %s" % tag.inspect next end unless tag =~ /^\w[-\w]*$/ fail Puppet::ParseError, "Invalid tag %s" % tag.inspect end tag = tag.to_s unless @tags.include?(tag) #Puppet.info "Tagging scope %s with %s" % [self.object_id, tag] @tags << tag end } 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 tmp = [] + @tags unless ! defined? @type or @type.nil? or @type == "" tmp << @type.to_s end if parent #info "Looking for tags in %s" % parent.type parent.tags.each { |tag| if tag.nil? or tag == "" Puppet.debug "parent returned tag %s" % tag.inspect next end unless tmp.include?(tag) tmp << tag end } end return tmp.sort.uniq end # Used mainly for logging def to_s if self.name return "%s[%s]" % [@type, @name] else return self.type.to_s end end # Convert our resource to a TransBucket. def to_trans bucket = Puppet::TransBucket.new([]) case self.type when "": bucket.type = "main" when nil: devfail "A Scope with no type" else bucket.type = @type end if self.name bucket.name = self.name end return bucket end # Undefine a variable; only used for testing. def unsetvar(var) if @symtable.include?(var) @symtable.delete(var) end end def virtual? self.virtual || self.exported? end end # $Id$ diff --git a/lib/puppet/parser/templatewrapper.rb b/lib/puppet/parser/templatewrapper.rb index 3b8cc3a3a..9c3c6c0d0 100644 --- a/lib/puppet/parser/templatewrapper.rb +++ b/lib/puppet/parser/templatewrapper.rb @@ -1,54 +1,54 @@ # 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) unless FileTest.exists?(@file) raise Puppet::ParseError, "Could not find template %s" % file end # We'll only ever not have an interpreter in testing, but, eh. - if @scope.interp - @scope.interp.newfile(@file) + 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 # $Id$ diff --git a/test/language/ast.rb b/test/language/ast.rb index 9e00c610d..38e658edb 100755 --- a/test/language/ast.rb +++ b/test/language/ast.rb @@ -1,193 +1,190 @@ #!/usr/bin/env ruby $:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/ require 'puppettest' require 'puppet/parser/interpreter' require 'puppet/parser/parser' require 'puppettest/resourcetesting' require 'puppettest/parsertesting' require 'puppettest/support/collection' class TestAST < Test::Unit::TestCase include PuppetTest::ParserTesting include PuppetTest::ResourceTesting include PuppetTest::Support::Collection def test_if astif = nil astelse = nil fakeelse = FakeAST.new(:else) faketest = FakeAST.new(true) fakeif = FakeAST.new(:if) assert_nothing_raised { astelse = AST::Else.new(:statements => fakeelse) } assert_nothing_raised { astif = AST::IfStatement.new( :test => faketest, :statements => fakeif, :else => astelse ) } # We initialized it to true, so we should get that first ret = nil assert_nothing_raised { ret = astif.evaluate(:scope => "yay") } assert_equal(:if, ret) # Now set it to false and check that faketest.evaluate = false assert_nothing_raised { ret = astif.evaluate(:scope => "yay") } assert_equal(:else, ret) end # Make sure our override object behaves "correctly" def test_override - interp, scope, source = mkclassframing + scope = mkscope ref = nil assert_nothing_raised do - ref = resourceoverride("resource", "yaytest", "one" => "yay", "two" => "boo") + ref = resourceoverride("file", "/yayness", "owner" => "blah", "group" => "boo") end + Puppet::Parser::Resource.expects(:new).with { |o| o.is_a?(Hash) }.returns(:override) + scope.expects(:setoverride).with(:override) ret = nil assert_nothing_raised do ret = ref.evaluate :scope => scope end - assert_instance_of(Puppet::Parser::Resource, ret) - - assert(ret.override?, "Resource was not an override resource") - - assert(scope.overridetable[ret.ref].include?(ret), - "Was not stored in the override table") + assert_equal(:override, ret, "Did not return override") end # make sure our resourcedefaults ast object works correctly. def test_resourcedefaults interp, scope, source = mkclassframing # Now make some defaults for files args = {:source => "/yay/ness", :group => "yayness"} assert_nothing_raised do obj = defaultobj "file", args obj.evaluate :scope => scope end hash = nil assert_nothing_raised do hash = scope.lookupdefaults("file") end hash.each do |name, value| assert_instance_of(Symbol, name) # params always convert assert_instance_of(Puppet::Parser::Resource::Param, value) end args.each do |name, value| assert(hash[name], "Did not get default %s" % name) assert_equal(value, hash[name].value) end end def test_node - interp = mkinterp - scope = mkscope(:interp => interp) + scope = mkscope + parser = scope.configuration.parser # Define a base node - basenode = interp.newnode "basenode", :code => AST::ASTArray.new(:children => [ + basenode = parser.newnode "basenode", :code => AST::ASTArray.new(:children => [ resourcedef("file", "/tmp/base", "owner" => "root") ]) # Now define a subnode - nodes = interp.newnode ["mynode", "othernode"], + 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(interp.nodesearch_code(node), "Could not find %s" % node) + assert(parser.nodes[node], "Could not find %s" % node) end - mynode = interp.nodesearch_code("mynode") + mynode = parser.nodes["mynode"] # Now try evaluating the node assert_nothing_raised do mynode.evaluate :scope => scope 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 = interp.newnode(%w{child}, :parent => "basenode").shift + child = parser.newnode(%w{child}, :parent => "basenode").shift - newscope = mkscope :interp => interp + newscope = mkscope :parser => parser assert_nothing_raised do child.evaluate :scope => newscope end assert(newscope.findresource("File[/tmp/base]"), "Could not find base resource") end def test_collection - interp = mkinterp - scope = mkscope(:interp => interp) + scope = mkscope coll = nil assert_nothing_raised do coll = AST::Collection.new(:type => "file", :form => :virtual) end assert_instance_of(AST::Collection, coll) ret = nil assert_nothing_raised do ret = coll.evaluate :scope => scope end assert_instance_of(Puppet::Parser::Collector, ret) # Now make sure we get it back from the scope - assert_equal([ret], scope.collections) + colls = scope.configuration.instance_variable_get("@collections") + assert_equal([ret], colls, "Did not store collector in config's collection list") end def test_virtual_collexp @interp, @scope, @source = mkclassframing # make a resource resource = mkresource(:type => "file", :title => "/tmp/testing", :params => {:owner => "root", :group => "bin", :mode => "644"}) run_collection_queries(:virtual) do |string, result, query| code = nil assert_nothing_raised do str, code = query.evaluate :scope => @scope end assert_instance_of(Proc, code) assert_nothing_raised do assert_equal(result, code.call(resource), "'#{string}' failed") end end end end # $Id$ diff --git a/test/language/ast/component.rb b/test/language/ast/component.rb index 13cf60857..cf0cce976 100755 --- a/test/language/ast/component.rb +++ b/test/language/ast/component.rb @@ -1,142 +1,156 @@ #!/usr/bin/env ruby # # Created by Luke A. Kanies on 2006-02-20. # Copyright (c) 2006. All rights reserved. $:.unshift("../../lib") if __FILE__ =~ /\.rb$/ require 'puppettest' require 'mocha' require 'puppettest/parsertesting' require 'puppettest/resourcetesting' class TestASTComponent < Test::Unit::TestCase include PuppetTest include PuppetTest::ParserTesting include PuppetTest::ResourceTesting AST = Puppet::Parser::AST - def test_component - parser, scope, source = mkclassframing + 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 = mkconfig + 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"))] + ) + # Now call it a couple of times # First try it without a required param - assert_raise(Puppet::ParseError) do - klass.evaluate(:scope => scope, + assert_raise(Puppet::ParseError, "Did not fail when a required parameter was not provided") do + klass.evaluate_resource(:scope => scope, :name => "bad", :arguments => {"owner" => "nobody"} ) end # And make sure it didn't create the file - assert_nil(scope.findresource("File[/tmp/bad]"), + assert_nil(config.findresource("File[/tmp/bad]"), "Made file with invalid params") assert_nothing_raised do - klass.evaluate(:scope => scope, + klass.evaluate_resource(:scope => scope, :title => "first", :arguments => {"mode" => "755"} ) end - firstobj = scope.findresource("File[/tmp/first]") + 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(:scope => scope, + klass.evaluate_resource(:scope => scope, :title => "first", :arguments => {"mode" => "755"} ) end # Now create another with different args assert_nothing_raised do - klass.evaluate(:scope => scope, + klass.evaluate_resource(:scope => scope, :title => "second", :arguments => {"mode" => "755", "owner" => "daemon"} ) end - secondobj = scope.findresource("File[/tmp/second]") + 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, scope, source = mkclassframing [ {:name => "one", :title => "two"}, {:title => "mytitle"}, ].each_with_index do |hash, i| # Create a definition that uses both name and title klass = parser.newdefine "yayness%s" % i subscope = klass.subscope(scope, "yayness%s" % i) klass.expects(:subscope).returns(subscope) args = {:title => hash[:title]} if hash[:name] args[:arguments] = {:name => hash[:name]} end args[:scope] = scope assert_nothing_raised("Could not evaluate definition with %s" % hash.inspect) do - klass.evaluate(args) + klass.evaluate_resource(args) end name = hash[:name] || hash[:title] title = hash[:title] args[:name] ||= name 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 = args[param] assert(subscope.tags.include?(val), "Scope was not tagged with %s" % 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 # $Id$ diff --git a/test/language/ast/hostclass.rb b/test/language/ast/hostclass.rb index 051bee36c..f093504ec 100755 --- a/test/language/ast/hostclass.rb +++ b/test/language/ast/hostclass.rb @@ -1,163 +1,166 @@ #!/usr/bin/env ruby # # Created by Luke A. Kanies on 2006-02-20. # Copyright (c) 2006. All rights reserved. $:.unshift("../../lib") if __FILE__ =~ /\.rb$/ require 'puppettest' require 'puppettest/parsertesting' require 'puppettest/resourcetesting' require 'mocha' class TestASTHostClass < Test::Unit::TestCase include PuppetTest include PuppetTest::ParserTesting include PuppetTest::ResourceTesting AST = Puppet::Parser::AST def test_hostclass - interp, scope, source = mkclassframing + scope = mkscope + parser = scope.configuration.parser # Create the class we're testing, first with no parent - klass = interp.newclass "first", + klass = parser.newclass "first", :code => AST::ASTArray.new( :children => [resourcedef("file", "/tmp", "owner" => "nobody", "mode" => "755")] ) assert_nothing_raised do klass.evaluate(:scope => scope) end # Then try it again assert_nothing_raised do klass.evaluate(:scope => scope) end assert(scope.class_scope(klass), "Class was not considered evaluated") tmp = scope.findresource("File[/tmp]") assert(tmp, "Could not find file /tmp") assert_equal("nobody", tmp[:owner]) assert_equal("755", tmp[:mode]) # Now create a couple more classes. - newbase = interp.newclass "newbase", + newbase = parser.newclass "newbase", :code => AST::ASTArray.new( :children => [resourcedef("file", "/tmp/other", "owner" => "nobody", "mode" => "644")] ) - newsub = interp.newclass "newsub", + newsub = parser.newclass "newsub", :parent => "newbase", :code => AST::ASTArray.new( :children => [resourcedef("file", "/tmp/yay", "owner" => "nobody", "mode" => "755"), resourceoverride("file", "/tmp/other", "owner" => "daemon") ] ) # Override a different variable in the top scope. - moresub = interp.newclass "moresub", + moresub = parser.newclass "moresub", :parent => "newbase", :code => AST::ASTArray.new( :children => [resourceoverride("file", "/tmp/other", "mode" => "755")] ) assert_nothing_raised do newsub.evaluate(:scope => scope) end assert_nothing_raised do moresub.evaluate(:scope => scope) end assert(scope.class_scope(newbase), "Did not eval newbase") assert(scope.class_scope(newsub), "Did not eval newsub") yay = scope.findresource("File[/tmp/yay]") assert(yay, "Did not find file /tmp/yay") assert_equal("nobody", yay[:owner]) assert_equal("755", yay[:mode]) other = scope.findresource("File[/tmp/other]") assert(other, "Did not find file /tmp/other") assert_equal("daemon", other[:owner]) assert_equal("755", other[:mode]) end # Make sure that classes set their namespaces to themselves. This # way they start looking for definitions in their own namespace. def test_hostclass_namespace - interp, scope, source = mkclassframing + scope = mkscope + parser = scope.configuration.parser # Create a new class klass = nil assert_nothing_raised do - klass = interp.newclass "funtest" + klass = parser.newclass "funtest" end # Now define a definition in that namespace define = nil assert_nothing_raised do - define = interp.newdefine "funtest::mydefine" + define = parser.newdefine "funtest::mydefine" end assert_equal("funtest", klass.namespace, "component namespace was not set in the class") assert_equal("funtest", define.namespace, "component namespace was not set in the definition") newscope = klass.subscope(scope) assert_equal(["funtest"], newscope.namespaces, "Scope did not inherit namespace") # Now make sure we can find the define assert(newscope.finddefine("mydefine"), "Could not find definition in my enclosing class") end # Make sure that our scope is a subscope of the parentclass's scope. # At the same time, make sure definitions in the parent class can be # found within the subclass (#517). def test_parent_scope_from_parentclass - interp = mkinterp + scope = mkscope + parser = scope.configuration.parser - interp.newclass("base") - fun = interp.newdefine("base::fun") - interp.newclass("middle", :parent => "base") - interp.newclass("sub", :parent => "middle") - scope = mkscope :interp => interp + parser.newclass("base") + fun = parser.newdefine("base::fun") + parser.newclass("middle", :parent => "base") + parser.newclass("sub", :parent => "middle") + scope = mkscope :parser => parser ret = nil assert_nothing_raised do - ret = scope.evalclasses("sub") + ret = scope.configuration.evaluate_classes(["sub"]) end subscope = scope.class_scope(scope.findclass("sub")) assert(subscope, "could not find sub scope") mscope = scope.class_scope(scope.findclass("middle")) assert(mscope, "could not find middle scope") pscope = scope.class_scope(scope.findclass("base")) assert(pscope, "could not find parent scope") assert(pscope == mscope.parent, "parent scope of middle was not set correctly") assert(mscope == subscope.parent, "parent scope of sub was not set correctly") result = mscope.finddefine("fun") assert(result, "could not find parent-defined definition from middle") assert(fun == result, "found incorrect parent-defined definition from middle") result = subscope.finddefine("fun") assert(result, "could not find parent-defined definition from sub") assert(fun == result, "found incorrect parent-defined definition from sub") end end # $Id$ diff --git a/test/language/ast/resourceref.rb b/test/language/ast/resourceref.rb index 7b7889dc1..a3d6775a2 100755 --- a/test/language/ast/resourceref.rb +++ b/test/language/ast/resourceref.rb @@ -1,95 +1,95 @@ #!/usr/bin/env ruby # # Created by Luke A. Kanies on 2007-07-8. # Copyright (c) 2007. All rights reserved. $:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/ require 'puppettest' require 'puppettest/parsertesting' class TestASTResourceRef < Test::Unit::TestCase include PuppetTest include PuppetTest::ParserTesting AST = Puppet::Parser::AST def newref(type, title) AST::ResourceRef.new(:type => type, :title => AST::String.new(:value => title)) end def setup super - @interp = mkinterp - @scope = mkscope :interp => @interp + @scope = mkscope + @parser = @scope.configuration.parser end def test_evaluate - @interp.newdefine "one::two" - @interp.newdefine "one-two" + @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 => @scope) end assert_equal(type, evaled.type, "Type did not translate correctly") assert_equal(title, evaled.title, "Title did not translate correctly") end end # Related to #706, make sure resource references correctly translate to qualified types. def test_scoped_references - @interp.newdefine "one" - @interp.newdefine "one::two" - @interp.newdefine "three" + @parser.newdefine "one" + @parser.newdefine "one::two" + @parser.newdefine "three" twoscope = @scope.newscope(:type => "one", :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(:scope => 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(:scope => 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(:scope => twoscope).type, "Defined type was not made fully qualified") # Then a builtin type assert_equal("file", newref("file", title).evaluate(:scope => 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(:scope => twoscope) end # Now run the same tests, but with the classes - @interp.newclass "four" - @interp.newclass "one::five" + @parser.newclass "four" + @parser.newclass "one::five" # First try an unqualified type assert_equal("four", newref("class", "four").evaluate(:scope => twoscope).title, "Unqualified class was not found") # Then a qualified class assert_equal("one::five", newref("class", "five").evaluate(:scope => 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(:scope => 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(:scope => twoscope) end end end # $Id$ diff --git a/test/language/collector.rb b/test/language/collector.rb index bdcaf4aec..a4119929f 100755 --- a/test/language/collector.rb +++ b/test/language/collector.rb @@ -1,179 +1,180 @@ #!/usr/bin/env ruby $:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/ require 'puppettest' require 'puppettest/parsertesting' require 'puppettest/resourcetesting' class TestCollector < Test::Unit::TestCase include PuppetTest include PuppetTest::ParserTesting include PuppetTest::ResourceTesting Parser = Puppet::Parser AST = Parser::AST def setup super Puppet[:trace] = false - @interp, @scope, @source = mkclassframing + @scope = mkscope + @config = @scope.configuration end # Test just collecting a specific resource. This is used by the 'realize' # function, and it's much faster than iterating over all of the resources. def test_collect_resource # Make a collector coll = nil assert_nothing_raised do coll = Puppet::Parser::Collector.new(@scope, "file", nil, nil, :virtual) end # Now set the resource in the collector assert_nothing_raised do coll.resources = ["File[/tmp/virtual1]", "File[/tmp/virtual3]"] end - @scope.newcollection(coll) + @config.add_collection(coll) # Evaluate the collector and make sure it doesn't fail with no resources # found yet assert_nothing_raised("Resource collection with no results failed") do assert_equal(false, coll.evaluate) end # Make a couple of virtual resources one = mkresource(:type => "file", :title => "/tmp/virtual1", :virtual => true, :params => {:owner => "root"}) two = mkresource(:type => "file", :title => "/tmp/virtual2", :virtual => true, :params => {:owner => "root"}) @scope.setresource one @scope.setresource two # Now run the collector again and make sure it finds our resource assert_nothing_raised do assert_equal([one], coll.evaluate, "did not find resource") end # And make sure the resource is no longer virtual assert(! one.virtual?, "Resource is still virtual") # But the other still is assert(two.virtual?, "Resource got realized") # Make sure that the collection is still there - assert(@scope.collections.include?(coll), "collection was deleted too soon") + assert(@config.collections.include?(coll), "collection was deleted too soon") # Now add our third resource three = mkresource(:type => "file", :title => "/tmp/virtual3", :virtual => true, :params => {:owner => "root"}) @scope.setresource three # Run the collection assert_nothing_raised do assert_equal([three], coll.evaluate, "did not find resource") end assert(! three.virtual?, "three is still virtual") # And make sure that the collection got deleted from the scope's list - assert(@scope.collections.empty?, "collection was not deleted") + assert(@config.collections.empty?, "collection was not deleted") end def test_virtual # Make a virtual resource virtual = mkresource(:type => "file", :title => "/tmp/virtual", :virtual => true, :params => {:owner => "root"}) @scope.setresource virtual # And a non-virtual real = mkresource(:type => "file", :title => "/tmp/real", :params => {:owner => "root"}) @scope.setresource real # Now make a collector coll = nil # Make a fake query code = proc do |res| true end assert_nothing_raised do coll = Puppet::Parser::Collector.new(@scope, "file", nil, code, :virtual) end # Set it in our scope - @scope.newcollection(coll) + @config.add_collection(coll) # Make sure it's in the collections - assert(@scope.collections.include?(coll), "collection was not added") + assert(@config.collections.include?(coll), "collection was not added") # And try to collect the virtual resources. ret = nil assert_nothing_raised do ret = coll.collect_virtual end assert_equal([virtual], ret) # Now make sure evaluate does the right thing. assert_nothing_raised do ret = coll.evaluate end # And make sure our virtual object is no longer virtual assert(! virtual.virtual?, "Virtual object did not get realized") # Now make a new collector of a different type and make sure it # finds nothing. assert_nothing_raised do coll = Puppet::Parser::Collector.new(@scope, "exec", nil, nil, :virtual) end # Remark this as virtual virtual.virtual = true assert_nothing_raised do ret = coll.evaluate end assert_equal(false, ret) end # Collections that specify resources should be deleted when they succeed, # but others should remain until the very end. def test_normal_collections_remain # Make a collector coll = nil assert_nothing_raised do coll = Puppet::Parser::Collector.new(@scope, "file", nil, nil, :virtual) end - @scope.newcollection(coll) + @config.add_collection(coll) # run the collection and make sure it doesn't get deleted, since it # didn't return anything assert_nothing_raised do assert_equal(false, coll.evaluate, "Evaluate returned incorrect value") end - assert_equal([coll], @scope.collections, "Collection was deleted") + assert_equal([coll], @config.collections, "Collection was deleted") # Make a resource one = mkresource(:type => "file", :title => "/tmp/virtual1", :virtual => true, :params => {:owner => "root"}) @scope.setresource one # Now perform the collection again, and it should still be there assert_nothing_raised do assert_equal([one], coll.evaluate, "Evaluate returned incorrect value") end - assert_equal([coll], @scope.collections, "Collection was deleted") + assert_equal([coll], @config.collections, "Collection was deleted") assert_equal(false, one.virtual?, "One was not realized") end end # $Id$ diff --git a/test/language/configuration.rb b/test/language/configuration.rb index 688782f7b..fbdf68e73 100755 --- a/test/language/configuration.rb +++ b/test/language/configuration.rb @@ -1,722 +1,745 @@ #!/usr/bin/env ruby $:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/ require 'mocha' require 'puppettest' require 'puppettest/parsertesting' require 'puppet/parser/configuration' # Test our configuration object. class TestConfiguration < Test::Unit::TestCase include PuppetTest include PuppetTest::ParserTesting Config = Puppet::Parser::Configuration Scope = Puppet::Parser::Scope Node = Puppet::Network::Handler.handler(:node) SimpleNode = Node::SimpleNode def mknode(name = "foo") @node = SimpleNode.new(name) end def mkparser # This should mock an interpreter @parser = mock 'parser' end def mkconfig(options = {}) if node = options[:node] options.delete(:node) else node = mknode end @config = Config.new(node, mkparser, options) end def test_initialize config = nil assert_nothing_raised("Could not init config with all required options") do config = Config.new("foo", "parser") end assert_equal("foo", config.node, "Did not set node correctly") assert_equal("parser", config.parser, "Did not set parser correctly") # We're not testing here whether we call initvars, because it's too difficult to # mock. # Now try it with some options assert_nothing_raised("Could not init config with extra options") do config = Config.new("foo", "parser", :ast_nodes => false) end assert_equal(false, config.ast_nodes?, "Did not set ast_nodes? correctly") end def test_initvars config = mkconfig [:class_scopes, :resource_table, :exported_resources, :resource_overrides].each do |table| assert_instance_of(Hash, config.send(:instance_variable_get, "@#{table}"), "Did not set %s table correctly" % table) end assert_instance_of(Scope, config.topscope, "Did not create a topscope") graph = config.instance_variable_get("@scope_graph") assert_instance_of(GRATR::Digraph, graph, "Did not create scope graph") assert(graph.vertex?(config.topscope), "Did not add top scope as a vertex in the graph") end # Make sure we store and can retrieve references to classes and their scopes. def test_class_set_and_class_scope klass = mock 'ast_class' klass.expects(:classname).returns("myname") config = mkconfig config.expects(:tag).with("myname") assert_nothing_raised("Could not set class") do config.class_set "myname", "myscope" end # First try to retrieve it by name. assert_equal("myscope", config.class_scope("myname"), "Could not retrieve class scope by name") # Then by object assert_equal("myscope", config.class_scope(klass), "Could not retrieve class scope by object") end def test_classlist config = mkconfig config.class_set "", "empty" config.class_set "one", "yep" config.class_set "two", "nope" # Make sure our class list is correct assert_equal(%w{one two}.sort, config.classlist.sort, "Did not get correct class list") end # Make sure collections get added to our internal array def test_add_collection config = mkconfig assert_nothing_raised("Could not add collection") do config.add_collection "nope" end assert_equal(%w{nope}, config.instance_variable_get("@collections"), "Did not add collection") end # Make sure we create a graph of scopes. def test_newscope config = mkconfig graph = config.instance_variable_get("@scope_graph") assert_instance_of(Scope, config.topscope, "Did not create top scope") assert_instance_of(GRATR::Digraph, graph, "Did not create graph") assert(graph.vertex?(config.topscope), "The top scope is not a vertex in the graph") # Now that we've got the top scope, create a new, subscope subscope = nil assert_nothing_raised("Could not create subscope") do subscope = config.newscope(config.topscope) end assert_instance_of(Scope, subscope, "Did not create subscope") assert(graph.edge?(config.topscope, subscope), "An edge between top scope and subscope was not added") # Make sure a scope can find its parent. assert(config.parent(subscope), "Could not look up parent scope on configuration") assert_equal(config.topscope.object_id, config.parent(subscope).object_id, "Did not get correct parent scope from configuration") assert_equal(config.topscope.object_id, subscope.parent.object_id, "Scope did not correctly retrieve its parent scope") # Now create another, this time specifying options another = nil assert_nothing_raised("Could not create subscope") do another = config.newscope(subscope, :name => "testing") end assert_equal("testing", another.name, "did not set scope option correctly") assert_instance_of(Scope, another, "Did not create second subscope") assert(graph.edge?(subscope, another), "An edge between parent scope and second subscope was not added") # Make sure it can find its parent. assert(config.parent(another), "Could not look up parent scope of second subscope on configuration") assert_equal(subscope.object_id, config.parent(another).object_id, "Did not get correct parent scope of second subscope from configuration") assert_equal(subscope.object_id, another.parent.object_id, "Second subscope did not correctly retrieve its parent scope") # And make sure both scopes show up in the right order in the search path assert_equal([another.object_id, subscope.object_id, config.topscope.object_id], another.scope_path.collect { |p| p.object_id }, "Did not get correct scope path") end # The heart of the action. def test_compile config = mkconfig [:set_node_parameters, :evaluate_main, :evaluate_ast_node, :evaluate_classes, :evaluate_generators, :fail_on_unevaluated, :finish].each do |method| config.expects(method) end config.expects(:extract).returns(:config) assert_equal(:config, config.compile, "Did not return the results of the extraction") end # Test setting the node's parameters into the top scope. def test_set_node_parameters config = mkconfig @node.parameters = {"a" => "b", "c" => "d"} scope = config.topscope @node.parameters.each do |param, value| scope.expects(:setvar).with(param, value) end assert_nothing_raised("Could not call 'set_node_parameters'") do config.send(:set_node_parameters) end end # Test that we can evaluate the main class, which is the one named "" in namespace # "". def test_evaluate_main config = mkconfig main = mock 'main_class' config.topscope.expects(:source=).with(main) main.expects(:safeevaluate).with(:scope => config.topscope, :nosubscope => true) @parser.expects(:findclass).with("", "").returns(main) assert_nothing_raised("Could not call evaluate_main") do config.send(:evaluate_main) end end # Make sure we either don't look for nodes, or that we find and evaluate the right object. def test_evaluate_ast_node # First try it with ast_nodes disabled config = mkconfig :ast_nodes => false config.expects(:ast_nodes?).returns(false) config.parser.expects(:nodes).never assert_nothing_raised("Could not call evaluate_ast_node when ast nodes are disabled") do config.send(:evaluate_ast_node) end # Now try it with them enabled, but no node found. nodes = mock 'node_hash' config = mkconfig :ast_nodes => true config.expects(:ast_nodes?).returns(true) config.parser.expects(:nodes).returns(nodes).times(4) # Set some names for our test @node.names = %w{a b c} nodes.expects(:[]).with("a").returns(nil) nodes.expects(:[]).with("b").returns(nil) nodes.expects(:[]).with("c").returns(nil) # It should check this last, of course. nodes.expects(:[]).with("default").returns(nil) # And make sure the lack of a node throws an exception assert_raise(Puppet::ParseError, "Did not fail when we couldn't find an ast node") do config.send(:evaluate_ast_node) end # Finally, make sure it works dandily when we have a node nodes = mock 'hash' config = mkconfig :ast_nodes => true config.expects(:ast_nodes?).returns(true) config.parser.expects(:nodes).returns(nodes).times(3) node = mock 'node' node.expects(:safeevaluate).with(:scope => config.topscope) # Set some names for our test @node.names = %w{a b c} nodes.expects(:[]).with("a").returns(nil) nodes.expects(:[]).with("b").returns(nil) nodes.expects(:[]).with("c").returns(node) nodes.expects(:[]).with("default").never # And make sure the lack of a node throws an exception assert_nothing_raised("Failed when a node was found") do config.send(:evaluate_ast_node) end # Lastly, check when we actually find the default. nodes = mock 'hash' config = mkconfig :ast_nodes => true config.expects(:ast_nodes?).returns(true) config.parser.expects(:nodes).returns(nodes).times(4) node = mock 'node' node.expects(:safeevaluate).with(:scope => config.topscope) # Set some names for our test @node.names = %w{a b c} nodes.expects(:[]).with("a").returns(nil) nodes.expects(:[]).with("b").returns(nil) nodes.expects(:[]).with("c").returns(nil) nodes.expects(:[]).with("default").returns(node) # And make sure the lack of a node throws an exception assert_nothing_raised("Failed when a node was found") do config.send(:evaluate_ast_node) end end # Make sure our config object handles tags appropriately. def test_tags config = mkconfig config.send(:tag, "one") assert_equal(%w{one}, config.send(:tags), "Did not add tag") config.send(:tag, "two", "three") assert_equal(%w{one two three}, config.send(:tags), "Did not add new tags") config.send(:tag, "two") assert_equal(%w{one two three}, config.send(:tags), "Allowed duplicate tag") end def test_evaluate_classes config = mkconfig classes = { "one" => mock('class one'), "three" => mock('class three') } classes.each do |name, obj| config.parser.expects(:findclass).with("", name).returns(obj) obj.expects(:safeevaluate).with(:scope => config.topscope) end %w{two four}.each do |name| config.parser.expects(:findclass).with("", name).returns(nil) end config.expects(:tag).with("two") config.expects(:tag).with("four") @node.classes = %w{one two three four} + result = nil assert_nothing_raised("could not call evaluate_classes") do - config.send(:evaluate_classes) + result = config.send(:evaluate_classes) end + assert_equal(%w{one three}, result, "Did not return the list of evaluated classes") end def test_evaluate_collections config = mkconfig colls = [] # Make sure we return false when there's nothing there. assert(! config.send(:evaluate_collections), "Returned true when there were no collections") # And when the collections fail to evaluate. colls << mock("coll1-false") colls << mock("coll2-false") colls.each { |c| c.expects(:evaluate).returns(false) } config.instance_variable_set("@collections", colls) assert(! config.send(:evaluate_collections), "Returned true when collections both evaluated nothing") # Now have one of the colls evaluate colls.clear colls << mock("coll1-one-true") colls << mock("coll2-one-true") colls[0].expects(:evaluate).returns(true) colls[1].expects(:evaluate).returns(false) assert(config.send(:evaluate_collections), "Did not return true when one collection evaluated true") # And have them both eval true colls.clear colls << mock("coll1-both-true") colls << mock("coll2-both-true") colls[0].expects(:evaluate).returns(true) colls[1].expects(:evaluate).returns(true) assert(config.send(:evaluate_collections), "Did not return true when both collections evaluated true") end def test_unevaluated_resources config = mkconfig resources = {} config.instance_variable_set("@resource_table", resources) # First test it when the table is empty assert_nil(config.send(:unevaluated_resources), "Somehow found unevaluated resources in an empty table") # Then add a builtin resources resources["one"] = mock("builtin only") resources["one"].expects(:builtin?).returns(true) assert_nil(config.send(:unevaluated_resources), "Considered a builtin resource unevaluated") # And do both builtin and non-builtin but already evaluated resources.clear resources["one"] = mock("builtin (with eval)") resources["one"].expects(:builtin?).returns(true) resources["two"] = mock("evaled (with builtin)") resources["two"].expects(:builtin?).returns(false) resources["two"].expects(:evaluated?).returns(true) assert_nil(config.send(:unevaluated_resources), "Considered either a builtin or evaluated resource unevaluated") # Now a single unevaluated resource. resources.clear resources["one"] = mock("unevaluated") resources["one"].expects(:builtin?).returns(false) resources["one"].expects(:evaluated?).returns(false) assert_equal([resources["one"]], config.send(:unevaluated_resources), "Did not find unevaluated resource") # With two uneval'ed resources, and an eval'ed one thrown in resources.clear resources["one"] = mock("unevaluated one") resources["one"].expects(:builtin?).returns(false) resources["one"].expects(:evaluated?).returns(false) resources["two"] = mock("unevaluated two") resources["two"].expects(:builtin?).returns(false) resources["two"].expects(:evaluated?).returns(false) resources["three"] = mock("evaluated") resources["three"].expects(:builtin?).returns(false) resources["three"].expects(:evaluated?).returns(true) result = config.send(:unevaluated_resources) %w{one two}.each do |name| assert(result.include?(resources[name]), "Did not find %s in the unevaluated list" % name) end end def test_evaluate_definitions # First try the case where there's nothing to return config = mkconfig config.expects(:unevaluated_resources).returns(nil) assert_nothing_raised("Could not test for unevaluated resources") do assert(! config.send(:evaluate_definitions), "evaluate_definitions returned true when no resources were evaluated") end # Now try it with resources left to evaluate resources = [] res1 = mock("resource1") res1.expects(:evaluate) res2 = mock("resource2") res2.expects(:evaluate) resources << res1 << res2 config = mkconfig config.expects(:unevaluated_resources).returns(resources) assert_nothing_raised("Could not test for unevaluated resources") do assert(config.send(:evaluate_definitions), "evaluate_definitions returned false when resources were evaluated") end end def test_evaluate_generators # First try the case where we have nothing to do config = mkconfig config.expects(:evaluate_definitions).returns(false) config.expects(:evaluate_collections).returns(false) assert_nothing_raised("Could not call :eval_iterate") do config.send(:evaluate_generators) end # FIXME I could not get this test to work, but the code is short # enough that I'm ok with it. # It's important that collections are evaluated before definitions, # so make sure that's the case by verifying that collections get tested # twice but definitions only once. #config = mkconfig #config.expects(:evaluate_collections).returns(true).returns(false) #config.expects(:evaluate_definitions).returns(false) #config.send(:eval_iterate) end def test_store config = mkconfig Puppet.features.expects(:rails?).returns(true) Puppet::Rails.expects(:connect) args = {:name => "yay"} config.expects(:store_to_active_record).with(args) config.send(:store, args) end def test_store_to_active_record config = mkconfig args = {:name => "yay"} Puppet::Rails::Host.stubs(:transaction).yields Puppet::Rails::Host.expects(:store).with(args) config.send(:store_to_active_record, args) end # Make sure that 'finish' gets called on all of our resources. def test_finish config = mkconfig table = config.instance_variable_get("@resource_table") # Add a resource that does respond to :finish yep = mock("finisher") yep.expects(:respond_to?).with(:finish).returns(true) yep.expects(:finish) table["yep"] = yep # And one that does not dnf = mock("dnf") dnf.expects(:respond_to?).with(:finish).returns(false) table["dnf"] = dnf config.send(:finish) end def test_extract config = mkconfig config.expects(:extraction_format).returns(:whatever) config.expects(:extract_to_whatever).returns(:result) assert_equal(:result, config.send(:extract), "Did not return extraction result as the method result") end # We want to make sure that the scope and resource graphs translate correctly def test_extract_to_transportable_simple # Start with a really simple graph -- one scope, one resource. config = mkconfig resources = config.instance_variable_get("@resource_graph") scopes = config.instance_variable_get("@scope_graph") # Get rid of the topscope scopes.vertices.each { |v| scopes.remove_vertex!(v) } scope = mock("scope") scope.expects(:to_trans).returns([]) scopes.add_vertex! scope # The topscope is the key to picking out the top of the graph. config.instance_variable_set("@topscope", scope) resource = mock "resource" resource.expects(:to_trans).returns(:resource) resources.add_edge! scope, resource result = nil assert_nothing_raised("Could not extract transportable configuration") do result = config.send :extract_to_transportable end assert_equal([:resource], result, "Did not translate simple configuration correctly") end def test_extract_to_transportable_complex # Now try it with a more complicated graph -- a three tier graph, each tier # having a scope and a resource. config = mkconfig resources = config.instance_variable_get("@resource_graph") scopes = config.instance_variable_get("@scope_graph") # Get rid of the topscope scopes.vertices.each { |v| scopes.remove_vertex!(v) } fakebucket = Class.new(Array) do attr_accessor :name def initialize(n) @name = n end end # Create our scopes. top = mock("top") top.expects(:to_trans).returns(fakebucket.new("top")) # The topscope is the key to picking out the top of the graph. config.instance_variable_set("@topscope", top) middle = mock("middle") middle.expects(:to_trans).returns(fakebucket.new("middle")) scopes.add_edge! top, middle bottom = mock("bottom") bottom.expects(:to_trans).returns(fakebucket.new("bottom")) scopes.add_edge! middle, bottom topres = mock "topres" topres.expects(:to_trans).returns(:topres) resources.add_edge! top, topres midres = mock "midres" midres.expects(:to_trans).returns(:midres) resources.add_edge! middle, midres botres = mock "botres" botres.expects(:to_trans).returns(:botres) resources.add_edge! bottom, botres result = nil assert_nothing_raised("Could not extract transportable configuration") do result = config.send :extract_to_transportable end assert_equal([[[:botres], :midres], :topres], result, "Did not translate medium configuration correctly") end def test_verify_uniqueness config = mkconfig resources = config.instance_variable_get("@resource_table") resource = mock("noconflict") resource.expects(:ref).returns("File[yay]") assert_nothing_raised("Raised an exception when there should have been no conflict") do config.send(:verify_uniqueness, resource) end # Now try the case where our type is isomorphic resources["thing"] = true isoconflict = mock("isoconflict") isoconflict.expects(:ref).returns("thing") isoconflict.expects(:type).returns("testtype") faketype = mock("faketype") faketype.expects(:isomorphic?).returns(false) faketype.expects(:name).returns("whatever") Puppet::Type.expects(:type).with("testtype").returns(faketype) assert_nothing_raised("Raised an exception when was a conflict in non-isomorphic types") do config.send(:verify_uniqueness, isoconflict) end # Now test for when we actually have an exception initial = mock("initial") resources["thing"] = initial initial.expects(:file).returns(false) conflict = mock("conflict") conflict.expects(:ref).returns("thing").times(2) conflict.expects(:type).returns("conflict") conflict.expects(:file).returns(false) conflict.expects(:line).returns(false) faketype = mock("faketype") faketype.expects(:isomorphic?).returns(true) Puppet::Type.expects(:type).with("conflict").returns(faketype) assert_raise(Puppet::ParseError, "Did not fail when two isomorphic resources conflicted") do config.send(:verify_uniqueness, conflict) end end def test_store_resource # Run once when there's no conflict config = mkconfig table = config.instance_variable_get("@resource_table") resource = mock("resource") resource.expects(:ref).returns("yay") config.expects(:verify_uniqueness).with(resource) scope = mock("scope") graph = config.instance_variable_get("@resource_graph") graph.expects(:add_edge!).with(scope, resource) assert_nothing_raised("Could not store resource") do config.store_resource(scope, resource) end assert_equal(resource, table["yay"], "Did not store resource in table") # Now for conflicts config = mkconfig table = config.instance_variable_get("@resource_table") resource = mock("resource") config.expects(:verify_uniqueness).with(resource).raises(ArgumentError) assert_raise(ArgumentError, "Did not raise uniqueness exception") do config.store_resource(scope, resource) end assert(table.empty?, "Conflicting resource was stored in table") end def test_fail_on_unevaluated config = mkconfig config.expects(:fail_on_unevaluated_overrides) config.expects(:fail_on_unevaluated_resource_collections) config.send :fail_on_unevaluated end def test_store_override # First test the case when the resource is not present. config = mkconfig overrides = config.instance_variable_get("@resource_overrides") override = Object.new override.expects(:ref).returns(:myref).times(2) override.expects(:override=).with(true) assert_nothing_raised("Could not call store_override") do config.store_override(override) end assert_instance_of(Array, overrides[:myref], "Overrides table is not a hash of arrays") assert_equal(override, overrides[:myref][0], "Did not store override in appropriately named array") # And when the resource already exists. resource = mock 'resource' resources = config.instance_variable_get("@resource_table") resources[:resref] = resource override = mock 'override' resource.expects(:merge).with(override) override.expects(:override=).with(true) override.expects(:ref).returns(:resref) assert_nothing_raised("Could not call store_override when the resource already exists.") do config.store_override(override) end end def test_resource_overrides config = mkconfig overrides = config.instance_variable_get("@resource_overrides") overrides[:test] = :yay resource = mock 'resource' resource.expects(:ref).returns(:test) assert_equal(:yay, config.resource_overrides(resource), "Did not return overrides from table") end def test_fail_on_unevaluated_resource_collections config = mkconfig collections = config.instance_variable_get("@collections") # Make sure we're fine when the list is empty assert_nothing_raised("Failed when no collections were present") do config.send :fail_on_unevaluated_resource_collections end # And that we're fine when we've got collections but with no resources collections << mock('coll') collections[0].expects(:resources).returns(nil) assert_nothing_raised("Failed when no resource collections were present") do config.send :fail_on_unevaluated_resource_collections end # But that we do fail when we've got resource collections left. collections.clear # return both an array and a string, because that's tested internally collections << mock('coll returns one') collections[0].expects(:resources).returns(:something) collections << mock('coll returns many') collections[1].expects(:resources).returns([:one, :two]) assert_raise(Puppet::ParseError, "Did not fail on unevaluated resource collections") do config.send :fail_on_unevaluated_resource_collections end end def test_fail_on_unevaluated_overrides config = mkconfig overrides = config.instance_variable_get("@resource_overrides") # Make sure we're fine when the list is empty assert_nothing_raised("Failed when no collections were present") do config.send :fail_on_unevaluated_overrides end # But that we fail if there are any overrides left in the table. overrides[:yay] = [] overrides[:foo] = [] overrides[:bar] = [mock("override")] overrides[:bar][0].expects(:ref).returns("yay") assert_raise(Puppet::ParseError, "Failed to fail when overrides remain") do config.send :fail_on_unevaluated_overrides end end def test_find_resource config = mkconfig resources = config.instance_variable_get("@resource_table") assert_nothing_raised("Could not call findresource when the resource table was empty") do assert_nil(config.findresource("yay", "foo"), "Returned a non-existent resource") assert_nil(config.findresource("yay[foo]"), "Returned a non-existent resource") end resources["Foo[bar]"] = :yay assert_nothing_raised("Could not call findresource when the resource table was not empty") do assert_equal(:yay, config.findresource("foo", "bar"), "Returned a non-existent resource") assert_equal(:yay, config.findresource("Foo[bar]"), "Returned a non-existent resource") end end + + # #620 - Nodes and classes should conflict, else classes don't get evaluated + def test_nodes_and_classes_name_conflict + # Test node then class + config = mkconfig + node = stub :nodescope? => true + klass = stub :nodescope? => false + config.class_set("one", node) + assert_raise(Puppet::ParseError, "Did not fail when replacing node with class") do + config.class_set("one", klass) + end + + # and class then node + config = mkconfig + node = stub :nodescope? => true + klass = stub :nodescope? => false + config.class_set("two", klass) + assert_raise(Puppet::ParseError, "Did not fail when replacing node with class") do + config.class_set("two", node) + end + end end diff --git a/test/language/functions.rb b/test/language/functions.rb index 34207de17..42d8d7585 100755 --- a/test/language/functions.rb +++ b/test/language/functions.rb @@ -1,532 +1,533 @@ #!/usr/bin/env ruby $:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/ require 'puppet' -require 'puppet/parser/interpreter' require 'puppet/parser/parser' require 'puppet/network/client' require 'puppettest' require 'puppettest/resourcetesting' class TestLangFunctions < Test::Unit::TestCase include PuppetTest::ParserTesting include PuppetTest::ResourceTesting def test_functions assert_raise(Puppet::ParseError) do Puppet::Parser::AST::Function.new( :name => "fakefunction", :arguments => AST::ASTArray.new( :children => [nameobj("avalue")] ) ) end assert_nothing_raised do Puppet::Parser::Functions.newfunction(:fakefunction, :type => :rvalue) do |input| return "output %s" % input[0] end end func = nil assert_nothing_raised do func = Puppet::Parser::AST::Function.new( :name => "fakefunction", :ftype => :rvalue, :arguments => AST::ASTArray.new( :children => [nameobj("avalue")] ) ) end scope = mkscope val = nil assert_nothing_raised do val = func.evaluate(:scope => scope) end assert_equal("output avalue", val) end def test_taggedfunction scope = mkscope tag = "yayness" scope.tag(tag) {"yayness" => true, "booness" => false}.each do |tag, retval| func = taggedobj(tag, :rvalue) val = nil assert_nothing_raised do val = func.evaluate(:scope => scope) end assert_equal(retval, val, "'tagged' returned %s for %s" % [val, tag]) end end def test_failfunction func = nil assert_nothing_raised do func = Puppet::Parser::AST::Function.new( :name => "fail", :ftype => :statement, :arguments => AST::ASTArray.new( :children => [stringobj("this is a failure"), stringobj("and another")] ) ) end scope = mkscope val = nil assert_raise(Puppet::ParseError) do val = func.evaluate(:scope => scope) end end def test_multipletemplates Dir.mkdir(Puppet[:templatedir]) onep = File.join(Puppet[:templatedir], "one") twop = File.join(Puppet[:templatedir], "two") File.open(onep, "w") do |f| f.puts "template <%= one %>" end File.open(twop, "w") do |f| f.puts "template <%= two %>" end func = nil assert_nothing_raised do func = Puppet::Parser::AST::Function.new( :name => "template", :ftype => :rvalue, :arguments => AST::ASTArray.new( :children => [stringobj("one"), stringobj("two")] ) ) end ast = varobj("output", func) scope = mkscope assert_raise(Puppet::ParseError) do ast.evaluate(:scope => scope) end scope.setvar("one", "One") assert_raise(Puppet::ParseError) do ast.evaluate(:scope => scope) end scope.setvar("two", "Two") assert_nothing_raised do ast.evaluate(:scope => scope) end assert_equal("template One\ntemplate Two\n", scope.lookupvar("output"), "Templates were not handled correctly") end # Now make sure we can fully qualify files, and specify just one def test_singletemplates template = tempfile() File.open(template, "w") do |f| f.puts "template <%= yayness %>" end func = nil assert_nothing_raised do func = Puppet::Parser::AST::Function.new( :name => "template", :ftype => :rvalue, :arguments => AST::ASTArray.new( :children => [stringobj(template)] ) ) end ast = varobj("output", func) scope = mkscope assert_raise(Puppet::ParseError) do ast.evaluate(:scope => scope) end scope.setvar("yayness", "this is yayness") assert_nothing_raised do ast.evaluate(:scope => scope) end assert_equal("template this is yayness\n", scope.lookupvar("output"), "Templates were not handled correctly") end def test_tempatefunction_cannot_see_scopes template = tempfile() File.open(template, "w") do |f| f.puts "<%= lookupvar('myvar') %>" end func = nil assert_nothing_raised do func = Puppet::Parser::AST::Function.new( :name => "template", :ftype => :rvalue, :arguments => AST::ASTArray.new( :children => [stringobj(template)] ) ) end ast = varobj("output", func) scope = mkscope scope.setvar("myvar", "this is yayness") assert_raise(Puppet::ParseError) do ast.evaluate(:scope => scope) end end def test_template_reparses template = tempfile() File.open(template, "w") do |f| f.puts "original text" end manifest = tempfile() file = tempfile() File.open(manifest, "w") do |f| f.puts %{file { "#{file}": content => template("#{template}") }} end - interpreter = Puppet::Parser::Interpreter.new( + interp = Puppet::Parser::Interpreter.new( :Manifest => manifest, :UseNodes => false ) + node = mknode - parsedate = interpreter.parsedate() + parsedate = interp.parsedate() objects = nil assert_nothing_raised { - objects = interpreter.run("myhost", {}) + objects = interp.compile(node) } fileobj = objects[0] assert_equal("original text\n", fileobj["content"], "Template did not work") - Puppet[:filetimeout] = 0 + Puppet[:filetimeout] = -5 # Have to sleep because one second is the fs's time granularity. sleep(1) # Now modify the template File.open(template, "w") do |f| f.puts "new text" end assert_nothing_raised { - objects = interpreter.run("myhost", {}) + objects = interp.compile(node) } - newdate = interpreter.parsedate() + newdate = interp.parsedate() assert(parsedate != newdate, "Parse date did not change") end def test_template_defined_vars template = tempfile() File.open(template, "w") do |f| f.puts "template <%= yayness %>" end func = nil assert_nothing_raised do func = Puppet::Parser::AST::Function.new( :name => "template", :ftype => :rvalue, :arguments => AST::ASTArray.new( :children => [stringobj(template)] ) ) end ast = varobj("output", func) { "" => "", false => "false", }.each do |string, value| scope = mkscope assert_raise(Puppet::ParseError) do ast.evaluate(:scope => scope) end scope.setvar("yayness", string) assert_equal(string, scope.lookupvar("yayness", false)) assert_nothing_raised("An empty string was not a valid variable value") do ast.evaluate(:scope => scope) end assert_equal("template #{value}\n", scope.lookupvar("output"), "%s did not get evaluated correctly" % string.inspect) end end def test_autoloading_functions assert_equal(false, Puppet::Parser::Functions.function(:autofunc), "Got told autofunc already exists") dir = tempfile() $: << dir newpath = File.join(dir, "puppet", "parser", "functions") FileUtils.mkdir_p(newpath) File.open(File.join(newpath, "autofunc.rb"), "w") { |f| f.puts %{ Puppet::Parser::Functions.newfunction(:autofunc, :type => :rvalue) do |vals| Puppet.wanring vals.inspect end } } obj = nil assert_nothing_raised { obj = Puppet::Parser::Functions.function(:autofunc) } assert(obj, "Did not autoload function") assert(Puppet::Parser::Scope.method_defined?(:function_autofunc), "Did not set function correctly") end def test_realize - @interp, @scope, @source = mkclassframing + scope = mkscope + parser = scope.configuration.parser # Make a definition - @interp.newdefine("mytype") + 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.setresource virtual + scope.setresource virtual ref = Puppet::Parser::Resource::Reference.new( :type => type, :title => title, - :scope => @scope + :scope => scope ) # Now call the realize function assert_nothing_raised do - @scope.function_realize(ref) + scope.function_realize(ref) end # Make sure it created a collection - assert_equal(1, @scope.collections.length, + assert_equal(1, scope.configuration.collections.length, "Did not set collection") assert_nothing_raised do - @scope.collections.each do |coll| coll.evaluate end + scope.configuration.collections.each do |coll| coll.evaluate end end - @scope.collections.clear + scope.configuration.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 + :scope => scope ) # The function works assert_nothing_raised do - @scope.function_realize(none.to_s) + scope.function_realize(none.to_s) end # Make sure it created a collection - assert_equal(1, @scope.collections.length, + assert_equal(1, scope.configuration.collections.length, "Did not set collection") # And the collection has our resource in it - assert_equal([none.to_s], @scope.collections[0].resources, + assert_equal([none.to_s], scope.configuration.collections[0].resources, "Did not set resources in collection") end def test_defined - interp = mkinterp - scope = mkscope(:interp => interp) + scope = mkscope + parser = scope.configuration.parser - interp.newclass("yayness") - interp.newdefine("rahness") + 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.setresource 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 - interp = mkinterp - scope = mkscope(:interp => interp) + parser = mkparser + scope = mkscope(:parser => parser) - fun = interp.newdefine("yay::ness") - foo = interp.newdefine("foo::bar") + 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 - interp = mkinterp - scope = mkscope(:interp => interp) + scope = mkscope + parser = scope.configuration.parser assert_raise(Puppet::ParseError, "did not throw error on missing class") do scope.function_include("nosuchclass") end - interp.newclass("myclass") + parser.newclass("myclass") assert_nothing_raised do scope.function_include "myclass" end - assert(scope.classlist.include?("myclass"), + assert(scope.configuration.classlist.include?("myclass"), "class was not evaluated") # Now try multiple classes at once - classes = %w{one two three}.each { |c| interp.newclass(c) } + classes = %w{one two three}.each { |c| parser.newclass(c) } assert_nothing_raised do scope.function_include classes end classes.each do |c| - assert(scope.classlist.include?(c), + assert(scope.configuration.classlist.include?(c), "class %s was not evaluated" % c) end # Now try a scoped class - interp.newclass("os::redhat") + parser.newclass("os::redhat") assert_nothing_raised("Could not include qualified class name") do scope.function_include("os::redhat") end end def test_file - interp = mkinterp - scope = mkscope(:interp => interp) + 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") - interp = mkinterp - scope = mkscope(:interp => interp) + scope = mkscope + parser = scope.configuration.parser val = nil assert_nothing_raised("Could not call generator with no args") do val = scope.function_generate([command]) end assert_equal("yay\n", val, "generator returned wrong results") assert_nothing_raised("Could not call generator with args") do val = scope.function_generate([command, "foo"]) end assert_equal("yay-foo\n", val, "generator returned wrong results") assert_raise(Puppet::ParseError, "Did not fail with an unqualified path") do val = scope.function_generate([File.basename(command), "foo"]) end assert_raise(Puppet::ParseError, "Did not fail when command failed") do val = scope.function_generate([%x{which touch}.chomp, "/this/dir/does/not/exist"]) end fake = File.join(File.dirname(command), "..") dir = File.dirname(command) dirname = File.basename(dir) bad = File.join(dir, "..", dirname, File.basename(command)) assert_raise(Puppet::ParseError, "Did not fail when command failed") do val = scope.function_generate([bad]) end end end # $Id$ diff --git a/test/language/interpreter.rb b/test/language/interpreter.rb index 6b9aa7258..ebbc3f87f 100755 --- a/test/language/interpreter.rb +++ b/test/language/interpreter.rb @@ -1,393 +1,152 @@ #!/usr/bin/env ruby $:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/ require 'facter' require 'puppet' require 'puppet/parser/interpreter' require 'puppet/parser/parser' require 'puppet/network/client' require 'puppettest' require 'puppettest/resourcetesting' require 'puppettest/parsertesting' require 'puppettest/servertest' require 'timeout' class TestInterpreter < PuppetTest::TestCase include PuppetTest include PuppetTest::ServerTest include PuppetTest::ParserTesting include PuppetTest::ResourceTesting AST = Puppet::Parser::AST # create a simple manifest that uses nodes to create a file def mknodemanifest(node, file) createdfile = tempfile() File.open(file, "w") { |f| f.puts "node %s { file { \"%s\": ensure => file, mode => 755 } }\n" % [node, createdfile] } return [file, createdfile] end def test_reloadfiles node = mknode(Facter["hostname"].value) file = tempfile() # Create a first version createdfile = mknodemanifest(node.name, file) interp = nil assert_nothing_raised { interp = Puppet::Parser::Interpreter.new(:Manifest => file) } config = nil assert_nothing_raised { config = interp.compile(node) } Puppet[:filetimeout] = -5 # Now create a new file createdfile = mknodemanifest(node.name, file) newconfig = nil assert_nothing_raised { newconfig = interp.compile(node) } assert(config != newconfig, "Configs are somehow the same") end def test_parsedate Puppet[:filetimeout] = 0 main = tempfile() sub = tempfile() mainfile = tempfile() subfile = tempfile() count = 0 updatemain = proc do count += 1 File.open(main, "w") { |f| f.puts "import '#{sub}' file { \"#{mainfile}\": content => #{count} } " } end updatesub = proc do count += 1 File.open(sub, "w") { |f| f.puts "file { \"#{subfile}\": content => #{count} } " } end updatemain.call updatesub.call interp = Puppet::Parser::Interpreter.new( :Manifest => main, :Local => true ) date = interp.parsedate # Now update the site file and make sure we catch it sleep 1 updatemain.call newdate = interp.parsedate assert(date != newdate, "Parsedate was not updated") date = newdate # And then the subfile sleep 1 updatesub.call newdate = interp.parsedate assert(date != newdate, "Parsedate was not updated") end # Make sure our whole chain works. - def test_evaluate - interp, scope, source = mkclassframing - - # Create a define that we'll be using - interp.newdefine("wrapper", :code => AST::ASTArray.new(:children => [ - resourcedef("file", varref("name"), "owner" => "root") - ])) - - # Now create a resource that uses that define - define = mkresource(:type => "wrapper", :title => "/tmp/testing", - :scope => scope, :source => source, :params => :none) - - scope.setresource define - - # And a normal resource - scope.setresource mkresource(:type => "file", :title => "/tmp/rahness", - :scope => scope, :source => source, - :params => {:owner => "root"}) - - # Now evaluate everything - objects = nil - interp.usenodes = false - assert_nothing_raised do - objects = interp.evaluate(nil, {}) - end - - assert_instance_of(Puppet::TransBucket, objects) - end - - # Test evaliterate. It's a very simple method, but it's pretty tough - # to test. It iterates over collections and instances of defined types - # until there's no more work to do. - def test_evaliterate - interp, scope, source = mkclassframing - - # Create a top-level definition that creates a builtin object - interp.newdefine("one", :arguments => [%w{owner}], - :code => AST::ASTArray.new(:children => [ - resourcedef("file", varref("name"), - "owner" => varref("owner") - ) - ]) - ) - - # Create another definition to call that one - interp.newdefine("two", :arguments => [%w{owner}], - :code => AST::ASTArray.new(:children => [ - resourcedef("one", varref("name"), - "owner" => varref("owner") - ) - ]) - ) - - # And then a third - interp.newdefine("three", :arguments => [%w{owner}], - :code => AST::ASTArray.new(:children => [ - resourcedef("two", varref("name"), - "owner" => varref("owner") - ) - ]) - ) - - # And create a definition that creates a virtual resource - interp.newdefine("virtualizer", :arguments => [%w{owner}], - :code => AST::ASTArray.new(:children => [ - virt_resourcedef("one", varref("name"), - "owner" => varref("owner") - ) - ]) - ) - - # Now create an instance of three - three = Puppet::Parser::Resource.new( - :type => "three", :title => "one", - :scope => scope, :source => source, - :params => paramify(source, :owner => "root") - ) - scope.setresource(three) - - # An instance of the virtualizer - virt = Puppet::Parser::Resource.new( - :type => "virtualizer", :title => "two", - :scope => scope, :source => source, - :params => paramify(source, :owner => "root") - ) - scope.setresource(virt) - - # And a virtual instance of three - virt_three = Puppet::Parser::Resource.new( - :type => "three", :title => "three", - :scope => scope, :source => source, - :params => paramify(source, :owner => "root") - ) - virt_three.virtual = true - scope.setresource(virt_three) - - # Create a normal, virtual resource - plainvirt = Puppet::Parser::Resource.new( - :type => "user", :title => "five", - :scope => scope, :source => source, - :params => paramify(source, :uid => "root") - ) - plainvirt.virtual = true - scope.setresource(plainvirt) - - # Now create some collections for our virtual resources - %w{Three[three] One[two]}.each do |ref| - coll = Puppet::Parser::Collector.new(scope, "file", nil, nil, :virtual) - coll.resources = [ref] - scope.newcollection(coll) - end - - # And create a generic user collector for our plain resource - coll = Puppet::Parser::Collector.new(scope, "user", nil, nil, :virtual) - scope.newcollection(coll) - - ret = nil - assert_nothing_raised do - ret = scope.unevaluated - end - - - assert_instance_of(Array, ret) - assert_equal(3, ret.length, - "did not get the correct number of unevaled resources") - - # Now translate the whole tree - assert_nothing_raised do - Timeout::timeout(2) do - interp.evaliterate(scope) - end - end - - # Now make sure we've got all of our files - %w{one two three}.each do |name| - file = scope.findresource("File[%s]" % name) - assert(file, "Could not find file %s" % name) - - assert_equal("root", file[:owner]) - assert(! file.virtual?, "file %s is still virtual" % name) - end - - # Now make sure we found the user - assert(! plainvirt.virtual?, "user was not realized") - end - - # Make sure we fail if there are any leftover overrides to perform. - # This would normally mean that someone is trying to override an object - # that does not exist. - def test_failonleftovers - interp, scope, source = mkclassframing - - # Make sure we don't fail, since there are no overrides - assert_nothing_raised do - interp.failonleftovers(scope) - end - - # Add an override, and make sure it causes a failure - over1 = mkresource :scope => scope, :source => source, - :params => {:one => "yay"} - - scope.setoverride(over1) - - assert_raise(Puppet::ParseError) do - interp.failonleftovers(scope) - end - - # Make a new scope to test leftover collections - scope = mkscope :interp => interp - interp.meta_def(:check_resource_collections) do - raise ArgumentError, "yep" - end - - assert_raise(ArgumentError, "did not call check_resource_colls") do - interp.failonleftovers(scope) - end - end - - def test_evalnode + def test_compile interp = mkinterp - interp.usenodes = false - scope = Parser::Scope.new(:interp => interp) - facts = Facter.to_hash - - # First make sure we get no failures when client is nil - assert_nothing_raised do - interp.evalnode(nil, scope, facts) - end - - # Now define a node - interp.newnode "mynode", :code => AST::ASTArray.new(:children => [ - resourcedef("file", "/tmp/testing", "owner" => "root") - ]) - - # Eval again, and make sure it does nothing - assert_nothing_raised do - interp.evalnode("mynode", scope, facts) - end - - assert_nil(scope.findresource("File[/tmp/testing]"), - "Eval'ed node with nodes off") - - # Now enable usenodes and make sure it works. - interp.usenodes = true - assert_nothing_raised do - interp.evalnode("mynode", scope, facts) - end - file = scope.findresource("File[/tmp/testing]") - - assert_instance_of(Puppet::Parser::Resource, file, - "Could not find file") - end - - # This is mostly used for the cfengine module - def test_specificclasses - interp = mkinterp :Classes => %w{klass1 klass2}, :UseNodes => false - - # Make sure it's not a failure to be missing classes, since - # we're using the cfengine class list, which is huge. - assert_nothing_raised do - interp.evaluate(nil, {}) - end - - interp.newclass("klass1", :code => AST::ASTArray.new(:children => [ - resourcedef("file", "/tmp/klass1", "owner" => "root") - ])) - interp.newclass("klass2", :code => AST::ASTArray.new(:children => [ - resourcedef("file", "/tmp/klass2", "owner" => "root") - ])) - - ret = nil - assert_nothing_raised do - ret = interp.evaluate(nil, {}) - end - - found = ret.flatten.collect do |res| res.name end - - assert(found.include?("/tmp/klass1"), "Did not evaluate klass1") - assert(found.include?("/tmp/klass2"), "Did not evaluate klass2") - end - - def test_check_resource_collections - interp = mkinterp - scope = mkscope :interp => interp - coll = Puppet::Parser::Collector.new(scope, "file", nil, nil, :virtual) - coll.resources = ["File[/tmp/virtual1]", "File[/tmp/virtual2]"] - scope.newcollection(coll) - - assert_raise(Puppet::ParseError, "Did not fail on remaining resource colls") do - interp.check_resource_collections(scope) - end + interp.expects(:parsefiles) + parser = interp.instance_variable_get("@parser") + + node = mock('node') + config = mock('config') + config.expects(:compile).returns(:config) + Puppet::Parser::Configuration.expects(:new).with(node, parser).returns(config) + assert_equal(:config, interp.compile(node), "Did not return the results of config.compile") end # Make sure that reparsing is atomic -- failures don't cause a broken state, and we aren't subject # to race conditions if someone contacts us while we're reparsing. def test_atomic_reparsing Puppet[:filetimeout] = -10 file = tempfile File.open(file, "w") { |f| f.puts %{file { '/tmp': ensure => directory }} } interp = mkinterp :Manifest => file, :UseNodes => false assert_nothing_raised("Could not compile the first time") do - interp.run("yay", {}) + interp.compile(mknode("yay")) end oldparser = interp.send(:instance_variable_get, "@parser") # Now add a syntax failure File.open(file, "w") { |f| f.puts %{file { /tmp: ensure => directory }} } assert_nothing_raised("Could not compile the first time") do - interp.run("yay", {}) + interp.compile(mknode("yay")) end # And make sure the old parser is still there newparser = interp.send(:instance_variable_get, "@parser") assert_equal(oldparser.object_id, newparser.object_id, "Failed parser still replaced existing parser") end end # $Id$ diff --git a/test/language/parser.rb b/test/language/parser.rb index 77595b155..c172aafca 100755 --- a/test/language/parser.rb +++ b/test/language/parser.rb @@ -1,1197 +1,1197 @@ #!/usr/bin/env ruby $:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/ require 'mocha' require 'puppet' require 'puppet/parser/parser' require 'puppettest' class TestParser < Test::Unit::TestCase include PuppetTest::ParserTesting def setup super Puppet[:parseonly] = true #@lexer = Puppet::Parser::Lexer.new() end def test_each_file textfiles { |file| parser = mkparser Puppet.debug("parsing %s" % file) if __FILE__ == $0 assert_nothing_raised() { parser.file = file parser.parse } Puppet::Type.eachtype { |type| type.each { |obj| assert(obj.file, "File is not set on %s" % obj.ref) assert(obj.name, "Name is not set on %s" % obj.ref) assert(obj.line, "Line is not set on %s" % obj.ref) } } Puppet::Type.allclear } end def test_failers failers { |file| parser = mkparser - interp = mkinterp Puppet.debug("parsing failer %s" % file) if __FILE__ == $0 - assert_raise(Puppet::ParseError) { + assert_raise(Puppet::ParseError, "Did not fail while parsing %s" % file) { parser.file = file ast = parser.parse - scope = mkscope :interp => interp - ast.classes[""].evaluate :scope => scope + config = mkconfig(parser) + config.compile + #ast.classes[""].evaluate :scope => config.topscope } Puppet::Type.allclear } end def test_arrayrvalues parser = mkparser ret = nil file = tempfile() assert_nothing_raised { parser.string = "file { \"#{file}\": mode => [755, 640] }" } assert_nothing_raised { ret = parser.parse } end def mkmanifest(file) name = File.join(tmpdir, "file%s" % rand(100)) @@tmpfiles << name File.open(file, "w") { |f| f.puts "file { \"%s\": ensure => file, mode => 755 }\n" % name } end def test_importglobbing basedir = File.join(tmpdir(), "importesting") @@tmpfiles << basedir Dir.mkdir(basedir) subdir = "subdir" Dir.mkdir(File.join(basedir, subdir)) manifest = File.join(basedir, "manifest") File.open(manifest, "w") { |f| f.puts "import \"%s/*\"" % subdir } 4.times { |i| path = File.join(basedir, subdir, "subfile%s" % i) mkmanifest(path) } assert_nothing_raised("Could not parse multiple files") { parser = mkparser parser.file = manifest parser.parse } end def test_nonexistent_import basedir = File.join(tmpdir(), "importesting") @@tmpfiles << basedir Dir.mkdir(basedir) manifest = File.join(basedir, "manifest") File.open(manifest, "w") do |f| f.puts "import \" no such file \"" end assert_raise(Puppet::ParseError) { parser = mkparser parser.file = manifest parser.parse } end def test_trailingcomma path = tempfile() str = %{file { "#{path}": ensure => file, } } parser = mkparser parser.string = str assert_nothing_raised("Could not parse trailing comma") { parser.parse } end def test_importedclasses imported = tempfile() importer = tempfile() made = tempfile() File.open(imported, "w") do |f| f.puts %{class foo { file { "#{made}": ensure => file }}} end File.open(importer, "w") do |f| f.puts %{import "#{imported}"\ninclude foo} end parser = mkparser parser.file = importer # Make sure it parses fine assert_nothing_raised { parser.parse } # Now make sure it actually does the work assert_creates(importer, made) end # Make sure fully qualified and unqualified files can be imported def test_fqfilesandlocalfiles dir = tempfile() Dir.mkdir(dir) importer = File.join(dir, "site.pp") fullfile = File.join(dir, "full.pp") localfile = File.join(dir, "local.pp") files = [] File.open(importer, "w") do |f| f.puts %{import "#{fullfile}"\ninclude full\nimport "local.pp"\ninclude local} end fullmaker = tempfile() files << fullmaker File.open(fullfile, "w") do |f| f.puts %{class full { file { "#{fullmaker}": ensure => file }}} end localmaker = tempfile() files << localmaker File.open(localfile, "w") do |f| f.puts %{class local { file { "#{localmaker}": ensure => file }}} end parser = mkparser parser.file = importer # Make sure it parses assert_nothing_raised { parser.parse } # Now make sure it actually does the work assert_creates(importer, *files) end # Make sure the parser adds '.pp' when necessary def test_addingpp dir = tempfile() Dir.mkdir(dir) importer = File.join(dir, "site.pp") localfile = File.join(dir, "local.pp") files = [] File.open(importer, "w") do |f| f.puts %{import "local"\ninclude local} end file = tempfile() files << file File.open(localfile, "w") do |f| f.puts %{class local { file { "#{file}": ensure => file }}} end parser = mkparser parser.file = importer assert_nothing_raised { parser.parse } end # Make sure that file importing changes file relative names. def test_changingrelativenames dir = tempfile() Dir.mkdir(dir) Dir.mkdir(File.join(dir, "subdir")) top = File.join(dir, "site.pp") subone = File.join(dir, "subdir/subone") subtwo = File.join(dir, "subdir/subtwo") files = [] file = tempfile() files << file File.open(subone + ".pp", "w") do |f| f.puts %{class one { file { "#{file}": ensure => file }}} end otherfile = tempfile() files << otherfile File.open(subtwo + ".pp", "w") do |f| f.puts %{import "subone"\n class two inherits one { file { "#{otherfile}": ensure => file } }} end File.open(top, "w") do |f| f.puts %{import "subdir/subtwo"} end parser = mkparser parser.file = top assert_nothing_raised { parser.parse } end # Defaults are purely syntactical, so it doesn't make sense to be able to # collect them. def test_uncollectabledefaults string = "@Port { protocols => tcp }" assert_raise(Puppet::ParseError) { mkparser.parse(string) } end # Verify that we can parse collections def test_collecting text = "Port <| |>" parser = mkparser parser.string = text ret = nil assert_nothing_raised { ret = parser.parse } ret.classes[""].code.each do |obj| assert_instance_of(AST::Collection, obj) end end def test_emptyfile file = tempfile() File.open(file, "w") do |f| f.puts %{} end parser = mkparser parser.file = file assert_nothing_raised { parser.parse } end def test_multiple_nodes_named file = tempfile() other = tempfile() File.open(file, "w") do |f| f.puts %{ node nodeA, nodeB { file { "#{other}": ensure => file } } } end parser = mkparser parser.file = file ast = nil assert_nothing_raised { ast = parser.parse } end def test_emptyarrays str = %{$var = []\n} parser = mkparser parser.string = str # Make sure it parses fine assert_nothing_raised { parser.parse } end # Make sure function names aren't reserved words. def test_functionnamecollision str = %{tag yayness tag(rahness) file { "/tmp/yayness": tag => "rahness", ensure => exists } } parser = mkparser parser.string = str # Make sure it parses fine assert_nothing_raised { parser.parse } end def test_metaparams_in_definition_prototypes parser = mkparser assert_raise(Puppet::ParseError) { parser.parse %{define mydef($schedule) {}} } assert_nothing_raised { parser.parse %{define adef($schedule = false) {}} parser.parse %{define mydef($schedule = daily) {}} } end def test_parsingif parser = mkparser exec = proc do |val| %{exec { "/bin/echo #{val}": logoutput => true }} end str1 = %{if true { #{exec.call("true")} }} ret = nil assert_nothing_raised { ret = parser.parse(str1).classes[""].code[0] } assert_instance_of(Puppet::Parser::AST::IfStatement, ret) parser.clear str2 = %{if true { #{exec.call("true")} } else { #{exec.call("false")} }} assert_nothing_raised { ret = parser.parse(str2).classes[""].code[0] } assert_instance_of(Puppet::Parser::AST::IfStatement, ret) assert_instance_of(Puppet::Parser::AST::Else, ret.else) end def test_hostclass parser = mkparser assert_nothing_raised { parser.parse %{class myclass { class other {} }} } assert(parser.classes["myclass"], "Could not find myclass") assert(parser.classes["myclass::other"], "Could not find myclass::other") assert_nothing_raised { parser.parse "class base {} class container { class deep::sub inherits base {} }" } sub = parser.classes["container::deep::sub"] assert(sub, "Could not find sub") # Now try it with a parent class being a fq class assert_nothing_raised { parser.parse "class container::one inherits container::deep::sub {}" } sub = parser.classes["container::one"] assert(sub, "Could not find one") assert_equal("container::deep::sub", sub.parentclass) # Finally, try including a qualified class assert_nothing_raised("Could not include fully qualified class") { parser.parse "include container::deep::sub" } end def test_topnamespace parser = mkparser # Make sure we put the top-level code into a class called "" in # the "" namespace assert_nothing_raised do out = parser.parse "" assert_instance_of(Puppet::Parser::Parser::ASTSet, out) assert_nil(parser.classes[""], "Got a 'main' class when we had no code") end # Now try something a touch more complicated parser.initvars assert_nothing_raised do out = parser.parse "Exec { path => '/usr/bin:/usr/sbin' }" assert_instance_of(Puppet::Parser::Parser::ASTSet, out) assert_equal("", parser.classes[""].classname) assert_equal("", parser.classes[""].namespace) end end # Make sure virtual and exported resources work appropriately. def test_virtualresources tests = [:virtual] if Puppet.features.rails? Puppet[:storeconfigs] = true tests << :exported end tests.each do |form| parser = mkparser if form == :virtual at = "@" else at = "@@" end check = proc do |res, msg| if res.is_a?(Puppet::Parser::Resource) txt = res.ref else txt = res.class end # Real resources get marked virtual when exported if form == :virtual or res.is_a?(Puppet::Parser::Resource) assert(res.virtual, "#{msg} #{at}#{txt} is not virtual") end if form == :virtual assert(! res.exported, "#{msg} #{at}#{txt} is exported") else assert(res.exported, "#{msg} #{at}#{txt} is not exported") end end ret = nil assert_nothing_raised do ret = parser.parse("#{at}file { '/tmp/testing': owner => root }") end assert_instance_of(AST::ASTArray, ret.classes[""].code) resdef = ret.classes[""].code[0] assert_instance_of(AST::ResourceDef, resdef) assert_equal("/tmp/testing", resdef.title.value) # We always get an astarray back, so... check.call(resdef, "simple resource") # Now let's try it with multiple resources in the same spec assert_nothing_raised do ret = parser.parse("#{at}file { ['/tmp/1', '/tmp/2']: owner => root }") end ret.classes[""].each do |res| assert_instance_of(AST::ResourceDef, res) check.call(res, "multiresource") end # Now evaluate these scope = mkscope klass = parser.newclass "" scope.source = klass assert_nothing_raised do ret.classes[""].evaluate :scope => scope end # Make sure we can find both of them %w{/tmp/1 /tmp/2}.each do |title| res = scope.findresource("File[#{title}]") assert(res, "Could not find %s" % title) check.call(res, "found multiresource") end end end def test_collections tests = [:virtual] if Puppet.features.rails? Puppet[:storeconfigs] = true tests << :exported end tests.each do |form| parser = mkparser if form == :virtual arrow = "<||>" else arrow = "<<||>>" end ret = nil assert_nothing_raised do ret = parser.parse("File #{arrow}") end coll = ret.classes[""].code[0] assert_instance_of(AST::Collection, coll) assert_equal(form, coll.form) end end def test_collectionexpressions %w{== !=}.each do |oper| str = "File <| title #{oper} '/tmp/testing' |>" parser = mkparser res = nil assert_nothing_raised do res = parser.parse(str).classes[""].code[0] end assert_instance_of(AST::Collection, res) query = res.query assert_instance_of(AST::CollExpr, query) assert_equal(:virtual, query.form) assert_equal("title", query.test1.value) assert_equal("/tmp/testing", query.test2.value) assert_equal(oper, query.oper) end end def test_collectionstatements %w{and or}.each do |joiner| str = "File <| title == '/tmp/testing' #{joiner} owner == root |>" parser = mkparser res = nil assert_nothing_raised do res = parser.parse(str).classes[""].code[0] end assert_instance_of(AST::Collection, res) query = res.query assert_instance_of(AST::CollExpr, query) assert_equal(joiner, query.oper) assert_instance_of(AST::CollExpr, query.test1) assert_instance_of(AST::CollExpr, query.test2) end end def test_collectionstatements_with_parens [ "(title == '/tmp/testing' and owner == root) or owner == wheel", "(title == '/tmp/testing')" ].each do |test| str = "File <| #{test} |>" parser = mkparser res = nil assert_nothing_raised("Could not parse '#{test}'") do res = parser.parse(str).classes[""].code[0] end assert_instance_of(AST::Collection, res) query = res.query assert_instance_of(AST::CollExpr, query) #assert_equal(joiner, query.oper) #assert_instance_of(AST::CollExpr, query.test1) #assert_instance_of(AST::CollExpr, query.test2) end end # We've had problems with files other than site.pp importing into main. def test_importing_into_main top = tempfile() other = tempfile() File.open(top, "w") do |f| f.puts "import '#{other}'" end file = tempfile() File.open(other, "w") do |f| f.puts "file { '#{file}': ensure => present }" end interp = mkinterp :Manifest => top, :UseNodes => false code = nil assert_nothing_raised do - code = interp.run("hostname.domain.com", {}).flatten + code = interp.compile(mknode).flatten end assert(code.length == 1, "Did not get the file") assert_instance_of(Puppet::TransObject, code[0]) end def test_fully_qualified_definitions parser = mkparser assert_nothing_raised("Could not parse fully-qualified definition") { parser.parse %{define one::two { }} } assert(parser.definitions["one::two"], "Could not find one::two with no namespace") # Now try using the definition assert_nothing_raised("Could not parse fully-qualified definition usage") { parser.parse %{one::two { yayness: }} } end # #524 def test_functions_with_no_arguments parser = mkparser assert_nothing_raised("Could not parse statement function with no args") { parser.parse %{tag()} } assert_nothing_raised("Could not parse rvalue function with no args") { parser.parse %{$testing = template()} } end def test_module_import basedir = File.join(tmpdir(), "module-import") @@tmpfiles << basedir Dir.mkdir(basedir) modfiles = [ "init.pp", "mani1.pp", "mani2.pp", "sub/smani1.pp", "sub/smani2.pp" ] modpath = File.join(basedir, "modules") Puppet[:modulepath] = modpath modname = "amod" manipath = File::join(modpath, modname, Puppet::Module::MANIFESTS) FileUtils::mkdir_p(File::join(manipath, "sub")) targets = [] modfiles.each do |fname| target = File::join(basedir, File::basename(fname, '.pp')) targets << target txt = %[ file { '#{target}': content => "#{fname}" } ] if fname == "init.pp" txt = %[import 'mani1' \nimport '#{modname}/mani2'\nimport '#{modname}/sub/*.pp' ] + txt end File::open(File::join(manipath, fname), "w") do |f| f.puts txt end end manifest_texts = [ "import '#{modname}'", "import '#{modname}/init'", "import '#{modname}/init.pp'" ] manifest = File.join(modpath, "manifest.pp") manifest_texts.each do |txt| File.open(manifest, "w") { |f| f.puts txt } assert_nothing_raised { parser = mkparser parser.file = manifest parser.parse } assert_creates(manifest, *targets) end end # #544 def test_ignoreimports parser = mkparser assert(! Puppet[:ignoreimport], ":ignoreimport defaulted to true") assert_raise(Puppet::ParseError, "Did not fail on missing import") do parser.parse("import 'nosuchfile'") end assert_nothing_raised("could not set :ignoreimport") do Puppet[:ignoreimport] = true end assert_nothing_raised("Parser did not follow :ignoreimports") do parser.parse("import 'nosuchfile'") end end def test_multiple_imports_on_one_line one = tempfile two = tempfile base = tempfile File.open(one, "w") { |f| f.puts "$var = value" } File.open(two, "w") { |f| f.puts "$var = value" } File.open(base, "w") { |f| f.puts "import '#{one}', '#{two}'" } parser = mkparser parser.file = base # Importing is logged at debug time. Puppet::Util::Log.level = :debug assert_nothing_raised("Parser could not import multiple files at once") do parser.parse end [one, two].each do |file| assert(@logs.detect { |l| l.message =~ /importing '#{file}'/}, "did not import %s" % file) end end def test_cannot_assign_qualified_variables parser = mkparser assert_raise(Puppet::ParseError, "successfully assigned a qualified variable") do parser.parse("$one::two = yay") end end # #588 def test_globbing_with_directories dir = tempfile Dir.mkdir(dir) subdir = File.join(dir, "subdir") Dir.mkdir(subdir) file = File.join(dir, "file.pp") maker = tempfile File.open(file, "w") { |f| f.puts "file { '#{maker}': ensure => file }" } parser = mkparser assert_nothing_raised("Globbing failed when it matched a directory") do parser.import("%s/*" % dir) end end # #629 - undef keyword def test_undef parser = mkparser result = nil assert_nothing_raised("Could not parse assignment to undef") { result = parser.parse %{$variable = undef} } main = result.classes[""].code children = main.children assert_instance_of(AST::VarDef, main.children[0]) assert_instance_of(AST::Undef, main.children[0].value) end # Prompted by #729 -- parsing should not modify the interpreter. def test_parse parser = mkparser str = "file { '/tmp/yay': ensure => file }\nclass yay {}\nnode foo {}\ndefine bar {}\n" result = nil assert_nothing_raised("Could not parse") do result = parser.parse(str) end assert_instance_of(Puppet::Parser::Parser::ASTSet, result, "Did not get a ASTSet back from parsing") assert_instance_of(AST::HostClass, result.classes["yay"], "Did not create 'yay' class") assert_instance_of(AST::HostClass, result.classes[""], "Did not create main class") assert_instance_of(AST::Component, 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 - parser = mkparser + scope = mkscope + parser = scope.configuration.parser mkcode = proc do |ary| classes = ary.collect do |string| AST::FlatString.new(:value => string) end AST::ASTArray.new(:children => classes) end - scope = Puppet::Parser::Scope.new(:interp => mkinterp) # First make sure that code is being appended code = mkcode.call(%w{original code}) klass = nil assert_nothing_raised { klass = parser.newclass("myclass", :code => code) } assert(klass, "Did not return class") assert(parser.classes["myclass"], "Could not find definition") assert_equal("myclass", parser.classes["myclass"].classname) assert_equal(%w{original code}, parser.classes["myclass"].code.evaluate(:scope => scope)) # Newclass behaves differently than the others -- it just appends # the code to the existing class. code = mkcode.call(%w{something new}) assert_nothing_raised do klass = parser.newclass("myclass", :code => code) end assert(klass, "Did not return class when appending") assert_equal(%w{original code something new}, parser.classes["myclass"].code.evaluate(:scope => scope)) # Now create the same class name in a different scope assert_nothing_raised { klass = parser.newclass("other::myclass", :code => mkcode.call(%w{something diff})) } assert(klass, "Did not return class") other = parser.classes["other::myclass"] assert(other, "Could not find class") assert_equal("other::myclass", other.classname) assert_equal("other::myclass", other.namespace) assert_equal(%w{something diff}, other.code.evaluate(:scope => scope)) # Make sure newclass deals correctly with nodes with no code klass = parser.newclass("nocode") assert(klass, "Did not return class") assert_nothing_raised do klass = parser.newclass("nocode", :code => mkcode.call(%w{yay test})) end assert(klass, "Did not return class with no code") assert_equal(%w{yay test}, parser.classes["nocode"].code.evaluate(:scope => scope)) # Then try merging something into nothing parser.newclass("nocode2", :code => mkcode.call(%w{foo test})) assert(klass, "Did not return class with no code") assert_nothing_raised do klass = parser.newclass("nocode2") end assert(klass, "Did not return class with no code") assert_equal(%w{foo test}, parser.classes["nocode2"].code.evaluate(:scope => scope)) # And lastly, nothing and nothing klass = parser.newclass("nocode3") assert(klass, "Did not return class with no code") assert_nothing_raised do klass = parser.newclass("nocode3") end assert(klass, "Did not return class with no code") assert_nil(parser.classes["nocode3"].code) end # Make sure you can't have classes and defines with the same name in the # same scope. def test_classes_beat_defines parser = mkparser assert_nothing_raised { parser.newclass("yay::funtest") } assert_raise(Puppet::ParseError) do parser.newdefine("yay::funtest") end assert_nothing_raised { parser.newdefine("yay::yaytest") } assert_raise(Puppet::ParseError) do parser.newclass("yay::yaytest") end end def test_namesplit parser = mkparser assert_nothing_raised do {"base::sub" => %w{base sub}, "main" => ["", "main"], "one::two::three::four" => ["one::two::three", "four"], }.each do |name, ary| result = parser.namesplit(name) assert_equal(ary, result, "%s split to %s" % [name, result]) end end end # Now make sure we get appropriate behaviour with parent class conflicts. def test_newclass_parentage parser = mkparser parser.newclass("base1") parser.newclass("one::two::three") # First create it with no parentclass. assert_nothing_raised { parser.newclass("sub") } assert(parser.classes["sub"], "Could not find definition") assert_nil(parser.classes["sub"].parentclass) # Make sure we can't set the parent class to ourself. assert_raise(Puppet::ParseError) { parser.newclass("sub", :parent => "sub") } # Now create another one, with a parentclass. assert_nothing_raised { parser.newclass("sub", :parent => "base1") } # Make sure we get the right parent class, and make sure it's not an object. assert_equal("base1", parser.classes["sub"].parentclass) # Now make sure we get a failure if we try to conflict. assert_raise(Puppet::ParseError) { parser.newclass("sub", :parent => "one::two::three") } # Make sure that failure didn't screw us up in any way. assert_equal("base1", parser.classes["sub"].parentclass) # But make sure we can create a class with a fq parent assert_nothing_raised { parser.newclass("another", :parent => "one::two::three") } assert_equal("one::two::three", parser.classes["another"].parentclass) end def test_fqfind parser = mkparser table = {} # Define a bunch of things. %w{a c a::b a::b::c a::c a::b::c::d a::b::c::d::e::f c::d}.each do |string| table[string] = string end check = proc do |namespace, hash| hash.each do |thing, result| assert_equal(result, parser.fqfind(namespace, thing, table), "Could not find %s in %s" % [thing, namespace]) end end # Now let's do some test lookups. # First do something really simple check.call "a", "b" => "a::b", "b::c" => "a::b::c", "d" => nil, "::c" => "c" check.call "a::b", "c" => "a::b::c", "b" => "a::b", "a" => "a" check.call "a::b::c::d::e", "c" => "a::b::c", "::c" => "c", "c::d" => "a::b::c::d", "::c::d" => "c::d" check.call "", "a" => "a", "a::c" => "a::c" end # Setup a module. def mk_module(name, files = {}) mdir = File.join(@dir, name) mandir = File.join(mdir, "manifests") FileUtils.mkdir_p mandir if defs = files[:define] files.delete(:define) end Dir.chdir(mandir) do files.each do |file, classes| File.open("%s.pp" % file, "w") do |f| classes.each { |klass| if defs f.puts "define %s {}" % klass else f.puts "class %s {}" % klass end } end end end end # #596 - make sure classes and definitions load automatically if they're in modules, so we don't have to manually load each one. def test_module_autoloading @dir = tempfile Puppet[:modulepath] = @dir FileUtils.mkdir_p @dir parser = mkparser # Make sure we fail like normal for actually missing classes assert_nil(parser.findclass("", "nosuchclass"), "Did not return nil on missing classes") # test the simple case -- the module class itself name = "simple" mk_module(name, :init => [name]) # Try to load the module automatically now klass = parser.findclass("", name) assert_instance_of(AST::HostClass, klass, "Did not autoload class from module init file") assert_equal(name, klass.classname, "Incorrect class was returned") # Try loading the simple module when we're in something other than the base namespace. parser = mkparser klass = parser.findclass("something::else", name) assert_instance_of(AST::HostClass, klass, "Did not autoload class from module init file") assert_equal(name, klass.classname, "Incorrect class was returned") # Now try it with a definition as the base file name = "simpdef" mk_module(name, :define => true, :init => [name]) klass = parser.finddefine("", name) assert_instance_of(AST::Component, klass, "Did not autoload class from module init file") assert_equal(name, klass.classname, "Incorrect class was returned") # Now try it with namespace classes where both classes are in the init file parser = mkparser modname = "both" name = "sub" mk_module(modname, :init => %w{both both::sub}) # First try it with a namespace klass = parser.findclass("both", name) assert_instance_of(AST::HostClass, klass, "Did not autoload sub class from module init file with a namespace") assert_equal("both::sub", klass.classname, "Incorrect class was returned") # Now try it using the fully qualified name parser = mkparser klass = parser.findclass("", "both::sub") assert_instance_of(AST::HostClass, klass, "Did not autoload sub class from module init file with no namespace") assert_equal("both::sub", klass.classname, "Incorrect class was returned") # Now try it with the class in a different file parser = mkparser modname = "separate" name = "sub" mk_module(modname, :init => %w{separate}, :sub => %w{separate::sub}) # First try it with a namespace klass = parser.findclass("separate", name) assert_instance_of(AST::HostClass, klass, "Did not autoload sub class from separate file with a namespace") assert_equal("separate::sub", klass.classname, "Incorrect class was returned") # Now try it using the fully qualified name parser = mkparser klass = parser.findclass("", "separate::sub") assert_instance_of(AST::HostClass, klass, "Did not autoload sub class from separate file with no namespace") assert_equal("separate::sub", klass.classname, "Incorrect class was returned") # Now make sure we don't get a failure when there's no module file parser = mkparser modname = "alone" name = "sub" mk_module(modname, :sub => %w{alone::sub}) # First try it with a namespace assert_nothing_raised("Could not autoload file when module file is missing") do klass = parser.findclass("alone", name) end assert_instance_of(AST::HostClass, klass, "Did not autoload sub class from alone file with a namespace") assert_equal("alone::sub", klass.classname, "Incorrect class was returned") # Now try it using the fully qualified name parser = mkparser klass = parser.findclass("", "alone::sub") assert_instance_of(AST::HostClass, klass, "Did not autoload sub class from alone file with no namespace") assert_equal("alone::sub", klass.classname, "Incorrect class was returned") end # Make sure class, node, and define methods are case-insensitive def test_structure_case_insensitivity parser = mkparser result = nil assert_nothing_raised do result = parser.newclass "Yayness" end assert_equal(result, parser.findclass("", "yayNess")) assert_nothing_raised do result = parser.newdefine "FunTest" end assert_equal(result, parser.finddefine("", "fUntEst"), "%s was not matched" % "fUntEst") end end # $Id$ diff --git a/test/language/resource.rb b/test/language/resource.rb index 73d516f51..50d58cf32 100755 --- a/test/language/resource.rb +++ b/test/language/resource.rb @@ -1,503 +1,464 @@ #!/usr/bin/env ruby $:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/ 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", :source => "source", :scope => "scope"} # 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 Reference.expects(:new).with(:type => "resource", :title => "testing", :scope => "scope").returns(:ref) res = nil assert_nothing_raised do res = Parser::Resource.new(args) end 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_overrides) 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_trans # First try translating a builtin resource. Make sure we use some references # and arrays, to make sure they translate correctly. + source = mock("source") + scope = mock("scope") + scope.expects(: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}, + :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) 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 def test_evaluate - @parser = mkparser - # Make a definition that we know will, um, do something - @parser.newdefine "evaltest", - :arguments => [%w{one}, ["two", stringobj("755")]], - :code => resourcedef("file", "/tmp", - "owner" => varref("one"), "mode" => varref("two")) - - res = Parser::Resource.new :type => "evaltest", :title => "yay", - :source => @source, :scope => @scope, - :params => paramify(@source, :one => "nobody") + # 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") + config = mock("config") + res.scope.expects(:configuration).returns(config) + config.expects(:delete_resource).with(res) - # Now try evaluating - ret = nil - assert_nothing_raised do - ret = res.evaluate + args = {:scope => res.scope, :arguments => res.to_hash} + # This is insane; FIXME we need to redesign how classes and components are evaluated. + [:type, :title, :virtual, :exported].each do |param| + args[param] = res.send(param) end + type.expects(:evaluate_resource).with(args) - # Make sure we can find our object now - result = @scope.findresource("File[/tmp]") - - # Now make sure we got the code we expected. - assert_instance_of(Puppet::Parser::Resource, result) - - assert_equal("file", result.type) - assert_equal("/tmp", result.title) - assert_equal("nobody", result["owner"]) - assert_equal("755", result["mode"]) - - # And that we cannot find the old resource - assert_nil(@scope.findresource("Evaltest[yay]"), - "Evaluated resource was not deleted") + res.evaluate end def test_add_overrides # Try it with nil res = mkresource res.scope = mock('scope') config = mock("config") res.scope.expects(:configuration).returns(config) config.expects(:resource_overrides).with(res).returns(nil) res.expects(:merge).never res.send(:add_overrides) # And an empty array res = mkresource res.scope = mock('scope') config = mock("config") res.scope.expects(:configuration).returns(config) config.expects(:resource_overrides).with(res).returns([]) res.expects(:merge).never res.send(:add_overrides) # And with some overrides res = mkresource res.scope = mock('scope') config = mock("config") res.scope.expects(:configuration).returns(config) returns = %w{a b} config.expects(:resource_overrides).with(res).returns(returns) res.expects(:merge).with("a") res.expects(:merge).with("b") res.send(:add_overrides) assert(returns.empty?, "Did not clear overrides") end def test_proxymethods res = Parser::Resource.new :type => "evaltest", :title => "yay", - :source => @source, :scope => @scope + :source => mock("source"), :scope => mock('scope') assert_equal("evaltest", res.type) assert_equal("yay", res.title) assert_equal(false, res.builtin?) end def test_add_metaparams res = mkresource params = res.instance_variable_get("@params") params[:a] = :b Puppet::Type.expects(:eachmetaparam).multiple_yields(:a, :b, :c) res.scope.expects(:lookupvar).with("b", false).returns(:something) res.scope.expects(:lookupvar).with("c", false).returns(:undefined) res.expects(:set_parameter).with(:b, :something) res.send(:add_metaparams) assert_nil(params[:c], "A value was created somehow for an unset metaparam") 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 = stub(:tags => []) 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 = stub(:tags => []) 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, "Component was considered builtin") end - # #472. Really, this still isn't the best behaviour, but at least - # it's consistent with what we have elsewhere. - def test_defaults_from_parent_classes - @parser = mkparser - # Make a parent class with some defaults in it - @parser.newclass("base", - :code => defaultobj("file", :owner => "root", :group => "root") - ) - - # Now a mid-level class with some different values - @parser.newclass("middle", :parent => "base", - :code => defaultobj("file", :owner => "bin", :mode => "755") - ) - - # Now a lower class with its own defaults plus a resource - @parser.newclass("bottom", :parent => "middle", - :code => AST::ASTArray.new(:children => [ - defaultobj("file", :owner => "adm", :recurse => "true"), - resourcedef("file", "/tmp/yayness", {}) - ]) - ) - - # Now evaluate the class. - assert_nothing_raised("Failed to evaluate class tree") do - @scope.evalclasses("bottom") - end - - # Make sure our resource got created. - res = @scope.findresource("File[/tmp/yayness]") - assert_nothing_raised("Could not add defaults") do - res.adddefaults - end - assert(res, "could not find resource") - {:owner => "adm", :recurse => "true", :group => "root", :mode => "755"}.each do |param, value| - assert_equal(value, res[param], "%s => %s did not inherit correctly" % - [param, value]) - end - end - # The second part of #539 - make sure resources pass the arguments # correctly. def test_title_with_definitions - @parser = mkparser - define = @parser.newdefine "yayness", + parser = mkparser + define = parser.newdefine "yayness", :code => resourcedef("file", "/tmp", "owner" => varref("name"), "mode" => varref("title")) - klass = @parser.findclass("", "") + + klass = parser.findclass("", "") should = {:name => :owner, :title => :mode} [ {:name => "one", :title => "two"}, {:title => "three"}, ].each do |hash| - scope = mkscope :parser => @parser + config = mkconfig parser args = {:type => "yayness", :title => hash[:title], - :source => klass, :scope => scope} + :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 = scope.findresource("File[/tmp]") + 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 => @source, :scope => @scope, + :source => mock("source"), :scope => mock("scope"), :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", + parser = mkparser + define = parser.newdefine("yayness", :code => resourcedef("file", varref("name"), "mode" => "644")) - res = mkresource :type => "yayness", :title => "foo", :params => {} + config = mkconfig(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 => {} + 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 end # $Id$ diff --git a/test/language/scope.rb b/test/language/scope.rb index bd90caa53..fc5e085d4 100755 --- a/test/language/scope.rb +++ b/test/language/scope.rb @@ -1,747 +1,637 @@ #!/usr/bin/env ruby $:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/ require 'mocha' require 'puppettest' require 'puppettest/parsertesting' require 'puppettest/resourcetesting' # so, what kind of things do we want to test? # we don't need to test function, since we're confident in the # library tests. We do, however, need to test how things are actually # working in the language. # so really, we want to do things like test that our ast is correct # and test whether we've got things in the right scopes class TestScope < Test::Unit::TestCase include PuppetTest::ParserTesting include PuppetTest::ResourceTesting def to_ary(hash) hash.collect { |key,value| [key,value] } end def test_variables - scope = nil - over = "over" - - scopes = [] - vars = [] - values = {} - ovalues = [] - - 10.times { |index| - # slap some recursion in there - scope = mkscope(:parent => scope) - scopes.push scope - - var = "var%s" % index - value = rand(1000) - ovalue = rand(1000) - - ovalues.push ovalue + config = mkconfig + topscope = config.topscope + midscope = config.newscope(topscope) + botscope = config.newscope(midscope) - vars.push var - values[var] = value + scopes = {:top => topscope, :mid => midscope, :bot => botscope} - # set the variable in the current scope - assert_nothing_raised { - scope.setvar(var,value) - } + # 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 - # this should override previous values - assert_nothing_raised { - scope.setvar(over,ovalue) - } + # 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 - assert_equal(value,scope.lookupvar(var)) - - #puts "%s vars, %s scopes" % [vars.length,scopes.length] - i = 0 - vars.zip(scopes) { |v,s| - # this recurses all the way up the tree as necessary - val = nil - oval = nil - - # look up the values using the bottom scope - assert_nothing_raised { - val = scope.lookupvar(v) - oval = scope.lookupvar(over) - } - - # verify they're correct - assert_equal(values[v],val) - assert_equal(ovalue,oval) - - # verify that we get the most recent value - assert_equal(ovalue,scope.lookupvar(over)) - - # verify that they aren't available in upper scopes - if parent = s.parent - val = nil - assert_nothing_raised { - val = parent.lookupvar(v) - } - assert_equal("", val, "Did not get empty string on missing var") - - # and verify that the parent sees its correct value - assert_equal(ovalues[i - 1],parent.lookupvar(over)) - end - i += 1 - } - } + # 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) klass.evaluate(:scope => scope) scopes[name] = scope.class_scope(klass) end classes.each do |name| var = [name, "var"].join("::") scopes[name].expects(:lookupvar).with("var", false).returns(name) assert_equal(name, scope.send(:lookup_qualified_var, var, false), "did not get correct value from lookupvar") end end def test_declarative # set to declarative top = mkscope sub = mkscope(:parent => top) assert_nothing_raised { top.setvar("test","value") } assert_raise(Puppet::ParseError) { top.setvar("test","other") } assert_nothing_raised { sub.setvar("test","later") } assert_raise(Puppet::ParseError) { top.setvar("test","yeehaw") } end def test_setdefaults config = mkconfig scope = config.topscope defaults = scope.instance_variable_get("@defaults") # First the case where there are no defaults and we pass a single param - param = stub :name => "myparam" + 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" - param2 = stub :name => "two" + 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" + newparam = stub :name => "myparam", :file => "f", :line => "l" assert_raise(Puppet::ParseError, "Allowed resetting of defaults") do scope.setdefaults(:mytype, param) end assert_equal({"myparam" => param}, defaults[:mytype], "Replaced default even though there was a failure") end def test_lookupdefaults config = mkconfig top = config.topscope # Make a subscope sub = config.newscope(top) topdefs = top.instance_variable_get("@defaults") subdefs = sub.instance_variable_get("@defaults") # First add some defaults to our top scope topdefs[:t1] = {:p1 => :p2, :p3 => :p4} topdefs[:t2] = {:p5 => :p6} # Then the sub scope subdefs[:t1] = {:p1 => :p7, :p8 => :p9} subdefs[:t2] = {:p5 => :p10, :p11 => :p12} # Now make sure we get the correct list back result = nil assert_nothing_raised("Could not get defaults") do result = sub.lookupdefaults(:t1) end assert_equal(:p9, result[:p8], "Did not get child defaults") assert_equal(:p4, result[:p3], "Did not override parent defaults with child default") assert_equal(:p7, result[:p1], "Did not get parent defaults") end def test_parent config = mkconfig top = config.topscope # Make a subscope sub = config.newscope(top) assert_equal(top, sub.parent, "Did not find parent scope correctly") assert_equal(top, sub.parent, "Did not find parent scope on second call") end def test_class_scope config = mkconfig scope = config.topscope config.expects(:class_scope).with(:testing).returns(:myscope) assert_equal(:myscope, scope.class_scope(:testing), "Did not pass back the results of config.class_scope") end - def test_strparser + def test_strinterp # Make and evaluate our classes so the qualified lookups work parser = mkparser klass = parser.newclass("") scope = mkscope(:parser => parser) klass.evaluate(:scope => scope) klass = parser.newclass("one") klass.evaluate(:scope => scope) klass = parser.newclass("one::two") klass.evaluate(:scope => scope) scope = scope.class_scope("") assert_nothing_raised { scope.setvar("test","value") } scopes = {"" => scope} %w{one one::two one::two::three}.each do |name| klass = parser.newclass(name) klass.evaluate(:scope => scope) scopes[name] = scope.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.strparser(input), + 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.strparser(string), + 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_setclass - parser, scope, source = mkclassframing - - base = scope.findclass("base") - assert(base, "Could not find base class") - assert(! scope.class_scope(base), "Class incorrectly set") - assert(! scope.classlist.include?("base"), "Class incorrectly in classlist") - assert_nothing_raised do - scope.setclass base - end - - assert(scope.class_scope(base), "Class incorrectly unset") - assert(scope.classlist.include?("base"), "Class not in classlist") - - # Make sure we can retrieve the scope. - assert_equal(scope, scope.class_scope(base), - "class scope was not set correctly") - - # Now try it with a normal string - Puppet[:trace] = false - assert_raise(Puppet::DevError) do - scope.setclass "string" + # Run through it when we're a normal class + config = mkconfig + scope = config.topscope + klass = mock("class") + klass.expects(:classname).returns(:myclass) + klass.expects(:is_a?).with(AST::HostClass).returns(true) + klass.expects(:is_a?).with(AST::Node).returns(false) + config.expects(:class_set).with(:myclass, scope) + scope.setclass(klass) + + # And when we're a node + config = mkconfig + scope = config.topscope + klass = mock("class2") + klass.expects(:classname).returns(:myclass) + klass.expects(:is_a?).with(AST::HostClass).returns(true) + klass.expects(:is_a?).with(AST::Node).returns(true) + config.expects(:class_set).with(:myclass, scope) + scope.setclass(klass) + assert(scope.nodescope?, "Did not set the scope as a node scope when evaluating a node") + + # And when we're invalid + config = mkconfig + scope = config.topscope + klass = mock("class3") + klass.expects(:is_a?).with(AST::HostClass).returns(false) + assert_raise(Puppet::DevError, "Did not fail when scope got passed a non-component") do + scope.setclass(klass) end - - assert(! scope.class_scope("string"), "string incorrectly set") - - # Set "" in the class list, and make sure it doesn't show up in the return - top = scope.findclass("") - assert(top, "Could not find top class") - scope.setclass top - - assert(! scope.classlist.include?(""), "Class list included empty") end def test_validtags scope = mkscope() ["a class", "a.class"].each do |bad| assert_raise(Puppet::ParseError, "Incorrectly allowed %s" % bad.inspect) do scope.tag(bad) end end ["a-class", "a_class", "Class", "class", "yayNess"].each do |good| assert_nothing_raised("Incorrectly banned %s" % good.inspect) do scope.tag(good) end end end def test_tagfunction - scope = mkscope() + scope = mkscope assert_nothing_raised { scope.function_tag(["yayness", "booness"]) } assert(scope.tags.include?("yayness"), "tag 'yayness' did not get set") assert(scope.tags.include?("booness"), "tag 'booness' did not get set") # Now verify that the 'tagged' function works correctly assert(scope.function_tagged("yayness"), "tagged function incorrectly returned false") assert(scope.function_tagged("booness"), "tagged function incorrectly returned false") assert(! scope.function_tagged("funtest"), "tagged function incorrectly returned true") end def test_includefunction parser = mkparser scope = mkscope :parser => parser myclass = parser.newclass "myclass" otherclass = parser.newclass "otherclass" function = Puppet::Parser::AST::Function.new( :name => "include", :ftype => :statement, :arguments => AST::ASTArray.new( :children => [nameobj("myclass"), nameobj("otherclass")] ) ) assert_nothing_raised do function.evaluate :scope => scope end [myclass, otherclass].each do |klass| assert(scope.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 - # Verify scope context is handled correctly. - def test_scopeinside - scope = mkscope() - - one = :one - two = :two - - # First just test the basic functionality. - assert_nothing_raised { - scope.inside :one do - assert_equal(:one, scope.inside, "Context did not get set") - end - assert_nil(scope.inside, "Context did not revert") - } - - # Now make sure error settings work. - assert_raise(RuntimeError) { - scope.inside :one do - raise RuntimeError, "This is a failure, yo" - end - } - assert_nil(scope.inside, "Context did not revert") - - # Now test it a bit deeper in. - assert_nothing_raised { - scope.inside :one do - scope.inside :two do - assert_equal(:two, scope.inside, "Context did not get set") - end - assert_equal(:one, scope.inside, "Context did not get set") - end - assert_nil(scope.inside, "Context did not revert") - } - - # And lastly, check errors deeper in - assert_nothing_raised { - scope.inside :one do - begin - scope.inside :two do - raise "a failure" - end - rescue - end - assert_equal(:one, scope.inside, "Context did not get set") - end - assert_nil(scope.inside, "Context did not revert") - } - - end - if defined? ActiveRecord # Verify that we recursively mark as exported the results of collectable # components. def test_exportedcomponents - parser, scope, source = mkclassframing - children = [] + config = mkconfig + parser = config.parser + + # Create a default source + config.topscope.source = parser.newclass "", "" 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 - obj.evaluate :scope => scope - # And then evaluate it - parser.evaliterate(scope) + obj.evaluate :scope => config.topscope + + # And run the loop. + config.send(:evaluate_generators) %w{file}.each do |type| - objects = scope.lookupexported(type) + objects = config.resources.find_all { |r| r.type == type and r.exported } assert(!objects.empty?, "Did not get an exported %s" % type) end end # Verify that we can both store and collect an object in the same # run, whether it's in the same scope as a collection or a different # scope. def test_storeandcollect Puppet[:storeconfigs] = true Puppet::Rails.init sleep 1 children = [] file = tempfile() File.open(file, "w") { |f| f.puts " class yay { @@host { myhost: ip => \"192.168.0.2\" } } include yay @@host { puppet: ip => \"192.168.0.3\" } Host <<||>>" } interp = nil assert_nothing_raised { interp = Puppet::Parser::Interpreter.new( :Manifest => file, :UseNodes => false, :ForkSave => false ) } objects = nil # We run it twice because we want to make sure there's no conflict # if we pull it up from the database. node = mknode node.parameters = {"hostname" => node.name} 2.times { |i| assert_nothing_raised { objects = interp.compile(node) } flat = objects.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 # Make sure tags behave appropriately. def test_tags parser, scope, source = mkclassframing # First 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 scope.tag tag end end # Now 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 scope.tag tag end end # And make sure we get each of them. ptags = scope.tags tags.each do |tag| assert(ptags.include?(tag), "missing #{tag}") end # Now create a subscope and set some tags there newscope = scope.newscope(:type => 'subscope') # set some tags newscope.tag "onemore", "yaytag" # And make sure we get them plus our parent tags assert_equal((ptags + %w{onemore subscope}).sort, newscope.tags.sort) end # FIXME This isn't a great test, but I need to move on. def test_to_trans bucket = mock("transbucket") Puppet::TransBucket.expects(:new).with([]).returns(bucket) scope = mkscope scope.type = "mytype" scope.name = "myname" bucket.expects(:name=).with("myname") bucket.expects(:type=).with("mytype") scope.to_trans end def test_namespaces parser, scope, source = mkclassframing 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 - - # #620 - Nodes and classes should conflict, else classes don't get evaluated - def test_nodes_and_classes_name_conflict - scope = mkscope - - node = AST::Node.new :classname => "test", :namespace => "" - scope.setclass(node) - - assert(scope.nodescope?, "Scope was not marked a node scope when a node was set") - - # Now make a subscope that will be a class scope - klass = AST::HostClass.new :classname => "test", :namespace => "" - kscope = klass.subscope(scope) - - # Now make sure we throw a failure, because we're trying to do a class and node - # with the same name - assert_raise(Puppet::ParseError, "Did not fail on class and node with same name") do - kscope.class_scope(klass) - end - end end # $Id$ diff --git a/test/lib/puppettest.rb b/test/lib/puppettest.rb index 5c385afb1..b56bc563e 100755 --- a/test/lib/puppettest.rb +++ b/test/lib/puppettest.rb @@ -1,308 +1,309 @@ # Add .../test/lib $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__))) # Add .../lib $LOAD_PATH.unshift(File.expand_path(File.join(File.dirname(__FILE__), '../../lib'))) require 'puppet' require 'mocha' require 'test/unit' # Yay; hackish but it works if ARGV.include?("-d") ARGV.delete("-d") $console = true end module PuppetTest # Munge cli arguments, so we can enable debugging if we want # and so we can run just specific methods. def self.munge_argv require 'getoptlong' result = GetoptLong.new( [ "--debug", "-d", GetoptLong::NO_ARGUMENT ], [ "--resolve", "-r", GetoptLong::REQUIRED_ARGUMENT ], [ "-n", GetoptLong::REQUIRED_ARGUMENT ], [ "--help", "-h", GetoptLong::NO_ARGUMENT ] ) usage = "USAGE: TESTOPTS='[-n -n ...] [-d]' rake [target] [target] ..." opts = [] dir = method = nil result.each { |opt,arg| case opt when "--resolve" dir, method = arg.split(",") when "--debug" $puppet_debug = true Puppet::Util::Log.level = :debug Puppet::Util::Log.newdestination(:console) when "--help" puts usage exit else opts << opt << arg end } suites = nil args = ARGV.dup # Reset the options, so the test suite can deal with them (this is # what makes things like '-n' work). opts.each { |o| ARGV << o } return args end # Find the root of the Puppet tree; this is not the test directory, but # the parent of that dir. def basedir(*list) unless defined? @@basedir Dir.chdir(File.dirname(__FILE__)) do @@basedir = File.dirname(File.dirname(Dir.getwd)) end end if list.empty? @@basedir else File.join(@@basedir, *list) end end def cleanup(&block) @@cleaners << block end def datadir(*list) File.join(basedir, "test", "data", *list) end def exampledir(*args) unless defined? @@exampledir @@exampledir = File.join(basedir, "examples") end if args.empty? return @@exampledir else return File.join(@@exampledir, *args) end end module_function :basedir, :datadir, :exampledir # Rails clobbers RUBYLIB, thanks def libsetup curlibs = ENV["RUBYLIB"].split(":") $:.reject do |dir| dir =~ /^\/usr/ end.each do |dir| unless curlibs.include?(dir) curlibs << dir end end ENV["RUBYLIB"] = curlibs.join(":") end def logcollector collector = [] Puppet::Util::Log.newdestination(collector) cleanup do Puppet::Util::Log.close(collector) end collector end def rake? $0 =~ /test_loader/ end # Redirect stdout and stderr def redirect @stderr = tempfile @stdout = tempfile $stderr = File.open(@stderr, "w") $stdout = File.open(@stdout, "w") cleanup do $stderr = STDERR $stdout = STDOUT end end def setup @memoryatstart = Puppet::Util.memory if defined? @@testcount @@testcount += 1 else @@testcount = 0 end @configpath = File.join(tmpdir, self.class.to_s + "configdir" + @@testcount.to_s + "/" ) unless defined? $user and $group $user = nonrootuser().uid.to_s $group = nonrootgroup().gid.to_s end Puppet.config.clear Puppet[:user] = $user Puppet[:group] = $group Puppet[:confdir] = @configpath Puppet[:vardir] = @configpath unless File.exists?(@configpath) Dir.mkdir(@configpath) end @@tmpfiles = [@configpath, tmpdir()] @@tmppids = [] @@cleaners = [] @logs = [] # If we're running under rake, then disable debugging and such. #if rake? or ! Puppet[:debug] if defined?($puppet_debug) or ! rake? if textmate? Puppet[:color] = false end Puppet::Util::Log.newdestination(@logs) if defined? $console Puppet.info @method_name Puppet::Util::Log.newdestination(:console) Puppet[:trace] = true end Puppet::Util::Log.level = :debug #$VERBOSE = 1 else Puppet::Util::Log.close Puppet::Util::Log.newdestination(@logs) Puppet[:httplog] = tempfile() end Puppet[:ignoreschedules] = true end def tempfile if defined? @@tmpfilenum @@tmpfilenum += 1 else @@tmpfilenum = 1 end f = File.join(self.tmpdir(), self.class.to_s + "_" + @method_name.to_s + @@tmpfilenum.to_s) @@tmpfiles << f return f end def textmate? if ENV["TM_FILENAME"] return true else return false end end def tstdir dir = tempfile() Dir.mkdir(dir) return dir end def tmpdir unless defined? @tmpdir and @tmpdir @tmpdir = case Facter["operatingsystem"].value when "Darwin": "/private/tmp" when "SunOS": "/var/tmp" else "/tmp" end @tmpdir = File.join(@tmpdir, "puppettesting") unless File.exists?(@tmpdir) FileUtils.mkdir_p(@tmpdir) File.chmod(01777, @tmpdir) end end @tmpdir end def teardown @@cleaners.each { |cleaner| cleaner.call() } @@tmpfiles.each { |file| unless file =~ /tmp/ puts "Not deleting tmpfile %s" % file next end if FileTest.exists?(file) system("chmod -R 755 %s" % file) system("rm -rf %s" % file) end } @@tmpfiles.clear @@tmppids.each { |pid| %x{kill -INT #{pid} 2>/dev/null} } @@tmppids.clear Puppet::Type.allclear Puppet::Util::Storage.clear Puppet.clear @memoryatend = Puppet::Util.memory diff = @memoryatend - @memoryatstart if diff > 1000 Puppet.info "%s#%s memory growth (%s to %s): %s" % [self.class, @method_name, @memoryatstart, @memoryatend, diff] end # reset all of the logs Puppet::Util::Log.close @logs.clear # Just in case there are processes waiting to die... require 'timeout' begin Timeout::timeout(5) do Process.waitall end rescue Timeout::Error # just move on end + mocha_verify if File.stat("/dev/null").mode & 007777 != 0666 File.open("/tmp/nullfailure", "w") { |f| f.puts self.class } exit(74) end end def logstore @logs = [] Puppet::Util::Log.newdestination(@logs) end end require 'puppettest/support' require 'puppettest/filetesting' require 'puppettest/fakes' require 'puppettest/exetest' require 'puppettest/parsertesting' require 'puppettest/servertest' require 'puppettest/testcase' # $Id$ diff --git a/test/lib/puppettest/parsertesting.rb b/test/lib/puppettest/parsertesting.rb index 368e112f9..0a695cbaa 100644 --- a/test/lib/puppettest/parsertesting.rb +++ b/test/lib/puppettest/parsertesting.rb @@ -1,407 +1,407 @@ require 'puppettest' require 'puppet/rails' module PuppetTest::ParserTesting include PuppetTest AST = Puppet::Parser::AST Config = Puppet::Parser::Configuration # A fake class that we can use for testing evaluation. class FakeAST attr_writer :evaluate def evaluated? defined? @evaluated and @evaluated end def evaluate(*args) @evaluated = true return @evaluate end def initialize(val = nil) if val @evaluate = val end end def reset @evaluated = nil end def safeevaluate(*args) evaluate() end end def astarray(*args) AST::ASTArray.new( :children => args ) end def mkconfig(parser = nil) require 'puppet/network/handler/node' parser ||= mkparser node = mknode - return Config.new(parser, node) + return Config.new(node, parser) end def mknode(name = nil) name ||= "nodename" Puppet::Network::Handler.handler(:node) Puppet::Network::Handler::Node::SimpleNode.new("nodename") end def mkinterp(args = {}) args[:Code] ||= "" unless args.include?(:Manifest) args[:Local] ||= true Puppet::Parser::Interpreter.new(args) end def mkparser Puppet::Parser::Parser.new() end def mkscope(hash = {}) - hash[:configuration] ||= mkconfig hash[:parser] ||= mkparser - hash[:source] ||= (hash[:parser].findclass("", "") || hash[:parser].newclass("")) + config ||= mkconfig(hash[:parser]) + config.topscope.source = (hash[:parser].findclass("", "") || hash[:parser].newclass("")) - unless hash[:source] + unless config.topscope.source raise "Could not find source for scope" end - Puppet::Parser::Scope.new(hash) + config.topscope end def classobj(name, hash = {}) hash[:file] ||= __FILE__ hash[:line] ||= __LINE__ hash[:type] ||= name AST::HostClass.new(hash) end def tagobj(*names) args = {} newnames = names.collect do |name| if name.is_a? AST name else nameobj(name) end end args[:type] = astarray(*newnames) assert_nothing_raised("Could not create tag %s" % names.inspect) { return AST::Tag.new(args) } end def resourcedef(type, title, params) unless title.is_a?(AST) title = stringobj(title) end assert_nothing_raised("Could not create %s %s" % [type, title]) { return AST::ResourceDef.new( :file => __FILE__, :line => __LINE__, :title => title, :type => type, :params => resourceinst(params) ) } end def virt_resourcedef(*args) res = resourcedef(*args) res.virtual = true res end def resourceoverride(type, title, params) assert_nothing_raised("Could not create %s %s" % [type, name]) { return AST::ResourceOverride.new( :file => __FILE__, :line => __LINE__, :object => resourceref(type, title), :type => type, :params => resourceinst(params) ) } end def resourceref(type, title) assert_nothing_raised("Could not create %s %s" % [type, title]) { return AST::ResourceRef.new( :file => __FILE__, :line => __LINE__, :type => type, :title => stringobj(title) ) } end def fileobj(path, hash = {"owner" => "root"}) assert_nothing_raised("Could not create file %s" % path) { return resourcedef("file", path, hash) } end def nameobj(name) assert_nothing_raised("Could not create name %s" % name) { return AST::Name.new( :file => tempfile(), :line => rand(100), :value => name ) } end def typeobj(name) assert_nothing_raised("Could not create type %s" % name) { return AST::Type.new( :file => tempfile(), :line => rand(100), :value => name ) } end def nodedef(name) assert_nothing_raised("Could not create node %s" % name) { return AST::NodeDef.new( :file => tempfile(), :line => rand(100), :names => nameobj(name), :code => AST::ASTArray.new( :children => [ varobj("%svar" % name, "%svalue" % name), fileobj("/%s" % name) ] ) ) } end def resourceinst(hash) assert_nothing_raised("Could not create resource instance") { params = hash.collect { |param, value| resourceparam(param, value) } return AST::ResourceInst.new( :file => tempfile(), :line => rand(100), :children => params ) } end def resourceparam(param, value) # Allow them to pass non-strings in if value.is_a?(String) value = stringobj(value) end assert_nothing_raised("Could not create param %s" % param) { return AST::ResourceParam.new( :file => tempfile(), :line => rand(100), :param => param, :value => value ) } end def stringobj(value) AST::String.new( :file => tempfile(), :line => rand(100), :value => value ) end def varobj(name, value) unless value.is_a? AST value = stringobj(value) end assert_nothing_raised("Could not create %s code" % name) { return AST::VarDef.new( :file => tempfile(), :line => rand(100), :name => nameobj(name), :value => value ) } end def varref(name) assert_nothing_raised("Could not create %s variable" % name) { return AST::Variable.new( :file => __FILE__, :line => __LINE__, :value => name ) } end def argobj(name, value) assert_nothing_raised("Could not create %s compargument" % name) { return AST::CompArgument.new( :children => [nameobj(name), stringobj(value)] ) } end def defaultobj(type, params) pary = [] params.each { |p,v| pary << AST::ResourceParam.new( :file => __FILE__, :line => __LINE__, :param => p, :value => stringobj(v) ) } past = AST::ASTArray.new( :file => __FILE__, :line => __LINE__, :children => pary ) assert_nothing_raised("Could not create defaults for %s" % type) { return AST::ResourceDefaults.new( :file => __FILE__, :line => __LINE__, :type => type, :params => past ) } end def taggedobj(name, ftype = :statement) functionobj("tagged", name, ftype) end def functionobj(function, name, ftype = :statement) func = nil assert_nothing_raised do func = Puppet::Parser::AST::Function.new( :name => function, :ftype => ftype, :arguments => AST::ASTArray.new( :children => [nameobj(name)] ) ) end return func end # This assumes no nodes def assert_creates(manifest, *files) interp = nil assert_nothing_raised { interp = Puppet::Parser::Interpreter.new( :Manifest => manifest, :UseNodes => false ) } config = nil assert_nothing_raised { - config = interp.run(Facter["hostname"].value, {}) + config = interp.compile(mknode) } comp = nil assert_nothing_raised { comp = config.to_type } assert_apply(comp) files.each do |file| assert(FileTest.exists?(file), "Did not create %s" % file) end end def mk_transobject(file = "/etc/passwd") obj = nil assert_nothing_raised { obj = Puppet::TransObject.new("file", file) obj["owner"] = "root" obj["mode"] = "644" } return obj end def mk_transbucket(*resources) bucket = nil assert_nothing_raised { bucket = Puppet::TransBucket.new bucket.name = "yayname" bucket.type = "yaytype" } resources.each { |o| bucket << o } return bucket end # Make a tree of resources, yielding if desired def mk_transtree(depth = 4, width = 2) top = nil assert_nothing_raised { top = Puppet::TransBucket.new top.name = "top" top.type = "bucket" } bucket = top file = tempfile() depth.times do |i| resources = [] width.times do |j| path = tempfile + i.to_s obj = Puppet::TransObject.new("file", path) obj["owner"] = "root" obj["mode"] = "644" # Yield, if they want if block_given? yield(obj, i, j) end resources << obj end newbucket = mk_transbucket(*resources) bucket.push newbucket bucket = newbucket end return top end # Take a list of AST resources, evaluate them, and return the results def assert_evaluate(children) top = nil assert_nothing_raised("Could not create top object") { top = AST::ASTArray.new( :children => children ) } trans = nil scope = nil assert_nothing_raised { scope = Puppet::Parser::Scope.new() trans = scope.evaluate(:ast => top) } return trans end end # $Id$