diff options
25 files changed, 1030 insertions, 1322 deletions
diff --git a/lib/puppet/parser/ast.rb b/lib/puppet/parser/ast.rb index ad8af7496..feceb60e0 100644 --- a/lib/puppet/parser/ast.rb +++ b/lib/puppet/parser/ast.rb @@ -99,15 +99,12 @@ require 'puppet/parser/ast/casestatement' require 'puppet/parser/ast/collection' require 'puppet/parser/ast/collexpr' require 'puppet/parser/ast/comparison_operator' -require 'puppet/parser/ast/definition' require 'puppet/parser/ast/else' require 'puppet/parser/ast/function' -require 'puppet/parser/ast/hostclass' require 'puppet/parser/ast/ifstatement' require 'puppet/parser/ast/leaf' require 'puppet/parser/ast/match_operator' require 'puppet/parser/ast/minus' -require 'puppet/parser/ast/node' require 'puppet/parser/ast/nop' require 'puppet/parser/ast/not' require 'puppet/parser/ast/resource' diff --git a/lib/puppet/parser/ast/definition.rb b/lib/puppet/parser/ast/definition.rb deleted file mode 100644 index 00b0416a0..000000000 --- a/lib/puppet/parser/ast/definition.rb +++ /dev/null @@ -1,207 +0,0 @@ -require 'puppet/parser/ast/branch' - -require 'puppet/util/warnings' - -# The AST class for defined types, which is also the base class -# nodes and classes. -class Puppet::Parser::AST::Definition < Puppet::Parser::AST::Branch - include Puppet::Util::Warnings - class << self - attr_accessor :name - end - - associates_doc - - # The class name - @name = :definition - - attr_accessor :classname, :arguments, :code, :scope, :keyword - attr_accessor :exported, :namespace, :parser, :virtual, :name - - attr_reader :parentclass - - def child_of?(klass) - false - end - - def get_classname(scope) - self.classname - end - - # Create a resource that knows how to evaluate our actual code. - def evaluate(scope) - resource = Puppet::Parser::Resource.new(:type => self.class.name, :title => get_classname(scope), :scope => scope, :source => scope.source) - - scope.catalog.tag(*resource.tags) - - scope.compiler.add_resource(scope, resource) - - return resource - end - - # Now evaluate the code associated with this class or definition. - def evaluate_code(resource) - # Create a new scope. - scope = subscope(resource.scope, resource) - - set_resource_parameters(scope, resource) - - if self.code - return self.code.safeevaluate(scope) - else - return nil - end - end - - def initialize(hash = {}) - @arguments = nil - @parentclass = nil - super - - # Convert the arguments to a hash for ease of later use. - if @arguments - unless @arguments.is_a? Array - @arguments = [@arguments] - end - oldargs = @arguments - @arguments = {} - oldargs.each do |arg, val| - @arguments[arg] = val - end - else - @arguments = {} - end - - # Deal with metaparams in the argument list. - @arguments.each do |arg, defvalue| - next unless Puppet::Type.metaparamclass(arg) - if defvalue - warnonce "%s is a metaparam; this value will inherit to all contained resources" % arg - else - raise Puppet::ParseError, "%s is a metaparameter; please choose another parameter name in the %s definition" % [arg, self.classname] - end - end - end - - def find_parentclass - @parser.find_hostclass(namespace, parentclass) - end - - # Set our parent class, with a little check to avoid some potential - # weirdness. - def parentclass=(name) - if name == self.classname - parsefail "Parent classes must have dissimilar names" - end - - @parentclass = name - end - - # Hunt down our class object. - def parentobj - return nil unless @parentclass - - # Cache our result, since it should never change. - unless defined?(@parentobj) - unless tmp = find_parentclass - parsefail "Could not find %s parent %s" % [self.class.name, @parentclass] - end - - if tmp == self - parsefail "Parent classes must have dissimilar names" - end - - @parentobj = tmp - end - @parentobj - end - - # Create a new subscope in which to evaluate our code. - def subscope(scope, resource) - args = { - :resource => resource, - :keyword => self.keyword, - :namespace => self.namespace, - :source => self - } - - oldscope = scope - scope = scope.newscope(args) - scope.source = self - - return scope - end - - def to_s - classname - end - - # Check whether a given argument is valid. Searches up through - # any parent classes that might exist. - def validattr?(param) - param = param.to_s - - if @arguments.include?(param) - # It's a valid arg for us - return true - elsif param == "name" - return true -# elsif defined? @parentclass and @parentclass -# # Else, check any existing parent -# if parent = @scope.lookuptype(@parentclass) and parent != [] -# return parent.validarg?(param) -# elsif builtin = Puppet::Type.type(@parentclass) -# return builtin.validattr?(param) -# else -# raise Puppet::Error, "Could not find parent class %s" % -# @parentclass -# end - elsif Puppet::Type.metaparam?(param) - return true - else - # Or just return false - return false - end - end - - private - - # Set any arguments passed by the resource as variables in the scope. - def set_resource_parameters(scope, resource) - args = symbolize_options(resource.to_hash || {}) - - # Verify that all required arguments are either present or - # have been provided with defaults. - if self.arguments - self.arguments.each { |arg, default| - arg = arg.to_sym - unless args.include?(arg) - if defined? default and ! default.nil? - default = default.safeevaluate scope - args[arg] = default - #Puppet.debug "Got default %s for %s in %s" % - # [default.inspect, arg.inspect, @name.inspect] - else - parsefail "Must pass %s to %s of type %s" % - [arg, resource.title, @classname] - end - end - } - end - - # Set each of the provided arguments as variables in the - # definition's scope. - args.each { |arg,value| - unless validattr?(arg) - parsefail "%s does not accept attribute %s" % [@classname, arg] - end - - exceptwrap do - scope.setvar(arg.to_s, args[arg]) - end - } - - scope.setvar("title", resource.title) unless args.include? :title - scope.setvar("name", resource.name) unless args.include? :name - end -end diff --git a/lib/puppet/parser/ast/hostclass.rb b/lib/puppet/parser/ast/hostclass.rb deleted file mode 100644 index 23d9a00e5..000000000 --- a/lib/puppet/parser/ast/hostclass.rb +++ /dev/null @@ -1,95 +0,0 @@ -require 'puppet/parser/ast/definition' - -# The code associated with a class. This is different from definitions -# in that each class is a singleton -- only one will exist for a given -# node. -class Puppet::Parser::AST::HostClass < Puppet::Parser::AST::Definition - - associates_doc - - @name = :class - - # Are we a child of the passed class? Do a recursive search up our - # parentage tree to figure it out. - def child_of?(klass) - return false unless self.parentclass - - if klass == self.parentobj - return true - else - return self.parentobj.child_of?(klass) - end - end - - # Make sure our parent class has been evaluated, if we have one. - def evaluate(scope) - if parentclass and ! scope.catalog.resource(self.class.name, parentclass) - parent_resource = parentobj.evaluate(scope) - end - - # 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. - if resource = scope.catalog.resource(self.class.name, self.classname) - return resource - end - - super - end - - # Evaluate the code associated with this class. - def evaluate_code(resource) - scope = resource.scope - # Verify that we haven't already been evaluated. This is - # what provides the singleton aspect. - if existing_scope = scope.compiler.class_scope(self) - Puppet.debug "Class '%s' already evaluated; not evaluating again" % (classname == "" ? "main" : classname) - return nil - end - - pnames = nil - if pklass = self.parentobj - parent_resource = resource.scope.compiler.catalog.resource(self.class.name, pklass.classname) - # This shouldn't evaluate if the class has already been evaluated. - pklass.evaluate_code(parent_resource) - - scope = parent_scope(scope, pklass) - pnames = scope.namespaces - end - - # Don't create a subscope for the top-level class, since it already - # has its own scope. - unless resource.title == :main - scope = subscope(scope, resource) - - scope.setvar("title", resource.title) - scope.setvar("name", resource.name) - end - - # Add the parent scope namespaces to our own. - if pnames - pnames.each do |ns| - scope.add_namespace(ns) - end - end - - # Set the class before we evaluate the code, so that it's set during - # the evaluation and can be inspected. - scope.compiler.class_set(self.classname, scope) - - # Now evaluate our code, yo. - if self.code - return self.code.safeevaluate(scope) - else - return nil - end - end - - def parent_scope(scope, klass) - if s = scope.compiler.class_scope(klass) - return s - else - raise Puppet::DevError, "Could not find scope for %s" % klass.classname - end - end -end diff --git a/lib/puppet/parser/ast/leaf.rb b/lib/puppet/parser/ast/leaf.rb index b73c781e1..07bba1b4c 100644 --- a/lib/puppet/parser/ast/leaf.rb +++ b/lib/puppet/parser/ast/leaf.rb @@ -94,6 +94,7 @@ class Puppet::Parser::AST 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, @@ -101,10 +102,6 @@ class Puppet::Parser::AST end end - def to_classname - to_s.downcase.gsub(/[^-\w:.]/,'').sub(/^\.+/,'') - 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) @@ -116,25 +113,6 @@ class Puppet::Parser::AST return @value.hash end - def match(value) - return @value.match(value) unless value.is_a?(HostName) - - if value.regex? and self.regex? - # Wow this is some sweet design; maybe a touch of refactoring - # in order here. - return value.value.value == self.value.value - elsif value.regex? # we know if the existing name is not a regex, it won't match a regex - return false - else - # else, we could be either a regex or normal and it doesn't matter - return @value.match(value.value) - end - end - - def regex? - @value.is_a?(Regex) - end - def to_s @value.to_s end diff --git a/lib/puppet/parser/ast/node.rb b/lib/puppet/parser/ast/node.rb deleted file mode 100644 index 4f75201eb..000000000 --- a/lib/puppet/parser/ast/node.rb +++ /dev/null @@ -1,42 +0,0 @@ -require 'puppet/parser/ast/hostclass' - -# The specific code associated with a host. Nodes are annoyingly unlike -# other objects. That's just the way it is, at least for now. -class Puppet::Parser::AST::Node < Puppet::Parser::AST::HostClass - - associates_doc - - @name = :node - - def initialize(options) - @parentclass = nil - super - end - - def namespace - "" - end - - # in Regex mode, our classname can't be our Regex. - # so we use the currently connected client as our - # classname, mimicing exactly what would have happened - # if there was a specific node definition for this node. - def get_classname(scope) - return scope.host if name.regex? - classname - end - - # Make sure node scopes are marked as such. - def subscope(*args) - scope = super - scope.nodescope = true - scope - end - - private - - # Search for the object matching our parent class. - def find_parentclass - @parser.node(parentclass) - end -end diff --git a/lib/puppet/parser/ast/resource_reference.rb b/lib/puppet/parser/ast/resource_reference.rb index 117bc88af..794e505b8 100644 --- a/lib/puppet/parser/ast/resource_reference.rb +++ b/lib/puppet/parser/ast/resource_reference.rb @@ -44,7 +44,7 @@ class Puppet::Parser::AST def qualified_class(scope, title) # Look up the full path to the class if classobj = scope.find_hostclass(title) - title = classobj.classname + title = classobj.name else raise Puppet::ParseError, "Could not find class %s" % title end @@ -57,7 +57,7 @@ class Puppet::Parser::AST objtype = @type.downcase unless builtintype?(objtype) if dtype = scope.find_definition(objtype) - objtype = dtype.classname + objtype = dtype.name else raise Puppet::ParseError, "Could not find resource type %s" % objtype end diff --git a/lib/puppet/parser/compiler.rb b/lib/puppet/parser/compiler.rb index 77b0bb98e..6b6cd6815 100644 --- a/lib/puppet/parser/compiler.rb +++ b/lib/puppet/parser/compiler.rb @@ -71,8 +71,8 @@ class Puppet::Parser::Compiler # variables. def class_scope(klass) # They might pass in either the class or class name - if klass.respond_to?(:classname) - @class_scopes[klass.classname] + if klass.respond_to?(:name) + @class_scopes[klass.name] else @class_scopes[klass] end @@ -140,7 +140,7 @@ class Puppet::Parser::Compiler if klass = scope.find_hostclass(name) found << name and next if class_scope(klass) - resource = klass.evaluate(scope) + resource = klass.mk_plain_resource(scope) # If they've disabled lazy evaluation (which the :include function does), # then evaluate our resource immediately. @@ -230,7 +230,7 @@ class Puppet::Parser::Compiler # Create a resource to model this node, and then add it to the list # of resources. - resource = astnode.evaluate(topscope) + resource = astnode.mk_plain_resource(topscope) resource.evaluate diff --git a/lib/puppet/parser/loaded_code.rb b/lib/puppet/parser/loaded_code.rb index 3efd115bc..d7f179ae7 100644 --- a/lib/puppet/parser/loaded_code.rb +++ b/lib/puppet/parser/loaded_code.rb @@ -8,44 +8,62 @@ class Puppet::Parser::LoadedCode @node_list = [] end - def add_hostclass(name, code) - @hostclasses[munge_name(name)] = code + def <<(thing) + add(thing) + self + end + + def add(instance) + method = "add_#{instance.type}" + send(method, instance) + instance.code_collection = self + instance + end + + def add_hostclass(instance) + dupe_check(instance, @hostclasses) { |dupe| "Class #{instance.name} is already defined#{dupe.error_context}; cannot redefine" } + dupe_check(instance, @definitions) { |dupe| "Definition #{instance.name} is already defined#{dupe.error_context}; cannot be redefined as a class" } + + @hostclasses[instance.name] = instance + instance end def hostclass(name) @hostclasses[munge_name(name)] end - def add_node(name, code) - name = check_name(name) - @node_list << name unless @node_list.include?(name) - @nodes[name] = code + def add_node(instance) + dupe_check(instance, @nodes) { |dupe| "Node #{instance.name} is already defined#{dupe.error_context}; cannot redefine" } + + @node_list << instance + @nodes[instance.name] = instance + instance end def node(name) - name = check_name(name) + name = munge_name(name) if node = @nodes[name] return node end - @node_list.each do |nodename| - n = @nodes[nodename] - return n if nodename.regex? and nodename.match(name) + @node_list.each do |node| + next unless node.name_is_regex? + return node if node.match(name) end nil end def node_exists?(name) - @nodes[check_name(name)] + @nodes[munge_name(name)] end def nodes? @nodes.length > 0 end - def add_definition(name, code) - @definitions[munge_name(name)] = code + def add_definition(code) + @definitions[code.name] = code end def definition(name) @@ -108,12 +126,9 @@ class Puppet::Parser::LoadedCode name.to_s.downcase end - # Check that the given (node) name is an HostName instance - # We're doing this so that hashing of node in the @nodes hash - # is consistent (see AST::HostName#hash and AST::HostName#eql?) - # and that the @nodes hash still keep its O(1) get/put properties. - def check_name(name) - name = Puppet::Parser::AST::HostName.new(:value => name) unless name.is_a?(Puppet::Parser::AST::HostName) - name + def dupe_check(instance, hash) + return unless dupe = hash[instance.name] + message = yield dupe + instance.fail Puppet::ParseError, message end end diff --git a/lib/puppet/parser/parser_support.rb b/lib/puppet/parser/parser_support.rb index 7a8fa812a..1b961cd04 100644 --- a/lib/puppet/parser/parser_support.rb +++ b/lib/puppet/parser/parser_support.rb @@ -4,6 +4,7 @@ class Puppet::Parser::Parser require 'puppet/parser/functions' require 'puppet/parser/files' require 'puppet/parser/loaded_code' + require 'puppet/parser/resource_type' require 'monitor' AST = Puppet::Parser::AST @@ -42,17 +43,16 @@ class Puppet::Parser::Parser # Create an AST object, and automatically add the file and line information if # available. def ast(klass, hash = {}) - hash[:line] = @lexer.line unless hash.include?(:line) - - unless hash.include?(:file) - if file = @lexer.file - hash[:file] = file - end - end + klass.new ast_context(klass.use_docs).merge(hash) + end - k = klass.new(hash) - k.doc = lexer.getcomment(hash[:line]) if !k.nil? and k.use_docs and k.doc.empty? - return k + def ast_context(include_docs = false) + result = { + :line => lexer.line, + :file => lexer.file + } + result[:doc] = lexer.getcomment(result[:line]) if include_docs + result end # The fully qualifed name, with the full namespace. @@ -276,126 +276,21 @@ class Puppet::Parser::Parser # Create a new class, or merge with an existing class. def newclass(name, options = {}) - name = name.downcase - - if @loaded_code.definition(name) - raise Puppet::ParseError, "Cannot redefine class %s as a definition" % name - end - code = options[:code] - parent = options[:parent] - doc = options[:doc] - - # If the class is already defined, then add code to it. - if other = @loaded_code.hostclass(name) || @loaded_code.definition(name) - # Make sure the parents match - if parent and other.parentclass and (parent != other.parentclass) - error("Class %s is already defined at %s:%s; cannot redefine" % [name, other.file, other.line]) - end - - # This might be dangerous... - if parent and ! other.parentclass - other.parentclass = parent - end - - # This might just be an empty, stub class. - if code - tmp = name - if tmp == "" - tmp = "main" - end - - Puppet.debug addcontext("Adding code to %s" % tmp) - # Else, add our code to it. - if other.code and code - # promote if neededcodes to ASTArray so that we can append code - # ASTArray knows how to evaluate its members. - other.code = ast AST::ASTArray, :children => [other.code] unless other.code.is_a?(AST::ASTArray) - code = ast AST::ASTArray, :children => [code] unless code.is_a?(AST::ASTArray) - other.code.children += code.children - else - other.code ||= code - end - end - - if other.doc and doc - other.doc += doc - else - other.doc ||= doc - end - else - # Define it anew. - # Note we're doing something somewhat weird here -- we're setting - # the class's namespace to its fully qualified name. This means - # anything inside that class starts looking in that namespace first. - args = {:namespace => name, :classname => name, :parser => self} - args[:code] = code if code - args[:parentclass] = parent if parent - args[:doc] = doc - args[:line] = options[:line] - - @loaded_code.add_hostclass(name, ast(AST::HostClass, args)) - end - - return @loaded_code.hostclass(name) + @loaded_code.add Puppet::Parser::ResourceType.new(:hostclass, name, ast_context(true).merge(options)) end # Create a new definition. def newdefine(name, options = {}) - name = name.downcase - if @loaded_code.hostclass(name) - raise Puppet::ParseError, "Cannot redefine class %s as a definition" % - name - end - # Make sure our definition doesn't already exist - if other = @loaded_code.definition(name) - error("%s is already defined at %s:%s; cannot redefine" % [name, other.file, other.line]) - end - - ns, whatever = namesplit(name) - args = { - :namespace => ns, - :arguments => options[:arguments], - :code => options[:code], - :parser => self, - :classname => name, - :doc => options[:doc], - :line => options[:line] - } - - [:code, :arguments].each do |param| - args[param] = options[param] if options[param] - end - - @loaded_code.add_definition(name, ast(AST::Definition, args)) + @loaded_code.add Puppet::Parser::ResourceType.new(:definition, name, ast_context(true).merge(options)) end # Create a new node. Nodes are special, because they're stored in a global # table, not according to namespaces. def newnode(names, options = {}) names = [names] unless names.instance_of?(Array) - doc = lexer.getcomment + context = ast_context(true) names.collect do |name| - name = AST::HostName.new :value => name unless name.is_a?(AST::HostName) - if other = @loaded_code.node_exists?(name) - error("Node %s is already defined at %s:%s; cannot redefine" % [other.name, other.file, other.line]) - end - name = name.to_s if name.is_a?(Symbol) - args = { - :name => name, - :parser => self, - :doc => doc, - :line => options[:line] - } - if options[:code] - args[:code] = options[:code] - end - if options[:parent] - args[:parentclass] = options[:parent] - end - node = ast(AST::Node, args) - node.classname = name.to_classname - @loaded_code.add_node(name, node) - node + @loaded_code.add(Puppet::Parser::ResourceType.new(:node, name, context.merge(options))) end end diff --git a/lib/puppet/parser/resource_type.rb b/lib/puppet/parser/resource_type.rb new file mode 100644 index 000000000..a67965960 --- /dev/null +++ b/lib/puppet/parser/resource_type.rb @@ -0,0 +1,235 @@ +require 'puppet/parser/parser' +require 'puppet/util/warnings' +require 'puppet/util/errors' +require 'puppet/util/inline_docs' +require 'puppet/parser/ast/leaf' + +class Puppet::Parser::ResourceType + include Puppet::Util::InlineDocs + include Puppet::Util::Warnings + include Puppet::Util::Errors + + RESOURCE_SUPERTYPES = [:hostclass, :node, :definition] + + attr_accessor :file, :line, :doc, :code, :parent, :code_collection + attr_reader :type, :namespace, :arguments, :behaves_like + + # 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 true if klass == parent_type + return parent_type.child_of?(klass) + end + + # Now evaluate the code associated with this class or definition. + def evaluate_code(resource) + # Create a new scope. + scope = subscope(resource.scope, resource) + + set_resource_parameters(resource, scope) + + return nil unless c = self.code + return c.safeevaluate(scope) + 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]) + 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? + + return @name =~ string + end + + # Add code from a new instance to our code. + def merge(other) + fail ArgumentError, "#{name} is not a class; cannot add code to it" unless type == :hostclass + fail ArgumentError, "#{other.name} is not a class; cannot add code from it" unless other.type == :hostclass + + if parent and other.parent and parent != other.parent + fail ArgumentError, "Cannot merge classes with different parent classes" + 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 + unless self.code.is_a?(array_class) + self.code = array_class.new(:children => [self.code]) + end + + if other.code.is_a?(array_class) + code.children += other.code.children + else + code.children << other.code + end + end + + # Make an instance of our resource type. This is only possible + # for those classes and nodes that don't have any arguments, and is + # only useful for things like the 'include' function. + def mk_plain_resource(scope) + type == :definition and raise ArgumentError, "Cannot create resources for defined resource types" + resource_type = type == :hostclass ? :class : :node + + # Make sure our parent class has been evaluated, if we have one. + if parent and ! scope.catalog.resource(resource_type, parent) + parent_type.mk_plain_resource(scope) + end + + # 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. + if resource = scope.catalog.resource(resource_type, name) + return resource + end + + resource = Puppet::Parser::Resource.new(:type => resource_type, :title => name, :scope => scope, :source => self) + scope.compiler.add_resource(scope, resource) + scope.catalog.tag(*resource.tags) + resource + end + + def name + return @name unless @name.is_a?(Regexp) + return @name.source.downcase.gsub(/[^-\w:.]/,'').sub(/^\.+/,'') + end + + def name_is_regex? + @name.is_a?(Regexp) + end + + def parent_type + return nil unless parent + + unless @parent_type ||= code_collection.send(type, parent) + fail Puppet::ParseError, "Could not find parent resource type '#{parent}'" + 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 validattr?(param) + + exceptwrap { scope.setvar(param.to_s, value) } + + set[param] = true + end + + # 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 + + scope.setvar(param.to_s, default.safeevaluate(scope)) + end + + scope.setvar("title", resource.title) unless set.include? :title + scope.setvar("name", resource.name) unless set.include? :name + 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 validattr?(param) + param = param.to_s + + return true if param == "name" + return true if Puppet::Type.metaparam?(param) + return false unless defined?(@arguments) + return true if arguments.include?(param) + return 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 + + # 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 set_name_and_namespace(name) + if name.is_a?(Regexp) + @name = name + @namespace = "" + else + @name = name.to_s.downcase + @namespace, ignored_shortname = 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/rdoc/parser.rb b/lib/puppet/util/rdoc/parser.rb index c2d307751..3f04fbf52 100644 --- a/lib/puppet/util/rdoc/parser.rb +++ b/lib/puppet/util/rdoc/parser.rb @@ -323,8 +323,8 @@ class Parser # that contains the documentation def parse_elements(container) Puppet.debug "rdoc: scanning manifest" - @ast.hostclasses.values.sort { |a,b| a.classname <=> b.classname }.each do |klass| - name = klass.classname + @ast.hostclasses.values.sort { |a,b| a.name <=> b.name }.each do |klass| + name = klass.name if klass.file == @input_file_name unless name.empty? document_class(name,klass,container) @@ -463,4 +463,4 @@ class Parser comment.sub!(/^#--.*/m, '') end end -end
\ No newline at end of file +end diff --git a/spec/integration/parser/parser.rb b/spec/integration/parser/parser.rb new file mode 100755 index 000000000..71ae136ed --- /dev/null +++ b/spec/integration/parser/parser.rb @@ -0,0 +1,21 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +describe Puppet::Parser::Parser do + before :each do + @loaded_code = Puppet::Parser::LoadedCode.new + @parser = Puppet::Parser::Parser.new :environment => "development", :loaded_code => @loaded_code + end + + describe "when parsing comments before statement" do + it "should associate the documentation to the statement AST node" do + ast = @parser.parse(""" + # comment + class test {} + """) + + ast.hostclass("test").doc.should == "comment\n" + end + end +end diff --git a/spec/integration/util/rdoc/parser.rb b/spec/integration/util/rdoc/parser.rb index df8c62df6..542660998 100755 --- a/spec/integration/util/rdoc/parser.rb +++ b/spec/integration/util/rdoc/parser.rb @@ -36,7 +36,7 @@ describe RDoc::Parser do end it "should parse to RDoc data structure" do - @parser.expects(:document_class).with { |n,k,c| n == "::test" and k.is_a?(Puppet::Parser::AST::HostClass) } + @parser.expects(:document_class).with { |n,k,c| n == "::test" and k.is_a?(Puppet::Parser::ResourceType) } @parser.scan end -end
\ No newline at end of file +end diff --git a/spec/unit/parser/ast.rb b/spec/unit/parser/ast.rb index 35f575b1b..ca6f8080a 100644 --- a/spec/unit/parser/ast.rb +++ b/spec/unit/parser/ast.rb @@ -20,9 +20,9 @@ describe Puppet::Parser::AST do ast.should respond_to(:use_docs) end - [ Puppet::Parser::AST::Collection, Puppet::Parser::AST::Definition, Puppet::Parser::AST::Else, - Puppet::Parser::AST::Function, Puppet::Parser::AST::HostClass, Puppet::Parser::AST::IfStatement, - Puppet::Parser::AST::Node, Puppet::Parser::AST::Resource, Puppet::Parser::AST::ResourceDefaults, + [ Puppet::Parser::AST::Collection, Puppet::Parser::AST::Else, + Puppet::Parser::AST::Function, Puppet::Parser::AST::IfStatement, + Puppet::Parser::AST::Resource, Puppet::Parser::AST::ResourceDefaults, Puppet::Parser::AST::ResourceOverride, Puppet::Parser::AST::VarDef ].each do |k| it "#{k}.use_docs should return true" do diff --git a/spec/unit/parser/ast/definition.rb b/spec/unit/parser/ast/definition.rb deleted file mode 100755 index c26706524..000000000 --- a/spec/unit/parser/ast/definition.rb +++ /dev/null @@ -1,213 +0,0 @@ -#!/usr/bin/env ruby - -require File.dirname(__FILE__) + '/../../../spec_helper' - -describe Puppet::Parser::AST::Definition, "when initializing" do -end - -describe Puppet::Parser::AST::Definition, "when evaluating" do - before do - @type = Puppet::Parser::Resource - @parser = Puppet::Parser::Parser.new :Code => "" - @source = @parser.newclass "" - @definition = @parser.newdefine "mydefine" - @node = Puppet::Node.new("yaynode") - @compiler = Puppet::Parser::Compiler.new(@node, @parser) - @scope = @compiler.topscope - - @resource = Puppet::Parser::Resource.new(:type => "mydefine", :title => "myresource", :scope => @scope, :source => @source) - end - - it "should create a new scope" do - scope = nil - code = mock 'code' - code.expects(:safeevaluate).with do |scope| - scope.object_id.should_not == @scope.object_id - true - end - @definition.stubs(:code).returns(code) - @definition.evaluate_code(@resource) - end - - it "should have a get_classname method" do - @definition.should respond_to(:get_classname) - end - - it "should return the current classname with get_classname" do - @definition.expects(:classname) - - @definition.get_classname(@scope) - end - - describe "when evaluating" do - it "should create a resource whose title comes from get_classname" do - @definition.expects(:get_classname).returns("classname") - - @definition.evaluate(@scope) - end - end - -# it "should copy its namespace to the scope" -# -# it "should mark the scope virtual if the resource is virtual" -# -# it "should mark the scope exported if the resource is exported" -# -# it "should set the resource's parameters as variables in the scope" -# -# it "should set the resource's title as a variable in the scope" -# -# it "should copy the resource's title in a 'name' variable in the scope" -# -# it "should not copy the resource's title as the name if 'name' is one of the resource parameters" -# -# it "should evaluate the associated code with the new scope" - - def old_test_initialize - parser = mkparser - - # Create a new definition - klass = parser.newdefine "yayness", - :arguments => [["owner", stringobj("nobody")], %w{mode}], - :code => AST::ASTArray.new( - :children => [resourcedef("file", "/tmp/$name", - "owner" => varref("owner"), "mode" => varref("mode"))] - ) - - # Test validattr? a couple different ways - [:owner, "owner", :schedule, "schedule"].each do |var| - assert(klass.validattr?(var), "%s was not considered valid" % var.inspect) - end - - [:random, "random"].each do |var| - assert(! klass.validattr?(var), "%s was considered valid" % var.inspect) - end - - end - - def oldtest_evaluate - parser = mkparser - config = mkcompiler - config.send(:evaluate_main) - scope = config.topscope - klass = parser.newdefine "yayness", - :arguments => [["owner", stringobj("nobody")], %w{mode}], - :code => AST::ASTArray.new( - :children => [resourcedef("file", "/tmp/$name", - "owner" => varref("owner"), "mode" => varref("mode"))] - ) - - resource = Puppet::Parser::Resource.new( - :title => "first", - :type => "yayness", - :exported => false, - :virtual => false, - :scope => scope, - :source => scope.source - ) - resource.send(:set_parameter, "name", "first") - resource.send(:set_parameter, "mode", "755") - - resource.stubs(:title) - assert_nothing_raised do - klass.evaluate_code(resource) - end - - firstobj = config.findresource("File[/tmp/first]") - assert(firstobj, "Did not create /tmp/first obj") - - assert_equal("File", firstobj.type) - assert_equal("/tmp/first", firstobj.title) - assert_equal("nobody", firstobj[:owner]) - assert_equal("755", firstobj[:mode]) - - # Make sure we can't evaluate it with the same args - assert_raise(Puppet::ParseError) do - klass.evaluate_code(resource) - end - - # Now create another with different args - resource2 = Puppet::Parser::Resource.new( - :title => "second", - :type => "yayness", - :exported => false, - :virtual => false, - :scope => scope, - :source => scope.source - ) - resource2.send(:set_parameter, "name", "second") - resource2.send(:set_parameter, "mode", "755") - resource2.send(:set_parameter, "owner", "daemon") - - assert_nothing_raised do - klass.evaluate_code(resource2) - end - - secondobj = config.findresource("File[/tmp/second]") - assert(secondobj, "Did not create /tmp/second obj") - - assert_equal("File", secondobj.type) - assert_equal("/tmp/second", secondobj.title) - assert_equal("daemon", secondobj[:owner]) - assert_equal("755", secondobj[:mode]) - end - - # #539 - definitions should support both names and titles - def oldtest_names_and_titles - parser = mkparser - scope = mkscope :parser => parser - - [ - {:name => "one", :title => "two"}, - {:title => "mytitle"} - ].each_with_index do |hash, i| - # Create a definition that uses both name and title. Put this - # inside the loop so the subscope expectations work. - klass = parser.newdefine "yayness%s" % i - - resource = Puppet::Parser::Resource.new( - :title => hash[:title], - :type => "yayness%s" % i, - :exported => false, - :virtual => false, - :scope => scope, - :source => scope.source - ) - - subscope = klass.subscope(scope, resource) - - klass.expects(:subscope).returns(subscope) - - if hash[:name] - resource.stubs(:to_hash).returns({:name => hash[:name]}) - end - - assert_nothing_raised("Could not evaluate definition with %s" % hash.inspect) do - klass.evaluate_code(resource) - end - - name = hash[:name] || hash[:title] - title = hash[:title] - - assert_equal(name, subscope.lookupvar("name"), - "Name did not get set correctly") - assert_equal(title, subscope.lookupvar("title"), - "title did not get set correctly") - - [:name, :title].each do |param| - val = resource.send(param) - assert(subscope.tags.include?(val), - "Scope was not tagged with %s '%s'" % [param, val]) - end - end - end - - # Testing the root cause of #615. We should be using the fqname for the type, instead - # of just the short name. - def oldtest_fully_qualified_types - parser = mkparser - klass = parser.newclass("one::two") - - assert_equal("one::two", klass.classname, "Class did not get fully qualified class name") - end -end diff --git a/spec/unit/parser/ast/hostclass.rb b/spec/unit/parser/ast/hostclass.rb deleted file mode 100755 index 10aa62176..000000000 --- a/spec/unit/parser/ast/hostclass.rb +++ /dev/null @@ -1,148 +0,0 @@ -#!/usr/bin/env ruby - -require File.dirname(__FILE__) + '/../../../spec_helper' - -describe Puppet::Parser::AST::HostClass do - before :each do - @node = Puppet::Node.new "testnode" - @parser = Puppet::Parser::Parser.new :environment => "development" - @scope_resource = stub 'scope_resource', :builtin? => true - @compiler = Puppet::Parser::Compiler.new(@node, @parser) - - @scope = @compiler.topscope - end - - describe Puppet::Parser::AST::HostClass, "when evaluating" do - - before do - @top = @parser.newclass "top" - @middle = @parser.newclass "middle", :parent => "top" - end - - it "should create a resource that references itself" do - @top.evaluate(@scope) - - @compiler.catalog.resource(:class, "top").should be_instance_of(Puppet::Parser::Resource) - end - - it "should evaluate the parent class if one exists" do - @middle.evaluate(@scope) - - @compiler.catalog.resource(:class, "top").should be_instance_of(Puppet::Parser::Resource) - end - - it "should fail to evaluate if a parent class is defined but cannot be found" do - othertop = @parser.newclass "something", :parent => "yay" - lambda { othertop.evaluate(@scope) }.should raise_error(Puppet::ParseError) - end - - it "should not create a new resource if one already exists" do - @compiler.catalog.expects(:resource).with(:class, "top").returns("something") - @compiler.catalog.expects(:add_resource).never - @top.evaluate(@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.evaluate(@scope).should == "something" - end - - it "should not create a new parent resource if one already exists and it has a parent class" do - @top.evaluate(@scope) - - top_resource = @compiler.catalog.resource(:class, "top") - - @middle.evaluate(@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.evaluate(@scope) - - @compiler.catalog.should be_tagged("middle") - end - - it "should tag the catalog with the parent class tags when it is evaluated" do - @middle.evaluate(@scope) - - @compiler.catalog.should be_tagged("top") - end - end - - describe Puppet::Parser::AST::HostClass, "when evaluating code" do - - before do - @top_resource = stub "top_resource" - @top = @parser.newclass "top", :code => @top_resource - - @middle_resource = stub "middle_resource" - @middle = @parser.newclass "top::middle", :parent => "top", :code => @middle_resource - end - - it "should set its namespace to its fully qualified name" do - @middle.namespace.should == "top::middle" - end - - it "should evaluate the code referred to by the class" do - @top_resource.expects(:safeevaluate) - - resource = @top.evaluate(@scope) - - @top.evaluate_code(resource) - end - - it "should evaluate the parent class's code if it has a parent" do - @top_resource.expects(:safeevaluate) - @middle_resource.expects(:safeevaluate) - - resource = @middle.evaluate(@scope) - - @middle.evaluate_code(resource) - end - - it "should not evaluate the parent class's code if the parent has already been evaluated" do - @top_resource.stubs(:safeevaluate) - resource = @top.evaluate(@scope) - @top.evaluate_code(resource) - - @top_resource.expects(:safeevaluate).never - @middle_resource.stubs(:safeevaluate) - resource = @middle.evaluate(@scope) - @middle.evaluate_code(resource) - end - - it "should use the parent class's scope as its parent scope" do - @top_resource.stubs(:safeevaluate) - @middle_resource.stubs(:safeevaluate) - resource = @middle.evaluate(@scope) - @middle.evaluate_code(resource) - - @compiler.class_scope(@middle).parent.should equal(@compiler.class_scope(@top)) - end - - it "should add the class's name and title to its own scope" do - @top_resource.stubs(:safeevaluate) - @middle_resource.stubs(:safeevaluate) - resource = @middle.evaluate(@scope) - scope = stub_everything 'scope', :compiler => @compiler - @middle.stubs(:subscope).returns(scope) - - scope.expects(:setvar).with("title","top::middle") - scope.expects(:setvar).with("name","top::middle") - - @middle.evaluate_code(resource) - end - - it "should add the parent class's namespace to its namespace search path" do - @top_resource.stubs(:safeevaluate) - @middle_resource.stubs(:safeevaluate) - resource = @middle.evaluate(@scope) - @middle.evaluate_code(resource) - - @compiler.class_scope(@middle).namespaces.should be_include(@top.namespace) - end - end -end diff --git a/spec/unit/parser/ast/leaf.rb b/spec/unit/parser/ast/leaf.rb index fecfba386..ee1ee04a7 100755 --- a/spec/unit/parser/ast/leaf.rb +++ b/spec/unit/parser/ast/leaf.rb @@ -195,40 +195,6 @@ describe Puppet::Parser::AST::HostName do @host.evaluate(@scope).should == @value end - it "should implement to_classname" do - @host.should respond_to(:to_classname) - end - - it "should return the downcased nodename as classname" do - host = Puppet::Parser::AST::HostName.new( :value => "KLASSNAME" ) - host.to_classname.should == "klassname" - end - - it "should preserve '_' in to_classname with a string nodename" do - host = Puppet::Parser::AST::HostName.new( :value => "node_with_underscore") - host.to_classname.should == "node_with_underscore" - end - - it "should preserve '_' in to_classname with a regex nodename" do - host = Puppet::Parser::AST::HostName.new( :value => Puppet::Parser::AST::Regex.new(:value => "/\dnode_with_underscore\.+/") ) - host.to_classname.should == "dnode_with_underscore." - end - - it "should return a string usable as classname when calling to_classname" do - host = Puppet::Parser::AST::HostName.new( :value => Puppet::Parser::AST::Regex.new(:value => "/^this-is not@a classname$/") ) - host.to_classname.should == "this-isnotaclassname" - end - - it "should return a string usable as a tag when calling to_classname" do - host = Puppet::Parser::AST::HostName.new( :value => Puppet::Parser::AST::Regex.new(:value => "/.+.reductivelabs\.com/") ) - host.to_classname.should == "reductivelabs.com" - end - - it "should delegate 'match' to the underlying value if it is an HostName" do - @value.expects(:match).with("value") - @host.match("value") - end - it "should delegate eql? to the underlying value if it is an HostName" do @value.expects(:eql?).with("value") @host.eql?("value") @@ -244,42 +210,4 @@ describe Puppet::Parser::AST::HostName do @value.expects(:hash) @host.hash end - - it "should return true when regex? is called and value is a Regex" do - @value.expects(:is_a?).with(Puppet::Parser::AST::Regex).returns(true) - @host.regex?.should be_true - end - - it "should return the results of comparing the regexes if asked whether a regex matches another regex" do - hosts = [1,2].collect do |num| - vreg = /vreg#{num}/ - value = Puppet::Parser::AST::Regex.new(:value => vreg) - Puppet::Parser::AST::HostName.new(:value => value) - end - - hosts[0].match(hosts[1]).should be_false - hosts[0].match(hosts[0]).should be_true - end - - it "should return false when comparing a non-regex to a regex" do - vreg = /vreg/ - value = Puppet::Parser::AST::Regex.new(:value => vreg) - regex = Puppet::Parser::AST::HostName.new(:value => value) - - value = Puppet::Parser::AST::Regex.new(:value => "foo") - normal = Puppet::Parser::AST::HostName.new(:value => value) - - normal.match(regex).should be_false - end - - it "should true when a provided string matches a regex" do - vreg = /r/ - value = Puppet::Parser::AST::Regex.new(:value => vreg) - regex = Puppet::Parser::AST::HostName.new(:value => value) - - value = Puppet::Parser::AST::Leaf.new(:value => "bar") - normal = Puppet::Parser::AST::HostName.new(:value => value) - - regex.match(normal).should be_true - end end diff --git a/spec/unit/parser/ast/node.rb b/spec/unit/parser/ast/node.rb deleted file mode 100755 index 5a4a5efe4..000000000 --- a/spec/unit/parser/ast/node.rb +++ /dev/null @@ -1,145 +0,0 @@ -#!/usr/bin/env ruby - -require File.dirname(__FILE__) + '/../../../spec_helper' - -describe Puppet::Parser::AST::Node do - before :each do - @node = Puppet::Node.new "testnode" - @parser = Puppet::Parser::Parser.new :environment => "development" - @scope_resource = stub 'scope_resource', :builtin? => true - @compiler = Puppet::Parser::Compiler.new(@node, @parser) - - @scope = @compiler.topscope - end - - describe "when calling get_classname" do - it "should return current node name if name is a Regex" do - name = stub 'name', :regex? => true - node = @parser.newnode("node").shift - node.stubs(:name).returns(name) - - @scope.expects(:host).returns("testnode") - - node.get_classname(@scope).should == "testnode" - end - - it "should return the current node classname if name is not a Regex" do - name = stub 'name', :regex? => false - node = @parser.newnode("node").shift - node.stubs(:name).returns(name) - - node.get_classname(@scope).should == "node" - end - end - - describe Puppet::Parser::AST::Node, "when evaluating" do - - before do - @top = @parser.newnode("top").shift - @middle = @parser.newnode("middle", :parent => "top").shift - end - - it "should create a resource that references itself" do - @top.evaluate(@scope) - - @compiler.catalog.resource(:node, "top").should be_an_instance_of(Puppet::Parser::Resource) - end - - it "should evaluate the parent class if one exists" do - @middle.evaluate(@scope) - - @compiler.catalog.resource(:node, "top").should be_an_instance_of(Puppet::Parser::Resource) - end - - it "should fail to evaluate if a parent class is defined but cannot be found" do - othertop = @parser.newnode("something", :parent => "yay").shift - lambda { othertop.evaluate(@scope) }.should raise_error(Puppet::ParseError) - end - - it "should not create a new resource if one already exists" do - @compiler.catalog.expects(:resource).with(:node, "top").returns("something") - @compiler.catalog.expects(:add_resource).never - @top.evaluate(@scope) - end - - it "should not create a new parent resource if one already exists and it has a parent class" do - @top.evaluate(@scope) - - top_resource = @compiler.catalog.resource(:node, "top") - - @middle.evaluate(@scope) - - @compiler.catalog.resource(:node, "top").should equal(top_resource) - end - - # #795 - tag before evaluation. - it "should tag the catalog with the resource tags when it is evaluated" do - @middle.evaluate(@scope) - - @compiler.catalog.should be_tagged("middle") - end - - it "should tag the catalog with the parent class tags when it is evaluated" do - @middle.evaluate(@scope) - - @compiler.catalog.should be_tagged("top") - end - end - - describe Puppet::Parser::AST::Node, "when evaluating code" do - - before do - @top_resource = stub "top_resource" - @top = @parser.newnode("top", :code => @top_resource).shift - - @middle_resource = stub "middle_resource" - @middle = @parser.newnode("middle", :parent => "top", :code => @middle_resource).shift - end - - it "should evaluate the code referred to by the class" do - @top_resource.expects(:safeevaluate) - - resource = @top.evaluate(@scope) - - @top.evaluate_code(resource) - end - - it "should evaluate the parent class's code if it has a parent" do - @top_resource.expects(:safeevaluate) - @middle_resource.expects(:safeevaluate) - - resource = @middle.evaluate(@scope) - - @middle.evaluate_code(resource) - end - - it "should not evaluate the parent class's code if the parent has already been evaluated" do - @top_resource.stubs(:safeevaluate) - resource = @top.evaluate(@scope) - @top.evaluate_code(resource) - - @top_resource.expects(:safeevaluate).never - @middle_resource.stubs(:safeevaluate) - resource = @middle.evaluate(@scope) - @middle.evaluate_code(resource) - end - - it "should use the parent class's scope as its parent scope" do - @top_resource.stubs(:safeevaluate) - @middle_resource.stubs(:safeevaluate) - resource = @middle.evaluate(@scope) - @middle.evaluate_code(resource) - - @compiler.class_scope(@middle).parent.should equal(@compiler.class_scope(@top)) - end - - it "should add the parent class's namespace to its namespace search path" do - @top_resource.stubs(:safeevaluate) - @middle_resource.stubs(:safeevaluate) - resource = @middle.evaluate(@scope) - @middle.evaluate_code(resource) - - @compiler.class_scope(@middle).namespaces.should be_include(@top.namespace) - end - end -end diff --git a/spec/unit/parser/ast/resource_reference.rb b/spec/unit/parser/ast/resource_reference.rb index 24865e846..10d9678c3 100755 --- a/spec/unit/parser/ast/resource_reference.rb +++ b/spec/unit/parser/ast/resource_reference.rb @@ -21,7 +21,7 @@ describe Puppet::Parser::AST::ResourceReference do %{ "one::two" "one-two"}.each do |type| it "should evaluate correctly reference to define" do - klass = stub 'klass', :title => "three", :classname => type + klass = stub 'klass', :title => "three", :name => type @scope.stubs(:find_definition).returns(klass) newref("three", type).evaluate(@scope).to_ref.should == Puppet::Parser::Resource::Reference.new( :type => type, :title => "three" ).to_ref @@ -29,13 +29,13 @@ describe Puppet::Parser::AST::ResourceReference do end it "should be able to call qualified_class" do - klass = stub 'klass', :title => "three", :classname => "one" + klass = stub 'klass', :title => "three", :name => "one" @scope.expects(:find_hostclass).with("one").returns(klass) newref("three","class").qualified_class(@scope,"one").should == "one" end it "should be able to find qualified classes when evaluating" do - klass = stub 'klass', :title => "one", :classname => "one" + klass = stub 'klass', :title => "one", :name => "one" @scope.stubs(:find_hostclass).returns(klass) evaled = newref("one", "class").evaluate(@scope) diff --git a/spec/unit/parser/compiler.rb b/spec/unit/parser/compiler.rb index adc9732dc..8a41242d6 100755 --- a/spec/unit/parser/compiler.rb +++ b/spec/unit/parser/compiler.rb @@ -51,7 +51,7 @@ describe Puppet::Parser::Compiler do it "should be able to retrieve class scopes by object" do klass = mock 'ast_class' - klass.expects(:classname).returns("myname") + klass.expects(:name).returns("myname") @compiler.class_set "myname", "myscope" @compiler.class_scope(klass).should == "myscope" end @@ -153,8 +153,8 @@ describe Puppet::Parser::Compiler do it "should evaluate any existing classes named in the node" do classes = %w{one two three four} main = stub 'main' - one = stub 'one', :classname => "one" - three = stub 'three', :classname => "three" + one = stub 'one', :name => "one" + three = stub 'three', :name => "three" @node.stubs(:name).returns("whatever") @node.stubs(:classes).returns(classes) @@ -396,7 +396,7 @@ describe Puppet::Parser::Compiler do describe "when evaluating found classes" do before do - @class = stub 'class', :classname => "my::class" + @class = stub 'class', :name => "my::class" @scope.stubs(:find_hostclass).with("myclass").returns(@class) @resource = stub 'resource', :ref => "Class[myclass]", :type => "file" @@ -405,7 +405,7 @@ describe Puppet::Parser::Compiler do it "should evaluate each class" do @compiler.catalog.stubs(:tag) - @class.expects(:evaluate).with(@scope) + @class.expects(:mk_plain_resource).with(@scope) @compiler.evaluate_classes(%w{myclass}, @scope) end @@ -415,7 +415,7 @@ describe Puppet::Parser::Compiler do @resource.expects(:evaluate).never - @class.expects(:evaluate).returns(@resource) + @class.expects(:mk_plain_resource).returns(@resource) @compiler.evaluate_classes(%w{myclass}, @scope) end @@ -424,7 +424,7 @@ describe Puppet::Parser::Compiler do @compiler.catalog.stubs(:tag) @resource.expects(:evaluate) - @class.expects(:evaluate).returns(@resource) + @class.expects(:mk_plain_resource).returns(@resource) @compiler.evaluate_classes(%w{myclass}, @scope, false) end @@ -459,7 +459,7 @@ describe Puppet::Parser::Compiler do @scope.stubs(:find_hostclass).with("notfound").returns(nil) Puppet::Parser::Resource.stubs(:new).returns(@resource) - @class.stubs :evaluate + @class.stubs :mk_plain_resource @compiler.evaluate_classes(%w{myclass notfound}, @scope).should == %w{myclass} end end @@ -495,31 +495,31 @@ describe Puppet::Parser::Compiler do end it "should evaluate the first node class matching the node name" do - node_class = stub 'node', :classname => "c", :evaluate_code => nil + node_class = stub 'node', :name => "c", :evaluate_code => nil @compiler.parser.stubs(:node).with("c").returns(node_class) node_resource = stub 'node resource', :ref => "Node[c]", :evaluate => nil, :type => "node" - node_class.expects(:evaluate).returns(node_resource) + node_class.expects(:mk_plain_resource).returns(node_resource) @compiler.compile end it "should match the default node if no matching node can be found" do - node_class = stub 'node', :classname => "default", :evaluate_code => nil + node_class = stub 'node', :name => "default", :evaluate_code => nil @compiler.parser.stubs(:node).with("default").returns(node_class) node_resource = stub 'node resource', :ref => "Node[default]", :evaluate => nil, :type => "node" - node_class.expects(:evaluate).returns(node_resource) + node_class.expects(:mk_plain_resource).returns(node_resource) @compiler.compile end it "should evaluate the node resource immediately rather than using lazy evaluation" do - node_class = stub 'node', :classname => "c" + node_class = stub 'node', :name => "c" @compiler.parser.stubs(:node).with("c").returns(node_class) node_resource = stub 'node resource', :ref => "Node[c]", :type => "node" - node_class.expects(:evaluate).returns(node_resource) + node_class.expects(:mk_plain_resource).returns(node_resource) node_resource.expects(:evaluate) @@ -528,13 +528,13 @@ describe Puppet::Parser::Compiler do 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', :classname => "c", :evaluate => node_resource + node_class = stub 'node', :name => "c", :mk_plain_resource => node_resource @compiler.parser.stubs(:node).with("c").returns(node_class) # The #evaluate method normally does this. scope = stub 'scope', :source => "mysource" - @compiler.class_set(node_class.classname, scope) + @compiler.class_set(node_class.name, scope) node_resource.stubs(:evaluate) @compiler.compile diff --git a/spec/unit/parser/loaded_code.rb b/spec/unit/parser/loaded_code.rb index 75f2bc7e2..9b72dab0d 100644 --- a/spec/unit/parser/loaded_code.rb +++ b/spec/unit/parser/loaded_code.rb @@ -3,23 +3,92 @@ require File.dirname(__FILE__) + '/../../spec_helper' require 'puppet/parser/loaded_code' +require 'puppet/parser/resource_type' describe Puppet::Parser::LoadedCode do + before do + @instance = Puppet::Parser::ResourceType.new(:hostclass, "foo") + @code = Puppet::Parser::LoadedCode.new + end + + it "should be able to add a resource type" do + Puppet::Parser::LoadedCode.new.should respond_to(:add) + end + + it "should consider '<<' to be an alias to 'add' but should return self" do + loader = Puppet::Parser::LoadedCode.new + loader.expects(:add).with "foo" + loader.expects(:add).with "bar" + loader << "foo" << "bar" + end + + it "should set itself as the code collection for added resource types" do + loader = Puppet::Parser::LoadedCode.new + + node = Puppet::Parser::ResourceType.new(:node, "foo") + + @code.add(node) + @code.node("foo").should equal(node) + + node.code_collection.should equal(@code) + end + + it "should store node resource types as nodes" do + node = Puppet::Parser::ResourceType.new(:node, "foo") + + @code.add(node) + @code.node("foo").should equal(node) + end + + it "should store hostclasses as hostclasses" do + klass = Puppet::Parser::ResourceType.new(:hostclass, "foo") + + @code.add(klass) + @code.hostclass("foo").should equal(klass) + end + + it "should store definitions as definitions" do + define = Puppet::Parser::ResourceType.new(:definition, "foo") + + @code.add(define) + @code.definition("foo").should equal(define) + end + %w{hostclass node definition}.each do |data| it "should have a method for adding a #{data}" do Puppet::Parser::LoadedCode.new.should respond_to("add_" + data) end + it "should use the name of the instance to add it" do + loader = Puppet::Parser::LoadedCode.new + loader.send("add_#{data}", @instance) + loader.send(data, @instance.name).should equal(@instance) + end + + it "should fail to add a #{data} when one already exists" do + loader = Puppet::Parser::LoadedCode.new + loader.add @instance + lambda { loader.add(@instance) }.should raise_error(Puppet::ParseError) + end + + it "should return the added #{data}" do + loader = Puppet::Parser::LoadedCode.new + + loader.add(@instance).should equal(@instance) + end + it "should be able to retrieve #{data} by name" do loader = Puppet::Parser::LoadedCode.new - loader.send("add_" + data, "foo", "bar") - loader.send(data, "foo").should == "bar" + instance = Puppet::Parser::ResourceType.new(data, "bar") + loader.add instance + loader.send(data, "bar").should equal(instance) end it "should retrieve #{data} insensitive to case" do loader = Puppet::Parser::LoadedCode.new - loader.send("add_" + data, "Foo", "bar") - loader.send(data, "fOo").should == "bar" + instance = Puppet::Parser::ResourceType.new(data, "Bar") + loader.add instance + loader.send(data, "bAr").should equal(instance) end it "should return nil when asked for a #{data} that has not been added" do @@ -29,16 +98,18 @@ describe Puppet::Parser::LoadedCode do it "should be able to retrieve all #{data}s" do plurals = { "hostclass" => "hostclasses", "node" => "nodes", "definition" => "definitions" } loader = Puppet::Parser::LoadedCode.new - loader.send("add_" + data , "foo", "bar") - loader.send(plurals[data]).should == { "foo" => "bar" } + instance = Puppet::Parser::ResourceType.new(data, "foo") + loader.add instance + loader.send(plurals[data]).should == { "foo" => instance } end end describe "when finding a qualified instance" do it "should return any found instance if the instance name is fully qualified" do loader = Puppet::Parser::LoadedCode.new - loader.add_hostclass "foo::bar", "yay" - loader.find("namespace", "::foo::bar", :hostclass).should == "yay" + instance = Puppet::Parser::ResourceType.new(:hostclass, "foo::bar") + loader.add instance + loader.find("namespace", "::foo::bar", :hostclass).should equal(instance) end it "should return nil if the instance name is fully qualified and no such instance exists" do @@ -48,37 +119,43 @@ describe Puppet::Parser::LoadedCode do it "should return the partially qualified object if it exists in the provided namespace" do loader = Puppet::Parser::LoadedCode.new - loader.add_hostclass "foo::bar::baz", "yay" - loader.find("foo", "bar::baz", :hostclass).should == "yay" + instance = Puppet::Parser::ResourceType.new(:hostclass, "foo::bar::baz") + loader.add instance + loader.find("foo", "bar::baz", :hostclass).should equal(instance) end it "should return the unqualified object if it exists in the provided namespace" do loader = Puppet::Parser::LoadedCode.new - loader.add_hostclass "foo::bar", "yay" - loader.find("foo", "bar", :hostclass).should == "yay" + instance = Puppet::Parser::ResourceType.new(:hostclass, "foo::bar") + loader.add instance + loader.find("foo", "bar", :hostclass).should equal(instance) end it "should return the unqualified object if it exists in the parent namespace" do loader = Puppet::Parser::LoadedCode.new - loader.add_hostclass "foo::bar", "yay" - loader.find("foo::bar::baz", "bar", :hostclass).should == "yay" + instance = Puppet::Parser::ResourceType.new(:hostclass, "foo::bar") + loader.add instance + loader.find("foo::bar::baz", "bar", :hostclass).should equal(instance) end it "should should return the partially qualified object if it exists in the parent namespace" do loader = Puppet::Parser::LoadedCode.new - loader.add_hostclass "foo::bar::baz", "yay" - loader.find("foo::bar", "bar::baz", :hostclass).should == "yay" + instance = Puppet::Parser::ResourceType.new(:hostclass, "foo::bar::baz") + loader.add instance + loader.find("foo::bar", "bar::baz", :hostclass).should equal(instance) end it "should return the qualified object if it exists in the root namespace" do loader = Puppet::Parser::LoadedCode.new - loader.add_hostclass "foo::bar::baz", "yay" - loader.find("foo::bar", "foo::bar::baz", :hostclass).should == "yay" + instance = Puppet::Parser::ResourceType.new(:hostclass, "foo::bar::baz") + loader.add instance + loader.find("foo::bar", "foo::bar::baz", :hostclass).should equal(instance) end it "should return nil if the object cannot be found" do loader = Puppet::Parser::LoadedCode.new - loader.add_hostclass "foo::bar::baz", "yay" + instance = Puppet::Parser::ResourceType.new(:hostclass, "foo::bar::baz") + loader.add instance loader.find("foo::bar", "eh", :hostclass).should be_nil end end @@ -103,7 +180,7 @@ describe Puppet::Parser::LoadedCode do it "should indicate whether any nodes are defined" do loader = Puppet::Parser::LoadedCode.new - loader.add_node("foo", "bar") + loader.add_node(Puppet::Parser::ResourceType.new(:node, "foo")) loader.should be_nodes end @@ -111,105 +188,32 @@ describe Puppet::Parser::LoadedCode do Puppet::Parser::LoadedCode.new.should_not be_nodes end - describe "when adding nodes" do - it "should create an HostName if nodename is a string" do - Puppet::Parser::AST::HostName.expects(:new).with(:value => "foo") - loader = Puppet::Parser::LoadedCode.new - loader.add_node("foo", "bar") - end - - it "should not create an HostName if nodename is an HostName" do - name = Puppet::Parser::AST::HostName.new(:value => "foo") - - Puppet::Parser::AST::HostName.expects(:new).with(:value => "foo").never - - loader = Puppet::Parser::LoadedCode.new - loader.add_node(name, "bar") - end - end - describe "when finding nodes" do before :each do @loader = Puppet::Parser::LoadedCode.new - - @nodename1 = stub 'nodename1', :is_a? => true - @node1 = stub 'node1' - @nodename2 = stub 'nodename2', :is_a? => true - @node2 = stub 'node2' - end - it "should create an HostName if nodename is a string" do - Puppet::Parser::AST::HostName.expects(:new).with(:value => "foo") - - @loader.node("foo") - end - - it "should not create an HostName if nodename is an HostName" do - name = Puppet::Parser::AST::HostName.new(:value => "foo") - - Puppet::Parser::AST::HostName.expects(:new).with(:value => "foo").never - - @loader.node(name) - end - - it "should be able to find node by HostName" do - namein = Puppet::Parser::AST::HostName.new(:value => "foo") - nameout = Puppet::Parser::AST::HostName.new(:value => "foo") - - @loader.add_node(namein, "bar") - @loader.node(nameout).should == "bar" end - it "should be able to find node by HostName strict equality" do - namein = Puppet::Parser::AST::HostName.new(:value => "foo") - nameout = Puppet::Parser::AST::HostName.new(:value => "foo") + it "should return any node whose name exactly matches the provided node name" do + node = Puppet::Parser::ResourceType.new(:node, "foo") + @loader << node - @loader.add_node(namein, "bar") - @loader.node_exists?(nameout).should == "bar" + @loader.node("foo").should equal(node) end - it "should not use node name matching when finding with strict node HostName" do - name1 = Puppet::Parser::AST::HostName.new(:value => "foo") - name2 = Puppet::Parser::AST::HostName.new(:value => Puppet::Parser::AST::Regex.new(:value => /foo/)) + it "should return the first regex node whose regex matches the provided node name" do + node1 = Puppet::Parser::ResourceType.new(:node, /\w/) + node2 = Puppet::Parser::ResourceType.new(:node, /\d/) + @loader << node1 << node2 - @loader.add_node(name1, "bar") - @loader.add_node(name2, "baz") - @loader.node_exists?(name1).should == "bar" + @loader.node("foo10").should equal(node1) end - it "should return the first matching regex nodename" do - @nodename1.stubs(:regex?).returns(true) - @nodename1.stubs(:match).returns(true) - @nodename2.stubs(:regex?).returns(true) - @nodename2.stubs(:match).returns(true) - - @loader.add_node(@nodename1, @node1) - @loader.add_node(@nodename2, @node2) - - @loader.node("test").should == @node1 - end - - it "should not scan non-regex node" do - @nodename1.stubs(:regex?).returns(true) - @nodename1.stubs(:match).returns(false) - @nodename2.stubs(:regex?).returns(false) - @nodename2.expects(:match).never - - @loader.add_node(@nodename1,@node1) - @loader.add_node(@nodename2,@node2) - - @loader.node("test") - end - - it "should prefer non-regex nodes to regex nodes" do - @nodename1.stubs(:regex?).returns(false) - @nodename1.expects(:match).never - @nodename2.stubs(:regex?).returns(true) - @nodename2.expects(:match).never - - @loader.add_node(@nodename1,@node1) - @loader.add_node(@nodename2,@node2) + it "should preferentially return a node whose name is string-equal over returning a node whose regex matches a provided name" do + node1 = Puppet::Parser::ResourceType.new(:node, /\w/) + node2 = Puppet::Parser::ResourceType.new(:node, "foo") + @loader << node1 << node2 - @loader.node(@nodename1) + @loader.node("foo").should equal(node2) end end end diff --git a/spec/unit/parser/parser.rb b/spec/unit/parser/parser.rb index 78caf18b0..a04444674 100755 --- a/spec/unit/parser/parser.rb +++ b/spec/unit/parser/parser.rb @@ -174,56 +174,62 @@ describe Puppet::Parser do end end - describe "when instantiating class of same name" do - - before :each do - @one = stub 'one', :is_a? => true - @one.stubs(:is_a?).with(ast::ASTArray).returns(false) - @one.stubs(:is_a?).with(ast).returns(true) - - @two = stub 'two' - @two.stubs(:is_a?).with(ast::ASTArray).returns(false) - @two.stubs(:is_a?).with(ast).returns(true) + describe "when providing AST context" do + before do + @lexer = stub 'lexer', :line => 50, :file => "/foo/bar", :getcomment => "whev" + @parser.stubs(:lexer).returns @lexer end - it "should return the first class" do - - klass1 = @parser.newclass("one", { :code => @one }) + it "should include the lexer's line" do + @parser.ast_context[:line].should == 50 + end - @parser.newclass("one", { :code => @two }).should == klass1 + it "should include the lexer's file" do + @parser.ast_context[:file].should == "/foo/bar" end - it "should concatenate code" do - klass1 = @parser.newclass("one", { :code => @one }) + it "should include the docs if directed to do so" do + @parser.ast_context(true)[:doc].should == "whev" + end - @parser.newclass("one", { :code => @two }) + it "should not include the docs when told not to" do + @parser.ast_context(false)[:doc].should be_nil + end - klass1.code.children.should == [@one,@two] + it "should not include the docs by default" do + @parser.ast_context()[:doc].should be_nil end end - describe "when parsing comments before statement" do - it "should associate the documentation to the statement AST node" do - ast = @parser.parse(""" - # comment - class test {} - """) + describe "when building ast nodes" do + before do + @lexer = stub 'lexer', :line => 50, :file => "/foo/bar", :getcomment => "whev" + @parser.stubs(:lexer).returns @lexer + @class = stub 'class', :use_docs => false + end - ast.hostclass("test").doc.should == "comment\n" + it "should return a new instance of the provided class created with the provided options" do + @class.expects(:new).with { |opts| opts[:foo] == "bar" } + @parser.ast(@class, :foo => "bar") end - end - describe "when building ast nodes" do - it "should get lexer comments if ast node declares use_docs" do - lexer = stub 'lexer' - ast = mock 'ast', :nil? => false, :use_docs => true, :doc => "" - @parser.stubs(:lexer).returns(lexer) + it "should merge the ast context into the provided options" do + @class.expects(:new).with { |opts| opts[:file] == "/foo" } + @parser.expects(:ast_context).returns :file => "/foo" + @parser.ast(@class, :foo => "bar") + end - Puppet::Parser::AST::Definition.expects(:new).returns(ast) - lexer.expects(:getcomment).returns("comment") - ast.expects(:doc=).with("comment") + it "should prefer provided options over AST context" do + @class.expects(:new).with { |opts| opts[:file] == "/bar" } + @parser.expects(:ast_context).returns :file => "/foo" + @parser.ast(@class, :file => "/bar") + end - @parser.ast(Puppet::Parser::AST::Definition) + it "should include docs when the AST class uses them" do + @class.expects(:use_docs).returns true + @class.stubs(:new) + @parser.expects(:ast_context).with(true).returns({}) + @parser.ast(@class, :file => "/bar") end end @@ -233,59 +239,15 @@ describe Puppet::Parser do @lexer.stubs(:getcomment) @parser.stubs(:lexer).returns(@lexer) @node = stub_everything 'node' - @parser.stubs(:ast).returns(@node) + @parser.stubs(:ast_context).returns({}) @parser.stubs(:node).returns(nil) - @nodename = stub 'nodename', :is_a? => false, :to_classname => "node" + @nodename = stub 'nodename', :is_a? => false, :value => "foo" @nodename.stubs(:is_a?).with(Puppet::Parser::AST::HostName).returns(true) end - it "should get the lexer stacked comments" do - @lexer.expects(:getcomment) - - @parser.newnode(@nodename) - end - - it "should create an HostName if needed" do - Puppet::Parser::AST::HostName.expects(:new).with(:value => "node").returns(@nodename) - - @parser.newnode("node") - end - - it "should raise an error if the node already exists" do - @loaded_code.stubs(:node_exists?).with(@nodename).returns(@node) - - lambda { @parser.newnode(@nodename) }.should raise_error - end - - it "should store the created node in the loaded code" do - @loaded_code.expects(:add_node).with(@nodename, @node) - - @parser.newnode(@nodename) - end - - it "should create the node with code if provided" do - @parser.stubs(:ast).with { |*args| args[1][:code] == :code }.returns(@node) - - @parser.newnode(@nodename, :code => :code) - end - - it "should create the node with a parentclass if provided" do - @parser.stubs(:ast).with { |*args| args[1][:parent] == :parent }.returns(@node) - - @parser.newnode(@nodename, :parent => :parent) - end - - it "should set the node classname from the HostName" do - @nodename.stubs(:to_classname).returns(:classname) - - @node.expects(:classname=).with(:classname) - - @parser.newnode(@nodename) - end - it "should return an array of nodes" do - @parser.newnode(@nodename).should == [@node] + @parser.newnode(@nodename).should be_instance_of(Array) end end diff --git a/spec/unit/parser/resource/reference.rb b/spec/unit/parser/resource/reference.rb index 6284e67cc..e082136c5 100755 --- a/spec/unit/parser/resource/reference.rb +++ b/spec/unit/parser/resource/reference.rb @@ -84,7 +84,7 @@ describe Puppet::Parser::Resource::Reference, " when modeling defined types" do scope = @compiler.topscope.class.new(:parent => @compiler.topscope, :namespace => "other", :parser => @parser) ref = @type.new(:type => "class", :title => "top", :scope => scope) - ref.definedtype.classname.should equal(top.classname) + ref.definedtype.name.should equal(top.name) end it "should only look for fully qualified definitions" do @@ -94,6 +94,6 @@ describe Puppet::Parser::Resource::Reference, " when modeling defined types" do scope = @compiler.topscope.class.new(:parent => @compiler.topscope, :namespace => "other", :parser => @parser) ref = @type.new(:type => "top", :title => "foo", :scope => scope) - ref.definedtype.classname.should equal(top.classname) + ref.definedtype.name.should equal(top.name) end end diff --git a/spec/unit/parser/resource_type.rb b/spec/unit/parser/resource_type.rb new file mode 100755 index 000000000..ddf8ce951 --- /dev/null +++ b/spec/unit/parser/resource_type.rb @@ -0,0 +1,522 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +require 'puppet/parser/resource_type' + +describe Puppet::Parser::ResourceType do + it "should have a 'name' attribute" do + Puppet::Parser::ResourceType.new(:hostclass, "foo").name.should == "foo" + end + + [:code, :doc, :line, :file, :code_collection].each do |attr| + it "should have a '#{attr}' attribute" do + type = Puppet::Parser::ResourceType.new(:hostclass, "foo") + type.send(attr.to_s + "=", "yay") + type.send(attr).should == "yay" + end + end + + describe "when a node" do + it "should allow a regex as its name" do + lambda { Puppet::Parser::ResourceType.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::Parser::ResourceType.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::Parser::ResourceType.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::Parser::ResourceType.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::Parser::ResourceType.new(:node, /w/).should be_name_is_regex + end + + it "should set its namespace to ''" do + Puppet::Parser::ResourceType.new(:node, /w/).namespace.should == "" + end + + it "should return the regex converted to a string when asked for its name" do + Puppet::Parser::ResourceType.new(:node, /ww/).name.should == "ww" + end + + it "should downcase the regex when returning the name as a string" do + Puppet::Parser::ResourceType.new(:node, /W/).name.should == "w" + end + + it "should remove non-alpha characters when returning the name as a string" do + Puppet::Parser::ResourceType.new(:node, /w*w/).name.should_not include("*") + end + + it "should remove leading dots when returning the name as a string" do + Puppet::Parser::ResourceType.new(:node, /.ww/).name.should_not =~ /^\./ + end + + it "should have a method for matching its regex name against a provided name" do + Puppet::Parser::ResourceType.new(:node, /.ww/).should respond_to(:match) + end + + it "should return true when its regex matches the provided name" do + Puppet::Parser::ResourceType.new(:node, /\w/).match("foo").should be_true + end + + it "should return false when its regex does not match the provided name" do + (!!Puppet::Parser::ResourceType.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::Parser::ResourceType.new(:node, "foo").match("foo").should be_true + end + + it "should return false when its name is matched against an unequal string" do + Puppet::Parser::ResourceType.new(:node, "foo").match("bar").should be_false + end + + it "should match names insensitive to case" do + Puppet::Parser::ResourceType.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::Parser::ResourceType.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::Parser::ResourceType.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::Parser::ResourceType.new(:node, name).name.should == /regex/.to_s + end + end + + describe "when initializing" do + it "should require a resource super type" do + Puppet::Parser::ResourceType.new(:hostclass, "foo").type.should == :hostclass + end + + it "should fail if provided an invalid resource super type" do + lambda { Puppet::Parser::ResourceType.new(:nope, "foo") }.should raise_error(ArgumentError) + end + + it "should set its name to the downcased, stringified provided name" do + Puppet::Parser::ResourceType.new(:hostclass, "Foo::Bar".intern).name.should == "foo::bar" + end + + it "should set its namespace to the downcased, stringified qualified portion of the name" do + Puppet::Parser::ResourceType.new(:hostclass, "Foo::Bar::Baz".intern).namespace.should == "foo::bar" + end + + %w{code line file doc}.each do |arg| + it "should set #{arg} if provided" do + type = Puppet::Parser::ResourceType.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::Parser::ResourceType.new(:hostclass, "foo", :arguments => {:foo => "bar", :baz => "biz"}) + type.should be_validattr("foo") + type.should be_validattr("baz") + end + + it "should set any provided arguments with they keys as strings" do + type = Puppet::Parser::ResourceType.new(:hostclass, "foo", :arguments => {"foo" => "bar", "baz" => "biz"}) + type.should be_validattr(:foo) + type.should be_validattr(:baz) + end + + it "should function if provided no arguments" do + type = Puppet::Parser::ResourceType.new(:hostclass, "foo") + type.should_not be_validattr(: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::Parser::ResourceType.new(:hostclass, "foo", :arguments => {"foo" => "bar"}).should be_validattr("foo") + end + + it "should return true if it is a metaparam" do + Puppet::Parser::ResourceType.new(:hostclass, "foo").should be_validattr("require") + end + + it "should return true if the parameter is named 'name'" do + Puppet::Parser::ResourceType.new(:hostclass, "foo").should be_validattr("name") + end + + it "should return false if it is not a metaparam and was not provided at initialization" do + Puppet::Parser::ResourceType.new(:hostclass, "foo").should_not be_validattr("yayness") + end + end + + describe "when creating a subscope" do + before do + @scope = stub 'scope', :newscope => nil + @resource = stub 'resource' + @type = Puppet::Parser::ResourceType.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 = stub 'scope', :newscope => nil, :setvar => nil + @resource = stub 'resource', :title => "yay", :name => "yea", :ref => "Foo[bar]" + @type = Puppet::Parser::ResourceType.new(:hostclass, "foo") + end + + it "should set each of the resource's parameters as variables in the scope" do + @type.set_arguments :foo => nil, :boo => nil + @resource.expects(:to_hash).returns(:foo => "bar", :boo => "baz") + + @scope.expects(:setvar).with("foo", "bar") + @scope.expects(:setvar).with("boo", "baz") + + @type.set_resource_parameters(@resource, @scope) + end + + it "should set the variables as strings" do + @type.set_arguments :foo => nil + @resource.expects(:to_hash).returns(:foo => "bar") + @scope.expects(:setvar).with("foo", "bar") + + @type.set_resource_parameters(@resource, @scope) + end + + it "should fail if any of the resource's parameters are not valid attributes" do + @type.set_arguments :foo => nil + @resource.expects(:to_hash).returns(: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") + @resource.expects(:to_hash).returns({}) + + @scope.expects(:setvar).with("foo", "something") + + @type.set_resource_parameters(@resource, @scope) + 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 + @resource.expects(:to_hash).returns({}) + + @resource.expects(:title).returns 'teetle' + @scope.expects(:setvar).with("title", "teetle") + + @type.set_resource_parameters(@resource, @scope) + end + + it "should set the resource's name as a variable if not otherwise provided" do + @resource.expects(:to_hash).returns({}) + + @resource.expects(:name).returns 'nombre' + @scope.expects(:setvar).with("name", "nombre") + + @type.set_resource_parameters(@resource, @scope) + end + end + + describe "when describing and managing parent classes" do + before do + @code = Puppet::Parser::LoadedCode.new + @parent = Puppet::Parser::ResourceType.new(:hostclass, "bar") + @code.add @parent + + @child = Puppet::Parser::ResourceType.new(:hostclass, "foo", :parent => "bar") + @code.add @child + end + + it "should be able to define a parent" do + Puppet::Parser::ResourceType.new(:hostclass, "foo", :parent => "bar") + end + + it "should use the code collection to find the parent resource type" do + @child.parent_type.should equal(@parent) + end + + it "should be able to find parent nodes" do + parent = Puppet::Parser::ResourceType.new(:node, "bar") + @code.add parent + child = Puppet::Parser::ResourceType.new(:node, "foo", :parent => "bar") + @code.add child + + child.parent_type.should equal(parent) + end + + it "should cache a reference to the parent type" do + @code.expects(:hostclass).once.with("bar").returns @parent + @child.parent_type + @child.parent_type + end + + it "should correctly state when it is another type's child" do + @child.should be_child_of(@parent) + end + + it "should be considered the child of a parent's parent" do + @grandchild = Puppet::Parser::ResourceType.new(:hostclass, "baz", :parent => "foo") + @code.add @grandchild + + @grandchild.should be_child_of(@parent) + end + + it "should correctly state when it is not another type's child" do + @notchild = Puppet::Parser::ResourceType.new(:hostclass, "baz") + @code.add @notchild + + @notchild.should_not be_child_of(@parent) + end + end + + describe "when evaluating its code" do + before do + @scope = stub 'scope', :newscope => nil, :setvar => nil + @resource = stub 'resource', :title => "yay", :name => "yea", :ref => "Foo[bar]", :scope => @scope + @type = Puppet::Parser::ResourceType.new(:hostclass, "foo") + @type.stubs(:set_resource_parameters) + end + + it "should set all of its parameters in a subscope" do + subscope = stub 'subscope' + @type.expects(:subscope).with(@scope, @resource).returns subscope + @type.expects(:set_resource_parameters).with(@resource, subscope) + + @type.evaluate_code(@resource) + end + + it "should evaluate the code if any is provided" do + code = stub 'code' + @type.expects(:code).returns code + @type.stubs(:subscope).returns stub("subscope") + code.expects(:safeevaluate).with @type.subscope + + @type.evaluate_code(@resource) + end + + it "should noop if there is no code" do + @type.expects(:code).returns nil + @type.stubs(:subscope).returns stub("subscope") + + @type.evaluate_code(@resource) + end + end + + describe "when creating a resource" do + before do + @catalog = Puppet::Resource::Catalog.new + @node = stub 'node', :name => "foo", :classes => [] + @compiler = Puppet::Parser::Compiler.new(@node, @catalog) + @scope = Puppet::Parser::Scope.new + @scope.stubs(:compiler).returns @compiler + + @top = Puppet::Parser::ResourceType.new :hostclass, "top" + @middle = Puppet::Parser::ResourceType.new :hostclass, "middle", :parent => "top" + + @code = Puppet::Parser::LoadedCode.new + @code.add @top + @code.add @middle + end + + it "should create a resource instance" do + @top.mk_plain_resource(@scope).should be_instance_of(Puppet::Parser::Resource) + end + + it "should set its resource type to 'class' when it is a hostclass" do + Puppet::Parser::ResourceType.new(:hostclass, "top").mk_plain_resource(@scope).type.should == "Class" + end + + it "should set its resource type to 'node' when it is a node" do + Puppet::Parser::ResourceType.new(:node, "top").mk_plain_resource(@scope).type.should == "Node" + end + + it "should fail when it is a definition" do + lambda { Puppet::Parser::ResourceType.new(:definition, "top").mk_plain_resource(@scope) }.should raise_error(ArgumentError) + end + + it "should add the created resource to the scope's catalog" do + @top.mk_plain_resource(@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.mk_plain_resource(@scope) + + @compiler.catalog.resource(:class, "top").should be_instance_of(Puppet::Parser::Resource) + end + + it "should fail to evaluate if a parent class is defined but cannot be found" do + othertop = Puppet::Parser::ResourceType.new :hostclass, "something", :parent => "yay" + @code.add othertop + lambda { othertop.mk_plain_resource(@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.mk_plain_resource(@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.mk_plain_resource(@scope).should == "something" + end + + it "should not create a new parent resource if one already exists and it has a parent class" do + @top.mk_plain_resource(@scope) + + top_resource = @compiler.catalog.resource(:class, "top") + + @middle.mk_plain_resource(@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.mk_plain_resource(@scope) + + @compiler.catalog.should be_tagged("middle") + end + + it "should tag the catalog with the parent class tags when it is evaluated" do + @middle.mk_plain_resource(@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::Parser::ResourceType.new(:node, "bar").merge("foo") }.should raise_error(ArgumentError) + end + + it "should fail unless the source instance is a class" do + dest = Puppet::Parser::ResourceType.new(:hostclass, "bar") + source = Puppet::Parser::ResourceType.new(:node, "foo") + lambda { dest.merge(source) }.should raise_error(ArgumentError) + end + + it "should fail if both classes have different parent classes" do + code = Puppet::Parser::LoadedCode.new + {"a" => "b", "c" => "d"}.each do |parent, child| + code.add Puppet::Parser::ResourceType.new(:hostclass, parent) + code.add Puppet::Parser::ResourceType.new(:hostclass, child, :parent => parent) + end + lambda { code.hostclass("b").merge(code.hostclass("d")) }.should raise_error(ArgumentError) + end + + it "should copy the other class's parent if it has not parent" do + dest = Puppet::Parser::ResourceType.new(:hostclass, "bar") + + parent = Puppet::Parser::ResourceType.new(:hostclass, "parent") + source = Puppet::Parser::ResourceType.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::Parser::ResourceType.new(:hostclass, "bar") + source = Puppet::Parser::ResourceType.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::Parser::ResourceType.new(:hostclass, "bar", :doc => "fooness") + source = Puppet::Parser::ResourceType.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::Parser::ResourceType.new(:hostclass, "bar", :code => code("foo")) + source = Puppet::Parser::ResourceType.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::Parser::ResourceType.new(:hostclass, "bar") + source = Puppet::Parser::ResourceType.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::Parser::ResourceType.new(:hostclass, "bar", :code => dcode) + + scode = Puppet::Parser::AST::ASTArray.new :children => [code("source")] + source = Puppet::Parser::ResourceType.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/rdoc/parser.rb b/spec/unit/util/rdoc/parser.rb index 593e6acc8..19c91bb9a 100755 --- a/spec/unit/util/rdoc/parser.rb +++ b/spec/unit/util/rdoc/parser.rb @@ -137,9 +137,9 @@ describe RDoc::Parser do describe "when parsing AST elements" do before :each do - @klass = stub_everything 'klass', :file => "module/manifests/init.pp", :classname => "myclass" - @definition = stub_everything 'definition', :file => "module/manifests/init.pp" - @node = stub_everything 'node', :file => "module/manifests/init.pp" + @klass = stub_everything 'klass', :file => "module/manifests/init.pp", :name => "myclass", :type => :hostclass + @definition = stub_everything 'definition', :file => "module/manifests/init.pp", :type => :definition, :name => "mydef" + @node = stub_everything 'node', :file => "module/manifests/init.pp", :type => :node, :name => "mynode" @loadedcode = Puppet::Parser::LoadedCode.new @parser.ast = @loadedcode @@ -148,7 +148,7 @@ describe RDoc::Parser do end it "should document classes in the parsed file" do - @loadedcode.add_hostclass("myclass", @klass) + @loadedcode.add_hostclass(@klass) @parser.expects(:document_class).with("myclass", @klass, @container) @@ -157,7 +157,7 @@ describe RDoc::Parser do it "should not document class parsed in an other file" do @klass.stubs(:file).returns("/not/same/path/file.pp") - @loadedcode.add_hostclass("myclass", @klass) + @loadedcode.add_hostclass(@klass) @parser.expects(:document_class).with("myclass", @klass, @container).never @@ -165,10 +165,11 @@ describe RDoc::Parser do end it "should document vardefs for the main class" do - @loadedcode.add_hostclass(:main, @klass) + @klass.stubs(:name).returns :main + @loadedcode.add_hostclass(@klass) code = stub 'code', :is_a? => false - @klass.stubs(:classname).returns("") + @klass.stubs(:name).returns("") @klass.stubs(:code).returns(code) @parser.expects(:scan_for_vardef).with(@container, code) @@ -177,7 +178,7 @@ describe RDoc::Parser do end it "should document definitions in the parsed file" do - @loadedcode.add_definition("mydef", @definition) + @loadedcode.add_definition(@definition) @parser.expects(:document_define).with("mydef", @definition, @container) @@ -186,7 +187,7 @@ describe RDoc::Parser do it "should not document definitions parsed in an other file" do @definition.stubs(:file).returns("/not/same/path/file.pp") - @loadedcode.add_definition("mydef", @definition) + @loadedcode.add_definition(@definition) @parser.expects(:document_define).with("mydef", @definition, @container).never @@ -194,7 +195,7 @@ describe RDoc::Parser do end it "should document nodes in the parsed file" do - @loadedcode.add_node("mynode", @node) + @loadedcode.add_node(@node) @parser.expects(:document_node).with("mynode", @node, @container) @@ -203,7 +204,7 @@ describe RDoc::Parser do it "should not document node parsed in an other file" do @node.stubs(:file).returns("/not/same/path/file.pp") - @loadedcode.add_node("mynode", @node) + @loadedcode.add_node(@node) @parser.expects(:document_node).with("mynode", @node, @container).never |