diff options
author | Luke Kanies <luke@madstop.com> | 2009-12-01 16:41:38 -0800 |
---|---|---|
committer | James Turnbull <james@lovedthanlost.net> | 2009-12-09 02:13:03 +1100 |
commit | 8971d8beae2c409f9052f27c3f80ad3bdfff4de2 (patch) | |
tree | c6f7eda0523c31c2b2f3a02b3761bf43ef716ebf | |
parent | 39d4a935d47f1d42241ce492c48818dc5b533c29 (diff) | |
download | puppet-8971d8beae2c409f9052f27c3f80ad3bdfff4de2.tar.gz puppet-8971d8beae2c409f9052f27c3f80ad3bdfff4de2.tar.xz puppet-8971d8beae2c409f9052f27c3f80ad3bdfff4de2.zip |
Fixing #2596 - Node, Class, Definition are not AST
This commit extracts these three classes into a single
ResourceType class in the Parser heirarchy, now completely
independent of the AST heirarchy.
Most of the other changes are just changing the interface
to the new class, which is greatly simplified over the previous
classes.
This opens up the possibility of drastically simplifying a lot
of this other code, too -- in particular, replacing the reference
to the parser with a reference to the (soon to be renamed)
LoadedCode class.
Signed-off-by: Luke Kanies <luke@madstop.com>
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 |