diff --git a/lib/puppet/parser/ast/leaf.rb b/lib/puppet/parser/ast/leaf.rb index 77617e992..c8ebc9483 100644 --- a/lib/puppet/parser/ast/leaf.rb +++ b/lib/puppet/parser/ast/leaf.rb @@ -1,221 +1,221 @@ class Puppet::Parser::AST # The base class for all of the leaves of the parse trees. These # basically just have types and values. Both of these parameters # are simple values, not AST objects. class Leaf < AST attr_accessor :value, :type # Return our value. def evaluate(scope) @value end def match(value) @value == value end def to_s @value.to_s unless @value.nil? end end # The boolean class. True or false. Converts the string it receives # to a Ruby boolean. class Boolean < AST::Leaf # Use the parent method, but then convert to a real boolean. def initialize(hash) super unless @value == true or @value == false raise Puppet::DevError, "'#{@value}' is not a boolean" end @value end def to_s @value ? "true" : "false" end end # The base string class. class String < AST::Leaf def evaluate(scope) @value.dup end def to_s "\"#{@value}\"" end end # An uninterpreted string. class FlatString < AST::Leaf def evaluate(scope) @value end def to_s "\"#{@value}\"" end end class Concat < AST::Leaf def evaluate(scope) @value.collect { |x| x.evaluate(scope) }.collect{ |x| x == :undef ? '' : x }.join end def to_s "#{@value.map { |s| s.to_s.gsub(/^"(.*)"$/, '\1') }.join}" end end # The 'default' option on case statements and selectors. class Default < AST::Leaf; end # Capitalized words; used mostly for type-defaults, but also # get returned by the lexer any other time an unquoted capitalized # word is found. class Type < AST::Leaf; end # Lower-case words. class Name < AST::Leaf; end # double-colon separated class names class ClassName < AST::Leaf; end # undef values; equiv to nil class Undef < AST::Leaf; end # Host names, either fully qualified or just the short name, or even a regex class HostName < AST::Leaf def initialize(hash) super # Note that this is an AST::Regex, not a Regexp @value = @value.to_s.downcase unless @value.is_a?(Regex) if @value =~ /[^-\w.]/ raise Puppet::DevError, "'#{@value}' is not a valid hostname" end end # implementing eql? and hash so that when an HostName is stored # in a hash it has the same hashing properties as the underlying value def eql?(value) value = value.value if value.is_a?(HostName) @value.eql?(value) end def hash @value.hash end def to_s @value.to_s end end # A simple variable. This object is only used during interpolation; # the VarDef class is used for assignment. class Variable < Name # Looks up the value of the object in the scope tree (does # not include syntactical constructs, like '$' and '{}'). def evaluate(scope) parsewrap do - if (var = scope.lookupvar(@value, false)) == :undefined + if (var = scope.lookupvar(@value, :file => file, :line => line)) == :undefined var = :undef end var end end def to_s "\$#{value}" end end class HashOrArrayAccess < AST::Leaf attr_accessor :variable, :key def evaluate_container(scope) container = variable.respond_to?(:evaluate) ? variable.safeevaluate(scope) : variable - (container.is_a?(Hash) or container.is_a?(Array)) ? container : scope.lookupvar(container) + (container.is_a?(Hash) or container.is_a?(Array)) ? container : scope.lookupvar(container, :file => file, :line => line) end def evaluate_key(scope) key.respond_to?(:evaluate) ? key.safeevaluate(scope) : key end def array_index_or_key(object, key) if object.is_a?(Array) raise Puppet::ParserError, "#{key} is not an integer, but is used as an index of an array" unless key = Puppet::Parser::Scope.number?(key) end key end def evaluate(scope) object = evaluate_container(scope) accesskey = evaluate_key(scope) raise Puppet::ParseError, "#{variable} is not an hash or array when accessing it with #{accesskey}" unless object.is_a?(Hash) or object.is_a?(Array) object[array_index_or_key(object, accesskey)] end # Assign value to this hashkey or array index def assign(scope, value) object = evaluate_container(scope) accesskey = evaluate_key(scope) if object.is_a?(Hash) and object.include?(accesskey) raise Puppet::ParseError, "Assigning to the hash '#{variable}' with an existing key '#{accesskey}' is forbidden" end # assign to hash or array object[array_index_or_key(object, accesskey)] = value end def to_s "\$#{variable.to_s}[#{key.to_s}]" end end class Regex < AST::Leaf def initialize(hash) super @value = Regexp.new(@value) unless @value.is_a?(Regexp) end # we're returning self here to wrap the regexp and to be used in places # where a string would have been used, without modifying any client code. # For instance, in many places we have the following code snippet: # val = @val.safeevaluate(@scope) # if val.match(otherval) # ... # end # this way, we don't have to modify this test specifically for handling # regexes. def evaluate(scope) self end def evaluate_match(value, scope, options = {}) value = value.is_a?(String) ? value : value.to_s if matched = @value.match(value) scope.ephemeral_from(matched, options[:file], options[:line]) end matched end def match(value) @value.match(value) end def to_s "/#{@value.source}/" end end end diff --git a/lib/puppet/parser/ast/vardef.rb b/lib/puppet/parser/ast/vardef.rb index 6de1860c8..b766311dd 100644 --- a/lib/puppet/parser/ast/vardef.rb +++ b/lib/puppet/parser/ast/vardef.rb @@ -1,33 +1,33 @@ require 'puppet/parser/ast/branch' class Puppet::Parser::AST # Define a variable. Stores the value in the current scope. class VarDef < AST::Branch associates_doc attr_accessor :name, :value, :append @settor = true # Look up our name and value, and store them appropriately. The # lexer strips off the syntax stuff like '$'. def evaluate(scope) value = @value.safeevaluate(scope) if name.is_a?(HashOrArrayAccess) name.assign(scope, value) else name = @name.safeevaluate(scope) parsewrap do - scope.setvar(name,value, :file => @file, :line => @line, :append => @append) + scope.setvar(name,value, :file => file, :line => line, :append => @append) end end end def each [@name,@value].each { |child| yield child } end end end diff --git a/lib/puppet/parser/functions/extlookup.rb b/lib/puppet/parser/functions/extlookup.rb index bc55410b9..5fbf26cec 100644 --- a/lib/puppet/parser/functions/extlookup.rb +++ b/lib/puppet/parser/functions/extlookup.rb @@ -1,157 +1,152 @@ require 'csv' module Puppet::Parser::Functions newfunction(:extlookup, :type => :rvalue, :doc => "This is a parser function to read data from external files, this version uses CSV files but the concept can easily be adjust for databases, yaml or any other queryable data source. The object of this is to make it obvious when it's being used, rather than magically loading data in when an module is loaded I prefer to look at the code and see statements like: $snmp_contact = extlookup(\"snmp_contact\") The above snippet will load the snmp_contact value from CSV files, this in its own is useful but a common construct in puppet manifests is something like this: case $domain { \"myclient.com\": { $snmp_contact = \"John Doe \" } default: { $snmp_contact = \"My Support \" } } Over time there will be a lot of this kind of thing spread all over your manifests and adding an additional client involves grepping through manifests to find all the places where you have constructs like this. This is a data problem and shouldn't be handled in code, a using this function you can do just that. First you configure it in site.pp: $extlookup_datadir = \"/etc/puppet/manifests/extdata\" $extlookup_precedence = [\"%{fqdn}\", \"domain_%{domain}\", \"common\"] The array tells the code how to resolve values, first it will try to find it in web1.myclient.com.csv then in domain_myclient.com.csv and finally in common.csv Now create the following data files in /etc/puppet/manifests/extdata: domain_myclient.com.csv: snmp_contact,John Doe root_contact,support@%{domain} client_trusted_ips,192.168.1.130,192.168.10.0/24 common.csv: snmp_contact,My Support root_contact,support@my.com Now you can replace the case statement with the simple single line to achieve the exact same outcome: $snmp_contact = extlookup(\"snmp_contact\") The above code shows some other features, you can use any fact or variable that is in scope by simply using %{varname} in your data files, you can return arrays by just having multiple values in the csv after the initial variable name. In the event that a variable is nowhere to be found a critical error will be raised that will prevent your manifest from compiling, this is to avoid accidentally putting in empty values etc. You can however specify a default value: $ntp_servers = extlookup(\"ntp_servers\", \"1.${country}.pool.ntp.org\") In this case it will default to \"1.${country}.pool.ntp.org\" if nothing is defined in any data file. You can also specify an additional data file to search first before any others at use time, for example: $version = extlookup(\"rsyslog_version\", \"present\", \"packages\") package{\"rsyslog\": ensure => $version } This will look for a version configured in packages.csv and then in the rest as configured by $extlookup_precedence if it's not found anywhere it will default to `present`, this kind of use case makes puppet a lot nicer for managing large amounts of packages since you do not need to edit a load of manifests to do simple things like adjust a desired version number. Precedence values can have variables embedded in them in the form %{fqdn}, you could for example do: $extlookup_precedence = [\"hosts/%{fqdn}\", \"common\"] This will result in /path/to/extdata/hosts/your.box.com.csv being searched. This is for back compatibility to interpolate variables with %. % interpolation is a workaround for a problem that has been fixed: Puppet variable interpolation at top scope used to only happen on each run.") do |args| key = args[0] default = args[1] datafile = args[2] raise Puppet::ParseError, ("extlookup(): wrong number of arguments (#{args.length}; must be <= 3)") if args.length > 3 - extlookup_datadir = lookupvar('extlookup_datadir') - extlookup_precedence = Array.new + extlookup_datadir = undef_as('',lookupvar('::extlookup_datadir')) - extlookup_precedence = lookupvar('extlookup_precedence').collect do |var| - var.gsub(/%\{(.+?)\}/) do |capture| - lookupvar($1) - end - end + extlookup_precedence = undef_as([],lookupvar('::extlookup_precedence')).collect { |var| var.gsub(/%\{(.+?)\}/) { lookupvar("::#{$1}") } } datafiles = Array.new # if we got a custom data file, put it first in the array of search files if datafile != "" datafiles << extlookup_datadir + "/#{datafile}.csv" if File.exists?(extlookup_datadir + "/#{datafile}.csv") end extlookup_precedence.each do |d| datafiles << extlookup_datadir + "/#{d}.csv" end desired = nil datafiles.each do |file| if desired.nil? if File.exists?(file) result = CSV.read(file).find_all do |r| r[0] == key end # return just the single result if theres just one, # else take all the fields in the csv and build an array if result.length > 0 if result[0].length == 2 val = result[0][1].to_s # parse %{}'s in the CSV into local variables using lookupvar() while val =~ /%\{(.+?)\}/ val.gsub!(/%\{#{$1}\}/, lookupvar($1)) end desired = val elsif result[0].length > 1 length = result[0].length cells = result[0][1,length] # Individual cells in a CSV result are a weird data type and throws # puppets yaml parsing, so just map it all to plain old strings desired = cells.map do |c| # parse %{}'s in the CSV into local variables using lookupvar() while c =~ /%\{(.+?)\}/ c.gsub!(/%\{#{$1}\}/, lookupvar($1)) end c.to_s end end end end end end desired || default or raise Puppet::ParseError, "No match found for '#{key}' in any data file during extlookup()" end end diff --git a/lib/puppet/parser/functions/fqdn_rand.rb b/lib/puppet/parser/functions/fqdn_rand.rb index 91157a148..93ab98bcd 100644 --- a/lib/puppet/parser/functions/fqdn_rand.rb +++ b/lib/puppet/parser/functions/fqdn_rand.rb @@ -1,12 +1,12 @@ Puppet::Parser::Functions::newfunction(:fqdn_rand, :type => :rvalue, :doc => "Generates random numbers based on the node's fqdn. Generated random values will be a range from 0 up to and excluding n, where n is the first parameter. The second argument specifies a number to add to the seed and is optional, for example: $random_number = fqdn_rand(30) $random_number_seed = fqdn_rand(30,30)") do |args| require 'digest/md5' max = args.shift - srand(Digest::MD5.hexdigest([lookupvar('fqdn'),args].join(':')).hex) + srand(Digest::MD5.hexdigest([lookupvar('::fqdn'),args].join(':')).hex) rand(max).to_s end diff --git a/lib/puppet/parser/resource.rb b/lib/puppet/parser/resource.rb index c007d4dbe..ace01bb4b 100644 --- a/lib/puppet/parser/resource.rb +++ b/lib/puppet/parser/resource.rb @@ -1,326 +1,326 @@ require 'puppet/resource' # The primary difference between this class and its # parent is that this class has rules on who can set # parameters class Puppet::Parser::Resource < Puppet::Resource require 'puppet/parser/resource/param' require 'puppet/util/tagging' require 'puppet/file_collection/lookup' require 'puppet/parser/yaml_trimmer' require 'puppet/resource/type_collection_helper' include Puppet::FileCollection::Lookup include Puppet::Resource::TypeCollectionHelper include Puppet::Util include Puppet::Util::MethodHelper include Puppet::Util::Errors include Puppet::Util::Logging include Puppet::Util::Tagging include Puppet::Parser::YamlTrimmer attr_accessor :source, :scope, :rails_id attr_accessor :virtual, :override, :translated, :catalog, :evaluated attr_reader :exported, :parameters # Determine whether the provided parameter name is a relationship parameter. def self.relationship_parameter?(name) @relationship_names ||= Puppet::Type.relationship_params.collect { |p| p.name } @relationship_names.include?(name) end # Set up some boolean test methods def translated?; !!@translated; end def override?; !!@override; end def evaluated?; !!@evaluated; end def [](param) param = symbolize(param) if param == :title return self.title end if @parameters.has_key?(param) @parameters[param].value else nil end end def []=(param, value) set_parameter(param, value) end def eachparam @parameters.each do |name, param| yield param end end def environment scope.environment end # Retrieve the associated definition and evaluate it. def evaluate return if evaluated? @evaluated = true if klass = resource_type and ! builtin_type? finish return klass.evaluate_code(self) elsif builtin? devfail "Cannot evaluate a builtin type (#{type})" else self.fail "Cannot find definition #{type}" end 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 return if finished? @finished = true add_defaults add_metaparams add_scope_tags validate end # Has this resource already been finished? def finished? @finished end def initialize(*args) raise ArgumentError, "Resources require a scope" unless args.last[:scope] super @source ||= scope.source end # Is this resource modeling an isomorphic resource type? def isomorphic? if builtin_type? return resource_type.isomorphic? else return true 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.parameters.each do |name, param| override_parameter(param) end end # Unless we're running >= 0.25, we're in compat mode. def metaparam_compatibility_mode? ! (catalog and ver = (catalog.client_version||'0.0.0').split(".") and (ver[0] > "0" or ver[1].to_i >= 25)) end def name self[:name] || self.title end # A temporary occasion, until I get paths in the scopes figured out. def path to_s end # Define a parameter in our resource. # if we ever receive a parameter named 'tag', set # the resource tags with its value. def set_parameter(param, value = nil) if ! value.nil? 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 tag(*param.value) if param.name == :tag # And store it in our parameter hash. @parameters[param.name] = param end def to_hash @parameters.inject({}) do |hash, ary| param = ary[1] # Skip "undef" values. hash[param.name] = param.value if param.value != :undef hash end end # Create a Puppet::Resource instance from this parser resource. # We plan, at some point, on not needing to do this conversion, but # it's sufficient for now. def to_resource result = Puppet::Resource.new(type, title) to_hash.each do |p, v| if v.is_a?(Puppet::Resource) v = Puppet::Resource.new(v.type, v.title) elsif v.is_a?(Array) # flatten resource references arrays v = v.flatten if v.flatten.find { |av| av.is_a?(Puppet::Resource) } v = v.collect do |av| av = Puppet::Resource.new(av.type, av.title) if av.is_a?(Puppet::Resource) av end 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. result[p] = if v.is_a?(Array) and v.length == 1 v[0] else v end end result.file = self.file result.line = self.line result.exported = self.exported result.virtual = self.virtual result.tag(*self.tags) result end # Translate our object to a transportable object. def to_trans return nil if virtual? to_resource.to_trans end # Convert this resource to a RAL resource. We hackishly go via the # transportable stuff. def to_ral to_resource.to_ral end private # Add default values from our definition. def add_defaults scope.lookupdefaults(self.type).each do |name, param| unless @parameters.include?(name) self.debug "Adding default for #{name}" @parameters[name] = param.dup end end end def add_backward_compatible_relationship_param(name) # Skip metaparams for which we get no value. - return unless val = scope.lookupvar(name.to_s, false) and val != :undefined + return unless val = scope.lookupvar(name.to_s) and val != :undefined # The default case: just set the value set_parameter(name, val) and return unless @parameters[name] # For relationship params, though, join the values (a la #446). @parameters[name].value = [@parameters[name].value, val].flatten 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 compat_mode = metaparam_compatibility_mode? Puppet::Type.eachmetaparam do |name| next unless self.class.relationship_parameter?(name) add_backward_compatible_relationship_param(name) if compat_mode end end def add_scope_tags if scope_resource = scope.resource tag(*scope_resource.tags) 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. (set_parameter(param) and return) unless current = @parameters[param.name] # The parameter is already set. Fail if they're not allowed to override it. unless param.source.child_of?(current.source) puts caller if Puppet[:trace] msg = "Parameter '#{param.name}' is already set on #{self}" msg += " by #{current.source}" if current.source.to_s != "" if current.file or current.line fields = [] fields << current.file if current.file fields << current.line.to_s if current.line msg += " at #{fields.join(":")}" end msg += "; cannot redefine" raise Puppet::ParseError.new(msg, param.line, param.file) end # If we've gotten this far, we're allowed to override. # Merge with previous value, if the parameter was generated with the +> # syntax. It's important that we use a copy of the new param instance # here, not the old one, and not the original new one, so that the source # is registered correctly for later overrides but the values aren't # implcitly shared when multiple resources are overrriden at once (see # ticket #3556). if param.add param = param.dup param.value = [current.value, param.value].flatten end set_parameter(param) end # Make sure the resource's parameters are all valid for the type. def validate @parameters.each do |name, param| validate_parameter(name) end rescue => detail fail Puppet::ParseError, detail.to_s end private def extract_parameters(params) params.each do |param| # Don't set the same parameter twice self.fail Puppet::ParseError, "Duplicate parameter '#{param.name}' for on #{self}" if @parameters[param.name] set_parameter(param) end end end diff --git a/lib/puppet/parser/scope.rb b/lib/puppet/parser/scope.rb index 24f1d01f7..8de9d60b1 100644 --- a/lib/puppet/parser/scope.rb +++ b/lib/puppet/parser/scope.rb @@ -1,518 +1,448 @@ # 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' require 'puppet/resource/type_collection_helper' class Puppet::Parser::Scope include Puppet::Resource::TypeCollectionHelper require 'puppet/parser/resource' AST = Puppet::Parser::AST Puppet::Util.logmethods(self) include Enumerable include Puppet::Util::Errors - attr_accessor :level, :source, :resource + attr_accessor :source, :resource attr_accessor :base, :keyword attr_accessor :top, :translated, :compiler - attr_accessor :parent + attr_accessor :parent, :dynamic attr_reader :namespaces # thin wrapper around an ephemeral # symbol table. # when a symbol class Ephemeral def initialize(parent=nil) @symbols = {} @parent = parent end [:include?, :delete, :[]=].each do |m| define_method(m) do |*args| @symbols.send(m, *args) end end def [](name) unless @symbols.include?(name) or @parent.nil? @parent[name] else @symbols[name] end end end # A demeterific shortcut to the catalog. def catalog compiler.catalog end def environment compiler.environment end # Proxy accessors def host @compiler.node.name end # Is the value true? This allows us to control the definition of truth # in one place. def self.true?(value) (value != false and value != "" and value != :undef) end # Is the value a number?, return the correct object or nil if not a number def self.number?(value) return nil unless value.is_a?(Fixnum) or value.is_a?(Bignum) or value.is_a?(Float) or value.is_a?(String) if value.is_a?(String) if value =~ /^-?\d+(:?\.\d+|(:?\.\d+)?e\d+)$/ return value.to_f elsif value =~ /^0x[0-9a-f]+$/i return value.to_i(16) elsif value =~ /^0[0-7]+$/ return value.to_i(8) elsif value =~ /^-?\d+$/ return value.to_i else return nil end end # it is one of Fixnum,Bignum or Float value 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 # Remove this when rebasing def environment compiler.environment end - # Are we the top scope? - def topscope? - @level == 1 - end - def find_hostclass(name) known_resource_types.find_hostclass(namespaces, name) end def find_definition(name) known_resource_types.find_definition(namespaces, name) end def findresource(string, name = nil) compiler.findresource(string, name) end # Initialize our new scope. Defaults to having no parent. def initialize(hash = {}) if hash.include?(:namespace) if n = hash[:namespace] @namespaces = [n] end hash.delete(:namespace) else @namespaces = [""] end hash.each { |name, val| method = name.to_s + "=" if self.respond_to? method self.send(method, val) else raise Puppet::DevError, "Invalid scope argument #{name}" end } extend_with_functions_module @tags = [] # The symbol table for this scope. This is where we store variables. @symtable = {} # the ephemeral symbol tables # those should not persist long, and are used for the moment only # for $0..$xy capture variables of regexes # this is actually implemented as a stack, with each ephemeral scope # shadowing the previous one @ephemeral = [ Ephemeral.new ] # 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] = {} } # The table for storing class singletons. This will only actually # be used by top scopes and node scopes. @class_scopes = {} 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) return parent.class_set(name,scope) if parent @class_scopes[name] = scope 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 k = klass.respond_to?(:name) ? klass.name : klass @class_scopes[k] || (parent && parent.class_scope(k)) 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] values end # Look up a defined type. def lookuptype(name) find_definition(name) || find_hostclass(name) end - def lookup_qualified_var(name, usestring) - parts = name.split(/::/) - shortname = parts.pop - klassname = parts.join("::") - klass = find_hostclass(klassname) - unless klass - warning "Could not look up qualified variable '#{name}'; class #{klassname} could not be found" - return usestring ? "" : :undefined - end - unless kscope = class_scope(klass) - warning "Could not look up qualified variable '#{name}'; class #{klassname} has not been evaluated" - return usestring ? "" : :undefined - end - kscope.lookupvar(shortname, usestring) + def undef_as(x,v) + (v == :undefined) ? x : (v == :undef) ? x : v end - private :lookup_qualified_var + def qualified_scope(classname) + raise "class #{classname} could not be found" unless klass = find_hostclass(classname) + raise "class #{classname} has not been evaluated" unless kscope = class_scope(klass) + kscope + end + + private :qualified_scope - # 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) + # Look up a variable. The simplest value search we do. + def lookupvar(name, options = {}) table = ephemeral?(name) ? @ephemeral.last : @symtable # 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 table[name]" here because the value might be false - if ephemeral_include?(name) or table.include?(name) - if usestring and table[name] == :undef - return "" - else - return table[name] + if name =~ /^(.*)::(.+)$/ + begin + qualified_scope($1).lookupvar($2,options) + rescue RuntimeError => e + location = (options[:file] && options[:line]) ? " at #{options[:file]}:#{options[:line]}" : '' + warning "Could not look up qualified variable '#{name}'; #{e.message}#{location}" + :undefined end - elsif self.parent - return parent.lookupvar(name, usestring) - elsif usestring - return "" + elsif ephemeral_include?(name) or table.include?(name) + # We can't use "if table[name]" here because the value might be false + if options[:dynamic] and self != compiler.topscope + location = (options[:file] && options[:line]) ? " at #{options[:file]}:#{options[:line]}" : '' + Puppet.deprecation_warning "Dynamic lookup of $#{name}#{location} will not be supported in future versions. Use a fully-qualified variable name or parameterized classes." + end + table[name] + elsif parent + parent.lookupvar(name,options.merge(:dynamic => (dynamic || options[:dynamic]))) else - return :undefined + :undefined end end # Return a hash containing our variables and their values, optionally (and # by default) including the values defined in our parent. Local values # shadow parent values. def to_hash(recursive = true) target = parent.to_hash(recursive) if recursive and parent target ||= Hash.new @symtable.keys.each { |name| value = @symtable[name] if value == :undef target.delete(name) else target[name] = value end } target end def namespaces @namespaces.dup end # Create a new scope and set these options. def newscope(options = {}) compiler.newscope(self, options) end def parent_module_name return nil unless @parent return nil unless @parent.source @parent.source.module_name end # Return the list of scopes up to the top scope, ordered with our own first. # This is used for looking up variables and defaults. def scope_path if parent [self, parent.scope_path].flatten.compact else [self] end end # Set defaults for a type. The typename should already be downcased, # so that the syntax is isolated. We don't do any kind of type-checking # here; instead we let the resource do it when the defaults are used. def setdefaults(type, params) table = @defaults[type] # if we got a single param, it'll be in its own array params = [params] unless params.is_a?(Array) params.each { |param| #Puppet.debug "Default for %s is %s => %s" % # [type,ary[0].inspect,ary[1].inspect] if table.include?(param.name) raise Puppet::ParseError.new("Default already defined for #{type} { #{param.name} }; cannot redefine", 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, options = {}) table = options[:ephemeral] ? @ephemeral.last : @symtable - #Puppet.debug "Setting %s to '%s' at level %s mode append %s" % - # [name.inspect,value,self.level, append] if table.include?(name) unless options[:append] error = Puppet::ParseError.new("Cannot reassign variable #{name}") else error = Puppet::ParseError.new("Cannot append, variable #{name} is defined in this scope") end error.file = options[:file] if options[:file] error.line = options[:line] if options[:line] raise error end unless options[:append] table[name] = value else # append case # lookup the value in the scope if it exists and insert the var - table[name] = lookupvar(name) + table[name] = undef_as('',lookupvar(name)) # concatenate if string, append if array, nothing for other types case value when Array table[name] += value when Hash raise ArgumentError, "Trying to append to a hash with something which is not a hash is unsupported" unless value.is_a?(Hash) table[name].merge!(value) else table[name] << value end end 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+|[0-9]+)\}|^\$([0-9])|^\$((\w*::)*\w+)/) - # If it matches the backslash, then just retun the dollar sign. - if ss.matched == '\\$' - out << '$' - else # look the variable up - # make sure $0-$9 are lookupable only if ephemeral - var = ss[1] || ss[3] || ss[4] - if var and var =~ /^[0-9]+$/ and not ephemeral_include?(var) - next - end - out << lookupvar(var).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}'" - str += " in file #{file}" if file - str += " at line #{line}" if line - 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 #{string.inspect}") - {:file= => file, :line= => line}.each do |m,v| - error.send(m, v) if v - end - raise error - end - out << tmp - end - end - - out - end - # Return the tags associated with this scope. It's basically # just our parents' tags, plus our type. We don't cache this value # because our parent tags might change between calls. def tags resource.tags end # Used mainly for logging def to_s "Scope(#{@resource})" end # Undefine a variable; only used for testing. def unsetvar(var) table = ephemeral?(var) ? @ephemeral.last : @symtable table.delete(var) if table.include?(var) end # remove ephemeral scope up to level def unset_ephemeral_var(level=:all) if level == :all @ephemeral = [ Ephemeral.new ] else (@ephemeral.size - level).times do @ephemeral.pop end end end # check if name exists in one of the ephemeral scope. def ephemeral_include?(name) @ephemeral.reverse.each do |eph| return true if eph.include?(name) end false end # is name an ephemeral variable? def ephemeral?(name) name =~ /^\d+$/ end def ephemeral_level @ephemeral.size end def new_ephemeral @ephemeral.push(Ephemeral.new(@ephemeral.last)) end def ephemeral_from(match, file = nil, line = nil) raise(ArgumentError,"Invalid regex match data") unless match.is_a?(MatchData) new_ephemeral setvar("0", match[0], :file => file, :line => line, :ephemeral => true) match.captures.each_with_index do |m,i| setvar("#{i+1}", m, :file => file, :line => line, :ephemeral => true) end end def find_resource_type(type) # It still works fine without the type == 'class' short-cut, but it is a lot slower. return nil if ["class", "node"].include? type.to_s.downcase find_builtin_resource_type(type) || find_defined_resource_type(type) end def find_builtin_resource_type(type) Puppet::Type.type(type.to_s.downcase.to_sym) end def find_defined_resource_type(type) environment.known_resource_types.find_definition(namespaces, type.to_s.downcase) end def resolve_type_and_titles(type, titles) raise ArgumentError, "titles must be an array" unless titles.is_a?(Array) case type.downcase when "class" # resolve the titles titles = titles.collect do |a_title| hostclass = find_hostclass(a_title) hostclass ? hostclass.name : a_title end when "node" # no-op else # resolve the type resource_type = find_resource_type(type) type = resource_type.name if resource_type end return [type, titles] end private def extend_with_functions_module extend Puppet::Parser::Functions.environment_module(Puppet::Node::Environment.root) extend Puppet::Parser::Functions.environment_module(compiler ? environment : nil) end end diff --git a/lib/puppet/parser/templatewrapper.rb b/lib/puppet/parser/templatewrapper.rb index 6864aa1a9..180a03dc9 100644 --- a/lib/puppet/parser/templatewrapper.rb +++ b/lib/puppet/parser/templatewrapper.rb @@ -1,115 +1,115 @@ # A simple wrapper for templates, so they don't have full access to # the scope objects. require 'puppet/parser/files' require 'erb' class Puppet::Parser::TemplateWrapper attr_writer :scope attr_reader :file attr_accessor :string include Puppet::Util Puppet::Util.logmethods(self) def initialize(scope) @__scope__ = scope end def scope @__scope__ end + def script_line + # find which line in the template (if any) we were called from + caller.find { |l| l =~ /#{file}:/ }.first[/:(\d+):/,1] + end + # Should return true if a variable is defined, false if it is not def has_variable?(name) - if scope.lookupvar(name.to_s, false) != :undefined - true - else - false - end + scope.lookupvar(name.to_s, :file => file, :line => script_line) != :undefined end # Allow templates to access the defined classes def classes scope.catalog.classes end # Allow templates to access the tags defined in the current scope def tags scope.tags end # Allow templates to access the all the defined tags def all_tags scope.catalog.tags end # Ruby treats variables like methods, so we used to expose variables # within scope to the ERB code via method_missing. As per RedMine #1427, # though, this means that conflicts between methods in our inheritance # tree (Kernel#fork) and variable names (fork => "yes/no") could arise. # # Worse, /new/ conflicts could pop up when a new kernel or object method # was added to Ruby, causing templates to suddenly fail mysteriously when # Ruby was upgraded. # # To ensure that legacy templates using unqualified names work we retain # the missing_method definition here until we declare the syntax finally # dead. 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) + value = scope.lookupvar(name.to_s,:file => file,:line => script_line) 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 '#{name}'" + raise Puppet::ParseError.new("Could not find value for '#{name}'",@file,script_line) end end def file=(filename) unless @file = Puppet::Parser::Files.find_template(filename, scope.compiler.environment.to_s) raise Puppet::ParseError, "Could not find template '#{filename}'" end # We'll only ever not have a parser in testing, but, eh. scope.known_resource_types.watch_file(file) @string = File.read(file) end def result(string = nil) if string self.string = string template_source = "inline template" else template_source = file end # Expose all the variables in our scope as instance variables of the # current object, making it possible to access them without conflict # to the regular methods. benchmark(:debug, "Bound template variables for #{template_source}") do scope.to_hash.each { |name, value| if name.kind_of?(String) realname = name.gsub(/[^\w]/, "_") else realname = name end instance_variable_set("@#{realname}", value) } end result = nil benchmark(:debug, "Interpolated template #{template_source}") do template = ERB.new(self.string, 0, "-") + template.filename = file result = template.result(binding) end result end def to_s "template[#{(file ? file : "inline")}]" end end diff --git a/lib/puppet/resource/type.rb b/lib/puppet/resource/type.rb index 48d8c1f48..f8d820b77 100644 --- a/lib/puppet/resource/type.rb +++ b/lib/puppet/resource/type.rb @@ -1,349 +1,342 @@ require 'puppet/parser/parser' require 'puppet/util/warnings' require 'puppet/util/errors' require 'puppet/util/inline_docs' require 'puppet/parser/ast/leaf' require 'puppet/dsl' class Puppet::Resource::Type Puppet::ResourceType = self include Puppet::Util::InlineDocs include Puppet::Util::Warnings include Puppet::Util::Errors RESOURCE_SUPERTYPES = [:hostclass, :node, :definition] attr_accessor :file, :line, :doc, :code, :ruby_code, :parent, :resource_type_collection attr_reader :type, :namespace, :arguments, :behaves_like, :module_name RESOURCE_SUPERTYPES.each do |t| define_method("#{t}?") { self.type == t } end require 'puppet/indirector' extend Puppet::Indirector indirects :resource_type, :terminus_class => :parser def self.from_pson(data) name = data.delete('name') or raise ArgumentError, "Resource Type names must be specified" type = data.delete('type') || "definition" data = data.inject({}) { |result, ary| result[ary[0].intern] = ary[1]; result } new(type, name, data) end def to_pson_data_hash data = [:doc, :line, :file, :parent].inject({}) do |hash, param| next hash unless (value = self.send(param)) and (value != "") hash[param.to_s] = value hash end data['arguments'] = arguments.dup unless arguments.empty? data['name'] = name data['type'] = type data end def to_pson(*args) to_pson_data_hash.to_pson(*args) end # Are we a child of the passed class? Do a recursive search up our # parentage tree to figure it out. def child_of?(klass) return false unless parent return(klass == parent_type ? true : parent_type.child_of?(klass)) end # Now evaluate the code associated with this class or definition. def evaluate_code(resource) - scope = resource.scope - if tmp = evaluate_parent_type(resource) - scope = tmp - end + static_parent = evaluate_parent_type(resource) + scope = static_parent || resource.scope - scope = subscope(scope, resource) unless resource.title == :main + scope = scope.newscope(:namespace => namespace, :source => self, :resource => resource, :dynamic => !static_parent) unless resource.title == :main scope.compiler.add_class(name) unless definition? set_resource_parameters(resource, scope) code.safeevaluate(scope) if code evaluate_ruby_code(resource, scope) if ruby_code end def initialize(type, name, options = {}) @type = type.to_s.downcase.to_sym raise ArgumentError, "Invalid resource supertype '#{type}'" unless RESOURCE_SUPERTYPES.include?(@type) name = convert_from_ast(name) if name.is_a?(Puppet::Parser::AST::HostName) set_name_and_namespace(name) [:code, :doc, :line, :file, :parent].each do |param| next unless value = options[param] send(param.to_s + "=", value) end set_arguments(options[:arguments]) @module_name = options[:module_name] end # This is only used for node names, and really only when the node name # is a regexp. def match(string) return string.to_s.downcase == name unless name_is_regex? @name =~ string end # Add code from a new instance to our code. def merge(other) fail "#{name} is not a class; cannot add code to it" unless type == :hostclass fail "#{other.name} is not a class; cannot add code from it" unless other.type == :hostclass fail "Cannot have code outside of a class/node/define because 'freeze_main' is enabled" if name == "" and Puppet.settings[:freeze_main] if parent and other.parent and parent != other.parent fail "Cannot merge classes with different parent classes (#{name} => #{parent} vs. #{other.name} => #{other.parent})" end # We know they're either equal or only one is set, so keep whichever parent is specified. self.parent ||= other.parent if other.doc self.doc ||= "" self.doc += other.doc end # This might just be an empty, stub class. return unless other.code unless self.code self.code = other.code return end array_class = Puppet::Parser::AST::ASTArray self.code = array_class.new(:children => [self.code]) unless self.code.is_a?(array_class) if other.code.is_a?(array_class) code.children += other.code.children else code.children << other.code end end # Make an instance of the resource type, and place it in the catalog # if it isn't in the catalog already. This is only possible for # classes and nodes. No parameters are be supplied--if this is a # parameterized class, then all parameters take on their default # values. def ensure_in_catalog(scope, parameters=nil) type == :definition and raise ArgumentError, "Cannot create resources for defined resource types" resource_type = type == :hostclass ? :class : :node # Do nothing if the resource already exists; this makes sure we don't # get multiple copies of the class resource, which helps provide the # singleton nature of classes. # we should not do this for classes with parameters # if parameters are passed, we should still try to create the resource # even if it exists so that we can fail # this prevents us from being able to combine param classes with include if resource = scope.catalog.resource(resource_type, name) and !parameters return resource end resource = Puppet::Parser::Resource.new(resource_type, name, :scope => scope, :source => self) if parameters parameters.each do |k,v| resource.set_parameter(k,v) end end instantiate_resource(scope, resource) scope.compiler.add_resource(scope, resource) resource end def instantiate_resource(scope, resource) # Make sure our parent class has been evaluated, if we have one. if parent && !scope.catalog.resource(resource.type, parent) parent_type(scope).ensure_in_catalog(scope) end if ['Class', 'Node'].include? resource.type scope.catalog.tag(*resource.tags) end end def name return @name unless @name.is_a?(Regexp) @name.source.downcase.gsub(/[^-\w:.]/,'').sub(/^\.+/,'') end def name_is_regex? @name.is_a?(Regexp) end # MQR TODO: # # The change(s) introduced by the fix for #4270 are mostly silly & should be # removed, though we didn't realize it at the time. If it can be established/ # ensured that nodes never call parent_type and that resource_types are always # (as they should be) members of exactly one resource_type_collection the # following method could / should be replaced with: # # def parent_type # @parent_type ||= parent && ( # resource_type_collection.find_or_load([name],parent,type.to_sym) || # fail Puppet::ParseError, "Could not find parent resource type '#{parent}' of type #{type} in #{resource_type_collection.environment}" # ) # end # # ...and then the rest of the changes around passing in scope reverted. # def parent_type(scope = nil) return nil unless parent unless @parent_type raise "Must pass scope to parent_type when called first time" unless scope unless @parent_type = scope.environment.known_resource_types.send("find_#{type}", [name], parent) fail Puppet::ParseError, "Could not find parent resource type '#{parent}' of type #{type} in #{scope.environment}" end end @parent_type end # Set any arguments passed by the resource as variables in the scope. def set_resource_parameters(resource, scope) set = {} resource.to_hash.each do |param, value| param = param.to_sym fail Puppet::ParseError, "#{resource.ref} does not accept attribute #{param}" unless valid_parameter?(param) exceptwrap { scope.setvar(param.to_s, value) } set[param] = true end if @type == :hostclass scope.setvar("title", resource.title.to_s.downcase) unless set.include? :title scope.setvar("name", resource.name.to_s.downcase ) unless set.include? :name else scope.setvar("title", resource.title ) unless set.include? :title scope.setvar("name", resource.name ) unless set.include? :name end scope.setvar("module_name", module_name) if module_name and ! set.include? :module_name if caller_name = scope.parent_module_name and ! set.include?(:caller_module_name) scope.setvar("caller_module_name", caller_name) end scope.class_set(self.name,scope) if hostclass? or node? # Verify that all required arguments are either present or # have been provided with defaults. arguments.each do |param, default| param = param.to_sym next if set.include?(param) # Even if 'default' is a false value, it's an AST value, so this works fine fail Puppet::ParseError, "Must pass #{param} to #{resource.ref}" unless default value = default.safeevaluate(scope) scope.setvar(param.to_s, value) # Set it in the resource, too, so the value makes it to the client. resource[param] = value end end - # Create a new subscope in which to evaluate our code. - def subscope(scope, resource) - scope.newscope :resource => resource, :namespace => self.namespace, :source => self - end - # Check whether a given argument is valid. def valid_parameter?(param) param = param.to_s return true if param == "name" return true if Puppet::Type.metaparam?(param) return false unless defined?(@arguments) return(arguments.include?(param) ? true : false) end def set_arguments(arguments) @arguments = {} return if arguments.nil? arguments.each do |arg, default| arg = arg.to_s warn_if_metaparam(arg, default) @arguments[arg] = default end end private def convert_from_ast(name) value = name.value if value.is_a?(Puppet::Parser::AST::Regex) name = value.value else name = value end end def evaluate_parent_type(resource) return unless klass = parent_type(resource.scope) and parent_resource = resource.scope.compiler.catalog.resource(:class, klass.name) || resource.scope.compiler.catalog.resource(:node, klass.name) parent_resource.evaluate unless parent_resource.evaluated? parent_scope(resource.scope, klass) end def evaluate_ruby_code(resource, scope) Puppet::DSL::ResourceAPI.new(resource, scope, ruby_code).evaluate 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 def parent_scope(scope, klass) scope.class_scope(klass) || raise(Puppet::DevError, "Could not find scope for #{klass.name}") end def set_name_and_namespace(name) if name.is_a?(Regexp) @name = name @namespace = "" else @name = name.to_s.downcase # 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. @namespace, ignored_shortname = @type == :hostclass ? [@name, ''] : namesplit(@name) end end def warn_if_metaparam(param, default) return unless Puppet::Type.metaparamclass(param) if default warnonce "#{param} is a metaparam; this value will inherit to all contained resources" else raise Puppet::ParseError, "#{param} is a metaparameter; please choose another parameter name in the #{self.name} definition" end end end diff --git a/lib/puppet/util/logging.rb b/lib/puppet/util/logging.rb index bc52b17f0..4e76ae414 100644 --- a/lib/puppet/util/logging.rb +++ b/lib/puppet/util/logging.rb @@ -1,40 +1,51 @@ # A module to make logging a bit easier. require 'puppet/util/log' module Puppet::Util::Logging def send_log(level, message) Puppet::Util::Log.create({:level => level, :source => log_source, :message => message}.merge(log_metadata)) end # Create a method for each log level. Puppet::Util::Log.eachlevel do |level| define_method(level) do |args| args = args.join(" ") if args.is_a?(Array) send_log(level, args) end end + def deprecation_warning(message) + $deprecation_warnings ||= Hash.new(0) + if $deprecation_warnings.length < 100 and ($deprecation_warnings[message] += 1) == 1 + warning message + end + end + + def clear_deprecation_warnings + $deprecation_warnings.clear if $deprecation_warnings + end + private def is_resource? defined?(Puppet::Type) && is_a?(Puppet::Type) end def is_resource_parameter? defined?(Puppet::Parameter) && is_a?(Puppet::Parameter) end def log_metadata [:file, :line, :tags].inject({}) do |result, attr| result[attr] = send(attr) if respond_to?(attr) result end end def log_source # We need to guard the existence of the constants, since this module is used by the base Puppet module. (is_resource? or is_resource_parameter?) and respond_to?(:path) and return path.to_s to_s end end diff --git a/spec/unit/parser/ast/arithmetic_operator_spec.rb b/spec/unit/parser/ast/arithmetic_operator_spec.rb index 6f57fd9b6..9b54f09d8 100755 --- a/spec/unit/parser/ast/arithmetic_operator_spec.rb +++ b/spec/unit/parser/ast/arithmetic_operator_spec.rb @@ -1,73 +1,63 @@ #!/usr/bin/env ruby require 'spec_helper' describe Puppet::Parser::AST::ArithmeticOperator do ast = Puppet::Parser::AST before :each do @scope = Puppet::Parser::Scope.new @one = stub 'lval', :safeevaluate => 1 @two = stub 'rval', :safeevaluate => 2 end it "should evaluate both branches" do lval = stub "lval" lval.expects(:safeevaluate).with(@scope).returns(1) rval = stub "rval" rval.expects(:safeevaluate).with(@scope).returns(2) operator = ast::ArithmeticOperator.new :rval => rval, :operator => "+", :lval => lval operator.evaluate(@scope) end it "should fail for an unknown operator" do lambda { operator = ast::ArithmeticOperator.new :lval => @one, :operator => "%", :rval => @two }.should raise_error end it "should call Puppet::Parser::Scope.number?" do Puppet::Parser::Scope.expects(:number?).with(1).returns(1) Puppet::Parser::Scope.expects(:number?).with(2).returns(2) ast::ArithmeticOperator.new(:lval => @one, :operator => "+", :rval => @two).evaluate(@scope) end %w{ + - * / << >>}.each do |op| it "should call ruby Numeric '#{op}'" do one = stub 'one' two = stub 'two' operator = ast::ArithmeticOperator.new :lval => @one, :operator => op, :rval => @two Puppet::Parser::Scope.stubs(:number?).with(1).returns(one) Puppet::Parser::Scope.stubs(:number?).with(2).returns(two) one.expects(:send).with(op,two) operator.evaluate(@scope) end end it "should work even with numbers embedded in strings" do two = stub 'two', :safeevaluate => "2" one = stub 'one', :safeevaluate => "1" operator = ast::ArithmeticOperator.new :lval => two, :operator => "+", :rval => one operator.evaluate(@scope).should == 3 end it "should work even with floats" do two = stub 'two', :safeevaluate => 2.53 one = stub 'one', :safeevaluate => 1.80 operator = ast::ArithmeticOperator.new :lval => two, :operator => "+", :rval => one operator.evaluate(@scope).should == 4.33 end - it "should work for variables too" do - @scope.expects(:lookupvar).with("one", false).returns(1) - @scope.expects(:lookupvar).with("two", false).returns(2) - one = ast::Variable.new( :value => "one" ) - two = ast::Variable.new( :value => "two" ) - - operator = ast::ArithmeticOperator.new :lval => one, :operator => "+", :rval => two - operator.evaluate(@scope).should == 3 - end - end diff --git a/spec/unit/parser/ast/comparison_operator_spec.rb b/spec/unit/parser/ast/comparison_operator_spec.rb index 169d92d0a..060827390 100755 --- a/spec/unit/parser/ast/comparison_operator_spec.rb +++ b/spec/unit/parser/ast/comparison_operator_spec.rb @@ -1,116 +1,116 @@ #!/usr/bin/env ruby require 'spec_helper' describe Puppet::Parser::AST::ComparisonOperator do before :each do @scope = Puppet::Parser::Scope.new @one = Puppet::Parser::AST::Leaf.new(:value => "1") @two = Puppet::Parser::AST::Leaf.new(:value => "2") @lval = Puppet::Parser::AST::Leaf.new(:value => "one") @rval = Puppet::Parser::AST::Leaf.new(:value => "two") end it "should evaluate both values" do @lval.expects(:safeevaluate).with(@scope) @rval.expects(:safeevaluate).with(@scope) operator = Puppet::Parser::AST::ComparisonOperator.new :lval => @lval, :operator => "==", :rval => @rval operator.evaluate(@scope) end it "should convert the arguments to numbers if they are numbers in string" do Puppet::Parser::Scope.expects(:number?).with("1").returns(1) Puppet::Parser::Scope.expects(:number?).with("2").returns(2) operator = Puppet::Parser::AST::ComparisonOperator.new :lval => @one, :operator => "==", :rval => @two operator.evaluate(@scope) end %w{< > <= >=}.each do |oper| it "should use string comparison #{oper} if operands are strings" do operator = Puppet::Parser::AST::ComparisonOperator.new :lval => @lval, :operator => oper, :rval => @rval operator.evaluate(@scope).should == "one".send(oper,"two") end end describe "with string comparison" do it "should use matching" do @rval.expects(:evaluate_match).with("one", @scope) operator = Puppet::Parser::AST::ComparisonOperator.new :lval => @lval, :operator => "==", :rval => @rval operator.evaluate(@scope) end it "should return true for :undef to '' equality" do astundef = Puppet::Parser::AST::Leaf.new(:value => :undef) empty = Puppet::Parser::AST::Leaf.new(:value => '') operator = Puppet::Parser::AST::ComparisonOperator.new :lval => astundef, :operator => "==", :rval => empty operator.evaluate(@scope).should be_true end [true, false].each do |result| it "should return #{(result).inspect} with '==' when matching return #{result.inspect}" do @rval.expects(:evaluate_match).with("one", @scope).returns result operator = Puppet::Parser::AST::ComparisonOperator.new :lval => @lval, :operator => "==", :rval => @rval operator.evaluate(@scope).should == result end it "should return #{(!result).inspect} with '!=' when matching return #{result.inspect}" do @rval.expects(:evaluate_match).with("one", @scope).returns result operator = Puppet::Parser::AST::ComparisonOperator.new :lval => @lval, :operator => "!=", :rval => @rval operator.evaluate(@scope).should == !result end end end it "should fail with arguments of different types" do operator = Puppet::Parser::AST::ComparisonOperator.new :lval => @one, :operator => ">", :rval => @rval lambda { operator.evaluate(@scope) }.should raise_error(ArgumentError) end it "should fail for an unknown operator" do lambda { operator = Puppet::Parser::AST::ComparisonOperator.new :lval => @one, :operator => "or", :rval => @two }.should raise_error end %w{< > <= >= ==}.each do |oper| it "should return the result of using '#{oper}' to compare the left and right sides" do operator = Puppet::Parser::AST::ComparisonOperator.new :lval => @one, :operator => oper, :rval => @two operator.evaluate(@scope).should == 1.send(oper,2) end end it "should return the result of using '!=' to compare the left and right sides" do operator = Puppet::Parser::AST::ComparisonOperator.new :lval => @one, :operator => '!=', :rval => @two operator.evaluate(@scope).should == true end it "should work for variables too" do one = Puppet::Parser::AST::Variable.new( :value => "one" ) two = Puppet::Parser::AST::Variable.new( :value => "two" ) - @scope.expects(:lookupvar).with("one", false).returns(1) - @scope.expects(:lookupvar).with("two", false).returns(2) + one.expects(:safeevaluate).with(@scope).returns(1) + two.expects(:safeevaluate).with(@scope).returns(2) operator = Puppet::Parser::AST::ComparisonOperator.new :lval => one, :operator => "<", :rval => two operator.evaluate(@scope).should == true end # see ticket #1759 %w{< > <= >=}.each do |oper| it "should return the correct result of using '#{oper}' to compare 10 and 9" do ten = Puppet::Parser::AST::Leaf.new(:value => "10") nine = Puppet::Parser::AST::Leaf.new(:value => "9") operator = Puppet::Parser::AST::ComparisonOperator.new :lval => ten, :operator => oper, :rval => nine operator.evaluate(@scope).should == 10.send(oper,9) end end end diff --git a/spec/unit/parser/ast/leaf_spec.rb b/spec/unit/parser/ast/leaf_spec.rb index 3b8c14efa..cd09eaf94 100755 --- a/spec/unit/parser/ast/leaf_spec.rb +++ b/spec/unit/parser/ast/leaf_spec.rb @@ -1,408 +1,413 @@ #!/usr/bin/env ruby require 'spec_helper' describe Puppet::Parser::AST::Leaf do before :each do @scope = stub 'scope' @value = stub 'value' @leaf = Puppet::Parser::AST::Leaf.new(:value => @value) end it "should have a evaluate_match method" do Puppet::Parser::AST::Leaf.new(:value => "value").should respond_to(:evaluate_match) end describe "when converting to string" do it "should transform its value to string" do value = stub 'value', :is_a? => true value.expects(:to_s) Puppet::Parser::AST::Leaf.new( :value => value ).to_s end end it "should have a match method" do @leaf.should respond_to(:match) end it "should delegate match to ==" do @value.expects(:==).with("value") @leaf.match("value") end end describe Puppet::Parser::AST::FlatString do describe "when converting to string" do it "should transform its value to a quoted string" do value = stub 'value', :is_a? => true, :to_s => "ab" Puppet::Parser::AST::FlatString.new( :value => value ).to_s.should == "\"ab\"" end end end describe Puppet::Parser::AST::String do describe "when converting to string" do it "should transform its value to a quoted string" do value = stub 'value', :is_a? => true, :to_s => "ab" Puppet::Parser::AST::String.new( :value => value ).to_s.should == "\"ab\"" end it "should return a dup of its value" do value = "" Puppet::Parser::AST::String.new( :value => value ).evaluate(stub('scope')).should_not be_equal(value) end end end describe Puppet::Parser::AST::Concat do describe "when evaluating" do before :each do @scope = stub_everything 'scope' end it "should interpolate variables and concatenate their values" do one = Puppet::Parser::AST::String.new(:value => "one") one.stubs(:evaluate).returns("one ") two = Puppet::Parser::AST::String.new(:value => "two") two.stubs(:evaluate).returns(" two ") three = Puppet::Parser::AST::String.new(:value => "three") three.stubs(:evaluate).returns(" three") var = Puppet::Parser::AST::Variable.new(:value => "myvar") var.stubs(:evaluate).returns("foo") array = Puppet::Parser::AST::Variable.new(:value => "array") array.stubs(:evaluate).returns(["bar","baz"]) concat = Puppet::Parser::AST::Concat.new(:value => [one,var,two,array,three]) concat.evaluate(@scope).should == 'one foo two barbaz three' end it "should transform undef variables to empty string" do var = Puppet::Parser::AST::Variable.new(:value => "myvar") var.stubs(:evaluate).returns(:undef) concat = Puppet::Parser::AST::Concat.new(:value => [var]) concat.evaluate(@scope).should == '' end end end describe Puppet::Parser::AST::Undef do before :each do @scope = stub 'scope' @undef = Puppet::Parser::AST::Undef.new(:value => :undef) end it "should match undef with undef" do @undef.evaluate_match(:undef, @scope).should be_true end it "should not match undef with an empty string" do @undef.evaluate_match("", @scope).should be_false end end describe Puppet::Parser::AST::HashOrArrayAccess do before :each do @scope = stub 'scope' end describe "when evaluating" do it "should evaluate the variable part if necessary" do - @scope.stubs(:lookupvar).with("a").returns(["b"]) + @scope.stubs(:lookupvar).with { |name,options| name == 'a'}.returns(["b"]) variable = stub 'variable', :evaluate => "a" access = Puppet::Parser::AST::HashOrArrayAccess.new(:variable => variable, :key => 0 ) variable.expects(:safeevaluate).with(@scope).returns("a") access.evaluate(@scope).should == "b" end it "should evaluate the access key part if necessary" do - @scope.stubs(:lookupvar).with("a").returns(["b"]) + @scope.stubs(:lookupvar).with { |name,options| name == 'a'}.returns(["b"]) index = stub 'index', :evaluate => 0 access = Puppet::Parser::AST::HashOrArrayAccess.new(:variable => "a", :key => index ) index.expects(:safeevaluate).with(@scope).returns(0) access.evaluate(@scope).should == "b" end it "should be able to return an array member" do - @scope.stubs(:lookupvar).with("a").returns(["val1", "val2", "val3"]) + @scope.stubs(:lookupvar).with { |name,options| name == 'a'}.returns(["val1", "val2", "val3"]) access = Puppet::Parser::AST::HashOrArrayAccess.new(:variable => "a", :key => 1 ) access.evaluate(@scope).should == "val2" end it "should be able to return an array member when index is a stringified number" do - @scope.stubs(:lookupvar).with("a").returns(["val1", "val2", "val3"]) + @scope.stubs(:lookupvar).with { |name,options| name == "a" }.returns(["val1", "val2", "val3"]) access = Puppet::Parser::AST::HashOrArrayAccess.new(:variable => "a", :key => "1" ) access.evaluate(@scope).should == "val2" end it "should raise an error when accessing an array with a key" do - @scope.stubs(:lookupvar).with("a").returns(["val1", "val2", "val3"]) + @scope.stubs(:lookupvar).with { |name,options| name == "a"}.returns(["val1", "val2", "val3"]) access = Puppet::Parser::AST::HashOrArrayAccess.new(:variable => "a", :key => "get_me_the_second_element_please" ) lambda { access.evaluate(@scope) }.should raise_error end it "should be able to return an hash value" do - @scope.stubs(:lookupvar).with("a").returns({ "key1" => "val1", "key2" => "val2", "key3" => "val3" }) + @scope.stubs(:lookupvar).with { |name,options| name == 'a'}.returns({ "key1" => "val1", "key2" => "val2", "key3" => "val3" }) access = Puppet::Parser::AST::HashOrArrayAccess.new(:variable => "a", :key => "key2" ) access.evaluate(@scope).should == "val2" end it "should be able to return an hash value with a numerical key" do - @scope.stubs(:lookupvar).with("a").returns({ "key1" => "val1", "key2" => "val2", "45" => "45", "key3" => "val3" }) + @scope.stubs(:lookupvar).with { |name,options| name == "a"}.returns({ "key1" => "val1", "key2" => "val2", "45" => "45", "key3" => "val3" }) access = Puppet::Parser::AST::HashOrArrayAccess.new(:variable => "a", :key => "45" ) access.evaluate(@scope).should == "45" end it "should raise an error if the variable lookup didn't return an hash or an array" do - @scope.stubs(:lookupvar).with("a").returns("I'm a string") + @scope.stubs(:lookupvar).with { |name,options| name == "a"}.returns("I'm a string") access = Puppet::Parser::AST::HashOrArrayAccess.new(:variable => "a", :key => "key2" ) lambda { access.evaluate(@scope) }.should raise_error end it "should raise an error if the variable wasn't in the scope" do - @scope.stubs(:lookupvar).with("a").returns(nil) + @scope.stubs(:lookupvar).with { |name,options| name == 'a'}.returns(nil) access = Puppet::Parser::AST::HashOrArrayAccess.new(:variable => "a", :key => "key2" ) lambda { access.evaluate(@scope) }.should raise_error end it "should return a correct string representation" do access = Puppet::Parser::AST::HashOrArrayAccess.new(:variable => "a", :key => "key2" ) access.to_s.should == '$a[key2]' end it "should work with recursive hash access" do - @scope.stubs(:lookupvar).with("a").returns({ "key" => { "subkey" => "b" }}) + @scope.stubs(:lookupvar).with { |name,options| name == 'a'}.returns({ "key" => { "subkey" => "b" }}) access1 = Puppet::Parser::AST::HashOrArrayAccess.new(:variable => "a", :key => "key") access2 = Puppet::Parser::AST::HashOrArrayAccess.new(:variable => access1, :key => "subkey") access2.evaluate(@scope).should == 'b' end it "should work with interleaved array and hash access" do - @scope.stubs(:lookupvar).with("a").returns({ "key" => [ "a" , "b" ]}) + @scope.stubs(:lookupvar).with { |name,options| name == 'a'}.returns({ "key" => [ "a" , "b" ]}) access1 = Puppet::Parser::AST::HashOrArrayAccess.new(:variable => "a", :key => "key") access2 = Puppet::Parser::AST::HashOrArrayAccess.new(:variable => access1, :key => 1) access2.evaluate(@scope).should == 'b' end end describe "when assigning" do it "should add a new key and value" do scope = Puppet::Parser::Scope.new scope.setvar("a", { 'a' => 'b' }) access = Puppet::Parser::AST::HashOrArrayAccess.new(:variable => "a", :key => "b") access.assign(scope, "c" ) scope.lookupvar("a").should be_include("b") end it "should raise an error when assigning an array element with a key" do - @scope.stubs(:lookupvar).with("a").returns([]) + @scope.stubs(:lookupvar).with { |name,options| name == "a"}.returns([]) access = Puppet::Parser::AST::HashOrArrayAccess.new(:variable => "a", :key => "get_me_the_second_element_please" ) lambda { access.assign(@scope, "test") }.should raise_error end it "should be able to return an array member when index is a stringified number" do scope = Puppet::Parser::Scope.new scope.setvar("a", []) access = Puppet::Parser::AST::HashOrArrayAccess.new(:variable => "a", :key => "0" ) access.assign(scope, "val2") scope.lookupvar("a").should == ["val2"] end it "should raise an error when trying to overwrite an hash value" do - @scope.stubs(:lookupvar).with("a").returns({ "key" => [ "a" , "b" ]}) + @scope.stubs(:lookupvar).with { |name,options| name == "a" }.returns({ "key" => [ "a" , "b" ]}) access = Puppet::Parser::AST::HashOrArrayAccess.new(:variable => "a", :key => "key") lambda { access.assign(@scope, "test") }.should raise_error end end end describe Puppet::Parser::AST::Regex do before :each do @scope = stub 'scope' end describe "when initializing" do it "should create a Regexp with its content when value is not a Regexp" do Regexp.expects(:new).with("/ab/") Puppet::Parser::AST::Regex.new :value => "/ab/" end it "should not create a Regexp with its content when value is a Regexp" do value = Regexp.new("/ab/") Regexp.expects(:new).with("/ab/").never Puppet::Parser::AST::Regex.new :value => value end end describe "when evaluating" do it "should return self" do val = Puppet::Parser::AST::Regex.new :value => "/ab/" val.evaluate(@scope).should === val end end describe "when evaluate_match" do before :each do @value = stub 'regex' @value.stubs(:match).with("value").returns(true) Regexp.stubs(:new).returns(@value) @regex = Puppet::Parser::AST::Regex.new :value => "/ab/" end it "should issue the regexp match" do @value.expects(:match).with("value") @regex.evaluate_match("value", @scope) end it "should not downcase the paramater value" do @value.expects(:match).with("VaLuE") @regex.evaluate_match("VaLuE", @scope) end it "should set ephemeral scope vars if there is a match" do @scope.expects(:ephemeral_from).with(true, nil, nil) @regex.evaluate_match("value", @scope) end it "should return the match to the caller" do @value.stubs(:match).with("value").returns(:match) @scope.stubs(:ephemeral_from) @regex.evaluate_match("value", @scope) end end it "should return the regex source with to_s" do regex = stub 'regex' Regexp.stubs(:new).returns(regex) val = Puppet::Parser::AST::Regex.new :value => "/ab/" regex.expects(:source) val.to_s end it "should delegate match to the underlying regexp match method" do regex = Regexp.new("/ab/") val = Puppet::Parser::AST::Regex.new :value => regex regex.expects(:match).with("value") val.match("value") end end describe Puppet::Parser::AST::Variable do before :each do @scope = stub 'scope' - @var = Puppet::Parser::AST::Variable.new(:value => "myvar") + @var = Puppet::Parser::AST::Variable.new(:value => "myvar", :file => 'my.pp', :line => 222) end it "should lookup the variable in scope" do - @scope.expects(:lookupvar).with("myvar", false).returns(:myvalue) + @scope.expects(:lookupvar).with { |name,options| name == "myvar" }.returns(:myvalue) + @var.safeevaluate(@scope).should == :myvalue + end + + it "should pass the source location to lookupvar" do + @scope.expects(:lookupvar).with { |name,options| name == "myvar" and options[:file] == 'my.pp' and options[:line] == 222 }.returns(:myvalue) @var.safeevaluate(@scope).should == :myvalue end it "should return undef if the variable wasn't set" do - @scope.expects(:lookupvar).with("myvar", false).returns(:undefined) + @scope.expects(:lookupvar).with { |name,options| name == "myvar" }.returns(:undefined) @var.safeevaluate(@scope).should == :undef end describe "when converting to string" do it "should transform its value to a variable" do value = stub 'value', :is_a? => true, :to_s => "myvar" Puppet::Parser::AST::Variable.new( :value => value ).to_s.should == "\$myvar" end end end describe Puppet::Parser::AST::HostName do before :each do @scope = stub 'scope' @value = stub 'value', :=~ => false @value.stubs(:to_s).returns(@value) @value.stubs(:downcase).returns(@value) @host = Puppet::Parser::AST::HostName.new( :value => @value) end it "should raise an error if hostname is not valid" do lambda { Puppet::Parser::AST::HostName.new( :value => "not an hostname!" ) }.should raise_error end it "should not raise an error if hostname is a regex" do lambda { Puppet::Parser::AST::HostName.new( :value => Puppet::Parser::AST::Regex.new(:value => "/test/") ) }.should_not raise_error end it "should stringify the value" do value = stub 'value', :=~ => false value.expects(:to_s).returns("test") Puppet::Parser::AST::HostName.new(:value => value) end it "should downcase the value" do value = stub 'value', :=~ => false value.stubs(:to_s).returns("UPCASED") host = Puppet::Parser::AST::HostName.new(:value => value) host.value == "upcased" end it "should evaluate to its value" do @host.evaluate(@scope).should == @value end it "should delegate eql? to the underlying value if it is an HostName" do @value.expects(:eql?).with("value") @host.eql?("value") end it "should delegate eql? to the underlying value if it is not an HostName" do value = stub 'compared', :is_a? => true, :value => "value" @value.expects(:eql?).with("value") @host.eql?(value) end it "should delegate hash to the underlying value" do @value.expects(:hash) @host.hash end end diff --git a/spec/unit/parser/ast/vardef_spec.rb b/spec/unit/parser/ast/vardef_spec.rb index a462e8ef5..5a851bb13 100755 --- a/spec/unit/parser/ast/vardef_spec.rb +++ b/spec/unit/parser/ast/vardef_spec.rb @@ -1,60 +1,67 @@ #!/usr/bin/env ruby require 'spec_helper' describe Puppet::Parser::AST::VarDef do before :each do @scope = Puppet::Parser::Scope.new end describe "when evaluating" do it "should evaluate arguments" do name = mock 'name' value = mock 'value' name.expects(:safeevaluate).with(@scope) value.expects(:safeevaluate).with(@scope) - vardef = Puppet::Parser::AST::VarDef.new :name => name, :value => value, :file => nil, - :line => nil + vardef = Puppet::Parser::AST::VarDef.new :name => name, :value => value, :file => nil, :line => nil vardef.evaluate(@scope) end it "should be in append=false mode if called without append" do name = stub 'name', :safeevaluate => "var" value = stub 'value', :safeevaluate => "1" @scope.expects(:setvar).with { |name,value,options| options[:append] == nil } - vardef = Puppet::Parser::AST::VarDef.new :name => name, :value => value, :file => nil, - :line => nil + vardef = Puppet::Parser::AST::VarDef.new :name => name, :value => value, :file => nil, :line => nil vardef.evaluate(@scope) end it "should call scope in append mode if append is true" do name = stub 'name', :safeevaluate => "var" value = stub 'value', :safeevaluate => "1" @scope.expects(:setvar).with { |name,value,options| options[:append] == true } - vardef = Puppet::Parser::AST::VarDef.new :name => name, :value => value, :file => nil, - :line => nil, :append => true + vardef = Puppet::Parser::AST::VarDef.new :name => name, :value => value, :file => nil, :line => nil, :append => true + vardef.evaluate(@scope) + end + + it "should call pass the source location to setvar" do + name = stub 'name', :safeevaluate => "var" + value = stub 'value', :safeevaluate => "1" + + @scope.expects(:setvar).with { |name,value,options| options[:file] == 'setvar.pp' and options[:line] == 917 } + + vardef = Puppet::Parser::AST::VarDef.new :name => name, :value => value, :file => 'setvar.pp', :line => 917 vardef.evaluate(@scope) end describe "when dealing with hash" do it "should delegate to the HashOrArrayAccess assign" do access = stub 'name' access.stubs(:is_a?).with(Puppet::Parser::AST::HashOrArrayAccess).returns(true) value = stub 'value', :safeevaluate => "1" vardef = Puppet::Parser::AST::VarDef.new :name => access, :value => value, :file => nil, :line => nil access.expects(:assign).with(@scope, '1') vardef.evaluate(@scope) end end end end diff --git a/spec/unit/parser/compiler_spec.rb b/spec/unit/parser/compiler_spec.rb index 0c896a4e3..de4bee3e9 100755 --- a/spec/unit/parser/compiler_spec.rb +++ b/spec/unit/parser/compiler_spec.rb @@ -1,836 +1,832 @@ #!/usr/bin/env ruby require 'spec_helper' class CompilerTestResource attr_accessor :builtin, :virtual, :evaluated, :type, :title def initialize(type, title) @type = type @title = title end def [](attr) return nil if attr == :stage :main end def ref "#{type.to_s.capitalize}[#{title}]" end def evaluated? @evaluated end def builtin_type? @builtin end def virtual? @virtual end def evaluate end end describe Puppet::Parser::Compiler do def resource(type, title) Puppet::Parser::Resource.new(type, title, :scope => @scope) end before :each do @node = Puppet::Node.new "testnode" @known_resource_types = Puppet::Resource::TypeCollection.new "development" @compiler = Puppet::Parser::Compiler.new(@node) @scope = Puppet::Parser::Scope.new(:compiler => @compiler, :source => stub('source')) @scope_resource = Puppet::Parser::Resource.new(:file, "/my/file", :scope => @scope) @scope.resource = @scope_resource @compiler.environment.stubs(:known_resource_types).returns @known_resource_types end it "should have a class method that compiles, converts, and returns a catalog" do compiler = stub 'compiler' Puppet::Parser::Compiler.expects(:new).with(@node).returns compiler catalog = stub 'catalog' compiler.expects(:compile).returns catalog converted_catalog = stub 'converted_catalog' catalog.expects(:to_resource).returns converted_catalog Puppet::Parser::Compiler.compile(@node).should equal(converted_catalog) end it "should fail intelligently when a class-level compile fails" do Puppet::Parser::Compiler.expects(:new).raises ArgumentError lambda { Puppet::Parser::Compiler.compile(@node) }.should raise_error(Puppet::Error) end it "should use the node's environment as its environment" do @compiler.environment.should equal(@node.environment) end it "should include the resource type collection helper" do Puppet::Parser::Compiler.ancestors.should be_include(Puppet::Resource::TypeCollectionHelper) end it "should be able to return a class list containing all added classes" do @compiler.add_class "" @compiler.add_class "one" @compiler.add_class "two" @compiler.classlist.sort.should == %w{one two}.sort end describe "when initializing" do it "should set its node attribute" do @compiler.node.should equal(@node) end it "should detect when ast nodes are absent" do @compiler.ast_nodes?.should be_false end it "should detect when ast nodes are present" do @known_resource_types.expects(:nodes?).returns true @compiler.ast_nodes?.should be_true end it "should copy the known_resource_types version to the catalog" do @compiler.catalog.version.should == @known_resource_types.version end it "should copy any node classes into the class list" do node = Puppet::Node.new("mynode") node.classes = %w{foo bar} compiler = Puppet::Parser::Compiler.new(node) compiler.classlist.should =~ ['foo', 'bar'] end it "should transform node class hashes into a class list" do node = Puppet::Node.new("mynode") node.classes = {'foo'=>{'one'=>'1'}, 'bar'=>{'two'=>'2'}} compiler = Puppet::Parser::Compiler.new(node) compiler.classlist.should =~ ['foo', 'bar'] end it "should add a 'main' stage to the catalog" do @compiler.catalog.resource(:stage, :main).should be_instance_of(Puppet::Parser::Resource) end end describe "when managing scopes" do it "should create a top scope" do @compiler.topscope.should be_instance_of(Puppet::Parser::Scope) end it "should be able to create new scopes" do @compiler.newscope(@compiler.topscope).should be_instance_of(Puppet::Parser::Scope) end - it "should correctly set the level of newly created scopes" do - @compiler.newscope(@compiler.topscope, :level => 5).level.should == 5 - end - it "should set the parent scope of the new scope to be the passed-in parent" do scope = mock 'scope' newscope = @compiler.newscope(scope) newscope.parent.should equal(scope) end it "should set the parent scope of the new scope to its topscope if the parent passed in is nil" do scope = mock 'scope' newscope = @compiler.newscope(nil) newscope.parent.should equal(@compiler.topscope) end end describe "when compiling" do def compile_methods [:set_node_parameters, :evaluate_main, :evaluate_ast_node, :evaluate_node_classes, :evaluate_generators, :fail_on_unevaluated, :finish, :store, :extract, :evaluate_relationships] end # Stub all of the main compile methods except the ones we're specifically interested in. def compile_stub(*except) (compile_methods - except).each { |m| @compiler.stubs(m) } end it "should set node parameters as variables in the top scope" do params = {"a" => "b", "c" => "d"} @node.stubs(:parameters).returns(params) compile_stub(:set_node_parameters) @compiler.compile @compiler.topscope.lookupvar("a").should == "b" @compiler.topscope.lookupvar("c").should == "d" end it "should set the client and server versions on the catalog" do params = {"clientversion" => "2", "serverversion" => "3"} @node.stubs(:parameters).returns(params) compile_stub(:set_node_parameters) @compiler.compile @compiler.catalog.client_version.should == "2" @compiler.catalog.server_version.should == "3" end it "should evaluate any existing classes named in the node" do classes = %w{one two three four} main = stub 'main' one = stub 'one', :name => "one" three = stub 'three', :name => "three" @node.stubs(:name).returns("whatever") @node.stubs(:classes).returns(classes) @compiler.expects(:evaluate_classes).with(classes, @compiler.topscope) @compiler.class.publicize_methods(:evaluate_node_classes) { @compiler.evaluate_node_classes } end it "should evaluate any parameterized classes named in the node" do classes = {'foo'=>{'1'=>'one'}, 'bar'=>{'2'=>'two'}} @node.stubs(:classes).returns(classes) @compiler.expects(:evaluate_classes).with(classes, @compiler.topscope) @compiler.compile end it "should evaluate the main class if it exists" do compile_stub(:evaluate_main) main_class = @known_resource_types.add Puppet::Resource::Type.new(:hostclass, "") main_class.expects(:evaluate_code).with { |r| r.is_a?(Puppet::Parser::Resource) } @compiler.topscope.expects(:source=).with(main_class) @compiler.compile end it "should create a new, empty 'main' if no main class exists" do compile_stub(:evaluate_main) @compiler.compile @known_resource_types.find_hostclass([""], "").should be_instance_of(Puppet::Resource::Type) end it "should add an edge between the main stage and main class" do @compiler.compile (stage = @compiler.catalog.resource(:stage, "main")).should be_instance_of(Puppet::Parser::Resource) (klass = @compiler.catalog.resource(:class, "")).should be_instance_of(Puppet::Parser::Resource) @compiler.catalog.edge?(stage, klass).should be_true end it "should evaluate any node classes" do @node.stubs(:classes).returns(%w{one two three four}) @compiler.expects(:evaluate_classes).with(%w{one two three four}, @compiler.topscope) @compiler.send(:evaluate_node_classes) end it "should evaluate all added collections" do colls = [] # And when the collections fail to evaluate. colls << mock("coll1-false") colls << mock("coll2-false") colls.each { |c| c.expects(:evaluate).returns(false) } @compiler.add_collection(colls[0]) @compiler.add_collection(colls[1]) compile_stub(:evaluate_generators) @compiler.compile end it "should ignore builtin resources" do resource = resource(:file, "testing") @compiler.add_resource(@scope, resource) resource.expects(:evaluate).never @compiler.compile end it "should evaluate unevaluated resources" do resource = CompilerTestResource.new(:file, "testing") @compiler.add_resource(@scope, resource) # We have to now mark the resource as evaluated resource.expects(:evaluate).with { |*whatever| resource.evaluated = true } @compiler.compile end it "should not evaluate already-evaluated resources" do resource = resource(:file, "testing") resource.stubs(:evaluated?).returns true @compiler.add_resource(@scope, resource) resource.expects(:evaluate).never @compiler.compile end it "should evaluate unevaluated resources created by evaluating other resources" do resource = CompilerTestResource.new(:file, "testing") @compiler.add_resource(@scope, resource) resource2 = CompilerTestResource.new(:file, "other") # We have to now mark the resource as evaluated resource.expects(:evaluate).with { |*whatever| resource.evaluated = true; @compiler.add_resource(@scope, resource2) } resource2.expects(:evaluate).with { |*whatever| resource2.evaluated = true } @compiler.compile end describe "when finishing" do before do @compiler.send(:evaluate_main) @catalog = @compiler.catalog end def add_resource(name, parent = nil) resource = Puppet::Parser::Resource.new "file", name, :scope => @scope @compiler.add_resource(@scope, resource) @catalog.add_edge(parent, resource) if parent resource end it "should call finish() on all resources" do # Add a resource that does respond to :finish resource = Puppet::Parser::Resource.new "file", "finish", :scope => @scope resource.expects(:finish) @compiler.add_resource(@scope, resource) # And one that does not dnf_resource = stub_everything "dnf", :ref => "File[dnf]", :type => "file" @compiler.add_resource(@scope, dnf_resource) @compiler.send(:finish) end it "should call finish() in add_resource order" do resources = sequence('resources') resource1 = add_resource("finish1") resource1.expects(:finish).in_sequence(resources) resource2 = add_resource("finish2") resource2.expects(:finish).in_sequence(resources) @compiler.send(:finish) end it "should add each container's metaparams to its contained resources" do main = @catalog.resource(:class, :main) main[:noop] = true resource1 = add_resource("meh", main) @compiler.send(:finish) resource1[:noop].should be_true end it "should add metaparams recursively" do main = @catalog.resource(:class, :main) main[:noop] = true resource1 = add_resource("meh", main) resource2 = add_resource("foo", resource1) @compiler.send(:finish) resource2[:noop].should be_true end it "should prefer metaparams from immediate parents" do main = @catalog.resource(:class, :main) main[:noop] = true resource1 = add_resource("meh", main) resource2 = add_resource("foo", resource1) resource1[:noop] = false @compiler.send(:finish) resource2[:noop].should be_false end it "should merge tags downward" do main = @catalog.resource(:class, :main) main.tag("one") resource1 = add_resource("meh", main) resource1.tag "two" resource2 = add_resource("foo", resource1) @compiler.send(:finish) resource2.tags.should be_include("one") resource2.tags.should be_include("two") end it "should work if only middle resources have metaparams set" do main = @catalog.resource(:class, :main) resource1 = add_resource("meh", main) resource1[:noop] = true resource2 = add_resource("foo", resource1) @compiler.send(:finish) resource2[:noop].should be_true end end it "should return added resources in add order" do resource1 = resource(:file, "yay") @compiler.add_resource(@scope, resource1) resource2 = resource(:file, "youpi") @compiler.add_resource(@scope, resource2) @compiler.resources.should == [resource1, resource2] end it "should add resources that do not conflict with existing resources" do resource = resource(:file, "yay") @compiler.add_resource(@scope, resource) @compiler.catalog.should be_vertex(resource) end it "should fail to add resources that conflict with existing resources" do path = Puppet.features.posix? ? "/foo" : "C:/foo" file1 = Puppet::Type.type(:file).new :path => path file2 = Puppet::Type.type(:file).new :path => path @compiler.add_resource(@scope, file1) lambda { @compiler.add_resource(@scope, file2) }.should raise_error(Puppet::Resource::Catalog::DuplicateResourceError) end it "should add an edge from the scope resource to the added resource" do resource = resource(:file, "yay") @compiler.add_resource(@scope, resource) @compiler.catalog.should be_edge(@scope.resource, resource) end it "should add an edge to any specified stage for class resources" do other_stage = resource(:stage, "other") @compiler.add_resource(@scope, other_stage) resource = resource(:class, "foo") resource[:stage] = 'other' @compiler.add_resource(@scope, resource) @compiler.catalog.edge?(other_stage, resource).should be_true end it "should fail if a non-class resource attempts to set a stage" do other_stage = resource(:stage, "other") @compiler.add_resource(@scope, other_stage) resource = resource(:file, "foo") resource[:stage] = 'other' lambda { @compiler.add_resource(@scope, resource) }.should raise_error(ArgumentError) end it "should fail if an unknown stage is specified" do resource = resource(:class, "foo") resource[:stage] = 'other' lambda { @compiler.add_resource(@scope, resource) }.should raise_error(ArgumentError) end it "should add edges from the class resources to the parent's stage if no stage is specified" do main = @compiler.catalog.resource(:stage, :main) foo_stage = resource(:stage, :foo_stage) @compiler.add_resource(@scope, foo_stage) resource = resource(:class, "foo") @scope.stubs(:resource).returns(:stage => :foo_stage) @compiler.add_resource(@scope, resource) @compiler.catalog.should be_edge(foo_stage, resource) end it "should add edges from top-level class resources to the main stage if no stage is specified" do main = @compiler.catalog.resource(:stage, :main) resource = resource(:class, "foo") @compiler.add_resource(@scope, resource) @compiler.catalog.should be_edge(main, resource) end it "should not add non-class resources that don't specify a stage to the 'main' stage" do main = @compiler.catalog.resource(:stage, :main) resource = resource(:file, "foo") @compiler.add_resource(@scope, resource) @compiler.catalog.should_not be_edge(main, resource) end it "should not add any parent-edges to stages" do stage = resource(:stage, "other") @compiler.add_resource(@scope, stage) @scope.resource = resource(:class, "foo") @compiler.catalog.edge?(@scope.resource, stage).should be_false end it "should not attempt to add stages to other stages" do other_stage = resource(:stage, "other") second_stage = resource(:stage, "second") @compiler.add_resource(@scope, other_stage) @compiler.add_resource(@scope, second_stage) second_stage[:stage] = "other" @compiler.catalog.edge?(other_stage, second_stage).should be_false end it "should have a method for looking up resources" do resource = resource(:yay, "foo") @compiler.add_resource(@scope, resource) @compiler.findresource("Yay[foo]").should equal(resource) end it "should be able to look resources up by type and title" do resource = resource(:yay, "foo") @compiler.add_resource(@scope, resource) @compiler.findresource("Yay", "foo").should equal(resource) end it "should not evaluate virtual defined resources" do resource = resource(:file, "testing") resource.virtual = true @compiler.add_resource(@scope, resource) resource.expects(:evaluate).never @compiler.compile end end describe "when evaluating collections" do it "should evaluate each collection" do 2.times { |i| coll = mock 'coll%s' % i @compiler.add_collection(coll) # This is the hard part -- we have to emulate the fact that # collections delete themselves if they are done evaluating. coll.expects(:evaluate).with do @compiler.delete_collection(coll) end } @compiler.class.publicize_methods(:evaluate_collections) { @compiler.evaluate_collections } end it "should not fail when there are unevaluated resource collections that do not refer to specific resources" do coll = stub 'coll', :evaluate => false coll.expects(:resources).returns(nil) @compiler.add_collection(coll) lambda { @compiler.compile }.should_not raise_error end it "should fail when there are unevaluated resource collections that refer to a specific resource" do coll = stub 'coll', :evaluate => false coll.expects(:resources).returns(:something) @compiler.add_collection(coll) lambda { @compiler.compile }.should raise_error Puppet::ParseError, 'Failed to realize virtual resources something' end it "should fail when there are unevaluated resource collections that refer to multiple specific resources" do coll = stub 'coll', :evaluate => false coll.expects(:resources).returns([:one, :two]) @compiler.add_collection(coll) lambda { @compiler.compile }.should raise_error Puppet::ParseError, 'Failed to realize virtual resources one, two' end end describe "when evaluating relationships" do it "should evaluate each relationship with its catalog" do dep = stub 'dep' dep.expects(:evaluate).with(@compiler.catalog) @compiler.add_relationship dep @compiler.evaluate_relationships end end describe "when told to evaluate missing classes" do it "should fail if there's no source listed for the scope" do scope = stub 'scope', :source => nil proc { @compiler.evaluate_classes(%w{one two}, scope) }.should raise_error(Puppet::DevError) end it "should raise an error if a class is not found" do @scope.expects(:find_hostclass).with("notfound").returns(nil) lambda{ @compiler.evaluate_classes(%w{notfound}, @scope) }.should raise_error(Puppet::Error, /Could not find class/) end it "should raise an error when it can't find class" do klasses = {'foo'=>nil} @node.classes = klasses @compiler.topscope.stubs(:find_hostclass).with('foo').returns(nil) lambda{ @compiler.compile }.should raise_error(Puppet::Error, /Could not find class foo for testnode/) end end describe "when evaluating found classes" do before do @class = stub 'class', :name => "my::class" @scope.stubs(:find_hostclass).with("myclass").returns(@class) @resource = stub 'resource', :ref => "Class[myclass]", :type => "file" end it "should evaluate each class" do @compiler.catalog.stubs(:tag) @class.expects(:ensure_in_catalog).with(@scope) @scope.stubs(:class_scope).with(@class) @compiler.evaluate_classes(%w{myclass}, @scope) end it "should ensure each node class hash is in catalog and have appropriate parameters" do klasses = {'foo'=>{'1'=>'one'}, 'bar::foo'=>{'2'=>'two'}, 'bar'=>{'1'=> [1,2,3], '2'=>{'foo'=>'bar'}}} @node.classes = klasses ast_obj = Puppet::Parser::AST::String.new(:value => 'foo') klasses.each do |name, params| klass = Puppet::Resource::Type.new(:hostclass, name, :arguments => {'1' => ast_obj, '2' => ast_obj}) @compiler.topscope.known_resource_types.add klass end catalog = @compiler.compile catalog.classes.should =~ ['foo', 'bar::foo', 'settings', 'bar'] r1 = catalog.resources.detect {|r| r.title == 'Foo' } r1.to_hash.should == {:'1' => 'one', :'2' => 'foo'} r1.tags. should =~ ['class', 'foo'] r2 = catalog.resources.detect {|r| r.title == 'Bar::Foo' } r2.to_hash.should == {:'1' => 'foo', :'2' => 'two'} r2.tags.should =~ ['bar::foo', 'class', 'bar', 'foo'] r2 = catalog.resources.detect {|r| r.title == 'Bar' } r2.to_hash.should == {:'1' => [1,2,3], :'2' => {'foo'=>'bar'}} r2.tags.should =~ ['class', 'bar'] end it "should ensure each node class is in catalog and has appropriate tags" do klasses = ['bar::foo'] @node.classes = klasses ast_obj = Puppet::Parser::AST::String.new(:value => 'foo') klasses.each do |name| klass = Puppet::Resource::Type.new(:hostclass, name, :arguments => {'1' => ast_obj, '2' => ast_obj}) @compiler.topscope.known_resource_types.add klass end catalog = @compiler.compile r2 = catalog.resources.detect {|r| r.title == 'Bar::Foo' } r2.tags.should =~ ['bar::foo', 'class', 'bar', 'foo'] end it "should fail if required parameters are missing" do klass = {'foo'=>{'1'=>'one'}} @node.classes = klass klass = Puppet::Resource::Type.new(:hostclass, 'foo', :arguments => {'1' => nil, '2' => nil}) @compiler.topscope.known_resource_types.add klass lambda { @compiler.compile }.should raise_error Puppet::ParseError, "Must pass 2 to Class[Foo]" end it "should fail if invalid parameters are passed" do klass = {'foo'=>{'3'=>'one'}} @node.classes = klass klass = Puppet::Resource::Type.new(:hostclass, 'foo', :arguments => {'1' => nil, '2' => nil}) @compiler.topscope.known_resource_types.add klass lambda { @compiler.compile }.should raise_error Puppet::ParseError, "Invalid parameter 3" end it "should ensure class is in catalog without params" do @node.classes = klasses = {'foo'=>nil} foo = Puppet::Resource::Type.new(:hostclass, 'foo') @compiler.topscope.known_resource_types.add foo catalog = @compiler.compile catalog.classes.should include 'foo' end it "should not evaluate the resources created for found classes unless asked" do @compiler.catalog.stubs(:tag) @resource.expects(:evaluate).never @class.expects(:ensure_in_catalog).returns(@resource) @scope.stubs(:class_scope).with(@class) @compiler.evaluate_classes(%w{myclass}, @scope) end it "should immediately evaluate the resources created for found classes when asked" do @compiler.catalog.stubs(:tag) @resource.expects(:evaluate) @class.expects(:ensure_in_catalog).returns(@resource) @scope.stubs(:class_scope).with(@class) @compiler.evaluate_classes(%w{myclass}, @scope, false) end it "should skip classes that have already been evaluated" do @compiler.catalog.stubs(:tag) @scope.stubs(:class_scope).with(@class).returns("something") @compiler.expects(:add_resource).never @resource.expects(:evaluate).never Puppet::Parser::Resource.expects(:new).never @compiler.evaluate_classes(%w{myclass}, @scope, false) end it "should skip classes previously evaluated with different capitalization" do @compiler.catalog.stubs(:tag) @scope.stubs(:find_hostclass).with("MyClass").returns(@class) @scope.stubs(:class_scope).with(@class).returns("something") @compiler.expects(:add_resource).never @resource.expects(:evaluate).never Puppet::Parser::Resource.expects(:new).never @compiler.evaluate_classes(%w{MyClass}, @scope, false) end end describe "when evaluating AST nodes with no AST nodes present" do it "should do nothing" do @compiler.expects(:ast_nodes?).returns(false) @compiler.known_resource_types.expects(:nodes).never Puppet::Parser::Resource.expects(:new).never @compiler.send(:evaluate_ast_node) end end describe "when evaluating AST nodes with AST nodes present" do before do @compiler.known_resource_types.stubs(:nodes?).returns true # Set some names for our test @node.stubs(:names).returns(%w{a b c}) @compiler.known_resource_types.stubs(:node).with("a").returns(nil) @compiler.known_resource_types.stubs(:node).with("b").returns(nil) @compiler.known_resource_types.stubs(:node).with("c").returns(nil) # It should check this last, of course. @compiler.known_resource_types.stubs(:node).with("default").returns(nil) end it "should fail if the named node cannot be found" do proc { @compiler.send(:evaluate_ast_node) }.should raise_error(Puppet::ParseError) end it "should evaluate the first node class matching the node name" do node_class = stub 'node', :name => "c", :evaluate_code => nil @compiler.known_resource_types.stubs(:node).with("c").returns(node_class) node_resource = stub 'node resource', :ref => "Node[c]", :evaluate => nil, :type => "node" node_class.expects(:ensure_in_catalog).returns(node_resource) @compiler.compile end it "should match the default node if no matching node can be found" do node_class = stub 'node', :name => "default", :evaluate_code => nil @compiler.known_resource_types.stubs(:node).with("default").returns(node_class) node_resource = stub 'node resource', :ref => "Node[default]", :evaluate => nil, :type => "node" node_class.expects(:ensure_in_catalog).returns(node_resource) @compiler.compile end it "should evaluate the node resource immediately rather than using lazy evaluation" do node_class = stub 'node', :name => "c" @compiler.known_resource_types.stubs(:node).with("c").returns(node_class) node_resource = stub 'node resource', :ref => "Node[c]", :type => "node" node_class.expects(:ensure_in_catalog).returns(node_resource) node_resource.expects(:evaluate) @compiler.send(:evaluate_ast_node) end it "should set the node's scope as the top scope" do node_resource = stub 'node resource', :ref => "Node[c]", :evaluate => nil, :type => "node" node_class = stub 'node', :name => "c", :ensure_in_catalog => node_resource @compiler.known_resource_types.stubs(:node).with("c").returns(node_class) # The #evaluate method normally does this. scope = stub 'scope', :source => "mysource" @compiler.topscope.expects(:class_scope).with(node_class).returns(scope) node_resource.stubs(:evaluate) @compiler.stubs :create_settings_scope @compiler.compile @compiler.topscope.should equal(scope) end end describe "when managing resource overrides" do before do @override = stub 'override', :ref => "File[/foo]", :type => "my" @resource = resource(:file, "/foo") end it "should be able to store overrides" do lambda { @compiler.add_override(@override) }.should_not raise_error end it "should apply overrides to the appropriate resources" do @compiler.add_resource(@scope, @resource) @resource.expects(:merge).with(@override) @compiler.add_override(@override) @compiler.compile end it "should accept overrides before the related resource has been created" do @resource.expects(:merge).with(@override) # First store the override @compiler.add_override(@override) # Then the resource @compiler.add_resource(@scope, @resource) # And compile, so they get resolved @compiler.compile end it "should fail if the compile is finished and resource overrides have not been applied" do @compiler.add_override(@override) lambda { @compiler.compile }.should raise_error Puppet::ParseError, 'Could not find resource(s) File[/foo] for overriding' end end end diff --git a/spec/unit/parser/functions/extlookup_spec.rb b/spec/unit/parser/functions/extlookup_spec.rb index f2560d3d9..ad89f6575 100755 --- a/spec/unit/parser/functions/extlookup_spec.rb +++ b/spec/unit/parser/functions/extlookup_spec.rb @@ -1,96 +1,96 @@ #! /usr/bin/env ruby require 'spec_helper' require 'tempfile' describe "the extlookup function" do before :all do Puppet::Parser::Functions.autoloader.loadall end before :each do @scope = Puppet::Parser::Scope.new @scope.stubs(:environment).returns(Puppet::Node::Environment.new('production')) end it "should exist" do Puppet::Parser::Functions.function("extlookup").should == "function_extlookup" end it "should raise a ParseError if there is less than 1 arguments" do lambda { @scope.function_extlookup([]) }.should( raise_error(Puppet::ParseError)) end it "should raise a ParseError if there is more than 3 arguments" do lambda { @scope.function_extlookup(["foo", "bar", "baz", "gazonk"]) }.should( raise_error(Puppet::ParseError)) end it "should return the default" do result = @scope.function_extlookup([ "key", "default"]) result.should == "default" end it "should lookup the key in a supplied datafile" do t = Tempfile.new('extlookup.csv') do t.puts 'key,value' t.puts 'nonkey,nonvalue' t.close result = @scope.function_extlookup([ "key", "default", t.path]) result.should == "value" end end it "should return an array if the datafile contains more than two columns" do t = Tempfile.new('extlookup.csv') do t.puts 'key,value1,value2' t.puts 'nonkey,nonvalue,nonvalue' t.close result = @scope.function_extlookup([ "key", "default", t.path]) result.should == ["value1", "value2"] end end it "should raise an error if there's no matching key and no default" do t = Tempfile.new('extlookup.csv') do t.puts 'key,value' t.puts 'nonkey,nonvalue' t.close result = @scope.function_extlookup([ "key", nil, t.path]) result.should == "value" end end describe "should look in $extlookup_datadir for data files listed by $extlookup_precedence" do before do - @scope.stubs(:lookupvar).with('extlookup_datadir').returns("/tmp") + @scope.stubs(:lookupvar).with('::extlookup_datadir').returns("/tmp") File.open("/tmp/one.csv","w"){|one| one.puts "key,value1" } File.open("/tmp/two.csv","w") do |two| two.puts "key,value2" two.puts "key2,value_two" end end it "when the key is in the first file" do - @scope.stubs(:lookupvar).with('extlookup_precedence').returns(["one","two"]) + @scope.stubs(:lookupvar).with('::extlookup_precedence').returns(["one","two"]) result = @scope.function_extlookup([ "key" ]) result.should == "value1" end it "when the key is in the second file" do - @scope.stubs(:lookupvar).with('extlookup_precedence').returns(["one","two"]) + @scope.stubs(:lookupvar).with('::extlookup_precedence').returns(["one","two"]) result = @scope.function_extlookup([ "key2" ]) result.should == "value_two" end it "should not modify extlookup_precedence data" do variable = '%{fqdn}' - @scope.stubs(:lookupvar).with('extlookup_precedence').returns([variable,"one"]) - @scope.stubs(:lookupvar).with('fqdn').returns('myfqdn') + @scope.stubs(:lookupvar).with('::extlookup_precedence').returns([variable,"one"]) + @scope.stubs(:lookupvar).with('::fqdn').returns('myfqdn') result = @scope.function_extlookup([ "key" ]) variable.should == '%{fqdn}' end end end diff --git a/spec/unit/parser/functions/fqdn_rand_spec.rb b/spec/unit/parser/functions/fqdn_rand_spec.rb index d810741c5..90792e182 100755 --- a/spec/unit/parser/functions/fqdn_rand_spec.rb +++ b/spec/unit/parser/functions/fqdn_rand_spec.rb @@ -1,65 +1,65 @@ #! /usr/bin/env ruby require 'spec_helper' describe "the fqdn_rand function" do before :all do Puppet::Parser::Functions.autoloader.loadall end before :each do @scope = Puppet::Parser::Scope.new end it "should exist" do Puppet::Parser::Functions.function("fqdn_rand").should == "function_fqdn_rand" end it "should handle 0 arguments" do - @scope.expects(:lookupvar).with("fqdn").returns("127.0.0.1") + @scope.expects(:lookupvar).with("::fqdn").returns("127.0.0.1") lambda { @scope.function_fqdn_rand([]) }.should_not raise_error(Puppet::ParseError) end it "should handle 1 argument'}" do - @scope.expects(:lookupvar).with("fqdn").returns("127.0.0.1") + @scope.expects(:lookupvar).with("::fqdn").returns("127.0.0.1") lambda { @scope.function_fqdn_rand([3]) }.should_not raise_error(Puppet::ParseError) end (1..10).each { |n| it "should handle #{n} additional arguments" do - @scope.expects(:lookupvar).with("fqdn").returns("127.0.0.1") + @scope.expects(:lookupvar).with("::fqdn").returns("127.0.0.1") lambda { @scope.function_fqdn_rand([3,1,2,3,4,5,6,7,8,9,10][0..n]) }.should_not raise_error(Puppet::ParseError) end it "should handle #{n} additional string arguments" do - @scope.expects(:lookupvar).with("fqdn").returns("127.0.0.1") + @scope.expects(:lookupvar).with("::fqdn").returns("127.0.0.1") lambda { @scope.function_fqdn_rand([3,%w{ 1 2 3 4 5 6 7 8 9 10}].flatten[0..n]) }.should_not raise_error(Puppet::ParseError) end } it "should return a value less than max" do - @scope.expects(:lookupvar).with("fqdn").returns("127.0.0.1") + @scope.expects(:lookupvar).with("::fqdn").returns("127.0.0.1") @scope.function_fqdn_rand([3]).should satisfy {|n| n.to_i < 3 } end it "should return the same values on subsequent invocations for the same host" do - @scope.expects(:lookupvar).with("fqdn").returns("127.0.0.1").twice + @scope.expects(:lookupvar).with("::fqdn").returns("127.0.0.1").twice @scope.function_fqdn_rand([3,4]).should eql(@scope.function_fqdn_rand([3, 4])) end it "should return different sequences of value for different hosts" do - @scope.expects(:lookupvar).with("fqdn").returns("127.0.0.1") + @scope.expects(:lookupvar).with("::fqdn").returns("127.0.0.1") val1 = @scope.function_fqdn_rand([10000000,4]) - @scope.expects(:lookupvar).with("fqdn").returns("127.0.0.2") + @scope.expects(:lookupvar).with("::fqdn").returns("127.0.0.2") val2 = @scope.function_fqdn_rand([10000000,4]) val1.should_not eql(val2) end it "should return different values for the same hosts with different seeds" do - @scope.expects(:lookupvar).with("fqdn").returns("127.0.0.1") + @scope.expects(:lookupvar).with("::fqdn").returns("127.0.0.1") val1 = @scope.function_fqdn_rand([10000000,4]) - @scope.expects(:lookupvar).with("fqdn").returns("127.0.0.1") + @scope.expects(:lookupvar).with("::fqdn").returns("127.0.0.1") val2 = @scope.function_fqdn_rand([10000000,42]) val1.should_not eql(val2) end end diff --git a/spec/unit/parser/scope_spec.rb b/spec/unit/parser/scope_spec.rb index 44e255956..8215535dd 100755 --- a/spec/unit/parser/scope_spec.rb +++ b/spec/unit/parser/scope_spec.rb @@ -1,628 +1,485 @@ #!/usr/bin/env ruby require 'spec_helper' describe Puppet::Parser::Scope do before :each do @topscope = Puppet::Parser::Scope.new # This is necessary so we don't try to use the compiler to discover our parent. @topscope.parent = nil @scope = Puppet::Parser::Scope.new @scope.compiler = Puppet::Parser::Compiler.new(Puppet::Node.new("foo")) @scope.parent = @topscope end it "should be able to store references to class scopes" do lambda { @scope.class_set "myname", "myscope" }.should_not raise_error end it "should be able to retrieve class scopes by name" do @scope.class_set "myname", "myscope" @scope.class_scope("myname").should == "myscope" end it "should be able to retrieve class scopes by object" do klass = mock 'ast_class' klass.expects(:name).returns("myname") @scope.class_set "myname", "myscope" @scope.class_scope(klass).should == "myscope" end it "should be able to retrieve its parent module name from the source of its parent type" do @topscope.source = Puppet::Resource::Type.new(:hostclass, :foo, :module_name => "foo") @scope.parent_module_name.should == "foo" end it "should return a nil parent module name if it has no parent" do @topscope.parent_module_name.should be_nil end it "should return a nil parent module name if its parent has no source" do @scope.parent_module_name.should be_nil end it "should get its environment from its compiler" do env = stub 'environment' compiler = stub 'compiler', :environment => env scope = Puppet::Parser::Scope.new :compiler => compiler scope.environment.should equal(env) end it "should use the resource type collection helper to find its known resource types" do Puppet::Parser::Scope.ancestors.should include(Puppet::Resource::TypeCollectionHelper) end describe "when initializing" do it "should extend itself with its environment's Functions module as well as the default" do env = Puppet::Node::Environment.new("myenv") compiler = stub 'compiler', :environment => env mod = Module.new root_mod = Module.new Puppet::Parser::Functions.expects(:environment_module).with(Puppet::Node::Environment.root).returns root_mod Puppet::Parser::Functions.expects(:environment_module).with(env).returns mod Puppet::Parser::Scope.new(:compiler => compiler).singleton_class.ancestors.should be_include(mod) end it "should extend itself with the default Functions module if it has no environment" do mod = Module.new Puppet::Parser::Functions.expects(:environment_module).with(Puppet::Node::Environment.root).returns(mod) Puppet::Parser::Functions.expects(:environment_module).with(nil).returns mod Puppet::Parser::Scope.new.singleton_class.ancestors.should be_include(mod) end - end - describe "when looking up a variable" do - it "should default to an empty string" do - @scope.lookupvar("var").should == "" + it "should remember if it is dynamic" do + (!!Puppet::Parser::Scope.new(:dynamic => true).dynamic).should == true end - it "should return an string when asked for a string" do - @scope.lookupvar("var", true).should == "" + it "should assume it is not dynamic" do + (!Puppet::Parser::Scope.new.dynamic).should == true end + end - it "should return ':undefined' for unset variables when asked not to return a string" do - @scope.lookupvar("var", false).should == :undefined + describe "when looking up a variable" do + it "should return ':undefined' for unset variables" do + @scope.lookupvar("var").should == :undefined end it "should be able to look up values" do @scope.setvar("var", "yep") @scope.lookupvar("var").should == "yep" end it "should be able to look up hashes" do @scope.setvar("var", {"a" => "b"}) @scope.lookupvar("var").should == {"a" => "b"} end it "should be able to look up variables in parent scopes" do @topscope.setvar("var", "parentval") @scope.lookupvar("var").should == "parentval" end it "should prefer its own values to parent values" do @topscope.setvar("var", "parentval") @scope.setvar("var", "childval") @scope.lookupvar("var").should == "childval" end describe "and the variable is qualified" do before do @compiler = Puppet::Parser::Compiler.new(Puppet::Node.new("foonode")) @scope.compiler = @compiler @known_resource_types = @scope.known_resource_types end def newclass(name) @known_resource_types.add Puppet::Resource::Type.new(:hostclass, name) end def create_class_scope(name) klass = newclass(name) Puppet::Parser::Resource.new("class", name, :scope => @scope, :source => mock('source')).evaluate @scope.class_scope(klass) end it "should be able to look up explicitly fully qualified variables from main" do other_scope = create_class_scope("") other_scope.setvar("othervar", "otherval") @scope.lookupvar("::othervar").should == "otherval" end it "should be able to look up explicitly fully qualified variables from other scopes" do other_scope = create_class_scope("other") other_scope.setvar("var", "otherval") @scope.lookupvar("::other::var").should == "otherval" end it "should be able to look up deeply qualified variables" do other_scope = create_class_scope("other::deep::klass") other_scope.setvar("var", "otherval") @scope.lookupvar("other::deep::klass::var").should == "otherval" end - it "should return an empty string for qualified variables that cannot be found in other classes" do + it "should return ':undefined' for qualified variables that cannot be found in other classes" do other_scope = create_class_scope("other::deep::klass") - @scope.lookupvar("other::deep::klass::var").should == "" + @scope.lookupvar("other::deep::klass::var").should == :undefined end - it "should warn and return an empty string for qualified variables whose classes have not been evaluated" do + it "should warn and return ':undefined' for qualified variables whose classes have not been evaluated" do klass = newclass("other::deep::klass") @scope.expects(:warning) - @scope.lookupvar("other::deep::klass::var").should == "" + @scope.lookupvar("other::deep::klass::var").should == :undefined end - it "should warn and return an empty string for qualified variables whose classes do not exist" do + it "should warn and return ':undefined' for qualified variables whose classes do not exist" do @scope.expects(:warning) - @scope.lookupvar("other::deep::klass::var").should == "" + @scope.lookupvar("other::deep::klass::var").should == :undefined end it "should return ':undefined' when asked for a non-string qualified variable from a class that does not exist" do @scope.stubs(:warning) - @scope.lookupvar("other::deep::klass::var", false).should == :undefined + @scope.lookupvar("other::deep::klass::var").should == :undefined end it "should return ':undefined' when asked for a non-string qualified variable from a class that has not been evaluated" do @scope.stubs(:warning) klass = newclass("other::deep::klass") - @scope.lookupvar("other::deep::klass::var", false).should == :undefined + @scope.lookupvar("other::deep::klass::var").should == :undefined end end end describe "when setvar is called with append=true" do it "should raise error if the variable is already defined in this scope" do @scope.setvar("var","1", :append => false) lambda { @scope.setvar("var","1", :append => true) }.should raise_error(Puppet::ParseError) end it "should lookup current variable value" do @scope.expects(:lookupvar).with("var").returns("2") @scope.setvar("var","1", :append => true) end it "should store the concatenated string '42'" do @topscope.setvar("var","4", :append => false) @scope.setvar("var","2", :append => true) @scope.lookupvar("var").should == "42" end it "should store the concatenated array [4,2]" do @topscope.setvar("var",[4], :append => false) @scope.setvar("var",[2], :append => true) @scope.lookupvar("var").should == [4,2] end it "should store the merged hash {a => b, c => d}" do @topscope.setvar("var",{"a" => "b"}, :append => false) @scope.setvar("var",{"c" => "d"}, :append => true) @scope.lookupvar("var").should == {"a" => "b", "c" => "d"} end it "should raise an error when appending a hash with something other than another hash" do @topscope.setvar("var",{"a" => "b"}, :append => false) lambda { @scope.setvar("var","not a hash", :append => true) }.should raise_error end end describe "when calling number?" do it "should return nil if called with anything not a number" do Puppet::Parser::Scope.number?([2]).should be_nil end it "should return a Fixnum for a Fixnum" do Puppet::Parser::Scope.number?(2).should be_an_instance_of(Fixnum) end it "should return a Float for a Float" do Puppet::Parser::Scope.number?(2.34).should be_an_instance_of(Float) end it "should return 234 for '234'" do Puppet::Parser::Scope.number?("234").should == 234 end it "should return nil for 'not a number'" do Puppet::Parser::Scope.number?("not a number").should be_nil end it "should return 23.4 for '23.4'" do Puppet::Parser::Scope.number?("23.4").should == 23.4 end it "should return 23.4e13 for '23.4e13'" do Puppet::Parser::Scope.number?("23.4e13").should == 23.4e13 end it "should understand negative numbers" do Puppet::Parser::Scope.number?("-234").should == -234 end it "should know how to convert exponential float numbers ala '23e13'" do Puppet::Parser::Scope.number?("23e13").should == 23e13 end it "should understand hexadecimal numbers" do Puppet::Parser::Scope.number?("0x234").should == 0x234 end it "should understand octal numbers" do Puppet::Parser::Scope.number?("0755").should == 0755 end it "should return nil on malformed integers" do Puppet::Parser::Scope.number?("0.24.5").should be_nil end it "should convert strings with leading 0 to integer if they are not octal" do Puppet::Parser::Scope.number?("0788").should == 788 end it "should convert strings of negative integers" do Puppet::Parser::Scope.number?("-0788").should == -788 end it "should return nil on malformed hexadecimal numbers" do Puppet::Parser::Scope.number?("0x89g").should be_nil end end describe "when using ephemeral variables" do it "should store the variable value" do @scope.setvar("1", :value, :ephemeral => true) @scope.lookupvar("1").should == :value end it "should remove the variable value when unset_ephemeral_var is called" do @scope.setvar("1", :value, :ephemeral => true) @scope.stubs(:parent).returns(nil) @scope.unset_ephemeral_var - @scope.lookupvar("1", false).should == :undefined + @scope.lookupvar("1").should == :undefined end it "should not remove classic variables when unset_ephemeral_var is called" do @scope.setvar("myvar", :value1) @scope.setvar("1", :value2, :ephemeral => true) @scope.stubs(:parent).returns(nil) @scope.unset_ephemeral_var - @scope.lookupvar("myvar", false).should == :value1 + @scope.lookupvar("myvar").should == :value1 end it "should raise an error when setting it again" do @scope.setvar("1", :value2, :ephemeral => true) lambda { @scope.setvar("1", :value3, :ephemeral => true) }.should raise_error end it "should declare ephemeral number only variable names" do @scope.ephemeral?("0").should be_true end it "should not declare ephemeral other variable names" do @scope.ephemeral?("abc0").should be_nil end describe "with more than one level" do it "should prefer latest ephemeral scopes" do @scope.setvar("0", :earliest, :ephemeral => true) @scope.new_ephemeral @scope.setvar("0", :latest, :ephemeral => true) - @scope.lookupvar("0", false).should == :latest + @scope.lookupvar("0").should == :latest end it "should be able to report the current level" do @scope.ephemeral_level.should == 1 @scope.new_ephemeral @scope.ephemeral_level.should == 2 end it "should check presence of an ephemeral variable accross multiple levels" do @scope.new_ephemeral @scope.setvar("1", :value1, :ephemeral => true) @scope.new_ephemeral @scope.setvar("0", :value2, :ephemeral => true) @scope.new_ephemeral @scope.ephemeral_include?("1").should be_true end it "should return false when an ephemeral variable doesn't exist in any ephemeral scope" do @scope.new_ephemeral @scope.setvar("1", :value1, :ephemeral => true) @scope.new_ephemeral @scope.setvar("0", :value2, :ephemeral => true) @scope.new_ephemeral @scope.ephemeral_include?("2").should be_false end it "should get ephemeral values from earlier scope when not in later" do @scope.setvar("1", :value1, :ephemeral => true) @scope.new_ephemeral @scope.setvar("0", :value2, :ephemeral => true) - @scope.lookupvar("1", false).should == :value1 + @scope.lookupvar("1").should == :value1 end describe "when calling unset_ephemeral_var without a level" do it "should remove all the variables values" do @scope.setvar("1", :value1, :ephemeral => true) @scope.new_ephemeral @scope.setvar("1", :value2, :ephemeral => true) @scope.unset_ephemeral_var - @scope.lookupvar("1", false).should == :undefined + @scope.lookupvar("1").should == :undefined end end describe "when calling unset_ephemeral_var with a level" do it "should remove ephemeral scopes up to this level" do @scope.setvar("1", :value1, :ephemeral => true) @scope.new_ephemeral @scope.setvar("1", :value2, :ephemeral => true) @scope.new_ephemeral @scope.setvar("1", :value3, :ephemeral => true) @scope.unset_ephemeral_var(2) - @scope.lookupvar("1", false).should == :value2 - end - end - end - end - - describe "when interpolating string" do - (0..9).each do |n| - it "should allow $#{n} to match" do - @scope.setvar(n.to_s, "value", :ephemeral => true) - - @scope.strinterp("$#{n}").should == "value" - end - end - - (0..9).each do |n| - it "should not allow $#{n} to match if not ephemeral" do - @scope.setvar(n.to_s, "value", :ephemeral => false) - - @scope.strinterp("$#{n}").should_not == "value" - end - end - - it "should not allow $10 to match" do - @scope.setvar("10", "value", :ephemeral => true) - - @scope.strinterp('==$10==').should_not == "==value==" - end - - it "should not allow ${10} to match" do - @scope.setvar("10", "value", :ephemeral => true) - - @scope.strinterp('==${10}==').should == "==value==" - end - - describe "with qualified variables" do - before do - @scopes = {} - klass = @scope.known_resource_types.add(Puppet::Resource::Type.new(:hostclass, "")) - Puppet::Parser::Resource.new("class", :main, :scope => @scope, :source => mock('source')).evaluate - @scopes[""] = @scope.class_scope(klass) - @scopes[""].setvar("test", "value") - - %w{one one::two one::two::three}.each do |name| - klass = @scope.known_resource_types.add(Puppet::Resource::Type.new(:hostclass, name)) - Puppet::Parser::Resource.new("class", name, :scope => @scope, :source => mock('source')).evaluate - @scopes[name] = @scope.class_scope(klass) - @scopes[name].setvar("test", "value-#{name.sub(/.+::/,'')}") + @scope.lookupvar("1").should == :value2 end end - { - "===${one::two::three::test}===" => "===value-three===", - "===$one::two::three::test===" => "===value-three===", - "===${one::two::test}===" => "===value-two===", - "===$one::two::test===" => "===value-two===", - "===${one::test}===" => "===value-one===", - "===$one::test===" => "===value-one===", - "===${::test}===" => "===value===", - "===$::test===" => "===value===" - }.each do |input, output| - it "should parse '#{input}' correctly" do - @scope.strinterp(input).should == output - end - end - end - - tests = { - "===${test}===" => "===value===", - "===${test} ${test} ${test}===" => "===value value value===", - "===$test ${test} $test===" => "===value value value===", - "===\\$test===" => "===$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| - it "should parse '#{input}' correctly" do - @scope.setvar("test", "value") - @scope.strinterp(input).should == output - end - end - - # #523 - %w{d f h l w z}.each do |l| - it "should parse '#{l}' when escaped" do - string = "\\#{l}" - @scope.strinterp(string).should == string - end - end - end - - def test_strinterp - # Make and evaluate our classes so the qualified lookups work - parser = mkparser - klass = parser.newclass("") - scope = mkscope(:parser => parser) - Puppet::Parser::Resource.new(:type => "class", :title => :main, :scope => scope, :source => mock('source')).evaluate - - assert_nothing_raised { - scope.setvar("test","value") - } - - scopes = {"" => scope} - - %w{one one::two one::two::three}.each do |name| - klass = parser.newclass(name) - Puppet::Parser::Resource.new(:type => "class", :title => name, :scope => scope, :source => mock('source')).evaluate - scopes[name] = scope.class_scope(klass) - scopes[name].setvar("test", "value-#{name.sub(/.+::/,'')}") - end - - assert_equal("value", scope.lookupvar("::test"), "did not look up qualified value correctly") - tests.each do |input, output| - assert_nothing_raised("Failed to scan #{input.inspect}") do - assert_equal(output, scope.strinterp(input), 'did not parserret %s correctly' % input.inspect) - end - end - - logs = [] - Puppet::Util::Log.close - Puppet::Util::Log.newdestination(logs) - - # #523 - %w{d f h l w z}.each do |l| - string = "\\#{l}" - assert_nothing_raised do - - assert_equal( - string, scope.strinterp(string), - - 'did not parserret %s correctly' % string) - end - - - assert( - logs.detect { |m| m.message =~ /Unrecognised escape/ }, - - "Did not get warning about escape sequence with #{string}") - logs.clear end end describe "when setting ephemeral vars from matches" do before :each do @match = stub 'match', :is_a? => true @match.stubs(:[]).with(0).returns("this is a string") @match.stubs(:captures).returns([]) @scope.stubs(:setvar) end it "should accept only MatchData" do lambda { @scope.ephemeral_from("match") }.should raise_error end it "should set $0 with the full match" do @scope.expects(:setvar).with { |*arg| arg[0] == "0" and arg[1] == "this is a string" and arg[2][:ephemeral] } @scope.ephemeral_from(@match) end it "should set every capture as ephemeral var" do @match.stubs(:captures).returns([:capture1,:capture2]) @scope.expects(:setvar).with { |*arg| arg[0] == "1" and arg[1] == :capture1 and arg[2][:ephemeral] } @scope.expects(:setvar).with { |*arg| arg[0] == "2" and arg[1] == :capture2 and arg[2][:ephemeral] } @scope.ephemeral_from(@match) end it "should create a new ephemeral level" do @scope.expects(:new_ephemeral) @scope.ephemeral_from(@match) end end describe "when unsetting variables" do it "should be able to unset normal variables" do @scope.setvar("foo", "bar") @scope.unsetvar("foo") - @scope.lookupvar("foo").should == "" + @scope.lookupvar("foo").should == :undefined end it "should be able to unset ephemeral variables" do @scope.setvar("0", "bar", :ephemeral => true) @scope.unsetvar("0") - @scope.lookupvar("0").should == "" + @scope.lookupvar("0").should == :undefined end it "should not unset ephemeral variables in previous ephemeral scope" do @scope.setvar("0", "bar", :ephemeral => true) @scope.new_ephemeral @scope.unsetvar("0") @scope.lookupvar("0").should == "bar" end end it "should use its namespaces to find hostclasses" do klass = @scope.known_resource_types.add Puppet::Resource::Type.new(:hostclass, "a::b::c") @scope.add_namespace "a::b" @scope.find_hostclass("c").should equal(klass) end it "should use its namespaces to find definitions" do define = @scope.known_resource_types.add Puppet::Resource::Type.new(:definition, "a::b::c") @scope.add_namespace "a::b" @scope.find_definition("c").should equal(define) end describe "when managing defaults" do it "should be able to set and lookup defaults" do param = Puppet::Parser::Resource::Param.new(:name => :myparam, :value => "myvalue", :source => stub("source")) @scope.setdefaults(:mytype, param) @scope.lookupdefaults(:mytype).should == {:myparam => param} end it "should fail if a default is already defined and a new default is being defined" do param = Puppet::Parser::Resource::Param.new(:name => :myparam, :value => "myvalue", :source => stub("source")) @scope.setdefaults(:mytype, param) lambda { @scope.setdefaults(:mytype, param) }.should raise_error(Puppet::ParseError) end it "should return multiple defaults at once" do param1 = Puppet::Parser::Resource::Param.new(:name => :myparam, :value => "myvalue", :source => stub("source")) @scope.setdefaults(:mytype, param1) param2 = Puppet::Parser::Resource::Param.new(:name => :other, :value => "myvalue", :source => stub("source")) @scope.setdefaults(:mytype, param2) @scope.lookupdefaults(:mytype).should == {:myparam => param1, :other => param2} end it "should look up defaults defined in parent scopes" do param1 = Puppet::Parser::Resource::Param.new(:name => :myparam, :value => "myvalue", :source => stub("source")) @scope.setdefaults(:mytype, param1) child_scope = @scope.newscope param2 = Puppet::Parser::Resource::Param.new(:name => :other, :value => "myvalue", :source => stub("source")) child_scope.setdefaults(:mytype, param2) child_scope.lookupdefaults(:mytype).should == {:myparam => param1, :other => param2} end end end diff --git a/spec/unit/parser/templatewrapper_spec.rb b/spec/unit/parser/templatewrapper_spec.rb index afb0032fd..0c7199ba1 100755 --- a/spec/unit/parser/templatewrapper_spec.rb +++ b/spec/unit/parser/templatewrapper_spec.rb @@ -1,145 +1,143 @@ #!/usr/bin/env ruby require 'spec_helper' require 'puppet/parser/templatewrapper' describe Puppet::Parser::TemplateWrapper do before(:each) do @known_resource_types = Puppet::Resource::TypeCollection.new("env") @compiler = Puppet::Parser::Compiler.new(Puppet::Node.new("mynode")) @compiler.environment.stubs(:known_resource_types).returns @known_resource_types @scope = Puppet::Parser::Scope.new :compiler => @compiler @file = "fake_template" Puppet::Parser::Files.stubs(:find_template).returns("/tmp/fake_template") FileTest.stubs(:exists?).returns("true") File.stubs(:read).with("/tmp/fake_template").returns("template content") @tw = Puppet::Parser::TemplateWrapper.new(@scope) end + def mock_template(source=nil) + template_mock = mock("template", :result => "woot!") + ERB.expects(:new).with("template contents", 0, "-").returns(template_mock) + template_mock.expects(:filename=).with(source) + end + it "should create a new object TemplateWrapper from a scope" do tw = Puppet::Parser::TemplateWrapper.new(@scope) tw.should be_a_kind_of(Puppet::Parser::TemplateWrapper) end it "should check template file existance and read its content" do Puppet::Parser::Files.expects(:find_template).with("fake_template", @scope.environment.to_s).returns("/tmp/fake_template") File.expects(:read).with("/tmp/fake_template").returns("template content") @tw.file = @file end it "should mark the file for watching" do Puppet::Parser::Files.expects(:find_template).returns("/tmp/fake_template") File.stubs(:read) @known_resource_types.expects(:watch_file).with("/tmp/fake_template") @tw.file = @file end it "should fail if a template cannot be found" do Puppet::Parser::Files.expects(:find_template).returns nil lambda { @tw.file = @file }.should raise_error(Puppet::ParseError) end it "should turn into a string like template[name] for file based template" do @tw.file = @file @tw.to_s.should eql("template[/tmp/fake_template]") end it "should turn into a string like template[inline] for string-based template" do @tw.to_s.should eql("template[inline]") end it "should return the processed template contents with a call to result" do - template_mock = mock("template", :result => "woot!") + mock_template("/tmp/fake_template") File.expects(:read).with("/tmp/fake_template").returns("template contents") - ERB.expects(:new).with("template contents", 0, "-").returns(template_mock) @tw.file = @file @tw.result.should eql("woot!") end it "should return the processed template contents with a call to result and a string" do - template_mock = mock("template", :result => "woot!") - ERB.expects(:new).with("template contents", 0, "-").returns(template_mock) - + mock_template @tw.result("template contents").should eql("woot!") end it "should return the contents of a variable if called via method_missing" do - @scope.expects(:lookupvar).with("chicken", false).returns("is good") + @scope.expects(:lookupvar).with { |name,options| name == "chicken"}.returns("is good") tw = Puppet::Parser::TemplateWrapper.new(@scope) tw.chicken.should eql("is good") end it "should throw an exception if a variable is called via method_missing and it does not exist" do - @scope.expects(:lookupvar).with("chicken", false).returns(:undefined) + @scope.expects(:lookupvar).with { |name,options| name == "chicken"}.returns(:undefined) tw = Puppet::Parser::TemplateWrapper.new(@scope) lambda { tw.chicken }.should raise_error(Puppet::ParseError) end it "should allow you to check whether a variable is defined with has_variable?" do - @scope.expects(:lookupvar).with("chicken", false).returns("is good") + @scope.expects(:lookupvar).with { |name,options| name == "chicken"}.returns("is good") tw = Puppet::Parser::TemplateWrapper.new(@scope) tw.has_variable?("chicken").should eql(true) end it "should allow you to check whether a variable is not defined with has_variable?" do - @scope.expects(:lookupvar).with("chicken", false).returns(:undefined) + @scope.expects(:lookupvar).with { |name,options| name == "chicken"}.returns(:undefined) tw = Puppet::Parser::TemplateWrapper.new(@scope) tw.has_variable?("chicken").should eql(false) end it "should allow you to retrieve the defined classes with classes" do catalog = mock 'catalog', :classes => ["class1", "class2"] @scope.expects(:catalog).returns( catalog ) tw = Puppet::Parser::TemplateWrapper.new(@scope) tw.classes.should == ["class1", "class2"] end it "should allow you to retrieve all the tags with all_tags" do catalog = mock 'catalog', :tags => ["tag1", "tag2"] @scope.expects(:catalog).returns( catalog ) tw = Puppet::Parser::TemplateWrapper.new(@scope) tw.all_tags.should == ["tag1","tag2"] end it "should allow you to retrieve the tags defined in the current scope" do @scope.expects(:tags).returns( ["tag1", "tag2"] ) tw = Puppet::Parser::TemplateWrapper.new(@scope) tw.tags.should == ["tag1","tag2"] end it "should set all of the scope's variables as instance variables" do - template_mock = mock("template", :result => "woot!") - ERB.expects(:new).with("template contents", 0, "-").returns(template_mock) - + mock_template @scope.expects(:to_hash).returns("one" => "foo") @tw.result("template contents") @tw.instance_variable_get("@one").should == "foo" end it "should not error out if one of the variables is a symbol" do - template_mock = mock("template", :result => "woot!") - ERB.expects(:new).with("template contents", 0, "-").returns(template_mock) + mock_template @scope.expects(:to_hash).returns(:_timestamp => "1234") @tw.result("template contents") end %w{! . ; :}.each do |badchar| it "should translate #{badchar} to _ when setting the instance variables" do - template_mock = mock("template", :result => "woot!") - ERB.expects(:new).with("template contents", 0, "-").returns(template_mock) - - @scope.expects(:to_hash).returns("one#{badchar}" => "foo") - @tw.result("template contents") + mock_template + @scope.expects(:to_hash).returns("one#{badchar}" => "foo") + @tw.result("template contents") - @tw.instance_variable_get("@one_").should == "foo" - end + @tw.instance_variable_get("@one_").should == "foo" + end end end diff --git a/spec/unit/resource/type_spec.rb b/spec/unit/resource/type_spec.rb index b6a5f6982..45c880440 100755 --- a/spec/unit/resource/type_spec.rb +++ b/spec/unit/resource/type_spec.rb @@ -1,797 +1,769 @@ #!/usr/bin/env ruby require 'spec_helper' require 'puppet/resource/type' describe Puppet::Resource::Type do it "should have a 'name' attribute" do Puppet::Resource::Type.new(:hostclass, "foo").name.should == "foo" end [:code, :doc, :line, :file, :resource_type_collection, :ruby_code].each do |attr| it "should have a '#{attr}' attribute" do type = Puppet::Resource::Type.new(:hostclass, "foo") type.send(attr.to_s + "=", "yay") type.send(attr).should == "yay" end end [:hostclass, :node, :definition].each do |type| it "should know when it is a #{type}" do Puppet::Resource::Type.new(type, "foo").send("#{type}?").should be_true end end it "should indirect 'resource_type'" do Puppet::Resource::Type.indirection.name.should == :resource_type end it "should default to 'parser' for its terminus class" do Puppet::Resource::Type.indirection.terminus_class.should == :parser end describe "when converting to json" do before do @type = Puppet::Resource::Type.new(:hostclass, "foo") end def from_json(json) Puppet::Resource::Type.from_pson(json) end def double_convert Puppet::Resource::Type.from_pson(PSON.parse(@type.to_pson)) end it "should include the name and type" do double_convert.name.should == @type.name double_convert.type.should == @type.type end it "should include any arguments" do @type.set_arguments("one" => nil, "two" => "foo") double_convert.arguments.should == {"one" => nil, "two" => "foo"} end it "should not include arguments if none are present" do @type.to_pson["arguments"].should be_nil end [:line, :doc, :file, :parent].each do |attr| it "should include #{attr} when set" do @type.send(attr.to_s + "=", "value") double_convert.send(attr).should == "value" end it "should not include #{attr} when not set" do @type.to_pson[attr.to_s].should be_nil end end it "should not include docs if they are empty" do @type.doc = "" @type.to_pson["doc"].should be_nil end end describe "when a node" do it "should allow a regex as its name" do lambda { Puppet::Resource::Type.new(:node, /foo/) }.should_not raise_error end it "should allow a AST::HostName instance as its name" do regex = Puppet::Parser::AST::Regex.new(:value => /foo/) name = Puppet::Parser::AST::HostName.new(:value => regex) lambda { Puppet::Resource::Type.new(:node, name) }.should_not raise_error end it "should match against the regexp in the AST::HostName when a HostName instance is provided" do regex = Puppet::Parser::AST::Regex.new(:value => /\w/) name = Puppet::Parser::AST::HostName.new(:value => regex) node = Puppet::Resource::Type.new(:node, name) node.match("foo").should be_true end it "should return the value of the hostname if provided a string-form AST::HostName instance as the name" do name = Puppet::Parser::AST::HostName.new(:value => "foo") node = Puppet::Resource::Type.new(:node, name) node.name.should == "foo" end describe "and the name is a regex" do it "should have a method that indicates that this is the case" do Puppet::Resource::Type.new(:node, /w/).should be_name_is_regex end it "should set its namespace to ''" do Puppet::Resource::Type.new(:node, /w/).namespace.should == "" end it "should return the regex converted to a string when asked for its name" do Puppet::Resource::Type.new(:node, /ww/).name.should == "ww" end it "should downcase the regex when returning the name as a string" do Puppet::Resource::Type.new(:node, /W/).name.should == "w" end it "should remove non-alpha characters when returning the name as a string" do Puppet::Resource::Type.new(:node, /w*w/).name.should_not include("*") end it "should remove leading dots when returning the name as a string" do Puppet::Resource::Type.new(:node, /.ww/).name.should_not =~ /^\./ end it "should have a method for matching its regex name against a provided name" do Puppet::Resource::Type.new(:node, /.ww/).should respond_to(:match) end it "should return true when its regex matches the provided name" do Puppet::Resource::Type.new(:node, /\w/).match("foo").should be_true end it "should return false when its regex does not match the provided name" do (!!Puppet::Resource::Type.new(:node, /\d/).match("foo")).should be_false end it "should return true when its name, as a string, is matched against an equal string" do Puppet::Resource::Type.new(:node, "foo").match("foo").should be_true end it "should return false when its name is matched against an unequal string" do Puppet::Resource::Type.new(:node, "foo").match("bar").should be_false end it "should match names insensitive to case" do Puppet::Resource::Type.new(:node, "fOo").match("foO").should be_true end end it "should return the name converted to a string when the name is not a regex" do pending "Need to define LoadedCode behaviour first" name = Puppet::Parser::AST::HostName.new(:value => "foo") Puppet::Resource::Type.new(:node, name).name.should == "foo" end it "should return the name converted to a string when the name is a regex" do pending "Need to define LoadedCode behaviour first" name = Puppet::Parser::AST::HostName.new(:value => /regex/) Puppet::Resource::Type.new(:node, name).name.should == /regex/.to_s end it "should mark any created scopes as a node scope" do pending "Need to define LoadedCode behaviour first" name = Puppet::Parser::AST::HostName.new(:value => /regex/) Puppet::Resource::Type.new(:node, name).name.should == /regex/.to_s end end describe "when initializing" do it "should require a resource super type" do Puppet::Resource::Type.new(:hostclass, "foo").type.should == :hostclass end it "should fail if provided an invalid resource super type" do lambda { Puppet::Resource::Type.new(:nope, "foo") }.should raise_error(ArgumentError) end it "should set its name to the downcased, stringified provided name" do Puppet::Resource::Type.new(:hostclass, "Foo::Bar".intern).name.should == "foo::bar" end it "should set its namespace to the downcased, stringified qualified name for classes" do Puppet::Resource::Type.new(:hostclass, "Foo::Bar::Baz".intern).namespace.should == "foo::bar::baz" end [:definition, :node].each do |type| it "should set its namespace to the downcased, stringified qualified portion of the name for #{type}s" do Puppet::Resource::Type.new(type, "Foo::Bar::Baz".intern).namespace.should == "foo::bar" end end %w{code line file doc}.each do |arg| it "should set #{arg} if provided" do type = Puppet::Resource::Type.new(:hostclass, "foo", arg.to_sym => "something") type.send(arg).should == "something" end end it "should set any provided arguments with the keys as symbols" do type = Puppet::Resource::Type.new(:hostclass, "foo", :arguments => {:foo => "bar", :baz => "biz"}) type.should be_valid_parameter("foo") type.should be_valid_parameter("baz") end it "should set any provided arguments with they keys as strings" do type = Puppet::Resource::Type.new(:hostclass, "foo", :arguments => {"foo" => "bar", "baz" => "biz"}) type.should be_valid_parameter(:foo) type.should be_valid_parameter(:baz) end it "should function if provided no arguments" do type = Puppet::Resource::Type.new(:hostclass, "foo") type.should_not be_valid_parameter(:foo) end end describe "when testing the validity of an attribute" do it "should return true if the parameter was typed at initialization" do Puppet::Resource::Type.new(:hostclass, "foo", :arguments => {"foo" => "bar"}).should be_valid_parameter("foo") end it "should return true if it is a metaparam" do Puppet::Resource::Type.new(:hostclass, "foo").should be_valid_parameter("require") end it "should return true if the parameter is named 'name'" do Puppet::Resource::Type.new(:hostclass, "foo").should be_valid_parameter("name") end it "should return false if it is not a metaparam and was not provided at initialization" do Puppet::Resource::Type.new(:hostclass, "foo").should_not be_valid_parameter("yayness") end end - describe "when creating a subscope" do - before do - @scope = stub 'scope', :newscope => nil - @resource = stub 'resource' - @type = Puppet::Resource::Type.new(:hostclass, "foo") - end - - it "should return a new scope created with the provided scope as the parent" do - @scope.expects(:newscope).returns "foo" - @type.subscope(@scope, @resource).should == "foo" - end - - it "should set the source as itself" do - @scope.expects(:newscope).with { |args| args[:source] == @type } - @type.subscope(@scope, @resource) - end - - it "should set the scope's namespace to its namespace" do - @type.expects(:namespace).returns "yayness" - @scope.expects(:newscope).with { |args| args[:namespace] == "yayness" } - @type.subscope(@scope, @resource) - end - - it "should set the scope's resource to the provided resource" do - @scope.expects(:newscope).with { |args| args[:resource] == @resource } - @type.subscope(@scope, @resource) - end - end - describe "when setting its parameters in the scope" do before do @scope = Puppet::Parser::Scope.new(:compiler => stub("compiler", :environment => Puppet::Node::Environment.new), :source => stub("source")) @resource = Puppet::Parser::Resource.new(:foo, "bar", :scope => @scope) @type = Puppet::Resource::Type.new(:hostclass, "foo") end ['module_name', 'name', 'title'].each do |variable| it "should allow #{variable} to be evaluated as param default" do @type.instance_eval { @module_name = "bar" } var = Puppet::Parser::AST::Variable.new({'value' => variable}) @type.set_arguments :foo => var @type.set_resource_parameters(@resource, @scope) @scope.lookupvar('foo').should == 'bar' end end # this test is to clarify a crazy edge case # if you specify these special names as params, the resource # will override the special variables it "resource should override defaults" do @type.set_arguments :name => nil @resource[:name] = 'foobar' var = Puppet::Parser::AST::Variable.new({'value' => 'name'}) @type.set_arguments :foo => var @type.set_resource_parameters(@resource, @scope) @scope.lookupvar('foo').should == 'foobar' end it "should set each of the resource's parameters as variables in the scope" do @type.set_arguments :foo => nil, :boo => nil @resource[:foo] = "bar" @resource[:boo] = "baz" @type.set_resource_parameters(@resource, @scope) @scope.lookupvar("foo").should == "bar" @scope.lookupvar("boo").should == "baz" end it "should set the variables as strings" do @type.set_arguments :foo => nil @resource[:foo] = "bar" @type.set_resource_parameters(@resource, @scope) @scope.lookupvar("foo").should == "bar" end it "should fail if any of the resource's parameters are not valid attributes" do @type.set_arguments :foo => nil @resource[:boo] = "baz" lambda { @type.set_resource_parameters(@resource, @scope) }.should raise_error(Puppet::ParseError) end it "should evaluate and set its default values as variables for parameters not provided by the resource" do @type.set_arguments :foo => stub("value", :safeevaluate => "something") @type.set_resource_parameters(@resource, @scope) @scope.lookupvar("foo").should == "something" end it "should set all default values as parameters in the resource" do @type.set_arguments :foo => stub("value", :safeevaluate => "something") @type.set_resource_parameters(@resource, @scope) @resource[:foo].should == "something" end it "should fail if the resource does not provide a value for a required argument" do @type.set_arguments :foo => nil @resource.expects(:to_hash).returns({}) lambda { @type.set_resource_parameters(@resource, @scope) }.should raise_error(Puppet::ParseError) end it "should set the resource's title as a variable if not otherwise provided" do @type.set_resource_parameters(@resource, @scope) @scope.lookupvar("title").should == "bar" end it "should set the resource's name as a variable if not otherwise provided" do @type.set_resource_parameters(@resource, @scope) @scope.lookupvar("name").should == "bar" end it "should set its module name in the scope if available" do @type.instance_eval { @module_name = "mymod" } @type.set_resource_parameters(@resource, @scope) @scope.lookupvar("module_name").should == "mymod" end it "should set its caller module name in the scope if available" do @scope.expects(:parent_module_name).returns "mycaller" @type.set_resource_parameters(@resource, @scope) @scope.lookupvar("caller_module_name").should == "mycaller" end end describe "when describing and managing parent classes" do before do @code = Puppet::Resource::TypeCollection.new("env") @parent = Puppet::Resource::Type.new(:hostclass, "bar") @code.add @parent @child = Puppet::Resource::Type.new(:hostclass, "foo", :parent => "bar") @code.add @child @env = stub "environment", :known_resource_types => @code @scope = stub "scope", :environment => @env, :namespaces => [""] end it "should be able to define a parent" do Puppet::Resource::Type.new(:hostclass, "foo", :parent => "bar") end it "should use the code collection to find the parent resource type" do @child.parent_type(@scope).should equal(@parent) end it "should be able to find parent nodes" do parent = Puppet::Resource::Type.new(:node, "bar") @code.add parent child = Puppet::Resource::Type.new(:node, "foo", :parent => "bar") @code.add child child.parent_type(@scope).should equal(parent) end it "should cache a reference to the parent type" do @code.stubs(:hostclass).with("foo::bar").returns nil @code.expects(:hostclass).with("bar").once.returns @parent @child.parent_type(@scope) @child.parent_type end it "should correctly state when it is another type's child" do @child.parent_type(@scope) @child.should be_child_of(@parent) end it "should be considered the child of a parent's parent" do @grandchild = Puppet::Resource::Type.new(:hostclass, "baz", :parent => "foo") @code.add @grandchild @child.parent_type(@scope) @grandchild.parent_type(@scope) @grandchild.should be_child_of(@parent) end it "should correctly state when it is not another type's child" do @notchild = Puppet::Resource::Type.new(:hostclass, "baz") @code.add @notchild @notchild.should_not be_child_of(@parent) end end describe "when evaluating its code" do before do @compiler = Puppet::Parser::Compiler.new(Puppet::Node.new("mynode")) @scope = Puppet::Parser::Scope.new :compiler => @compiler @resource = Puppet::Parser::Resource.new(:foo, "yay", :scope => @scope) # This is so the internal resource lookup works, yo. @compiler.catalog.add_resource @resource @known_resource_types = stub 'known_resource_types' @resource.stubs(:known_resource_types).returns @known_resource_types @type = Puppet::Resource::Type.new(:hostclass, "foo") end it "should add hostclass names to the classes list" do @type.evaluate_code(@resource) @compiler.catalog.classes.should be_include("foo") end it "should add node names to the classes list" do @type = Puppet::Resource::Type.new(:node, "foo") @type.evaluate_code(@resource) @compiler.catalog.classes.should be_include("foo") end it "should not add defined resource names to the classes list" do @type = Puppet::Resource::Type.new(:definition, "foo") @type.evaluate_code(@resource) @compiler.catalog.classes.should_not be_include("foo") end it "should set all of its parameters in a subscope" do subscope = stub 'subscope', :compiler => @compiler - @type.expects(:subscope).with(@scope, @resource).returns subscope + @scope.expects(:newscope).with(:source => @type, :dynamic => true, :namespace => 'foo', :resource => @resource).returns subscope @type.expects(:set_resource_parameters).with(@resource, subscope) @type.evaluate_code(@resource) end it "should not create a subscope for the :main class" do @resource.stubs(:title).returns(:main) @type.expects(:subscope).never @type.expects(:set_resource_parameters).with(@resource, @scope) @type.evaluate_code(@resource) end it "should store the class scope" do @type.evaluate_code(@resource) @scope.class_scope(@type).should be_instance_of(@scope.class) end it "should still create a scope but not store it if the type is a definition" do @type = Puppet::Resource::Type.new(:definition, "foo") @type.evaluate_code(@resource) @scope.class_scope(@type).should be_nil end it "should evaluate the AST code if any is provided" do code = stub 'code' @type.stubs(:code).returns code - @type.stubs(:subscope).returns stub_everything("subscope", :compiler => @compiler) - code.expects(:safeevaluate).with @type.subscope + subscope = stub_everything("subscope", :compiler => @compiler) + @scope.stubs(:newscope).returns subscope + code.expects(:safeevaluate).with subscope @type.evaluate_code(@resource) end describe "and ruby code is provided" do it "should create a DSL Resource API and evaluate it" do @type.stubs(:ruby_code).returns(proc { "foo" }) @api = stub 'api' Puppet::DSL::ResourceAPI.expects(:new).with { |res, scope, code| code == @type.ruby_code }.returns @api @api.expects(:evaluate) @type.evaluate_code(@resource) end end it "should noop if there is no code" do @type.expects(:code).returns nil @type.evaluate_code(@resource) end describe "and it has a parent class" do before do @parent_type = Puppet::Resource::Type.new(:hostclass, "parent") @type.parent = "parent" @parent_resource = Puppet::Parser::Resource.new(:class, "parent", :scope => @scope) @compiler.add_resource @scope, @parent_resource @type.resource_type_collection = @scope.known_resource_types @type.resource_type_collection.add @parent_type end it "should evaluate the parent's resource" do @type.parent_type(@scope) @type.evaluate_code(@resource) @scope.class_scope(@parent_type).should_not be_nil end it "should not evaluate the parent's resource if it has already been evaluated" do @parent_resource.evaluate @type.parent_type(@scope) @parent_resource.expects(:evaluate).never @type.evaluate_code(@resource) end it "should use the parent's scope as its base scope" do @type.parent_type(@scope) @type.evaluate_code(@resource) @scope.class_scope(@type).parent.object_id.should == @scope.class_scope(@parent_type).object_id end end describe "and it has a parent node" do before do @type = Puppet::Resource::Type.new(:node, "foo") @parent_type = Puppet::Resource::Type.new(:node, "parent") @type.parent = "parent" @parent_resource = Puppet::Parser::Resource.new(:node, "parent", :scope => @scope) @compiler.add_resource @scope, @parent_resource @type.resource_type_collection = @scope.known_resource_types @type.resource_type_collection.add(@parent_type) end it "should evaluate the parent's resource" do @type.parent_type(@scope) @type.evaluate_code(@resource) @scope.class_scope(@parent_type).should_not be_nil end it "should not evaluate the parent's resource if it has already been evaluated" do @parent_resource.evaluate @type.parent_type(@scope) @parent_resource.expects(:evaluate).never @type.evaluate_code(@resource) end it "should use the parent's scope as its base scope" do @type.parent_type(@scope) @type.evaluate_code(@resource) @scope.class_scope(@type).parent.object_id.should == @scope.class_scope(@parent_type).object_id end end end describe "when creating a resource" do before do @node = Puppet::Node.new("foo", :environment => 'env') @compiler = Puppet::Parser::Compiler.new(@node) @scope = Puppet::Parser::Scope.new(:compiler => @compiler) @top = Puppet::Resource::Type.new :hostclass, "top" @middle = Puppet::Resource::Type.new :hostclass, "middle", :parent => "top" @code = Puppet::Resource::TypeCollection.new("env") @code.add @top @code.add @middle @node.environment.stubs(:known_resource_types).returns(@code) end it "should create a resource instance" do @top.ensure_in_catalog(@scope).should be_instance_of(Puppet::Parser::Resource) end it "should set its resource type to 'class' when it is a hostclass" do Puppet::Resource::Type.new(:hostclass, "top").ensure_in_catalog(@scope).type.should == "Class" end it "should set its resource type to 'node' when it is a node" do Puppet::Resource::Type.new(:node, "top").ensure_in_catalog(@scope).type.should == "Node" end it "should fail when it is a definition" do lambda { Puppet::Resource::Type.new(:definition, "top").ensure_in_catalog(@scope) }.should raise_error(ArgumentError) end it "should add the created resource to the scope's catalog" do @top.ensure_in_catalog(@scope) @compiler.catalog.resource(:class, "top").should be_instance_of(Puppet::Parser::Resource) end it "should add specified parameters to the resource" do @top.ensure_in_catalog(@scope, {'one'=>'1', 'two'=>'2'}) @compiler.catalog.resource(:class, "top")['one'].should == '1' @compiler.catalog.resource(:class, "top")['two'].should == '2' end it "should not require params for a param class" do @top.ensure_in_catalog(@scope, {}) @compiler.catalog.resource(:class, "top").should be_instance_of(Puppet::Parser::Resource) end it "should evaluate the parent class if one exists" do @middle.ensure_in_catalog(@scope) @compiler.catalog.resource(:class, "top").should be_instance_of(Puppet::Parser::Resource) end it "should evaluate the parent class if one exists" do @middle.ensure_in_catalog(@scope, {}) @compiler.catalog.resource(:class, "top").should be_instance_of(Puppet::Parser::Resource) end it "should fail if you try to create duplicate class resources" do othertop = Puppet::Parser::Resource.new(:class, 'top',:source => @source, :scope => @scope ) # add the same class resource to the catalog @compiler.catalog.add_resource(othertop) lambda { @top.ensure_in_catalog(@scope, {}) }.should raise_error(Puppet::Resource::Catalog::DuplicateResourceError) end it "should fail to evaluate if a parent class is defined but cannot be found" do othertop = Puppet::Resource::Type.new :hostclass, "something", :parent => "yay" @code.add othertop lambda { othertop.ensure_in_catalog(@scope) }.should raise_error(Puppet::ParseError) end it "should not create a new resource if one already exists" do @compiler.catalog.expects(:resource).with(:class, "top").returns("something") @compiler.catalog.expects(:add_resource).never @top.ensure_in_catalog(@scope) end it "should return the existing resource when not creating a new one" do @compiler.catalog.expects(:resource).with(:class, "top").returns("something") @compiler.catalog.expects(:add_resource).never @top.ensure_in_catalog(@scope).should == "something" end it "should not create a new parent resource if one already exists and it has a parent class" do @top.ensure_in_catalog(@scope) top_resource = @compiler.catalog.resource(:class, "top") @middle.ensure_in_catalog(@scope) @compiler.catalog.resource(:class, "top").should equal(top_resource) end # #795 - tag before evaluation. it "should tag the catalog with the resource tags when it is evaluated" do @middle.ensure_in_catalog(@scope) @compiler.catalog.should be_tagged("middle") end it "should tag the catalog with the parent class tags when it is evaluated" do @middle.ensure_in_catalog(@scope) @compiler.catalog.should be_tagged("top") end end describe "when merging code from another instance" do def code(str) Puppet::Parser::AST::Leaf.new :value => str end it "should fail unless it is a class" do lambda { Puppet::Resource::Type.new(:node, "bar").merge("foo") }.should raise_error(Puppet::Error) end it "should fail unless the source instance is a class" do dest = Puppet::Resource::Type.new(:hostclass, "bar") source = Puppet::Resource::Type.new(:node, "foo") lambda { dest.merge(source) }.should raise_error(Puppet::Error) end it "should fail if both classes have different parent classes" do code = Puppet::Resource::TypeCollection.new("env") {"a" => "b", "c" => "d"}.each do |parent, child| code.add Puppet::Resource::Type.new(:hostclass, parent) code.add Puppet::Resource::Type.new(:hostclass, child, :parent => parent) end lambda { code.hostclass("b").merge(code.hostclass("d")) }.should raise_error(Puppet::Error) end it "should fail if it's named 'main' and 'freeze_main' is enabled" do Puppet.settings[:freeze_main] = true code = Puppet::Resource::TypeCollection.new("env") code.add Puppet::Resource::Type.new(:hostclass, "") other = Puppet::Resource::Type.new(:hostclass, "") lambda { code.hostclass("").merge(other) }.should raise_error(Puppet::Error) end it "should copy the other class's parent if it has not parent" do dest = Puppet::Resource::Type.new(:hostclass, "bar") parent = Puppet::Resource::Type.new(:hostclass, "parent") source = Puppet::Resource::Type.new(:hostclass, "foo", :parent => "parent") dest.merge(source) dest.parent.should == "parent" end it "should copy the other class's documentation as its docs if it has no docs" do dest = Puppet::Resource::Type.new(:hostclass, "bar") source = Puppet::Resource::Type.new(:hostclass, "foo", :doc => "yayness") dest.merge(source) dest.doc.should == "yayness" end it "should append the other class's docs to its docs if it has any" do dest = Puppet::Resource::Type.new(:hostclass, "bar", :doc => "fooness") source = Puppet::Resource::Type.new(:hostclass, "foo", :doc => "yayness") dest.merge(source) dest.doc.should == "foonessyayness" end it "should turn its code into an ASTArray if necessary" do dest = Puppet::Resource::Type.new(:hostclass, "bar", :code => code("foo")) source = Puppet::Resource::Type.new(:hostclass, "foo", :code => code("bar")) dest.merge(source) dest.code.should be_instance_of(Puppet::Parser::AST::ASTArray) end it "should set the other class's code as its code if it has none" do dest = Puppet::Resource::Type.new(:hostclass, "bar") source = Puppet::Resource::Type.new(:hostclass, "foo", :code => code("bar")) dest.merge(source) dest.code.value.should == "bar" end it "should append the other class's code to its code if it has any" do dcode = Puppet::Parser::AST::ASTArray.new :children => [code("dest")] dest = Puppet::Resource::Type.new(:hostclass, "bar", :code => dcode) scode = Puppet::Parser::AST::ASTArray.new :children => [code("source")] source = Puppet::Resource::Type.new(:hostclass, "foo", :code => scode) dest.merge(source) dest.code.children.collect { |l| l.value }.should == %w{dest source} end end end diff --git a/spec/unit/util/logging_spec.rb b/spec/unit/util/logging_spec.rb index edc88f115..bc2eaff43 100755 --- a/spec/unit/util/logging_spec.rb +++ b/spec/unit/util/logging_spec.rb @@ -1,95 +1,120 @@ #!/usr/bin/env ruby require 'spec_helper' require 'puppet/util/logging' class LoggingTester include Puppet::Util::Logging end describe Puppet::Util::Logging do before do @logger = LoggingTester.new end Puppet::Util::Log.eachlevel do |level| it "should have a method for sending '#{level}' logs" do @logger.should respond_to(level) end end it "should have a method for sending a log with a specified log level" do @logger.expects(:to_s).returns "I'm a string!" Puppet::Util::Log.expects(:create).with { |args| args[:source] == "I'm a string!" and args[:level] == "loglevel" and args[:message] == "mymessage" } @logger.send_log "loglevel", "mymessage" end describe "when sending a log" do it "should use the Log's 'create' entrance method" do Puppet::Util::Log.expects(:create) @logger.notice "foo" end it "should send itself converted to a string as the log source" do @logger.expects(:to_s).returns "I'm a string!" Puppet::Util::Log.expects(:create).with { |args| args[:source] == "I'm a string!" } @logger.notice "foo" end it "should queue logs sent without a specified destination" do Puppet::Util::Log.close_all Puppet::Util::Log.expects(:queuemessage) @logger.notice "foo" end it "should use the path of any provided resource type" do resource = Puppet::Type.type(:mount).new :name => "foo" resource.expects(:path).returns "/path/to/mount".to_sym Puppet::Util::Log.expects(:create).with { |args| args[:source] == "/path/to/mount" } resource.notice "foo" end it "should use the path of any provided resource parameter" do resource = Puppet::Type.type(:mount).new :name => "foo" param = resource.parameter(:name) param.expects(:path).returns "/path/to/param".to_sym Puppet::Util::Log.expects(:create).with { |args| args[:source] == "/path/to/param" } param.notice "foo" end it "should send the provided argument as the log message" do Puppet::Util::Log.expects(:create).with { |args| args[:message] == "foo" } @logger.notice "foo" end it "should join any provided arguments into a single string for the message" do Puppet::Util::Log.expects(:create).with { |args| args[:message] == "foo bar baz" } @logger.notice ["foo", "bar", "baz"] end [:file, :line, :tags].each do |attr| it "should include #{attr} if available" do @logger.singleton_class.send(:attr_accessor, attr) @logger.send(attr.to_s + "=", "myval") Puppet::Util::Log.expects(:create).with { |args| args[attr] == "myval" } @logger.notice "foo" end end end + + describe "when sending a deprecation warning" do + before do + @logger.clear_deprecation_warnings + end + + it "should the message with warn" do + @logger.expects(:warning).with('foo') + @logger.deprecation_warning 'foo' + end + + it "should only log each unique message once" do + @logger.expects(:warning).with('foo').once + 5.times { @logger.deprecation_warning 'foo' } + end + + it "should only log the first 100 messages" do + (1..100).each { |i| + @logger.expects(:warning).with(i).once + @logger.deprecation_warning i + } + @logger.expects(:warning).with(101).never + @logger.deprecation_warning 101 + end + end end diff --git a/test/language/functions.rb b/test/language/functions.rb index e882b68f3..84b1b3861 100755 --- a/test/language/functions.rb +++ b/test/language/functions.rb @@ -1,543 +1,541 @@ #!/usr/bin/env ruby require File.expand_path(File.dirname(__FILE__) + '/../lib/puppettest') require 'puppet' require 'puppet/parser/parser' require 'puppet/network/client' require 'puppettest' require 'puppettest/resourcetesting' class TestLangFunctions < Test::Unit::TestCase include PuppetTest::ParserTesting include PuppetTest::ResourceTesting def test_functions Puppet::Node::Environment.stubs(:current).returns nil assert_nothing_raised do Puppet::Parser::AST::Function.new( :name => "fakefunction", :arguments => AST::ASTArray.new( :children => [nameobj("avalue")] ) ) end assert_raise(Puppet::ParseError) do func = Puppet::Parser::AST::Function.new( :name => "fakefunction", :arguments => AST::ASTArray.new( :children => [nameobj("avalue")] ) ) func.evaluate(mkscope) end assert_nothing_raised do Puppet::Parser::Functions.newfunction(:fakefunction, :type => :rvalue) do |input| return "output #{input[0]}" end end func = nil assert_nothing_raised do func = Puppet::Parser::AST::Function.new( :name => "fakefunction", :ftype => :rvalue, :arguments => AST::ASTArray.new( :children => [nameobj("avalue")] ) ) end scope = mkscope val = nil assert_nothing_raised do val = func.evaluate(scope) end assert_equal("output avalue", val) end def test_taggedfunction scope = mkscope scope.resource.tag("yayness") # Make sure the ast stuff does what it's supposed to {"yayness" => true, "booness" => false}.each do |tag, retval| func = taggedobj(tag, :rvalue) val = nil assert_nothing_raised do val = func.evaluate(scope) end assert_equal(retval, val, "'tagged' returned #{val} for #{tag}") end # Now make sure we correctly get tags. scope.resource.tag("resourcetag") assert(scope.function_tagged("resourcetag"), "tagged function did not catch resource tags") scope.compiler.catalog.tag("configtag") assert(scope.function_tagged("configtag"), "tagged function did not catch catalog tags") end def test_failfunction func = nil assert_nothing_raised do func = Puppet::Parser::AST::Function.new( :name => "fail", :ftype => :statement, :arguments => AST::ASTArray.new( :children => [stringobj("this is a failure"), stringobj("and another")] ) ) end scope = mkscope val = nil assert_raise(Puppet::ParseError) do val = func.evaluate(scope) end end def test_multipletemplates Dir.mkdir(Puppet[:templatedir]) onep = File.join(Puppet[:templatedir], "one") twop = File.join(Puppet[:templatedir], "two") File.open(onep, "w") do |f| f.puts "<%- if @one.nil? then raise '@one undefined' end -%>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 # Test that our manual exception throw fails the parse assert_raise(Puppet::ParseError) do ast.evaluate(scope) end # Test that our use of an undefined instance variable does not throw # an exception, but only safely continues. scope.setvar("one", "One") assert_nothing_raised do ast.evaluate(scope) end # Ensure that we got the output we expected from that evaluation. assert_equal("template One\ntemplate \n", scope.lookupvar("output"), "Undefined template variables do not raise exceptions") # Now, fill in the last variable and make sure the whole thing # evaluates correctly. scope.setvar("two", "Two") scope.unsetvar("output") assert_nothing_raised do ast.evaluate(scope) end assert_equal( "template One\ntemplate Two\n", scope.lookupvar("output"), "Templates were not handled correctly") end # Now make sure we can fully qualify files, and specify just one def test_singletemplates template = tempfile File.open(template, "w") do |f| f.puts "template <%= @yay.nil?() ? raise('yay undefined') : @yay %>" end func = nil assert_nothing_raised do func = Puppet::Parser::AST::Function.new( :name => "template", :ftype => :rvalue, :arguments => AST::ASTArray.new( :children => [stringobj(template)] ) ) end ast = varobj("output", func) scope = mkscope assert_raise(Puppet::ParseError) do ast.evaluate(scope) end scope.setvar("yay", "this is yay") assert_nothing_raised do ast.evaluate(scope) end assert_equal( "template this is yay\n", scope.lookupvar("output"), "Templates were not handled correctly") end # Make sure that legacy template variable access works as expected. def test_legacyvariables template = tempfile File.open(template, "w") do |f| f.puts "template <%= deprecated %>" 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) # Verify that we get an exception using old-style accessors. scope = mkscope assert_raise(Puppet::ParseError) do ast.evaluate(scope) end # Verify that we evaluate and return their value correctly. scope.setvar("deprecated", "deprecated value") assert_nothing_raised do ast.evaluate(scope) end assert_equal( "template deprecated value\n", scope.lookupvar("output"), "Deprecated template variables were not handled correctly") end # Make sure that problems with kernel method visibility still exist. def test_kernel_module_shadows_deprecated_var_lookup template = tempfile File.open(template, "w").puts("<%= binding %>") 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) # Verify that Kernel methods still shadow deprecated variable lookups. scope = mkscope assert_nothing_raised("No exception for Kernel shadowed variable names") do ast.evaluate(scope) end end def test_tempatefunction_cannot_see_scopes template = tempfile File.open(template, "w") do |f| f.puts "<%= lookupvar('myvar') %>" end func = nil assert_nothing_raised do func = Puppet::Parser::AST::Function.new( :name => "template", :ftype => :rvalue, :arguments => AST::ASTArray.new( :children => [stringobj(template)] ) ) end ast = varobj("output", func) scope = mkscope scope.setvar("myvar", "this is yayness") assert_raise(Puppet::ParseError) do ast.evaluate(scope) end end def test_template_reparses template = tempfile File.open(template, "w") do |f| f.puts "original text" end file = tempfile Puppet[:code] = %{file { "#{file}": content => template("#{template}") }} Puppet[:environment] = "yay" node = mknode node.stubs(:environment).returns Puppet::Node::Environment.new Puppet[:environment] = "yay" catalog = Puppet::Parser::Compiler.new(node).compile version = catalog.version fileobj = catalog.vertices.find { |r| r.title == file } assert(fileobj, "File was not in catalog") assert_equal( "original text\n", fileobj["content"], "Template did not work") Puppet[:filetimeout] = -5 # Have to sleep because one second is the fs's time granularity. sleep(1) # Now modify the template File.open(template, "w") do |f| f.puts "new text" end newversion = Puppet::Parser::Compiler.new(node).compile.version assert(version != newversion, "Parse date did not change") end def test_template_defined_vars template = tempfile File.open(template, "w") do |f| f.puts "template <%= @yayness %>" end func = nil assert_nothing_raised do func = Puppet::Parser::AST::Function.new( :name => "template", :ftype => :rvalue, :arguments => AST::ASTArray.new( :children => [stringobj(template)] ) ) end ast = varobj("output", func) { "" => "", false => "false", }.each do |string, value| scope = mkscope scope.setvar("yayness", string) - assert_equal(string, scope.lookupvar("yayness", false)) + assert_equal(string, scope.lookupvar("yayness")) assert_nothing_raised("An empty string was not a valid variable value") do ast.evaluate(scope) end - - assert_equal( - "template #{value}\n", scope.lookupvar("output"), - - "#{string.inspect} did not get evaluated correctly") + assert_equal( + "template #{value}\n", scope.lookupvar("output"), + "#{string.inspect} did not get evaluated correctly") end end def test_autoloading_functions #assert_equal(false, Puppet::Parser::Functions.function(:autofunc), # "Got told autofunc already exists") dir = tempfile $LOAD_PATH << 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 } } Puppet::Node::Environment.stubs(:current).returns nil obj = nil assert_nothing_raised { obj = Puppet::Parser::Functions.function(:autofunc) } assert(obj, "Did not autoload function") assert(Puppet::Parser::Functions.environment_module.method_defined?(:function_autofunc), "Did not set function correctly") end def test_search scope = mkscope fun = scope.known_resource_types.add Puppet::Resource::Type.new(:definition, "yay::ness") foo = scope.known_resource_types.add Puppet::Resource::Type.new(:definition, "foo::bar") search = Puppet::Parser::Functions.function(:search) scope.function_search(["foo", "yay"]) ffun = ffoo = nil assert_nothing_raised("Search path change did not work") do ffun = scope.find_definition("ness") ffoo = scope.find_definition('bar') end assert(ffun, "Could not find definition in 'fun' namespace") assert(ffoo, "Could not find definition in 'foo' namespace") end def test_include scope = mkscope parser = mkparser include = Puppet::Parser::Functions.function(:include) assert_raise(Puppet::Error, "did not throw error on missing class") do scope.function_include("nosuchclass") end scope.known_resource_types.add Puppet::Resource::Type.new(:hostclass, "myclass", {}) scope.compiler.expects(:evaluate_classes).with(%w{myclass otherclass}, scope, false).returns(%w{myclass otherclass}) assert_nothing_raised do scope.function_include(["myclass", "otherclass"]) end end def test_file parser = mkparser scope = mkscope(:parser => parser) file = Puppet::Parser::Functions.function(:file) 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") Puppet::Node::Environment.stubs(:current).returns nil generate = Puppet::Parser::Functions.function(:generate) scope = mkscope parser = mkparser val = nil assert_nothing_raised("Could not call generator with no args") do val = scope.function_generate([command]) end assert_equal("yay\n", val, "generator returned wrong results") assert_nothing_raised("Could not call generator with args") do val = scope.function_generate([command, "foo"]) end assert_equal("yay-foo\n", val, "generator returned wrong results") assert_raise(Puppet::ParseError, "Did not fail with an unqualified path") do val = scope.function_generate([File.basename(command), "foo"]) end assert_raise(Puppet::ParseError, "Did not fail when command failed") do val = scope.function_generate([%x{which touch}.chomp, "/this/dir/does/not/exist"]) end fake = File.join(File.dirname(command), "..") dir = File.dirname(command) dirname = File.basename(dir) bad = File.join(dir, "..", dirname, File.basename(command)) assert_raise(Puppet::ParseError, "Did not fail when command failed") do val = scope.function_generate([bad]) end end end diff --git a/test/language/scope.rb b/test/language/scope.rb index c4154dc27..ccc359651 100755 --- a/test/language/scope.rb +++ b/test/language/scope.rb @@ -1,277 +1,266 @@ #!/usr/bin/env ruby require File.expand_path(File.dirname(__FILE__) + '/../lib/puppettest') require 'mocha' require 'puppettest' require 'puppettest/parsertesting' require 'puppettest/resourcetesting' # so, what kind of things do we want to test? # we don't need to test function, since we're confident in the # library tests. We do, however, need to test how things are actually # working in the language. # so really, we want to do things like test that our ast is correct # and test whether we've got things in the right scopes class TestScope < Test::Unit::TestCase include PuppetTest::ParserTesting include PuppetTest::ResourceTesting def setup Puppet::Node::Environment.clear super end def to_ary(hash) hash.collect { |key,value| [key,value] } end def test_variables config = mkcompiler topscope = config.topscope midscope = config.newscope(topscope) botscope = config.newscope(midscope) scopes = {:top => topscope, :mid => midscope, :bot => botscope} # Set a variable in the top and make sure all three can get it topscope.setvar("first", "topval") scopes.each do |name, scope| - assert_equal("topval", scope.lookupvar("first", false), "Could not find var in #{name}") + assert_equal("topval", scope.lookupvar("first"), "Could not find var in #{name}") end # Now set a var in the midscope and make sure the mid and bottom can see it but not the top midscope.setvar("second", "midval") - assert_equal(:undefined, scopes[:top].lookupvar("second", false), "Found child var in top scope") + assert_equal(:undefined, scopes[:top].lookupvar("second"), "Found child var in top scope") [:mid, :bot].each do |name| - assert_equal("midval", scopes[name].lookupvar("second", false), "Could not find var in #{name}") + assert_equal("midval", scopes[name].lookupvar("second"), "Could not find var in #{name}") end # And set something in the bottom, and make sure we only find it there. botscope.setvar("third", "botval") [:top, :mid].each do |name| - assert_equal(:undefined, scopes[name].lookupvar("third", false), "Found child var in top scope") + assert_equal(:undefined, scopes[name].lookupvar("third"), "Found child var in top scope") end - assert_equal("botval", scopes[:bot].lookupvar("third", false), "Could not find var in bottom scope") + assert_equal("botval", scopes[:bot].lookupvar("third"), "Could not find var in bottom scope") # Test that the scopes convert to hash structures correctly. # For topscope recursive vs non-recursive should be identical assert_equal(topscope.to_hash(false), topscope.to_hash(true), "Recursive and non-recursive hash is identical for topscope") # Check the variable we expect is present. assert_equal({"first" => "topval"}, topscope.to_hash, "topscope returns the expected hash of variables") # Now, check that midscope does the right thing in all cases. assert_equal( {"second" => "midval"}, midscope.to_hash(false), "midscope non-recursive hash contains only midscope variable") assert_equal( {"first" => "topval", "second" => "midval"}, midscope.to_hash(true), "midscope recursive hash contains topscope variable also") # Finally, check the ability to shadow symbols by adding a shadow to # bottomscope, then checking that we see the right stuff. botscope.setvar("first", "shadowval") assert_equal( {"third" => "botval", "first" => "shadowval"}, botscope.to_hash(false), "botscope has the right non-recursive hash") assert_equal( {"third" => "botval", "first" => "shadowval", "second" => "midval"}, botscope.to_hash(true), "botscope values shadow parent scope values") 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_parent config = mkcompiler top = config.topscope # Make a subscope sub = config.newscope(top) assert_equal(top, sub.parent, "Did not find parent scope correctly") assert_equal(top, sub.parent, "Did not find parent scope on second call") end # 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 def test_virtual_definitions_do_not_get_evaluated parser = mkparser config = mkcompiler # Create a default source parser.known_resource_types.add Puppet::Resource::Type.new(:hostclass, "") config.topscope.source = parser.known_resource_types.hostclass("") # And a scope resource scope_res = Puppet::Parser::Resource.new(:file, "/file", :scope => config.topscope) config.topscope.resource = scope_res args = AST::ASTArray.new( :children => [nameobj("arg")] ) # Create a top-level define parser.known_resource_types.add Puppet::Resource::Type.new(:definition, "one", :arguments => [%w{arg}], :code => AST::ASTArray.new( :children => [ resourcedef("file", "/tmp", {"owner" => varref("arg")}) ] )) # create a resource that calls our third define obj = resourcedef("one", "boo", {"arg" => "parentfoo"}) # And mark it as virtual obj.virtual = true # And then evaluate it obj.evaluate config.topscope # And run the loop. config.send(:evaluate_generators) %w{File}.each do |type| objects = config.resources.find_all { |r| r.type == type and r.virtual } assert(objects.empty?, "Virtual define got evaluated") end end if defined? ::ActiveRecord # 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 catalog_cache_class = Puppet::Resource::Catalog.indirection.cache_class facts_cache_class = Puppet::Node::Facts.indirection.cache_class node_cache_class = Puppet::Node.indirection.cache_class Puppet[:storeconfigs] = true Puppet::Rails.init sleep 1 children = [] Puppet[:code] = " class yay { @@host { myhost: ip => \"192.168.0.2\" } } include yay @@host { puppet: ip => \"192.168.0.3\" } Host <<||>>" config = nil # We run it twice because we want to make sure there's no conflict # if we pull it up from the database. node = mknode node.merge "hostname" => node.name 2.times { |i| catalog = Puppet::Parser::Compiler.new(node).compile assert_instance_of(Puppet::Parser::Resource, catalog.resource(:host, "puppet")) assert_instance_of(Puppet::Parser::Resource, catalog.resource(:host, "myhost")) } ensure Puppet[:storeconfigs] = false Puppet::Resource::Catalog.indirection.cache_class = catalog_cache_class Puppet::Node::Facts.indirection.cache_class = facts_cache_class Puppet::Node.indirection.cache_class = node_cache_class end else $stderr.puts "No ActiveRecord -- skipping collection tests" end def test_namespaces scope = mkscope assert_equal( [""], scope.namespaces, "Started out with incorrect namespaces") assert_nothing_raised { scope.add_namespace("fun::test") } assert_equal(["fun::test"], scope.namespaces, "Did not add namespace correctly") assert_nothing_raised { scope.add_namespace("yay::test") } assert_equal(["fun::test", "yay::test"], scope.namespaces, "Did not add extra namespace correctly") end # #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") + assert_equal(:undef, scope.lookupvar("testing"), "undef was not returned as :undef") end end