diff --git a/lib/puppet/module.rb b/lib/puppet/module.rb index 00468df96..5ebf601ea 100644 --- a/lib/puppet/module.rb +++ b/lib/puppet/module.rb @@ -1,207 +1,200 @@ require 'puppet/util/logging' # Support for modules class Puppet::Module class Error < Puppet::Error; end class MissingModule < Error; end class IncompatibleModule < Error; end class UnsupportedPlatform < Error; end class IncompatiblePlatform < Error; end class MissingMetadata < Error; end class InvalidName < Error; end include Puppet::Util::Logging TEMPLATES = "templates" FILES = "files" MANIFESTS = "manifests" PLUGINS = "plugins" FILETYPES = [MANIFESTS, FILES, TEMPLATES, PLUGINS] - # Return an array of paths by splitting the +modulepath+ config - # parameter. Only consider paths that are absolute and existing - # directories - def self.modulepath(environment = nil) - Puppet::Node::Environment.new(environment).modulepath - end - # Find and return the +module+ that +path+ belongs to. If +path+ is # absolute, or if there is no module whose name is the first component # of +path+, return +nil+ def self.find(modname, environment = nil) return nil unless modname Puppet::Node::Environment.new(environment).module(modname) end attr_reader :name, :environment attr_writer :environment attr_accessor :source, :author, :version, :license, :puppetversion, :summary, :description, :project_page def has_metadata? return false unless metadata_file return false unless FileTest.exist?(metadata_file) metadata = PSON.parse File.read(metadata_file) return metadata.is_a?(Hash) && !metadata.keys.empty? end def initialize(name, environment = nil) @name = name assert_validity if environment.is_a?(Puppet::Node::Environment) @environment = environment else @environment = Puppet::Node::Environment.new(environment) end load_metadata if has_metadata? validate_puppet_version validate_dependencies end FILETYPES.each do |type| # A boolean method to let external callers determine if # we have files of a given type. define_method(type +'?') do return false unless path return false unless FileTest.exist?(subpath(type)) return true end # A method for returning a given file of a given type. # e.g., file = mod.manifest("my/manifest.pp") # # If the file name is nil, then the base directory for the # file type is passed; this is used for fileserving. define_method(type.to_s.sub(/s$/, '')) do |file| return nil unless path # If 'file' is nil then they're asking for the base path. # This is used for things like fileserving. if file full_path = File.join(subpath(type), file) else full_path = subpath(type) end return nil unless FileTest.exist?(full_path) return full_path end end def exist? ! path.nil? end # Find the first 'files' directory. This is used by the XMLRPC fileserver. def file_directory subpath("files") end def license_file return @license_file if defined?(@license_file) return @license_file = nil unless path @license_file = File.join(path, "License") end def load_metadata data = PSON.parse File.read(metadata_file) [:source, :author, :version, :license, :puppetversion].each do |attr| unless value = data[attr.to_s] unless attr == :puppetversion raise MissingMetadata, "No #{attr} module metadata provided for #{self.name}" end end send(attr.to_s + "=", value) end end # Return the list of manifests matching the given glob pattern, # defaulting to 'init.{pp,rb}' for empty modules. def match_manifests(rest) pat = File.join(path, MANIFESTS, rest || 'init') [manifest("init.pp"),manifest("init.rb")].compact + Dir. glob(pat + (File.extname(pat).empty? ? '.{pp,rb}' : '')). reject { |f| FileTest.directory?(f) } end def metadata_file return @metadata_file if defined?(@metadata_file) return @metadata_file = nil unless path @metadata_file = File.join(path, "metadata.json") end # Find this module in the modulepath. def path environment.modulepath.collect { |path| File.join(path, name) }.find { |d| FileTest.directory?(d) } end # Find all plugin directories. This is used by the Plugins fileserving mount. def plugin_directory subpath("plugins") end def requires(name, version = nil) @requires ||= [] @requires << [name, version] end def supports(name, version = nil) @supports ||= [] @supports << [name, version] end def to_s result = "Module #{name}" result += "(#{path})" if path result end def validate_dependencies return unless defined?(@requires) @requires.each do |name, version| unless mod = environment.module(name) raise MissingModule, "Missing module #{name} required by #{self.name}" end if version and mod.version != version raise IncompatibleModule, "Required module #{name} is version #{mod.version} but #{self.name} requires #{version}" end end end def validate_puppet_version return unless puppetversion and puppetversion != Puppet.version raise IncompatibleModule, "Module #{self.name} is only compatible with Puppet version #{puppetversion}, not #{Puppet.version}" end private def subpath(type) return File.join(path, type) unless type.to_s == "plugins" backward_compatible_plugins_dir end def backward_compatible_plugins_dir if dir = File.join(path, "plugins") and FileTest.exist?(dir) Puppet.warning "using the deprecated 'plugins' directory for ruby extensions; please move to 'lib'" return dir else return File.join(path, "lib") end end def assert_validity raise InvalidName, "Invalid module name; module names must be alphanumeric (plus '-'), not '#{name}'" unless name =~ /^[-\w]+$/ end end diff --git a/lib/puppet/util/rdoc/parser.rb b/lib/puppet/util/rdoc/parser.rb index a8996ee9a..05e3aab4d 100644 --- a/lib/puppet/util/rdoc/parser.rb +++ b/lib/puppet/util/rdoc/parser.rb @@ -1,495 +1,495 @@ # Puppet "parser" for the rdoc system # The parser uses puppet parser and traverse the AST to instruct RDoc about # our current structures. It also parses ruby files that could contain # either custom facts or puppet plugins (functions, types...) # rdoc mandatory includes require "rdoc/code_objects" require "puppet/util/rdoc/code_objects" require "rdoc/tokenstream" if ::RUBY_VERSION =~ /1.9/ require "rdoc/markup/preprocess" require "rdoc/parser" else require "rdoc/markup/simple_markup/preprocess" require "rdoc/parsers/parserfactory" end module RDoc class Parser extend ParserFactory unless ::RUBY_VERSION =~ /1.9/ SITE = "__site__" attr_accessor :input_file_name, :top_level # parser registration into RDoc parse_files_matching(/\.(rb|pp)$/) # called with the top level file def initialize(top_level, file_name, content, options, stats) @options = options @stats = stats @input_file_name = file_name @top_level = PuppetTopLevel.new(top_level) @progress = $stderr unless options.quiet end # main entry point def scan environment = Puppet::Node::Environment.new @known_resource_types = environment.known_resource_types unless environment.known_resource_types.watching_file?(@input_file_name) Puppet.info "rdoc: scanning #{@input_file_name}" if @input_file_name =~ /\.pp$/ @parser = Puppet::Parser::Parser.new(environment) @parser.file = @input_file_name @parser.parse.instantiate('').each do |type| @known_resource_types.add type end end end scan_top_level(@top_level) @top_level end # Due to a bug in RDoc, we need to roll our own find_module_named # The issue is that RDoc tries harder by asking the parent for a class/module # of the name. But by doing so, it can mistakenly use a module of same name # but from which we are not descendant. def find_object_named(container, name) return container if container.name == name container.each_classmodule do |m| return m if m.name == name end nil end # walk down the namespace and lookup/create container as needed def get_class_or_module(container, name) # class ::A -> A is in the top level if name =~ /^::/ container = @top_level end names = name.split('::') final_name = names.pop names.each do |name| prev_container = container container = find_object_named(container, name) container ||= prev_container.add_class(PuppetClass, name, nil) end [container, final_name] end # split_module tries to find if +path+ belongs to the module path # if it does, it returns the module name, otherwise if we are sure # it is part of the global manifest path, "__site__" is returned. # And finally if this path couldn't be mapped anywhere, nil is returned. def split_module(path) # find a module fullpath = File.expand_path(path) Puppet.debug "rdoc: testing #{fullpath}" if fullpath =~ /(.*)\/([^\/]+)\/(?:manifests|plugins|lib)\/.+\.(pp|rb)$/ modpath = $1 name = $2 Puppet.debug "rdoc: module #{name} into #{modpath} ?" - Puppet::Module.modulepath.each do |mp| + Puppet::Node::Environment.new.modulepath.each do |mp| if File.identical?(modpath,mp) Puppet.debug "rdoc: found module #{name}" return name end end end if fullpath =~ /\.(pp|rb)$/ # there can be paths we don't want to scan under modules # imagine a ruby or manifest that would be distributed as part as a module # but we don't want those to be hosted under - Puppet::Module.modulepath.each do |mp| + Puppet::Node::Environment.new.modulepath.each do |mp| # check that fullpath is a descendant of mp dirname = fullpath previous = dirname while (dirname = File.dirname(previous)) != previous previous = dirname return nil if File.identical?(dirname,mp) end end end # we are under a global manifests Puppet.debug "rdoc: global manifests" SITE end # create documentation for the top level +container+ def scan_top_level(container) # use the module README as documentation for the module comment = "" readme = File.join(File.dirname(File.dirname(@input_file_name)), "README") comment = File.open(readme,"r") { |f| f.read } if FileTest.readable?(readme) look_for_directives_in(container, comment) unless comment.empty? # infer module name from directory name = split_module(@input_file_name) if name.nil? # skip .pp files that are not in manifests directories as we can't guarantee they're part # of a module or the global configuration. container.document_self = false return end Puppet.debug "rdoc: scanning for #{name}" container.module_name = name container.global=true if name == SITE @stats.num_modules += 1 container, name = get_class_or_module(container,name) mod = container.add_module(PuppetModule, name) mod.record_location(@top_level) mod.comment = comment if @input_file_name =~ /\.pp$/ parse_elements(mod) elsif @input_file_name =~ /\.rb$/ parse_plugins(mod) end end # create documentation for include statements we can find in +code+ # and associate it with +container+ def scan_for_include_or_require(container, code) code = [code] unless code.is_a?(Array) code.each do |stmt| scan_for_include_or_require(container,stmt.children) if stmt.is_a?(Puppet::Parser::AST::ASTArray) if stmt.is_a?(Puppet::Parser::AST::Function) and ['include','require'].include?(stmt.name) stmt.arguments.each do |included| Puppet.debug "found #{stmt.name}: #{included}" container.send("add_#{stmt.name}",Include.new(included.to_s, stmt.doc)) end end end end # create documentation for realize statements we can find in +code+ # and associate it with +container+ def scan_for_realize(container, code) code = [code] unless code.is_a?(Array) code.each do |stmt| scan_for_realize(container,stmt.children) if stmt.is_a?(Puppet::Parser::AST::ASTArray) if stmt.is_a?(Puppet::Parser::AST::Function) and stmt.name == 'realize' stmt.arguments.each do |realized| Puppet.debug "found #{stmt.name}: #{realized}" container.add_realize(Include.new(realized.to_s, stmt.doc)) end end end end # create documentation for global variables assignements we can find in +code+ # and associate it with +container+ def scan_for_vardef(container, code) code = [code] unless code.is_a?(Array) code.each do |stmt| scan_for_vardef(container,stmt.children) if stmt.is_a?(Puppet::Parser::AST::ASTArray) if stmt.is_a?(Puppet::Parser::AST::VarDef) Puppet.debug "rdoc: found constant: #{stmt.name} = #{stmt.value}" container.add_constant(Constant.new(stmt.name.to_s, stmt.value.to_s, stmt.doc)) end end end # create documentation for resources we can find in +code+ # and associate it with +container+ def scan_for_resource(container, code) code = [code] unless code.is_a?(Array) code.each do |stmt| scan_for_resource(container,stmt.children) if stmt.is_a?(Puppet::Parser::AST::ASTArray) if stmt.is_a?(Puppet::Parser::AST::Resource) and !stmt.type.nil? begin type = stmt.type.split("::").collect { |s| s.capitalize }.join("::") stmt.instances.each do |inst| title = inst.title.is_a?(Puppet::Parser::AST::ASTArray) ? inst.title.to_s.gsub(/\[(.*)\]/,'\1') : inst.title.to_s Puppet.debug "rdoc: found resource: #{type}[#{title}]" param = [] inst.parameters.children.each do |p| res = {} res["name"] = p.param res["value"] = "#{p.value.to_s}" unless p.value.nil? param << res end container.add_resource(PuppetResource.new(type, title, stmt.doc, param)) end rescue => detail raise Puppet::ParseError, "impossible to parse resource in #{stmt.file} at line #{stmt.line}: #{detail}" end end end end def resource_stmt_to_ref(stmt) type = stmt.type.split("::").collect { |s| s.capitalize }.join("::") title = stmt.title.is_a?(Puppet::Parser::AST::ASTArray) ? stmt.title.to_s.gsub(/\[(.*)\]/,'\1') : stmt.title.to_s param = stmt.params.children.collect do |p| {"name" => p.param, "value" => p.value.to_s} end PuppetResource.new(type, title, stmt.doc, param) end # create documentation for a class named +name+ def document_class(name, klass, container) Puppet.debug "rdoc: found new class #{name}" container, name = get_class_or_module(container, name) superclass = klass.parent superclass = "" if superclass.nil? or superclass.empty? @stats.num_classes += 1 comment = klass.doc look_for_directives_in(container, comment) unless comment.empty? cls = container.add_class(PuppetClass, name, superclass) # it is possible we already encountered this class, while parsing some namespaces # from other classes of other files. But at that time we couldn't know this class superclass # so, now we know it and force it. cls.superclass = superclass cls.record_location(@top_level) # scan class code for include code = klass.code.children if klass.code.is_a?(Puppet::Parser::AST::ASTArray) code ||= klass.code unless code.nil? scan_for_include_or_require(cls, code) scan_for_realize(cls, code) scan_for_resource(cls, code) if Puppet.settings[:document_all] end cls.comment = comment rescue => detail raise Puppet::ParseError, "impossible to parse class '#{name}' in #{klass.file} at line #{klass.line}: #{detail}" end # create documentation for a node def document_node(name, node, container) Puppet.debug "rdoc: found new node #{name}" superclass = node.parent superclass = "" if superclass.nil? or superclass.empty? comment = node.doc look_for_directives_in(container, comment) unless comment.empty? n = container.add_node(name, superclass) n.record_location(@top_level) code = node.code.children if node.code.is_a?(Puppet::Parser::AST::ASTArray) code ||= node.code unless code.nil? scan_for_include_or_require(n, code) scan_for_realize(n, code) scan_for_vardef(n, code) scan_for_resource(n, code) if Puppet.settings[:document_all] end n.comment = comment rescue => detail raise Puppet::ParseError, "impossible to parse node '#{name}' in #{node.file} at line #{node.line}: #{detail}" end # create documentation for a define def document_define(name, define, container) Puppet.debug "rdoc: found new definition #{name}" # find superclas if any @stats.num_methods += 1 # find the parent # split define name by :: to find the complete module hierarchy container, name = get_class_or_module(container,name) # build up declaration declaration = "" define.arguments.each do |arg,value| declaration << "\$#{arg}" unless value.nil? declaration << " => " case value when Puppet::Parser::AST::Leaf declaration << "'#{value.value}'" when Puppet::Parser::AST::ASTArray declaration << "[#{value.children.collect { |v| "'#{v}'" }.join(", ")}]" else declaration << "#{value.to_s}" end end declaration << ", " end declaration.chop!.chop! if declaration.size > 1 # register method into the container meth = AnyMethod.new(declaration, name) meth.comment = define.doc container.add_method(meth) look_for_directives_in(container, meth.comment) unless meth.comment.empty? meth.params = "( #{declaration} )" meth.visibility = :public meth.document_self = true meth.singleton = false rescue => detail raise Puppet::ParseError, "impossible to parse definition '#{name}' in #{define.file} at line #{define.line}: #{detail}" end # Traverse the AST tree and produce code-objects node # that contains the documentation def parse_elements(container) Puppet.debug "rdoc: scanning manifest" @known_resource_types.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) else # on main class document vardefs code = klass.code.children if klass.code.is_a?(Puppet::Parser::AST::ASTArray) code ||= klass.code scan_for_vardef(container, code) unless code.nil? end end end @known_resource_types.definitions.each do |name, define| if define.file == @input_file_name document_define(name,define,container) end end @known_resource_types.nodes.each do |name, node| if node.file == @input_file_name document_node(name.to_s,node,container) end end end # create documentation for plugins def parse_plugins(container) Puppet.debug "rdoc: scanning plugin or fact" if @input_file_name =~ /\/facter\/[^\/]+\.rb$/ parse_fact(container) else parse_puppet_plugin(container) end end # this is a poor man custom fact parser :-) def parse_fact(container) comments = "" current_fact = nil File.open(@input_file_name) do |of| of.each do |line| # fetch comments if line =~ /^[ \t]*# ?(.*)$/ comments += $1 + "\n" elsif line =~ /^[ \t]*Facter.add\(['"](.*?)['"]\)/ current_fact = Fact.new($1,{}) look_for_directives_in(container, comments) unless comments.empty? current_fact.comment = comments container.add_fact(current_fact) current_fact.record_location(@top_level) comments = "" Puppet.debug "rdoc: found custom fact #{current_fact.name}" elsif line =~ /^[ \t]*confine[ \t]*:(.*?)[ \t]*=>[ \t]*(.*)$/ current_fact.confine = { :type => $1, :value => $2 } unless current_fact.nil? else # unknown line type comments ="" end end end end # this is a poor man puppet plugin parser :-) # it doesn't extract doc nor desc :-( def parse_puppet_plugin(container) comments = "" current_plugin = nil File.open(@input_file_name) do |of| of.each do |line| # fetch comments if line =~ /^[ \t]*# ?(.*)$/ comments += $1 + "\n" elsif line =~ /^[ \t]*newfunction[ \t]*\([ \t]*:(.*?)[ \t]*,[ \t]*:type[ \t]*=>[ \t]*(:rvalue|:lvalue)\)/ current_plugin = Plugin.new($1, "function") container.add_plugin(current_plugin) look_for_directives_in(container, comments) unless comments.empty? current_plugin.comment = comments current_plugin.record_location(@top_level) comments = "" Puppet.debug "rdoc: found new function plugins #{current_plugin.name}" elsif line =~ /^[ \t]*Puppet::Type.newtype[ \t]*\([ \t]*:(.*?)\)/ current_plugin = Plugin.new($1, "type") container.add_plugin(current_plugin) look_for_directives_in(container, comments) unless comments.empty? current_plugin.comment = comments current_plugin.record_location(@top_level) comments = "" Puppet.debug "rdoc: found new type plugins #{current_plugin.name}" elsif line =~ /module Puppet::Parser::Functions/ # skip else # unknown line type comments ="" end end end end # look_for_directives_in scans the current +comment+ for RDoc directives def look_for_directives_in(context, comment) preprocess = SM::PreProcess.new(@input_file_name, @options.rdoc_include) preprocess.handle(comment) do |directive, param| case directive when "stopdoc" context.stop_doc "" when "startdoc" context.start_doc context.force_documentation = true "" when "enddoc" #context.done_documenting = true #"" throw :enddoc when "main" options = Options.instance options.main_page = param "" when "title" options = Options.instance options.title = param "" when "section" context.set_current_section(param, comment) comment.replace("") # 1.8 doesn't support #clear break else warn "Unrecognized directive '#{directive}'" break end end remove_private_comments(comment) end def remove_private_comments(comment) comment.gsub!(/^#--.*?^#\+\+/m, '') comment.sub!(/^#--.*/m, '') end end end diff --git a/spec/unit/module_spec.rb b/spec/unit/module_spec.rb index a0f64c6d3..e31e5044f 100755 --- a/spec/unit/module_spec.rb +++ b/spec/unit/module_spec.rb @@ -1,598 +1,579 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet_spec/files' describe Puppet::Module do include PuppetSpec::Files before do # This is necessary because of the extra checks we have for the deprecated # 'plugins' directory FileTest.stubs(:exist?).returns false end it "should have a class method that returns a named module from a given environment" do env = mock 'module' env.expects(:module).with("mymod").returns "yep" Puppet::Node::Environment.expects(:new).with("myenv").returns env Puppet::Module.find("mymod", "myenv").should == "yep" end it "should return nil if asked for a named module that doesn't exist" do env = mock 'module' env.expects(:module).with("mymod").returns nil Puppet::Node::Environment.expects(:new).with("myenv").returns env Puppet::Module.find("mymod", "myenv").should be_nil end it "should support a 'version' attribute" do mod = Puppet::Module.new("mymod") mod.version = 1.09 mod.version.should == 1.09 end it "should support a 'source' attribute" do mod = Puppet::Module.new("mymod") mod.source = "http://foo/bar" mod.source.should == "http://foo/bar" end it "should support a 'project_page' attribute" do mod = Puppet::Module.new("mymod") mod.project_page = "http://foo/bar" mod.project_page.should == "http://foo/bar" end it "should support an 'author' attribute" do mod = Puppet::Module.new("mymod") mod.author = "Luke Kanies " mod.author.should == "Luke Kanies " end it "should support a 'license' attribute" do mod = Puppet::Module.new("mymod") mod.license = "GPL2" mod.license.should == "GPL2" end it "should support a 'summary' attribute" do mod = Puppet::Module.new("mymod") mod.summary = "GPL2" mod.summary.should == "GPL2" end it "should support a 'description' attribute" do mod = Puppet::Module.new("mymod") mod.description = "GPL2" mod.description.should == "GPL2" end it "should support specifying a compatible puppet version" do mod = Puppet::Module.new("mymod") mod.puppetversion = "0.25" mod.puppetversion.should == "0.25" end it "should validate that the puppet version is compatible" do mod = Puppet::Module.new("mymod") mod.puppetversion = "0.25" Puppet.expects(:version).returns "0.25" mod.validate_puppet_version end it "should fail if the specified puppet version is not compatible" do mod = Puppet::Module.new("mymod") mod.puppetversion = "0.25" Puppet.stubs(:version).returns "0.24" lambda { mod.validate_puppet_version }.should raise_error(Puppet::Module::IncompatibleModule) end describe "when specifying required modules" do it "should support specifying a required module" do mod = Puppet::Module.new("mymod") mod.requires "foobar" end it "should support specifying multiple required modules" do mod = Puppet::Module.new("mymod") mod.requires "foobar" mod.requires "baz" end it "should support specifying a required module and version" do mod = Puppet::Module.new("mymod") mod.requires "foobar", 1.0 end it "should fail when required modules are missing" do mod = Puppet::Module.new("mymod") mod.requires "foobar" mod.environment.expects(:module).with("foobar").returns nil lambda { mod.validate_dependencies }.should raise_error(Puppet::Module::MissingModule) end it "should fail when required modules are present but of the wrong version" do mod = Puppet::Module.new("mymod") mod.requires "foobar", 1.0 foobar = Puppet::Module.new("foobar") foobar.version = 2.0 mod.environment.expects(:module).with("foobar").returns foobar lambda { mod.validate_dependencies }.should raise_error(Puppet::Module::IncompatibleModule) end it "should have valid dependencies when no dependencies have been specified" do mod = Puppet::Module.new("mymod") lambda { mod.validate_dependencies }.should_not raise_error end it "should fail when some dependencies are present but others aren't" do mod = Puppet::Module.new("mymod") mod.requires "foobar" mod.requires "baz" mod.environment.expects(:module).with("foobar").returns Puppet::Module.new("foobar") mod.environment.expects(:module).with("baz").returns nil lambda { mod.validate_dependencies }.should raise_error(Puppet::Module::MissingModule) end it "should have valid dependencies when all dependencies are met" do mod = Puppet::Module.new("mymod") mod.requires "foobar", 1.0 mod.requires "baz" foobar = Puppet::Module.new("foobar") foobar.version = 1.0 baz = Puppet::Module.new("baz") mod.environment.expects(:module).with("foobar").returns foobar mod.environment.expects(:module).with("baz").returns baz lambda { mod.validate_dependencies }.should_not raise_error end it "should validate its dependendencies on initialization" do Puppet::Module.any_instance.expects(:validate_dependencies) Puppet::Module.new("mymod") end end describe "when managing supported platforms" do it "should support specifying a supported platform" do mod = Puppet::Module.new("mymod") mod.supports "solaris" end it "should support specifying a supported platform and version" do mod = Puppet::Module.new("mymod") mod.supports "solaris", 1.0 end it "should fail when not running on a supported platform" do pending "Not sure how to send client platform to the module" mod = Puppet::Module.new("mymod") Facter.expects(:value).with("operatingsystem").returns "Solaris" mod.supports "hpux" lambda { mod.validate_supported_platform }.should raise_error(Puppet::Module::UnsupportedPlatform) end it "should fail when supported platforms are present but of the wrong version" do pending "Not sure how to send client platform to the module" mod = Puppet::Module.new("mymod") Facter.expects(:value).with("operatingsystem").returns "Solaris" Facter.expects(:value).with("operatingsystemrelease").returns 2.0 mod.supports "Solaris", 1.0 lambda { mod.validate_supported_platform }.should raise_error(Puppet::Module::IncompatiblePlatform) end it "should be considered supported when no supported platforms have been specified" do pending "Not sure how to send client platform to the module" mod = Puppet::Module.new("mymod") lambda { mod.validate_supported_platform }.should_not raise_error end it "should be considered supported when running on a supported platform" do pending "Not sure how to send client platform to the module" mod = Puppet::Module.new("mymod") Facter.expects(:value).with("operatingsystem").returns "Solaris" Facter.expects(:value).with("operatingsystemrelease").returns 2.0 mod.supports "Solaris", 1.0 lambda { mod.validate_supported_platform }.should raise_error(Puppet::Module::IncompatiblePlatform) end it "should be considered supported when running on any of multiple supported platforms" do pending "Not sure how to send client platform to the module" end it "should validate its platform support on initialization" do pending "Not sure how to send client platform to the module" end end it "should return nil if asked for a module whose name is 'nil'" do Puppet::Module.find(nil, "myenv").should be_nil end it "should provide support for logging" do Puppet::Module.ancestors.should be_include(Puppet::Util::Logging) end it "should be able to be converted to a string" do Puppet::Module.new("foo").to_s.should == "Module foo" end it "should add the path to its string form if the module is found" do mod = Puppet::Module.new("foo") mod.stubs(:path).returns "/a" mod.to_s.should == "Module foo(/a)" end it "should fail if its name is not alphanumeric" do lambda { Puppet::Module.new(".something") }.should raise_error(Puppet::Module::InvalidName) end it "should require a name at initialization" do lambda { Puppet::Module.new }.should raise_error(ArgumentError) end it "should convert an environment name into an Environment instance" do Puppet::Module.new("foo", "prod").environment.should be_instance_of(Puppet::Node::Environment) end it "should accept an environment at initialization" do Puppet::Module.new("foo", :prod).environment.name.should == :prod end it "should use the default environment if none is provided" do env = Puppet::Node::Environment.new Puppet::Module.new("foo").environment.should equal(env) end it "should use any provided Environment instance" do env = Puppet::Node::Environment.new Puppet::Module.new("foo", env).environment.should equal(env) end it "should return the path to the first found instance in its environment's module paths as its path" do dir = tmpdir("deep_path") first = File.join(dir, "first") second = File.join(dir, "second") FileUtils.mkdir_p(first) FileUtils.mkdir_p(second) Puppet[:modulepath] = "#{first}#{File::PATH_SEPARATOR}#{second}" modpath = File.join(first, "foo") FileUtils.mkdir_p(modpath) # Make a second one, which we shouldn't find FileUtils.mkdir_p(File.join(second, "foo")) mod = Puppet::Module.new("foo") mod.path.should == modpath end it "should be able to find itself in a directory other than the first directory in the module path" do dir = tmpdir("deep_path") first = File.join(dir, "first") second = File.join(dir, "second") FileUtils.mkdir_p(first) FileUtils.mkdir_p(second) Puppet[:modulepath] = "#{first}#{File::PATH_SEPARATOR}#{second}" modpath = File.join(second, "foo") FileUtils.mkdir_p(modpath) mod = Puppet::Module.new("foo") mod.should be_exist mod.path.should == modpath end it "should be considered existent if it exists in at least one module path" do mod = Puppet::Module.new("foo") mod.expects(:path).returns "/a/foo" mod.should be_exist end it "should be considered nonexistent if it does not exist in any of the module paths" do mod = Puppet::Module.new("foo") mod.expects(:path).returns nil mod.should_not be_exist end [:plugins, :templates, :files, :manifests].each do |filetype| dirname = filetype == :plugins ? "lib" : filetype.to_s it "should be able to return individual #{filetype}" do mod = Puppet::Module.new("foo") mod.stubs(:path).returns "/a/foo" path = File.join("/a/foo", dirname, "my/file") FileTest.expects(:exist?).with(path).returns true mod.send(filetype.to_s.sub(/s$/, ''), "my/file").should == path end it "should consider #{filetype} to be present if their base directory exists" do mod = Puppet::Module.new("foo") mod.stubs(:path).returns "/a/foo" path = File.join("/a/foo", dirname) FileTest.expects(:exist?).with(path).returns true mod.send(filetype.to_s + "?").should be_true end it "should consider #{filetype} to be absent if their base directory does not exist" do mod = Puppet::Module.new("foo") mod.stubs(:path).returns "/a/foo" path = File.join("/a/foo", dirname) FileTest.expects(:exist?).with(path).returns false mod.send(filetype.to_s + "?").should be_false end it "should consider #{filetype} to be absent if the module base directory does not exist" do mod = Puppet::Module.new("foo") mod.stubs(:path).returns nil mod.send(filetype.to_s + "?").should be_false end it "should return nil if asked to return individual #{filetype} that don't exist" do mod = Puppet::Module.new("foo") mod.stubs(:path).returns "/a/foo" path = File.join("/a/foo", dirname, "my/file") FileTest.expects(:exist?).with(path).returns false mod.send(filetype.to_s.sub(/s$/, ''), "my/file").should be_nil end it "should return nil when asked for individual #{filetype} if the module does not exist" do mod = Puppet::Module.new("foo") mod.stubs(:path).returns nil mod.send(filetype.to_s.sub(/s$/, ''), "my/file").should be_nil end it "should return the base directory if asked for a nil path" do mod = Puppet::Module.new("foo") mod.stubs(:path).returns "/a/foo" base = File.join("/a/foo", dirname) FileTest.expects(:exist?).with(base).returns true mod.send(filetype.to_s.sub(/s$/, ''), nil).should == base end end %w{plugins files}.each do |filetype| short = filetype.sub(/s$/, '') dirname = filetype == "plugins" ? "lib" : filetype.to_s it "should be able to return the #{short} directory" do Puppet::Module.new("foo").should respond_to(short + "_directory") end it "should return the path to the #{short} directory" do mod = Puppet::Module.new("foo") mod.stubs(:path).returns "/a/foo" mod.send(short + "_directory").should == "/a/foo/#{dirname}" end end it "should throw a warning if plugins are in a 'plugins' directory rather than a 'lib' directory" do mod = Puppet::Module.new("foo") mod.stubs(:path).returns "/a/foo" FileTest.expects(:exist?).with("/a/foo/plugins").returns true mod.plugin_directory.should == "/a/foo/plugins" @logs.first.message.should == "using the deprecated 'plugins' directory for ruby extensions; please move to 'lib'" @logs.first.level.should == :warning end it "should default to 'lib' for the plugins directory" do mod = Puppet::Module.new("foo") mod.stubs(:path).returns "/a/foo" mod.plugin_directory.should == "/a/foo/lib" end end -describe Puppet::Module, " when building its search path" do - it "should use the current environment's search path if no environment is specified" do - env = mock 'env' - env.expects(:modulepath).returns "eh" - Puppet::Node::Environment.expects(:new).with(nil).returns env - - Puppet::Module.modulepath.should == "eh" - end - - it "should use the specified environment's search path if an environment is specified" do - env = mock 'env' - env.expects(:modulepath).returns "eh" - Puppet::Node::Environment.expects(:new).with("foo").returns env - - Puppet::Module.modulepath("foo").should == "eh" - end -end - describe Puppet::Module, "when finding matching manifests" do before do @mod = Puppet::Module.new("mymod") @mod.stubs(:path).returns "/a" @pq_glob_with_extension = "yay/*.xx" @fq_glob_with_extension = "/a/manifests/#{@pq_glob_with_extension}" end it "should return all manifests matching the glob pattern" do Dir.expects(:glob).with(@fq_glob_with_extension).returns(%w{foo bar}) FileTest.stubs(:directory?).returns false @mod.match_manifests(@pq_glob_with_extension).should == %w{foo bar} end it "should not return directories" do Dir.expects(:glob).with(@fq_glob_with_extension).returns(%w{foo bar}) FileTest.expects(:directory?).with("foo").returns false FileTest.expects(:directory?).with("bar").returns true @mod.match_manifests(@pq_glob_with_extension).should == %w{foo} end it "should default to the 'init' file if no glob pattern is specified" do Dir.expects(:glob).with("/a/manifests/init.{pp,rb}").returns(%w{/a/manifests/init.pp}) @mod.match_manifests(nil).should == %w{/a/manifests/init.pp} end it "should return all manifests matching the glob pattern in all existing paths" do Dir.expects(:glob).with(@fq_glob_with_extension).returns(%w{a b}) @mod.match_manifests(@pq_glob_with_extension).should == %w{a b} end it "should match the glob pattern plus '.{pp,rb}' if no extention is specified" do Dir.expects(:glob).with("/a/manifests/yay/foo.{pp,rb}").returns(%w{yay}) @mod.match_manifests("yay/foo").should == %w{yay} end it "should return an empty array if no manifests matched" do Dir.expects(:glob).with(@fq_glob_with_extension).returns([]) @mod.match_manifests(@pq_glob_with_extension).should == [] end end describe Puppet::Module do before do Puppet::Module.any_instance.stubs(:path).returns "/my/mod/path" @module = Puppet::Module.new("foo") end it "should use 'License' in its current path as its metadata file" do @module.license_file.should == "/my/mod/path/License" end it "should return nil as its license file when the module has no path" do Puppet::Module.any_instance.stubs(:path).returns nil Puppet::Module.new("foo").license_file.should be_nil end it "should cache the license file" do Puppet::Module.any_instance.expects(:path).once.returns nil mod = Puppet::Module.new("foo") mod.license_file.should == mod.license_file end it "should use 'metadata.json' in its current path as its metadata file" do @module.metadata_file.should == "/my/mod/path/metadata.json" end it "should return nil as its metadata file when the module has no path" do Puppet::Module.any_instance.stubs(:path).returns nil Puppet::Module.new("foo").metadata_file.should be_nil end it "should cache the metadata file" do Puppet::Module.any_instance.expects(:path).once.returns nil mod = Puppet::Module.new("foo") mod.metadata_file.should == mod.metadata_file end it "should have metadata if it has a metadata file and its data is not empty" do FileTest.expects(:exist?).with(@module.metadata_file).returns true File.stubs(:read).with(@module.metadata_file).returns "{\"foo\" : \"bar\"}" @module.should be_has_metadata end it "should have metadata if it has a metadata file and its data is not empty" do FileTest.expects(:exist?).with(@module.metadata_file).returns true File.stubs(:read).with(@module.metadata_file).returns "{\"foo\" : \"bar\"}" @module.should be_has_metadata end it "should not have metadata if has a metadata file and its data is empty" do FileTest.expects(:exist?).with(@module.metadata_file).returns true File.stubs(:read).with(@module.metadata_file).returns "/* +-----------------------------------------------------------------------+ | | | ==> DO NOT EDIT THIS FILE! <== | | | | You should edit the `Modulefile` and run `puppet-module build` | | to generate the `metadata.json` file for your releases. | | | +-----------------------------------------------------------------------+ */ {}" @module.should_not be_has_metadata end it "should know if it is missing a metadata file" do FileTest.expects(:exist?).with(@module.metadata_file).returns false @module.should_not be_has_metadata end it "should be able to parse its metadata file" do @module.should respond_to(:load_metadata) end it "should parse its metadata file on initialization if it is present" do Puppet::Module.any_instance.expects(:has_metadata?).returns true Puppet::Module.any_instance.expects(:load_metadata) Puppet::Module.new("yay") end describe "when loading the medatada file", :if => Puppet.features.pson? do before do @data = { :license => "GPL2", :author => "luke", :version => "1.0", :source => "http://foo/", :puppetversion => "0.25" } @text = @data.to_pson @module = Puppet::Module.new("foo") @module.stubs(:metadata_file).returns "/my/file" File.stubs(:read).with("/my/file").returns @text end %w{source author version license}.each do |attr| it "should set #{attr} if present in the metadata file" do @module.load_metadata @module.send(attr).should == @data[attr.to_sym] end it "should fail if #{attr} is not present in the metadata file" do @data.delete(attr.to_sym) @text = @data.to_pson File.stubs(:read).with("/my/file").returns @text lambda { @module.load_metadata }.should raise_error( Puppet::Module::MissingMetadata, "No #{attr} module metadata provided for foo" ) end end it "should set puppetversion if present in the metadata file" do @module.load_metadata @module.puppetversion.should == @data[:puppetversion] end - it "should fail if the discovered name is different than the metadata name" end end diff --git a/spec/unit/util/rdoc/parser_spec.rb b/spec/unit/util/rdoc/parser_spec.rb index 9c8cc7588..892b932b2 100755 --- a/spec/unit/util/rdoc/parser_spec.rb +++ b/spec/unit/util/rdoc/parser_spec.rb @@ -1,568 +1,568 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/resource/type_collection' require 'puppet/util/rdoc/parser' require 'puppet/util/rdoc/code_objects' require 'rdoc/options' require 'rdoc/rdoc' describe RDoc::Parser, :'fails_on_ruby_1.9.2' => true do include PuppetSpec::Files before :each do File.stubs(:stat).with("init.pp") @top_level = stub_everything 'toplevel', :file_relative_name => "init.pp" @parser = RDoc::Parser.new(@top_level, "module/manifests/init.pp", nil, Options.instance, RDoc::Stats.new) end describe "when scanning files" do it "should parse puppet files with the puppet parser" do @parser.stubs(:scan_top_level) parser = stub 'parser' Puppet::Parser::Parser.stubs(:new).returns(parser) parser.expects(:parse).returns(Puppet::Parser::AST::Hostclass.new('')).at_least_once parser.expects(:file=).with("module/manifests/init.pp") parser.expects(:file=).with(File.expand_path("/dev/null/manifests/site.pp")) @parser.scan end it "should scan the ast for Puppet files" do parser = stub_everything 'parser' Puppet::Parser::Parser.stubs(:new).returns(parser) parser.expects(:parse).returns(Puppet::Parser::AST::Hostclass.new('')).at_least_once @parser.expects(:scan_top_level) @parser.scan end it "should return a PuppetTopLevel to RDoc" do parser = stub_everything 'parser' Puppet::Parser::Parser.stubs(:new).returns(parser) parser.expects(:parse).returns(Puppet::Parser::AST::Hostclass.new('')).at_least_once @parser.expects(:scan_top_level) @parser.scan.should be_a(RDoc::PuppetTopLevel) end it "should scan the top level even if the file has already parsed" do known_type = stub 'known_types' env = stub 'env' Puppet::Node::Environment.stubs(:new).returns(env) env.stubs(:known_resource_types).returns(known_type) known_type.expects(:watching_file?).with("module/manifests/init.pp").returns(true) @parser.expects(:scan_top_level) @parser.scan end end describe "when scanning top level entities" do before :each do @resource_type_collection = resource_type_collection = stub_everything('resource_type_collection') @parser.instance_eval { @known_resource_types = resource_type_collection } @parser.stubs(:split_module).returns("module") @topcontainer = stub_everything 'topcontainer' @container = stub_everything 'container' @module = stub_everything 'module' @container.stubs(:add_module).returns(@module) @parser.stubs(:get_class_or_module).returns([@container, "module"]) end it "should read any present README as module documentation" do FileTest.stubs(:readable?).returns(true) File.stubs(:open).returns("readme") @parser.stubs(:parse_elements) @module.expects(:comment=).with("readme") @parser.scan_top_level(@topcontainer) end it "should tell the container its module name" do @parser.stubs(:parse_elements) @topcontainer.expects(:module_name=).with("module") @parser.scan_top_level(@topcontainer) end it "should not document our toplevel if it isn't a valid module" do @parser.stubs(:split_module).returns(nil) @topcontainer.expects(:document_self=).with(false) @parser.expects(:parse_elements).never @parser.scan_top_level(@topcontainer) end it "should set the module as global if we parse the global manifests (ie __site__ module)" do @parser.stubs(:split_module).returns(RDoc::Parser::SITE) @parser.stubs(:parse_elements) @topcontainer.expects(:global=).with(true) @parser.scan_top_level(@topcontainer) end it "should attach this module container to the toplevel container" do @parser.stubs(:parse_elements) @container.expects(:add_module).with(RDoc::PuppetModule, "module").returns(@module) @parser.scan_top_level(@topcontainer) end it "should defer ast parsing to parse_elements for this module" do @parser.expects(:parse_elements).with(@module) @parser.scan_top_level(@topcontainer) end it "should defer plugins parsing to parse_plugins for this module" do @parser.input_file_name = "module/lib/puppet/parser/function.rb" @parser.expects(:parse_plugins).with(@module) @parser.scan_top_level(@topcontainer) end end describe "when finding modules from filepath" do before :each do - Puppet::Module.stubs(:modulepath).returns("/path/to/modules") + Puppet::Node::Environment.any_instance.stubs(:modulepath).returns("/path/to/modules") end it "should return the module name for modulized puppet manifests" do File.stubs(:expand_path).returns("/path/to/module/manifests/init.pp") File.stubs(:identical?).with("/path/to", "/path/to/modules").returns(true) @parser.split_module("/path/to/modules/mymodule/manifests/init.pp").should == "module" end it "should return for manifests not under module path" do File.stubs(:expand_path).returns("/path/to/manifests/init.pp") File.stubs(:identical?).returns(false) @parser.split_module("/path/to/manifests/init.pp").should == RDoc::Parser::SITE end it "should handle windows paths with drive letters", :if => Puppet.features.microsoft_windows? do @parser.split_module("C:/temp/init.pp").should == RDoc::Parser::SITE end end describe "when parsing AST elements" do before :each do @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" @resource_type_collection = resource_type_collection = Puppet::Resource::TypeCollection.new("env") @parser.instance_eval { @known_resource_types = resource_type_collection } @container = stub_everything 'container' end it "should document classes in the parsed file" do @resource_type_collection.add_hostclass(@klass) @parser.expects(:document_class).with("myclass", @klass, @container) @parser.parse_elements(@container) end it "should not document class parsed in an other file" do @klass.stubs(:file).returns("/not/same/path/file.pp") @resource_type_collection.add_hostclass(@klass) @parser.expects(:document_class).with("myclass", @klass, @container).never @parser.parse_elements(@container) end it "should document vardefs for the main class" do @klass.stubs(:name).returns :main @resource_type_collection.add_hostclass(@klass) code = stub 'code', :is_a? => false @klass.stubs(:name).returns("") @klass.stubs(:code).returns(code) @parser.expects(:scan_for_vardef).with(@container, code) @parser.parse_elements(@container) end it "should document definitions in the parsed file" do @resource_type_collection.add_definition(@definition) @parser.expects(:document_define).with("mydef", @definition, @container) @parser.parse_elements(@container) end it "should not document definitions parsed in an other file" do @definition.stubs(:file).returns("/not/same/path/file.pp") @resource_type_collection.add_definition(@definition) @parser.expects(:document_define).with("mydef", @definition, @container).never @parser.parse_elements(@container) end it "should document nodes in the parsed file" do @resource_type_collection.add_node(@node) @parser.expects(:document_node).with("mynode", @node, @container) @parser.parse_elements(@container) end it "should not document node parsed in an other file" do @node.stubs(:file).returns("/not/same/path/file.pp") @resource_type_collection.add_node(@node) @parser.expects(:document_node).with("mynode", @node, @container).never @parser.parse_elements(@container) end end describe "when documenting definition" do before(:each) do @define = stub_everything 'define', :arguments => [], :doc => "mydoc", :file => "file", :line => 42 @class = stub_everything 'class' @parser.stubs(:get_class_or_module).returns([@class, "mydef"]) end it "should register a RDoc method to the current container" do @class.expects(:add_method).with { |m| m.name == "mydef"} @parser.document_define("mydef", @define, @class) end it "should attach the documentation to this method" do @class.expects(:add_method).with { |m| m.comment = "mydoc" } @parser.document_define("mydef", @define, @class) end it "should produce a better error message on unhandled exception" do @class.expects(:add_method).raises(ArgumentError) lambda { @parser.document_define("mydef", @define, @class) }.should raise_error(Puppet::ParseError, /in file at line 42/) end it "should convert all definition parameter to string" do arg = stub 'arg' val = stub 'val' @define.stubs(:arguments).returns({arg => val}) arg.expects(:to_s).returns("arg") val.expects(:to_s).returns("val") @parser.document_define("mydef", @define, @class) end end describe "when documenting nodes" do before :each do @code = stub_everything 'code' @node = stub_everything 'node', :doc => "mydoc", :parent => "parent", :code => @code, :file => "file", :line => 42 @rdoc_node = stub_everything 'rdocnode' @class = stub_everything 'class' @class.stubs(:add_node).returns(@rdoc_node) end it "should add a node to the current container" do @class.expects(:add_node).with("mynode", "parent").returns(@rdoc_node) @parser.document_node("mynode", @node, @class) end it "should associate the node documentation to the rdoc node" do @rdoc_node.expects(:comment=).with("mydoc") @parser.document_node("mynode", @node, @class) end it "should scan for include and require" do @parser.expects(:scan_for_include_or_require).with(@rdoc_node, @code) @parser.document_node("mynode", @node, @class) end it "should scan for variable definition" do @parser.expects(:scan_for_vardef).with(@rdoc_node, @code) @parser.document_node("mynode", @node, @class) end it "should scan for resources if needed" do Puppet.settings.stubs(:[]).with(:document_all).returns(true) @parser.expects(:scan_for_resource).with(@rdoc_node, @code) @parser.document_node("mynode", @node, @class) end it "should produce a better error message on unhandled exception" do @class.stubs(:add_node).raises(ArgumentError) lambda { @parser.document_node("mynode", @node, @class) }.should raise_error(Puppet::ParseError, /in file at line 42/) end end describe "when documenting classes" do before :each do @code = stub_everything 'code' @class = stub_everything 'class', :doc => "mydoc", :parent => "parent", :code => @code, :file => "file", :line => 42 @rdoc_class = stub_everything 'rdoc-class' @module = stub_everything 'class' @module.stubs(:add_class).returns(@rdoc_class) @parser.stubs(:get_class_or_module).returns([@module, "myclass"]) end it "should add a class to the current container" do @module.expects(:add_class).with(RDoc::PuppetClass, "myclass", "parent").returns(@rdoc_class) @parser.document_class("mynode", @class, @module) end it "should set the superclass" do @rdoc_class.expects(:superclass=).with("parent") @parser.document_class("mynode", @class, @module) end it "should associate the node documentation to the rdoc class" do @rdoc_class.expects(:comment=).with("mydoc") @parser.document_class("mynode", @class, @module) end it "should scan for include and require" do @parser.expects(:scan_for_include_or_require).with(@rdoc_class, @code) @parser.document_class("mynode", @class, @module) end it "should scan for resources if needed" do Puppet.settings.stubs(:[]).with(:document_all).returns(true) @parser.expects(:scan_for_resource).with(@rdoc_class, @code) @parser.document_class("mynode", @class, @module) end it "should produce a better error message on unhandled exception" do @module.stubs(:add_class).raises(ArgumentError) lambda { @parser.document_class("mynode", @class, @module) }.should raise_error(Puppet::ParseError, /in file at line 42/) end end describe "when scanning for includes and requires" do def create_stmt(name) stmt_value = stub "#{name}_value", :to_s => "myclass" Puppet::Parser::AST::Function.new( :name => name, :arguments => [stmt_value], :doc => 'mydoc' ) end before(:each) do @class = stub_everything 'class' @code = stub_everything 'code' @code.stubs(:is_a?).with(Puppet::Parser::AST::ASTArray).returns(true) end it "should also scan mono-instruction code" do @class.expects(:add_include).with { |i| i.is_a?(RDoc::Include) and i.name == "myclass" and i.comment == "mydoc" } @parser.scan_for_include_or_require(@class, create_stmt("include")) end it "should register recursively includes to the current container" do @code.stubs(:children).returns([ create_stmt("include") ]) @class.expects(:add_include)#.with { |i| i.is_a?(RDoc::Include) and i.name == "myclass" and i.comment == "mydoc" } @parser.scan_for_include_or_require(@class, [@code]) end it "should register requires to the current container" do @code.stubs(:children).returns([ create_stmt("require") ]) @class.expects(:add_require).with { |i| i.is_a?(RDoc::Include) and i.name == "myclass" and i.comment == "mydoc" } @parser.scan_for_include_or_require(@class, [@code]) end end describe "when scanning for realized virtual resources" do def create_stmt stmt_value = stub "resource_ref", :to_s => "File[\"/tmp/a\"]" Puppet::Parser::AST::Function.new( :name => 'realize', :arguments => [stmt_value], :doc => 'mydoc' ) end before(:each) do @class = stub_everything 'class' @code = stub_everything 'code' @code.stubs(:is_a?).with(Puppet::Parser::AST::ASTArray).returns(true) end it "should also scan mono-instruction code" do @class.expects(:add_realize).with { |i| i.is_a?(RDoc::Include) and i.name == "File[\"/tmp/a\"]" and i.comment == "mydoc" } @parser.scan_for_realize(@class,create_stmt) end it "should register recursively includes to the current container" do @code.stubs(:children).returns([ create_stmt ]) @class.expects(:add_realize).with { |i| i.is_a?(RDoc::Include) and i.name == "File[\"/tmp/a\"]" and i.comment == "mydoc" } @parser.scan_for_realize(@class, [@code]) end end describe "when scanning for variable definition" do before :each do @class = stub_everything 'class' @stmt = stub_everything 'stmt', :name => "myvar", :value => "myvalue", :doc => "mydoc" @stmt.stubs(:is_a?).with(Puppet::Parser::AST::ASTArray).returns(false) @stmt.stubs(:is_a?).with(Puppet::Parser::AST::VarDef).returns(true) @code = stub_everything 'code' @code.stubs(:is_a?).with(Puppet::Parser::AST::ASTArray).returns(true) end it "should recursively register variables to the current container" do @code.stubs(:children).returns([ @stmt ]) @class.expects(:add_constant).with { |i| i.is_a?(RDoc::Constant) and i.name == "myvar" and i.comment == "mydoc" } @parser.scan_for_vardef(@class, [ @code ]) end it "should also scan mono-instruction code" do @class.expects(:add_constant).with { |i| i.is_a?(RDoc::Constant) and i.name == "myvar" and i.comment == "mydoc" } @parser.scan_for_vardef(@class, @stmt) end end describe "when scanning for resources" do before :each do @class = stub_everything 'class' @stmt = Puppet::Parser::AST::Resource.new( :type => "File", :instances => Puppet::Parser::AST::ASTArray.new(:children => [ Puppet::Parser::AST::ResourceInstance.new( :title => Puppet::Parser::AST::Name.new(:value => "myfile"), :parameters => Puppet::Parser::AST::ASTArray.new(:children => []) ) ]), :doc => 'mydoc' ) @code = stub_everything 'code' @code.stubs(:is_a?).with(Puppet::Parser::AST::ASTArray).returns(true) end it "should register a PuppetResource to the current container" do @code.stubs(:children).returns([ @stmt ]) @class.expects(:add_resource).with { |i| i.is_a?(RDoc::PuppetResource) and i.title == "myfile" and i.comment == "mydoc" } @parser.scan_for_resource(@class, [ @code ]) end it "should also scan mono-instruction code" do @class.expects(:add_resource).with { |i| i.is_a?(RDoc::PuppetResource) and i.title == "myfile" and i.comment == "mydoc" } @parser.scan_for_resource(@class, @stmt) end end describe "when parsing plugins" do before :each do @container = stub 'container' end it "should delegate parsing custom facts to parse_facts" do @parser = RDoc::Parser.new(@top_level, "module/manifests/lib/puppet/facter/test.rb", nil, Options.instance, RDoc::Stats.new) @parser.expects(:parse_fact).with(@container) @parser.parse_plugins(@container) end it "should delegate parsing plugins to parse_plugins" do @parser = RDoc::Parser.new(@top_level, "module/manifests/lib/puppet/functions/test.rb", nil, Options.instance, RDoc::Stats.new) @parser.expects(:parse_puppet_plugin).with(@container) @parser.parse_plugins(@container) end end describe "when parsing plugins" do before :each do @container = stub_everything 'container' end it "should add custom functions to the container" do File.stubs(:open).yields("# documentation module Puppet::Parser::Functions newfunction(:myfunc, :type => :rvalue) do |args| File.dirname(args[0]) end end".split("\n")) @container.expects(:add_plugin).with do |plugin| plugin.comment == "documentation\n" #and plugin.name == "myfunc" end @parser.parse_puppet_plugin(@container) end it "should add custom types to the container" do File.stubs(:open).yields("# documentation Puppet::Type.newtype(:mytype) do end".split("\n")) @container.expects(:add_plugin).with do |plugin| plugin.comment == "documentation\n" #and plugin.name == "mytype" end @parser.parse_puppet_plugin(@container) end end describe "when parsing facts" do before :each do @container = stub_everything 'container' File.stubs(:open).yields(["# documentation", "Facter.add('myfact') do", "confine :kernel => :linux", "end"]) end it "should add facts to the container" do @container.expects(:add_fact).with do |fact| fact.comment == "documentation\n" and fact.name == "myfact" end @parser.parse_fact(@container) end it "should add confine to the parsed facts" do ourfact = nil @container.expects(:add_fact).with do |fact| ourfact = fact true end @parser.parse_fact(@container) ourfact.confine.should == { :type => "kernel", :value => ":linux" } end end end