diff --git a/Rakefile b/Rakefile index 8e21cebd9..728f1e3de 100644 --- a/Rakefile +++ b/Rakefile @@ -1,54 +1,54 @@ # Rakefile for Puppet -*- ruby -*- $LOAD_PATH << File.join(File.dirname(__FILE__), 'tasks') require 'rake' require 'rake/packagetask' require 'rake/gempackagetask' -require 'spec' -require 'spec/rake/spectask' +require 'rspec' +require "rspec/core/rake_task" module Puppet PUPPETVERSION = File.read('lib/puppet.rb')[/PUPPETVERSION *= *'(.*)'/,1] or fail "Couldn't find PUPPETVERSION" end Dir['tasks/**/*.rake'].each { |t| load t } FILES = FileList[ '[A-Z]*', 'install.rb', 'bin/**/*', 'sbin/**/*', 'lib/**/*', 'conf/**/*', 'man/**/*', 'examples/**/*', 'ext/**/*', 'tasks/**/*', 'test/**/*', 'spec/**/*' ] Rake::PackageTask.new("puppet", Puppet::PUPPETVERSION) do |pkg| pkg.package_dir = 'pkg' pkg.need_tar_gz = true pkg.package_files = FILES.to_a end task :default do sh %{rake -T} end desc "Create the tarball and the gem - use when releasing" task :puppetpackages => [:create_gem, :package] -Spec::Rake::SpecTask.new do |t| - t.spec_opts = ['--format','s', '--loadby','mtime','--color'] +RSpec::Core::RakeTask.new do |t| + t.rspec_opts = ['--format','s', '--color'] t.pattern ='spec/{unit,integration}/**/*.rb' t.fail_on_error = false end desc "Run the unit tests" task :unit do sh "cd test; rake" end diff --git a/lib/puppet/application/apply.rb b/lib/puppet/application/apply.rb index 59a95d35a..33a70ce8a 100644 --- a/lib/puppet/application/apply.rb +++ b/lib/puppet/application/apply.rb @@ -1,173 +1,157 @@ require 'puppet/application' class Puppet::Application::Apply < Puppet::Application should_parse_config option("--debug","-d") option("--execute EXECUTE","-e") do |arg| options[:code] = arg end option("--loadclasses","-L") option("--verbose","-v") option("--use-nodes") option("--detailed-exitcodes") option("--apply catalog", "-a catalog") do |arg| options[:catalog] = arg end option("--logdest LOGDEST", "-l") do |arg| begin Puppet::Util::Log.newdestination(arg) options[:logset] = true rescue => detail $stderr.puts detail.to_s end end def run_command if options[:catalog] apply elsif Puppet[:parseonly] parseonly else main end end def apply if options[:catalog] == "-" text = $stdin.read else text = File.read(options[:catalog]) end begin catalog = Puppet::Resource::Catalog.convert_from(Puppet::Resource::Catalog.default_format,text) catalog = Puppet::Resource::Catalog.pson_create(catalog) unless catalog.is_a?(Puppet::Resource::Catalog) rescue => detail raise Puppet::Error, "Could not deserialize catalog from pson: #{detail}" end catalog = catalog.to_ral require 'puppet/configurer' configurer = Puppet::Configurer.new configurer.run :catalog => catalog end def parseonly # Set our code or file to use. if options[:code] or command_line.args.length == 0 Puppet[:code] = options[:code] || STDIN.read else Puppet[:manifest] = command_line.args.shift end begin Puppet::Node::Environment.new(Puppet[:environment]).known_resource_types rescue => detail Puppet.err detail exit 1 end exit 0 end def main # Set our code or file to use. if options[:code] or command_line.args.length == 0 Puppet[:code] = options[:code] || STDIN.read else manifest = command_line.args.shift raise "Could not find file #{manifest}" unless File.exist?(manifest) Puppet.warning("Only one file can be applied per run. Skipping #{command_line.args.join(', ')}") if command_line.args.size > 0 Puppet[:manifest] = manifest end # Collect our facts. unless facts = Puppet::Node::Facts.find(Puppet[:certname]) raise "Could not find facts for #{Puppet[:certname]}" end # Find our Node unless node = Puppet::Node.find(Puppet[:certname]) raise "Could not find node #{Puppet[:certname]}" end # Merge in the facts. node.merge(facts.values) # Allow users to load the classes that puppet agent creates. if options[:loadclasses] file = Puppet[:classfile] if FileTest.exists?(file) unless FileTest.readable?(file) $stderr.puts "#{file} is not readable" exit(63) end node.classes = File.read(file).split(/[\s\n]+/) end end begin # Compile our catalog starttime = Time.now catalog = Puppet::Resource::Catalog.find(node.name, :use_node => node) # Translate it to a RAL catalog catalog = catalog.to_ral catalog.finalize catalog.retrieval_duration = Time.now - starttime require 'puppet/configurer' configurer = Puppet::Configurer.new - configurer.execute_prerun_command + report = configurer.run(:skip_plugin_download => true, :catalog => catalog) - # And apply it - if Puppet[:report] - report = configurer.initialize_report - Puppet::Util::Log.newdestination(report) - end - transaction = catalog.apply - - configurer.execute_postrun_command - - if Puppet[:report] - Puppet::Util::Log.close(report) - configurer.send_report(report, transaction) - else - transaction.generate_report - end - - exit( Puppet[:noop] ? 0 : options[:detailed_exitcodes] ? transaction.report.exit_status : 0 ) + exit( Puppet[:noop] ? 0 : options[:detailed_exitcodes] ? report.exit_status : 0 ) rescue => detail puts detail.backtrace if Puppet[:trace] $stderr.puts detail.message exit(1) end end def setup exit(Puppet.settings.print_configs ? 0 : 1) if Puppet.settings.print_configs? # If noop is set, then also enable diffs Puppet[:show_diff] = true if Puppet[:noop] Puppet::Util::Log.newdestination(:console) unless options[:logset] client = nil server = nil trap(:INT) do $stderr.puts "Exiting" exit(1) end if options[:debug] Puppet::Util::Log.level = :debug elsif options[:verbose] Puppet::Util::Log.level = :info end end end diff --git a/lib/puppet/application/inspect.rb b/lib/puppet/application/inspect.rb index c28fef326..c7be893c7 100644 --- a/lib/puppet/application/inspect.rb +++ b/lib/puppet/application/inspect.rb @@ -1,80 +1,86 @@ require 'puppet/application' class Puppet::Application::Inspect < Puppet::Application should_parse_config run_mode :agent option("--debug","-d") option("--verbose","-v") option("--logdest LOGDEST", "-l") do |arg| begin Puppet::Util::Log.newdestination(arg) options[:logset] = true rescue => detail $stderr.puts detail.to_s end end def setup exit(Puppet.settings.print_configs ? 0 : 1) if Puppet.settings.print_configs? raise "Inspect requires reporting to be enabled. Set report=true in puppet.conf to enable reporting." unless Puppet[:report] @report = Puppet::Transaction::Report.new("inspect") Puppet::Util::Log.newdestination(@report) Puppet::Util::Log.newdestination(:console) unless options[:logset] trap(:INT) do $stderr.puts "Exiting" exit(1) end if options[:debug] Puppet::Util::Log.level = :debug elsif options[:verbose] Puppet::Util::Log.level = :info end Puppet::Transaction::Report.terminus_class = :rest Puppet::Resource::Catalog.terminus_class = :yaml end def run_command retrieval_starttime = Time.now unless catalog = Puppet::Resource::Catalog.find(Puppet[:certname]) raise "Could not find catalog for #{Puppet[:certname]}" end - retrieval_time = Time.now - retrieval_starttime - @report.add_times("config_retrieval", retrieval_time) + @report.configuration_version = catalog.version - starttime = Time.now + inspect_starttime = Time.now + @report.add_times("config_retrieval", inspect_starttime - retrieval_starttime) catalog.to_ral.resources.each do |ral_resource| audited_attributes = ral_resource[:audit] next unless audited_attributes audited_resource = ral_resource.to_resource status = Puppet::Resource::Status.new(ral_resource) audited_attributes.each do |name| - event = ral_resource.event(:previous_value => audited_resource[name], :property => name, :status => "audit", :message => "inspected value is #{audited_resource[name].inspect}") - status.add_event(event) + next if audited_resource[name].nil? + # Skip :absent properties of :absent resources. Really, it would be nicer if the RAL returned nil for those, but it doesn't. ~JW + if name == :ensure or audited_resource[:ensure] != :absent or audited_resource[name] != :absent + event = ral_resource.event(:previous_value => audited_resource[name], :property => name, :status => "audit", :message => "inspected value is #{audited_resource[name].inspect}") + status.add_event(event) + end end @report.add_resource_status(status) end - @report.add_metric(:time, {"config_retrieval" => retrieval_time, "inspect" => Time.now - starttime}) + finishtime = Time.now + @report.add_times("inspect", finishtime - inspect_starttime) + @report.finalize_report begin @report.save rescue => detail puts detail.backtrace if Puppet[:trace] Puppet.err "Could not send report: #{detail}" end end end diff --git a/lib/puppet/configurer.rb b/lib/puppet/configurer.rb index 31d31c2d2..d3c902576 100644 --- a/lib/puppet/configurer.rb +++ b/lib/puppet/configurer.rb @@ -1,239 +1,237 @@ # The client for interacting with the puppetmaster config server. require 'sync' require 'timeout' require 'puppet/network/http_pool' require 'puppet/util' class Puppet::Configurer class CommandHookError < RuntimeError; end require 'puppet/configurer/fact_handler' require 'puppet/configurer/plugin_handler' include Puppet::Configurer::FactHandler include Puppet::Configurer::PluginHandler # For benchmarking include Puppet::Util attr_reader :compile_time # Provide more helpful strings to the logging that the Agent does def self.to_s "Puppet configuration client" end class << self # Puppetd should only have one instance running, and we need a way # to retrieve it. attr_accessor :instance include Puppet::Util end # How to lock instances of this class. def self.lockfile_path Puppet[:puppetdlockfile] end def clear @catalog.clear(true) if @catalog @catalog = nil end def execute_postrun_command execute_from_setting(:postrun_command) end def execute_prerun_command execute_from_setting(:prerun_command) end # Initialize and load storage def dostorage Puppet::Util::Storage.load @compile_time ||= Puppet::Util::Storage.cache(:configuration)[:compile_time] rescue => detail puts detail.backtrace if Puppet[:trace] Puppet.err "Corrupt state file #{Puppet[:statefile]}: #{detail}" begin ::File.unlink(Puppet[:statefile]) retry rescue => detail raise Puppet::Error.new("Cannot remove #{Puppet[:statefile]}: #{detail}") end end # Just so we can specify that we are "the" instance. def initialize Puppet.settings.use(:main, :ssl, :agent) self.class.instance = self @running = false @splayed = false end - def initialize_report - Puppet::Transaction::Report.new - end - # Prepare for catalog retrieval. Downloads everything necessary, etc. - def prepare + def prepare(options) dostorage - download_plugins + download_plugins unless options[:skip_plugin_download] - download_fact_plugins + download_fact_plugins unless options[:skip_plugin_download] execute_prerun_command end # Get the remote catalog, yo. Returns nil if no catalog can be found. def retrieve_catalog if Puppet::Resource::Catalog.indirection.terminus_class == :rest # This is a bit complicated. We need the serialized and escaped facts, # and we need to know which format they're encoded in. Thus, we # get a hash with both of these pieces of information. fact_options = facts_for_uploading else fact_options = {} end # First try it with no cache, then with the cache. unless (Puppet[:use_cached_catalog] and result = retrieve_catalog_from_cache(fact_options)) or result = retrieve_new_catalog(fact_options) if ! Puppet[:usecacheonfailure] Puppet.warning "Not using cache on failed catalog" return nil end result = retrieve_catalog_from_cache(fact_options) end return nil unless result convert_catalog(result, @duration) end # Convert a plain resource catalog into our full host catalog. def convert_catalog(result, duration) catalog = result.to_ral catalog.finalize catalog.retrieval_duration = duration catalog.write_class_file catalog end # The code that actually runs the catalog. # This just passes any options on to the catalog, # which accepts :tags and :ignoreschedules. def run(options = {}) begin - prepare + prepare(options) rescue SystemExit,NoMemoryError raise rescue Exception => detail puts detail.backtrace if Puppet[:trace] Puppet.err "Failed to prepare catalog: #{detail}" end - options[:report] ||= initialize_report + options[:report] ||= Puppet::Transaction::Report.new("apply") report = options[:report] Puppet::Util::Log.newdestination(report) if catalog = options[:catalog] options.delete(:catalog) elsif ! catalog = retrieve_catalog Puppet.err "Could not retrieve catalog; skipping run" return end + report.configuration_version = catalog.version + transaction = nil begin benchmark(:notice, "Finished catalog run") do transaction = catalog.apply(options) end report rescue => detail puts detail.backtrace if Puppet[:trace] Puppet.err "Failed to apply catalog: #{detail}" return end ensure # Make sure we forget the retained module_directories of any autoload # we might have used. Thread.current[:env_module_directories] = nil # Now close all of our existing http connections, since there's no # reason to leave them lying open. Puppet::Network::HttpPool.clear_http_instances execute_postrun_command Puppet::Util::Log.close(report) send_report(report, transaction) end - def send_report(report, trans = nil) - trans.generate_report if trans + def send_report(report, trans) + report.finalize_report if trans puts report.summary if Puppet[:summarize] report.save if Puppet[:report] rescue => detail puts detail.backtrace if Puppet[:trace] Puppet.err "Could not send report: #{detail}" end private def self.timeout timeout = Puppet[:configtimeout] case timeout when String if timeout =~ /^\d+$/ timeout = Integer(timeout) else raise ArgumentError, "Configuration timeout must be an integer" end when Integer # nothing else raise ArgumentError, "Configuration timeout must be an integer" end timeout end def execute_from_setting(setting) return if (command = Puppet[setting]) == "" begin Puppet::Util.execute([command]) rescue => detail raise CommandHookError, "Could not run command from #{setting}: #{detail}" end end def retrieve_catalog_from_cache(fact_options) result = nil @duration = thinmark do result = Puppet::Resource::Catalog.find(Puppet[:certname], fact_options.merge(:ignore_terminus => true)) end Puppet.notice "Using cached catalog" result rescue => detail puts detail.backtrace if Puppet[:trace] Puppet.err "Could not retrieve catalog from cache: #{detail}" return nil end def retrieve_new_catalog(fact_options) result = nil @duration = thinmark do result = Puppet::Resource::Catalog.find(Puppet[:certname], fact_options.merge(:ignore_cache => true)) end result rescue SystemExit,NoMemoryError raise rescue Exception => detail puts detail.backtrace if Puppet[:trace] Puppet.err "Could not retrieve catalog from remote server: #{detail}" return nil end end diff --git a/lib/puppet/parser/ast.rb b/lib/puppet/parser/ast.rb index 54e034acb..a5aaeddc4 100644 --- a/lib/puppet/parser/ast.rb +++ b/lib/puppet/parser/ast.rb @@ -1,134 +1,138 @@ # the parent class for all of our syntactical objects require 'puppet' require 'puppet/util/autoload' require 'puppet/file_collection/lookup' # The base class for all of the objects that make up the parse trees. # Handles things like file name, line #, and also does the initialization # for all of the parameters of all of the child objects. class Puppet::Parser::AST # Do this so I don't have to type the full path in all of the subclasses AST = Puppet::Parser::AST include Puppet::FileCollection::Lookup include Puppet::Util::Errors include Puppet::Util::MethodHelper include Puppet::Util::Docs attr_accessor :parent, :scope + def inspect + "( #{self.class} #{self.to_s} #{@children.inspect} )" + end + # don't fetch lexer comment by default def use_docs self.class.use_docs end # allow our subclass to specify they want documentation class << self attr_accessor :use_docs def associates_doc self.use_docs = true end end # Does this ast object set something? If so, it gets evaluated first. def self.settor? if defined?(@settor) @settor else false end end # Evaluate the current object. Just a stub method, since the subclass # should override this method. # of the contained children and evaluates them in turn, returning a # list of all of the collected values, rejecting nil values def evaluate(*options) raise Puppet::DevError, "Did not override #evaluate in #{self.class}" end # Throw a parse error. def parsefail(message) self.fail(Puppet::ParseError, message) end # Wrap a statemp in a reusable way so we always throw a parse error. def parsewrap exceptwrap :type => Puppet::ParseError do yield end end # The version of the evaluate method that should be called, because it # correctly handles errors. It is critical to use this method because # it can enable you to catch the error where it happens, rather than # much higher up the stack. def safeevaluate(*options) # We duplicate code here, rather than using exceptwrap, because this # is called so many times during parsing. begin return self.evaluate(*options) rescue Puppet::Error => detail raise adderrorcontext(detail) rescue => detail error = Puppet::Error.new(detail.to_s) # We can't use self.fail here because it always expects strings, # not exceptions. raise adderrorcontext(error, detail) end end # Initialize the object. Requires a hash as the argument, and # takes each of the parameters of the hash and calls the settor # method for them. This is probably pretty inefficient and should # likely be changed at some point. def initialize(args) set_options(args) end # evaluate ourselves, and match def evaluate_match(value, scope) obj = self.safeevaluate(scope) obj = obj.downcase if obj.respond_to?(:downcase) value = value.downcase if value.respond_to?(:downcase) obj = Puppet::Parser::Scope.number?(obj) || obj value = Puppet::Parser::Scope.number?(value) || value # "" == undef for case/selector/if obj == value or (obj == "" and value == :undef) end end # And include all of the AST subclasses. require 'puppet/parser/ast/arithmetic_operator' require 'puppet/parser/ast/astarray' require 'puppet/parser/ast/asthash' require 'puppet/parser/ast/branch' require 'puppet/parser/ast/boolean_operator' require 'puppet/parser/ast/caseopt' 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/else' require 'puppet/parser/ast/function' require 'puppet/parser/ast/ifstatement' require 'puppet/parser/ast/in_operator' require 'puppet/parser/ast/leaf' require 'puppet/parser/ast/match_operator' require 'puppet/parser/ast/minus' require 'puppet/parser/ast/nop' require 'puppet/parser/ast/not' require 'puppet/parser/ast/resource' require 'puppet/parser/ast/resource_defaults' require 'puppet/parser/ast/resource_override' require 'puppet/parser/ast/resource_reference' require 'puppet/parser/ast/resourceparam' require 'puppet/parser/ast/selector' require 'puppet/parser/ast/tag' require 'puppet/parser/ast/vardef' require 'puppet/parser/ast/relationship' diff --git a/lib/puppet/relationship.rb b/lib/puppet/relationship.rb index 7079fb44b..08d7d042b 100644 --- a/lib/puppet/relationship.rb +++ b/lib/puppet/relationship.rb @@ -1,94 +1,98 @@ #!/usr/bin/env ruby # # Created by Luke A. Kanies on 2006-11-24. # Copyright (c) 2006. All rights reserved. # subscriptions are permanent associations determining how different # objects react to an event require 'puppet/util/pson' # This is Puppet's class for modeling edges in its configuration graph. # It used to be a subclass of GRATR::Edge, but that class has weird hash # overrides that dramatically slow down the graphing. class Puppet::Relationship extend Puppet::Util::Pson attr_accessor :source, :target, :callback attr_reader :event def self.from_pson(pson) source = pson["source"] target = pson["target"] args = {} if event = pson["event"] args[:event] = event end if callback = pson["callback"] args[:callback] = callback end new(source, target, args) end def event=(event) raise ArgumentError, "You must pass a callback for non-NONE events" if event != :NONE and ! callback @event = event end def initialize(source, target, options = {}) @source, @target = source, target options = (options || {}).inject({}) { |h,a| h[a[0].to_sym] = a[1]; h } [:callback, :event].each do |option| if value = options[option] send(option.to_s + "=", value) end end end # Does the passed event match our event? This is where the meaning # of :NONE comes from. def match?(event) if self.event.nil? or event == :NONE or self.event == :NONE return false elsif self.event == :ALL_EVENTS or event == self.event return true else return false end end def label result = {} result[:callback] = callback if callback result[:event] = event if event result end def ref "#{source} => #{target}" end + def inspect + "{ #{source} => #{target} }" + end + def to_pson_data_hash data = { 'source' => source.to_s, 'target' => target.to_s } ["event", "callback"].each do |attr| next unless value = send(attr) data[attr] = value end data end def to_pson(*args) to_pson_data_hash.to_pson(*args) end def to_s ref end end diff --git a/lib/puppet/resource.rb b/lib/puppet/resource.rb index 4f0d50750..b0a3ecee6 100644 --- a/lib/puppet/resource.rb +++ b/lib/puppet/resource.rb @@ -1,416 +1,420 @@ require 'puppet' require 'puppet/util/tagging' require 'puppet/util/pson' # The simplest resource class. Eventually it will function as the # base class for all resource-like behaviour. class Puppet::Resource include Puppet::Util::Tagging require 'puppet/resource/type_collection_helper' include Puppet::Resource::TypeCollectionHelper extend Puppet::Util::Pson include Enumerable attr_accessor :file, :line, :catalog, :exported, :virtual, :validate_parameters, :strict attr_reader :type, :title require 'puppet/indirector' extend Puppet::Indirector indirects :resource, :terminus_class => :ral ATTRIBUTES = [:file, :line, :exported] def self.from_pson(pson) raise ArgumentError, "No resource type provided in pson data" unless type = pson['type'] raise ArgumentError, "No resource title provided in pson data" unless title = pson['title'] resource = new(type, title) if params = pson['parameters'] params.each { |param, value| resource[param] = value } end if tags = pson['tags'] tags.each { |tag| resource.tag(tag) } end ATTRIBUTES.each do |a| if value = pson[a.to_s] resource.send(a.to_s + "=", value) end end resource.exported ||= false resource end + def inspect + "#{@type}[#{@title}]#{to_hash.inspect}" + end + def to_pson_data_hash data = ([:type, :title, :tags] + ATTRIBUTES).inject({}) do |hash, param| next hash unless value = self.send(param) hash[param.to_s] = value hash end data["exported"] ||= false params = self.to_hash.inject({}) do |hash, ary| param, value = ary # Don't duplicate the title as the namevar next hash if param == namevar and value == title hash[param] = Puppet::Resource.value_to_pson_data(value) hash end data["parameters"] = params unless params.empty? data end def self.value_to_pson_data(value) if value.is_a? Array value.map{|v| value_to_pson_data(v) } elsif value.is_a? Puppet::Resource value.to_s else value end end def yaml_property_munge(x) case x when Hash x.inject({}) { |h,kv| k,v = kv h[k] = self.class.value_to_pson_data(v) h } else self.class.value_to_pson_data(x) end end def to_pson(*args) to_pson_data_hash.to_pson(*args) end # Proxy these methods to the parameters hash. It's likely they'll # be overridden at some point, but this works for now. %w{has_key? keys length delete empty? <<}.each do |method| define_method(method) do |*args| @parameters.send(method, *args) end end # Set a given parameter. Converts all passed names # to lower-case symbols. def []=(param, value) validate_parameter(param) if validate_parameters @parameters[parameter_name(param)] = value end # Return a given parameter's value. Converts all passed names # to lower-case symbols. def [](param) @parameters[parameter_name(param)] end def ==(other) return false unless other.respond_to?(:title) and self.type == other.type and self.title == other.title return false unless to_hash == other.to_hash true end # Compatibility method. def builtin? builtin_type? end # Is this a builtin resource type? def builtin_type? resource_type.is_a?(Class) end # Iterate over each param/value pair, as required for Enumerable. def each @parameters.each { |p,v| yield p, v } end def include?(parameter) super || @parameters.keys.include?( parameter_name(parameter) ) end # These two methods are extracted into a Helper # module, but file load order prevents me # from including them in the class, and I had weird # behaviour (i.e., sometimes it didn't work) when # I directly extended each resource with the helper. def environment Puppet::Node::Environment.new(@environment) end def environment=(env) if env.is_a?(String) or env.is_a?(Symbol) @environment = env else @environment = env.name end end %w{exported virtual strict}.each do |m| define_method(m+"?") do self.send(m) end end # This stub class is only needed for serialization compatibility with 0.25.x class Reference attr_accessor :type,:title def initialize(type,title) @type,@title = type,title end end # Create our resource. def initialize(type, title = nil, attributes = {}) @parameters = {} # Set things like strictness first. attributes.each do |attr, value| next if attr == :parameters send(attr.to_s + "=", value) end @type, @title = extract_type_and_title(type, title) @type = munge_type_name(@type) if @type == "Class" @title = :main if @title == "" @title = munge_type_name(@title) end if params = attributes[:parameters] extract_parameters(params) end tag(self.type) tag(self.title) if valid_tag?(self.title) @reference = Reference.new(@type,@title) # for serialization compatibility with 0.25.x raise ArgumentError, "Invalid resource type #{type}" if strict? and ! resource_type end def ref to_s end # Find our resource. def resolve return(catalog ? catalog.resource(to_s) : nil) end def resource_type case type when "Class"; known_resource_types.hostclass(title == :main ? "" : title) when "Node"; known_resource_types.node(title) else Puppet::Type.type(type.to_s.downcase.to_sym) || known_resource_types.definition(type) end end # Produce a simple hash of our parameters. def to_hash parse_title.merge @parameters end def to_s "#{type}[#{title}]" end def uniqueness_key # Temporary kludge to deal with inconsistant use patters h = self.to_hash h[namevar] ||= h[:name] h[:name] ||= h[namevar] h.values_at(*key_attributes.sort_by { |k| k.to_s }) end def key_attributes return(resource_type.respond_to? :key_attributes) ? resource_type.key_attributes : [:name] end # Convert our resource to Puppet code. def to_manifest "%s { '%s':\n%s\n}" % [self.type.to_s.downcase, self.title, @parameters.collect { |p, v| if v.is_a? Array " #{p} => [\'#{v.join("','")}\']" else " #{p} => \'#{v}\'" end }.join(",\n") ] end def to_ref ref end # Convert our resource to a RAL resource instance. Creates component # instances for resource types that don't exist. def to_ral if typeklass = Puppet::Type.type(self.type) return typeklass.new(self) else return Puppet::Type::Component.new(self) end end # Translate our object to a backward-compatible transportable object. def to_trans if builtin_type? and type.downcase.to_s != "stage" result = to_transobject else result = to_transbucket end result.file = self.file result.line = self.line result end def to_trans_ref [type.to_s, title.to_s] end # Create an old-style TransObject instance, for builtin resource types. def to_transobject # Now convert to a transobject result = Puppet::TransObject.new(title, type) to_hash.each do |p, v| if v.is_a?(Puppet::Resource) v = v.to_trans_ref elsif v.is_a?(Array) v = v.collect { |av| av = av.to_trans_ref if av.is_a?(Puppet::Resource) av } end # If the value is an array with only one value, then # convert it to a single value. This is largely so that # the database interaction doesn't have to worry about # whether it returns an array or a string. result[p.to_s] = if v.is_a?(Array) and v.length == 1 v[0] else v end end result.tags = self.tags result end def name # this is potential namespace conflict # between the notion of an "indirector name" # and a "resource name" [ type, title ].join('/') end def to_resource self end def valid_parameter?(name) resource_type.valid_parameter?(name) end def validate_parameter(name) raise ArgumentError, "Invalid parameter #{name}" unless valid_parameter?(name) end private # Produce a canonical method name. def parameter_name(param) param = param.to_s.downcase.to_sym if param == :name and n = namevar param = namevar end param end # The namevar for our resource type. If the type doesn't exist, # always use :name. def namevar if builtin_type? and t = resource_type and t.key_attributes.length == 1 t.key_attributes.first else :name end end # Create an old-style TransBucket instance, for non-builtin resource types. def to_transbucket bucket = Puppet::TransBucket.new([]) bucket.type = self.type bucket.name = self.title # TransBuckets don't support parameters, which is why they're being deprecated. bucket end def extract_parameters(params) params.each do |param, value| validate_parameter(param) if strict? self[param] = value end end def extract_type_and_title(argtype, argtitle) if (argtitle || argtype) =~ /^([^\[\]]+)\[(.+)\]$/m then [ $1, $2 ] elsif argtitle then [ argtype, argtitle ] elsif argtype.is_a?(Puppet::Type) then [ argtype.class.name, argtype.title ] elsif argtype.is_a?(Hash) then raise ArgumentError, "Puppet::Resource.new does not take a hash as the first argument. "+ "Did you mean (#{(argtype[:type] || argtype["type"]).inspect}, #{(argtype[:title] || argtype["title"]).inspect }) ?" else raise ArgumentError, "No title provided and #{argtype.inspect} is not a valid resource reference" end end def munge_type_name(value) return :main if value == :main return "Class" if value == "" or value.nil? or value.to_s.downcase == "component" value.to_s.split("::").collect { |s| s.capitalize }.join("::") end def parse_title h = {} type = resource_type if type.respond_to? :title_patterns type.title_patterns.each { |regexp, symbols_and_lambdas| if captures = regexp.match(title.to_s) symbols_and_lambdas.zip(captures[1..-1]).each { |symbol_and_lambda,capture| sym, lam = symbol_and_lambda #self[sym] = lam.call(capture) h[sym] = lam.call(capture) } return h end } else return { :name => title.to_s } end end end diff --git a/lib/puppet/resource/status.rb b/lib/puppet/resource/status.rb index 2bdfbbfef..ee83004bb 100644 --- a/lib/puppet/resource/status.rb +++ b/lib/puppet/resource/status.rb @@ -1,58 +1,77 @@ module Puppet class Resource class Status include Puppet::Util::Tagging include Puppet::Util::Logging - ATTRIBUTES = [:resource, :node, :version, :file, :line, :current_values, :skipped_reason, :status, :evaluation_time, :change_count] - attr_accessor *ATTRIBUTES + attr_accessor :resource, :node, :file, :line, :current_values, :status, :evaluation_time STATES = [:skipped, :failed, :failed_to_restart, :restarted, :changed, :out_of_sync, :scheduled] attr_accessor *STATES attr_reader :source_description, :default_log_level, :time, :resource + attr_reader :change_count, :out_of_sync_count, :resource_type, :title + + YAML_ATTRIBUTES = %w{@resource @file @line @evaluation_time @change_count @out_of_sync_count @tags @time @events @out_of_sync @changed @resource_type @title} # Provide a boolean method for each of the states. STATES.each do |attr| define_method("#{attr}?") do !! send(attr) end end def <<(event) add_event(event) self end def add_event(event) @events << event if event.status == 'failure' self.failed = true + elsif event.status == 'success' + @change_count += 1 + @changed = true + end + if event.status != 'audit' + @out_of_sync_count += 1 + @out_of_sync = true end end def events @events end def initialize(resource) @source_description = resource.path @resource = resource.to_s + @change_count = 0 + @out_of_sync_count = 0 + @changed = false + @out_of_sync = false - [:file, :line, :version].each do |attr| + [:file, :line].each do |attr| send(attr.to_s + "=", resource.send(attr)) end tag(*resource.tags) @time = Time.now @events = [] + @resource_type = resource.type.to_s.capitalize + @title = resource.title + end + + def to_yaml_properties + (YAML_ATTRIBUTES & instance_variables).sort end private def log_source source_description end end end end diff --git a/lib/puppet/resource/type_collection.rb b/lib/puppet/resource/type_collection.rb index 6a03458b3..277d37b18 100644 --- a/lib/puppet/resource/type_collection.rb +++ b/lib/puppet/resource/type_collection.rb @@ -1,213 +1,217 @@ class Puppet::Resource::TypeCollection attr_reader :environment def clear @hostclasses.clear @definitions.clear @nodes.clear end def initialize(env) @environment = env.is_a?(String) ? Puppet::Node::Environment.new(env) : env @hostclasses = {} @definitions = {} @nodes = {} # So we can keep a list and match the first-defined regex @node_list = [] @watched_files = {} end + def inspect + "TypeCollection" + { :hostclasses => @hostclasses.keys, :definitions => @definitions.keys, :nodes => @nodes.keys }.inspect + end + def <<(thing) add(thing) self end def add(instance) if instance.type == :hostclass and other = @hostclasses[instance.name] and other.type == :hostclass other.merge(instance) return other end method = "add_#{instance.type}" send(method, instance) instance.resource_type_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(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 loader require 'puppet/parser/type_loader' @loader ||= Puppet::Parser::TypeLoader.new(environment) end def node(name) name = munge_name(name) if node = @nodes[name] return node end @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[munge_name(name)] end def nodes? @nodes.length > 0 end def add_definition(instance) dupe_check(instance, @hostclasses) { |dupe| "'#{instance.name}' is already defined#{dupe.error_context} as a class; cannot redefine as a definition" } dupe_check(instance, @definitions) { |dupe| "Definition '#{instance.name}' is already defined#{dupe.error_context}; cannot be redefined" } @definitions[instance.name] = instance end def definition(name) @definitions[munge_name(name)] end def find(namespaces, name, type) #Array("") == [] for some reason namespaces = [namespaces] unless namespaces.is_a?(Array) if name =~ /^::/ return send(type, name.sub(/^::/, '')) end namespaces.each do |namespace| ary = namespace.split("::") while ary.length > 0 tmp_namespace = ary.join("::") if r = find_partially_qualified(tmp_namespace, name, type) return r end # Delete the second to last object, which reduces our namespace by one. ary.pop end if result = send(type, name) return result end end nil end def find_or_load(namespaces, name, type) name = name.downcase namespaces = [namespaces] unless namespaces.is_a?(Array) namespaces = namespaces.collect { |ns| ns.downcase } # This could be done in the load_until, but the knowledge seems to # belong here. if r = find(namespaces, name, type) return r end loader.load_until(namespaces, name) { find(namespaces, name, type) } end def find_node(namespaces, name) find("", name, :node) end def find_hostclass(namespaces, name) find_or_load(namespaces, name, :hostclass) end def find_definition(namespaces, name) find_or_load(namespaces, name, :definition) end [:hostclasses, :nodes, :definitions].each do |m| define_method(m) do instance_variable_get("@#{m}").dup end end def perform_initial_import parser = Puppet::Parser::Parser.new(environment) if code = Puppet.settings.uninterpolated_value(:code, environment.to_s) and code != "" parser.string = code else file = Puppet.settings.value(:manifest, environment.to_s) return unless File.exist?(file) parser.file = file end parser.parse rescue => detail msg = "Could not parse for environment #{environment}: #{detail}" error = Puppet::Error.new(msg) error.set_backtrace(detail.backtrace) raise error end def stale? @watched_files.values.detect { |file| file.changed? } end def version return @version if defined?(@version) if environment[:config_version] == "" @version = Time.now.to_i return @version end @version = Puppet::Util.execute([environment[:config_version]]).strip rescue Puppet::ExecutionFailure => e raise Puppet::ParseError, "Unable to set config_version: #{e.message}" end def watch_file(file) @watched_files[file] = Puppet::Util::LoadedFile.new(file) end def watching_file?(file) @watched_files.include?(file) end private def find_partially_qualified(namespace, name, type) send(type, [namespace, name].join("::")) end def munge_name(name) name.to_s.downcase end 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/simple_graph.rb b/lib/puppet/simple_graph.rb index 55b39fadf..c5dac0f6c 100644 --- a/lib/puppet/simple_graph.rb +++ b/lib/puppet/simple_graph.rb @@ -1,448 +1,452 @@ # Created by Luke A. Kanies on 2007-11-07. # Copyright (c) 2007. All rights reserved. require 'puppet/external/dot' require 'puppet/relationship' require 'set' # A hopefully-faster graph class to replace the use of GRATR. class Puppet::SimpleGraph # An internal class for handling a vertex's edges. class VertexWrapper attr_accessor :in, :out, :vertex # Remove all references to everything. def clear @adjacencies[:in].clear @adjacencies[:out].clear @vertex = nil end def initialize(vertex) @vertex = vertex @adjacencies = {:in => {}, :out => {}} end # Find adjacent vertices or edges. def adjacent(options) direction = options[:direction] || :out options[:type] ||= :vertices return send(direction.to_s + "_edges") if options[:type] == :edges @adjacencies[direction].keys.reject { |vertex| @adjacencies[direction][vertex].empty? } end # Add an edge to our list. def add_edge(direction, edge) opposite_adjacencies(direction, edge) << edge end # Return all known edges. def edges in_edges + out_edges end # Test whether we share an edge with a given vertex. def has_edge?(direction, vertex) return(vertex_adjacencies(direction, vertex).length > 0 ? true : false) end # Create methods for returning the degree and edges. [:in, :out].each do |direction| # LAK:NOTE If you decide to create methods for directly # testing the degree, you'll have to get the values and flatten # the results -- you might have duplicate edges, which can give # a false impression of what the degree is. That's just # as expensive as just getting the edge list, so I've decided # to only add this method. define_method("#{direction}_edges") do @adjacencies[direction].values.inject([]) { |total, adjacent| total += adjacent.to_a; total } end end # The other vertex in the edge. def other_vertex(direction, edge) case direction when :in; edge.source else edge.target end end # Remove an edge from our list. Assumes that we've already checked # that the edge is valid. def remove_edge(direction, edge) opposite_adjacencies(direction, edge).delete(edge) end def to_s vertex.to_s end + def inspect + { :@adjacencies => @adjacencies, :@vertex => @vertex.to_s }.inspect + end + private # These methods exist so we don't need a Hash with a default proc. # Look up the adjacencies for a vertex at the other end of an # edge. def opposite_adjacencies(direction, edge) opposite_vertex = other_vertex(direction, edge) vertex_adjacencies(direction, opposite_vertex) end # Look up the adjacencies for a given vertex. def vertex_adjacencies(direction, vertex) @adjacencies[direction][vertex] ||= Set.new @adjacencies[direction][vertex] end end def initialize @vertices = {} @edges = [] end # Clear our graph. def clear @vertices.each { |vertex, wrapper| wrapper.clear } @vertices.clear @edges.clear end # Which resources a given resource depends upon. def dependents(resource) tree_from_vertex(resource).keys end # Which resources depend upon the given resource. def dependencies(resource) # Cache the reversal graph, because it's somewhat expensive # to create. @reversal ||= reversal # Strangely, it's significantly faster to search a reversed # tree in the :out direction than to search a normal tree # in the :in direction. @reversal.tree_from_vertex(resource, :out).keys end # Whether our graph is directed. Always true. Used to produce dot files. def directed? true end # Determine all of the leaf nodes below a given vertex. def leaves(vertex, direction = :out) tree = tree_from_vertex(vertex, direction) l = tree.keys.find_all { |c| adjacent(c, :direction => direction).empty? } end # Collect all of the edges that the passed events match. Returns # an array of edges. def matching_edges(event, base = nil) source = base || event.resource unless vertex?(source) Puppet.warning "Got an event from invalid vertex #{source.ref}" return [] end # Get all of the edges that this vertex should forward events # to, which is the same thing as saying all edges directly below # This vertex in the graph. adjacent(source, :direction => :out, :type => :edges).find_all do |edge| edge.match?(event.name) end end # Return a reversed version of this graph. def reversal result = self.class.new vertices.each { |vertex| result.add_vertex(vertex) } edges.each do |edge| newedge = edge.class.new(edge.target, edge.source, edge.label) result.add_edge(newedge) end result end # Return the size of the graph. def size @vertices.length end # Return the graph as an array. def to_a @vertices.keys end # Provide a topological sort. def topsort degree = {} zeros = [] result = [] # Collect each of our vertices, with the number of in-edges each has. @vertices.each do |name, wrapper| edges = wrapper.in_edges zeros << wrapper if edges.length == 0 degree[wrapper.vertex] = edges end # Iterate over each 0-degree vertex, decrementing the degree of # each of its out-edges. while wrapper = zeros.pop result << wrapper.vertex wrapper.out_edges.each do |edge| degree[edge.target].delete(edge) zeros << @vertices[edge.target] if degree[edge.target].length == 0 end end # If we have any vertices left with non-zero in-degrees, then we've found a cycle. if cycles = degree.find_all { |vertex, edges| edges.length > 0 } and cycles.length > 0 message = cycles.collect { |vertex, edges| edges.collect { |e| e.to_s }.join(", ") }.join(", ") raise Puppet::Error, "Found dependency cycles in the following relationships: #{message}; try using the '--graph' option and open the '.dot' files in OmniGraffle or GraphViz" end result end # Add a new vertex to the graph. def add_vertex(vertex) @reversal = nil return false if vertex?(vertex) setup_vertex(vertex) true # don't return the VertexWrapper instance. end # Remove a vertex from the graph. def remove_vertex!(vertex) return nil unless vertex?(vertex) @vertices[vertex].edges.each { |edge| remove_edge!(edge) } @edges -= @vertices[vertex].edges @vertices[vertex].clear @vertices.delete(vertex) end # Test whether a given vertex is in the graph. def vertex?(vertex) @vertices.include?(vertex) end # Return a list of all vertices. def vertices @vertices.keys end # Add a new edge. The graph user has to create the edge instance, # since they have to specify what kind of edge it is. def add_edge(source, target = nil, label = nil) @reversal = nil if target edge = Puppet::Relationship.new(source, target, label) else edge = source end [edge.source, edge.target].each { |vertex| setup_vertex(vertex) unless vertex?(vertex) } @vertices[edge.source].add_edge :out, edge @vertices[edge.target].add_edge :in, edge @edges << edge true end # Find a matching edge. Note that this only finds the first edge, # not all of them or whatever. def edge(source, target) @edges.each_with_index { |test_edge, index| return test_edge if test_edge.source == source and test_edge.target == target } end def edge_label(source, target) return nil unless edge = edge(source, target) edge.label end # Is there an edge between the two vertices? def edge?(source, target) return false unless vertex?(source) and vertex?(target) @vertices[source].has_edge?(:out, target) end def edges @edges.dup end # Remove an edge from our graph. def remove_edge!(edge) @vertices[edge.source].remove_edge(:out, edge) @vertices[edge.target].remove_edge(:in, edge) @edges.delete(edge) nil end # Find adjacent edges. def adjacent(vertex, options = {}) return [] unless wrapper = @vertices[vertex] wrapper.adjacent(options) end private # An internal method that skips the validation, so we don't have # duplicate validation calls. def setup_vertex(vertex) @vertices[vertex] = VertexWrapper.new(vertex) end public # # For some reason, unconnected vertices do not show up in # # this graph. # def to_jpg(path, name) # gv = vertices # Dir.chdir(path) do # induced_subgraph(gv).write_to_graphic_file('jpg', name) # end # end # Take container information from another graph and use it # to replace any container vertices with their respective leaves. # This creates direct relationships where there were previously # indirect relationships through the containers. def splice!(other, type) # We have to get the container list via a topological sort on the # configuration graph, because otherwise containers that contain # other containers will add those containers back into the # graph. We could get a similar affect by only setting relationships # to container leaves, but that would result in many more # relationships. stage_class = Puppet::Type.type(:stage) whit_class = Puppet::Type.type(:whit) containers = other.topsort.find_all { |v| (v.is_a?(type) or v.is_a?(stage_class)) and vertex?(v) } containers.each do |container| # Get the list of children from the other graph. children = other.adjacent(container, :direction => :out) # MQR TODO: Luke suggests that it should be possible to refactor the system so that # container nodes are retained, thus obviating the need for the whit. children = [whit_class.new(:name => container.name, :catalog => other)] if children.empty? # First create new edges for each of the :in edges [:in, :out].each do |dir| edges = adjacent(container, :direction => dir, :type => :edges) edges.each do |edge| children.each do |child| if dir == :in s = edge.source t = child else s = child t = edge.target end add_edge(s, t, edge.label) end # Now get rid of the edge, so remove_vertex! works correctly. remove_edge!(edge) end end remove_vertex!(container) end end # Just walk the tree and pass each edge. def walk(source, direction) # Use an iterative, breadth-first traversal of the graph. One could do # this recursively, but Ruby's slow function calls and even slower # recursion make the shorter, recursive algorithm cost-prohibitive. stack = [source] seen = Set.new until stack.empty? node = stack.shift next if seen.member? node connected = adjacent(node, :direction => direction) connected.each do |target| yield node, target end stack.concat(connected) seen << node end end # A different way of walking a tree, and a much faster way than the # one that comes with GRATR. def tree_from_vertex(start, direction = :out) predecessor={} walk(start, direction) do |parent, child| predecessor[child] = parent end predecessor end # LAK:FIXME This is just a paste of the GRATR code with slight modifications. # Return a DOT::DOTDigraph for directed graphs or a DOT::DOTSubgraph for an # undirected Graph. _params_ can contain any graph property specified in # rdot.rb. If an edge or vertex label is a kind of Hash then the keys # which match +dot+ properties will be used as well. def to_dot_graph (params = {}) params['name'] ||= self.class.name.gsub(/:/,'_') fontsize = params['fontsize'] ? params['fontsize'] : '8' graph = (directed? ? DOT::DOTDigraph : DOT::DOTSubgraph).new(params) edge_klass = directed? ? DOT::DOTDirectedEdge : DOT::DOTEdge vertices.each do |v| name = v.to_s params = {'name' => '"'+name+'"', 'fontsize' => fontsize, 'label' => name} v_label = v.to_s params.merge!(v_label) if v_label and v_label.kind_of? Hash graph << DOT::DOTNode.new(params) end edges.each do |e| params = {'from' => '"'+ e.source.to_s + '"', 'to' => '"'+ e.target.to_s + '"', 'fontsize' => fontsize } e_label = e.to_s params.merge!(e_label) if e_label and e_label.kind_of? Hash graph << edge_klass.new(params) end graph end # Output the dot format as a string def to_dot (params={}) to_dot_graph(params).to_s; end # Call +dotty+ for the graph which is written to the file 'graph.dot' # in the # current directory. def dotty (params = {}, dotfile = 'graph.dot') File.open(dotfile, 'w') {|f| f << to_dot(params) } system('dotty', dotfile) end # Use +dot+ to create a graphical representation of the graph. Returns the # filename of the graphics file. def write_to_graphic_file (fmt='png', dotfile='graph') src = dotfile + '.dot' dot = dotfile + '.' + fmt File.open(src, 'w') {|f| f << self.to_dot << "\n"} system( "dot -T#{fmt} #{src} -o #{dot}" ) dot end # Produce the graph files if requested. def write_graph(name) return unless Puppet[:graph] Puppet.settings.use(:graphing) file = File.join(Puppet[:graphdir], "#{name}.dot") File.open(file, "w") { |f| f.puts to_dot("name" => name.to_s.capitalize) } end end diff --git a/lib/puppet/transaction.rb b/lib/puppet/transaction.rb index dcd9aad0a..aa650eea1 100644 --- a/lib/puppet/transaction.rb +++ b/lib/puppet/transaction.rb @@ -1,361 +1,334 @@ # the class that actually walks our resource/property tree, collects the changes, # and performs them require 'puppet' require 'puppet/util/tagging' require 'puppet/application' class Puppet::Transaction - require 'puppet/transaction/change' require 'puppet/transaction/event' require 'puppet/transaction/event_manager' require 'puppet/transaction/resource_harness' require 'puppet/resource/status' attr_accessor :component, :catalog, :ignoreschedules attr_accessor :sorted_resources, :configurator # The report, once generated. attr_accessor :report # Routes and stores any events and subscriptions. attr_reader :event_manager # Handles most of the actual interacting with resources attr_reader :resource_harness include Puppet::Util include Puppet::Util::Tagging # Wraps application run state check to flag need to interrupt processing def stop_processing? Puppet::Application.stop_requested? end # Add some additional times for reporting def add_times(hash) hash.each do |name, num| report.add_times(name, num) end end # Are there any failed resources in this transaction? def any_failed? report.resource_statuses.values.detect { |status| status.failed? } end # Apply all changes for a resource def apply(resource, ancestor = nil) status = resource_harness.evaluate(resource) add_resource_status(status) event_manager.queue_events(ancestor || resource, status.events) rescue => detail resource.err "Could not evaluate: #{detail}" end # Find all of the changed resources. def changed? report.resource_statuses.values.find_all { |status| status.changed }.collect { |status| catalog.resource(status.resource) } end # Copy an important relationships from the parent to the newly-generated # child resource. def make_parent_child_relationship(resource, children) depthfirst = resource.depthfirst? children.each do |gen_child| if depthfirst edge = [gen_child, resource] else edge = [resource, gen_child] end relationship_graph.add_vertex(gen_child) unless relationship_graph.edge?(edge[1], edge[0]) relationship_graph.add_edge(*edge) else resource.debug "Skipping automatic relationship to #{gen_child}" end end end # See if the resource generates new resources at evaluation time. def eval_generate(resource) generate_additional_resources(resource, :eval_generate) end # Evaluate a single resource. def eval_resource(resource, ancestor = nil) if skip?(resource) resource_status(resource).skipped = true else eval_children_and_apply_resource(resource, ancestor) end # Check to see if there are any events queued for this resource event_manager.process_events(resource) end def eval_children_and_apply_resource(resource, ancestor = nil) resource_status(resource).scheduled = true # We need to generate first regardless, because the recursive # actions sometimes change how the top resource is applied. children = eval_generate(resource) if ! children.empty? and resource.depthfirst? children.each do |child| # The child will never be skipped when the parent isn't eval_resource(child, ancestor || resource) end end # Perform the actual changes apply(resource, ancestor) if ! children.empty? and ! resource.depthfirst? children.each do |child| eval_resource(child, ancestor || resource) end end end # This method does all the actual work of running a transaction. It # collects all of the changes, executes them, and responds to any # necessary events. def evaluate # Start logging. Puppet::Util::Log.newdestination(@report) prepare Puppet.info "Applying configuration version '#{catalog.version}'" if catalog.version begin @sorted_resources.each do |resource| next if stop_processing? if resource.is_a?(Puppet::Type::Component) Puppet.warning "Somehow left a component in the relationship graph" next end ret = nil seconds = thinmark do ret = eval_resource(resource) end resource.info "Evaluated in %0.2f seconds" % seconds if Puppet[:evaltrace] and @catalog.host_config? ret end ensure # And then close the transaction log. Puppet::Util::Log.close(@report) end Puppet.debug "Finishing transaction #{object_id}" end def events event_manager.events end def failed?(resource) s = resource_status(resource) and s.failed? end # Does this resource have any failed dependencies? def failed_dependencies?(resource) # First make sure there are no failed dependencies. To do this, # we check for failures in any of the vertexes above us. It's not # enough to check the immediate dependencies, which is why we use # a tree from the reversed graph. found_failed = false relationship_graph.dependencies(resource).each do |dep| next unless failed?(dep) resource.notice "Dependency #{dep} has failures: #{resource_status(dep).failed}" found_failed = true end found_failed end # A general method for recursively generating new resources from a # resource. def generate_additional_resources(resource, method) return [] unless resource.respond_to?(method) begin made = resource.send(method) rescue => detail puts detail.backtrace if Puppet[:trace] resource.err "Failed to generate additional resources using '#{method}': #{detail}" end return [] unless made made = [made] unless made.is_a?(Array) made.uniq.find_all do |res| begin res.tag(*resource.tags) @catalog.add_resource(res) do |r| r.finish make_parent_child_relationship(resource, [r]) # Call 'generate' recursively generate_additional_resources(r, method) end true rescue Puppet::Resource::Catalog::DuplicateResourceError res.info "Duplicate generated resource; skipping" false end end end # Collect any dynamically generated resources. This method is called # before the transaction starts. def generate list = @catalog.vertices newlist = [] while ! list.empty? list.each do |resource| newlist += generate_additional_resources(resource, :generate) end list = newlist newlist = [] end end - # Generate a transaction report. - def generate_report - @report.calculate_metrics - @report - end - # Should we ignore tags? def ignore_tags? ! (@catalog.host_config? or Puppet[:name] == "puppet") end # this should only be called by a Puppet::Type::Component resource now # and it should only receive an array def initialize(catalog) @catalog = catalog - @report = Report.new + @report = Report.new("apply", catalog.version) @event_manager = Puppet::Transaction::EventManager.new(self) @resource_harness = Puppet::Transaction::ResourceHarness.new(self) end # Prefetch any providers that support it. We don't support prefetching # types, just providers. def prefetch prefetchers = {} @catalog.vertices.each do |resource| if provider = resource.provider and provider.class.respond_to?(:prefetch) prefetchers[provider.class] ||= {} prefetchers[provider.class][resource.name] = resource end end # Now call prefetch, passing in the resources so that the provider instances can be replaced. prefetchers.each do |provider, resources| Puppet.debug "Prefetching #{provider.name} resources for #{provider.resource_type.name}" begin provider.prefetch(resources) rescue => detail puts detail.backtrace if Puppet[:trace] Puppet.err "Could not prefetch #{provider.resource_type.name} provider '#{provider.name}': #{detail}" end end end # Prepare to evaluate the resources in a transaction. def prepare # Now add any dynamically generated resources generate # Then prefetch. It's important that we generate and then prefetch, # so that any generated resources also get prefetched. prefetch # This will throw an error if there are cycles in the graph. @sorted_resources = relationship_graph.topsort end def relationship_graph catalog.relationship_graph end - # Send off the transaction report. - def send_report - begin - report = generate_report - rescue => detail - Puppet.err "Could not generate report: #{detail}" - return - end - - puts report.summary if Puppet[:summarize] - - if Puppet[:report] - begin - report.save - rescue => detail - Puppet.err "Reporting failed: #{detail}" - end - end - end - def add_resource_status(status) report.add_resource_status status end def resource_status(resource) report.resource_statuses[resource.to_s] || add_resource_status(Puppet::Resource::Status.new(resource)) end # Is the resource currently scheduled? def scheduled?(resource) self.ignoreschedules or resource_harness.scheduled?(resource_status(resource), resource) end # Should this resource be skipped? def skip?(resource) if missing_tags?(resource) resource.debug "Not tagged with #{tags.join(", ")}" elsif ! scheduled?(resource) resource.debug "Not scheduled" elsif failed_dependencies?(resource) resource.warning "Skipping because of failed dependencies" elsif resource.virtual? resource.debug "Skipping because virtual" else return false end true end # The tags we should be checking. def tags self.tags = Puppet[:tags] unless defined?(@tags) super end def handle_qualified_tags( qualified ) # The default behavior of Puppet::Util::Tagging is # to split qualified tags into parts. That would cause # qualified tags to match too broadly here. return end # Is this resource tagged appropriately? def missing_tags?(resource) return false if ignore_tags? return false if tags.empty? not resource.tagged?(*tags) end end require 'puppet/transaction/report' diff --git a/lib/puppet/transaction/change.rb b/lib/puppet/transaction/change.rb deleted file mode 100644 index d57ac1917..000000000 --- a/lib/puppet/transaction/change.rb +++ /dev/null @@ -1,75 +0,0 @@ -require 'puppet/transaction' -require 'puppet/transaction/event' - -# Handle all of the work around performing an actual change, -# including calling 'sync' on the properties and producing events. -class Puppet::Transaction::Change - attr_accessor :is, :should, :property, :proxy, :auditing, :old_audit_value - - def auditing? - auditing - end - - def initialize(property, currentvalue) - @property = property - @is = currentvalue - - @should = property.should - - @changed = false - end - - def apply - event = property.event - event.previous_value = is - event.desired_value = should - event.historical_value = old_audit_value - - if auditing? and old_audit_value != is - event.message = "audit change: previously recorded value #{property.is_to_s(old_audit_value)} has been changed to #{property.is_to_s(is)}" - event.status = "audit" - event.audited = true - brief_audit_message = " (previously recorded value was #{property.is_to_s(old_audit_value)})" - else - brief_audit_message = "" - end - - if property.insync?(is) - # nothing happens - elsif noop? - event.message = "is #{property.is_to_s(is)}, should be #{property.should_to_s(should)} (noop)#{brief_audit_message}" - event.status = "noop" - else - property.sync - event.message = [ property.change_to_s(is, should), brief_audit_message ].join - event.status = "success" - end - event - rescue => detail - puts detail.backtrace if Puppet[:trace] - event.status = "failure" - - event.message = "change from #{property.is_to_s(is)} to #{property.should_to_s(should)} failed: #{detail}" - event - ensure - event.send_log - end - - # Is our property noop? This is used for generating special events. - def noop? - @property.noop - end - - # The resource that generated this change. This is used for handling events, - # and the proxy resource is used for generated resources, since we can't - # send an event to a resource we don't have a direct relationship with. If we - # have a proxy resource, then the events will be considered to be from - # that resource, rather than us, so the graph resolution will still work. - def resource - self.proxy || @property.resource - end - - def to_s - "change #{@property.change_to_s(@is, @should)}" - end -end diff --git a/lib/puppet/transaction/event.rb b/lib/puppet/transaction/event.rb index da5b14727..cd695cff8 100644 --- a/lib/puppet/transaction/event.rb +++ b/lib/puppet/transaction/event.rb @@ -1,61 +1,66 @@ require 'puppet/transaction' require 'puppet/util/tagging' require 'puppet/util/logging' # A simple struct for storing what happens on the system. class Puppet::Transaction::Event include Puppet::Util::Tagging include Puppet::Util::Logging - ATTRIBUTES = [:name, :resource, :property, :previous_value, :desired_value, :historical_value, :status, :message, :node, :version, :file, :line, :source_description, :audited] + ATTRIBUTES = [:name, :resource, :property, :previous_value, :desired_value, :historical_value, :status, :message, :file, :line, :source_description, :audited] + YAML_ATTRIBUTES = %w{@audited @property @previous_value @desired_value @historical_value @message @name @status @time} attr_accessor *ATTRIBUTES attr_writer :tags attr_accessor :time attr_reader :default_log_level EVENT_STATUSES = %w{noop success failure audit} - def initialize(*args) - options = args.last.is_a?(Hash) ? args.pop : ATTRIBUTES.inject({}) { |hash, attr| hash[attr] = args.pop; hash } - options.each { |attr, value| send(attr.to_s + "=", value) unless value.nil? } + def initialize(options = {}) + @audited = false + options.each { |attr, value| send(attr.to_s + "=", value) } @time = Time.now end def property=(prop) @property = prop.to_s end def resource=(res) if res.respond_to?(:[]) and level = res[:loglevel] @default_log_level = level end @resource = res.to_s end def send_log super(log_level, message) end def status=(value) raise ArgumentError, "Event status can only be #{EVENT_STATUSES.join(', ')}" unless EVENT_STATUSES.include?(value) @status = value end def to_s message end + def to_yaml_properties + (YAML_ATTRIBUTES & instance_variables).sort + end + private # If it's a failure, use 'err', else use either the resource's log level (if available) # or 'notice'. def log_level status == "failure" ? :err : (@default_log_level || :notice) end # Used by the Logging module def log_source source_description || property || resource end end diff --git a/lib/puppet/transaction/report.rb b/lib/puppet/transaction/report.rb index 75c08fc7a..8e04759ad 100644 --- a/lib/puppet/transaction/report.rb +++ b/lib/puppet/transaction/report.rb @@ -1,150 +1,169 @@ require 'puppet' require 'puppet/indirector' # A class for reporting what happens on each client. Reports consist of # two types of data: Logs and Metrics. Logs are the output that each # change produces, and Metrics are all of the numerical data involved # in the transaction. class Puppet::Transaction::Report extend Puppet::Indirector indirects :report, :terminus_class => :processor - attr_reader :resource_statuses, :logs, :metrics, :host, :time, :kind + attr_accessor :configuration_version + attr_reader :resource_statuses, :logs, :metrics, :host, :time, :kind, :status # This is necessary since Marshall doesn't know how to # dump hash with default proc (see below @records) def self.default_format :yaml end def <<(msg) @logs << msg self end def add_times(name, value) @external_times[name] = value end def add_metric(name, hash) metric = Puppet::Util::Metric.new(name) hash.each do |name, value| metric.newvalue(name, value) end @metrics[metric.name] = metric metric end def add_resource_status(status) @resource_statuses[status.resource] = status end - def calculate_metrics - calculate_resource_metrics - calculate_time_metrics - calculate_change_metrics - calculate_event_metrics + def compute_status(resource_metrics, change_metric) + if (resource_metrics["failed"] || 0) > 0 + 'failed' + elsif change_metric > 0 + 'changed' + else + 'unchanged' + end + end + + def finalize_report + resource_metrics = add_metric(:resources, calculate_resource_metrics) + add_metric(:time, calculate_time_metrics) + change_metric = calculate_change_metric + add_metric(:changes, {"total" => change_metric}) + add_metric(:events, calculate_event_metrics) + @status = compute_status(resource_metrics, change_metric) end - def initialize(kind = "apply") + def initialize(kind, configuration_version=nil) @metrics = {} @logs = [] @resource_statuses = {} @external_times ||= {} @host = Puppet[:certname] @time = Time.now @kind = kind + @report_format = 2 + @puppet_version = Puppet.version + @configuration_version = configuration_version + @status = 'failed' # assume failed until the report is finalized end def name host end # Provide a summary of this report. def summary ret = "" @metrics.sort { |a,b| a[1].label <=> b[1].label }.each do |name, metric| ret += "#{metric.label}:\n" metric.values.sort { |a,b| # sort by label if a[0] == :total 1 elsif b[0] == :total -1 else a[1] <=> b[1] end }.each do |name, label, value| next if value == 0 value = "%0.2f" % value if value.is_a?(Float) ret += " %15s %s\n" % [label + ":", value] end end ret end # Based on the contents of this report's metrics, compute a single number # that represents the report. The resulting number is a bitmask where # individual bits represent the presence of different metrics. def exit_status status = 0 - status |= 2 if @metrics["changes"][:total] > 0 - status |= 4 if @metrics["resources"][:failed] > 0 + status |= 2 if @metrics["changes"]["total"] > 0 + status |= 4 if @metrics["resources"]["failed"] > 0 status end - private + def to_yaml_properties + (instance_variables - ["@external_times"]).sort + end - def calculate_change_metrics - metrics = Hash.new(0) - resource_statuses.each do |name, status| - metrics[:total] += status.change_count if status.change_count - end + private - add_metric(:changes, metrics) + def calculate_change_metric + resource_statuses.map { |name, status| status.change_count || 0 }.inject(0) { |a,b| a+b } end def calculate_event_metrics metrics = Hash.new(0) + metrics["total"] = 0 resource_statuses.each do |name, status| - metrics[:total] += status.events.length + metrics["total"] += status.events.length status.events.each do |event| metrics[event.status] += 1 end end - add_metric(:events, metrics) + metrics end def calculate_resource_metrics metrics = Hash.new(0) - metrics[:total] = resource_statuses.length + metrics["total"] = resource_statuses.length resource_statuses.each do |name, status| Puppet::Resource::Status::STATES.each do |state| - metrics[state] += 1 if status.send(state) + metrics[state.to_s] += 1 if status.send(state) end end - add_metric(:resources, metrics) + metrics end def calculate_time_metrics metrics = Hash.new(0) resource_statuses.each do |name, status| type = Puppet::Resource.new(name).type metrics[type.to_s.downcase] += status.evaluation_time if status.evaluation_time end @external_times.each do |name, value| metrics[name.to_s.downcase] = value end - add_metric(:time, metrics) + metrics["total"] = metrics.values.inject(0) { |a,b| a+b } + + metrics end end diff --git a/lib/puppet/transaction/resource_harness.rb b/lib/puppet/transaction/resource_harness.rb index c978e5545..c259d3e05 100644 --- a/lib/puppet/transaction/resource_harness.rb +++ b/lib/puppet/transaction/resource_harness.rb @@ -1,152 +1,193 @@ require 'puppet/resource/status' class Puppet::Transaction::ResourceHarness extend Forwardable def_delegators :@transaction, :relationship_graph attr_reader :transaction def allow_changes?(resource) - return true unless resource.purging? and resource.deleting? - return true unless deps = relationship_graph.dependents(resource) and ! deps.empty? and deps.detect { |d| ! d.deleting? } - - deplabel = deps.collect { |r| r.ref }.join(",") - plurality = deps.length > 1 ? "":"s" - resource.warning "#{deplabel} still depend#{plurality} on me -- not purging" - false - end - - def apply_changes(status, changes) - changes.each do |change| - status << change.apply - - cache(change.property.resource, change.property.name, change.is) if change.auditing? + if resource.purging? and resource.deleting? and deps = relationship_graph.dependents(resource) \ + and ! deps.empty? and deps.detect { |d| ! d.deleting? } + deplabel = deps.collect { |r| r.ref }.join(",") + plurality = deps.length > 1 ? "":"s" + resource.warning "#{deplabel} still depend#{plurality} on me -- not purging" + false + else + true end - status.changed = true end # Used mostly for scheduling and auditing at this point. def cached(resource, name) Puppet::Util::Storage.cache(resource)[name] end # Used mostly for scheduling and auditing at this point. def cache(resource, name, value) Puppet::Util::Storage.cache(resource)[name] = value end - def changes_to_perform(status, resource) + def perform_changes(resource) current = resource.retrieve_resource cache resource, :checked, Time.now return [] if ! allow_changes?(resource) - audited = copy_audited_parameters(resource, current) + current_values = current.to_hash + historical_values = Puppet::Util::Storage.cache(resource).dup + desired_values = resource.to_resource.to_hash + audited_params = (resource[:audit] || []).map { |p| p.to_sym } + synced_params = [] - if param = resource.parameter(:ensure) - return [] if absent_and_not_being_created?(current, param) - unless ensure_is_insync?(current, param) - audited.keys.reject{|name| name == :ensure}.each do |name| - resource.parameter(name).notice "audit change: previously recorded value #{audited[name]} has been changed to #{current[param]}" - cache(resource, name, current[param]) + # Record the current state in state.yml. + audited_params.each do |param| + cache(resource, param, current_values[param]) + end + + # Update the machine state & create logs/events + events = [] + ensure_param = resource.parameter(:ensure) + if desired_values[:ensure] && !ensure_param.insync?(current_values[:ensure]) + events << apply_parameter(ensure_param, current_values[:ensure], audited_params.include?(:ensure), historical_values[:ensure]) + synced_params << :ensure + elsif current_values[:ensure] != :absent + work_order = resource.properties # Note: only the resource knows what order to apply changes in + work_order.each do |param| + if !param.insync?(current_values[param.name]) + events << apply_parameter(param, current_values[param.name], audited_params.include?(param.name), historical_values[param.name]) + synced_params << param.name end - return [Puppet::Transaction::Change.new(param, current[:ensure])] end - return [] if ensure_should_be_absent?(current, param) end - resource.properties.reject { |param| param.name == :ensure }.select do |param| - (audited.include?(param.name) && audited[param.name] != current[param.name]) || (param.should != nil && !param_is_insync?(current, param)) - end.collect do |param| - change = Puppet::Transaction::Change.new(param, current[param.name]) - change.auditing = true if audited.include?(param.name) - change.old_audit_value = audited[param.name] - change + # Add more events to capture audit results + audited_params.each do |param_name| + if historical_values.include?(param_name) + if historical_values[param_name] != current_values[param_name] && !synced_params.include?(param_name) + event = create_change_event(resource.parameter(param_name), current_values[param_name], true, historical_values[param_name]) + event.send_log + events << event + end + else + resource.property(param_name).notice "audit change: newly-recorded value #{current_values[param_name]}" + end end + + events end - def copy_audited_parameters(resource, current) - return {} unless audit = resource[:audit] - audit = Array(audit).collect { |p| p.to_sym } - audited = {} - audit.find_all do |param| - if value = cached(resource, param) - audited[param] = value - else - resource.property(param).notice "audit change: newly-recorded recorded value #{current[param]}" - cache(resource, param, current[param]) + def create_change_event(property, current_value, do_audit, historical_value) + event = property.event + event.previous_value = current_value + event.desired_value = property.should + event.historical_value = historical_value + + if do_audit + event.audited = true + event.status = "audit" + if historical_value != current_value + event.message = "audit change: previously recorded value #{property.is_to_s(historical_value)} has been changed to #{property.is_to_s(current_value)}" end end - audited + event + end + + def apply_parameter(property, current_value, do_audit, historical_value) + event = create_change_event(property, current_value, do_audit, historical_value) + + if do_audit && historical_value && historical_value != current_value + brief_audit_message = " (previously recorded value was #{property.is_to_s(historical_value)})" + else + brief_audit_message = "" + end + + if property.noop + event.message = "current_value #{property.is_to_s(current_value)}, should be #{property.should_to_s(property.should)} (noop)#{brief_audit_message}" + event.status = "noop" + else + property.sync + event.message = [ property.change_to_s(current_value, property.should), brief_audit_message ].join + event.status = "success" + end + event + rescue => detail + puts detail.backtrace if Puppet[:trace] + event.status = "failure" + + event.message = "change from #{property.is_to_s(current_value)} to #{property.should_to_s(property.should)} failed: #{detail}" + event + ensure + event.send_log end def evaluate(resource) start = Time.now status = Puppet::Resource::Status.new(resource) - if changes = changes_to_perform(status, resource) and ! changes.empty? - status.out_of_sync = true - status.change_count = changes.length - apply_changes(status, changes) - if ! resource.noop? - cache(resource, :synced, Time.now) - resource.flush if resource.respond_to?(:flush) - end + perform_changes(resource).each do |event| + status << event end + + if status.changed? && ! resource.noop? + cache(resource, :synced, Time.now) + resource.flush if resource.respond_to?(:flush) + end + return status rescue => detail resource.fail "Could not create resource status: #{detail}" unless status puts detail.backtrace if Puppet[:trace] resource.err "Could not evaluate: #{detail}" status.failed = true return status ensure (status.evaluation_time = Time.now - start) if status end def initialize(transaction) @transaction = transaction end def scheduled?(status, resource) return true if Puppet[:ignoreschedules] return true unless schedule = schedule(resource) # We use 'checked' here instead of 'synced' because otherwise we'll # end up checking most resources most times, because they will generally # have been synced a long time ago (e.g., a file only gets updated # once a month on the server and its schedule is daily; the last sync time # will have been a month ago, so we'd end up checking every run). schedule.match?(cached(resource, :checked).to_i) end def schedule(resource) unless resource.catalog resource.warning "Cannot schedule without a schedule-containing catalog" return nil end return nil unless name = resource[:schedule] resource.catalog.resource(:schedule, name) || resource.fail("Could not find schedule #{name}") end private def absent_and_not_being_created?(current, param) current[:ensure] == :absent and param.should.nil? end def ensure_is_insync?(current, param) param.insync?(current[:ensure]) end def ensure_should_be_absent?(current, param) param.should == :absent end def param_is_insync?(current, param) param.insync?(current[param.name]) end end diff --git a/lib/puppet/type.rb b/lib/puppet/type.rb index 1b6e7dcd7..ea3944b4e 100644 --- a/lib/puppet/type.rb +++ b/lib/puppet/type.rb @@ -1,1897 +1,1897 @@ require 'puppet' require 'puppet/util/log' require 'puppet/util/metric' require 'puppet/property' require 'puppet/parameter' require 'puppet/util' require 'puppet/util/autoload' require 'puppet/metatype/manager' require 'puppet/util/errors' require 'puppet/util/log_paths' require 'puppet/util/logging' require 'puppet/util/cacher' require 'puppet/file_collection/lookup' require 'puppet/util/tagging' # see the bottom of the file for the rest of the inclusions module Puppet class Type include Puppet::Util include Puppet::Util::Errors include Puppet::Util::LogPaths include Puppet::Util::Logging include Puppet::Util::Cacher include Puppet::FileCollection::Lookup include Puppet::Util::Tagging ############################### # Code related to resource type attributes. class << self include Puppet::Util::ClassGen include Puppet::Util::Warnings attr_reader :properties end def self.states warnonce "The states method is deprecated; use properties" properties end # All parameters, in the appropriate order. The key_attributes come first, then # the provider, then the properties, and finally the params and metaparams # in the order they were specified in the files. def self.allattrs key_attributes | (parameters & [:provider]) | properties.collect { |property| property.name } | parameters | metaparams end # Retrieve an attribute alias, if there is one. def self.attr_alias(param) @attr_aliases[symbolize(param)] end # Create an alias to an existing attribute. This will cause the aliased # attribute to be valid when setting and retrieving values on the instance. def self.set_attr_alias(hash) hash.each do |new, old| @attr_aliases[symbolize(new)] = symbolize(old) end end # Find the class associated with any given attribute. def self.attrclass(name) @attrclasses ||= {} # We cache the value, since this method gets called such a huge number # of times (as in, hundreds of thousands in a given run). unless @attrclasses.include?(name) @attrclasses[name] = case self.attrtype(name) when :property; @validproperties[name] when :meta; @@metaparamhash[name] when :param; @paramhash[name] end end @attrclasses[name] end # What type of parameter are we dealing with? Cache the results, because # this method gets called so many times. def self.attrtype(attr) @attrtypes ||= {} unless @attrtypes.include?(attr) @attrtypes[attr] = case when @validproperties.include?(attr); :property when @paramhash.include?(attr); :param when @@metaparamhash.include?(attr); :meta end end @attrtypes[attr] end def self.eachmetaparam @@metaparams.each { |p| yield p.name } end # Create the 'ensure' class. This is a separate method so other types # can easily call it and create their own 'ensure' values. def self.ensurable(&block) if block_given? self.newproperty(:ensure, :parent => Puppet::Property::Ensure, &block) else self.newproperty(:ensure, :parent => Puppet::Property::Ensure) do self.defaultvalues end end end # Should we add the 'ensure' property to this class? def self.ensurable? # If the class has all three of these methods defined, then it's # ensurable. ens = [:exists?, :create, :destroy].inject { |set, method| set &&= self.public_method_defined?(method) } ens end # Deal with any options passed into parameters. def self.handle_param_options(name, options) # If it's a boolean parameter, create a method to test the value easily if options[:boolean] define_method(name.to_s + "?") do val = self[name] if val == :true or val == true return true end end end end # Is the parameter in question a meta-parameter? def self.metaparam?(param) @@metaparamhash.include?(symbolize(param)) end # Find the metaparameter class associated with a given metaparameter name. def self.metaparamclass(name) @@metaparamhash[symbolize(name)] end def self.metaparams @@metaparams.collect { |param| param.name } end def self.metaparamdoc(metaparam) @@metaparamhash[metaparam].doc end # Create a new metaparam. Requires a block and a name, stores it in the # @parameters array, and does some basic checking on it. def self.newmetaparam(name, options = {}, &block) @@metaparams ||= [] @@metaparamhash ||= {} name = symbolize(name) param = genclass( name, :parent => options[:parent] || Puppet::Parameter, :prefix => "MetaParam", :hash => @@metaparamhash, :array => @@metaparams, :attributes => options[:attributes], &block ) # Grr. param.required_features = options[:required_features] if options[:required_features] handle_param_options(name, options) param.metaparam = true param end def self.key_attribute_parameters @key_attribute_parameters ||= ( params = @parameters.find_all { |param| param.isnamevar? or param.name == :name } ) end def self.key_attributes key_attribute_parameters.collect { |p| p.name } end def self.title_patterns case key_attributes.length when 0; [] when 1; identity = lambda {|x| x} [ [ /(.*)/m, [ [key_attributes.first, identity ] ] ] ] else raise Puppet::DevError,"you must specify title patterns when there are two or more key attributes" end end def uniqueness_key to_resource.uniqueness_key end # Create a new parameter. Requires a block and a name, stores it in the # @parameters array, and does some basic checking on it. def self.newparam(name, options = {}, &block) options[:attributes] ||= {} param = genclass( name, :parent => options[:parent] || Puppet::Parameter, :attributes => options[:attributes], :block => block, :prefix => "Parameter", :array => @parameters, :hash => @paramhash ) handle_param_options(name, options) # Grr. param.required_features = options[:required_features] if options[:required_features] param.isnamevar if options[:namevar] param end def self.newstate(name, options = {}, &block) Puppet.warning "newstate() has been deprecrated; use newproperty(#{name})" newproperty(name, options, &block) end # Create a new property. The first parameter must be the name of the property; # this is how users will refer to the property when creating new instances. # The second parameter is a hash of options; the options are: # * :parent: The parent class for the property. Defaults to Puppet::Property. # * :retrieve: The method to call on the provider or @parent object (if # the provider is not set) to retrieve the current value. def self.newproperty(name, options = {}, &block) name = symbolize(name) # This is here for types that might still have the old method of defining # a parent class. unless options.is_a? Hash raise Puppet::DevError, "Options must be a hash, not #{options.inspect}" end raise Puppet::DevError, "Class #{self.name} already has a property named #{name}" if @validproperties.include?(name) if parent = options[:parent] options.delete(:parent) else parent = Puppet::Property end # We have to create our own, new block here because we want to define # an initial :retrieve method, if told to, and then eval the passed # block if available. prop = genclass(name, :parent => parent, :hash => @validproperties, :attributes => options) do # If they've passed a retrieve method, then override the retrieve # method on the class. if options[:retrieve] define_method(:retrieve) do provider.send(options[:retrieve]) end end class_eval(&block) if block end # If it's the 'ensure' property, always put it first. if name == :ensure @properties.unshift prop else @properties << prop end prop end def self.paramdoc(param) @paramhash[param].doc end # Return the parameter names def self.parameters return [] unless defined?(@parameters) @parameters.collect { |klass| klass.name } end # Find the parameter class associated with a given parameter name. def self.paramclass(name) @paramhash[name] end # Return the property class associated with a name def self.propertybyname(name) @validproperties[name] end def self.validattr?(name) name = symbolize(name) return true if name == :name @validattrs ||= {} unless @validattrs.include?(name) @validattrs[name] = !!(self.validproperty?(name) or self.validparameter?(name) or self.metaparam?(name)) end @validattrs[name] end # does the name reflect a valid property? def self.validproperty?(name) name = symbolize(name) @validproperties.include?(name) && @validproperties[name] end # Return the list of validproperties def self.validproperties return {} unless defined?(@parameters) @validproperties.keys end # does the name reflect a valid parameter? def self.validparameter?(name) raise Puppet::DevError, "Class #{self} has not defined parameters" unless defined?(@parameters) !!(@paramhash.include?(name) or @@metaparamhash.include?(name)) end # This is a forward-compatibility method - it's the validity interface we'll use in Puppet::Resource. def self.valid_parameter?(name) validattr?(name) end # Return either the attribute alias or the attribute. def attr_alias(name) name = symbolize(name) if synonym = self.class.attr_alias(name) return synonym else return name end end # Are we deleting this resource? def deleting? obj = @parameters[:ensure] and obj.should == :absent end # Create a new property if it is valid but doesn't exist # Returns: true if a new parameter was added, false otherwise def add_property_parameter(prop_name) if self.class.validproperty?(prop_name) && !@parameters[prop_name] self.newattr(prop_name) return true end false end # # The name_var is the key_attribute in the case that there is only one. # def name_var key_attributes = self.class.key_attributes (key_attributes.length == 1) && key_attributes.first end # abstract accessing parameters and properties, and normalize # access to always be symbols, not strings # This returns a value, not an object. It returns the 'is' # value, but you can also specifically return 'is' and 'should' # values using 'object.is(:property)' or 'object.should(:property)'. def [](name) name = attr_alias(name) fail("Invalid parameter #{name}(#{name.inspect})") unless self.class.validattr?(name) if name == :name name = name_var end if obj = @parameters[name] # Note that if this is a property, then the value is the "should" value, # not the current value. obj.value else return nil end end # Abstract setting parameters and properties, and normalize # access to always be symbols, not strings. This sets the 'should' # value on properties, and otherwise just sets the appropriate parameter. def []=(name,value) name = attr_alias(name) fail("Invalid parameter #{name}") unless self.class.validattr?(name) if name == :name name = name_var end raise Puppet::Error.new("Got nil value for #{name}") if value.nil? property = self.newattr(name) if property begin # make sure the parameter doesn't have any errors property.value = value rescue => detail error = Puppet::Error.new("Parameter #{name} failed: #{detail}") error.set_backtrace(detail.backtrace) raise error end end nil end # remove a property from the object; useful in testing or in cleanup # when an error has been encountered def delete(attr) attr = symbolize(attr) if @parameters.has_key?(attr) @parameters.delete(attr) else raise Puppet::DevError.new("Undefined attribute '#{attr}' in #{self}") end end # iterate across the existing properties def eachproperty # properties is a private method properties.each { |property| yield property } end # Create a transaction event. Called by Transaction or by # a property. def event(options = {}) - Puppet::Transaction::Event.new({:resource => self, :file => file, :line => line, :tags => tags, :version => version}.merge(options)) + Puppet::Transaction::Event.new({:resource => self, :file => file, :line => line, :tags => tags}.merge(options)) end # Let the catalog determine whether a given cached value is # still valid or has expired. def expirer catalog end # retrieve the 'should' value for a specified property def should(name) name = attr_alias(name) (prop = @parameters[name] and prop.is_a?(Puppet::Property)) ? prop.should : nil end # Create the actual attribute instance. Requires either the attribute # name or class as the first argument, then an optional hash of # attributes to set during initialization. def newattr(name) if name.is_a?(Class) klass = name name = klass.name end unless klass = self.class.attrclass(name) raise Puppet::Error, "Resource type #{self.class.name} does not support parameter #{name}" end if provider and ! provider.class.supports_parameter?(klass) missing = klass.required_features.find_all { |f| ! provider.class.feature?(f) } info "Provider %s does not support features %s; not managing attribute %s" % [provider.class.name, missing.join(", "), name] return nil end return @parameters[name] if @parameters.include?(name) @parameters[name] = klass.new(:resource => self) end # return the value of a parameter def parameter(name) @parameters[name.to_sym] end def parameters @parameters.dup end # Is the named property defined? def propertydefined?(name) name = name.intern unless name.is_a? Symbol @parameters.include?(name) end # Return an actual property instance by name; to return the value, use 'resource[param]' # LAK:NOTE(20081028) Since the 'parameter' method is now a superset of this method, # this one should probably go away at some point. def property(name) (obj = @parameters[symbolize(name)] and obj.is_a?(Puppet::Property)) ? obj : nil end # For any parameters or properties that have defaults and have not yet been # set, set them now. This method can be handed a list of attributes, # and if so it will only set defaults for those attributes. def set_default(attr) return unless klass = self.class.attrclass(attr) return unless klass.method_defined?(:default) return if @parameters.include?(klass.name) return unless parameter = newattr(klass.name) if value = parameter.default and ! value.nil? parameter.value = value else @parameters.delete(parameter.name) end end # Convert our object to a hash. This just includes properties. def to_hash rethash = {} @parameters.each do |name, obj| rethash[name] = obj.value end rethash end def type self.class.name end # Return a specific value for an attribute. def value(name) name = attr_alias(name) (obj = @parameters[name] and obj.respond_to?(:value)) ? obj.value : nil end def version return 0 unless catalog catalog.version end # Return all of the property objects, in the order specified in the # class. def properties self.class.properties.collect { |prop| @parameters[prop.name] }.compact end # Is this type's name isomorphic with the object? That is, if the # name conflicts, does it necessarily mean that the objects conflict? # Defaults to true. def self.isomorphic? if defined?(@isomorphic) return @isomorphic else return true end end def isomorphic? self.class.isomorphic? end # is the instance a managed instance? A 'yes' here means that # the instance was created from the language, vs. being created # in order resolve other questions, such as finding a package # in a list def managed? # Once an object is managed, it always stays managed; but an object # that is listed as unmanaged might become managed later in the process, # so we have to check that every time if @managed return @managed else @managed = false properties.each { |property| s = property.should if s and ! property.class.unmanaged @managed = true break end } return @managed end end ############################### # Code related to the container behaviour. # this is a retarded hack method to get around the difference between # component children and file children def self.depthfirst? @depthfirst end def depthfirst? self.class.depthfirst? end # Remove an object. The argument determines whether the object's # subscriptions get eliminated, too. def remove(rmdeps = true) # This is hackish (mmm, cut and paste), but it works for now, and it's # better than warnings. @parameters.each do |name, obj| obj.remove end @parameters.clear @parent = nil # Remove the reference to the provider. if self.provider @provider.clear @provider = nil end end ############################### # Code related to evaluating the resources. # Flush the provider, if it supports it. This is called by the # transaction. def flush self.provider.flush if self.provider and self.provider.respond_to?(:flush) end # if all contained objects are in sync, then we're in sync # FIXME I don't think this is used on the type instances any more, # it's really only used for testing def insync?(is) insync = true if property = @parameters[:ensure] unless is.include? property raise Puppet::DevError, "The is value is not in the is array for '#{property.name}'" end ensureis = is[property] if property.insync?(ensureis) and property.should == :absent return true end end properties.each { |property| unless is.include? property raise Puppet::DevError, "The is value is not in the is array for '#{property.name}'" end propis = is[property] unless property.insync?(propis) property.debug("Not in sync: #{propis.inspect} vs #{property.should.inspect}") insync = false #else # property.debug("In sync") end } #self.debug("#{self} sync status is #{insync}") insync end # retrieve the current value of all contained properties def retrieve fail "Provider #{provider.class.name} is not functional on this host" if self.provider.is_a?(Puppet::Provider) and ! provider.class.suitable? result = Puppet::Resource.new(type, title) # Provide the name, so we know we'll always refer to a real thing result[:name] = self[:name] unless self[:name] == title if ensure_prop = property(:ensure) or (self.class.validattr?(:ensure) and ensure_prop = newattr(:ensure)) result[:ensure] = ensure_state = ensure_prop.retrieve else ensure_state = nil end properties.each do |property| next if property.name == :ensure if ensure_state == :absent result[property] = :absent else result[property] = property.retrieve end end result end def retrieve_resource resource = retrieve resource = Resource.new(type, title, :parameters => resource) if resource.is_a? Hash resource end # Get a hash of the current properties. Returns a hash with # the actual property instance as the key and the current value # as the, um, value. def currentpropvalues # It's important to use the 'properties' method here, as it follows the order # in which they're defined in the class. It also guarantees that 'ensure' # is the first property, which is important for skipping 'retrieve' on # all the properties if the resource is absent. ensure_state = false return properties.inject({}) do | prophash, property| if property.name == :ensure ensure_state = property.retrieve prophash[property] = ensure_state else if ensure_state == :absent prophash[property] = :absent else prophash[property] = property.retrieve end end prophash end end # Are we running in noop mode? def noop? # If we're not a host_config, we're almost certainly part of # Settings, and we want to ignore 'noop' return false if catalog and ! catalog.host_config? if defined?(@noop) @noop else Puppet[:noop] end end def noop noop? end ############################### # Code related to managing resource instances. require 'puppet/transportable' # retrieve a named instance of the current type def self.[](name) raise "Global resource access is deprecated" @objects[name] || @aliases[name] end # add an instance by name to the class list of instances def self.[]=(name,object) raise "Global resource storage is deprecated" newobj = nil if object.is_a?(Puppet::Type) newobj = object else raise Puppet::DevError, "must pass a Puppet::Type object" end if exobj = @objects[name] and self.isomorphic? msg = "Object '#{newobj.class.name}[#{name}]' already exists" msg += ("in file #{object.file} at line #{object.line}") if exobj.file and exobj.line msg += ("and cannot be redefined in file #{object.file} at line #{object.line}") if object.file and object.line error = Puppet::Error.new(msg) raise error else #Puppet.info("adding %s of type %s to class list" % # [name,object.class]) @objects[name] = newobj end end # Create an alias. We keep these in a separate hash so that we don't encounter # the objects multiple times when iterating over them. def self.alias(name, obj) raise "Global resource aliasing is deprecated" if @objects.include?(name) unless @objects[name] == obj raise Puppet::Error.new( "Cannot create alias #{name}: object already exists" ) end end if @aliases.include?(name) unless @aliases[name] == obj raise Puppet::Error.new( "Object #{@aliases[name].name} already has alias #{name}" ) end end @aliases[name] = obj end # remove all of the instances of a single type def self.clear raise "Global resource removal is deprecated" if defined?(@objects) @objects.each do |name, obj| obj.remove(true) end @objects.clear end @aliases.clear if defined?(@aliases) end # Force users to call this, so that we can merge objects if # necessary. def self.create(args) # LAK:DEP Deprecation notice added 12/17/2008 Puppet.warning "Puppet::Type.create is deprecated; use Puppet::Type.new" new(args) end # remove a specified object def self.delete(resource) raise "Global resource removal is deprecated" return unless defined?(@objects) @objects.delete(resource.title) if @objects.include?(resource.title) @aliases.delete(resource.title) if @aliases.include?(resource.title) if @aliases.has_value?(resource) names = [] @aliases.each do |name, otherres| if otherres == resource names << name end end names.each { |name| @aliases.delete(name) } end end # iterate across each of the type's instances def self.each raise "Global resource iteration is deprecated" return unless defined?(@objects) @objects.each { |name,instance| yield instance } end # does the type have an object with the given name? def self.has_key?(name) raise "Global resource access is deprecated" @objects.has_key?(name) end # Retrieve all known instances. Either requires providers or must be overridden. def self.instances raise Puppet::DevError, "#{self.name} has no providers and has not overridden 'instances'" if provider_hash.empty? # Put the default provider first, then the rest of the suitable providers. provider_instances = {} providers_by_source.collect do |provider| provider.instances.collect do |instance| # We always want to use the "first" provider instance we find, unless the resource # is already managed and has a different provider set if other = provider_instances[instance.name] Puppet.warning "%s %s found in both %s and %s; skipping the %s version" % [self.name.to_s.capitalize, instance.name, other.class.name, instance.class.name, instance.class.name] next end provider_instances[instance.name] = instance new(:name => instance.name, :provider => instance, :audit => :all) end end.flatten.compact end # Return a list of one suitable provider per source, with the default provider first. def self.providers_by_source # Put the default provider first, then the rest of the suitable providers. sources = [] [defaultprovider, suitableprovider].flatten.uniq.collect do |provider| next if sources.include?(provider.source) sources << provider.source provider end.compact end # Convert a simple hash into a Resource instance. def self.hash2resource(hash) hash = hash.inject({}) { |result, ary| result[ary[0].to_sym] = ary[1]; result } title = hash.delete(:title) title ||= hash[:name] title ||= hash[key_attributes.first] if key_attributes.length == 1 raise Puppet::Error, "Title or name must be provided" unless title # Now create our resource. resource = Puppet::Resource.new(self.name, title) [:catalog].each do |attribute| if value = hash[attribute] hash.delete(attribute) resource.send(attribute.to_s + "=", value) end end hash.each do |param, value| resource[param] = value end resource end # Create the path for logging and such. def pathbuilder if p = parent [p.pathbuilder, self.ref].flatten else [self.ref] end end ############################### # Add all of the meta parameters. newmetaparam(:noop) do desc "Boolean flag indicating whether work should actually be done." newvalues(:true, :false) munge do |value| case value when true, :true, "true"; @resource.noop = true when false, :false, "false"; @resource.noop = false end end end newmetaparam(:schedule) do desc "On what schedule the object should be managed. You must create a schedule object, and then reference the name of that object to use that for your schedule: schedule { daily: period => daily, range => \"2-4\" } exec { \"/usr/bin/apt-get update\": schedule => daily } The creation of the schedule object does not need to appear in the configuration before objects that use it." end newmetaparam(:audit) do desc "Audit specified attributes of resources over time, and report if any have changed. This attribute can be used to track changes to any resource over time, and can provide an audit trail of every change that happens on any given machine. Note that you cannot both audit and manage an attribute - managing it guarantees the value, and any changes already get logged." validate do |list| list = Array(list).collect {|p| p.to_sym} unless list == [:all] list.each do |param| next if @resource.class.validattr?(param) fail "Cannot audit #{param}: not a valid attribute for #{resource}" end end end munge do |args| properties_to_audit(args).each do |param| next unless resource.class.validproperty?(param) resource.newattr(param) end end def all_properties resource.class.properties.find_all do |property| resource.provider.nil? or resource.provider.class.supports_parameter?(property) end.collect do |property| property.name end end def properties_to_audit(list) if !list.kind_of?(Array) && list.to_sym == :all list = all_properties else list = Array(list).collect { |p| p.to_sym } end end end newmetaparam(:check) do desc "Audit specified attributes of resources over time, and report if any have changed. This parameter has been deprecated in favor of 'audit'." munge do |args| resource.warning "'check' attribute is deprecated; use 'audit' instead" resource[:audit] = args end end newmetaparam(:loglevel) do desc "Sets the level that information will be logged. The log levels have the biggest impact when logs are sent to syslog (which is currently the default)." defaultto :notice newvalues(*Puppet::Util::Log.levels) newvalues(:verbose) munge do |loglevel| val = super(loglevel) if val == :verbose val = :info end val end end newmetaparam(:alias) do desc "Creates an alias for the object. Puppet uses this internally when you provide a symbolic name: file { sshdconfig: path => $operatingsystem ? { solaris => \"/usr/local/etc/ssh/sshd_config\", default => \"/etc/ssh/sshd_config\" }, source => \"...\" } service { sshd: subscribe => File[sshdconfig] } When you use this feature, the parser sets `sshdconfig` as the name, and the library sets that as an alias for the file so the dependency lookup for `sshd` works. You can use this parameter yourself, but note that only the library can use these aliases; for instance, the following code will not work: file { \"/etc/ssh/sshd_config\": owner => root, group => root, alias => sshdconfig } file { sshdconfig: mode => 644 } There's no way here for the Puppet parser to know that these two stanzas should be affecting the same file. See the [Language Tutorial](http://docs.puppetlabs.com/guides/language_tutorial.html) for more information. " munge do |aliases| aliases = [aliases] unless aliases.is_a?(Array) raise(ArgumentError, "Cannot add aliases without a catalog") unless @resource.catalog aliases.each do |other| if obj = @resource.catalog.resource(@resource.class.name, other) unless obj.object_id == @resource.object_id self.fail("#{@resource.title} can not create alias #{other}: object already exists") end next end # Newschool, add it to the catalog. @resource.catalog.alias(@resource, other) end end end newmetaparam(:tag) do desc "Add the specified tags to the associated resource. While all resources are automatically tagged with as much information as possible (e.g., each class and definition containing the resource), it can be useful to add your own tags to a given resource. Tags are currently useful for things like applying a subset of a host's configuration: puppet agent --test --tags mytag This way, when you're testing a configuration you can run just the portion you're testing." munge do |tags| tags = [tags] unless tags.is_a? Array tags.each do |tag| @resource.tag(tag) end end end class RelationshipMetaparam < Puppet::Parameter class << self attr_accessor :direction, :events, :callback, :subclasses end @subclasses = [] def self.inherited(sub) @subclasses << sub end def munge(references) references = [references] unless references.is_a?(Array) references.collect do |ref| if ref.is_a?(Puppet::Resource) ref else Puppet::Resource.new(ref) end end end def validate_relationship @value.each do |ref| unless @resource.catalog.resource(ref.to_s) description = self.class.direction == :in ? "dependency" : "dependent" fail "Could not find #{description} #{ref} for #{resource.ref}" end end end # Create edges from each of our relationships. :in # relationships are specified by the event-receivers, and :out # relationships are specified by the event generator. This # way 'source' and 'target' are consistent terms in both edges # and events -- that is, an event targets edges whose source matches # the event's source. The direction of the relationship determines # which resource is applied first and which resource is considered # to be the event generator. def to_edges @value.collect do |reference| reference.catalog = resource.catalog # Either of the two retrieval attempts could have returned # nil. unless related_resource = reference.resolve self.fail "Could not retrieve dependency '#{reference}' of #{@resource.ref}" end # Are we requiring them, or vice versa? See the method docs # for futher info on this. if self.class.direction == :in source = related_resource target = @resource else source = @resource target = related_resource end if method = self.class.callback subargs = { :event => self.class.events, :callback => method } self.debug("subscribes to #{related_resource.ref}") else # If there's no callback, there's no point in even adding # a label. subargs = nil self.debug("requires #{related_resource.ref}") end rel = Puppet::Relationship.new(source, target, subargs) end end end def self.relationship_params RelationshipMetaparam.subclasses end # Note that the order in which the relationships params is defined # matters. The labelled params (notify and subcribe) must be later, # so that if both params are used, those ones win. It's a hackish # solution, but it works. newmetaparam(:require, :parent => RelationshipMetaparam, :attributes => {:direction => :in, :events => :NONE}) do desc "One or more objects that this object depends on. This is used purely for guaranteeing that changes to required objects happen before the dependent object. For instance: # Create the destination directory before you copy things down file { \"/usr/local/scripts\": ensure => directory } file { \"/usr/local/scripts/myscript\": source => \"puppet://server/module/myscript\", mode => 755, require => File[\"/usr/local/scripts\"] } Multiple dependencies can be specified by providing a comma-seperated list of resources, enclosed in square brackets: require => [ File[\"/usr/local\"], File[\"/usr/local/scripts\"] ] Note that Puppet will autorequire everything that it can, and there are hooks in place so that it's easy for resources to add new ways to autorequire objects, so if you think Puppet could be smarter here, let us know. In fact, the above code was redundant -- Puppet will autorequire any parent directories that are being managed; it will automatically realize that the parent directory should be created before the script is pulled down. Currently, exec resources will autorequire their CWD (if it is specified) plus any fully qualified paths that appear in the command. For instance, if you had an `exec` command that ran the `myscript` mentioned above, the above code that pulls the file down would be automatically listed as a requirement to the `exec` code, so that you would always be running againts the most recent version. " end newmetaparam(:subscribe, :parent => RelationshipMetaparam, :attributes => {:direction => :in, :events => :ALL_EVENTS, :callback => :refresh}) do desc "One or more objects that this object depends on. Changes in the subscribed to objects result in the dependent objects being refreshed (e.g., a service will get restarted). For instance: class nagios { file { \"/etc/nagios/nagios.conf\": source => \"puppet://server/module/nagios.conf\", alias => nagconf # just to make things easier for me } service { nagios: ensure => running, subscribe => File[nagconf] } } Currently the `exec`, `mount` and `service` type support refreshing. " end newmetaparam(:before, :parent => RelationshipMetaparam, :attributes => {:direction => :out, :events => :NONE}) do desc %{This parameter is the opposite of **require** -- it guarantees that the specified object is applied later than the specifying object: file { "/var/nagios/configuration": source => "...", recurse => true, before => Exec["nagios-rebuid"] } exec { "nagios-rebuild": command => "/usr/bin/make", cwd => "/var/nagios/configuration" } This will make sure all of the files are up to date before the make command is run.} end newmetaparam(:notify, :parent => RelationshipMetaparam, :attributes => {:direction => :out, :events => :ALL_EVENTS, :callback => :refresh}) do desc %{This parameter is the opposite of **subscribe** -- it sends events to the specified object: file { "/etc/sshd_config": source => "....", notify => Service[sshd] } service { sshd: ensure => running } This will restart the sshd service if the sshd config file changes.} end newmetaparam(:stage) do desc %{Which run stage a given resource should reside in. This just creates a dependency on or from the named milestone. For instance, saying that this is in the 'bootstrap' stage creates a dependency on the 'bootstrap' milestone. By default, all classes get directly added to the 'main' stage. You can create new stages as resources: stage { [pre, post]: } To order stages, use standard relationships: stage { pre: before => Stage[main] } Or use the new relationship syntax: Stage[pre] -> Stage[main] -> Stage[post] Then use the new class parameters to specify a stage: class { foo: stage => pre } Stages can only be set on classes, not individual resources. This will fail: file { '/foo': stage => pre, ensure => file } } end ############################### # All of the provider plumbing for the resource types. require 'puppet/provider' require 'puppet/util/provider_features' # Add the feature handling module. extend Puppet::Util::ProviderFeatures attr_reader :provider # the Type class attribute accessors class << self attr_accessor :providerloader attr_writer :defaultprovider end # Find the default provider. def self.defaultprovider unless @defaultprovider suitable = suitableprovider # Find which providers are a default for this system. defaults = suitable.find_all { |provider| provider.default? } # If we don't have any default we use suitable providers defaults = suitable if defaults.empty? max = defaults.collect { |provider| provider.specificity }.max defaults = defaults.find_all { |provider| provider.specificity == max } retval = nil if defaults.length > 1 Puppet.warning( "Found multiple default providers for #{self.name}: #{defaults.collect { |i| i.name.to_s }.join(", ")}; using #{defaults[0].name}" ) retval = defaults.shift elsif defaults.length == 1 retval = defaults.shift else raise Puppet::DevError, "Could not find a default provider for #{self.name}" end @defaultprovider = retval end @defaultprovider end def self.provider_hash_by_type(type) @provider_hashes ||= {} @provider_hashes[type] ||= {} end def self.provider_hash Puppet::Type.provider_hash_by_type(self.name) end # Retrieve a provider by name. def self.provider(name) name = Puppet::Util.symbolize(name) # If we don't have it yet, try loading it. @providerloader.load(name) unless provider_hash.has_key?(name) provider_hash[name] end # Just list all of the providers. def self.providers provider_hash.keys end def self.validprovider?(name) name = Puppet::Util.symbolize(name) (provider_hash.has_key?(name) && provider_hash[name].suitable?) end # Create a new provider of a type. This method must be called # directly on the type that it's implementing. def self.provide(name, options = {}, &block) name = Puppet::Util.symbolize(name) if obj = provider_hash[name] Puppet.debug "Reloading #{name} #{self.name} provider" unprovide(name) end parent = if pname = options[:parent] options.delete(:parent) if pname.is_a? Class pname else if provider = self.provider(pname) provider else raise Puppet::DevError, "Could not find parent provider #{pname} of #{name}" end end else Puppet::Provider end options[:resource_type] ||= self self.providify provider = genclass( name, :parent => parent, :hash => provider_hash, :prefix => "Provider", :block => block, :include => feature_module, :extend => feature_module, :attributes => options ) provider end # Make sure we have a :provider parameter defined. Only gets called if there # are providers. def self.providify return if @paramhash.has_key? :provider newparam(:provider) do desc "The specific backend for #{self.name.to_s} to use. You will seldom need to specify this -- Puppet will usually discover the appropriate provider for your platform." # This is so we can refer back to the type to get a list of # providers for documentation. class << self attr_accessor :parenttype end # We need to add documentation for each provider. def self.doc @doc + " Available providers are:\n\n" + parenttype.providers.sort { |a,b| a.to_s <=> b.to_s }.collect { |i| "* **#{i}**: #{parenttype().provider(i).doc}" }.join("\n") end defaultto { @resource.class.defaultprovider.name } validate do |provider_class| provider_class = provider_class[0] if provider_class.is_a? Array provider_class = provider_class.class.name if provider_class.is_a?(Puppet::Provider) unless provider = @resource.class.provider(provider_class) raise ArgumentError, "Invalid #{@resource.class.name} provider '#{provider_class}'" end end munge do |provider| provider = provider[0] if provider.is_a? Array provider = provider.intern if provider.is_a? String @resource.provider = provider if provider.is_a?(Puppet::Provider) provider.class.name else provider end end end.parenttype = self end def self.unprovide(name) if provider_hash.has_key? name rmclass( name, :hash => provider_hash, :prefix => "Provider" ) if @defaultprovider and @defaultprovider.name == name @defaultprovider = nil end end end # Return an array of all of the suitable providers. def self.suitableprovider providerloader.loadall if provider_hash.empty? provider_hash.find_all { |name, provider| provider.suitable? }.collect { |name, provider| provider }.reject { |p| p.name == :fake } # For testing end def provider=(name) if name.is_a?(Puppet::Provider) @provider = name @provider.resource = self elsif klass = self.class.provider(name) @provider = klass.new(self) else raise ArgumentError, "Could not find #{name} provider of #{self.class.name}" end end ############################### # All of the relationship code. # Specify a block for generating a list of objects to autorequire. This # makes it so that you don't have to manually specify things that you clearly # require. def self.autorequire(name, &block) @autorequires ||= {} @autorequires[name] = block end # Yield each of those autorequires in turn, yo. def self.eachautorequire @autorequires ||= {} @autorequires.each { |type, block| yield(type, block) } end # Figure out of there are any objects we can automatically add as # dependencies. def autorequire(rel_catalog = nil) rel_catalog ||= catalog raise(Puppet::DevError, "You cannot add relationships without a catalog") unless rel_catalog reqs = [] self.class.eachautorequire { |type, block| # Ignore any types we can't find, although that would be a bit odd. next unless typeobj = Puppet::Type.type(type) # Retrieve the list of names from the block. next unless list = self.instance_eval(&block) list = [list] unless list.is_a?(Array) # Collect the current prereqs list.each { |dep| obj = nil # Support them passing objects directly, to save some effort. unless dep.is_a? Puppet::Type # Skip autorequires that we aren't managing unless dep = rel_catalog.resource(type, dep) next end end reqs << Puppet::Relationship.new(dep, self) } } reqs end # Build the dependencies associated with an individual object. def builddepends # Handle the requires self.class.relationship_params.collect do |klass| if param = @parameters[klass.name] param.to_edges end end.flatten.reject { |r| r.nil? } end # Define the initial list of tags. def tags=(list) tag(self.class.name) tag(*list) end # Types (which map to resources in the languages) are entirely composed of # attribute value pairs. Generally, Puppet calls any of these things an # 'attribute', but these attributes always take one of three specific # forms: parameters, metaparams, or properties. # In naming methods, I have tried to consistently name the method so # that it is clear whether it operates on all attributes (thus has 'attr' in # the method name, or whether it operates on a specific type of attributes. attr_writer :title attr_writer :noop include Enumerable # class methods dealing with Type management public # the Type class attribute accessors class << self attr_reader :name attr_accessor :self_refresh include Enumerable, Puppet::Util::ClassGen include Puppet::MetaType::Manager include Puppet::Util include Puppet::Util::Logging end # all of the variables that must be initialized for each subclass def self.initvars # all of the instances of this class @objects = Hash.new @aliases = Hash.new @defaults = {} @parameters ||= [] @validproperties = {} @properties = [] @parameters = [] @paramhash = {} @attr_aliases = {} @paramdoc = Hash.new { |hash,key| key = key.intern if key.is_a?(String) if hash.include?(key) hash[key] else "Param Documentation for #{key} not found" end } @doc ||= "" end def self.to_s if defined?(@name) "Puppet::Type::#{@name.to_s.capitalize}" else super end end # Create a block to validate that our object is set up entirely. This will # be run before the object is operated on. def self.validate(&block) define_method(:validate, &block) #@validate = block end # The catalog that this resource is stored in. attr_accessor :catalog # is the resource exported attr_accessor :exported # is the resource virtual (it should not :-)) attr_accessor :virtual # create a log at specified level def log(msg) Puppet::Util::Log.create( :level => @parameters[:loglevel].value, :message => msg, :source => self ) end # instance methods related to instance intrinsics # e.g., initialize and name public attr_reader :original_parameters # initialize the type instance def initialize(resource) raise Puppet::DevError, "Got TransObject instead of Resource or hash" if resource.is_a?(Puppet::TransObject) resource = self.class.hash2resource(resource) unless resource.is_a?(Puppet::Resource) # The list of parameter/property instances. @parameters = {} # Set the title first, so any failures print correctly. if resource.type.to_s.downcase.to_sym == self.class.name self.title = resource.title else # This should only ever happen for components self.title = resource.ref end [:file, :line, :catalog, :exported, :virtual].each do |getter| setter = getter.to_s + "=" if val = resource.send(getter) self.send(setter, val) end end @tags = resource.tags @original_parameters = resource.to_hash set_name(@original_parameters) set_default(:provider) set_parameters(@original_parameters) self.validate if self.respond_to?(:validate) end private # Set our resource's name. def set_name(hash) self[name_var] = hash.delete(name_var) if name_var end # Set all of the parameters from a hash, in the appropriate order. def set_parameters(hash) # Use the order provided by allattrs, but add in any # extra attributes from the resource so we get failures # on invalid attributes. no_values = [] (self.class.allattrs + hash.keys).uniq.each do |attr| begin # Set any defaults immediately. This is mostly done so # that the default provider is available for any other # property validation. if hash.has_key?(attr) self[attr] = hash[attr] else no_values << attr end rescue ArgumentError, Puppet::Error, TypeError raise rescue => detail error = Puppet::DevError.new( "Could not set #{attr} on #{self.class.name}: #{detail}") error.set_backtrace(detail.backtrace) raise error end end no_values.each do |attr| set_default(attr) end end public # Set up all of our autorequires. def finish # Make sure all of our relationships are valid. Again, must be done # when the entire catalog is instantiated. self.class.relationship_params.collect do |klass| if param = @parameters[klass.name] param.validate_relationship end end.flatten.reject { |r| r.nil? } end # For now, leave the 'name' method functioning like it used to. Once 'title' # works everywhere, I'll switch it. def name self[:name] end # Look up our parent in the catalog, if we have one. def parent return nil unless catalog unless defined?(@parent) if parents = catalog.adjacent(self, :direction => :in) # We should never have more than one parent, so let's just ignore # it if we happen to. @parent = parents.shift else @parent = nil end end @parent end # Return the "type[name]" style reference. def ref "#{self.class.name.to_s.capitalize}[#{self.title}]" end def self_refresh? self.class.self_refresh end # Mark that we're purging. def purging @purging = true end # Is this resource being purged? Used by transactions to forbid # deletion when there are dependencies. def purging? if defined?(@purging) @purging else false end end # Retrieve the title of an object. If no title was set separately, # then use the object's name. def title unless @title if self.class.validparameter?(name_var) @title = self[:name] elsif self.class.validproperty?(name_var) @title = self.should(name_var) else self.devfail "Could not find namevar #{name_var} for #{self.class.name}" end end @title end # convert to a string def to_s self.ref end # Convert to a transportable object def to_trans(ret = true) trans = TransObject.new(self.title, self.class.name) values = retrieve_resource values.each do |name, value| name = name.name if name.respond_to? :name trans[name] = value end @parameters.each do |name, param| # Avoid adding each instance name twice next if param.class.isnamevar? and param.value == self.title # We've already got property values next if param.is_a?(Puppet::Property) trans[name] = param.value end trans.tags = self.tags # FIXME I'm currently ignoring 'parent' and 'path' trans end def to_resource # this 'type instance' versus 'resource' distinction seems artificial # I'd like to see it collapsed someday ~JW self.to_trans.to_resource end def virtual?; !!@virtual; end def exported?; !!@exported; end end end require 'puppet/provider' # Always load these types. require 'puppet/type/component' diff --git a/lib/puppet/type/file.rb b/lib/puppet/type/file.rb index 6523c99a0..eee948cd5 100644 --- a/lib/puppet/type/file.rb +++ b/lib/puppet/type/file.rb @@ -1,799 +1,801 @@ require 'digest/md5' require 'cgi' require 'etc' require 'uri' require 'fileutils' require 'puppet/network/handler' require 'puppet/util/diff' require 'puppet/util/checksums' require 'puppet/network/client' require 'puppet/util/backups' Puppet::Type.newtype(:file) do include Puppet::Util::MethodHelper include Puppet::Util::Checksums include Puppet::Util::Backups @doc = "Manages local files, including setting ownership and permissions, creation of both files and directories, and retrieving entire files from remote servers. As Puppet matures, it expected that the `file` resource will be used less and less to manage content, and instead native resources will be used to do so. If you find that you are often copying files in from a central location, rather than using native resources, please contact Puppet Labs and we can hopefully work with you to develop a native resource to support what you are doing." def self.title_patterns [ [ /^(.*?)\/*\Z/m, [ [ :path, lambda{|x| x} ] ] ] ] end newparam(:path) do desc "The path to the file to manage. Must be fully qualified." isnamevar validate do |value| # accept various path syntaxes: lone slash, posix, win32, unc unless (Puppet.features.posix? and (value =~ /^\/$/ or value =~ /^\/[^\/]/)) or (Puppet.features.microsoft_windows? and (value =~ /^.:\// or value =~ /^\/\/[^\/]+\/[^\/]+/)) fail Puppet::Error, "File paths must be fully qualified, not '#{value}'" end end # convert the current path in an index into the collection and the last # path name. The aim is to use less storage for all common paths in a hierarchy munge do |value| path, name = File.split(value.gsub(/\/+/,'/')) { :index => Puppet::FileCollection.collection.index(path), :name => name } end # and the reverse unmunge do |value| basedir = Puppet::FileCollection.collection.path(value[:index]) # a lone slash as :name indicates a root dir on windows if value[:name] == '/' basedir else File.join( basedir, value[:name] ) end end end newparam(:backup) do desc "Whether files should be backed up before being replaced. The preferred method of backing files up is via a `filebucket`, which stores files by their MD5 sums and allows easy retrieval without littering directories with backups. You can specify a local filebucket or a network-accessible server-based filebucket by setting `backup => bucket-name`. Alternatively, if you specify any value that begins with a `.` (e.g., `.puppet-bak`), then Puppet will use copy the file in the same directory with that value as the extension of the backup. Setting `backup => false` disables all backups of the file in question. Puppet automatically creates a local filebucket named `puppet` and defaults to backing up there. To use a server-based filebucket, you must specify one in your configuration filebucket { main: server => puppet } The `puppet master` daemon creates a filebucket by default, so you can usually back up to your main server with this configuration. Once you've described the bucket in your configuration, you can use it in any file file { \"/my/file\": source => \"/path/in/nfs/or/something\", backup => main } This will back the file up to the central server. At this point, the benefits of using a filebucket are that you do not have backup files lying around on each of your machines, a given version of a file is only backed up once, and you can restore any given file manually, no matter how old. Eventually, transactional support will be able to automatically restore filebucketed files. " defaultto "puppet" munge do |value| # I don't really know how this is happening. value = value.shift if value.is_a?(Array) case value when false, "false", :false false when true, "true", ".puppet-bak", :true ".puppet-bak" when String value else self.fail "Invalid backup type #{value.inspect}" end end end newparam(:recurse) do desc "Whether and how deeply to do recursive management." newvalues(:true, :false, :inf, :remote, /^[0-9]+$/) # Replace the validation so that we allow numbers in # addition to string representations of them. validate { |arg| } munge do |value| newval = super(value) case newval when :true, :inf; true when :false; false when :remote; :remote when Integer, Fixnum, Bignum self.warning "Setting recursion depth with the recurse parameter is now deprecated, please use recurselimit" # recurse == 0 means no recursion return false if value == 0 resource[:recurselimit] = value true when /^\d+$/ self.warning "Setting recursion depth with the recurse parameter is now deprecated, please use recurselimit" value = Integer(value) # recurse == 0 means no recursion return false if value == 0 resource[:recurselimit] = value true else self.fail "Invalid recurse value #{value.inspect}" end end end newparam(:recurselimit) do desc "How deeply to do recursive management." newvalues(/^[0-9]+$/) munge do |value| newval = super(value) case newval when Integer, Fixnum, Bignum; value when /^\d+$/; Integer(value) else self.fail "Invalid recurselimit value #{value.inspect}" end end end newparam(:replace, :boolean => true) do desc "Whether or not to replace a file that is sourced but exists. This is useful for using file sources purely for initialization." newvalues(:true, :false) aliasvalue(:yes, :true) aliasvalue(:no, :false) defaultto :true end newparam(:force, :boolean => true) do desc "Force the file operation. Currently only used when replacing directories with links." newvalues(:true, :false) defaultto false end newparam(:ignore) do desc "A parameter which omits action on files matching specified patterns during recursion. Uses Ruby's builtin globbing engine, so shell metacharacters are fully supported, e.g. `[a-z]*`. Matches that would descend into the directory structure are ignored, e.g., `*/*`." validate do |value| unless value.is_a?(Array) or value.is_a?(String) or value == false self.devfail "Ignore must be a string or an Array" end end end newparam(:links) do desc "How to handle links during file actions. During file copying, `follow` will copy the target file instead of the link, `manage` will copy the link itself, and `ignore` will just pass it by. When not copying, `manage` and `ignore` behave equivalently (because you cannot really ignore links entirely during local recursion), and `follow` will manage the file to which the link points." newvalues(:follow, :manage) defaultto :manage end newparam(:purge, :boolean => true) do desc "Whether unmanaged files should be purged. If you have a filebucket configured the purged files will be uploaded, but if you do not, this will destroy data. Only use this option for generated files unless you really know what you are doing. This option only makes sense when recursively managing directories. Note that when using `purge` with `source`, Puppet will purge any files that are not on the remote system." defaultto :false newvalues(:true, :false) end newparam(:sourceselect) do desc "Whether to copy all valid sources, or just the first one. This parameter is only used in recursive copies; by default, the first valid source is the only one used as a recursive source, but if this parameter is set to `all`, then all valid sources will have all of their contents copied to the local host, and for sources that have the same file, the source earlier in the list will be used." defaultto :first newvalues(:first, :all) end # Autorequire any parent directories. autorequire(:file) do basedir = File.dirname(self[:path]) if basedir != self[:path] basedir else nil end end # Autorequire the owner and group of the file. {:user => :owner, :group => :group}.each do |type, property| autorequire(type) do if @parameters.include?(property) # The user/group property automatically converts to IDs next unless should = @parameters[property].shouldorig val = should[0] if val.is_a?(Integer) or val =~ /^\d+$/ nil else val end end end end CREATORS = [:content, :source, :target] validate do count = 0 CREATORS.each do |param| count += 1 if self.should(param) end count += 1 if @parameters.include?(:source) self.fail "You cannot specify more than one of #{CREATORS.collect { |p| p.to_s}.join(", ")}" if count > 1 self.fail "You cannot specify a remote recursion without a source" if !self[:source] and self[:recurse] == :remote self.warning "Possible error: recurselimit is set but not recurse, no recursion will happen" if !self[:recurse] and self[:recurselimit] end def self.[](path) return nil unless path super(path.gsub(/\/+/, '/').sub(/\/$/, '')) end # List files, but only one level deep. def self.instances(base = "/") return [] unless FileTest.directory?(base) files = [] Dir.entries(base).reject { |e| e == "." or e == ".." }.each do |name| path = File.join(base, name) if obj = self[path] obj[:audit] = :all files << obj else files << self.new( :name => path, :audit => :all ) end end files end @depthfirst = false # Determine the user to write files as. def asuser if self.should(:owner) and ! self.should(:owner).is_a?(Symbol) writeable = Puppet::Util::SUIDManager.asuser(self.should(:owner)) { FileTest.writable?(File.dirname(self[:path])) } # If the parent directory is writeable, then we execute # as the user in question. Otherwise we'll rely on # the 'owner' property to do things. asuser = self.should(:owner) if writeable end asuser end def bucket return @bucket if @bucket backup = self[:backup] return nil unless backup return nil if backup =~ /^\./ unless catalog or backup == "puppet" fail "Can not find filebucket for backups without a catalog" end unless catalog and filebucket = catalog.resource(:filebucket, backup) or backup == "puppet" fail "Could not find filebucket #{backup} specified in backup" end return default_bucket unless filebucket @bucket = filebucket.bucket @bucket end def default_bucket Puppet::Type.type(:filebucket).mkdefaultbucket.bucket end # Does the file currently exist? Just checks for whether # we have a stat def exist? stat ? true : false end # We have to do some extra finishing, to retrieve our bucket if # there is one. def finish # Look up our bucket, if there is one bucket super end # Create any children via recursion or whatever. def eval_generate return [] unless self.recurse? recurse #recurse.reject do |resource| # catalog.resource(:file, resource[:path]) #end.each do |child| # catalog.add_resource child # catalog.relationship_graph.add_edge self, child #end end def flush # We want to make sure we retrieve metadata anew on each transaction. @parameters.each do |name, param| param.flush if param.respond_to?(:flush) end @stat = nil end def initialize(hash) # Used for caching clients @clients = {} super # If they've specified a source, we get our 'should' values # from it. unless self[:ensure] if self[:target] self[:ensure] = :symlink elsif self[:content] self[:ensure] = :file end end @stat = nil end # Configure discovered resources to be purged. def mark_children_for_purging(children) children.each do |name, child| next if child[:source] child[:ensure] = :absent end end # Create a new file or directory object as a child to the current # object. def newchild(path) full_path = File.join(self[:path], path) # Add some new values to our original arguments -- these are the ones # set at initialization. We specifically want to exclude any param # values set by the :source property or any default values. # LAK:NOTE This is kind of silly, because the whole point here is that # the values set at initialization should live as long as the resource # but values set by default or by :source should only live for the transaction # or so. Unfortunately, we don't have a straightforward way to manage # the different lifetimes of this data, so we kludge it like this. # The right-side hash wins in the merge. options = @original_parameters.merge(:path => full_path).reject { |param, value| value.nil? } # These should never be passed to our children. [:parent, :ensure, :recurse, :recurselimit, :target, :alias, :source].each do |param| options.delete(param) if options.include?(param) end self.class.new(options) end # Files handle paths specially, because they just lengthen their # path names, rather than including the full parent's title each # time. def pathbuilder # We specifically need to call the method here, so it looks # up our parent in the catalog graph. if parent = parent() # We only need to behave specially when our parent is also # a file if parent.is_a?(self.class) # Remove the parent file name list = parent.pathbuilder list.pop # remove the parent's path info return list << self.ref else return super end else return [self.ref] end end # Should we be purging? def purge? @parameters.include?(:purge) and (self[:purge] == :true or self[:purge] == "true") end # Recursively generate a list of file resources, which will # be used to copy remote files, manage local files, and/or make links # to map to another directory. def recurse children = {} children = recurse_local if self[:recurse] != :remote if self[:target] recurse_link(children) elsif self[:source] recurse_remote(children) end # If we're purging resources, then delete any resource that isn't on the # remote system. mark_children_for_purging(children) if self.purge? result = children.values.sort { |a, b| a[:path] <=> b[:path] } remove_less_specific_files(result) end # This is to fix bug #2296, where two files recurse over the same # set of files. It's a rare case, and when it does happen you're # not likely to have many actual conflicts, which is good, because # this is a pretty inefficient implementation. def remove_less_specific_files(files) mypath = self[:path].split(File::Separator) other_paths = catalog.vertices. select { |r| r.is_a?(self.class) and r[:path] != self[:path] }. collect { |r| r[:path].split(File::Separator) }. select { |p| p[0,mypath.length] == mypath } return files if other_paths.empty? files.reject { |file| path = file[:path].split(File::Separator) other_paths.any? { |p| path[0,p.length] == p } } end # A simple method for determining whether we should be recursing. def recurse? return false unless @parameters.include?(:recurse) val = @parameters[:recurse].value !!(val and (val == true or val == :remote)) end # Recurse the target of the link. def recurse_link(children) perform_recursion(self[:target]).each do |meta| if meta.relative_path == "." self[:ensure] = :directory next end children[meta.relative_path] ||= newchild(meta.relative_path) if meta.ftype == "directory" children[meta.relative_path][:ensure] = :directory else children[meta.relative_path][:ensure] = :link children[meta.relative_path][:target] = meta.full_path end end children end # Recurse the file itself, returning a Metadata instance for every found file. def recurse_local result = perform_recursion(self[:path]) return {} unless result result.inject({}) do |hash, meta| next hash if meta.relative_path == "." hash[meta.relative_path] = newchild(meta.relative_path) hash end end # Recurse against our remote file. def recurse_remote(children) sourceselect = self[:sourceselect] total = self[:source].collect do |source| next unless result = perform_recursion(source) return if top = result.find { |r| r.relative_path == "." } and top.ftype != "directory" result.each { |data| data.source = "#{source}/#{data.relative_path}" } break result if result and ! result.empty? and sourceselect == :first result end.flatten # This only happens if we have sourceselect == :all unless sourceselect == :first found = [] total.reject! do |data| result = found.include?(data.relative_path) found << data.relative_path unless found.include?(data.relative_path) result end end total.each do |meta| if meta.relative_path == "." parameter(:source).metadata = meta next end children[meta.relative_path] ||= newchild(meta.relative_path) children[meta.relative_path][:source] = meta.source children[meta.relative_path][:checksum] = :md5 if meta.ftype == "file" children[meta.relative_path].parameter(:source).metadata = meta end children end def perform_recursion(path) Puppet::FileServing::Metadata.search( path, :links => self[:links], :recurse => (self[:recurse] == :remote ? true : self[:recurse]), :recurselimit => self[:recurselimit], :ignore => self[:ignore], :checksum_type => (self[:source] || self[:content]) ? self[:checksum] : :none ) end # Remove any existing data. This is only used when dealing with # links or directories. def remove_existing(should) return unless s = stat self.fail "Could not back up; will not replace" unless perform_backup unless should.to_s == "link" return if s.ftype.to_s == should.to_s end case s.ftype when "directory" if self[:force] == :true debug "Removing existing directory for replacement with #{should}" FileUtils.rmtree(self[:path]) else notice "Not removing directory; use 'force' to override" end when "link", "file" debug "Removing existing #{s.ftype} for replacement with #{should}" File.unlink(self[:path]) else self.fail "Could not back up files of type #{s.ftype}" end expire end def retrieve if source = parameter(:source) source.copy_source_values end super end # Set the checksum, from another property. There are multiple # properties that modify the contents of a file, and they need the # ability to make sure that the checksum value is in sync. def setchecksum(sum = nil) if @parameters.include? :checksum if sum @parameters[:checksum].checksum = sum else # If they didn't pass in a sum, then tell checksum to # figure it out. currentvalue = @parameters[:checksum].retrieve @parameters[:checksum].checksum = currentvalue end end end # Should this thing be a normal file? This is a relatively complex # way of determining whether we're trying to create a normal file, # and it's here so that the logic isn't visible in the content property. def should_be_file? return true if self[:ensure] == :file # I.e., it's set to something like "directory" return false if e = self[:ensure] and e != :present # The user doesn't really care, apparently if self[:ensure] == :present return true unless s = stat return(s.ftype == "file" ? true : false) end # If we've gotten here, then :ensure isn't set return true if self[:content] return true if stat and stat.ftype == "file" false end # Stat our file. Depending on the value of the 'links' attribute, we # use either 'stat' or 'lstat', and we expect the properties to use the # resulting stat object accordingly (mostly by testing the 'ftype' # value). cached_attr(:stat) do method = :stat # Files are the only types that support links if (self.class.name == :file and self[:links] != :follow) or self.class.name == :tidy method = :lstat end path = self[:path] begin File.send(method, self[:path]) rescue Errno::ENOENT => error return nil rescue Errno::EACCES => error warning "Could not stat; permission denied" return nil end end # We have to hack this just a little bit, because otherwise we'll get # an error when the target and the contents are created as properties on # the far side. def to_trans(retrieve = true) obj = super obj.delete(:target) if obj[:target] == :notlink obj end # Write out the file. Requires the property name for logging. # Write will be done by the content property, along with checksum computation def write(property) remove_existing(:file) use_temporary_file = write_temporary_file? if use_temporary_file path = "#{self[:path]}.puppettmp_#{rand(10000)}" path = "#{self[:path]}.puppettmp_#{rand(10000)}" while File.exists?(path) or File.symlink?(path) else path = self[:path] end mode = self.should(:mode) # might be nil umask = mode ? 000 : 022 mode_int = mode ? mode.to_i(8) : nil content_checksum = Puppet::Util.withumask(umask) { File.open(path, 'w', mode_int ) { |f| write_content(f) } } # And put our new file in place if use_temporary_file # This is only not true when our file is empty. begin fail_if_checksum_is_wrong(path, content_checksum) if validate_checksum? File.rename(path, self[:path]) rescue => detail fail "Could not rename temporary file #{path} to #{self[:path]}: #{detail}" ensure # Make sure the created file gets removed File.unlink(path) if FileTest.exists?(path) end end # make sure all of the modes are actually correct property_fix end private # Should we validate the checksum of the file we're writing? def validate_checksum? self[:checksum] !~ /time/ end # Make sure the file we wrote out is what we think it is. def fail_if_checksum_is_wrong(path, content_checksum) newsum = parameter(:checksum).sum_file(path) return if [:absent, nil, content_checksum].include?(newsum) self.fail "File written to disk did not match checksum; discarding changes (#{content_checksum} vs #{newsum})" end # write the current content. Note that if there is no content property # simply opening the file with 'w' as done in write is enough to truncate # or write an empty length file. def write_content(file) (content = property(:content)) && content.write(file) end private def write_temporary_file? # unfortunately we don't know the source file size before fetching it # so let's assume the file won't be empty (c = property(:content) and c.length) || (s = @parameters[:source] and 1) end # There are some cases where all of the work does not get done on # file creation/modification, so we have to do some extra checking. def property_fix properties.each do |thing| next unless [:mode, :owner, :group, :seluser, :selrole, :seltype, :selrange].include?(thing.name) # Make sure we get a new stat objct expire currentvalue = thing.retrieve thing.sync unless thing.insync?(currentvalue) end end end # We put all of the properties in separate files, because there are so many # of them. The order these are loaded is important, because it determines # the order they are in the property lit. require 'puppet/type/file/checksum' require 'puppet/type/file/content' # can create the file require 'puppet/type/file/source' # can create the file require 'puppet/type/file/target' # creates a different type of file require 'puppet/type/file/ensure' # can create the file require 'puppet/type/file/owner' require 'puppet/type/file/group' require 'puppet/type/file/mode' require 'puppet/type/file/type' require 'puppet/type/file/selcontext' # SELinux file context +require 'puppet/type/file/ctime' +require 'puppet/type/file/mtime' diff --git a/lib/puppet/type/file/ctime.rb b/lib/puppet/type/file/ctime.rb new file mode 100644 index 000000000..24b098703 --- /dev/null +++ b/lib/puppet/type/file/ctime.rb @@ -0,0 +1,18 @@ +module Puppet + Puppet::Type.type(:file).newproperty(:ctime) do + desc "A read-only state to check the file ctime." + + def retrieve + current_value = :absent + if stat = @resource.stat(false) + current_value = stat.ctime + end + current_value + end + + validate do + fail "ctime is read-only" + end + end +end + diff --git a/lib/puppet/type/file/mtime.rb b/lib/puppet/type/file/mtime.rb new file mode 100644 index 000000000..8ca7ed0d6 --- /dev/null +++ b/lib/puppet/type/file/mtime.rb @@ -0,0 +1,17 @@ +module Puppet + Puppet::Type.type(:file).newproperty(:mtime) do + desc "A read-only state to check the file mtime." + + def retrieve + current_value = :absent + if stat = @resource.stat(false) + current_value = stat.mtime + end + current_value + end + + validate do + fail "mtime is read-only" + end + end +end diff --git a/lib/puppet/type/file/type.rb b/lib/puppet/type/file/type.rb index eb50b81f9..4da54e2cb 100755 --- a/lib/puppet/type/file/type.rb +++ b/lib/puppet/type/file/type.rb @@ -1,26 +1,19 @@ module Puppet Puppet::Type.type(:file).newproperty(:type) do require 'etc' desc "A read-only state to check the file type." - #munge do |value| - # raise Puppet::Error, ":type is read-only" - #end - def retrieve - currentvalue = :absent + current_value = :absent if stat = @resource.stat(false) - currentvalue = stat.ftype + current_value = stat.ftype end - # so this state is never marked out of sync - @should = [currentvalue] - currentvalue + current_value end - - def sync - raise Puppet::Error, ":type is read-only" + validate do + fail "type is read-only" end end end diff --git a/lib/puppet/util/checksums.rb b/lib/puppet/util/checksums.rb index 5aebd8392..6fdf14ecf 100644 --- a/lib/puppet/util/checksums.rb +++ b/lib/puppet/util/checksums.rb @@ -1,125 +1,136 @@ # A stand-alone module for calculating checksums # in a generic way. module Puppet::Util::Checksums + class FakeChecksum + def <<(*args) + self + end + end + # Is the provided string a checksum? def checksum?(string) string =~ /^\{(\w{3,5})\}\S+/ end # Strip the checksum type from an existing checksum def sumdata(checksum) checksum =~ /^\{(\w+)\}(.+)/ ? $2 : nil end # Strip the checksum type from an existing checksum def sumtype(checksum) checksum =~ /^\{(\w+)\}/ ? $1 : nil end # Calculate a checksum using Digest::MD5. def md5(content) require 'digest/md5' Digest::MD5.hexdigest(content) end # Calculate a checksum of the first 500 chars of the content using Digest::MD5. def md5lite(content) md5(content[0..511]) end # Calculate a checksum of a file's content using Digest::MD5. def md5_file(filename, lite = false) require 'digest/md5' digest = Digest::MD5.new checksum_file(digest, filename, lite) end # Calculate a checksum of the first 500 chars of a file's content using Digest::MD5. def md5lite_file(filename) md5_file(filename, true) end def md5_stream(&block) require 'digest/md5' digest = Digest::MD5.new yield digest digest.hexdigest end alias :md5lite_stream :md5_stream # Return the :mtime timestamp of a file. def mtime_file(filename) File.stat(filename).send(:mtime) end # by definition this doesn't exist + # but we still need to execute the block given def mtime_stream + noop_digest = FakeChecksum.new + yield noop_digest nil end alias :ctime_stream :mtime_stream # Calculate a checksum using Digest::SHA1. def sha1(content) require 'digest/sha1' Digest::SHA1.hexdigest(content) end # Calculate a checksum of the first 500 chars of the content using Digest::SHA1. def sha1lite(content) sha1(content[0..511]) end # Calculate a checksum of a file's content using Digest::SHA1. def sha1_file(filename, lite = false) require 'digest/sha1' digest = Digest::SHA1.new checksum_file(digest, filename, lite) end # Calculate a checksum of the first 500 chars of a file's content using Digest::SHA1. def sha1lite_file(filename) sha1_file(filename, true) end def sha1_stream require 'digest/sha1' digest = Digest::SHA1.new yield digest digest.hexdigest end alias :sha1lite_stream :sha1_stream # Return the :ctime of a file. def ctime_file(filename) File.stat(filename).send(:ctime) end # Return a "no checksum" def none_file(filename) "" end def none_stream + noop_digest = FakeChecksum.new + yield noop_digest "" end private # Perform an incremental checksum on a file. def checksum_file(digest, filename, lite = false) buffer = lite ? 512 : 4096 File.open(filename, 'r') do |file| while content = file.read(buffer) digest << content break if lite end end digest.hexdigest end end diff --git a/lib/puppet/util/log.rb b/lib/puppet/util/log.rb index 7764dc1d1..3fdac3f69 100644 --- a/lib/puppet/util/log.rb +++ b/lib/puppet/util/log.rb @@ -1,258 +1,258 @@ require 'puppet/util/tagging' require 'puppet/util/classgen' # Pass feedback to the user. Log levels are modeled after syslog's, and it is # expected that that will be the most common log destination. Supports # multiple destinations, one of which is a remote server. class Puppet::Util::Log include Puppet::Util extend Puppet::Util::ClassGen include Puppet::Util::Tagging @levels = [:debug,:info,:notice,:warning,:err,:alert,:emerg,:crit] @loglevel = 2 @desttypes = {} # Create a new destination type. def self.newdesttype(name, options = {}, &block) dest = genclass( name, :parent => Puppet::Util::Log::Destination, :prefix => "Dest", :block => block, :hash => @desttypes, :attributes => options ) dest.match(dest.name) dest end require 'puppet/util/log/destination' require 'puppet/util/log/destinations' @destinations = {} @queued = [] class << self include Puppet::Util include Puppet::Util::ClassGen attr_reader :desttypes end # Reset log to basics. Basically just flushes and closes files and # undefs other objects. def Log.close(destination) if @destinations.include?(destination) @destinations[destination].flush if @destinations[destination].respond_to?(:flush) @destinations[destination].close if @destinations[destination].respond_to?(:close) @destinations.delete(destination) end end def self.close_all destinations.keys.each { |dest| close(dest) } end # Flush any log destinations that support such operations. def Log.flush @destinations.each { |type, dest| dest.flush if dest.respond_to?(:flush) } end # Create a new log message. The primary role of this method is to # avoid creating log messages below the loglevel. def Log.create(hash) raise Puppet::DevError, "Logs require a level" unless hash.include?(:level) raise Puppet::DevError, "Invalid log level #{hash[:level]}" unless @levels.index(hash[:level]) @levels.index(hash[:level]) >= @loglevel ? Puppet::Util::Log.new(hash) : nil end def Log.destinations @destinations end # Yield each valid level in turn def Log.eachlevel @levels.each { |level| yield level } end # Return the current log level. def Log.level @levels[@loglevel] end # Set the current log level. def Log.level=(level) level = level.intern unless level.is_a?(Symbol) raise Puppet::DevError, "Invalid loglevel #{level}" unless @levels.include?(level) @loglevel = @levels.index(level) end def Log.levels @levels.dup end # Create a new log destination. def Log.newdestination(dest) # Each destination can only occur once. if @destinations.find { |name, obj| obj.name == dest } return end name, type = @desttypes.find do |name, klass| klass.match?(dest) end raise Puppet::DevError, "Unknown destination type #{dest}" unless type begin if type.instance_method(:initialize).arity == 1 @destinations[dest] = type.new(dest) else @destinations[dest] = type.new end flushqueue @destinations[dest] rescue => detail puts detail.backtrace if Puppet[:debug] # If this was our only destination, then add the console back in. newdestination(:console) if @destinations.empty? and (dest != :console and dest != "console") end end # Route the actual message. FIXME There are lots of things this method # should do, like caching and a bit more. It's worth noting that there's # a potential for a loop here, if the machine somehow gets the destination set as # itself. def Log.newmessage(msg) return if @levels.index(msg.level) < @loglevel queuemessage(msg) if @destinations.length == 0 @destinations.each do |name, dest| threadlock(dest) do dest.handle(msg) end end end def Log.queuemessage(msg) @queued.push(msg) end def Log.flushqueue return unless @destinations.size >= 1 @queued.each do |msg| Log.newmessage(msg) end @queued.clear end def Log.sendlevel?(level) @levels.index(level) >= @loglevel end # Reopen all of our logs. def Log.reopen Puppet.notice "Reopening log files" types = @destinations.keys @destinations.each { |type, dest| dest.close if dest.respond_to?(:close) } @destinations.clear # We need to make sure we always end up with some kind of destination begin types.each { |type| Log.newdestination(type) } rescue => detail if @destinations.empty? Log.newdestination(:syslog) Puppet.err detail.to_s end end end # Is the passed level a valid log level? def self.validlevel?(level) @levels.include?(level) end - attr_accessor :time, :remote, :file, :line, :version, :source + attr_accessor :time, :remote, :file, :line, :source attr_reader :level, :message def initialize(args) self.level = args[:level] self.message = args[:message] self.source = args[:source] || "Puppet" @time = Time.now if tags = args[:tags] tags.each { |t| self.tag(t) } end - [:file, :line, :version].each do |attr| + [:file, :line].each do |attr| next unless value = args[attr] send(attr.to_s + "=", value) end Log.newmessage(self) end def message=(msg) raise ArgumentError, "Puppet::Util::Log requires a message" unless msg @message = msg.to_s end def level=(level) raise ArgumentError, "Puppet::Util::Log requires a log level" unless level @level = level.to_sym raise ArgumentError, "Invalid log level #{@level}" unless self.class.validlevel?(@level) # Tag myself with my log level tag(level) end # If they pass a source in to us, we make sure it is a string, and # we retrieve any tags we can. def source=(source) if source.respond_to?(:source_descriptors) descriptors = source.source_descriptors @source = descriptors[:path] descriptors[:tags].each { |t| tag(t) } - [:file, :line, :version].each do |param| + [:file, :line].each do |param| next unless descriptors[param] send(param.to_s + "=", descriptors[param]) end else @source = source.to_s end end def to_report "#{time} #{source} (#{level}): #{to_s}" end def to_s message end end # This is for backward compatibility from when we changed the constant to Puppet::Util::Log # because the reports include the constant name. Apparently the alias was created in # March 2007, should could probably be removed soon. Puppet::Log = Puppet::Util::Log diff --git a/lib/puppet/util/log_paths.rb b/lib/puppet/util/log_paths.rb index f59197ed1..2fefd4505 100644 --- a/lib/puppet/util/log_paths.rb +++ b/lib/puppet/util/log_paths.rb @@ -1,27 +1,27 @@ # Created by Luke Kanies on 2007-07-04. # Copyright (c) 2007. All rights reserved. module Puppet::Util::LogPaths # return the full path to us, for logging and rollback # some classes (e.g., FileTypeRecords) will have to override this def path @path ||= pathbuilder "/" + @path.join("/") end def source_descriptors descriptors = {} descriptors[:tags] = tags - [:path, :file, :line, :version].each do |param| + [:path, :file, :line].each do |param| next unless value = send(param) descriptors[param] = value end descriptors end end diff --git a/lib/puppet/util/logging.rb b/lib/puppet/util/logging.rb index f20444a3b..bc52b17f0 100644 --- a/lib/puppet/util/logging.rb +++ b/lib/puppet/util/logging.rb @@ -1,40 +1,40 @@ # A module to make logging a bit easier. require 'puppet/util/log' module Puppet::Util::Logging def send_log(level, message) Puppet::Util::Log.create({:level => level, :source => log_source, :message => message}.merge(log_metadata)) end # Create a method for each log level. Puppet::Util::Log.eachlevel do |level| define_method(level) do |args| args = args.join(" ") if args.is_a?(Array) send_log(level, args) end end private def is_resource? defined?(Puppet::Type) && is_a?(Puppet::Type) end def is_resource_parameter? defined?(Puppet::Parameter) && is_a?(Puppet::Parameter) end def log_metadata - [:file, :line, :version, :tags].inject({}) do |result, attr| + [:file, :line, :tags].inject({}) do |result, attr| result[attr] = send(attr) if respond_to?(attr) result end end def log_source # We need to guard the existence of the constants, since this module is used by the base Puppet module. (is_resource? or is_resource_parameter?) and respond_to?(:path) and return path.to_s to_s end end diff --git a/lib/puppet/util/metric.rb b/lib/puppet/util/metric.rb index 8f55e7b44..09bbb6137 100644 --- a/lib/puppet/util/metric.rb +++ b/lib/puppet/util/metric.rb @@ -1,187 +1,188 @@ # included so we can test object types require 'puppet' # A class for handling metrics. This is currently ridiculously hackish. class Puppet::Util::Metric attr_accessor :type, :name, :value, :label attr_writer :values attr_writer :basedir # Return a specific value def [](name) if value = @values.find { |v| v[0] == name } return value[2] else return 0 end end def basedir if defined?(@basedir) @basedir else Puppet[:rrddir] end end def create(start = nil) Puppet.settings.use(:main, :metrics) start ||= Time.now.to_i - 5 args = [] if Puppet.features.rrd_legacy? && ! Puppet.features.rrd? @rrd = RRDtool.new(self.path) end values.each { |value| # the 7200 is the heartbeat -- this means that any data that isn't # more frequently than every two hours gets thrown away args.push "DS:#{value[0]}:GAUGE:7200:U:U" } args.push "RRA:AVERAGE:0.5:1:300" begin if Puppet.features.rrd_legacy? && ! Puppet.features.rrd? @rrd.create( Puppet[:rrdinterval].to_i, start, args) else RRD.create( self.path, '-s', Puppet[:rrdinterval].to_i.to_s, '-b', start.to_i.to_s, *args) end rescue => detail raise "Could not create RRD file #{path}: #{detail}" end end def dump if Puppet.features.rrd_legacy? && ! Puppet.features.rrd? puts @rrd.info else puts RRD.info(self.path) end end def graph(range = nil) unless Puppet.features.rrd? || Puppet.features.rrd_legacy? Puppet.warning "RRD library is missing; cannot graph metrics" return end unit = 60 * 60 * 24 colorstack = %w{#00ff00 #ff0000 #0000ff #ffff00 #ff99ff #ff9966 #66ffff #990000 #099000 #000990 #f00990 #0f0f0f #555555 #333333 #ffffff} {:daily => unit, :weekly => unit * 7, :monthly => unit * 30, :yearly => unit * 365}.each do |name, time| file = self.path.sub(/\.rrd$/, "-#{name}.png") args = [file] args.push("--title",self.label) args.push("--imgformat","PNG") args.push("--interlace") i = 0 defs = [] lines = [] #p @values.collect { |s,l| s } values.zip(colorstack).each { |value,color| next if value.nil? # this actually uses the data label defs.push("DEF:#{value[0]}=#{self.path}:#{value[0]}:AVERAGE") lines.push("LINE2:#{value[0]}#{color}:#{value[1]}") } args << defs args << lines args.flatten! if range if Puppet.features.rrd_legacy? && ! Puppet.features.rrd? args.push("--start",range[0],"--end",range[1]) else args.push("--start",range[0].to_i.to_s,"--end",range[1].to_i.to_s) end else if Puppet.features.rrd_legacy? && ! Puppet.features.rrd? args.push("--start", Time.now.to_i - time, "--end", Time.now.to_i) else args.push("--start", (Time.now.to_i - time).to_s, "--end", Time.now.to_i.to_s) end end begin #Puppet.warning "args = #{args}" if Puppet.features.rrd_legacy? && ! Puppet.features.rrd? RRDtool.graph( args ) else RRD.graph( *args ) end rescue => detail Puppet.err "Failed to graph #{self.name}: #{detail}" end end end def initialize(name,label = nil) @name = name.to_s @label = label || labelize(name) @values = [] end def path File.join(self.basedir, @name + ".rrd") end def newvalue(name,value,label = nil) + raise ArgumentError.new("metric name #{name.inspect} is not a string") unless name.is_a? String label ||= labelize(name) @values.push [name,label,value] end def store(time) unless Puppet.features.rrd? || Puppet.features.rrd_legacy? Puppet.warning "RRD library is missing; cannot store metrics" return end self.create(time - 5) unless FileTest.exists?(self.path) if Puppet.features.rrd_legacy? && ! Puppet.features.rrd? @rrd ||= RRDtool.new(self.path) end # XXX this is not terribly error-resistant args = [time] temps = [] values.each { |value| #Puppet.warning "value[0]: #{value[0]}; value[1]: #{value[1]}; value[2]: #{value[2]}; " args.push value[2] temps.push value[0] } arg = args.join(":") template = temps.join(":") begin if Puppet.features.rrd_legacy? && ! Puppet.features.rrd? @rrd.update( template, [ arg ] ) else RRD.update( self.path, '-t', template, arg ) end #system("rrdtool updatev #{self.path} '#{arg}'") rescue => detail raise Puppet::Error, "Failed to update #{self.name}: #{detail}" end end def values @values.sort { |a, b| a[1] <=> b[1] } end private # Convert a name into a label. def labelize(name) name.to_s.capitalize.gsub("_", " ") end end # This is necessary because we changed the class path in early 2007, # and reports directly yaml-dump these metrics, so both client and server # have to agree on the class name. Puppet::Metric = Puppet::Util::Metric diff --git a/spec/integration/application/apply_spec.rb b/spec/integration/application/apply_spec.rb index 840917eff..363aa3469 100755 --- a/spec/integration/application/apply_spec.rb +++ b/spec/integration/application/apply_spec.rb @@ -1,33 +1,32 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../spec_helper' require 'puppet_spec/files' require 'puppet/application/apply' describe "apply" do include PuppetSpec::Files - describe "when applying provided catalogs" do - confine "PSON library is missing; cannot test applying catalogs" => Puppet.features.pson? + describe "when applying provided catalogs", :if => Puppet.features.pson? do it "should be able to apply catalogs provided in a file in pson" do file_to_create = tmpfile("pson_catalog") catalog = Puppet::Resource::Catalog.new resource = Puppet::Resource.new(:file, file_to_create, :parameters => {:content => "my stuff"}) catalog.add_resource resource manifest = tmpfile("manifest") File.open(manifest, "w") { |f| f.print catalog.to_pson } puppet = Puppet::Application[:apply] puppet.options[:catalog] = manifest puppet.apply File.should be_exist(file_to_create) File.read(file_to_create).should == "my stuff" end end end diff --git a/spec/integration/indirector/catalog/queue_spec.rb b/spec/integration/indirector/catalog/queue_spec.rb index 0f8bd41fb..4581e3062 100755 --- a/spec/integration/indirector/catalog/queue_spec.rb +++ b/spec/integration/indirector/catalog/queue_spec.rb @@ -1,61 +1,58 @@ #!/usr/bin/env ruby Dir.chdir(File.dirname(__FILE__)) { (s = lambda { |f| File.exist?(f) ? require(f) : Dir.chdir("..") { s.call(f) } }).call("spec/spec_helper.rb") } require 'puppet/resource/catalog' - -describe "Puppet::Resource::Catalog::Queue" do - confine "Missing pson support; cannot test queue" => Puppet.features.pson? - +describe "Puppet::Resource::Catalog::Queue", :if => Puppet.features.pson? do before do Puppet::Resource::Catalog.indirection.terminus(:queue) @catalog = Puppet::Resource::Catalog.new @one = Puppet::Resource.new(:file, "/one") @two = Puppet::Resource.new(:file, "/two") @catalog.add_resource(@one, @two) @catalog.add_edge(@one, @two) Puppet[:trace] = true end after { Puppet.settings.clear } it "should render catalogs to pson and send them via the queue client when catalogs are saved" do terminus = Puppet::Resource::Catalog.indirection.terminus(:queue) client = mock 'client' terminus.stubs(:client).returns client client.expects(:send_message).with(:catalog, @catalog.to_pson) request = Puppet::Indirector::Request.new(:catalog, :save, "foo", :instance => @catalog) terminus.save(request) end it "should intern catalog messages when they are passed via a subscription" do client = mock 'client' Puppet::Resource::Catalog::Queue.stubs(:client).returns client pson = @catalog.to_pson client.expects(:subscribe).with(:catalog).yields(pson) Puppet.expects(:err).never result = [] Puppet::Resource::Catalog::Queue.subscribe do |catalog| result << catalog end catalog = result.shift catalog.should be_instance_of(Puppet::Resource::Catalog) catalog.resource(:file, "/one").should be_instance_of(Puppet::Resource) catalog.resource(:file, "/two").should be_instance_of(Puppet::Resource) catalog.should be_edge(catalog.resource(:file, "/one"), catalog.resource(:file, "/two")) end end diff --git a/spec/integration/indirector/report/rest_spec.rb b/spec/integration/indirector/report/rest_spec.rb index fdc218975..5b5b2ddd8 100644 --- a/spec/integration/indirector/report/rest_spec.rb +++ b/spec/integration/indirector/report/rest_spec.rb @@ -1,97 +1,93 @@ #!/usr/bin/env ruby Dir.chdir(File.dirname(__FILE__)) { (s = lambda { |f| File.exist?(f) ? require(f) : Dir.chdir("..") { s.call(f) } }).call("spec/spec_helper.rb") } require 'puppet/transaction/report' require 'puppet/network/server' require 'puppet/network/http/webrick/rest' describe "Report REST Terminus" do before do Puppet[:masterport] = 34343 Puppet[:server] = "localhost" # Get a safe temporary file @tmpfile = Tempfile.new("webrick_integration_testing") @dir = @tmpfile.path + "_dir" Puppet.settings[:confdir] = @dir Puppet.settings[:vardir] = @dir Puppet.settings[:group] = Process.gid Puppet.settings[:server] = "127.0.0.1" Puppet.settings[:masterport] = "34343" Puppet::Util::Cacher.expire Puppet[:servertype] = 'webrick' Puppet[:server] = '127.0.0.1' Puppet[:certname] = '127.0.0.1' # Generate the certificate with a local CA Puppet::SSL::Host.ca_location = :local ca = Puppet::SSL::CertificateAuthority.new ca.generate(Puppet[:certname]) unless Puppet::SSL::Certificate.find(Puppet[:certname]) ca.generate("foo.madstop.com") unless Puppet::SSL::Certificate.find(Puppet[:certname]) @host = Puppet::SSL::Host.new(Puppet[:certname]) @params = { :port => 34343, :handlers => [ :report ] } @server = Puppet::Network::Server.new(@params) @server.listen # Let's use REST for our reports :-) @old_terminus = Puppet::Transaction::Report.indirection.terminus_class Puppet::Transaction::Report.terminus_class = :rest # LAK:NOTE We need to have a fake model here so that our indirected methods get # passed through REST; otherwise we'd be stubbing 'save', which would cause an immediate # return. @report = stub_everything 'report' @mock_model = stub_everything 'faked model', :name => "report", :convert_from => @report Puppet::Indirector::Request.any_instance.stubs(:model).returns(@mock_model) Puppet::Network::HTTP::WEBrickREST.any_instance.stubs(:check_authorization) end after do Puppet::Network::HttpPool.expire Puppet::SSL::Host.ca_location = :none Puppet.settings.clear @server.unlisten Puppet::Transaction::Report.terminus_class = @old_terminus end it "should be able to send a report to the server" do @report.expects(:save) - report = Puppet::Transaction::Report.new + report = Puppet::Transaction::Report.new("apply") resourcemetrics = { - :total => 12, - :out_of_sync => 20, - :applied => 45, - :skipped => 1, - :restarted => 23, - :failed_restarts => 1, - :scheduled => 10 + "total" => 12, + "out_of_sync" => 20, + "applied" => 45, + "skipped" => 1, + "restarted" => 23, + "failed_restarts" => 1, + "scheduled" => 10 } report.add_metric(:resources, resourcemetrics) timemetrics = { - :resource1 => 10, - :resource2 => 50, - :resource3 => 40, - :resource4 => 20, + "resource1" => 10, + "resource2" => 50, + "resource3" => 40, + "resource4" => 20, } report.add_metric(:times, timemetrics) - report.add_metric( - :changes, - - :total => 20 - ) + report.add_metric(:changes, "total" => 20) report.save end end diff --git a/spec/integration/network/formats_spec.rb b/spec/integration/network/formats_spec.rb index e6cf28abb..d141abf00 100755 --- a/spec/integration/network/formats_spec.rb +++ b/spec/integration/network/formats_spec.rb @@ -1,110 +1,107 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../spec_helper' require 'puppet/network/formats' class PsonIntTest attr_accessor :string def ==(other) other.class == self.class and string == other.string end def self.from_pson(data) new(data[0]) end def initialize(string) @string = string end def to_pson(*args) { 'type' => self.class.name, 'data' => [@string] }.to_pson(*args) end def self.canonical_order(s) s.gsub(/\{"data":\[(.*?)\],"type":"PsonIntTest"\}/,'{"type":"PsonIntTest","data":[\1]}') end end describe Puppet::Network::FormatHandler.format(:s) do before do @format = Puppet::Network::FormatHandler.format(:s) end it "should support certificates" do @format.should be_supported(Puppet::SSL::Certificate) end it "should not support catalogs" do @format.should_not be_supported(Puppet::Resource::Catalog) end end describe Puppet::Network::FormatHandler.format(:pson) do - describe "when pson is absent" do - confine "'pson' library is present" => (! Puppet.features.pson?) + describe "when pson is absent", :if => (! Puppet.features.pson?) do before do @pson = Puppet::Network::FormatHandler.format(:pson) end it "should not be suitable" do @pson.should_not be_suitable end end - describe "when pson is available" do - confine "Missing 'pson' library" => Puppet.features.pson? - + describe "when pson is available", :if => Puppet.features.pson? do before do @pson = Puppet::Network::FormatHandler.format(:pson) end it "should be able to render an instance to pson" do instance = PsonIntTest.new("foo") PsonIntTest.canonical_order(@pson.render(instance)).should == PsonIntTest.canonical_order('{"type":"PsonIntTest","data":["foo"]}' ) end it "should be able to render arrays to pson" do @pson.render([1,2]).should == '[1,2]' end it "should be able to render arrays containing hashes to pson" do @pson.render([{"one"=>1},{"two"=>2}]).should == '[{"one":1},{"two":2}]' end it "should be able to render multiple instances to pson" do Puppet.features.add(:pson, :libs => ["pson"]) one = PsonIntTest.new("one") two = PsonIntTest.new("two") PsonIntTest.canonical_order(@pson.render([one,two])).should == PsonIntTest.canonical_order('[{"type":"PsonIntTest","data":["one"]},{"type":"PsonIntTest","data":["two"]}]') end it "should be able to intern pson into an instance" do @pson.intern(PsonIntTest, '{"type":"PsonIntTest","data":["foo"]}').should == PsonIntTest.new("foo") end it "should be able to intern pson with no class information into an instance" do @pson.intern(PsonIntTest, '["foo"]').should == PsonIntTest.new("foo") end it "should be able to intern multiple instances from pson" do @pson.intern_multiple(PsonIntTest, '[{"type": "PsonIntTest", "data": ["one"]},{"type": "PsonIntTest", "data": ["two"]}]').should == [ PsonIntTest.new("one"), PsonIntTest.new("two") ] end it "should be able to intern multiple instances from pson with no class information" do @pson.intern_multiple(PsonIntTest, '[["one"],["two"]]').should == [ PsonIntTest.new("one"), PsonIntTest.new("two") ] end end end diff --git a/spec/integration/network/server/mongrel_spec.rb b/spec/integration/network/server/mongrel_spec.rb index cc90773e6..c2815b565 100755 --- a/spec/integration/network/server/mongrel_spec.rb +++ b/spec/integration/network/server/mongrel_spec.rb @@ -1,66 +1,65 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../../spec_helper' require 'puppet/network/server' require 'socket' describe Puppet::Network::Server do - describe "when using mongrel" do - confine "Mongrel is not available" => Puppet.features.mongrel? + describe "when using mongrel", :if => Puppet.features.mongrel? do before :each do Puppet[:servertype] = 'mongrel' Puppet[:server] = '127.0.0.1' @params = { :port => 34346, :handlers => [ :node ] } @server = Puppet::Network::Server.new(@params) end after { Puppet.settings.clear } describe "before listening" do it "should not be reachable at the specified address and port" do lambda { TCPSocket.new('127.0.0.1', 34346) }.should raise_error(Errno::ECONNREFUSED) end end describe "when listening" do it "should be reachable on the specified address and port" do @server.listen lambda { TCPSocket.new('127.0.0.1', 34346) }.should_not raise_error end it "should default to '127.0.0.1' as its bind address" do @server = Puppet::Network::Server.new(@params.merge(:port => 34343)) @server.stubs(:unlisten) # we're breaking listening internally, so we have to keep it from unlistening @server.send(:http_server).expects(:listen).with { |args| args[:address] == "127.0.0.1" } @server.listen end it "should use any specified bind address" do Puppet[:bindaddress] = "0.0.0.0" @server = Puppet::Network::Server.new(@params.merge(:port => 34343)) @server.stubs(:unlisten) # we're breaking listening internally, so we have to keep it from unlistening @server.send(:http_server).expects(:listen).with { |args| args[:address] == "0.0.0.0" } @server.listen end it "should not allow multiple servers to listen on the same address and port" do @server.listen @server2 = Puppet::Network::Server.new(@params) lambda { @server2.listen }.should raise_error end end describe "after unlistening" do it "should not be reachable on the port and address assigned" do @server.listen @server.unlisten lambda { TCPSocket.new('127.0.0.1', 34346) }.should raise_error(Errno::ECONNREFUSED) end end after :each do @server.unlisten if @server.listening? end end end diff --git a/spec/integration/provider/mailalias/aliases_spec.rb b/spec/integration/provider/mailalias/aliases_spec.rb index 0511205f2..1bce13f90 100755 --- a/spec/integration/provider/mailalias/aliases_spec.rb +++ b/spec/integration/provider/mailalias/aliases_spec.rb @@ -1,25 +1,24 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../../spec_helper' require 'puppettest/support/utils' require 'puppettest/fileparsing' provider_class = Puppet::Type.type(:mailalias).provider(:aliases) describe provider_class do - include PuppetTest include PuppetTest::FileParsing include PuppetTest::Support::Utils before :each do @provider = provider_class end # #1560 it "should be able to parse the mailalias examples" do fakedata("data/providers/mailalias/aliases").each { |file| fakedataparse(file) } end end diff --git a/spec/integration/provider/package_spec.rb b/spec/integration/provider/package_spec.rb index 736a34e68..472662d6b 100755 --- a/spec/integration/provider/package_spec.rb +++ b/spec/integration/provider/package_spec.rb @@ -1,26 +1,24 @@ #!/usr/bin/env ruby Dir.chdir(File.dirname(__FILE__)) { (s = lambda { |f| File.exist?(f) ? require(f) : Dir.chdir("..") { s.call(f) } }).call("spec/spec_helper.rb") } describe "Package Provider" do Puppet::Type.type(:package).providers.each do |name| provider = Puppet::Type.type(:package).provider(name) - describe name do - confine "Provider #{name} is not suitable" => provider.suitable? - + describe name, :if => provider.suitable? do it "should fail when asked to install an invalid package" do pending("This test hangs forever with recent versions of RubyGems") if provider.name == :gem pkg = Puppet::Type.newpackage :name => "nosuch#{provider.name}", :provider => provider.name lambda { pkg.provider.install }.should raise_error end it "should be able to get a list of existing packages" do provider.instances.each do |package| package.should be_instance_of(provider) package.properties[:provider].should == provider.name end end end end end diff --git a/spec/integration/provider/service/init_spec.rb b/spec/integration/provider/service/init_spec.rb index d916ab32a..2e2505bd4 100755 --- a/spec/integration/provider/service/init_spec.rb +++ b/spec/integration/provider/service/init_spec.rb @@ -1,32 +1,26 @@ #!/usr/bin/env ruby # Find and load the spec file. Dir.chdir(File.dirname(__FILE__)) { (s = lambda { |f| File.exist?(f) ? require(f) : Dir.chdir("..") { s.call(f) } }).call("spec/spec_helper.rb") } provider = Puppet::Type.type(:service).provider(:init) describe provider do - describe "when running on FreeBSD" do - confine "Not running on FreeBSD" => (Facter.value(:operatingsystem) == "FreeBSD") - + describe "when running on FreeBSD", :if => (Facter.value(:operatingsystem) == "FreeBSD") do it "should set its default path to include /etc/init.d and /usr/local/etc/init.d" do provider.defpath.should == ["/etc/rc.d", "/usr/local/etc/rc.d"] end end - describe "when running on HP-UX" do - confine "Not running on HP-UX" => (Facter.value(:operatingsystem) == "HP-UX") - + describe "when running on HP-UX", :if => (Facter.value(:operatingsystem) == "HP-UX")do it "should set its default path to include /sbin/init.d" do provider.defpath.should == "/sbin/init.d" end end - describe "when not running on FreeBSD or HP-UX" do - confine "Running on HP-UX or FreeBSD" => (! %w{HP-UX FreeBSD}.include?(Facter.value(:operatingsystem))) - + describe "when not running on FreeBSD or HP-UX", :if => (! %w{HP-UX FreeBSD}.include?(Facter.value(:operatingsystem))) do it "should set its default path to include /etc/init.d" do provider.defpath.should == "/etc/init.d" end end end diff --git a/spec/integration/resource/catalog_spec.rb b/spec/integration/resource/catalog_spec.rb index 0a3d47a80..da2b70409 100755 --- a/spec/integration/resource/catalog_spec.rb +++ b/spec/integration/resource/catalog_spec.rb @@ -1,61 +1,60 @@ #!/usr/bin/env ruby # # Created by Luke Kanies on 2007-4-8. # Copyright (c) 2008. All rights reserved. require File.dirname(__FILE__) + '/../../spec_helper' describe Puppet::Resource::Catalog do - describe "when pson is available" do - confine "PSON library is missing" => Puppet.features.pson? + describe "when pson is available", :if => Puppet.features.pson? do it "should support pson" do Puppet::Resource::Catalog.supported_formats.should be_include(:pson) end end describe "when using the indirector" do after { Puppet::Util::Cacher.expire } before do # This is so the tests work w/out networking. Facter.stubs(:to_hash).returns({"hostname" => "foo.domain.com"}) Facter.stubs(:value).returns("eh") end it "should be able to delegate to the :yaml terminus" do Puppet::Resource::Catalog.indirection.stubs(:terminus_class).returns :yaml # Load now, before we stub the exists? method. terminus = Puppet::Resource::Catalog.indirection.terminus(:yaml) terminus.expects(:path).with("me").returns "/my/yaml/file" FileTest.expects(:exist?).with("/my/yaml/file").returns false Puppet::Resource::Catalog.find("me").should be_nil end it "should be able to delegate to the :compiler terminus" do Puppet::Resource::Catalog.indirection.stubs(:terminus_class).returns :compiler # Load now, before we stub the exists? method. compiler = Puppet::Resource::Catalog.indirection.terminus(:compiler) node = mock 'node' node.stub_everything Puppet::Node.expects(:find).returns(node) compiler.expects(:compile).with(node).returns nil Puppet::Resource::Catalog.find("me").should be_nil end it "should pass provided node information directly to the terminus" do terminus = mock 'terminus' Puppet::Resource::Catalog.indirection.stubs(:terminus).returns terminus node = mock 'node' terminus.expects(:find).with { |request| request.options[:use_node] == node } Puppet::Resource::Catalog.find("me", :use_node => node) end end end diff --git a/spec/integration/transaction/report_spec.rb b/spec/integration/transaction/report_spec.rb index eed7acaa9..e7d952eb2 100755 --- a/spec/integration/transaction/report_spec.rb +++ b/spec/integration/transaction/report_spec.rb @@ -1,29 +1,29 @@ #!/usr/bin/env ruby # # Created by Luke Kanies on 2008-4-8. # Copyright (c) 2008. All rights reserved. require File.dirname(__FILE__) + '/../../spec_helper' describe Puppet::Transaction::Report do describe "when using the indirector" do after do Puppet::Util::Cacher.expire Puppet.settings.stubs(:use) end it "should be able to delegate to the :processor terminus" do Puppet::Transaction::Report.indirection.stubs(:terminus_class).returns :processor terminus = Puppet::Transaction::Report.indirection.terminus(:processor) Facter.stubs(:value).returns "host.domain.com" - report = Puppet::Transaction::Report.new + report = Puppet::Transaction::Report.new("apply") terminus.expects(:process).with(report) report.save end end end diff --git a/spec/monkey_patches/add_confine_and_runnable_to_rspec_dsl.rb b/spec/monkey_patches/add_confine_and_runnable_to_rspec_dsl.rb deleted file mode 100644 index 3762b7033..000000000 --- a/spec/monkey_patches/add_confine_and_runnable_to_rspec_dsl.rb +++ /dev/null @@ -1,46 +0,0 @@ -dir = File.expand_path(File.dirname(__FILE__)) -[ "#{dir}/../../lib", "#{dir}/../../test/lib"].each do |dir| - fulldir = File.expand_path(dir) - $LOAD_PATH.unshift(fulldir) unless $LOAD_PATH.include?(fulldir) -end - -require 'spec' -require 'puppettest/runnable_test' - -module Spec - module Runner - class ExampleGroupRunner - def run - prepare - success = true - example_groups.each do |example_group| - unless example_group.runnable? - warn "Skipping unsuitable example group #{example_group.description}: #{example_group.messages.join(", ")}" - next - end - success = success & example_group.run(@options) - Puppet.settings.clear - end - return success - ensure - finish - end - end - end -end - -module Spec - module Example - class ExampleGroup - extend PuppetTest::RunnableTest - end - end -end - -module Test - module Unit - class TestCase - extend PuppetTest::RunnableTest - end - end -end diff --git a/spec/monkey_patches/alias_should_to_must.rb b/spec/monkey_patches/alias_should_to_must.rb index c8744136a..1a1111799 100644 --- a/spec/monkey_patches/alias_should_to_must.rb +++ b/spec/monkey_patches/alias_should_to_must.rb @@ -1,6 +1,8 @@ +require 'rspec' + class Object # This is necessary because the RAL has a 'should' # method. alias :must :should alias :must_not :should_not end diff --git a/spec/shared_behaviours/file_server_terminus.rb b/spec/shared_behaviours/file_server_terminus.rb index 665b46cd5..94a044d2e 100644 --- a/spec/shared_behaviours/file_server_terminus.rb +++ b/spec/shared_behaviours/file_server_terminus.rb @@ -1,45 +1,45 @@ #!/usr/bin/env ruby # # Created by Luke Kanies on 2007-10-18. # Copyright (c) 2007. All rights reserved. -describe "Puppet::Indirector::FileServerTerminus", :shared => true do +shared_examples_for "Puppet::Indirector::FileServerTerminus" do # This only works if the shared behaviour is included before # the 'before' block in the including context. before do Puppet::Util::Cacher.expire FileTest.stubs(:exists?).returns true FileTest.stubs(:exists?).with(Puppet[:fileserverconfig]).returns(true) @path = Tempfile.new("file_server_testing") path = @path.path @path.close! @path = path Dir.mkdir(@path) File.open(File.join(@path, "myfile"), "w") { |f| f.print "my content" } # Use a real mount, so the integration is a bit deeper. @mount1 = Puppet::FileServing::Configuration::Mount::File.new("one") @mount1.path = @path @parser = stub 'parser', :changed? => false @parser.stubs(:parse).returns("one" => @mount1) Puppet::FileServing::Configuration::Parser.stubs(:new).returns(@parser) # Stub out the modules terminus @modules = mock 'modules terminus' @request = Puppet::Indirector::Request.new(:indirection, :method, "puppet://myhost/one/myfile") end it "should use the file server configuration to find files" do @modules.stubs(:find).returns(nil) @terminus.indirection.stubs(:terminus).with(:modules).returns(@modules) path = File.join(@path, "myfile") @terminus.find(@request).should be_instance_of(@test_class) end end diff --git a/spec/shared_behaviours/file_serving.rb b/spec/shared_behaviours/file_serving.rb index 5f5b2b0af..450fff87a 100644 --- a/spec/shared_behaviours/file_serving.rb +++ b/spec/shared_behaviours/file_serving.rb @@ -1,71 +1,71 @@ #!/usr/bin/env ruby # # Created by Luke Kanies on 2007-10-18. # Copyright (c) 2007. All rights reserved. -describe "Puppet::FileServing::Files", :shared => true do +shared_examples_for "Puppet::FileServing::Files" do it "should use the rest terminus when the 'puppet' URI scheme is used and a host name is present" do uri = "puppet://myhost/fakemod/my/file" # It appears that the mocking somehow interferes with the caching subsystem. # This mock somehow causes another terminus to get generated. term = @indirection.terminus(:rest) @indirection.stubs(:terminus).with(:rest).returns term term.expects(:find) @test_class.find(uri) end it "should use the rest terminus when the 'puppet' URI scheme is used, no host name is present, and the process name is not 'puppet' or 'apply'" do uri = "puppet:///fakemod/my/file" Puppet.settings.stubs(:value).returns "foo" Puppet.settings.stubs(:value).with(:name).returns("puppetd") Puppet.settings.stubs(:value).with(:modulepath).returns("") @indirection.terminus(:rest).expects(:find) @test_class.find(uri) end it "should use the file_server terminus when the 'puppet' URI scheme is used, no host name is present, and the process name is 'puppet'" do uri = "puppet:///fakemod/my/file" Puppet::Node::Environment.stubs(:new).returns(stub("env", :name => "testing", :module => nil, :modulepath => [])) Puppet.settings.stubs(:value).returns "" Puppet.settings.stubs(:value).with(:name).returns("puppet") Puppet.settings.stubs(:value).with(:fileserverconfig).returns("/whatever") @indirection.terminus(:file_server).expects(:find) @indirection.terminus(:file_server).stubs(:authorized?).returns(true) @test_class.find(uri) end it "should use the file_server terminus when the 'puppet' URI scheme is used, no host name is present, and the process name is 'apply'" do uri = "puppet:///fakemod/my/file" Puppet::Node::Environment.stubs(:new).returns(stub("env", :name => "testing", :module => nil, :modulepath => [])) Puppet.settings.stubs(:value).returns "" Puppet.settings.stubs(:value).with(:name).returns("apply") Puppet.settings.stubs(:value).with(:fileserverconfig).returns("/whatever") @indirection.terminus(:file_server).expects(:find) @indirection.terminus(:file_server).stubs(:authorized?).returns(true) @test_class.find(uri) end it "should use the file terminus when the 'file' URI scheme is used" do uri = "file:///fakemod/my/file" @indirection.terminus(:file).expects(:find) @test_class.find(uri) end it "should use the file terminus when a fully qualified path is provided" do uri = "/fakemod/my/file" @indirection.terminus(:file).expects(:find) @test_class.find(uri) end it "should use the configuration to test whether the request is allowed" do uri = "fakemod/my/file" mount = mock 'mount' config = stub 'configuration', :split_path => [mount, "eh"] @indirection.terminus(:file_server).stubs(:configuration).returns config @indirection.terminus(:file_server).expects(:find) mount.expects(:allowed?).returns(true) @test_class.find(uri, :node => "foo", :ip => "bar") end end diff --git a/spec/shared_behaviours/memory_terminus.rb b/spec/shared_behaviours/memory_terminus.rb index 5c9f35cca..f9325a969 100644 --- a/spec/shared_behaviours/memory_terminus.rb +++ b/spec/shared_behaviours/memory_terminus.rb @@ -1,32 +1,32 @@ # # Created by Luke Kanies on 2008-4-8. # Copyright (c) 2008. All rights reserved. -describe "A Memory Terminus", :shared => true do +shared_examples_for "A Memory Terminus" do it "should find no instances by default" do @searcher.find(@request).should be_nil end it "should be able to find instances that were previously saved" do @searcher.save(@request) @searcher.find(@request).should equal(@instance) end it "should replace existing saved instances when a new instance with the same name is saved" do @searcher.save(@request) two = stub 'second', :name => @name trequest = stub 'request', :key => @name, :instance => two @searcher.save(trequest) @searcher.find(@request).should equal(two) end it "should be able to remove previously saved instances" do @searcher.save(@request) @searcher.destroy(@request) @searcher.find(@request).should be_nil end it "should fail when asked to destroy an instance that does not exist" do proc { @searcher.destroy(@request) }.should raise_error(ArgumentError) end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index ed4e2c2fb..ed4e826c9 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,79 +1,81 @@ unless defined?(SPEC_HELPER_IS_LOADED) SPEC_HELPER_IS_LOADED = 1 dir = File.expand_path(File.dirname(__FILE__)) $LOAD_PATH.unshift("#{dir}/") $LOAD_PATH.unshift("#{dir}/lib") # a spec-specific test lib dir $LOAD_PATH.unshift("#{dir}/../lib") +$LOAD_PATH.unshift("#{dir}/../test/lib") # Don't want puppet getting the command line arguments for rake or autotest ARGV.clear require 'puppet' require 'mocha' -gem 'rspec', '>=1.2.9' -require 'spec/autorun' +gem 'rspec', '>=2.0.0' # So everyone else doesn't have to include this base constant. module PuppetSpec FIXTURE_DIR = File.join(dir = File.expand_path(File.dirname(__FILE__)), "fixtures") unless defined?(FIXTURE_DIR) end +module PuppetTest +end + require 'lib/puppet_spec/files' require 'monkey_patches/alias_should_to_must' -require 'monkey_patches/add_confine_and_runnable_to_rspec_dsl' require 'monkey_patches/publicize_methods' -Spec::Runner.configure do |config| +RSpec.configure do |config| config.mock_with :mocha - config.prepend_after :each do + config.after :each do Puppet.settings.clear Puppet::Node::Environment.clear Puppet::Util::Storage.clear if defined?($tmpfiles) $tmpfiles.each do |file| file = File.expand_path(file) if Puppet.features.posix? and file !~ /^\/tmp/ and file !~ /^\/var\/folders/ puts "Not deleting tmpfile #{file} outside of /tmp or /var/folders" next elsif Puppet.features.microsoft_windows? tempdir = File.expand_path(File.join(Dir::LOCAL_APPDATA, "Temp")) if file !~ /^#{tempdir}/ puts "Not deleting tmpfile #{file} outside of #{tempdir}" next end end if FileTest.exist?(file) system("chmod -R 755 '#{file}'") system("rm -rf '#{file}'") end end $tmpfiles.clear end @logs.clear Puppet::Util::Log.close_all end - config.prepend_before :each do + config.before :each do # these globals are set by Application $puppet_application_mode = nil $puppet_application_name = nil # Set the confdir and vardir to gibberish so that tests # have to be correctly mocked. Puppet[:confdir] = "/dev/null" Puppet[:vardir] = "/dev/null" # Avoid opening ports to the outside world Puppet.settings[:bindaddress] = "127.0.0.1" @logs = [] Puppet::Util::Log.newdestination(@logs) end end end diff --git a/spec/spec_specs/runnable_spec.rb b/spec/spec_specs/runnable_spec.rb deleted file mode 100644 index da4faca4e..000000000 --- a/spec/spec_specs/runnable_spec.rb +++ /dev/null @@ -1,95 +0,0 @@ -require File.dirname(__FILE__) + '/../spec_helper' - -describe PuppetTest::RunnableTest do - before do - @runnable_test = Class.new.extend(PuppetTest::RunnableTest) - end - - describe "#confine" do - subject { @runnable_test } - - it "should accept a hash" do - subject.confine({}).should_not raise_error(ArgumentError) - end - - it "should accept a message and a block" do - subject.confine(""){}.should_not raise_error(ArgumentError) - end - - end - - describe "#runnable?" do - describe "when the superclass is not runnable" do - before { @runnable_test.stubs(:superclass).returns(stub("unrunnable superclass", :runnable? => false)) } - subject { @runnable_test.runnable? } - - it { should be_false } - end - - describe "when a confine is false" do - before { @runnable_test.confine(:message => false) } - subject { @runnable_test.runnable? } - - it { should be_false } - end - - describe "when a confine has a block that returns false" do - before { @runnable_test.confine(:message){ false } } - subject { @runnable_test.runnable? } - - it { should be_false } - end - - describe "when a confine is true and no false confines" do - before { @runnable_test.confine(:message => true) } - subject { @runnable_test.runnable? } - - it { should be_true } - end - - describe "when a confine has block that returns true and no false confines" do - before { @runnable_test.confine(:message){ true } } - subject { @runnable_test.runnable? } - - it { should be_true } - end - - end - - describe "#messages" do - describe "before runnable? is called" do - subject { @runnable_test.messages } - - it { should == [] } - end - - describe "when runnable? is called and returns false" do - before do - @runnable_test.confine(:message => false) - @runnable_test.runnable? - end - - subject { @runnable_test.messages } - - it "should include the failed confine's message" do - should include(:message) - end - - end - - describe "when runnable? is called whose block returns false" do - before do - @runnable_test.confine(:message){ false } - @runnable_test.runnable? - end - - subject { @runnable_test.messages } - - it "should include the failed confine's message" do - should include(:message) - end - - end - - end -end diff --git a/spec/unit/application/apply_spec.rb b/spec/unit/application/apply_spec.rb index 877c47bcc..4e1744206 100755 --- a/spec/unit/application/apply_spec.rb +++ b/spec/unit/application/apply_spec.rb @@ -1,405 +1,405 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../spec_helper' require 'puppet/application/apply' require 'puppet/file_bucket/dipper' +require 'puppet/configurer' describe Puppet::Application::Apply do before :each do @apply = Puppet::Application[:apply] Puppet::Util::Log.stubs(:newdestination) Puppet::Util::Log.stubs(:level=) end [:debug,:loadclasses,:verbose,:use_nodes,:detailed_exitcodes].each do |option| it "should declare handle_#{option} method" do @apply.should respond_to("handle_#{option}".to_sym) end it "should store argument value when calling handle_#{option}" do @apply.options.expects(:[]=).with(option, 'arg') @apply.send("handle_#{option}".to_sym, 'arg') end end it "should set the code to the provided code when :execute is used" do @apply.options.expects(:[]=).with(:code, 'arg') @apply.send("handle_execute".to_sym, 'arg') end it "should ask Puppet::Application to parse Puppet configuration file" do @apply.should_parse_config?.should be_true end describe "when applying options" do it "should set the log destination with --logdest" do Puppet::Log.expects(:newdestination).with("console") @apply.handle_logdest("console") end it "should put the logset options to true" do @apply.options.expects(:[]=).with(:logset,true) @apply.handle_logdest("console") end end describe "during setup" do before :each do Puppet::Log.stubs(:newdestination) Puppet.stubs(:trap) Puppet::Log.stubs(:level=) Puppet.stubs(:parse_config) Puppet::FileBucket::Dipper.stubs(:new) STDIN.stubs(:read) @apply.options.stubs(:[]).with(any_parameters) end it "should set show_diff on --noop" do Puppet.stubs(:[]=) Puppet.stubs(:[]).with(:config) Puppet.stubs(:[]).with(:noop).returns(true) Puppet.expects(:[]=).with(:show_diff, true) @apply.setup end it "should set console as the log destination if logdest option wasn't provided" do Puppet::Log.expects(:newdestination).with(:console) @apply.setup end it "should set INT trap" do @apply.expects(:trap).with(:INT) @apply.setup end it "should set log level to debug if --debug was passed" do @apply.options.stubs(:[]).with(:debug).returns(true) Puppet::Log.expects(:level=).with(:debug) @apply.setup end it "should set log level to info if --verbose was passed" do @apply.options.stubs(:[]).with(:verbose).returns(true) Puppet::Log.expects(:level=).with(:info) @apply.setup end it "should print puppet config if asked to in Puppet config" do @apply.stubs(:exit) Puppet.settings.stubs(:print_configs?).returns(true) Puppet.settings.expects(:print_configs) @apply.setup end it "should exit after printing puppet config if asked to in Puppet config" do Puppet.settings.stubs(:print_configs?).returns(true) lambda { @apply.setup }.should raise_error(SystemExit) end end describe "when executing" do it "should dispatch to parseonly if parseonly is set" do @apply.stubs(:options).returns({}) Puppet.stubs(:[]).with(:parseonly).returns(true) @apply.expects(:parseonly) @apply.run_command end it "should dispatch to 'apply' if it was called with 'apply'" do @apply.options[:catalog] = "foo" @apply.expects(:apply) @apply.run_command end it "should dispatch to main if parseonly is not set" do @apply.stubs(:options).returns({}) Puppet.stubs(:[]).with(:parseonly).returns(false) @apply.expects(:main) @apply.run_command end describe "the parseonly command" do before :each do Puppet.stubs(:[]).with(:environment) Puppet.stubs(:[]).with(:manifest).returns("site.pp") Puppet.stubs(:err) @apply.stubs(:exit) @apply.options.stubs(:[]).with(:code).returns "some code" @collection = stub_everything Puppet::Resource::TypeCollection.stubs(:new).returns(@collection) end it "should use a Puppet Resource Type Collection to parse the file" do @collection.expects(:perform_initial_import) @apply.parseonly end it "should exit with exit code 0 if no error" do @apply.expects(:exit).with(0) @apply.parseonly end it "should exit with exit code 1 if error" do @collection.stubs(:perform_initial_import).raises(Puppet::ParseError) @apply.expects(:exit).with(1) @apply.parseonly end end describe "the main command" do before :each do Puppet.stubs(:[]) Puppet.settings.stubs(:use) Puppet.stubs(:[]).with(:prerun_command).returns "" Puppet.stubs(:[]).with(:postrun_command).returns "" Puppet.stubs(:[]).with(:trace).returns(true) @apply.options.stubs(:[]) @facts = stub_everything 'facts' Puppet::Node::Facts.stubs(:find).returns(@facts) @node = stub_everything 'node' Puppet::Node.stubs(:find).returns(@node) @catalog = stub_everything 'catalog' @catalog.stubs(:to_ral).returns(@catalog) Puppet::Resource::Catalog.stubs(:find).returns(@catalog) STDIN.stubs(:read) @transaction = stub_everything 'transaction' @catalog.stubs(:apply).returns(@transaction) @apply.stubs(:exit) + + Puppet::Util::Storage.stubs(:load) + Puppet::Configurer.any_instance.stubs(:save_last_run_summary) # to prevent it from trying to write files end it "should set the code to run from --code" do @apply.options.stubs(:[]).with(:code).returns("code to run") Puppet.expects(:[]=).with(:code,"code to run") @apply.main end it "should set the code to run from STDIN if no arguments" do @apply.command_line.stubs(:args).returns([]) STDIN.stubs(:read).returns("code to run") Puppet.expects(:[]=).with(:code,"code to run") @apply.main end it "should set the manifest if a file is passed on command line and the file exists" do File.stubs(:exist?).with('site.pp').returns true @apply.command_line.stubs(:args).returns(['site.pp']) Puppet.expects(:[]=).with(:manifest,"site.pp") @apply.main end it "should raise an error if a file is passed on command line and the file does not exist" do File.stubs(:exist?).with('noexist.pp').returns false @apply.command_line.stubs(:args).returns(['noexist.pp']) lambda { @apply.main }.should raise_error(RuntimeError, 'Could not find file noexist.pp') end it "should set the manifest to the first file and warn other files will be skipped" do File.stubs(:exist?).with('starwarsIV').returns true File.expects(:exist?).with('starwarsI').never @apply.command_line.stubs(:args).returns(['starwarsIV', 'starwarsI', 'starwarsII']) Puppet.expects(:[]=).with(:manifest,"starwarsIV") Puppet.expects(:warning).with('Only one file can be applied per run. Skipping starwarsI, starwarsII') @apply.main end it "should collect the node facts" do Puppet::Node::Facts.expects(:find).returns(@facts) @apply.main end it "should raise an error if we can't find the node" do Puppet::Node::Facts.expects(:find).returns(nil) lambda { @apply.main }.should raise_error end it "should look for the node" do Puppet::Node.expects(:find).returns(@node) @apply.main end it "should raise an error if we can't find the node" do Puppet::Node.expects(:find).returns(nil) lambda { @apply.main }.should raise_error end it "should merge in our node the loaded facts" do @facts.stubs(:values).returns("values") @node.expects(:merge).with("values") @apply.main end it "should load custom classes if loadclasses" do @apply.options.stubs(:[]).with(:loadclasses).returns(true) Puppet.stubs(:[]).with(:classfile).returns("/etc/puppet/classes.txt") FileTest.stubs(:exists?).with("/etc/puppet/classes.txt").returns(true) FileTest.stubs(:readable?).with("/etc/puppet/classes.txt").returns(true) File.stubs(:read).with("/etc/puppet/classes.txt").returns("class") @node.expects(:classes=) @apply.main end it "should compile the catalog" do Puppet::Resource::Catalog.expects(:find).returns(@catalog) @apply.main end it "should transform the catalog to ral" do @catalog.expects(:to_ral).returns(@catalog) @apply.main end it "should finalize the catalog" do @catalog.expects(:finalize) @apply.main end it "should call the prerun and postrun commands on a Configurer instance" do - configurer = stub 'configurer' - - Puppet::Configurer.expects(:new).returns configurer - configurer.expects(:execute_prerun_command) - configurer.expects(:execute_postrun_command) + Puppet::Configurer.any_instance.expects(:execute_prerun_command) + Puppet::Configurer.any_instance.expects(:execute_postrun_command) @apply.main end it "should apply the catalog" do @catalog.expects(:apply).returns(stub_everything('transaction')) @apply.main end describe "with detailed_exitcodes" do it "should exit with report's computed exit status" do Puppet.stubs(:[]).with(:noop).returns(false) @apply.options.stubs(:[]).with(:detailed_exitcodes).returns(true) - report = stub 'report', :exit_status => 666 - @transaction.stubs(:report).returns(report) + Puppet::Transaction::Report.any_instance.stubs(:exit_status).returns(666) @apply.expects(:exit).with(666) @apply.main end it "should always exit with 0 if option is disabled" do Puppet.stubs(:[]).with(:noop).returns(false) @apply.options.stubs(:[]).with(:detailed_exitcodes).returns(false) report = stub 'report', :exit_status => 666 @transaction.stubs(:report).returns(report) @apply.expects(:exit).with(0) @apply.main end it "should always exit with 0 if --noop" do Puppet.stubs(:[]).with(:noop).returns(true) @apply.options.stubs(:[]).with(:detailed_exitcodes).returns(true) report = stub 'report', :exit_status => 666 @transaction.stubs(:report).returns(report) @apply.expects(:exit).with(0) @apply.main end end end describe "the 'apply' command" do it "should read the catalog in from disk if a file name is provided" do @apply.options[:catalog] = "/my/catalog.pson" File.expects(:read).with("/my/catalog.pson").returns "something" Puppet::Resource::Catalog.stubs(:convert_from).with(:pson,'something').returns Puppet::Resource::Catalog.new @apply.apply end it "should read the catalog in from stdin if '-' is provided" do @apply.options[:catalog] = "-" $stdin.expects(:read).returns "something" Puppet::Resource::Catalog.stubs(:convert_from).with(:pson,'something').returns Puppet::Resource::Catalog.new @apply.apply end it "should deserialize the catalog from the default format" do @apply.options[:catalog] = "/my/catalog.pson" File.stubs(:read).with("/my/catalog.pson").returns "something" Puppet::Resource::Catalog.stubs(:default_format).returns :rot13_piglatin Puppet::Resource::Catalog.stubs(:convert_from).with(:rot13_piglatin,'something').returns Puppet::Resource::Catalog.new @apply.apply end it "should fail helpfully if deserializing fails" do @apply.options[:catalog] = "/my/catalog.pson" File.stubs(:read).with("/my/catalog.pson").returns "something syntacically invalid" lambda { @apply.apply }.should raise_error(Puppet::Error) end it "should convert plain data structures into a catalog if deserialization does not do so" do @apply.options[:catalog] = "/my/catalog.pson" File.stubs(:read).with("/my/catalog.pson").returns "something" Puppet::Resource::Catalog.stubs(:convert_from).with(:pson,"something").returns({:foo => "bar"}) Puppet::Resource::Catalog.expects(:pson_create).with({:foo => "bar"}).returns(Puppet::Resource::Catalog.new) @apply.apply end it "should convert the catalog to a RAL catalog and use a Configurer instance to apply it" do @apply.options[:catalog] = "/my/catalog.pson" File.stubs(:read).with("/my/catalog.pson").returns "something" catalog = Puppet::Resource::Catalog.new Puppet::Resource::Catalog.stubs(:convert_from).with(:pson,'something').returns catalog catalog.expects(:to_ral).returns "mycatalog" configurer = stub 'configurer' Puppet::Configurer.expects(:new).returns configurer configurer.expects(:run).with(:catalog => "mycatalog") @apply.apply end end end end diff --git a/spec/unit/application/inspect_spec.rb b/spec/unit/application/inspect_spec.rb index a3cc74d86..b931708c3 100644 --- a/spec/unit/application/inspect_spec.rb +++ b/spec/unit/application/inspect_spec.rb @@ -1,79 +1,102 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../spec_helper' require 'puppet/application/inspect' require 'puppet/resource/catalog' require 'puppet/indirector/catalog/yaml' require 'puppet/indirector/report/rest' describe Puppet::Application::Inspect do before :each do @inspect = Puppet::Application[:inspect] end describe "during setup" do it "should print its configuration if asked" do Puppet[:configprint] = "all" Puppet.settings.expects(:print_configs).returns(true) lambda { @inspect.setup }.should raise_error(SystemExit) end it "should fail if reporting is turned off" do Puppet[:report] = false lambda { @inspect.setup }.should raise_error(/report=true/) end end describe "when executing" do before :each do Puppet[:report] = true Puppet::Util::Log.stubs(:newdestination) Puppet::Transaction::Report::Rest.any_instance.stubs(:save) @inspect.setup end it "should retrieve the local catalog" do Puppet::Resource::Catalog::Yaml.any_instance.expects(:find).with {|request| request.key == Puppet[:certname] }.returns(Puppet::Resource::Catalog.new) @inspect.run_command end it "should save the report to REST" do Puppet::Resource::Catalog::Yaml.any_instance.stubs(:find).returns(Puppet::Resource::Catalog.new) Puppet::Transaction::Report::Rest.any_instance.expects(:save).with {|request| request.instance.host == Puppet[:certname] } @inspect.run_command end it "should audit the specified properties" do catalog = Puppet::Resource::Catalog.new file = Tempfile.new("foo") file.puts("file contents") - file.flush + file.close resource = Puppet::Resource.new(:file, file.path, :parameters => {:audit => "all"}) catalog.add_resource(resource) Puppet::Resource::Catalog::Yaml.any_instance.stubs(:find).returns(catalog) events = nil Puppet::Transaction::Report::Rest.any_instance.expects(:save).with do |request| events = request.instance.resource_statuses.values.first.events end @inspect.run_command properties = events.inject({}) do |property_values, event| property_values.merge(event.property => event.previous_value) end properties["ensure"].should == :file properties["content"].should == "{md5}#{Digest::MD5.hexdigest("file contents\n")}" + properties.has_key?("target").should == false + end + + it "should not report irrelevent attributes if the resource is absent" do + catalog = Puppet::Resource::Catalog.new + file = Tempfile.new("foo") + resource = Puppet::Resource.new(:file, file.path, :parameters => {:audit => "all"}) + file.delete + catalog.add_resource(resource) + Puppet::Resource::Catalog::Yaml.any_instance.stubs(:find).returns(catalog) + + events = nil + + Puppet::Transaction::Report::Rest.any_instance.expects(:save).with do |request| + events = request.instance.resource_statuses.values.first.events + end + + @inspect.run_command + + properties = events.inject({}) do |property_values, event| + property_values.merge(event.property => event.previous_value) + end + properties.should == {"ensure" => :absent} end end after :all do Puppet::Resource::Catalog.indirection.reset_terminus_class Puppet::Transaction::Report.indirection.terminus_class = :processor end end diff --git a/spec/unit/application/kick_spec.rb b/spec/unit/application/kick_spec.rb index dea7ec147..c18a84de3 100755 --- a/spec/unit/application/kick_spec.rb +++ b/spec/unit/application/kick_spec.rb @@ -1,313 +1,311 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../spec_helper' require 'puppet/application/kick' -describe Puppet::Application::Kick do - - confine "Kick's eventloops can only start on POSIX" => Puppet.features.posix? +describe Puppet::Application::Kick, :if => Puppet.features.posix? do before :each do require 'puppet/util/ldap/connection' Puppet::Util::Ldap::Connection.stubs(:new).returns(stub_everything) @kick = Puppet::Application[:kick] Puppet::Util::Log.stubs(:newdestination) Puppet::Util::Log.stubs(:level=) end describe ".new" do it "should take a command-line object as an argument" do command_line = stub_everything "command_line" lambda{ Puppet::Application::Kick.new( command_line ) }.should_not raise_error end end it "should ask Puppet::Application to not parse Puppet configuration file" do @kick.should_parse_config?.should be_false end it "should declare a main command" do @kick.should respond_to(:main) end it "should declare a test command" do @kick.should respond_to(:test) end it "should declare a preinit block" do @kick.should respond_to(:preinit) end describe "during preinit" do before :each do @kick.stubs(:trap) end it "should catch INT and TERM" do @kick.stubs(:trap).with { |arg,block| arg == :INT or arg == :TERM } @kick.preinit end it "should set parallel option to 1" do @kick.preinit @kick.options[:parallel].should == 1 end it "should set verbose by default" do @kick.preinit @kick.options[:verbose].should be_true end it "should set fqdn by default" do @kick.preinit @kick.options[:fqdn].should be_true end it "should set ignoreschedules to 'false'" do @kick.preinit @kick.options[:ignoreschedules].should be_false end it "should set foreground to 'false'" do @kick.preinit @kick.options[:foreground].should be_false end end describe "when applying options" do before do @kick.preinit end [:all, :foreground, :debug, :ping, :test].each do |option| it "should declare handle_#{option} method" do @kick.should respond_to("handle_#{option}".to_sym) end it "should store argument value when calling handle_#{option}" do @kick.options.expects(:[]=).with(option, 'arg') @kick.send("handle_#{option}".to_sym, 'arg') end end it "should add to the host list with the host option" do @kick.handle_host('host') @kick.hosts.should == ['host'] end it "should add to the tag list with the tag option" do @kick.handle_tag('tag') @kick.tags.should == ['tag'] end it "should add to the class list with the class option" do @kick.handle_class('class') @kick.classes.should == ['class'] end end describe "during setup" do before :each do @kick.classes = [] @kick.tags = [] @kick.hosts = [] Puppet::Log.stubs(:level=) @kick.stubs(:trap) @kick.stubs(:puts) Puppet.stubs(:parse_config) @kick.options.stubs(:[]).with(any_parameters) end it "should set log level to debug if --debug was passed" do @kick.options.stubs(:[]).with(:debug).returns(true) Puppet::Log.expects(:level=).with(:debug) @kick.setup end it "should set log level to info if --verbose was passed" do @kick.options.stubs(:[]).with(:verbose).returns(true) Puppet::Log.expects(:level=).with(:info) @kick.setup end it "should Parse puppet config" do Puppet.expects(:parse_config) @kick.setup end describe "when using the ldap node terminus" do before :each do Puppet.stubs(:[]).with(:node_terminus).returns("ldap") end it "should pass the fqdn option to search" do @kick.options.stubs(:[]).with(:fqdn).returns(:something) @kick.options.stubs(:[]).with(:all).returns(true) @kick.stubs(:puts) Puppet::Node.expects(:search).with("whatever",:fqdn => :something).returns([]) @kick.setup end it "should search for all nodes if --all" do @kick.options.stubs(:[]).with(:all).returns(true) @kick.stubs(:puts) Puppet::Node.expects(:search).with("whatever",:fqdn => nil).returns([]) @kick.setup end it "should search for nodes including given classes" do @kick.options.stubs(:[]).with(:all).returns(false) @kick.stubs(:puts) @kick.classes = ['class'] Puppet::Node.expects(:search).with("whatever", :class => "class", :fqdn => nil).returns([]) @kick.setup end end describe "when using regular nodes" do it "should fail if some classes have been specified" do $stderr.stubs(:puts) @kick.classes = ['class'] @kick.expects(:exit).with(24) @kick.setup end end end describe "when running" do before :each do @kick.stubs(:puts) end it "should dispatch to test if --test is used" do @kick.options.stubs(:[]).with(:test).returns(true) @kick.expects(:test) @kick.run_command end it "should dispatch to main if --test is not used" do @kick.options.stubs(:[]).with(:test).returns(false) @kick.expects(:main) @kick.run_command end describe "the test command" do it "should exit with exit code 0 " do @kick.expects(:exit).with(0) @kick.test end end describe "the main command" do before :each do @kick.options.stubs(:[]).with(:parallel).returns(1) @kick.options.stubs(:[]).with(:ping).returns(false) @kick.options.stubs(:[]).with(:ignoreschedules).returns(false) @kick.options.stubs(:[]).with(:foreground).returns(false) @kick.options.stubs(:[]).with(:debug).returns(false) @kick.stubs(:print) @kick.stubs(:exit) @kick.preinit @kick.stubs(:parse_options) @kick.setup $stderr.stubs(:puts) end it "should create as much childs as --parallel" do @kick.options.stubs(:[]).with(:parallel).returns(3) @kick.hosts = ['host1', 'host2', 'host3'] @kick.stubs(:exit).raises(SystemExit) Process.stubs(:wait).returns(1).then.returns(2).then.returns(3).then.raises(Errno::ECHILD) @kick.expects(:fork).times(3).returns(1).then.returns(2).then.returns(3) lambda { @kick.main }.should raise_error end it "should delegate to run_for_host per host" do @kick.hosts = ['host1', 'host2'] @kick.stubs(:exit).raises(SystemExit) @kick.stubs(:fork).returns(1).yields Process.stubs(:wait).returns(1).then.raises(Errno::ECHILD) @kick.expects(:run_for_host).times(2) lambda { @kick.main }.should raise_error end describe "during call of run_for_host" do before do require 'puppet/run' options = { :background => true, :ignoreschedules => false, :tags => [] } @kick.preinit @agent_run = Puppet::Run.new( options.dup ) @agent_run.stubs(:status).returns("success") Puppet::Run.indirection.expects(:terminus_class=).with( :rest ) Puppet::Run.expects(:new).with( options ).returns(@agent_run) end it "should call run on a Puppet::Run for the given host" do @agent_run.expects(:save).with('https://host:8139/production/run/host').returns(@agent_run) @kick.run_for_host('host') end it "should exit the child with 0 on success" do @agent_run.stubs(:status).returns("success") @kick.expects(:exit).with(0) @kick.run_for_host('host') end it "should exit the child with 3 on running" do @agent_run.stubs(:status).returns("running") @kick.expects(:exit).with(3) @kick.run_for_host('host') end it "should exit the child with 12 on unknown answer" do @agent_run.stubs(:status).returns("whatever") @kick.expects(:exit).with(12) @kick.run_for_host('host') end end end end end diff --git a/spec/unit/application/master_spec.rb b/spec/unit/application/master_spec.rb index 216c7dc90..074249a4d 100644 --- a/spec/unit/application/master_spec.rb +++ b/spec/unit/application/master_spec.rb @@ -1,453 +1,451 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../spec_helper' require 'puppet/application/master' require 'puppet/daemon' require 'puppet/network/server' describe Puppet::Application::Master do before :each do @master = Puppet::Application[:master] @daemon = stub_everything 'daemon' Puppet::Daemon.stubs(:new).returns(@daemon) Puppet::Util::Log.stubs(:newdestination) Puppet::Util::Log.stubs(:level=) Puppet::Node.stubs(:terminus_class=) Puppet::Node.stubs(:cache_class=) Puppet::Node::Facts.stubs(:terminus_class=) Puppet::Node::Facts.stubs(:cache_class=) Puppet::Transaction::Report.stubs(:terminus_class=) Puppet::Resource::Catalog.stubs(:terminus_class=) end it "should operate in master run_mode" do @master.class.run_mode.name.should equal(:master) end it "should ask Puppet::Application to parse Puppet configuration file" do @master.should_parse_config?.should be_true end it "should declare a main command" do @master.should respond_to(:main) end it "should declare a parseonly command" do @master.should respond_to(:parseonly) end it "should declare a compile command" do @master.should respond_to(:compile) end it "should declare a preinit block" do @master.should respond_to(:preinit) end describe "during preinit" do before :each do @master.stubs(:trap) end it "should catch INT" do @master.stubs(:trap).with { |arg,block| arg == :INT } @master.preinit end it "should create a Puppet Daemon" do Puppet::Daemon.expects(:new).returns(@daemon) @master.preinit end it "should give ARGV to the Daemon" do argv = stub 'argv' ARGV.stubs(:dup).returns(argv) @daemon.expects(:argv=).with(argv) @master.preinit end end [:debug,:verbose].each do |option| it "should declare handle_#{option} method" do @master.should respond_to("handle_#{option}".to_sym) end it "should store argument value when calling handle_#{option}" do @master.options.expects(:[]=).with(option, 'arg') @master.send("handle_#{option}".to_sym, 'arg') end end describe "when applying options" do before do @master.command_line.stubs(:args).returns([]) end it "should set the log destination with --logdest" do Puppet::Log.expects(:newdestination).with("console") @master.handle_logdest("console") end it "should put the setdest options to true" do @master.options.expects(:[]=).with(:setdest,true) @master.handle_logdest("console") end it "should parse the log destination from ARGV" do @master.command_line.stubs(:args).returns(%w{--logdest /my/file}) Puppet::Util::Log.expects(:newdestination).with("/my/file") @master.parse_options end end describe "during setup" do before :each do Puppet::Log.stubs(:newdestination) Puppet.stubs(:settraps) Puppet::Log.stubs(:level=) Puppet::SSL::CertificateAuthority.stubs(:instance) Puppet::SSL::CertificateAuthority.stubs(:ca?) Puppet.settings.stubs(:use) @master.options.stubs(:[]).with(any_parameters) end it "should set log level to debug if --debug was passed" do @master.options.stubs(:[]).with(:debug).returns(true) Puppet::Log.expects(:level=).with(:debug) @master.setup end it "should set log level to info if --verbose was passed" do @master.options.stubs(:[]).with(:verbose).returns(true) Puppet::Log.expects(:level=).with(:info) @master.setup end it "should set console as the log destination if no --logdest and --daemonize" do @master.stubs(:[]).with(:daemonize).returns(:false) Puppet::Log.expects(:newdestination).with(:syslog) @master.setup end it "should set syslog as the log destination if no --logdest and not --daemonize" do Puppet::Log.expects(:newdestination).with(:syslog) @master.setup end it "should set syslog as the log destination if --rack" do @master.options.stubs(:[]).with(:rack).returns(:true) Puppet::Log.expects(:newdestination).with(:syslog) @master.setup end it "should print puppet config if asked to in Puppet config" do @master.stubs(:exit) Puppet.settings.stubs(:print_configs?).returns(true) Puppet.settings.expects(:print_configs) @master.setup end it "should exit after printing puppet config if asked to in Puppet config" do Puppet.settings.stubs(:print_configs?).returns(true) lambda { @master.setup }.should raise_error(SystemExit) end it "should tell Puppet.settings to use :main,:ssl and :master category" do Puppet.settings.expects(:use).with(:main,:master,:ssl) @master.setup end it "should cache class in yaml" do Puppet::Node.expects(:cache_class=).with(:yaml) @master.setup end describe "with no ca" do it "should set the ca_location to none" do Puppet::SSL::Host.expects(:ca_location=).with(:none) @master.setup end end describe "with a ca configured" do before :each do Puppet::SSL::CertificateAuthority.stubs(:ca?).returns(true) end it "should set the ca_location to local" do Puppet::SSL::Host.expects(:ca_location=).with(:local) @master.setup end it "should tell Puppet.settings to use :ca category" do Puppet.settings.expects(:use).with(:ca) @master.setup end it "should instantiate the CertificateAuthority singleton" do Puppet::SSL::CertificateAuthority.expects(:instance) @master.setup end end end describe "when running" do before do @master.preinit end it "should dispatch to parseonly if parseonly is set" do Puppet.stubs(:[]).with(:parseonly).returns(true) @master.options[:node] = nil @master.expects(:parseonly) @master.run_command end it "should dispatch to compile if called with --compile" do @master.options[:node] = "foo" @master.expects(:compile) @master.run_command end it "should dispatch to main if parseonly is not set" do Puppet.stubs(:[]).with(:parseonly).returns(false) @master.options[:node] = nil @master.expects(:main) @master.run_command end describe "the parseonly command" do before :each do Puppet.stubs(:[]).with(:environment) Puppet.stubs(:[]).with(:manifest).returns("site.pp") Puppet.stubs(:err) @master.stubs(:exit) @collection = stub_everything Puppet::Resource::TypeCollection.stubs(:new).returns(@collection) end it "should use a Puppet Resource Type Collection to parse the file" do @collection.expects(:perform_initial_import) @master.parseonly end it "should exit with exit code 0 if no error" do @master.expects(:exit).with(0) @master.parseonly end it "should exit with exit code 1 if error" do @collection.stubs(:perform_initial_import).raises(Puppet::ParseError) @master.expects(:exit).with(1) @master.parseonly end end describe "the compile command" do before do Puppet.stubs(:[]).with(:environment) Puppet.stubs(:[]).with(:manifest).returns("site.pp") Puppet.stubs(:err) @master.stubs(:jj) @master.stubs(:exit) Puppet.features.stubs(:pson?).returns true end it "should fail if pson isn't available" do Puppet.features.expects(:pson?).returns false lambda { @master.compile }.should raise_error end it "should compile a catalog for the specified node" do @master.options[:node] = "foo" Puppet::Resource::Catalog.expects(:find).with("foo").returns Puppet::Resource::Catalog.new $stdout.stubs(:puts) @master.compile end it "should convert the catalog to a pure-resource catalog and use 'jj' to pretty-print the catalog" do catalog = Puppet::Resource::Catalog.new Puppet::Resource::Catalog.expects(:find).returns catalog catalog.expects(:to_resource).returns("rescat") @master.options[:node] = "foo" @master.expects(:jj).with("rescat") @master.compile end it "should exit with error code 30 if no catalog can be found" do @master.options[:node] = "foo" Puppet::Resource::Catalog.expects(:find).returns nil @master.expects(:exit).with(30) $stderr.expects(:puts) @master.compile end it "should exit with error code 30 if there's a failure" do @master.options[:node] = "foo" Puppet::Resource::Catalog.expects(:find).raises ArgumentError @master.expects(:exit).with(30) $stderr.expects(:puts) @master.compile end end describe "the main command" do before :each do @master.preinit @server = stub_everything 'server' Puppet::Network::Server.stubs(:new).returns(@server) @app = stub_everything 'app' Puppet::SSL::Host.stubs(:localhost) Puppet::SSL::CertificateAuthority.stubs(:ca?) Process.stubs(:uid).returns(1000) Puppet.stubs(:service) Puppet.stubs(:[]) Puppet.stubs(:notice) Puppet.stubs(:start) end it "should create a Server" do Puppet::Network::Server.expects(:new) @master.main end it "should give the server to the daemon" do @daemon.expects(:server=).with(@server) @master.main end it "should create the server with the right XMLRPC handlers" do Puppet::Network::Server.expects(:new).with { |args| args[:xmlrpc_handlers] == [:Status, :FileServer, :Master, :Report, :Filebucket]} @master.main end it "should create the server with a :ca xmlrpc handler if needed" do Puppet.stubs(:[]).with(:ca).returns(true) Puppet::Network::Server.expects(:new).with { |args| args[:xmlrpc_handlers].include?(:CA) } @master.main end it "should generate a SSL cert for localhost" do Puppet::SSL::Host.expects(:localhost) @master.main end it "should make sure to *only* hit the CA for data" do Puppet::SSL::CertificateAuthority.stubs(:ca?).returns(true) Puppet::SSL::Host.expects(:ca_location=).with(:only) @master.main end it "should drop privileges if running as root" do Puppet.features.stubs(:root?).returns true Puppet::Util.expects(:chuser) @master.main end it "should daemonize if needed" do Puppet.stubs(:[]).with(:daemonize).returns(true) @daemon.expects(:daemonize) @master.main end it "should start the service" do @daemon.expects(:start) @master.main end - describe "with --rack" do - confine "Rack is not available" => Puppet.features.rack? - + describe "with --rack", :if => Puppet.features.rack? do before do require 'puppet/network/http/rack' Puppet::Network::HTTP::Rack.stubs(:new).returns(@app) end it "it should create the app with REST and XMLRPC support" do @master.options.stubs(:[]).with(:rack).returns(:true) Puppet::Network::HTTP::Rack.expects(:new).with { |args| args[:xmlrpc_handlers] == [:Status, :FileServer, :Master, :Report, :Filebucket] and args[:protocols] == [:rest, :xmlrpc] } @master.main end it "it should not start a daemon" do @master.options.stubs(:[]).with(:rack).returns(:true) @daemon.expects(:start).never @master.main end it "it should return the app" do @master.options.stubs(:[]).with(:rack).returns(:true) app = @master.main app.should equal(@app) end end end end end diff --git a/spec/unit/application_spec.rb b/spec/unit/application_spec.rb index be7cda340..f68a7e209 100755 --- a/spec/unit/application_spec.rb +++ b/spec/unit/application_spec.rb @@ -1,512 +1,509 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../spec_helper' require 'puppet/application' require 'puppet' require 'getoptlong' describe Puppet::Application do before do @app = Class.new(Puppet::Application).new @appclass = @app.class # avoid actually trying to parse any settings Puppet.settings.stubs(:parse) end describe ".run_mode" do it "should default to user" do @appclass.run_mode.name.should == :user end it "should set and get a value" do @appclass.run_mode :agent @appclass.run_mode.name.should == :agent end end it "should have a run entry-point" do @app.should respond_to(:run) end it "should have a read accessor to options" do @app.should respond_to(:options) end it "should include a default setup method" do @app.should respond_to(:setup) end it "should include a default preinit method" do @app.should respond_to(:preinit) end it "should include a default run_command method" do @app.should respond_to(:run_command) end it "should invoke main as the default" do @app.expects( :main ) @app.run_command end describe 'when invoking clear!' do before :each do Puppet::Application.run_status = :stop_requested Puppet::Application.clear! end it 'should have nil run_status' do Puppet::Application.run_status.should be_nil end it 'should return false for restart_requested?' do Puppet::Application.restart_requested?.should be_false end it 'should return false for stop_requested?' do Puppet::Application.stop_requested?.should be_false end it 'should return false for interrupted?' do Puppet::Application.interrupted?.should be_false end it 'should return true for clear?' do Puppet::Application.clear?.should be_true end end describe 'after invoking stop!' do before :each do Puppet::Application.run_status = nil Puppet::Application.stop! end after :each do Puppet::Application.run_status = nil end it 'should have run_status of :stop_requested' do Puppet::Application.run_status.should == :stop_requested end it 'should return true for stop_requested?' do Puppet::Application.stop_requested?.should be_true end it 'should return false for restart_requested?' do Puppet::Application.restart_requested?.should be_false end it 'should return true for interrupted?' do Puppet::Application.interrupted?.should be_true end it 'should return false for clear?' do Puppet::Application.clear?.should be_false end end describe 'when invoking restart!' do before :each do Puppet::Application.run_status = nil Puppet::Application.restart! end after :each do Puppet::Application.run_status = nil end it 'should have run_status of :restart_requested' do Puppet::Application.run_status.should == :restart_requested end it 'should return true for restart_requested?' do Puppet::Application.restart_requested?.should be_true end it 'should return false for stop_requested?' do Puppet::Application.stop_requested?.should be_false end it 'should return true for interrupted?' do Puppet::Application.interrupted?.should be_true end it 'should return false for clear?' do Puppet::Application.clear?.should be_false end end describe 'when performing a controlled_run' do it 'should not execute block if not :clear?' do Puppet::Application.run_status = :stop_requested target = mock 'target' target.expects(:some_method).never Puppet::Application.controlled_run do target.some_method end end it 'should execute block if :clear?' do Puppet::Application.run_status = nil target = mock 'target' target.expects(:some_method).once Puppet::Application.controlled_run do target.some_method end end - describe 'on POSIX systems' do - confine "HUP works only on POSIX systems" => Puppet.features.posix? - + describe 'on POSIX systems', :if => Puppet.features.posix? do it 'should signal process with HUP after block if restart requested during block execution' do Puppet::Application.run_status = nil target = mock 'target' target.expects(:some_method).once old_handler = trap('HUP') { target.some_method } begin Puppet::Application.controlled_run do Puppet::Application.run_status = :restart_requested end ensure trap('HUP', old_handler) end end end after :each do Puppet::Application.run_status = nil end end describe "when parsing command-line options" do before :each do @app.command_line.stubs(:args).returns([]) Puppet.settings.stubs(:optparse_addargs).returns([]) end it "should pass the banner to the option parser" do option_parser = stub "option parser" option_parser.stubs(:on) option_parser.stubs(:parse!) @app.class.instance_eval do banner "banner" end OptionParser.expects(:new).with("banner").returns(option_parser) @app.parse_options end it "should get options from Puppet.settings.optparse_addargs" do Puppet.settings.expects(:optparse_addargs).returns([]) @app.parse_options end it "should add Puppet.settings options to OptionParser" do Puppet.settings.stubs(:optparse_addargs).returns( [["--option","-o", "Funny Option"]]) Puppet.settings.expects(:handlearg).with("--option", 'true') @app.command_line.stubs(:args).returns(["--option"]) @app.parse_options end it "should ask OptionParser to parse the command-line argument" do @app.command_line.stubs(:args).returns(%w{ fake args }) OptionParser.any_instance.expects(:parse!).with(%w{ fake args }) @app.parse_options end - describe "when using --help" do - confine "rdoc" => Puppet.features.usage? + describe "when using --help", :if => Puppet.features.usage? do it "should call RDoc::usage and exit" do @app.expects(:exit) RDoc.expects(:usage).returns(true) @app.handle_help(nil) end end describe "when using --version" do it "should declare a version option" do @app.should respond_to(:handle_version) end it "should exit after printing the version" do @app.stubs(:puts) lambda { @app.handle_version(nil) }.should raise_error(SystemExit) end end describe "when dealing with an argument not declared directly by the application" do it "should pass it to handle_unknown if this method exists" do Puppet.settings.stubs(:optparse_addargs).returns([["--not-handled", :REQUIRED]]) @app.expects(:handle_unknown).with("--not-handled", "value").returns(true) @app.command_line.stubs(:args).returns(["--not-handled", "value"]) @app.parse_options end it "should pass it to Puppet.settings if handle_unknown says so" do Puppet.settings.stubs(:optparse_addargs).returns([["--topuppet", :REQUIRED]]) @app.stubs(:handle_unknown).with("--topuppet", "value").returns(false) Puppet.settings.expects(:handlearg).with("--topuppet", "value") @app.command_line.stubs(:args).returns(["--topuppet", "value"]) @app.parse_options end it "should pass it to Puppet.settings if there is no handle_unknown method" do Puppet.settings.stubs(:optparse_addargs).returns([["--topuppet", :REQUIRED]]) @app.stubs(:respond_to?).returns(false) Puppet.settings.expects(:handlearg).with("--topuppet", "value") @app.command_line.stubs(:args).returns(["--topuppet", "value"]) @app.parse_options end it "should transform boolean false value to string for Puppet.settings" do Puppet.settings.expects(:handlearg).with("--option", "false") @app.handlearg("--option", false) end it "should transform boolean true value to string for Puppet.settings" do Puppet.settings.expects(:handlearg).with("--option", "true") @app.handlearg("--option", true) end it "should transform boolean option to normal form for Puppet.settings" do Puppet.settings.expects(:handlearg).with("--option", "true") @app.handlearg("--[no-]option", true) end it "should transform boolean option to no- form for Puppet.settings" do Puppet.settings.expects(:handlearg).with("--no-option", "false") @app.handlearg("--[no-]option", false) end end it "should exit if OptionParser raises an error" do $stderr.stubs(:puts) OptionParser.any_instance.stubs(:parse!).raises(OptionParser::ParseError.new("blah blah")) @app.expects(:exit) lambda { @app.parse_options }.should_not raise_error end end describe "when calling default setup" do before :each do @app.stubs(:should_parse_config?).returns(false) @app.options.stubs(:[]) end [ :debug, :verbose ].each do |level| it "should honor option #{level}" do @app.options.stubs(:[]).with(level).returns(true) Puppet::Util::Log.stubs(:newdestination) Puppet::Util::Log.expects(:level=).with(level == :verbose ? :info : :debug) @app.setup end end it "should honor setdest option" do @app.options.stubs(:[]).with(:setdest).returns(false) Puppet::Util::Log.expects(:newdestination).with(:syslog) @app.setup end end describe "when running" do before :each do @app.stubs(:preinit) @app.stubs(:setup) @app.stubs(:parse_options) end it "should call preinit" do @app.stubs(:run_command) @app.expects(:preinit) @app.run end it "should call parse_options" do @app.stubs(:run_command) @app.expects(:parse_options) @app.run end it "should call run_command" do @app.expects(:run_command) @app.run end it "should parse Puppet configuration if should_parse_config is called" do @app.stubs(:run_command) @app.class.should_parse_config Puppet.settings.expects(:parse) @app.run end it "should not parse_option if should_not_parse_config is called" do @app.stubs(:run_command) @app.class.should_not_parse_config Puppet.settings.expects(:parse).never @app.run end it "should parse Puppet configuration if needed" do @app.stubs(:run_command) @app.stubs(:should_parse_config?).returns(true) Puppet.settings.expects(:parse) @app.run end it "should call run_command" do @app.expects(:run_command) @app.run end it "should call main as the default command" do @app.expects(:main) @app.run end it "should warn and exit if no command can be called" do $stderr.expects(:puts) @app.expects(:exit).with(1) @app.run end it "should raise an error if dispatch returns no command" do @app.stubs(:get_command).returns(nil) $stderr.expects(:puts) @app.expects(:exit).with(1) @app.run end it "should raise an error if dispatch returns an invalid command" do @app.stubs(:get_command).returns(:this_function_doesnt_exist) $stderr.expects(:puts) @app.expects(:exit).with(1) @app.run end end describe "when metaprogramming" do describe "when calling option" do it "should create a new method named after the option" do @app.class.option("--test1","-t") do end @app.should respond_to(:handle_test1) end it "should transpose in option name any '-' into '_'" do @app.class.option("--test-dashes-again","-t") do end @app.should respond_to(:handle_test_dashes_again) end it "should create a new method called handle_test2 with option(\"--[no-]test2\")" do @app.class.option("--[no-]test2","-t") do end @app.should respond_to(:handle_test2) end describe "when a block is passed" do it "should create a new method with it" do @app.class.option("--[no-]test2","-t") do raise "I can't believe it, it works!" end lambda { @app.handle_test2 }.should raise_error end it "should declare the option to OptionParser" do OptionParser.any_instance.stubs(:on) OptionParser.any_instance.expects(:on).with { |*arg| arg[0] == "--[no-]test3" } @app.class.option("--[no-]test3","-t") do end @app.parse_options end it "should pass a block that calls our defined method" do OptionParser.any_instance.stubs(:on) OptionParser.any_instance.stubs(:on).with('--test4','-t').yields(nil) @app.expects(:send).with(:handle_test4, nil) @app.class.option("--test4","-t") do end @app.parse_options end end describe "when no block is given" do it "should declare the option to OptionParser" do OptionParser.any_instance.stubs(:on) OptionParser.any_instance.expects(:on).with("--test4","-t") @app.class.option("--test4","-t") @app.parse_options end it "should give to OptionParser a block that adds the the value to the options array" do OptionParser.any_instance.stubs(:on) OptionParser.any_instance.stubs(:on).with("--test4","-t").yields(nil) @app.options.expects(:[]=).with(:test4,nil) @app.class.option("--test4","-t") @app.parse_options end end end end end diff --git a/spec/unit/configurer_spec.rb b/spec/unit/configurer_spec.rb index ebc5768ea..cf73a3706 100755 --- a/spec/unit/configurer_spec.rb +++ b/spec/unit/configurer_spec.rb @@ -1,494 +1,478 @@ #!/usr/bin/env ruby # # Created by Luke Kanies on 2007-11-12. # Copyright (c) 2007. All rights reserved. require File.dirname(__FILE__) + '/../spec_helper' require 'puppet/configurer' describe Puppet::Configurer do before do Puppet.settings.stubs(:use).returns(true) @agent = Puppet::Configurer.new end it "should include the Plugin Handler module" do Puppet::Configurer.ancestors.should be_include(Puppet::Configurer::PluginHandler) end it "should include the Fact Handler module" do Puppet::Configurer.ancestors.should be_include(Puppet::Configurer::FactHandler) end it "should use the puppetdlockfile as its lockfile path" do Puppet.settings.expects(:value).with(:puppetdlockfile).returns("/my/lock") Puppet::Configurer.lockfile_path.should == "/my/lock" end describe "when executing a pre-run hook" do it "should do nothing if the hook is set to an empty string" do Puppet.settings[:prerun_command] = "" Puppet::Util.expects(:exec).never @agent.execute_prerun_command end it "should execute any pre-run command provided via the 'prerun_command' setting" do Puppet.settings[:prerun_command] = "/my/command" Puppet::Util.expects(:execute).with { |args| args[0] == "/my/command" } @agent.execute_prerun_command end it "should fail if the command fails" do Puppet.settings[:prerun_command] = "/my/command" Puppet::Util.expects(:execute).raises Puppet::ExecutionFailure lambda { @agent.execute_prerun_command }.should raise_error(Puppet::Configurer::CommandHookError) end end describe "when executing a post-run hook" do it "should do nothing if the hook is set to an empty string" do Puppet.settings[:postrun_command] = "" Puppet::Util.expects(:exec).never @agent.execute_postrun_command end it "should execute any post-run command provided via the 'postrun_command' setting" do Puppet.settings[:postrun_command] = "/my/command" Puppet::Util.expects(:execute).with { |args| args[0] == "/my/command" } @agent.execute_postrun_command end it "should fail if the command fails" do Puppet.settings[:postrun_command] = "/my/command" Puppet::Util.expects(:execute).raises Puppet::ExecutionFailure lambda { @agent.execute_postrun_command }.should raise_error(Puppet::Configurer::CommandHookError) end end end -describe Puppet::Configurer, "when initializing a report" do - it "should return an instance of a transaction report" do - Puppet.settings.stubs(:use).returns(true) - @agent = Puppet::Configurer.new - @agent.initialize_report.should be_instance_of(Puppet::Transaction::Report) - end -end - describe Puppet::Configurer, "when executing a catalog run" do before do Puppet.settings.stubs(:use).returns(true) @agent = Puppet::Configurer.new @agent.stubs(:prepare) @agent.stubs(:facts_for_uploading).returns({}) @catalog = Puppet::Resource::Catalog.new @catalog.stubs(:apply) @agent.stubs(:retrieve_catalog).returns @catalog Puppet::Util::Log.stubs(:newdestination) Puppet::Util::Log.stubs(:close) end it "should prepare for the run" do @agent.expects(:prepare) @agent.run end it "should initialize a transaction report if one is not provided" do - report = stub 'report' - @agent.expects(:initialize_report).returns report + report = Puppet::Transaction::Report.new("apply") + Puppet::Transaction::Report.expects(:new).returns report @agent.run end it "should pass the new report to the catalog" do - report = stub 'report' - @agent.stubs(:initialize_report).returns report + report = Puppet::Transaction::Report.new("apply") + Puppet::Transaction::Report.stubs(:new).returns report @catalog.expects(:apply).with{|options| options[:report] == report} @agent.run end it "should use the provided report if it was passed one" do - report = stub 'report' - @agent.expects(:initialize_report).never + report = Puppet::Transaction::Report.new("apply") + Puppet::Transaction::Report.expects(:new).never @catalog.expects(:apply).with{|options| options[:report] == report} @agent.run(:report => report) end it "should set the report as a log destination" do - report = stub 'report' - @agent.expects(:initialize_report).returns report + report = Puppet::Transaction::Report.new("apply") + Puppet::Transaction::Report.expects(:new).returns report Puppet::Util::Log.expects(:newdestination).with(report) @agent.run end it "should retrieve the catalog" do @agent.expects(:retrieve_catalog) @agent.run end it "should log a failure and do nothing if no catalog can be retrieved" do @agent.expects(:retrieve_catalog).returns nil Puppet.expects(:err).with "Could not retrieve catalog; skipping run" @agent.run end it "should apply the catalog with all options to :run" do @agent.expects(:retrieve_catalog).returns @catalog @catalog.expects(:apply).with { |args| args[:one] == true } @agent.run :one => true end it "should accept a catalog and use it instead of retrieving a different one" do @agent.expects(:retrieve_catalog).never @catalog.expects(:apply) @agent.run :one => true, :catalog => @catalog end it "should benchmark how long it takes to apply the catalog" do @agent.expects(:benchmark).with(:notice, "Finished catalog run") @agent.expects(:retrieve_catalog).returns @catalog @catalog.expects(:apply).never # because we're not yielding @agent.run end it "should execute post-run hooks after the run" do @agent.expects(:execute_postrun_command) @agent.run end it "should send the report" do - report = stub 'report' - @agent.expects(:initialize_report).returns report + report = Puppet::Transaction::Report.new("apply") + Puppet::Transaction::Report.expects(:new).returns(report) @agent.expects(:send_report).with { |r, trans| r == report } @agent.run end it "should send the transaction report with a reference to the transaction if a run was actually made" do - report = stub 'report' - @agent.expects(:initialize_report).returns report + report = Puppet::Transaction::Report.new("apply") + Puppet::Transaction::Report.expects(:new).returns(report) trans = stub 'transaction' @catalog.expects(:apply).returns trans @agent.expects(:send_report).with { |r, t| t == trans } @agent.run :catalog => @catalog end it "should send the transaction report even if the catalog could not be retrieved" do @agent.expects(:retrieve_catalog).returns nil - report = stub 'report' - @agent.expects(:initialize_report).returns report + report = Puppet::Transaction::Report.new("apply") + Puppet::Transaction::Report.expects(:new).returns(report) @agent.expects(:send_report) @agent.run end it "should send the transaction report even if there is a failure" do @agent.expects(:retrieve_catalog).raises "whatever" - report = stub 'report' - @agent.expects(:initialize_report).returns report + report = Puppet::Transaction::Report.new("apply") + Puppet::Transaction::Report.expects(:new).returns(report) @agent.expects(:send_report) lambda { @agent.run }.should raise_error end it "should remove the report as a log destination when the run is finished" do - report = stub 'report' - @agent.expects(:initialize_report).returns report + report = Puppet::Transaction::Report.new("apply") + Puppet::Transaction::Report.expects(:new).returns(report) Puppet::Util::Log.expects(:close).with(report) @agent.run end it "should return the report as the result of the run" do - report = stub 'report' - @agent.expects(:initialize_report).returns report + report = Puppet::Transaction::Report.new("apply") + Puppet::Transaction::Report.expects(:new).returns(report) @agent.run.should equal(report) end end describe Puppet::Configurer, "when sending a report" do before do Puppet.settings.stubs(:use).returns(true) @configurer = Puppet::Configurer.new - @report = stub 'report' + @report = Puppet::Transaction::Report.new("apply") @trans = stub 'transaction' end - it "should require a report" do - lambda { @configurer.send_report }.should raise_error(ArgumentError) - end - - it "should allow specification of a transaction" do - lambda { @configurer.send_report(@report, @trans) }.should_not raise_error(ArgumentError) - end - - it "should use any provided transaction to add metrics to the report" do - @trans.expects(:generate_report) + it "should finalize the report" do + @report.expects(:finalize_report) @configurer.send_report(@report, @trans) end it "should print a report summary if configured to do so" do Puppet.settings[:summarize] = true @report.expects(:summary).returns "stuff" @configurer.expects(:puts).with("stuff") - @configurer.send_report(@report) + @configurer.send_report(@report, nil) end it "should not print a report summary if not configured to do so" do Puppet.settings[:summarize] = false @configurer.expects(:puts).never - @configurer.send_report(@report) + @configurer.send_report(@report, nil) end it "should save the report if reporting is enabled" do Puppet.settings[:report] = true @report.expects(:save) - @configurer.send_report(@report) + @configurer.send_report(@report, nil) end it "should not save the report if reporting is disabled" do Puppet.settings[:report] = false @report.expects(:save).never - @configurer.send_report(@report) + @configurer.send_report(@report, nil) end it "should log but not fail if saving the report fails" do Puppet.settings[:report] = true @report.expects(:save).raises "whatever" Puppet.expects(:err) - lambda { @configurer.send_report(@report) }.should_not raise_error + lambda { @configurer.send_report(@report, nil) }.should_not raise_error end end describe Puppet::Configurer, "when retrieving a catalog" do before do Puppet.settings.stubs(:use).returns(true) @agent = Puppet::Configurer.new @agent.stubs(:facts_for_uploading).returns({}) @catalog = Puppet::Resource::Catalog.new # this is the default when using a Configurer instance Puppet::Resource::Catalog.indirection.stubs(:terminus_class).returns :rest @agent.stubs(:convert_catalog).returns @catalog end describe "and configured to only retrieve a catalog from the cache" do before do Puppet.settings[:use_cached_catalog] = true end it "should first look in the cache for a catalog" do Puppet::Resource::Catalog.expects(:find).with { |name, options| options[:ignore_terminus] == true }.returns @catalog Puppet::Resource::Catalog.expects(:find).with { |name, options| options[:ignore_cache] == true }.never @agent.retrieve_catalog.should == @catalog end it "should compile a new catalog if none is found in the cache" do Puppet::Resource::Catalog.expects(:find).with { |name, options| options[:ignore_terminus] == true }.returns nil Puppet::Resource::Catalog.expects(:find).with { |name, options| options[:ignore_cache] == true }.returns @catalog @agent.retrieve_catalog.should == @catalog end end describe "when not using a REST terminus for catalogs" do it "should not pass any facts when retrieving the catalog" do @agent.expects(:facts_for_uploading).never Puppet::Resource::Catalog.expects(:find).with { |name, options| options[:facts].nil? }.returns @catalog @agent.retrieve_catalog end end describe "when using a REST terminus for catalogs" do it "should pass the prepared facts and the facts format as arguments when retrieving the catalog" do @agent.expects(:facts_for_uploading).returns(:facts => "myfacts", :facts_format => :foo) Puppet::Resource::Catalog.expects(:find).with { |name, options| options[:facts] == "myfacts" and options[:facts_format] == :foo }.returns @catalog @agent.retrieve_catalog end end it "should use the Catalog class to get its catalog" do Puppet::Resource::Catalog.expects(:find).returns @catalog @agent.retrieve_catalog end it "should use its certname to retrieve the catalog" do Facter.stubs(:value).returns "eh" Puppet.settings[:certname] = "myhost.domain.com" Puppet::Resource::Catalog.expects(:find).with { |name, options| name == "myhost.domain.com" }.returns @catalog @agent.retrieve_catalog end it "should default to returning a catalog retrieved directly from the server, skipping the cache" do Puppet::Resource::Catalog.expects(:find).with { |name, options| options[:ignore_cache] == true }.returns @catalog @agent.retrieve_catalog.should == @catalog end it "should log and return the cached catalog when no catalog can be retrieved from the server" do Puppet::Resource::Catalog.expects(:find).with { |name, options| options[:ignore_cache] == true }.returns nil Puppet::Resource::Catalog.expects(:find).with { |name, options| options[:ignore_terminus] == true }.returns @catalog Puppet.expects(:notice) @agent.retrieve_catalog.should == @catalog end it "should not look in the cache for a catalog if one is returned from the server" do Puppet::Resource::Catalog.expects(:find).with { |name, options| options[:ignore_cache] == true }.returns @catalog Puppet::Resource::Catalog.expects(:find).with { |name, options| options[:ignore_terminus] == true }.never @agent.retrieve_catalog.should == @catalog end it "should return the cached catalog when retrieving the remote catalog throws an exception" do Puppet::Resource::Catalog.expects(:find).with { |name, options| options[:ignore_cache] == true }.raises "eh" Puppet::Resource::Catalog.expects(:find).with { |name, options| options[:ignore_terminus] == true }.returns @catalog @agent.retrieve_catalog.should == @catalog end it "should log and return nil if no catalog can be retrieved from the server and :usecacheonfailure is disabled" do Puppet.stubs(:[]) Puppet.expects(:[]).with(:usecacheonfailure).returns false Puppet::Resource::Catalog.expects(:find).with { |name, options| options[:ignore_cache] == true }.returns nil Puppet.expects(:warning) @agent.retrieve_catalog.should be_nil end it "should return nil if no cached catalog is available and no catalog can be retrieved from the server" do Puppet::Resource::Catalog.expects(:find).with { |name, options| options[:ignore_cache] == true }.returns nil Puppet::Resource::Catalog.expects(:find).with { |name, options| options[:ignore_terminus] == true }.returns nil @agent.retrieve_catalog.should be_nil end it "should convert the catalog before returning" do Puppet::Resource::Catalog.stubs(:find).returns @catalog @agent.expects(:convert_catalog).with { |cat, dur| cat == @catalog }.returns "converted catalog" @agent.retrieve_catalog.should == "converted catalog" end it "should return nil if there is an error while retrieving the catalog" do Puppet::Resource::Catalog.expects(:find).at_least_once.raises "eh" @agent.retrieve_catalog.should be_nil end end describe Puppet::Configurer, "when converting the catalog" do before do Puppet.settings.stubs(:use).returns(true) @agent = Puppet::Configurer.new @catalog = Puppet::Resource::Catalog.new @oldcatalog = stub 'old_catalog', :to_ral => @catalog end it "should convert the catalog to a RAL-formed catalog" do @oldcatalog.expects(:to_ral).returns @catalog @agent.convert_catalog(@oldcatalog, 10).should equal(@catalog) end it "should finalize the catalog" do @catalog.expects(:finalize) @agent.convert_catalog(@oldcatalog, 10) end it "should record the passed retrieval time with the RAL catalog" do @catalog.expects(:retrieval_duration=).with 10 @agent.convert_catalog(@oldcatalog, 10) end it "should write the RAL catalog's class file" do @catalog.expects(:write_class_file) @agent.convert_catalog(@oldcatalog, 10) end end describe Puppet::Configurer, "when preparing for a run" do before do Puppet.settings.stubs(:use).returns(true) @agent = Puppet::Configurer.new @agent.stubs(:dostorage) @agent.stubs(:download_fact_plugins) @agent.stubs(:download_plugins) @agent.stubs(:execute_prerun_command) @facts = {"one" => "two", "three" => "four"} end it "should initialize the metadata store" do @agent.class.stubs(:facts).returns(@facts) @agent.expects(:dostorage) - @agent.prepare + @agent.prepare({}) end it "should download fact plugins" do @agent.expects(:download_fact_plugins) - @agent.prepare + @agent.prepare({}) end it "should download plugins" do @agent.expects(:download_plugins) - @agent.prepare + @agent.prepare({}) end it "should perform the pre-run commands" do @agent.expects(:execute_prerun_command) - @agent.prepare + @agent.prepare({}) end end diff --git a/spec/unit/file_serving/metadata_spec.rb b/spec/unit/file_serving/metadata_spec.rb index aa0dcd511..dd40324bc 100755 --- a/spec/unit/file_serving/metadata_spec.rb +++ b/spec/unit/file_serving/metadata_spec.rb @@ -1,286 +1,286 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../spec_helper' require 'puppet/file_serving/metadata' describe Puppet::FileServing::Metadata do it "should should be a subclass of Base" do Puppet::FileServing::Metadata.superclass.should equal(Puppet::FileServing::Base) end it "should indirect file_metadata" do Puppet::FileServing::Metadata.indirection.name.should == :file_metadata end it "should should include the IndirectionHooks module in its indirection" do Puppet::FileServing::Metadata.indirection.singleton_class.included_modules.should include(Puppet::FileServing::IndirectionHooks) end it "should have a method that triggers attribute collection" do Puppet::FileServing::Metadata.new("/foo/bar").should respond_to(:collect) end it "should support pson serialization" do Puppet::FileServing::Metadata.new("/foo/bar").should respond_to(:to_pson) end it "should support to_pson_data_hash" do Puppet::FileServing::Metadata.new("/foo/bar").should respond_to(:to_pson_data_hash) end it "should support pson deserialization" do Puppet::FileServing::Metadata.should respond_to(:from_pson) end describe "when serializing" do before do @metadata = Puppet::FileServing::Metadata.new("/foo/bar") end it "should perform pson serialization by calling to_pson on it's pson_data_hash" do pdh = mock "data hash" pdh_as_pson = mock "data as pson" @metadata.expects(:to_pson_data_hash).returns pdh pdh.expects(:to_pson).returns pdh_as_pson @metadata.to_pson.should == pdh_as_pson end it "should serialize as FileMetadata" do @metadata.to_pson_data_hash['document_type'].should == "FileMetadata" end it "the data should include the path, relative_path, links, owner, group, mode, checksum, type, and destination" do @metadata.to_pson_data_hash['data'].keys.sort.should == %w{ path relative_path links owner group mode checksum type destination }.sort end it "should pass the path in the hash verbatum" do @metadata.to_pson_data_hash['data']['path'] == @metadata.path end it "should pass the relative_path in the hash verbatum" do @metadata.to_pson_data_hash['data']['relative_path'] == @metadata.relative_path end it "should pass the links in the hash verbatum" do @metadata.to_pson_data_hash['data']['links'] == @metadata.links end it "should pass the path owner in the hash verbatum" do @metadata.to_pson_data_hash['data']['owner'] == @metadata.owner end it "should pass the group in the hash verbatum" do @metadata.to_pson_data_hash['data']['group'] == @metadata.group end it "should pass the mode in the hash verbatum" do @metadata.to_pson_data_hash['data']['mode'] == @metadata.mode end it "should pass the ftype in the hash verbatum as the 'type'" do @metadata.to_pson_data_hash['data']['type'] == @metadata.ftype end it "should pass the destination verbatum" do @metadata.to_pson_data_hash['data']['destination'] == @metadata.destination end it "should pass the checksum in the hash as a nested hash" do @metadata.to_pson_data_hash['data']['checksum'].should be_is_a(Hash) end it "should pass the checksum_type in the hash verbatum as the checksum's type" do @metadata.to_pson_data_hash['data']['checksum']['type'] == @metadata.checksum_type end it "should pass the checksum in the hash verbatum as the checksum's value" do @metadata.to_pson_data_hash['data']['checksum']['value'] == @metadata.checksum end end end describe Puppet::FileServing::Metadata, " when finding the file to use for setting attributes" do before do @path = "/my/path" @metadata = Puppet::FileServing::Metadata.new(@path) # Use a link because it's easier to test -- no checksumming @stat = stub "stat", :uid => 10, :gid => 20, :mode => 0755, :ftype => "link" # Not quite. We don't want to checksum links, but we must because they might be being followed. @checksum = Digest::MD5.hexdigest("some content\n") # Remove these when :managed links are no longer checksumed. @metadata.stubs(:md5_file).returns(@checksum) # end it "should accept a base path path to which the file should be relative" do File.expects(:lstat).with(@path).returns @stat File.expects(:readlink).with(@path).returns "/what/ever" @metadata.collect end it "should use the set base path if one is not provided" do File.expects(:lstat).with(@path).returns @stat File.expects(:readlink).with(@path).returns "/what/ever" @metadata.collect end it "should raise an exception if the file does not exist" do File.expects(:lstat).with(@path).raises(Errno::ENOENT) proc { @metadata.collect}.should raise_error(Errno::ENOENT) end end describe Puppet::FileServing::Metadata, " when collecting attributes" do before do @path = "/my/file" # Use a real file mode, so we can validate the masking is done. @stat = stub 'stat', :uid => 10, :gid => 20, :mode => 33261, :ftype => "file" File.stubs(:lstat).returns(@stat) @checksum = Digest::MD5.hexdigest("some content\n") @metadata = Puppet::FileServing::Metadata.new("/my/file") @metadata.stubs(:md5_file).returns(@checksum) @metadata.collect end it "should be able to produce xmlrpc-style attribute information" do @metadata.should respond_to(:attributes_with_tabs) end # LAK:FIXME This should actually change at some point it "should set the owner by id" do @metadata.owner.should be_instance_of(Fixnum) end # LAK:FIXME This should actually change at some point it "should set the group by id" do @metadata.group.should be_instance_of(Fixnum) end it "should set the owner to the file's current owner" do @metadata.owner.should == 10 end it "should set the group to the file's current group" do @metadata.group.should == 20 end it "should set the mode to the file's masked mode" do @metadata.mode.should == 0755 end it "should set the checksum to the file's current checksum" do @metadata.checksum.should == "{md5}#{@checksum}" end describe "when managing files" do it "should default to a checksum of type MD5" do @metadata.checksum.should == "{md5}#{@checksum}" end it "should give a mtime checksum when checksum_type is set" do time = Time.now @metadata.checksum_type = "mtime" @metadata.expects(:mtime_file).returns(@time) @metadata.collect @metadata.checksum.should == "{mtime}#{@time}" end it "should produce tab-separated mode, type, owner, group, and checksum for xmlrpc" do @metadata.attributes_with_tabs.should == "#{0755.to_s}\tfile\t10\t20\t{md5}#{@checksum}" end end describe "when managing directories" do before do @stat.stubs(:ftype).returns("directory") @time = Time.now @metadata.expects(:ctime_file).returns(@time) end it "should only use checksums of type 'ctime' for directories" do @metadata.collect @metadata.checksum.should == "{ctime}#{@time}" end it "should only use checksums of type 'ctime' for directories even if checksum_type set" do @metadata.checksum_type = "mtime" @metadata.expects(:mtime_file).never @metadata.collect @metadata.checksum.should == "{ctime}#{@time}" end it "should produce tab-separated mode, type, owner, group, and checksum for xmlrpc" do @metadata.collect @metadata.attributes_with_tabs.should == "#{0755.to_s}\tdirectory\t10\t20\t{ctime}#{@time.to_s}" end end describe "when managing links" do before do @stat.stubs(:ftype).returns("link") File.expects(:readlink).with("/my/file").returns("/path/to/link") @metadata.collect @checksum = Digest::MD5.hexdigest("some content\n") # Remove these when :managed links are no longer checksumed. @file.stubs(:md5_file).returns(@checksum) # end it "should read links instead of returning their checksums" do @metadata.destination.should == "/path/to/link" end - it "should produce tab-separated mode, type, owner, group, and destination for xmlrpc" do - pending "We'd like this to be true, but we need to always collect the checksum because in the server/client/server round trip we lose the distintion between manage and follow." + pending "should produce tab-separated mode, type, owner, group, and destination for xmlrpc" do + # "We'd like this to be true, but we need to always collect the checksum because in the server/client/server round trip we lose the distintion between manage and follow." @metadata.attributes_with_tabs.should == "#{0755}\tlink\t10\t20\t/path/to/link" end it "should produce tab-separated mode, type, owner, group, checksum, and destination for xmlrpc" do @metadata.attributes_with_tabs.should == "#{0755}\tlink\t10\t20\t{md5}eb9c2bf0eb63f3a7bc0ea37ef18aeba5\t/path/to/link" end end end describe Puppet::FileServing::Metadata, " when pointing to a link" do describe "when links are managed" do before do @file = Puppet::FileServing::Metadata.new("/base/path/my/file", :links => :manage) File.expects(:lstat).with("/base/path/my/file").returns stub("stat", :uid => 1, :gid => 2, :ftype => "link", :mode => 0755) File.expects(:readlink).with("/base/path/my/file").returns "/some/other/path" @checksum = Digest::MD5.hexdigest("some content\n") # Remove these when :managed links are no longer checksumed. @file.stubs(:md5_file).returns(@checksum) # end it "should store the destination of the link in :destination if links are :manage" do @file.collect @file.destination.should == "/some/other/path" end - it "should not collect the checksum if links are :manage" do - pending "We'd like this to be true, but we need to always collect the checksum because in the server/client/server round trip we lose the distintion between manage and follow." + pending "should not collect the checksum if links are :manage" do + # We'd like this to be true, but we need to always collect the checksum because in the server/client/server round trip we lose the distintion between manage and follow. @file.collect @file.checksum.should be_nil end it "should collect the checksum if links are :manage" do # see pending note above @file.collect @file.checksum.should == "{md5}#{@checksum}" end end describe "when links are followed" do before do @file = Puppet::FileServing::Metadata.new("/base/path/my/file", :links => :follow) File.expects(:stat).with("/base/path/my/file").returns stub("stat", :uid => 1, :gid => 2, :ftype => "file", :mode => 0755) File.expects(:readlink).with("/base/path/my/file").never @checksum = Digest::MD5.hexdigest("some content\n") @file.stubs(:md5_file).returns(@checksum) end it "should not store the destination of the link in :destination if links are :follow" do @file.collect @file.destination.should be_nil end it "should collect the checksum if links are :follow" do @file.collect @file.checksum.should == "{md5}#{@checksum}" end end end diff --git a/spec/unit/indirector/catalog/active_record_spec.rb b/spec/unit/indirector/catalog/active_record_spec.rb index ba8f1dad9..a368fb3a6 100755 --- a/spec/unit/indirector/catalog/active_record_spec.rb +++ b/spec/unit/indirector/catalog/active_record_spec.rb @@ -1,159 +1,157 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../../spec_helper' -describe "Puppet::Resource::Catalog::ActiveRecord" do - confine "Missing Rails" => Puppet.features.rails? - +describe "Puppet::Resource::Catalog::ActiveRecord", :if => Puppet.features.rails? do require 'puppet/rails' before :all do class Tableless < ActiveRecord::Base def self.columns @columns ||= [] end def self.column(name, sql_type=nil, default=nil, null=true) columns << ActiveRecord::ConnectionAdapters::Column.new(name.to_s, default, sql_type.to_s, null) end end class Host < Tableless column :name, :string, :null => false column :ip, :string column :environment, :string column :last_compile, :datetime end end before do require 'puppet/indirector/catalog/active_record' Puppet.features.stubs(:rails?).returns true Puppet::Rails.stubs(:init) @terminus = Puppet::Resource::Catalog::ActiveRecord.new end it "should be a subclass of the ActiveRecord terminus class" do Puppet::Resource::Catalog::ActiveRecord.ancestors.should be_include(Puppet::Indirector::ActiveRecord) end it "should use Puppet::Rails::Host as its ActiveRecord model" do Puppet::Resource::Catalog::ActiveRecord.ar_model.should equal(Puppet::Rails::Host) end describe "when finding an instance" do before do @request = stub 'request', :key => "foo", :options => {:cache_integration_hack => true} end # This hack is here because we don't want to look in the db unless we actually want # to look in the db, but our indirection architecture in 0.24.x isn't flexible # enough to tune that via configuration. it "should return nil unless ':cache_integration_hack' is set to true" do @request.options[:cache_integration_hack] = false Puppet::Rails::Host.expects(:find_by_name).never @terminus.find(@request).should be_nil end it "should use the Hosts ActiveRecord class to find the host" do Puppet::Rails::Host.expects(:find_by_name).with { |key, args| key == "foo" } @terminus.find(@request) end it "should return nil if no host instance can be found" do Puppet::Rails::Host.expects(:find_by_name).returns nil @terminus.find(@request).should be_nil end it "should return a catalog with the same name as the host if the host can be found" do host = stub 'host', :name => "foo", :resources => [] Puppet::Rails::Host.expects(:find_by_name).returns host result = @terminus.find(@request) result.should be_instance_of(Puppet::Resource::Catalog) result.name.should == "foo" end it "should set each of the host's resources as a transportable resource within the catalog" do host = stub 'host', :name => "foo" Puppet::Rails::Host.expects(:find_by_name).returns host res1 = mock 'res1', :to_transportable => "trans_res1" res2 = mock 'res2', :to_transportable => "trans_res2" host.expects(:resources).returns [res1, res2] catalog = stub 'catalog' Puppet::Resource::Catalog.expects(:new).returns catalog catalog.expects(:add_resource).with "trans_res1" catalog.expects(:add_resource).with "trans_res2" @terminus.find(@request) end end describe "when saving an instance" do before do @host = Host.new(:name => "foo") @host.stubs(:merge_resources) @host.stubs(:save) @host.stubs(:railsmark).yields @node = Puppet::Node.new("foo", :environment => "environment") Puppet::Node.indirection.stubs(:find).with("foo").returns(@node) Puppet::Rails::Host.stubs(:find_by_name).returns @host @catalog = Puppet::Resource::Catalog.new("foo") @request = Puppet::Indirector::Request.new(:active_record, :save, @catalog) end it "should find the Rails host with the same name" do Puppet::Rails::Host.expects(:find_by_name).with("foo").returns @host @terminus.save(@request) end it "should create a new Rails host if none can be found" do Puppet::Rails::Host.expects(:find_by_name).with("foo").returns nil Puppet::Rails::Host.expects(:create).with(:name => "foo").returns @host @terminus.save(@request) end it "should set the catalog vertices as resources on the Rails host instance" do @catalog.expects(:vertices).returns "foo" @host.expects(:merge_resources).with("foo") @terminus.save(@request) end it "should set host ip if we could find a matching node" do @node.stubs(:parameters).returns({"ipaddress" => "192.168.0.1"}) @terminus.save(@request) @host.ip.should == '192.168.0.1' end it "should set host environment if we could find a matching node" do @terminus.save(@request) @host.environment.should == "environment" end it "should set the last compile time on the host" do now = Time.now Time.expects(:now).returns now @terminus.save(@request) @host.last_compile.should == now end it "should save the Rails host instance" do @host.expects(:save) @terminus.save(@request) end end end diff --git a/spec/unit/indirector/facts/active_record_spec.rb b/spec/unit/indirector/facts/active_record_spec.rb index 0bdcfcb77..e96e01056 100755 --- a/spec/unit/indirector/facts/active_record_spec.rb +++ b/spec/unit/indirector/facts/active_record_spec.rb @@ -1,105 +1,103 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../../spec_helper' require 'puppet/rails' require 'puppet/node/facts' -describe "Puppet::Node::Facts::ActiveRecord" do - confine "Missing Rails" => Puppet.features.rails? - +describe "Puppet::Node::Facts::ActiveRecord", :if => Puppet.features.rails? do before do require 'puppet/indirector/facts/active_record' Puppet.features.stubs(:rails?).returns true Puppet::Rails.stubs(:init) @terminus = Puppet::Node::Facts::ActiveRecord.new end it "should be a subclass of the ActiveRecord terminus class" do Puppet::Node::Facts::ActiveRecord.ancestors.should be_include(Puppet::Indirector::ActiveRecord) end it "should use Puppet::Rails::Host as its ActiveRecord model" do Puppet::Node::Facts::ActiveRecord.ar_model.should equal(Puppet::Rails::Host) end describe "when finding an instance" do before do @request = stub 'request', :key => "foo" end it "should use the Hosts ActiveRecord class to find the host" do Puppet::Rails::Host.expects(:find_by_name).with { |key, args| key == "foo" } @terminus.find(@request) end it "should include the fact names and values when finding the host" do Puppet::Rails::Host.expects(:find_by_name).with { |key, args| args[:include] == {:fact_values => :fact_name} } @terminus.find(@request) end it "should return nil if no host instance can be found" do Puppet::Rails::Host.expects(:find_by_name).returns nil @terminus.find(@request).should be_nil end it "should convert the node's parameters into a Facts instance if a host instance is found" do host = stub 'host', :name => "foo" host.expects(:get_facts_hash).returns("one" => [mock("two_value", :value => "two")], "three" => [mock("three_value", :value => "four")]) Puppet::Rails::Host.expects(:find_by_name).returns host result = @terminus.find(@request) result.should be_instance_of(Puppet::Node::Facts) result.name.should == "foo" result.values.should == {"one" => "two", "three" => "four"} end it "should convert all single-member arrays into non-arrays" do host = stub 'host', :name => "foo" host.expects(:get_facts_hash).returns("one" => [mock("two_value", :value => "two")]) Puppet::Rails::Host.expects(:find_by_name).returns host @terminus.find(@request).values["one"].should == "two" end end describe "when saving an instance" do before do @host = stub 'host', :name => "foo", :save => nil, :merge_facts => nil Puppet::Rails::Host.stubs(:find_by_name).returns @host @facts = Puppet::Node::Facts.new("foo", "one" => "two", "three" => "four") @request = stub 'request', :key => "foo", :instance => @facts end it "should find the Rails host with the same name" do Puppet::Rails::Host.expects(:find_by_name).with("foo").returns @host @terminus.save(@request) end it "should create a new Rails host if none can be found" do Puppet::Rails::Host.expects(:find_by_name).with("foo").returns nil Puppet::Rails::Host.expects(:create).with(:name => "foo").returns @host @terminus.save(@request) end it "should set the facts as facts on the Rails host instance" do # There is other stuff added to the hash. @host.expects(:merge_facts).with { |args| args["one"] == "two" and args["three"] == "four" } @terminus.save(@request) end it "should save the Rails host instance" do @host.expects(:save) @terminus.save(@request) end end end diff --git a/spec/unit/indirector/facts/couch_spec.rb b/spec/unit/indirector/facts/couch_spec.rb index c0dd54b8a..e3a9d7f14 100644 --- a/spec/unit/indirector/facts/couch_spec.rb +++ b/spec/unit/indirector/facts/couch_spec.rb @@ -1,98 +1,97 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../../spec_helper' require 'puppet/node/facts' -describe "Puppet::Node::Facts::Couch" do - confine "couchrest gem is missing; cannot test couch terminus" => Puppet.features.couchdb? +describe "Puppet::Node::Facts::Couch", :if => Puppet.features.couchdb? do require 'puppet/indirector/facts/couch' if Puppet.features.couchdb? before do @mock_db = mock('couch db') mock_document = CouchRest::Document.new(:_id => fake_request.key, :facts => fake_request.values) mock_document.stubs(:database).returns(@mock_db) @mock_db.stubs(:get).with(fake_request.key).returns(mock_document) Puppet::Node::Facts::Couch.stubs(:db).returns(@mock_db) end subject { Puppet::Node::Facts::Couch } describe "#find" do describe "when the node document exists" do it "should find the request by key" do @mock_db.expects(:get).with(fake_request.key).returns({'_id' => fake_request.key, 'facts' => fake_request.instance.values}) subject.new.find(fake_request).should == fake_request.instance end end describe "when the node document does not exist" do before do @mock_db.expects(:get). with(fake_request.key). raises(RestClient::ResourceNotFound) end it "should return nil" do subject.new.find(fake_request).should be_nil end it "should send Puppet a debug message" do Puppet.expects(:debug).with("No couchdb document with id: test.local") subject.new.find(fake_request).should be_nil end end end describe "#save" do describe "with options" do subject do lambda { Puppet::Node::Facts::Couch.new.save(fake_request([1])) } end it { should raise_error(ArgumentError, "PUT does not accept options") } end it "should save the json to the CouchDB database" do @mock_db.expects(:save_doc).at_least_once.returns({'ok' => true }) subject.new.save(fake_request) end describe "when the document exists" do before do @doc = CouchRest::Document.new(:_id => fake_request.key, :facts => fake_request.instance.values) @mock_db.expects(:get).with(fake_request.key).returns(@doc) end it "saves the document" do @doc.expects(:save) subject.new.save(fake_request) end end describe "when the document does not exist" do before do @mock_db.expects(:get). with(fake_request.key). raises(RestClient::ResourceNotFound) end it "saves the document" do @mock_db.expects(:save_doc) subject.new.save(fake_request) end end end def fake_request(options={}) facts = YAML.load_file(File.join(PuppetSpec::FIXTURE_DIR, 'yaml', 'test.local.yaml')) Struct.new(:instance, :key, :options).new(facts, facts.name, options) end private :fake_request end diff --git a/spec/unit/indirector/indirection_spec.rb b/spec/unit/indirector/indirection_spec.rb index b0e0f019c..1e774fb2e 100755 --- a/spec/unit/indirector/indirection_spec.rb +++ b/spec/unit/indirector/indirection_spec.rb @@ -1,795 +1,795 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../spec_helper' require 'puppet/indirector/indirection' -describe "Indirection Delegator", :shared => true do +shared_examples_for "Indirection Delegator" do it "should create a request object with the appropriate method name and all of the passed arguments" do request = Puppet::Indirector::Request.new(:indirection, :find, "me") @indirection.expects(:request).with(@method, "mystuff", :one => :two).returns request @terminus.stubs(@method) @indirection.send(@method, "mystuff", :one => :two) end it "should let the :select_terminus method choose the terminus using the created request if the :select_terminus method is available" do # Define the method, so our respond_to? hook matches. class << @indirection def select_terminus(request) end end request = Puppet::Indirector::Request.new(:indirection, :find, "me") @indirection.stubs(:request).returns request @indirection.expects(:select_terminus).with(request).returns :test_terminus @indirection.stubs(:check_authorization) @terminus.expects(@method) @indirection.send(@method, "me") end it "should fail if the :select_terminus hook does not return a terminus name" do # Define the method, so our respond_to? hook matches. class << @indirection def select_terminus(request) end end request = stub 'request', :key => "me", :options => {} @indirection.stubs(:request).returns request @indirection.expects(:select_terminus).with(request).returns nil lambda { @indirection.send(@method, "me") }.should raise_error(ArgumentError) end it "should choose the terminus returned by the :terminus_class method if no :select_terminus method is available" do @indirection.expects(:terminus_class).returns :test_terminus @terminus.expects(@method) @indirection.send(@method, "me") end it "should let the appropriate terminus perform the lookup" do @terminus.expects(@method).with { |r| r.is_a?(Puppet::Indirector::Request) } @indirection.send(@method, "me") end end -describe "Delegation Authorizer", :shared => true do +shared_examples_for "Delegation Authorizer" do before do # So the :respond_to? turns out correctly. class << @terminus def authorized? end end end it "should not check authorization if a node name is not provided" do @terminus.expects(:authorized?).never @terminus.stubs(@method) # The quotes are necessary here, else it looks like a block. @request.stubs(:options).returns({}) @indirection.send(@method, "/my/key") end it "should pass the request to the terminus's authorization method" do @terminus.expects(:authorized?).with { |r| r.is_a?(Puppet::Indirector::Request) }.returns(true) @terminus.stubs(@method) @indirection.send(@method, "/my/key", :node => "mynode") end it "should fail if authorization returns false" do @terminus.expects(:authorized?).returns(false) @terminus.stubs(@method) proc { @indirection.send(@method, "/my/key", :node => "mynode") }.should raise_error(ArgumentError) end it "should continue if authorization returns true" do @terminus.expects(:authorized?).returns(true) @terminus.stubs(@method) @indirection.send(@method, "/my/key", :node => "mynode") end end describe Puppet::Indirector::Indirection do after do Puppet::Util::Cacher.expire end describe "when initializing" do # (LAK) I've no idea how to test this, really. it "should store a reference to itself before it consumes its options" do proc { @indirection = Puppet::Indirector::Indirection.new(Object.new, :testingness, :not_valid_option) }.should raise_error Puppet::Indirector::Indirection.instance(:testingness).should be_instance_of(Puppet::Indirector::Indirection) Puppet::Indirector::Indirection.instance(:testingness).delete end it "should keep a reference to the indirecting model" do model = mock 'model' @indirection = Puppet::Indirector::Indirection.new(model, :myind) @indirection.model.should equal(model) end it "should set the name" do @indirection = Puppet::Indirector::Indirection.new(mock('model'), :myind) @indirection.name.should == :myind end it "should require indirections to have unique names" do @indirection = Puppet::Indirector::Indirection.new(mock('model'), :test) proc { Puppet::Indirector::Indirection.new(:test) }.should raise_error(ArgumentError) end it "should extend itself with any specified module" do mod = Module.new @indirection = Puppet::Indirector::Indirection.new(mock('model'), :test, :extend => mod) @indirection.singleton_class.included_modules.should include(mod) end after do @indirection.delete if defined?(@indirection) end end describe "when an instance" do before :each do @terminus_class = mock 'terminus_class' @terminus = mock 'terminus' @terminus_class.stubs(:new).returns(@terminus) @cache = stub 'cache', :name => "mycache" @cache_class = mock 'cache_class' Puppet::Indirector::Terminus.stubs(:terminus_class).with(:test, :cache_terminus).returns(@cache_class) Puppet::Indirector::Terminus.stubs(:terminus_class).with(:test, :test_terminus).returns(@terminus_class) @indirection = Puppet::Indirector::Indirection.new(mock('model'), :test) @indirection.terminus_class = :test_terminus @instance = stub 'instance', :expiration => nil, :expiration= => nil, :name => "whatever" @name = :mything #@request = stub 'instance', :key => "/my/key", :instance => @instance, :options => {} @request = mock 'instance' end it "should allow setting the ttl" do @indirection.ttl = 300 @indirection.ttl.should == 300 end it "should default to the :runinterval setting, converted to an integer, for its ttl" do Puppet.settings.expects(:value).returns "1800" @indirection.ttl.should == 1800 end it "should calculate the current expiration by adding the TTL to the current time" do @indirection.stubs(:ttl).returns(100) now = Time.now Time.stubs(:now).returns now @indirection.expiration.should == (Time.now + 100) end it "should have a method for creating an indirection request instance" do @indirection.should respond_to(:request) end describe "creates a request" do it "should create it with its name as the request's indirection name" do Puppet::Indirector::Request.expects(:new).with { |name, *other| @indirection.name == name } @indirection.request(:funtest, "yayness") end it "should require a method and key" do Puppet::Indirector::Request.expects(:new).with { |name, method, key, *other| method == :funtest and key == "yayness" } @indirection.request(:funtest, "yayness") end it "should support optional arguments" do Puppet::Indirector::Request.expects(:new).with { |name, method, key, other| other == {:one => :two} } @indirection.request(:funtest, "yayness", :one => :two) end it "should not pass options if none are supplied" do Puppet::Indirector::Request.expects(:new).with { |*args| args.length < 4 } @indirection.request(:funtest, "yayness") end it "should return the request" do request = mock 'request' Puppet::Indirector::Request.expects(:new).returns request @indirection.request(:funtest, "yayness").should equal(request) end end describe "and looking for a model instance" do before { @method = :find } it_should_behave_like "Indirection Delegator" it_should_behave_like "Delegation Authorizer" it "should return the results of the delegation" do @terminus.expects(:find).returns(@instance) @indirection.find("me").should equal(@instance) end it "should set the expiration date on any instances without one set" do @terminus.stubs(:find).returns(@instance) @indirection.expects(:expiration).returns :yay @instance.expects(:expiration).returns(nil) @instance.expects(:expiration=).with(:yay) @indirection.find("/my/key") end it "should not override an already-set expiration date on returned instances" do @terminus.stubs(:find).returns(@instance) @indirection.expects(:expiration).never @instance.expects(:expiration).returns(:yay) @instance.expects(:expiration=).never @indirection.find("/my/key") end it "should filter the result instance if the terminus supports it" do @terminus.stubs(:find).returns(@instance) @terminus.stubs(:respond_to?).with(:filter).returns(true) @terminus.expects(:filter).with(@instance) @indirection.find("/my/key") end describe "when caching is enabled" do before do @indirection.cache_class = :cache_terminus @cache_class.stubs(:new).returns(@cache) @instance.stubs(:expired?).returns false end it "should first look in the cache for an instance" do @terminus.stubs(:find).never @cache.expects(:find).returns @instance @indirection.find("/my/key") end it "should not look in the cache if the request specifies not to use the cache" do @terminus.expects(:find).returns @instance @cache.expects(:find).never @cache.stubs(:save) @indirection.find("/my/key", :ignore_cache => true) end it "should still save to the cache even if the cache is being ignored during readin" do @terminus.expects(:find).returns @instance @cache.expects(:save) @indirection.find("/my/key", :ignore_cache => true) end it "should only look in the cache if the request specifies not to use the terminus" do @terminus.expects(:find).never @cache.expects(:find) @indirection.find("/my/key", :ignore_terminus => true) end it "should use a request to look in the cache for cached objects" do @cache.expects(:find).with { |r| r.method == :find and r.key == "/my/key" }.returns @instance @cache.stubs(:save) @indirection.find("/my/key") end it "should return the cached object if it is not expired" do @instance.stubs(:expired?).returns false @cache.stubs(:find).returns @instance @indirection.find("/my/key").should equal(@instance) end it "should not fail if the cache fails" do @terminus.stubs(:find).returns @instance @cache.expects(:find).raises ArgumentError @cache.stubs(:save) lambda { @indirection.find("/my/key") }.should_not raise_error end it "should look in the main terminus if the cache fails" do @terminus.expects(:find).returns @instance @cache.expects(:find).raises ArgumentError @cache.stubs(:save) @indirection.find("/my/key").should equal(@instance) end it "should send a debug log if it is using the cached object" do Puppet.expects(:debug) @cache.stubs(:find).returns @instance @indirection.find("/my/key") end it "should not return the cached object if it is expired" do @instance.stubs(:expired?).returns true @cache.stubs(:find).returns @instance @terminus.stubs(:find).returns nil @indirection.find("/my/key").should be_nil end it "should send an info log if it is using the cached object" do Puppet.expects(:info) @instance.stubs(:expired?).returns true @cache.stubs(:find).returns @instance @terminus.stubs(:find).returns nil @indirection.find("/my/key") end it "should cache any objects not retrieved from the cache" do @cache.expects(:find).returns nil @terminus.expects(:find).returns(@instance) @cache.expects(:save) @indirection.find("/my/key") end it "should use a request to look in the cache for cached objects" do @cache.expects(:find).with { |r| r.method == :find and r.key == "/my/key" }.returns nil @terminus.stubs(:find).returns(@instance) @cache.stubs(:save) @indirection.find("/my/key") end it "should cache the instance using a request with the instance set to the cached object" do @cache.stubs(:find).returns nil @terminus.stubs(:find).returns(@instance) @cache.expects(:save).with { |r| r.method == :save and r.instance == @instance } @indirection.find("/my/key") end it "should send an info log that the object is being cached" do @cache.stubs(:find).returns nil @terminus.stubs(:find).returns(@instance) @cache.stubs(:save) Puppet.expects(:info) @indirection.find("/my/key") end end end describe "and storing a model instance" do before { @method = :save } it_should_behave_like "Indirection Delegator" it_should_behave_like "Delegation Authorizer" it "should return the result of the save" do @terminus.stubs(:save).returns "foo" @indirection.save(@instance).should == "foo" end describe "when caching is enabled" do before do @indirection.cache_class = :cache_terminus @cache_class.stubs(:new).returns(@cache) @instance.stubs(:expired?).returns false end it "should return the result of saving to the terminus" do request = stub 'request', :instance => @instance, :node => nil @indirection.expects(:request).returns request @cache.stubs(:save) @terminus.stubs(:save).returns @instance @indirection.save(@instance).should equal(@instance) end it "should use a request to save the object to the cache" do request = stub 'request', :instance => @instance, :node => nil @indirection.expects(:request).returns request @cache.expects(:save).with(request) @terminus.stubs(:save) @indirection.save(@instance) end it "should not save to the cache if the normal save fails" do request = stub 'request', :instance => @instance, :node => nil @indirection.expects(:request).returns request @cache.expects(:save).never @terminus.expects(:save).raises "eh" lambda { @indirection.save(@instance) }.should raise_error end end end describe "and removing a model instance" do before { @method = :destroy } it_should_behave_like "Indirection Delegator" it_should_behave_like "Delegation Authorizer" it "should return the result of removing the instance" do @terminus.stubs(:destroy).returns "yayness" @indirection.destroy("/my/key").should == "yayness" end describe "when caching is enabled" do before do @indirection.cache_class = :cache_terminus @cache_class.expects(:new).returns(@cache) @instance.stubs(:expired?).returns false end it "should use a request instance to search in and remove objects from the cache" do destroy = stub 'destroy_request', :key => "/my/key", :node => nil find = stub 'destroy_request', :key => "/my/key", :node => nil @indirection.expects(:request).with(:destroy, "/my/key").returns destroy @indirection.expects(:request).with(:find, "/my/key").returns find cached = mock 'cache' @cache.expects(:find).with(find).returns cached @cache.expects(:destroy).with(destroy) @terminus.stubs(:destroy) @indirection.destroy("/my/key") end end end describe "and searching for multiple model instances" do before { @method = :search } it_should_behave_like "Indirection Delegator" it_should_behave_like "Delegation Authorizer" it "should set the expiration date on any instances without one set" do @terminus.stubs(:search).returns([@instance]) @indirection.expects(:expiration).returns :yay @instance.expects(:expiration).returns(nil) @instance.expects(:expiration=).with(:yay) @indirection.search("/my/key") end it "should not override an already-set expiration date on returned instances" do @terminus.stubs(:search).returns([@instance]) @indirection.expects(:expiration).never @instance.expects(:expiration).returns(:yay) @instance.expects(:expiration=).never @indirection.search("/my/key") end it "should return the results of searching in the terminus" do @terminus.expects(:search).returns([@instance]) @indirection.search("/my/key").should == [@instance] end end describe "and expiring a model instance" do describe "when caching is not enabled" do it "should do nothing" do @cache_class.expects(:new).never @indirection.expire("/my/key") end end describe "when caching is enabled" do before do @indirection.cache_class = :cache_terminus @cache_class.expects(:new).returns(@cache) @instance.stubs(:expired?).returns false @cached = stub 'cached', :expiration= => nil, :name => "/my/key" end it "should use a request to find within the cache" do @cache.expects(:find).with { |r| r.is_a?(Puppet::Indirector::Request) and r.method == :find } @indirection.expire("/my/key") end it "should do nothing if no such instance is cached" do @cache.expects(:find).returns nil @indirection.expire("/my/key") end it "should log when expiring a found instance" do @cache.expects(:find).returns @cached @cache.stubs(:save) Puppet.expects(:info) @indirection.expire("/my/key") end it "should set the cached instance's expiration to a time in the past" do @cache.expects(:find).returns @cached @cache.stubs(:save) @cached.expects(:expiration=).with { |t| t < Time.now } @indirection.expire("/my/key") end it "should save the now expired instance back into the cache" do @cache.expects(:find).returns @cached @cached.expects(:expiration=).with { |t| t < Time.now } @cache.expects(:save) @indirection.expire("/my/key") end it "should use a request to save the expired resource to the cache" do @cache.expects(:find).returns @cached @cached.expects(:expiration=).with { |t| t < Time.now } @cache.expects(:save).with { |r| r.is_a?(Puppet::Indirector::Request) and r.instance == @cached and r.method == :save }.returns(@cached) @indirection.expire("/my/key") end end end after :each do @indirection.delete Puppet::Util::Cacher.expire end end describe "when managing indirection instances" do it "should allow an indirection to be retrieved by name" do @indirection = Puppet::Indirector::Indirection.new(mock('model'), :test) Puppet::Indirector::Indirection.instance(:test).should equal(@indirection) end it "should return nil when the named indirection has not been created" do Puppet::Indirector::Indirection.instance(:test).should be_nil end it "should allow an indirection's model to be retrieved by name" do mock_model = mock('model') @indirection = Puppet::Indirector::Indirection.new(mock_model, :test) Puppet::Indirector::Indirection.model(:test).should equal(mock_model) end it "should return nil when no model matches the requested name" do Puppet::Indirector::Indirection.model(:test).should be_nil end after do @indirection.delete if defined?(@indirection) end end describe "when routing to the correct the terminus class" do before do @indirection = Puppet::Indirector::Indirection.new(mock('model'), :test) @terminus = mock 'terminus' @terminus_class = stub 'terminus class', :new => @terminus Puppet::Indirector::Terminus.stubs(:terminus_class).with(:test, :default).returns(@terminus_class) end it "should fail if no terminus class can be picked" do proc { @indirection.terminus_class }.should raise_error(Puppet::DevError) end it "should choose the default terminus class if one is specified" do @indirection.terminus_class = :default @indirection.terminus_class.should equal(:default) end it "should use the provided Puppet setting if told to do so" do Puppet::Indirector::Terminus.stubs(:terminus_class).with(:test, :my_terminus).returns(mock("terminus_class2")) Puppet.settings.expects(:value).with(:my_setting).returns("my_terminus") @indirection.terminus_setting = :my_setting @indirection.terminus_class.should equal(:my_terminus) end it "should fail if the provided terminus class is not valid" do Puppet::Indirector::Terminus.stubs(:terminus_class).with(:test, :nosuchclass).returns(nil) proc { @indirection.terminus_class = :nosuchclass }.should raise_error(ArgumentError) end after do @indirection.delete if defined?(@indirection) end end describe "when specifying the terminus class to use" do before do @indirection = Puppet::Indirector::Indirection.new(mock('model'), :test) @terminus = mock 'terminus' @terminus_class = stub 'terminus class', :new => @terminus end it "should allow specification of a terminus type" do @indirection.should respond_to(:terminus_class=) end it "should fail to redirect if no terminus type has been specified" do proc { @indirection.find("blah") }.should raise_error(Puppet::DevError) end it "should fail when the terminus class name is an empty string" do proc { @indirection.terminus_class = "" }.should raise_error(ArgumentError) end it "should fail when the terminus class name is nil" do proc { @indirection.terminus_class = nil }.should raise_error(ArgumentError) end it "should fail when the specified terminus class cannot be found" do Puppet::Indirector::Terminus.expects(:terminus_class).with(:test, :foo).returns(nil) proc { @indirection.terminus_class = :foo }.should raise_error(ArgumentError) end it "should select the specified terminus class if a terminus class name is provided" do Puppet::Indirector::Terminus.expects(:terminus_class).with(:test, :foo).returns(@terminus_class) @indirection.terminus(:foo).should equal(@terminus) end it "should use the configured terminus class if no terminus name is specified" do Puppet::Indirector::Terminus.stubs(:terminus_class).with(:test, :foo).returns(@terminus_class) @indirection.terminus_class = :foo @indirection.terminus.should equal(@terminus) end after do @indirection.delete if defined?(@indirection) end end describe "when managing terminus instances" do before do @indirection = Puppet::Indirector::Indirection.new(mock('model'), :test) @terminus = mock 'terminus' @terminus_class = mock 'terminus class' Puppet::Indirector::Terminus.stubs(:terminus_class).with(:test, :foo).returns(@terminus_class) end it "should create an instance of the chosen terminus class" do @terminus_class.stubs(:new).returns(@terminus) @indirection.terminus(:foo).should equal(@terminus) end # Make sure it caches the terminus. it "should return the same terminus instance each time for a given name" do @terminus_class.stubs(:new).returns(@terminus) @indirection.terminus(:foo).should equal(@terminus) @indirection.terminus(:foo).should equal(@terminus) end it "should not create a terminus instance until one is actually needed" do Puppet::Indirector.expects(:terminus).never indirection = Puppet::Indirector::Indirection.new(mock('model'), :lazytest) end after do @indirection.delete end end describe "when deciding whether to cache" do before do @indirection = Puppet::Indirector::Indirection.new(mock('model'), :test) @terminus = mock 'terminus' @terminus_class = mock 'terminus class' @terminus_class.stubs(:new).returns(@terminus) Puppet::Indirector::Terminus.stubs(:terminus_class).with(:test, :foo).returns(@terminus_class) @indirection.terminus_class = :foo end it "should provide a method for setting the cache terminus class" do @indirection.should respond_to(:cache_class=) end it "should fail to cache if no cache type has been specified" do proc { @indirection.cache }.should raise_error(Puppet::DevError) end it "should fail to set the cache class when the cache class name is an empty string" do proc { @indirection.cache_class = "" }.should raise_error(ArgumentError) end it "should allow resetting the cache_class to nil" do @indirection.cache_class = nil @indirection.cache_class.should be_nil end it "should fail to set the cache class when the specified cache class cannot be found" do Puppet::Indirector::Terminus.expects(:terminus_class).with(:test, :foo).returns(nil) proc { @indirection.cache_class = :foo }.should raise_error(ArgumentError) end after do @indirection.delete end end describe "when using a cache" do before :each do Puppet.settings.stubs(:value).with("test_terminus").returns("test_terminus") @terminus_class = mock 'terminus_class' @terminus = mock 'terminus' @terminus_class.stubs(:new).returns(@terminus) @cache = mock 'cache' @cache_class = mock 'cache_class' Puppet::Indirector::Terminus.stubs(:terminus_class).with(:test, :cache_terminus).returns(@cache_class) Puppet::Indirector::Terminus.stubs(:terminus_class).with(:test, :test_terminus).returns(@terminus_class) @indirection = Puppet::Indirector::Indirection.new(mock('model'), :test) @indirection.terminus_class = :test_terminus end describe "and managing the cache terminus" do it "should not create a cache terminus at initialization" do # This is weird, because all of the code is in the setup. If we got # new called on the cache class, we'd get an exception here. end it "should reuse the cache terminus" do @cache_class.expects(:new).returns(@cache) Puppet.settings.stubs(:value).with("test_cache").returns("cache_terminus") @indirection.cache_class = :cache_terminus @indirection.cache.should equal(@cache) @indirection.cache.should equal(@cache) end end describe "and saving" do end describe "and finding" do end after :each do @indirection.delete end end end diff --git a/spec/unit/indirector/ldap_spec.rb b/spec/unit/indirector/ldap_spec.rb index 31a3406e9..c071f870e 100755 --- a/spec/unit/indirector/ldap_spec.rb +++ b/spec/unit/indirector/ldap_spec.rb @@ -1,143 +1,139 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../spec_helper' require 'puppet/indirector/ldap' describe Puppet::Indirector::Ldap do before do @indirection = stub 'indirection', :name => :testing Puppet::Indirector::Indirection.stubs(:instance).returns(@indirection) @ldap_class = Class.new(Puppet::Indirector::Ldap) do def self.to_s "Testing::Mytype" end end @connection = mock 'ldap' @searcher = @ldap_class.new end describe "when searching ldap" do before do # Stub everything, and we can selectively replace with an expect as # we need to for testing. @searcher.stubs(:connection).returns(@connection) @searcher.stubs(:search_filter).returns(:filter) @searcher.stubs(:search_base).returns(:base) @searcher.stubs(:process) @request = stub 'request', :key => "yay" end it "should call the ldapsearch method with the search filter" do @searcher.expects(:search_filter).with("yay").returns("yay's filter") @searcher.expects(:ldapsearch).with("yay's filter") @searcher.find @request end it "should fail if no block is passed to the ldapsearch method" do proc { @searcher.ldapsearch("blah") }.should raise_error(ArgumentError) end it "should use the results of the ldapbase method as the ldap search base" do @searcher.stubs(:search_base).returns("mybase") @connection.expects(:search).with do |*args| args[0].should == "mybase" true end @searcher.find @request end it "should default to the value of the :search_base setting as the result of the ldapbase method" do Puppet.expects(:[]).with(:ldapbase).returns("myldapbase") searcher = @ldap_class.new searcher.search_base.should == "myldapbase" end it "should use the results of the :search_attributes method as the list of attributes to return" do @searcher.stubs(:search_attributes).returns(:myattrs) @connection.expects(:search).with do |*args| args[3].should == :myattrs true end @searcher.find @request end it "should use depth 2 when searching" do @connection.expects(:search).with do |*args| args[1].should == 2 true end @searcher.find @request end it "should call process() on the first found entry" do @connection.expects(:search).yields("myresult") @searcher.expects(:process).with("myresult") @searcher.find @request end it "should reconnect and retry the search if there is a failure" do run = false @connection.stubs(:search).with do |*args| if run true else run = true raise "failed" end end.yields("myresult") @searcher.expects(:process).with("myresult") @searcher.find @request end it "should not reconnect on failure more than once" do count = 0 @connection.stubs(:search).with do |*args| count += 1 raise ArgumentError, "yay" end proc { @searcher.find(@request) }.should raise_error(Puppet::Error) count.should == 2 end it "should return true if an entry is found" do @connection.expects(:search).yields("result") @searcher.ldapsearch("whatever") { |r| }.should be_true end end - describe "when connecting to ldap" do - confine "LDAP is not available" => Puppet.features.ldap? - + describe "when connecting to ldap", :if => Puppet.features.ldap? do it "should create and start a Util::Ldap::Connection instance" do conn = mock 'connection', :connection => "myconn", :start => nil Puppet::Util::Ldap::Connection.expects(:instance).returns conn @searcher.connection.should == "myconn" end it "should only create the ldap connection when asked for it the first time" do conn = mock 'connection', :connection => "myconn", :start => nil Puppet::Util::Ldap::Connection.expects(:instance).returns conn @searcher.connection end it "should cache the connection" do conn = mock 'connection', :connection => "myconn", :start => nil Puppet::Util::Ldap::Connection.expects(:instance).returns conn @searcher.connection.should equal(@searcher.connection) end end - describe "when reconnecting to ldap" do - confine "Not running on culain as root" => (Puppet.features.root? and Facter.value("hostname") == "culain") - + describe "when reconnecting to ldap", :if => (Puppet.features.root? and Facter.value("hostname") == "culain") do it "should reconnect to ldap when connections are lost" end end diff --git a/spec/unit/indirector/node/active_record_spec.rb b/spec/unit/indirector/node/active_record_spec.rb index 3540ef738..69229e144 100755 --- a/spec/unit/indirector/node/active_record_spec.rb +++ b/spec/unit/indirector/node/active_record_spec.rb @@ -1,40 +1,38 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../../spec_helper' require 'puppet/node' -describe "Puppet::Node::ActiveRecord" do +describe "Puppet::Node::ActiveRecord", :if => Puppet.features.rails? && Puppet.features.sqlite? do include PuppetSpec::Files - confine "Missing Rails" => Puppet.features.rails? - confine "Missing sqlite" => Puppet.features.sqlite? before do require 'puppet/indirector/node/active_record' end it "should be a subclass of the ActiveRecord terminus class" do Puppet::Node::ActiveRecord.ancestors.should be_include(Puppet::Indirector::ActiveRecord) end it "should use Puppet::Rails::Host as its ActiveRecord model" do Puppet::Node::ActiveRecord.ar_model.should equal(Puppet::Rails::Host) end it "should call fact_merge when a node is found" do db_instance = stub 'db_instance' Puppet::Node::ActiveRecord.ar_model.expects(:find_by_name).returns db_instance node = Puppet::Node.new("foo") db_instance.expects(:to_puppet).returns node Puppet[:statedir] = tmpdir('active_record_tmp') Puppet[:railslog] = '$statedir/rails.log' ar = Puppet::Node::ActiveRecord.new node.expects(:fact_merge) request = Puppet::Indirector::Request.new(:node, :find, "what.ever") ar.find(request) end end diff --git a/spec/unit/indirector/queue_spec.rb b/spec/unit/indirector/queue_spec.rb index 83e9c771d..00463ee0f 100755 --- a/spec/unit/indirector/queue_spec.rb +++ b/spec/unit/indirector/queue_spec.rb @@ -1,123 +1,121 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../spec_helper' require 'puppet/indirector/queue' class Puppet::Indirector::Queue::TestClient end class FooExampleData attr_accessor :name def self.pson_create(pson) new(pson['data'].to_sym) end def initialize(name = nil) @name = name if name end def render(format = :pson) to_pson end def to_pson(*args) {:type => self.class.to_s, :data => name}.to_pson(*args) end end -describe Puppet::Indirector::Queue do - confine "PSON library is missing; cannot test queueing" => Puppet.features.pson? - +describe Puppet::Indirector::Queue, :if => Puppet.features.pson? do before :each do @model = mock 'model' @indirection = stub 'indirection', :name => :my_queue, :register_terminus_type => nil, :model => @model Puppet::Indirector::Indirection.stubs(:instance).with(:my_queue).returns(@indirection) @store_class = Class.new(Puppet::Indirector::Queue) do def self.to_s 'MyQueue::MyType' end end @store = @store_class.new @subject_class = FooExampleData @subject = @subject_class.new @subject.name = :me Puppet.settings.stubs(:value).returns("bogus setting data") Puppet.settings.stubs(:value).with(:queue_type).returns(:test_client) Puppet::Util::Queue.stubs(:queue_type_to_class).with(:test_client).returns(Puppet::Indirector::Queue::TestClient) @request = stub 'request', :key => :me, :instance => @subject end it "should require PSON" do Puppet.features.expects(:pson?).returns false lambda { @store_class.new }.should raise_error(ArgumentError) end it 'should use the correct client type and queue' do @store.queue.should == :my_queue @store.client.should be_an_instance_of(Puppet::Indirector::Queue::TestClient) end describe "when saving" do it 'should render the instance using pson' do @subject.expects(:render).with(:pson) @store.client.stubs(:send_message) @store.save(@request) end it "should send the rendered message to the appropriate queue on the client" do @subject.expects(:render).returns "mypson" @store.client.expects(:send_message).with(:my_queue, "mypson") @store.save(@request) end it "should catch any exceptions raised" do @store.client.expects(:send_message).raises ArgumentError lambda { @store.save(@request) }.should raise_error(Puppet::Error) end end describe "when subscribing to the queue" do before do @store_class.stubs(:model).returns @model end it "should use the model's Format support to intern the message from pson" do @model.expects(:convert_from).with(:pson, "mymessage") @store_class.client.expects(:subscribe).yields("mymessage") @store_class.subscribe {|o| o } end it "should yield each interned received message" do @model.stubs(:convert_from).returns "something" @subject_two = @subject_class.new @subject_two.name = :too @store_class.client.expects(:subscribe).with(:my_queue).multiple_yields(@subject, @subject_two) received = [] @store_class.subscribe do |obj| received.push(obj) end received.should == %w{something something} end it "should log but not propagate errors" do @store_class.client.expects(:subscribe).yields("foo") @store_class.expects(:intern).raises ArgumentError Puppet.expects(:err) @store_class.subscribe {|o| o } end end end diff --git a/spec/unit/indirector/rest_spec.rb b/spec/unit/indirector/rest_spec.rb index 3efbfce97..5f0fe363a 100755 --- a/spec/unit/indirector/rest_spec.rb +++ b/spec/unit/indirector/rest_spec.rb @@ -1,455 +1,455 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../spec_helper' require 'puppet/indirector/rest' -describe "a REST http call", :shared => true do +shared_examples_for "a REST http call" do it "should accept a path" do lambda { @search.send(@method, *@arguments) }.should_not raise_error(ArgumentError) end it "should require a path" do lambda { @searcher.send(@method) }.should raise_error(ArgumentError) end it "should return the results of deserializing the response to the request" do conn = mock 'connection' conn.stubs(:put).returns @response conn.stubs(:delete).returns @response conn.stubs(:get).returns @response Puppet::Network::HttpPool.stubs(:http_instance).returns conn @searcher.expects(:deserialize).with(@response).returns "myobject" @searcher.send(@method, *@arguments).should == 'myobject' end end describe Puppet::Indirector::REST do before do Puppet::Indirector::Terminus.stubs(:register_terminus_class) @model = stub('model', :supported_formats => %w{}, :convert_from => nil) @instance = stub('model instance', :name= => nil) @indirection = stub('indirection', :name => :mystuff, :register_terminus_type => nil, :model => @model) Puppet::Indirector::Indirection.stubs(:instance).returns(@indirection) @rest_class = Class.new(Puppet::Indirector::REST) do def self.to_s "This::Is::A::Test::Class" end end @response = stub('mock response', :body => 'result', :code => "200") @response.stubs(:[]).with('content-type').returns "text/plain" @response.stubs(:[]).with('content-encoding').returns nil @searcher = @rest_class.new @searcher.stubs(:model).returns @model end it "should include the v1 REST API module" do Puppet::Indirector::REST.ancestors.should be_include(Puppet::Network::HTTP::API::V1) end it "should have a method for specifying what setting a subclass should use to retrieve its server" do @rest_class.should respond_to(:use_server_setting) end it "should use any specified setting to pick the server" do @rest_class.expects(:server_setting).returns :servset Puppet.settings.expects(:value).with(:servset).returns "myserver" @rest_class.server.should == "myserver" end it "should default to :server for the server setting" do @rest_class.expects(:server_setting).returns nil Puppet.settings.expects(:value).with(:server).returns "myserver" @rest_class.server.should == "myserver" end it "should have a method for specifying what setting a subclass should use to retrieve its port" do @rest_class.should respond_to(:use_port_setting) end it "should use any specified setting to pick the port" do @rest_class.expects(:port_setting).returns :servset Puppet.settings.expects(:value).with(:servset).returns "321" @rest_class.port.should == 321 end it "should default to :port for the port setting" do @rest_class.expects(:port_setting).returns nil Puppet.settings.expects(:value).with(:masterport).returns "543" @rest_class.port.should == 543 end describe "when deserializing responses" do it "should return nil if the response code is 404" do response = mock 'response' response.expects(:code).returns "404" @searcher.deserialize(response).should be_nil end [300,400,403,405,500,501,502,503,504].each { |rc| describe "when the response code is #{rc}" do before :each do @model.expects(:convert_from).never @response = mock 'response' @response.stubs(:code).returns rc.to_s @response.stubs(:[]).with('content-encoding').returns nil @response.stubs(:message).returns "There was a problem (header)" end it "should fail" do @response.stubs(:body).returns nil lambda { @searcher.deserialize(@response) }.should raise_error(Net::HTTPError) end it "should take the error message from the body, if present" do @response.stubs(:body).returns "There was a problem (body)" lambda { @searcher.deserialize(@response) }.should raise_error(Net::HTTPError,"Error #{rc} on SERVER: There was a problem (body)") end it "should take the error message from the response header if the body is empty" do @response.stubs(:body).returns "" lambda { @searcher.deserialize(@response) }.should raise_error(Net::HTTPError,"Error #{rc} on SERVER: There was a problem (header)") end it "should take the error message from the response header if the body is absent" do @response.stubs(:body).returns nil lambda { @searcher.deserialize(@response) }.should raise_error(Net::HTTPError,"Error #{rc} on SERVER: There was a problem (header)") end describe "and with http compression" do it "should uncompress the body" do @response.stubs(:body).returns("compressed body") @searcher.expects(:uncompress_body).with(@response).returns("uncompressed") lambda { @searcher.deserialize(@response) }.should raise_error { |e| e.message =~ /uncompressed/ } end end end } it "should return the results of converting from the format specified by the content-type header if the response code is in the 200s" do @model.expects(:convert_from).with("myformat", "mydata").returns "myobject" response = mock 'response' response.stubs(:[]).with("content-type").returns "myformat" response.stubs(:[]).with("content-encoding").returns nil response.stubs(:body).returns "mydata" response.stubs(:code).returns "200" @searcher.deserialize(response).should == "myobject" end it "should convert and return multiple instances if the return code is in the 200s and 'multiple' is specified" do @model.expects(:convert_from_multiple).with("myformat", "mydata").returns "myobjects" response = mock 'response' response.stubs(:[]).with("content-type").returns "myformat" response.stubs(:[]).with("content-encoding").returns nil response.stubs(:body).returns "mydata" response.stubs(:code).returns "200" @searcher.deserialize(response, true).should == "myobjects" end it "should strip the content-type header to keep only the mime-type" do @model.expects(:convert_from).with("text/plain", "mydata").returns "myobject" response = mock 'response' response.stubs(:[]).with("content-type").returns "text/plain; charset=utf-8" response.stubs(:[]).with("content-encoding").returns nil response.stubs(:body).returns "mydata" response.stubs(:code).returns "200" @searcher.deserialize(response) end it "should uncompress the body" do @model.expects(:convert_from).with("myformat", "uncompressed mydata").returns "myobject" response = mock 'response' response.stubs(:[]).with("content-type").returns "myformat" response.stubs(:body).returns "compressed mydata" response.stubs(:code).returns "200" @searcher.expects(:uncompress_body).with(response).returns("uncompressed mydata") @searcher.deserialize(response).should == "myobject" end end describe "when creating an HTTP client" do before do Puppet.settings.stubs(:value).returns("rest_testing") end it "should use the class's server and port if the indirection request provides neither" do @request = stub 'request', :key => "foo", :server => nil, :port => nil @searcher.class.expects(:port).returns 321 @searcher.class.expects(:server).returns "myserver" Puppet::Network::HttpPool.expects(:http_instance).with("myserver", 321).returns "myconn" @searcher.network(@request).should == "myconn" end it "should use the server from the indirection request if one is present" do @request = stub 'request', :key => "foo", :server => "myserver", :port => nil @searcher.class.stubs(:port).returns 321 Puppet::Network::HttpPool.expects(:http_instance).with("myserver", 321).returns "myconn" @searcher.network(@request).should == "myconn" end it "should use the port from the indirection request if one is present" do @request = stub 'request', :key => "foo", :server => nil, :port => 321 @searcher.class.stubs(:server).returns "myserver" Puppet::Network::HttpPool.expects(:http_instance).with("myserver", 321).returns "myconn" @searcher.network(@request).should == "myconn" end end describe "when doing a find" do before :each do @connection = stub('mock http connection', :get => @response) @searcher.stubs(:network).returns(@connection) # neuter the network connection # Use a key with spaces, so we can test escaping @request = Puppet::Indirector::Request.new(:foo, :find, "foo bar") end it "should call the GET http method on a network connection" do @searcher.expects(:network).returns @connection @connection.expects(:get).returns @response @searcher.find(@request) end it "should deserialize and return the http response, setting name" do @connection.expects(:get).returns @response instance = stub 'object' instance.expects(:name=) @searcher.expects(:deserialize).with(@response).returns instance @searcher.find(@request).should == instance end it "should deserialize and return the http response, and not require name=" do @connection.expects(:get).returns @response instance = stub 'object' @searcher.expects(:deserialize).with(@response).returns instance @searcher.find(@request).should == instance end it "should use the URI generated by the Handler module" do @searcher.expects(:indirection2uri).with(@request).returns "/my/uri" @connection.expects(:get).with { |path, args| path == "/my/uri" }.returns(@response) @searcher.find(@request) end it "should provide an Accept header containing the list of supported formats joined with commas" do @connection.expects(:get).with { |path, args| args["Accept"] == "supported, formats" }.returns(@response) @searcher.model.expects(:supported_formats).returns %w{supported formats} @searcher.find(@request) end it "should add Accept-Encoding header" do @searcher.expects(:add_accept_encoding).returns({"accept-encoding" => "gzip"}) @connection.expects(:get).with { |path, args| args["accept-encoding"] == "gzip" }.returns(@response) @searcher.find(@request) end it "should deserialize and return the network response" do @searcher.expects(:deserialize).with(@response).returns @instance @searcher.find(@request).should equal(@instance) end it "should set the name of the resulting instance to the asked-for name" do @searcher.expects(:deserialize).with(@response).returns @instance @instance.expects(:name=).with "foo bar" @searcher.find(@request) end it "should generate an error when result data deserializes fails" do @searcher.expects(:deserialize).raises(ArgumentError) lambda { @searcher.find(@request) }.should raise_error(ArgumentError) end end describe "when doing a search" do before :each do @connection = stub('mock http connection', :get => @response) @searcher.stubs(:network).returns(@connection) # neuter the network connection @model.stubs(:convert_from_multiple) @request = Puppet::Indirector::Request.new(:foo, :search, "foo bar") end it "should call the GET http method on a network connection" do @searcher.expects(:network).returns @connection @connection.expects(:get).returns @response @searcher.search(@request) end it "should deserialize as multiple instances and return the http response" do @connection.expects(:get).returns @response @searcher.expects(:deserialize).with(@response, true).returns "myobject" @searcher.search(@request).should == 'myobject' end it "should use the URI generated by the Handler module" do @searcher.expects(:indirection2uri).with(@request).returns "/mys/uri" @connection.expects(:get).with { |path, args| path == "/mys/uri" }.returns(@response) @searcher.search(@request) end it "should provide an Accept header containing the list of supported formats joined with commas" do @connection.expects(:get).with { |path, args| args["Accept"] == "supported, formats" }.returns(@response) @searcher.model.expects(:supported_formats).returns %w{supported formats} @searcher.search(@request) end it "should return an empty array if serialization returns nil" do @model.stubs(:convert_from_multiple).returns nil @searcher.search(@request).should == [] end it "should generate an error when result data deserializes fails" do @searcher.expects(:deserialize).raises(ArgumentError) lambda { @searcher.search(@request) }.should raise_error(ArgumentError) end end describe "when doing a destroy" do before :each do @connection = stub('mock http connection', :delete => @response) @searcher.stubs(:network).returns(@connection) # neuter the network connection @request = Puppet::Indirector::Request.new(:foo, :destroy, "foo bar") end it "should call the DELETE http method on a network connection" do @searcher.expects(:network).returns @connection @connection.expects(:delete).returns @response @searcher.destroy(@request) end it "should fail if any options are provided, since DELETE apparently does not support query options" do @request.stubs(:options).returns(:one => "two", :three => "four") lambda { @searcher.destroy(@request) }.should raise_error(ArgumentError) end it "should deserialize and return the http response" do @connection.expects(:delete).returns @response @searcher.expects(:deserialize).with(@response).returns "myobject" @searcher.destroy(@request).should == 'myobject' end it "should use the URI generated by the Handler module" do @searcher.expects(:indirection2uri).with(@request).returns "/my/uri" @connection.expects(:delete).with { |path, args| path == "/my/uri" }.returns(@response) @searcher.destroy(@request) end it "should not include the query string" do @connection.stubs(:delete).returns @response @searcher.destroy(@request) end it "should provide an Accept header containing the list of supported formats joined with commas" do @connection.expects(:delete).with { |path, args| args["Accept"] == "supported, formats" }.returns(@response) @searcher.model.expects(:supported_formats).returns %w{supported formats} @searcher.destroy(@request) end it "should deserialize and return the network response" do @searcher.expects(:deserialize).with(@response).returns @instance @searcher.destroy(@request).should equal(@instance) end it "should generate an error when result data deserializes fails" do @searcher.expects(:deserialize).raises(ArgumentError) lambda { @searcher.destroy(@request) }.should raise_error(ArgumentError) end end describe "when doing a save" do before :each do @connection = stub('mock http connection', :put => @response) @searcher.stubs(:network).returns(@connection) # neuter the network connection @instance = stub 'instance', :render => "mydata", :mime => "mime" @request = Puppet::Indirector::Request.new(:foo, :save, "foo bar") @request.instance = @instance end it "should call the PUT http method on a network connection" do @searcher.expects(:network).returns @connection @connection.expects(:put).returns @response @searcher.save(@request) end it "should fail if any options are provided, since DELETE apparently does not support query options" do @request.stubs(:options).returns(:one => "two", :three => "four") lambda { @searcher.save(@request) }.should raise_error(ArgumentError) end it "should use the URI generated by the Handler module" do @searcher.expects(:indirection2uri).with(@request).returns "/my/uri" @connection.expects(:put).with { |path, args| path == "/my/uri" }.returns(@response) @searcher.save(@request) end it "should serialize the instance using the default format and pass the result as the body of the request" do @instance.expects(:render).returns "serial_instance" @connection.expects(:put).with { |path, data, args| data == "serial_instance" }.returns @response @searcher.save(@request) end it "should deserialize and return the http response" do @connection.expects(:put).returns @response @searcher.expects(:deserialize).with(@response).returns "myobject" @searcher.save(@request).should == 'myobject' end it "should provide an Accept header containing the list of supported formats joined with commas" do @connection.expects(:put).with { |path, data, args| args["Accept"] == "supported, formats" }.returns(@response) @searcher.model.expects(:supported_formats).returns %w{supported formats} @searcher.save(@request) end it "should provide a Content-Type header containing the mime-type of the sent object" do @connection.expects(:put).with { |path, data, args| args['Content-Type'] == "mime" }.returns(@response) @instance.expects(:mime).returns "mime" @searcher.save(@request) end it "should deserialize and return the network response" do @searcher.expects(:deserialize).with(@response).returns @instance @searcher.save(@request).should equal(@instance) end it "should generate an error when result data deserializes fails" do @searcher.expects(:deserialize).raises(ArgumentError) lambda { @searcher.save(@request) }.should raise_error(ArgumentError) end end end diff --git a/spec/unit/indirector_spec.rb b/spec/unit/indirector_spec.rb index fb21532ba..acbfc1726 100755 --- a/spec/unit/indirector_spec.rb +++ b/spec/unit/indirector_spec.rb @@ -1,157 +1,157 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../spec_helper' require 'puppet/defaults' require 'puppet/indirector' describe Puppet::Indirector, " when available to a model" do before do @thingie = Class.new do extend Puppet::Indirector end end it "should provide a way for the model to register an indirection under a name" do @thingie.should respond_to(:indirects) end end describe Puppet::Indirector, "when registering an indirection" do before do @thingie = Class.new do extend Puppet::Indirector attr_reader :name def initialize(name) @name = name end end end it "should require a name when registering a model" do Proc.new {@thingie.send(:indirects) }.should raise_error(ArgumentError) end it "should create an indirection instance to manage each indirecting model" do @indirection = @thingie.indirects(:test) @indirection.should be_instance_of(Puppet::Indirector::Indirection) end it "should not allow a model to register under multiple names" do # Keep track of the indirection instance so we can delete it on cleanup @indirection = @thingie.indirects :first Proc.new { @thingie.indirects :second }.should raise_error(ArgumentError) end it "should make the indirection available via an accessor" do @indirection = @thingie.indirects :first @thingie.indirection.should equal(@indirection) end it "should pass any provided options to the indirection during initialization" do klass = mock 'terminus class' Puppet::Indirector::Indirection.expects(:new).with(@thingie, :first, {:some => :options}) @indirection = @thingie.indirects :first, :some => :options end it "should extend the class with the Format Handler" do @indirection = @thingie.indirects :first @thingie.singleton_class.ancestors.should be_include(Puppet::Network::FormatHandler) end after do @indirection.delete if @indirection end end -describe "Delegated Indirection Method", :shared => true do +shared_examples_for "Delegated Indirection Method" do it "should delegate to the indirection" do @indirection.expects(@method) @thingie.send(@method, "me") end it "should pass all of the passed arguments directly to the indirection instance" do @indirection.expects(@method).with("me", :one => :two) @thingie.send(@method, "me", :one => :two) end it "should return the results of the delegation as its result" do request = mock 'request' @indirection.expects(@method).returns "yay" @thingie.send(@method, "me").should == "yay" end end describe Puppet::Indirector, "when redirecting a model" do before do @thingie = Class.new do extend Puppet::Indirector attr_reader :name def initialize(name) @name = name end end @indirection = @thingie.send(:indirects, :test) end it "should include the Envelope module in the model" do @thingie.ancestors.should be_include(Puppet::Indirector::Envelope) end describe "when finding instances via the model" do before { @method = :find } it_should_behave_like "Delegated Indirection Method" end describe "when destroying instances via the model" do before { @method = :destroy } it_should_behave_like "Delegated Indirection Method" end describe "when searching for instances via the model" do before { @method = :search } it_should_behave_like "Delegated Indirection Method" end describe "when expiring instances via the model" do before { @method = :expire } it_should_behave_like "Delegated Indirection Method" end # This is an instance method, so it behaves a bit differently. describe "when saving instances via the model" do before do @instance = @thingie.new("me") end it "should delegate to the indirection" do @indirection.expects(:save) @instance.save end it "should pass the instance and an optional key to the indirection's :save method" do @indirection.expects(:save).with("key", @instance) @instance.save "key" end it "should return the results of the delegation as its result" do request = mock 'request' @indirection.expects(:save).returns "yay" @instance.save.should == "yay" end end it "should give the model the ability to set the indirection terminus class" do @indirection.expects(:terminus_class=).with(:myterm) @thingie.terminus_class = :myterm end it "should give the model the ability to set the indirection cache class" do @indirection.expects(:cache_class=).with(:mycache) @thingie.cache_class = :mycache end after do @indirection.delete end end diff --git a/spec/unit/module_spec.rb b/spec/unit/module_spec.rb index c3436dfdd..37dad7e25 100755 --- a/spec/unit/module_spec.rb +++ b/spec/unit/module_spec.rb @@ -1,547 +1,545 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../spec_helper' describe Puppet::Module do 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 mod = Puppet::Module.new("foo") env = mock 'environment' mod.stubs(:environment).returns env env.expects(:modulepath).returns %w{/a /b /c} FileTest.expects(:exist?).with("/a/foo").returns false FileTest.expects(:exist?).with("/b/foo").returns true FileTest.expects(:exist?).with("/c/foo").never mod.path.should == "/b/foo" 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.expects(:warning) mod.plugin_directory.should == "/a/foo/plugins" 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 know if it has a metadata file" do FileTest.expects(:exist?).with(@module.metadata_file).returns true @module.should 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" do - confine "Cannot test module metadata without json" => Puppet.features.json? - + describe "when loading the medatada file", :if => Puppet.features.json? do before do @data = { :license => "GPL2", :author => "luke", :version => "1.0", :source => "http://foo/", :puppetversion => "0.25" } @text = @data.to_json @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_json File.stubs(:read).with("/my/file").returns @text lambda { @module.load_metadata }.should raise_error(Puppet::Module::MissingMetadata) 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/network/formats_spec.rb b/spec/unit/network/formats_spec.rb index 7c8e7b1f4..2c58a0534 100755 --- a/spec/unit/network/formats_spec.rb +++ b/spec/unit/network/formats_spec.rb @@ -1,337 +1,334 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../spec_helper' require 'puppet/network/formats' class PsonTest attr_accessor :string def ==(other) string == other.string end def self.from_pson(data) new(data) end def initialize(string) @string = string end def to_pson(*args) { 'type' => self.class.name, 'data' => @string }.to_pson(*args) end end describe "Puppet Network Format" do it "should include a yaml format" do Puppet::Network::FormatHandler.format(:yaml).should_not be_nil end describe "yaml" do before do @yaml = Puppet::Network::FormatHandler.format(:yaml) end it "should have its mime type set to text/yaml" do @yaml.mime.should == "text/yaml" end it "should be supported on Strings" do @yaml.should be_supported(String) end it "should render by calling 'to_yaml' on the instance" do instance = mock 'instance' instance.expects(:to_yaml).returns "foo" @yaml.render(instance).should == "foo" end it "should render multiple instances by calling 'to_yaml' on the array" do instances = [mock('instance')] instances.expects(:to_yaml).returns "foo" @yaml.render_multiple(instances).should == "foo" end it "should intern by calling 'YAML.load'" do text = "foo" YAML.expects(:load).with("foo").returns "bar" @yaml.intern(String, text).should == "bar" end it "should intern multiples by calling 'YAML.load'" do text = "foo" YAML.expects(:load).with("foo").returns "bar" @yaml.intern_multiple(String, text).should == "bar" end end - describe "base64 compressed yaml" do + describe "base64 compressed yaml", :if => Puppet.features.zlib? do yaml = Puppet::Network::FormatHandler.format(:b64_zlib_yaml) - confine "We must have zlib" => Puppet.features.zlib? before do @yaml = Puppet::Network::FormatHandler.format(:b64_zlib_yaml) end it "should have its mime type set to text/b64_zlib_yaml" do @yaml.mime.should == "text/b64_zlib_yaml" end it "should render by calling 'to_yaml' on the instance" do instance = mock 'instance' instance.expects(:to_yaml).returns "foo" @yaml.render(instance) end it "should encode generated yaml on render" do instance = mock 'instance', :to_yaml => "foo" @yaml.expects(:encode).with("foo").returns "bar" @yaml.render(instance).should == "bar" end it "should render multiple instances by calling 'to_yaml' on the array" do instances = [mock('instance')] instances.expects(:to_yaml).returns "foo" @yaml.render_multiple(instances) end it "should encode generated yaml on render" do instances = [mock('instance')] instances.stubs(:to_yaml).returns "foo" @yaml.expects(:encode).with("foo").returns "bar" @yaml.render(instances).should == "bar" end it "should intern by calling decode" do text = "foo" @yaml.expects(:decode).with("foo").returns "bar" @yaml.intern(String, text).should == "bar" end it "should intern multiples by calling 'decode'" do text = "foo" @yaml.expects(:decode).with("foo").returns "bar" @yaml.intern_multiple(String, text).should == "bar" end it "should decode by base64 decoding, uncompressing and Yaml loading" do Base64.expects(:decode64).with("zorg").returns "foo" Zlib::Inflate.expects(:inflate).with("foo").returns "baz" YAML.expects(:load).with("baz").returns "bar" @yaml.decode("zorg").should == "bar" end it "should encode by compressing and base64 encoding" do Zlib::Deflate.expects(:deflate).with("foo", Zlib::BEST_COMPRESSION).returns "bar" Base64.expects(:encode64).with("bar").returns "baz" @yaml.encode("foo").should == "baz" end describe "when zlib is disabled" do before do Puppet[:zlib] = false end it "use_zlib? should return false" do @yaml.use_zlib?.should == false end it "should refuse to encode" do lambda{ @yaml.encode("foo") }.should raise_error end it "should refuse to decode" do lambda{ @yaml.decode("foo") }.should raise_error end end describe "when zlib is not installed" do it "use_zlib? should return false" do Puppet[:zlib] = true Puppet.features.expects(:zlib?).returns(false) @yaml.use_zlib?.should == false end end end it "should include a marshal format" do Puppet::Network::FormatHandler.format(:marshal).should_not be_nil end describe "marshal" do before do @marshal = Puppet::Network::FormatHandler.format(:marshal) end it "should have its mime type set to text/marshal" do Puppet::Network::FormatHandler.format(:marshal).mime.should == "text/marshal" end it "should be supported on Strings" do @marshal.should be_supported(String) end it "should render by calling 'Marshal.dump' on the instance" do instance = mock 'instance' Marshal.expects(:dump).with(instance).returns "foo" @marshal.render(instance).should == "foo" end it "should render multiple instances by calling 'to_marshal' on the array" do instances = [mock('instance')] Marshal.expects(:dump).with(instances).returns "foo" @marshal.render_multiple(instances).should == "foo" end it "should intern by calling 'Marshal.load'" do text = "foo" Marshal.expects(:load).with("foo").returns "bar" @marshal.intern(String, text).should == "bar" end it "should intern multiples by calling 'Marshal.load'" do text = "foo" Marshal.expects(:load).with("foo").returns "bar" @marshal.intern_multiple(String, text).should == "bar" end end describe "plaintext" do before do @text = Puppet::Network::FormatHandler.format(:s) end it "should have its mimetype set to text/plain" do @text.mime.should == "text/plain" end it "should use 'txt' as its extension" do @text.extension.should == "txt" end end describe "dot" do before do @dot = Puppet::Network::FormatHandler.format(:dot) end it "should have its mimetype set to text/dot" do @dot.mime.should == "text/dot" end end describe Puppet::Network::FormatHandler.format(:raw) do before do @format = Puppet::Network::FormatHandler.format(:raw) end it "should exist" do @format.should_not be_nil end it "should have its mimetype set to application/x-raw" do @format.mime.should == "application/x-raw" end it "should always be supported" do @format.should be_supported(String) end it "should fail if its multiple_render method is used" do lambda { @format.render_multiple("foo") }.should raise_error(NotImplementedError) end it "should fail if its multiple_intern method is used" do lambda { @format.intern_multiple(String, "foo") }.should raise_error(NotImplementedError) end it "should have a weight of 1" do @format.weight.should == 1 end end it "should include a pson format" do Puppet::Network::FormatHandler.format(:pson).should_not be_nil end - describe "pson" do - confine "Missing 'pson' library" => Puppet.features.pson? - + describe "pson", :if => Puppet.features.pson? do before do @pson = Puppet::Network::FormatHandler.format(:pson) end it "should have its mime type set to text/pson" do Puppet::Network::FormatHandler.format(:pson).mime.should == "text/pson" end it "should require the :render_method" do Puppet::Network::FormatHandler.format(:pson).required_methods.should be_include(:render_method) end it "should require the :intern_method" do Puppet::Network::FormatHandler.format(:pson).required_methods.should be_include(:intern_method) end it "should have a weight of 10" do @pson.weight.should == 10 end describe "when supported" do it "should render by calling 'to_pson' on the instance" do instance = PsonTest.new("foo") instance.expects(:to_pson).returns "foo" @pson.render(instance).should == "foo" end it "should render multiple instances by calling 'to_pson' on the array" do instances = [mock('instance')] instances.expects(:to_pson).returns "foo" @pson.render_multiple(instances).should == "foo" end it "should intern by calling 'PSON.parse' on the text and then using from_pson to convert the data into an instance" do text = "foo" PSON.expects(:parse).with("foo").returns("type" => "PsonTest", "data" => "foo") PsonTest.expects(:from_pson).with("foo").returns "parsed_pson" @pson.intern(PsonTest, text).should == "parsed_pson" end it "should not render twice if 'PSON.parse' creates the appropriate instance" do text = "foo" instance = PsonTest.new("foo") PSON.expects(:parse).with("foo").returns(instance) PsonTest.expects(:from_pson).never @pson.intern(PsonTest, text).should equal(instance) end it "should intern by calling 'PSON.parse' on the text and then using from_pson to convert the actual into an instance if the pson has no class/data separation" do text = "foo" PSON.expects(:parse).with("foo").returns("foo") PsonTest.expects(:from_pson).with("foo").returns "parsed_pson" @pson.intern(PsonTest, text).should == "parsed_pson" end it "should intern multiples by parsing the text and using 'class.intern' on each resulting data structure" do text = "foo" PSON.expects(:parse).with("foo").returns ["bar", "baz"] PsonTest.expects(:from_pson).with("bar").returns "BAR" PsonTest.expects(:from_pson).with("baz").returns "BAZ" @pson.intern_multiple(PsonTest, text).should == %w{BAR BAZ} end end end end diff --git a/spec/unit/network/http/compression_spec.rb b/spec/unit/network/http/compression_spec.rb index b46941f46..c5bbbb064 100644 --- a/spec/unit/network/http/compression_spec.rb +++ b/spec/unit/network/http/compression_spec.rb @@ -1,199 +1,197 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../../spec_helper' describe "http compression" do describe "when zlib is not available" do before(:each) do Puppet.features.stubs(:zlib?).returns false require 'puppet/network/http/compression' class HttpUncompressor include Puppet::Network::HTTP::Compression::None end @uncompressor = HttpUncompressor.new end it "should have a module function that returns the None underlying module" do Puppet::Network::HTTP::Compression.module.should == Puppet::Network::HTTP::Compression::None end it "should not add any Accept-Encoding header" do @uncompressor.add_accept_encoding({}).should == {} end it "should not tamper the body" do response = stub 'response', :body => "data" @uncompressor.uncompress_body(response).should == "data" end it "should yield an identity uncompressor" do response = stub 'response' @uncompressor.uncompress(response) { |u| u.should be_instance_of(Puppet::Network::HTTP::Compression::IdentityAdapter) } end end - describe "when zlib is available" do - confine "Zlib is missing" => Puppet.features.zlib? - + describe "when zlib is available", :if => Puppet.features.zlib? do before(:each) do Puppet.features.stubs(:zlib?).returns true require 'puppet/network/http/compression' class HttpUncompressor include Puppet::Network::HTTP::Compression::Active end @uncompressor = HttpUncompressor.new end it "should have a module function that returns the Active underlying module" do Puppet::Network::HTTP::Compression.module.should == Puppet::Network::HTTP::Compression::Active end it "should add an Accept-Encoding header when http compression is available" do Puppet.settings.expects(:[]).with(:http_compression).returns(true) headers = @uncompressor.add_accept_encoding({}) headers.should have_key('accept-encoding') headers['accept-encoding'].should =~ /gzip/ headers['accept-encoding'].should =~ /deflate/ headers['accept-encoding'].should =~ /identity/ end it "should not add Accept-Encoding header if http compression is not available" do Puppet.settings.stubs(:[]).with(:http_compression).returns(false) @uncompressor.add_accept_encoding({}).should == {} end describe "when uncompressing response body" do before do @response = stub 'response' @response.stubs(:[]).with('content-encoding') @response.stubs(:body).returns("mydata") end it "should return untransformed response body with no content-encoding" do @uncompressor.uncompress_body(@response).should == "mydata" end it "should return untransformed response body with 'identity' content-encoding" do @response.stubs(:[]).with('content-encoding').returns('identity') @uncompressor.uncompress_body(@response).should == "mydata" end it "should use a Zlib inflater with 'deflate' content-encoding" do @response.stubs(:[]).with('content-encoding').returns('deflate') inflater = stub 'inflater' Zlib::Inflate.expects(:new).returns(inflater) inflater.expects(:inflate).with("mydata").returns "uncompresseddata" @uncompressor.uncompress_body(@response).should == "uncompresseddata" end it "should use a GzipReader with 'gzip' content-encoding" do @response.stubs(:[]).with('content-encoding').returns('gzip') io = stub 'io' StringIO.expects(:new).with("mydata").returns io reader = stub 'gzip reader' Zlib::GzipReader.expects(:new).with(io).returns(reader) reader.expects(:read).returns "uncompresseddata" @uncompressor.uncompress_body(@response).should == "uncompresseddata" end end describe "when uncompressing by chunk" do before do @response = stub 'response' @response.stubs(:[]).with('content-encoding') @inflater = stub_everything 'inflater' Zlib::Inflate.stubs(:new).returns(@inflater) end it "should yield an identity uncompressor with no content-encoding" do @uncompressor.uncompress(@response) { |u| u.should be_instance_of(Puppet::Network::HTTP::Compression::IdentityAdapter) } end it "should yield an identity uncompressor with 'identity' content-encoding" do @response.stubs(:[]).with('content-encoding').returns 'identity' @uncompressor.uncompress(@response) { |u| u.should be_instance_of(Puppet::Network::HTTP::Compression::IdentityAdapter) } end %w{gzip deflate}.each do |c| it "should yield a Zlib uncompressor with '#{c}' content-encoding" do @response.stubs(:[]).with('content-encoding').returns c @uncompressor.uncompress(@response) { |u| u.should be_instance_of(Puppet::Network::HTTP::Compression::Active::ZlibAdapter) } end end it "should close the underlying adapter" do adapter = stub_everything 'adapter' Puppet::Network::HTTP::Compression::IdentityAdapter.expects(:new).returns(adapter) adapter.expects(:close) @uncompressor.uncompress(@response) { |u| } end end describe "zlib adapter" do before do @inflater = stub_everything 'inflater' Zlib::Inflate.stubs(:new).returns(@inflater) @adapter = Puppet::Network::HTTP::Compression::Active::ZlibAdapter.new end it "should initialize the underlying inflater with gzip/zlib header parsing" do Zlib::Inflate.expects(:new).with(15+32) Puppet::Network::HTTP::Compression::Active::ZlibAdapter.new end it "should inflate the given chunk" do @inflater.expects(:inflate).with("chunk") @adapter.uncompress("chunk") end it "should return the inflated chunk" do @inflater.stubs(:inflate).with("chunk").returns("uncompressed") @adapter.uncompress("chunk").should == "uncompressed" end it "should try a 'regular' inflater on Zlib::DataError" do @inflater.expects(:inflate).raises(Zlib::DataError.new("not a zlib stream")) inflater = stub_everything 'inflater2' inflater.expects(:inflate).with("chunk").returns("uncompressed") Zlib::Inflate.expects(:new).with.returns(inflater) @adapter.uncompress("chunk") end it "should raise the error the second time" do @inflater.expects(:inflate).raises(Zlib::DataError.new("not a zlib stream")) Zlib::Inflate.expects(:new).with.returns(@inflater) lambda { @adapter.uncompress("chunk") }.should raise_error end it "should finish the stream on close" do @inflater.expects(:finish) @adapter.close end it "should close the stream on close" do @inflater.expects(:close) @adapter.close end end end end diff --git a/spec/unit/network/http/mongrel/rest_spec.rb b/spec/unit/network/http/mongrel/rest_spec.rb index 92a81a10b..fb24521d5 100755 --- a/spec/unit/network/http/mongrel/rest_spec.rb +++ b/spec/unit/network/http/mongrel/rest_spec.rb @@ -1,249 +1,248 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../../../spec_helper' require 'puppet/network/http' -describe "Puppet::Network::HTTP::MongrelREST" do - confine "Mongrel is not available" => Puppet.features.mongrel? +describe "Puppet::Network::HTTP::MongrelREST", :if => Puppet.features.mongrel? do before do require 'puppet/network/http/mongrel/rest' end it "should include the Puppet::Network::HTTP::Handler module" do Puppet::Network::HTTP::MongrelREST.ancestors.should be_include(Puppet::Network::HTTP::Handler) end describe "when initializing" do it "should call the Handler's initialization hook with its provided arguments as the server and handler" do Puppet::Network::HTTP::MongrelREST.any_instance.expects(:initialize_for_puppet).with(:server => "my", :handler => "arguments") Puppet::Network::HTTP::MongrelREST.new(:server => "my", :handler => "arguments") end end describe "when receiving a request" do before do @params = {} @request = stub('mongrel http request', :params => @params) @head = stub('response head') @body = stub('response body', :write => true) @response = stub('mongrel http response') @response.stubs(:start).yields(@head, @body) @model_class = stub('indirected model class') @mongrel = stub('mongrel http server', :register => true) Puppet::Indirector::Indirection.stubs(:model).with(:foo).returns(@model_class) @handler = Puppet::Network::HTTP::MongrelREST.new(:server => @mongrel, :handler => :foo) end describe "and using the HTTP Handler interface" do it "should return the HTTP_ACCEPT parameter as the accept header" do @params.expects(:[]).with("HTTP_ACCEPT").returns "myaccept" @handler.accept_header(@request).should == "myaccept" end it "should return the Content-Type parameter as the Content-Type header" do @params.expects(:[]).with("HTTP_CONTENT_TYPE").returns "mycontent" @handler.content_type_header(@request).should == "mycontent" end it "should use the REQUEST_METHOD as the http method" do @params.expects(:[]).with(Mongrel::Const::REQUEST_METHOD).returns "mymethod" @handler.http_method(@request).should == "mymethod" end it "should return the request path as the path" do @params.expects(:[]).with(Mongrel::Const::REQUEST_PATH).returns "/foo/bar" @handler.path(@request).should == "/foo/bar" end it "should return the request body as the body" do @request.expects(:body).returns StringIO.new("mybody") @handler.body(@request).should == "mybody" end it "should set the response's content-type header when setting the content type" do @header = mock 'header' @response.expects(:header).returns @header @header.expects(:[]=).with('Content-Type', "mytype") @handler.set_content_type(@response, "mytype") end it "should set the status and write the body when setting the response for a successful request" do head = mock 'head' body = mock 'body' @response.expects(:start).with(200).yields(head, body) body.expects(:write).with("mybody") @handler.set_response(@response, "mybody", 200) end describe "when the result is a File" do it "should use response send_file" do head = mock 'head' body = mock 'body' stat = stub 'stat', :size => 100 file = stub 'file', :stat => stat, :path => "/tmp/path" file.stubs(:is_a?).with(File).returns(true) @response.expects(:start).with(200).yields(head, body) @response.expects(:send_status).with(100) @response.expects(:send_header) @response.expects(:send_file).with("/tmp/path") @handler.set_response(@response, file, 200) end end it "should set the status and reason and write the body when setting the response for a successful request" do head = mock 'head' body = mock 'body' @response.expects(:start).with(400, false, "mybody").yields(head, body) body.expects(:write).with("mybody") @handler.set_response(@response, "mybody", 400) end end describe "and determining the request parameters" do before do @request.stubs(:params).returns({}) end it "should skip empty parameter values" do @request.expects(:params).returns('QUERY_STRING' => "&=") lambda { @handler.params(@request) }.should_not raise_error end it "should include the HTTP request parameters, with the keys as symbols" do @request.expects(:params).returns('QUERY_STRING' => 'foo=baz&bar=xyzzy') result = @handler.params(@request) result[:foo].should == "baz" result[:bar].should == "xyzzy" end it "should CGI-decode the HTTP parameters" do encoding = CGI.escape("foo bar") @request.expects(:params).returns('QUERY_STRING' => "foo=#{encoding}") result = @handler.params(@request) result[:foo].should == "foo bar" end it "should convert the string 'true' to the boolean" do @request.expects(:params).returns('QUERY_STRING' => 'foo=true') result = @handler.params(@request) result[:foo].should be_true end it "should convert the string 'false' to the boolean" do @request.expects(:params).returns('QUERY_STRING' => 'foo=false') result = @handler.params(@request) result[:foo].should be_false end it "should convert integer arguments to Integers" do @request.expects(:params).returns('QUERY_STRING' => 'foo=15') result = @handler.params(@request) result[:foo].should == 15 end it "should convert floating point arguments to Floats" do @request.expects(:params).returns('QUERY_STRING' => 'foo=1.5') result = @handler.params(@request) result[:foo].should == 1.5 end it "should YAML-load and URI-decode values that are YAML-encoded" do escaping = CGI.escape(YAML.dump(%w{one two})) @request.expects(:params).returns('QUERY_STRING' => "foo=#{escaping}") result = @handler.params(@request) result[:foo].should == %w{one two} end it "should not allow the client to set the node via the query string" do @request.stubs(:params).returns('QUERY_STRING' => "node=foo") @handler.params(@request)[:node].should be_nil end it "should not allow the client to set the IP address via the query string" do @request.stubs(:params).returns('QUERY_STRING' => "ip=foo") @handler.params(@request)[:ip].should be_nil end it "should pass the client's ip address to model find" do @request.stubs(:params).returns("REMOTE_ADDR" => "ipaddress") @handler.params(@request)[:ip].should == "ipaddress" end it "should pass the client's provided X-Forwared-For value as the ip" do @request.stubs(:params).returns("HTTP_X_FORWARDED_FOR" => "ipaddress") @handler.params(@request)[:ip].should == "ipaddress" end it "should pass the client's provided X-Forwared-For first value as the ip" do @request.stubs(:params).returns("HTTP_X_FORWARDED_FOR" => "ipproxy1,ipproxy2,ipaddress") @handler.params(@request)[:ip].should == "ipaddress" end it "should pass the client's provided X-Forwared-For value as the ip instead of the REMOTE_ADDR" do @request.stubs(:params).returns("REMOTE_ADDR" => "remote_addr") @request.stubs(:params).returns("HTTP_X_FORWARDED_FOR" => "ipaddress") @handler.params(@request)[:ip].should == "ipaddress" end it "should use the :ssl_client_header to determine the parameter when looking for the certificate" do Puppet.settings.stubs(:value).returns "eh" Puppet.settings.expects(:value).with(:ssl_client_header).returns "myheader" @request.stubs(:params).returns("myheader" => "/CN=host.domain.com") @handler.params(@request) end it "should retrieve the hostname by matching the certificate parameter" do Puppet.settings.stubs(:value).returns "eh" Puppet.settings.expects(:value).with(:ssl_client_header).returns "myheader" @request.stubs(:params).returns("myheader" => "/CN=host.domain.com") @handler.params(@request)[:node].should == "host.domain.com" end it "should use the :ssl_client_header to determine the parameter for checking whether the host certificate is valid" do Puppet.settings.stubs(:value).with(:ssl_client_header).returns "certheader" Puppet.settings.expects(:value).with(:ssl_client_verify_header).returns "myheader" @request.stubs(:params).returns("myheader" => "SUCCESS", "certheader" => "/CN=host.domain.com") @handler.params(@request) end it "should consider the host authenticated if the validity parameter contains 'SUCCESS'" do Puppet.settings.stubs(:value).with(:ssl_client_header).returns "certheader" Puppet.settings.stubs(:value).with(:ssl_client_verify_header).returns "myheader" @request.stubs(:params).returns("myheader" => "SUCCESS", "certheader" => "/CN=host.domain.com") @handler.params(@request)[:authenticated].should be_true end it "should consider the host unauthenticated if the validity parameter does not contain 'SUCCESS'" do Puppet.settings.stubs(:value).with(:ssl_client_header).returns "certheader" Puppet.settings.stubs(:value).with(:ssl_client_verify_header).returns "myheader" @request.stubs(:params).returns("myheader" => "whatever", "certheader" => "/CN=host.domain.com") @handler.params(@request)[:authenticated].should be_false end it "should consider the host unauthenticated if no certificate information is present" do Puppet.settings.stubs(:value).with(:ssl_client_header).returns "certheader" Puppet.settings.stubs(:value).with(:ssl_client_verify_header).returns "myheader" @request.stubs(:params).returns("myheader" => nil, "certheader" => "SUCCESS") @handler.params(@request)[:authenticated].should be_false end it "should resolve the node name with an ip address look-up if no certificate is present" do Puppet.settings.stubs(:value).returns "eh" Puppet.settings.expects(:value).with(:ssl_client_header).returns "myheader" @request.stubs(:params).returns("myheader" => nil) @handler.expects(:resolve_node).returns("host.domain.com") @handler.params(@request)[:node].should == "host.domain.com" end end end end diff --git a/spec/unit/network/http/mongrel_spec.rb b/spec/unit/network/http/mongrel_spec.rb index ac3d72bae..1e24be0c6 100755 --- a/spec/unit/network/http/mongrel_spec.rb +++ b/spec/unit/network/http/mongrel_spec.rb @@ -1,131 +1,125 @@ #!/usr/bin/env ruby # # Created by Rick Bradley on 2007-10-15. # Copyright (c) 2007. All rights reserved. require File.dirname(__FILE__) + '/../../../spec_helper' require 'puppet/network/http' -describe "Puppet::Network::HTTP::Mongrel", "after initializing" do - confine "Mongrel is not available" => Puppet.features.mongrel? - +describe "Puppet::Network::HTTP::Mongrel", "after initializing", :if => Puppet.features.mongrel? do it "should not be listening" do require 'puppet/network/http/mongrel' Puppet::Network::HTTP::Mongrel.new.should_not be_listening end end -describe "Puppet::Network::HTTP::Mongrel", "when turning on listening" do - confine "Mongrel is not available" => Puppet.features.mongrel? - +describe "Puppet::Network::HTTP::Mongrel", "when turning on listening", :if => Puppet.features.mongrel? do before do require 'puppet/network/http/mongrel' @server = Puppet::Network::HTTP::Mongrel.new @mock_mongrel = mock('mongrel') @mock_mongrel.stubs(:run) @mock_mongrel.stubs(:register) Mongrel::HttpServer.stubs(:new).returns(@mock_mongrel) @mock_puppet_mongrel = mock('puppet_mongrel') Puppet::Network::HTTPServer::Mongrel.stubs(:new).returns(@mock_puppet_mongrel) @listen_params = { :address => "127.0.0.1", :port => 31337, :protocols => [ :rest, :xmlrpc ], :xmlrpc_handlers => [ :status, :fileserver ] } end it "should fail if already listening" do @server.listen(@listen_params) Proc.new { @server.listen(@listen_params) }.should raise_error(RuntimeError) end it "should require at least one protocol" do Proc.new { @server.listen(@listen_params.delete_if {|k,v| :protocols == k}) }.should raise_error(ArgumentError) end it "should require a listening address to be specified" do Proc.new { @server.listen(@listen_params.delete_if {|k,v| :address == k})}.should raise_error(ArgumentError) end it "should require a listening port to be specified" do Proc.new { @server.listen(@listen_params.delete_if {|k,v| :port == k})}.should raise_error(ArgumentError) end it "should order a mongrel server to start" do @mock_mongrel.expects(:run) @server.listen(@listen_params) end it "should tell mongrel to listen on the specified address and port" do Mongrel::HttpServer.expects(:new).with("127.0.0.1", 31337).returns(@mock_mongrel) @server.listen(@listen_params) end it "should be listening" do Mongrel::HttpServer.expects(:new).returns(@mock_mongrel) @server.listen(@listen_params) @server.should be_listening end describe "when providing REST services" do it "should instantiate a handler at / for handling REST calls" do Puppet::Network::HTTP::MongrelREST.expects(:new).returns "myhandler" @mock_mongrel.expects(:register).with("/", "myhandler") @server.listen(@listen_params) end it "should use a Mongrel + REST class to configure Mongrel when REST services are requested" do @server.expects(:class_for_protocol).with(:rest).at_least_once.returns(Puppet::Network::HTTP::MongrelREST) @server.listen(@listen_params) end end describe "when providing XMLRPC services" do it "should do nothing if no xmlrpc handlers have been provided" do Puppet::Network::HTTPServer::Mongrel.expects(:new).never @server.listen(@listen_params.merge(:xmlrpc_handlers => [])) end it "should create an instance of the existing Mongrel http server with the right handlers" do Puppet::Network::HTTPServer::Mongrel.expects(:new).with([:status, :master]).returns(@mock_puppet_mongrel) @server.listen(@listen_params.merge(:xmlrpc_handlers => [:status, :master])) end it "should register the Mongrel server instance at /RPC2" do @mock_mongrel.expects(:register).with("/RPC2", @mock_puppet_mongrel) @server.listen(@listen_params.merge(:xmlrpc_handlers => [:status, :master])) end end end -describe "Puppet::Network::HTTP::Mongrel", "when turning off listening" do - confine "Mongrel is not available" => Puppet.features.mongrel? - +describe "Puppet::Network::HTTP::Mongrel", "when turning off listening", :if => Puppet.features.mongrel? do before do @mock_mongrel = mock('mongrel httpserver') @mock_mongrel.stubs(:run) @mock_mongrel.stubs(:register) Mongrel::HttpServer.stubs(:new).returns(@mock_mongrel) @server = Puppet::Network::HTTP::Mongrel.new @listen_params = { :address => "127.0.0.1", :port => 31337, :handlers => [ :node, :catalog ], :protocols => [ :rest ] } end it "should fail unless listening" do Proc.new { @server.unlisten }.should raise_error(RuntimeError) end it "should order mongrel server to stop" do @server.listen(@listen_params) @mock_mongrel.expects(:stop) @server.unlisten end it "should not be listening" do @server.listen(@listen_params) @mock_mongrel.stubs(:stop) @server.unlisten @server.should_not be_listening end end diff --git a/spec/unit/network/http/rack/rest_spec.rb b/spec/unit/network/http/rack/rest_spec.rb index 96cf84c37..3eed4a2cb 100755 --- a/spec/unit/network/http/rack/rest_spec.rb +++ b/spec/unit/network/http/rack/rest_spec.rb @@ -1,249 +1,247 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../../../spec_helper' require 'puppet/network/http/rack' if Puppet.features.rack? require 'puppet/network/http/rack/rest' -describe "Puppet::Network::HTTP::RackREST" do - confine "Rack is not available" => Puppet.features.rack? - +describe "Puppet::Network::HTTP::RackREST", :if => Puppet.features.rack? do it "should include the Puppet::Network::HTTP::Handler module" do Puppet::Network::HTTP::RackREST.ancestors.should be_include(Puppet::Network::HTTP::Handler) end describe "when initializing" do it "should call the Handler's initialization hook with its provided arguments" do Puppet::Network::HTTP::RackREST.any_instance.expects(:initialize_for_puppet).with(:server => "my", :handler => "arguments") Puppet::Network::HTTP::RackREST.new(:server => "my", :handler => "arguments") end end describe "when serving a request" do before :all do @model_class = stub('indirected model class') Puppet::Indirector::Indirection.stubs(:model).with(:foo).returns(@model_class) @handler = Puppet::Network::HTTP::RackREST.new(:handler => :foo) end before :each do @response = Rack::Response.new end def mk_req(uri, opts = {}) env = Rack::MockRequest.env_for(uri, opts) Rack::Request.new(env) end describe "and using the HTTP Handler interface" do it "should return the HTTP_ACCEPT parameter as the accept header" do req = mk_req('/', 'HTTP_ACCEPT' => 'myaccept') @handler.accept_header(req).should == "myaccept" end it "should return the CONTENT_TYPE parameter as the content type header" do req = mk_req('/', 'CONTENT_TYPE' => 'mycontent') @handler.content_type_header(req).should == "mycontent" end it "should use the REQUEST_METHOD as the http method" do req = mk_req('/', :method => 'MYMETHOD') @handler.http_method(req).should == "MYMETHOD" end it "should return the request path as the path" do req = mk_req('/foo/bar') @handler.path(req).should == "/foo/bar" end it "should return the request body as the body" do req = mk_req('/foo/bar', :input => 'mybody') @handler.body(req).should == "mybody" end it "should set the response's content-type header when setting the content type" do @header = mock 'header' @response.expects(:header).returns @header @header.expects(:[]=).with('Content-Type', "mytype") @handler.set_content_type(@response, "mytype") end it "should set the status and write the body when setting the response for a request" do @response.expects(:status=).with(400) @response.expects(:write).with("mybody") @handler.set_response(@response, "mybody", 400) end describe "when result is a File" do before :each do stat = stub 'stat', :size => 100 @file = stub 'file', :stat => stat, :path => "/tmp/path" @file.stubs(:is_a?).with(File).returns(true) end it "should set the Content-Length header as a string" do @response.expects(:[]=).with("Content-Length", '100') @handler.set_response(@response, @file, 200) end it "should return a RackFile adapter as body" do @response.expects(:body=).with { |val| val.is_a?(Puppet::Network::HTTP::RackREST::RackFile) } @handler.set_response(@response, @file, 200) end end end describe "and determining the request parameters" do it "should include the HTTP request parameters, with the keys as symbols" do req = mk_req('/?foo=baz&bar=xyzzy') result = @handler.params(req) result[:foo].should == "baz" result[:bar].should == "xyzzy" end it "should CGI-decode the HTTP parameters" do encoding = CGI.escape("foo bar") req = mk_req("/?foo=#{encoding}") result = @handler.params(req) result[:foo].should == "foo bar" end it "should convert the string 'true' to the boolean" do req = mk_req("/?foo=true") result = @handler.params(req) result[:foo].should be_true end it "should convert the string 'false' to the boolean" do req = mk_req("/?foo=false") result = @handler.params(req) result[:foo].should be_false end it "should convert integer arguments to Integers" do req = mk_req("/?foo=15") result = @handler.params(req) result[:foo].should == 15 end it "should convert floating point arguments to Floats" do req = mk_req("/?foo=1.5") result = @handler.params(req) result[:foo].should == 1.5 end it "should YAML-load and CGI-decode values that are YAML-encoded" do escaping = CGI.escape(YAML.dump(%w{one two})) req = mk_req("/?foo=#{escaping}") result = @handler.params(req) result[:foo].should == %w{one two} end it "should not allow the client to set the node via the query string" do req = mk_req("/?node=foo") @handler.params(req)[:node].should be_nil end it "should not allow the client to set the IP address via the query string" do req = mk_req("/?ip=foo") @handler.params(req)[:ip].should be_nil end it "should pass the client's ip address to model find" do req = mk_req("/", 'REMOTE_ADDR' => 'ipaddress') @handler.params(req)[:ip].should == "ipaddress" end it "should set 'authenticated' to false if no certificate is present" do req = mk_req('/') @handler.params(req)[:authenticated].should be_false end end describe "with pre-validated certificates" do it "should use the :ssl_client_header to determine the parameter when looking for the certificate" do Puppet.settings.stubs(:value).returns "eh" Puppet.settings.expects(:value).with(:ssl_client_header).returns "myheader" req = mk_req('/', "myheader" => "/CN=host.domain.com") @handler.params(req) end it "should retrieve the hostname by matching the certificate parameter" do Puppet.settings.stubs(:value).returns "eh" Puppet.settings.expects(:value).with(:ssl_client_header).returns "myheader" req = mk_req('/', "myheader" => "/CN=host.domain.com") @handler.params(req)[:node].should == "host.domain.com" end it "should use the :ssl_client_header to determine the parameter for checking whether the host certificate is valid" do Puppet.settings.stubs(:value).with(:ssl_client_header).returns "certheader" Puppet.settings.expects(:value).with(:ssl_client_verify_header).returns "myheader" req = mk_req('/', "myheader" => "SUCCESS", "certheader" => "/CN=host.domain.com") @handler.params(req) end it "should consider the host authenticated if the validity parameter contains 'SUCCESS'" do Puppet.settings.stubs(:value).with(:ssl_client_header).returns "certheader" Puppet.settings.stubs(:value).with(:ssl_client_verify_header).returns "myheader" req = mk_req('/', "myheader" => "SUCCESS", "certheader" => "/CN=host.domain.com") @handler.params(req)[:authenticated].should be_true end it "should consider the host unauthenticated if the validity parameter does not contain 'SUCCESS'" do Puppet.settings.stubs(:value).with(:ssl_client_header).returns "certheader" Puppet.settings.stubs(:value).with(:ssl_client_verify_header).returns "myheader" req = mk_req('/', "myheader" => "whatever", "certheader" => "/CN=host.domain.com") @handler.params(req)[:authenticated].should be_false end it "should consider the host unauthenticated if no certificate information is present" do Puppet.settings.stubs(:value).with(:ssl_client_header).returns "certheader" Puppet.settings.stubs(:value).with(:ssl_client_verify_header).returns "myheader" req = mk_req('/', "myheader" => nil, "certheader" => "/CN=host.domain.com") @handler.params(req)[:authenticated].should be_false end it "should resolve the node name with an ip address look-up if no certificate is present" do Puppet.settings.stubs(:value).returns "eh" Puppet.settings.expects(:value).with(:ssl_client_header).returns "myheader" req = mk_req('/', "myheader" => nil) @handler.expects(:resolve_node).returns("host.domain.com") @handler.params(req)[:node].should == "host.domain.com" end end end end describe Puppet::Network::HTTP::RackREST::RackFile do before(:each) do stat = stub 'stat', :size => 100 @file = stub 'file', :stat => stat, :path => "/tmp/path" @rackfile = Puppet::Network::HTTP::RackREST::RackFile.new(@file) end it "should have an each method" do @rackfile.should be_respond_to(:each) end it "should yield file chunks by chunks" do @file.expects(:read).times(3).with(8192).returns("1", "2", nil) i = 1 @rackfile.each do |chunk| chunk.to_i.should == i i += 1 end end it "should have a close method" do @rackfile.should be_respond_to(:close) end it "should delegate close to File close" do @file.expects(:close) @rackfile.close end end diff --git a/spec/unit/network/http/rack/xmlrpc_spec.rb b/spec/unit/network/http/rack/xmlrpc_spec.rb index 870438f2c..e6411524e 100755 --- a/spec/unit/network/http/rack/xmlrpc_spec.rb +++ b/spec/unit/network/http/rack/xmlrpc_spec.rb @@ -1,158 +1,156 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../../../spec_helper' require 'puppet/network/handler' require 'puppet/network/http/rack' if Puppet.features.rack? require 'puppet/network/http/rack/xmlrpc' if Puppet.features.rack? -describe "Puppet::Network::HTTP::RackXMLRPC" do - confine "Rack is not available" => Puppet.features.rack? - +describe "Puppet::Network::HTTP::RackXMLRPC", :if => Puppet.features.rack? do describe "when initializing" do it "should create an Puppet::Network::XMLRPCServer" do Puppet::Network::XMLRPCServer.expects(:new).returns stub_everything Puppet::Network::HTTP::RackXMLRPC.new([]) end it "should create each handler" do handler = stub_everything 'handler' Puppet::Network::XMLRPCServer.any_instance.stubs(:add_handler) Puppet::Network::Handler.expects(:handler).returns(handler).times(2) Puppet::Network::HTTP::RackXMLRPC.new([:foo, :bar]) end it "should add each handler to the XMLRPCserver" do handler = stub_everything 'handler' Puppet::Network::Handler.stubs(:handler).returns(handler) Puppet::Network::XMLRPCServer.any_instance.expects(:add_handler).times(2) Puppet::Network::HTTP::RackXMLRPC.new([:foo, :bar]) end end describe "when serving a request" do before :each do foo_handler = stub_everything 'foo_handler' Puppet::Network::Handler.stubs(:handler).with(:foo).returns foo_handler Puppet::Network::XMLRPCServer.any_instance.stubs(:add_handler) Puppet::Network::XMLRPCServer.any_instance.stubs(:process).returns('') @handler = Puppet::Network::HTTP::RackXMLRPC.new([:foo]) end before :each do @response = Rack::Response.new end def mk_req(opts = {}) opts[:method] = 'POST' if !opts[:method] opts['CONTENT_TYPE'] = 'text/xml; foo=bar' if !opts['CONTENT_TYPE'] env = Rack::MockRequest.env_for('/RPC2', opts) Rack::Request.new(env) end it "should reject non-POST requests" do req = mk_req :method => 'PUT' @handler.process(req, @response) @response.status.should == 405 end it "should reject non text/xml requests" do req = mk_req 'CONTENT_TYPE' => 'yadda/plain' end it "should create a ClientRequest" do cr = Puppet::Network::ClientRequest.new(nil, '127.0.0.1', false) Puppet::Network::ClientRequest.expects(:new).returns cr req = mk_req @handler.process(req, @response) end it "should let xmlrpcserver process the request" do Puppet::Network::XMLRPCServer.any_instance.expects(:process).returns('yay') req = mk_req @handler.process(req, @response) end it "should report the response as OK" do req = mk_req @handler.process(req, @response) @response.status.should == 200 end it "should report the response with the correct content type" do req = mk_req @handler.process(req, @response) @response['Content-Type'].should == 'text/xml; charset=utf-8' end it "should set 'authenticated' to false if no certificate is present" do req = mk_req Puppet::Network::ClientRequest.expects(:new).with { |node,ip,authenticated| authenticated == false } @handler.process(req, @response) end it "should use the client's ip address" do req = mk_req 'REMOTE_ADDR' => 'ipaddress' Puppet::Network::ClientRequest.expects(:new).with { |node,ip,authenticated| ip == 'ipaddress' } @handler.process(req, @response) end describe "with pre-validated certificates" do it "should use the :ssl_client_header to determine the parameter when looking for the certificate" do Puppet.settings.stubs(:value).returns "eh" Puppet.settings.expects(:value).with(:ssl_client_header).returns "myheader" req = mk_req "myheader" => "/CN=host.domain.com" @handler.process(req, @response) end it "should retrieve the hostname by matching the certificate parameter" do Puppet.settings.stubs(:value).returns "eh" Puppet.settings.expects(:value).with(:ssl_client_header).returns "myheader" Puppet::Network::ClientRequest.expects(:new).with { |node,ip,authenticated| node == "host.domain.com" } req = mk_req "myheader" => "/CN=host.domain.com" @handler.process(req, @response) end it "should use the :ssl_client_header to determine the parameter for checking whether the host certificate is valid" do Puppet.settings.stubs(:value).with(:ssl_client_header).returns "certheader" Puppet.settings.expects(:value).with(:ssl_client_verify_header).returns "myheader" req = mk_req "myheader" => "SUCCESS", "certheader" => "/CN=host.domain.com" @handler.process(req, @response) end it "should consider the host authenticated if the validity parameter contains 'SUCCESS'" do Puppet.settings.stubs(:value).with(:ssl_client_header).returns "certheader" Puppet.settings.stubs(:value).with(:ssl_client_verify_header).returns "myheader" Puppet::Network::ClientRequest.expects(:new).with { |node,ip,authenticated| authenticated == true } req = mk_req "myheader" => "SUCCESS", "certheader" => "/CN=host.domain.com" @handler.process(req, @response) end it "should consider the host unauthenticated if the validity parameter does not contain 'SUCCESS'" do Puppet.settings.stubs(:value).with(:ssl_client_header).returns "certheader" Puppet.settings.stubs(:value).with(:ssl_client_verify_header).returns "myheader" Puppet::Network::ClientRequest.expects(:new).with { |node,ip,authenticated| authenticated == false } req = mk_req "myheader" => "whatever", "certheader" => "/CN=host.domain.com" @handler.process(req, @response) end it "should consider the host unauthenticated if no certificate information is present" do Puppet.settings.stubs(:value).with(:ssl_client_header).returns "certheader" Puppet.settings.stubs(:value).with(:ssl_client_verify_header).returns "myheader" Puppet::Network::ClientRequest.expects(:new).with { |node,ip,authenticated| authenticated == false } req = mk_req "myheader" => nil, "certheader" => "/CN=host.domain.com" @handler.process(req, @response) end it "should resolve the node name with an ip address look-up if no certificate is present" do Puppet.settings.stubs(:value).returns "eh" Puppet.settings.expects(:value).with(:ssl_client_header).returns "myheader" Resolv.any_instance.expects(:getname).returns("host.domain.com") Puppet::Network::ClientRequest.expects(:new).with { |node,ip,authenticated| node == "host.domain.com" } req = mk_req "myheader" => nil @handler.process(req, @response) end end end end diff --git a/spec/unit/network/http/rack_spec.rb b/spec/unit/network/http/rack_spec.rb index 8be9ccb50..434294ce8 100755 --- a/spec/unit/network/http/rack_spec.rb +++ b/spec/unit/network/http/rack_spec.rb @@ -1,103 +1,101 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../../spec_helper' require 'puppet/network/handler' require 'puppet/network/http/rack' if Puppet.features.rack? -describe "Puppet::Network::HTTP::Rack" do - confine "Rack is not available" => Puppet.features.rack? - +describe "Puppet::Network::HTTP::Rack", :if => Puppet.features.rack? do describe "while initializing" do it "should require a protocol specification" do Proc.new { Puppet::Network::HTTP::Rack.new({}) }.should raise_error(ArgumentError) end it "should not accept imaginary protocols" do Proc.new { Puppet::Network::HTTP::Rack.new({:protocols => [:foo]}) }.should raise_error(ArgumentError) end it "should accept the REST protocol" do Proc.new { Puppet::Network::HTTP::Rack.new({:protocols => [:rest]}) }.should_not raise_error(ArgumentError) end it "should create a RackREST instance" do Puppet::Network::HTTP::RackREST.expects(:new) Puppet::Network::HTTP::Rack.new({:protocols => [:rest]}) end describe "with XMLRPC enabled" do it "should require XMLRPC handlers" do Proc.new { Puppet::Network::HTTP::Rack.new({:protocols => [:xmlrpc]}) }.should raise_error(ArgumentError) end it "should create a RackXMLRPC instance" do Puppet::Network::HTTP::RackXMLRPC.expects(:new) Puppet::Network::HTTP::Rack.new({:protocols => [:xmlrpc], :xmlrpc_handlers => [:Status]}) end end end describe "when called" do before :all do @app = Puppet::Network::HTTP::Rack.new({:protocols => [:rest]}) # let's use Rack::Lint to verify that we're OK with the rack specification @linted = Rack::Lint.new(@app) end before :each do @env = Rack::MockRequest.env_for('/') end it "should create a Request object" do request = Rack::Request.new(@env) Rack::Request.expects(:new).returns request @linted.call(@env) end it "should create a Response object" do Rack::Response.expects(:new).returns stub_everything @app.call(@env) # can't lint when Rack::Response is a stub end it "should let RackREST process the request" do Puppet::Network::HTTP::RackREST.any_instance.expects(:process).once @linted.call(@env) end it "should catch unhandled exceptions from RackREST" do Puppet::Network::HTTP::RackREST.any_instance.expects(:process).raises(ArgumentError, 'test error') Proc.new { @linted.call(@env) }.should_not raise_error end it "should finish() the Response" do Rack::Response.any_instance.expects(:finish).once @app.call(@env) # can't lint when finish is a stub end end describe "when serving XMLRPC" do before :all do @app = Puppet::Network::HTTP::Rack.new({:protocols => [:rest, :xmlrpc], :xmlrpc_handlers => [:Status]}) @linted = Rack::Lint.new(@app) end before :each do @env = Rack::MockRequest.env_for('/RPC2', :method => 'POST') end it "should use RackXMLRPC to serve /RPC2 requests" do Puppet::Network::HTTP::RackXMLRPC.any_instance.expects(:process).once @linted.call(@env) end end end diff --git a/spec/unit/parameter_spec.rb b/spec/unit/parameter_spec.rb index 966bbfb81..f8ab05d62 100755 --- a/spec/unit/parameter_spec.rb +++ b/spec/unit/parameter_spec.rb @@ -1,172 +1,171 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../spec_helper' require 'puppet/parameter' describe Puppet::Parameter do before do @class = Class.new(Puppet::Parameter) do @name = :foo end @class.initvars @resource = mock 'resource' @resource.stub_everything @parameter = @class.new :resource => @resource end it "should create a value collection" do @class = Class.new(Puppet::Parameter) @class.value_collection.should be_nil @class.initvars @class.value_collection.should be_instance_of(Puppet::Parameter::ValueCollection) end it "should return its name as a string when converted to a string" do @parameter.to_s.should == @parameter.name.to_s end it "should be able to use cached attributes" do Puppet::Parameter.ancestors.should be_include(Puppet::Util::Cacher) end it "should use the resource catalog for expiration" do catalog = mock 'catalog' @resource.stubs(:catalog).returns catalog @parameter.expirer.should equal(catalog) end [:line, :file, :version].each do |data| it "should return its resource's #{data} as its #{data}" do @resource.expects(data).returns "foo" @parameter.send(data).should == "foo" end end it "should return the resource's tags plus its name as its tags" do @resource.expects(:tags).returns %w{one two} @parameter.tags.should == %w{one two foo} end it "should provide source_descriptors" do @resource.expects(:line).returns 10 @resource.expects(:file).returns "file" @resource.expects(:tags).returns %w{one two} - @resource.expects(:version).returns 50 - @parameter.source_descriptors.should == {:tags=>["one", "two", "foo"], :path=>"//foo", :version=>50, :file => "file", :line => 10} + @parameter.source_descriptors.should == {:tags=>["one", "two", "foo"], :path=>"//foo", :file => "file", :line => 10} end describe "when returning the value" do it "should return nil if no value is set" do @parameter.value.should be_nil end it "should validate the value" do @parameter.expects(:validate).with("foo") @parameter.value = "foo" end it "should munge the value and use any result as the actual value" do @parameter.expects(:munge).with("foo").returns "bar" @parameter.value = "foo" @parameter.value.should == "bar" end it "should unmunge the value when accessing the actual value" do @parameter.class.unmunge do |value| value.to_sym end @parameter.value = "foo" @parameter.value.should == :foo end it "should return the actual value by default when unmunging" do @parameter.unmunge("bar").should == "bar" end it "should return any set value" do @parameter.value = "foo" @parameter.value.should == "foo" end end describe "when validating values" do it "should do nothing if no values or regexes have been defined" do @parameter.validate("foo") end it "should catch abnormal failures thrown during validation" do @class.validate { |v| raise "This is broken" } lambda { @parameter.validate("eh") }.should raise_error(Puppet::DevError) end it "should fail if the value is not a defined value or alias and does not match a regex" do @class.newvalues :foo lambda { @parameter.validate("bar") }.should raise_error(Puppet::Error) end it "should succeed if the value is one of the defined values" do @class.newvalues :foo lambda { @parameter.validate(:foo) }.should_not raise_error(ArgumentError) end it "should succeed if the value is one of the defined values even if the definition uses a symbol and the validation uses a string" do @class.newvalues :foo lambda { @parameter.validate("foo") }.should_not raise_error(ArgumentError) end it "should succeed if the value is one of the defined values even if the definition uses a string and the validation uses a symbol" do @class.newvalues "foo" lambda { @parameter.validate(:foo) }.should_not raise_error(ArgumentError) end it "should succeed if the value is one of the defined aliases" do @class.newvalues :foo @class.aliasvalue :bar, :foo lambda { @parameter.validate("bar") }.should_not raise_error(ArgumentError) end it "should succeed if the value matches one of the regexes" do @class.newvalues %r{\d} lambda { @parameter.validate("10") }.should_not raise_error(ArgumentError) end end describe "when munging values" do it "should do nothing if no values or regexes have been defined" do @parameter.munge("foo").should == "foo" end it "should catch abnormal failures thrown during munging" do @class.munge { |v| raise "This is broken" } lambda { @parameter.munge("eh") }.should raise_error(Puppet::DevError) end it "should return return any matching defined values" do @class.newvalues :foo, :bar @parameter.munge("foo").should == :foo end it "should return any matching aliases" do @class.newvalues :foo @class.aliasvalue :bar, :foo @parameter.munge("bar").should == :foo end it "should return the value if it matches a regex" do @class.newvalues %r{\w} @parameter.munge("bar").should == "bar" end it "should return the value if no other option is matched" do @class.newvalues :foo @parameter.munge("bar").should == "bar" end end describe "when logging" do it "should use its resource's log level and the provided message" do @resource.expects(:[]).with(:loglevel).returns :notice @parameter.expects(:send_log).with(:notice, "mymessage") @parameter.log "mymessage" end end end diff --git a/spec/unit/parser/collector_spec.rb b/spec/unit/parser/collector_spec.rb index 908cda63a..4cab26c44 100755 --- a/spec/unit/parser/collector_spec.rb +++ b/spec/unit/parser/collector_spec.rb @@ -1,560 +1,556 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../spec_helper' require 'puppet/rails' require 'puppet/parser/collector' describe Puppet::Parser::Collector, "when initializing" do before do @scope = mock 'scope' @resource_type = 'resource_type' @form = :exported @vquery = mock 'vquery' @equery = mock 'equery' @collector = Puppet::Parser::Collector.new(@scope, @resource_type, @equery, @vquery, @form) end it "should require a scope" do @collector.scope.should equal(@scope) end it "should require a resource type" do @collector.type.should == 'Resource_type' end it "should only accept :virtual or :exported as the collector form" do proc { @collector = Puppet::Parser::Collector.new(@scope, @resource_type, @vquery, @equery, :other) }.should raise_error(ArgumentError) end it "should accept an optional virtual query" do @collector.vquery.should equal(@vquery) end it "should accept an optional exported query" do @collector.equery.should equal(@equery) end it "should canonize the type name" do @collector = Puppet::Parser::Collector.new(@scope, "resource::type", @equery, @vquery, @form) @collector.type.should == "Resource::Type" end it "should accept an optional resource override" do @collector = Puppet::Parser::Collector.new(@scope, "resource::type", @equery, @vquery, @form) override = { :parameters => "whatever" } @collector.add_override(override) @collector.overrides.should equal(override) end end describe Puppet::Parser::Collector, "when collecting specific virtual resources" do before do @scope = mock 'scope' @vquery = mock 'vquery' @equery = mock 'equery' @collector = Puppet::Parser::Collector.new(@scope, "resource_type", @equery, @vquery, :virtual) end it "should not fail when it does not find any resources to collect" do @collector.resources = ["File[virtual1]", "File[virtual2]"] @scope.stubs(:findresource).returns(false) proc { @collector.evaluate }.should_not raise_error end it "should mark matched resources as non-virtual" do @collector.resources = ["File[virtual1]", "File[virtual2]"] one = stub_everything 'one' one.expects(:virtual=).with(false) @scope.stubs(:findresource).with("File[virtual1]").returns(one) @scope.stubs(:findresource).with("File[virtual2]").returns(nil) @collector.evaluate end it "should return matched resources" do @collector.resources = ["File[virtual1]", "File[virtual2]"] one = stub_everything 'one' @scope.stubs(:findresource).with("File[virtual1]").returns(one) @scope.stubs(:findresource).with("File[virtual2]").returns(nil) @collector.evaluate.should == [one] end it "should delete itself from the compile's collection list if it has found all of its resources" do @collector.resources = ["File[virtual1]"] one = stub_everything 'one' @compiler.expects(:delete_collection).with(@collector) @scope.expects(:compiler).returns(@compiler) @scope.stubs(:findresource).with("File[virtual1]").returns(one) @collector.evaluate end it "should not delete itself from the compile's collection list if it has unfound resources" do @collector.resources = ["File[virtual1]"] one = stub_everything 'one' @compiler.expects(:delete_collection).never @scope.stubs(:findresource).with("File[virtual1]").returns(nil) @collector.evaluate end end describe Puppet::Parser::Collector, "when collecting virtual and catalog resources" do before do @scope = mock 'scope' @compiler = mock 'compile' @scope.stubs(:compiler).returns(@compiler) @resource_type = "Mytype" @vquery = proc { |res| true } @collector = Puppet::Parser::Collector.new(@scope, @resource_type, nil, @vquery, :virtual) end it "should find all virtual resources matching the vquery" do one = stub_everything 'one', :type => "Mytype", :virtual? => true two = stub_everything 'two', :type => "Mytype", :virtual? => true @compiler.expects(:resources).returns([one, two]) @collector.evaluate.should == [one, two] end it "should find all non-virtual resources matching the vquery" do one = stub_everything 'one', :type => "Mytype", :virtual? => false two = stub_everything 'two', :type => "Mytype", :virtual? => false @compiler.expects(:resources).returns([one, two]) @collector.evaluate.should == [one, two] end it "should mark all matched resources as non-virtual" do one = stub_everything 'one', :type => "Mytype", :virtual? => true one.expects(:virtual=).with(false) @compiler.expects(:resources).returns([one]) @collector.evaluate end it "should return matched resources" do one = stub_everything 'one', :type => "Mytype", :virtual? => true two = stub_everything 'two', :type => "Mytype", :virtual? => true @compiler.expects(:resources).returns([one, two]) @collector.evaluate.should == [one, two] end it "should return all resources of the correct type if there is no virtual query" do one = stub_everything 'one', :type => "Mytype", :virtual? => true two = stub_everything 'two', :type => "Mytype", :virtual? => true one.expects(:virtual=).with(false) two.expects(:virtual=).with(false) @compiler.expects(:resources).returns([one, two]) @collector = Puppet::Parser::Collector.new(@scope, @resource_type, nil, nil, :virtual) @collector.evaluate.should == [one, two] end it "should not return or mark resources of a different type" do one = stub_everything 'one', :type => "Mytype", :virtual? => true two = stub_everything 'two', :type => :other, :virtual? => true one.expects(:virtual=).with(false) two.expects(:virtual=).never @compiler.expects(:resources).returns([one, two]) @collector.evaluate.should == [one] end it "should create a resource with overridden parameters" do one = stub_everything 'one', :type => "Mytype", :virtual? => true, :title => "test" param = stub 'param' @compiler.stubs(:add_override) @compiler.expects(:resources).returns([one]) @collector.add_override(:parameters => param ) Puppet::Parser::Resource.expects(:new).with { |type, title, h| h[:parameters] == param } @collector.evaluate end it "should define a new allow all child_of? on overriden resource" do one = stub_everything 'one', :type => "Mytype", :virtual? => true, :title => "test" param = stub 'param' source = stub 'source' @compiler.stubs(:add_override) @compiler.expects(:resources).returns([one]) @collector.add_override(:parameters => param, :source => source ) Puppet::Parser::Resource.stubs(:new) source.expects(:meta_def).with { |name,block| name == :child_of? } @collector.evaluate end it "should not override already overriden resources for this same collection in a previous run" do one = stub_everything 'one', :type => "Mytype", :virtual? => true, :title => "test" param = stub 'param' @compiler.stubs(:add_override) @compiler.expects(:resources).at_least(2).returns([one]) @collector.add_override(:parameters => param ) Puppet::Parser::Resource.expects(:new).once.with { |type, title, h| h[:parameters] == param } @collector.evaluate @collector.evaluate end it "should not return resources that were collected in a previous run of this collector" do one = stub_everything 'one', :type => "Mytype", :virtual? => true, :title => "test" @compiler.stubs(:resources).returns([one]) @collector.evaluate @collector.evaluate.should be_false end it "should tell the compiler about the overriden resources" do one = stub_everything 'one', :type => "Mytype", :virtual? => true, :title => "test" param = stub 'param' one.expects(:virtual=).with(false) @compiler.expects(:resources).returns([one]) @collector.add_override(:parameters => param ) Puppet::Parser::Resource.stubs(:new).returns("whatever") @compiler.expects(:add_override).with("whatever") @collector.evaluate end it "should not return or mark non-matching resources" do @collector.vquery = proc { |res| res.name == :one } one = stub_everything 'one', :name => :one, :type => "Mytype", :virtual? => true two = stub_everything 'two', :name => :two, :type => "Mytype", :virtual? => true one.expects(:virtual=).with(false) two.expects(:virtual=).never @compiler.expects(:resources).returns([one, two]) @collector.evaluate.should == [one] end end -describe Puppet::Parser::Collector, "when collecting exported resources" do - confine "Cannot test Rails integration without ActiveRecord" => Puppet.features.rails? - +describe Puppet::Parser::Collector, "when collecting exported resources", :if => Puppet.features.rails? do before do @compiler = Puppet::Parser::Compiler.new(Puppet::Node.new("mynode")) @scope = Puppet::Parser::Scope.new :compiler => @compiler @resource_type = "Mytype" @equery = "test = true" @vquery = proc { |r| true } res = stub("resource 1") res.stubs(:type).returns @resource_type Puppet::Resource.stubs(:new).returns res Puppet.settings.stubs(:value).with(:storeconfigs).returns true Puppet.settings.stubs(:value).with(:environment).returns "production" @collector = Puppet::Parser::Collector.new(@scope, @resource_type, @equery, @vquery, :exported) end # Stub most of our interface to Rails. def stub_rails(everything = false) ActiveRecord::Base.stubs(:connected?).returns(false) Puppet::Rails.stubs(:init) if everything Puppet::Rails::Host.stubs(:find_by_name).returns(nil) Puppet::Rails::Resource.stubs(:find).returns([]) end end it "should just return false if :storeconfigs is not enabled" do Puppet.settings.expects(:value).with(:storeconfigs).returns false @collector.evaluate.should be_false end it "should use initialize the Rails support if ActiveRecord is not connected" do @compiler.stubs(:resources).returns([]) ActiveRecord::Base.expects(:connected?).returns(false) Puppet::Rails.expects(:init) Puppet::Rails::Host.stubs(:find_by_name).returns(nil) Puppet::Rails::Resource.stubs(:find).returns([]) @collector.evaluate end it "should return all matching resources from the current compile and mark them non-virtual and non-exported" do stub_rails(true) one = stub 'one', :type => "Mytype", :virtual? => true, :exported? => true, :ref => "one" two = stub 'two', :type => "Mytype", :virtual? => true, :exported? => true, :ref => "two" one.stubs(:exported=) one.stubs(:virtual=) two.stubs(:exported=) two.stubs(:virtual=) @compiler.expects(:resources).returns([one, two]) @collector.evaluate.should == [one, two] end it "should mark all returned resources as not virtual" do stub_rails(true) one = stub 'one', :type => "Mytype", :virtual? => true, :exported? => true, :ref => "one" one.stubs(:exported=) one.expects(:virtual=).with(false) @compiler.expects(:resources).returns([one]) @collector.evaluate.should == [one] end it "should convert all found resources into parser resources" do stub_rails Puppet::Rails::Host.stubs(:find_by_name).returns(nil) one = stub 'one', :restype => "Mytype", :title => "one", :virtual? => true, :exported? => true, :ref => "one" Puppet::Rails::Resource.stubs(:find).returns([one]) resource = mock 'resource' one.expects(:to_resource).with(@scope).returns(resource) resource.stubs(:exported=) resource.stubs(:virtual=) resource.stubs(:ref) @compiler.stubs(:resources).returns([]) @scope.stubs(:findresource).returns(nil) @compiler.stubs(:add_resource) @collector.evaluate.should == [resource] end it "should override all exported collected resources if collector has an override" do stub_rails Puppet::Rails::Host.stubs(:find_by_name).returns(nil) one = stub 'one', :restype => "Mytype", :title => "one", :virtual? => true, :exported? => true, :ref => "one" Puppet::Rails::Resource.stubs(:find).returns([one]) resource = mock 'resource', :type => "Mytype" one.expects(:to_resource).with(@scope).returns(resource) resource.stubs(:exported=) resource.stubs(:virtual=) resource.stubs(:ref) resource.stubs(:title) @compiler.stubs(:resources).returns([]) @scope.stubs(:findresource).returns(nil) param = stub 'param' @compiler.stubs(:add_override) @compiler.stubs(:add_resource) @collector.add_override(:parameters => param ) Puppet::Parser::Resource.expects(:new).once.with { |type, title, h| h[:parameters] == param } @collector.evaluate end it "should store converted resources in the compile's resource list" do stub_rails Puppet::Rails::Host.stubs(:find_by_name).returns(nil) one = stub 'one', :restype => "Mytype", :title => "one", :virtual? => true, :exported? => true, :ref => "one" Puppet::Rails::Resource.stubs(:find).returns([one]) resource = mock 'resource' one.expects(:to_resource).with(@scope).returns(resource) resource.stubs(:exported=) resource.stubs(:virtual=) resource.stubs(:ref) @compiler.stubs(:resources).returns([]) @scope.stubs(:findresource).returns(nil) @compiler.expects(:add_resource).with(@scope, resource) @collector.evaluate.should == [resource] end # This way one host doesn't store another host's resources as exported. it "should mark resources collected from the database as not exported" do stub_rails Puppet::Rails::Host.stubs(:find_by_name).returns(nil) one = stub 'one', :restype => "Mytype", :title => "one", :virtual? => true, :exported? => true, :ref => "one" Puppet::Rails::Resource.stubs(:find).returns([one]) resource = mock 'resource' one.expects(:to_resource).with(@scope).returns(resource) resource.expects(:exported=).with(false) resource.stubs(:virtual=) resource.stubs(:ref) @compiler.stubs(:resources).returns([]) @scope.stubs(:findresource).returns(nil) @compiler.stubs(:add_resource) @collector.evaluate end it "should fail if an equivalent resource already exists in the compile" do stub_rails Puppet::Rails::Host.stubs(:find_by_name).returns(nil) rails = stub 'one', :restype => "Mytype", :title => "one", :virtual? => true, :exported? => true, :id => 1, :ref => "yay" inmemory = stub 'one', :type => "Mytype", :virtual? => true, :exported? => true, :rails_id => 2 Puppet::Rails::Resource.stubs(:find).returns([rails]) resource = mock 'resource' @compiler.stubs(:resources).returns([]) @scope.stubs(:findresource).returns(inmemory) @compiler.stubs(:add_resource) proc { @collector.evaluate }.should raise_error(Puppet::ParseError) end it "should ignore exported resources that match already-collected resources" do stub_rails Puppet::Rails::Host.stubs(:find_by_name).returns(nil) rails = stub 'one', :restype => "Mytype", :title => "one", :virtual? => true, :exported? => true, :id => 1, :ref => "yay" inmemory = stub 'one', :type => "Mytype", :virtual? => true, :exported? => true, :rails_id => 1 Puppet::Rails::Resource.stubs(:find).returns([rails]) resource = mock 'resource' @compiler.stubs(:resources).returns([]) @scope.stubs(:findresource).returns(inmemory) @compiler.stubs(:add_resource) proc { @collector.evaluate }.should_not raise_error(Puppet::ParseError) end end -describe Puppet::Parser::Collector, "when building its ActiveRecord query for collecting exported resources" do - confine "Cannot test Rails integration without ActiveRecord" => Puppet.features.rails? - +describe Puppet::Parser::Collector, "when building its ActiveRecord query for collecting exported resources", :if => Puppet.features.rails? do before do @scope = stub 'scope', :host => "myhost", :debug => nil @compiler = mock 'compile' @scope.stubs(:compiler).returns(@compiler) @resource_type = "Mytype" @equery = nil @vquery = proc { |r| true } @resource = stub_everything 'collected' @collector = Puppet::Parser::Collector.new(@scope, @resource_type, @equery, @vquery, :exported) @collector.stubs(:exported_resource).with(@resource).returns(@resource) @compiler.stubs(:resources).returns([]) ActiveRecord::Base.stubs(:connected?).returns(false) Puppet::Rails.stubs(:init) Puppet::Rails::Host.stubs(:find_by_name).returns(nil) Puppet::Rails::Resource.stubs(:find).returns([]) Puppet.settings.stubs(:value).with(:storeconfigs).returns true end it "should exclude all resources from the host if ActiveRecord contains information for this host" do @host = mock 'host' @host.stubs(:id).returns 5 Puppet::Rails::Host.expects(:find_by_name).with(@scope.host).returns(@host) Puppet::Rails::Resource.stubs(:find).with { |*arguments| options = arguments[1] options[:conditions][0] =~ /^host_id != \?/ and options[:conditions][1] == 5 }.returns([@resource]) @collector.evaluate.should == [@resource] end it "should join with parameter names, parameter values when querying ActiveRecord" do @collector.equery = "param_names.name = title" Puppet::Rails::Resource.stubs(:find).with { |*arguments| options = arguments[1] options[:joins] == {:param_values => :param_name} }.returns([@resource]) @collector.evaluate.should == [@resource] end it "should join with tag tables when querying ActiveRecord with a tag exported query" do @collector.equery = "puppet_tags.name = test" Puppet::Rails::Resource.stubs(:find).with { |*arguments| options = arguments[1] options[:joins] == {:resource_tags => :puppet_tag} }.returns([@resource]) @collector.evaluate.should == [@resource] end it "should not join parameters when querying ActiveRecord with a tag exported query" do @collector.equery = "puppet_tags.name = test" Puppet::Rails::Resource.stubs(:find).with { |*arguments| options = arguments[1] options[:joins] == {:param_values => :param_name} }.returns([@resource]) @collector.evaluate.should be_false end it "should only search for exported resources with the matching type" do Puppet::Rails::Resource.stubs(:find).with { |*arguments| options = arguments[1] options[:conditions][0].include?("(exported=? AND restype=?)") and options[:conditions][1] == true and options[:conditions][2] == "Mytype" }.returns([@resource]) @collector.evaluate.should == [@resource] end it "should include the export query if one is provided" do @collector.equery = "test = true" Puppet::Rails::Resource.stubs(:find).with { |*arguments| options = arguments[1] options[:conditions][0].include?("test = true") }.returns([@resource]) @collector.evaluate.should == [@resource] end end diff --git a/spec/unit/parser/lexer_spec.rb b/spec/unit/parser/lexer_spec.rb index d52add399..860326973 100755 --- a/spec/unit/parser/lexer_spec.rb +++ b/spec/unit/parser/lexer_spec.rb @@ -1,664 +1,665 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../spec_helper' require 'puppet/parser/lexer' # This is a special matcher to match easily lexer output -Spec::Matchers.define :be_like do |*expected| +RSpec::Matchers.define :be_like do |*expected| match do |actual| expected.zip(actual).all? { |e,a| !e or a[0] == e or (e.is_a? Array and a[0] == e[0] and (a[1] == e[1] or (a[1].is_a?(Hash) and a[1][:value] == e[1]))) } end end __ = nil describe Puppet::Parser::Lexer do describe "when reading strings" do before { @lexer = Puppet::Parser::Lexer.new } it "should increment the line count for every carriage return in the string" do @lexer.line = 10 @lexer.string = "this\nis\natest'" @lexer.slurpstring("'") @lexer.line.should == 12 end it "should not increment the line count for escapes in the string" do @lexer.line = 10 @lexer.string = "this\\nis\\natest'" @lexer.slurpstring("'") @lexer.line.should == 10 end it "should not think the terminator is escaped, when preceeded by an even number of backslashes" do @lexer.line = 10 @lexer.string = "here\nis\nthe\nstring\\\\'with\nextra\njunk" @lexer.slurpstring("'") @lexer.line.should == 13 end end end describe Puppet::Parser::Lexer::Token do before do @token = Puppet::Parser::Lexer::Token.new(%r{something}, :NAME) end [:regex, :name, :string, :skip, :incr_line, :skip_text, :accumulate].each do |param| it "should have a #{param.to_s} reader" do @token.should be_respond_to(param) end it "should have a #{param.to_s} writer" do @token.should be_respond_to(param.to_s + "=") end end end describe Puppet::Parser::Lexer::Token, "when initializing" do it "should create a regex if the first argument is a string" do Puppet::Parser::Lexer::Token.new("something", :NAME).regex.should == %r{something} end it "should set the string if the first argument is one" do Puppet::Parser::Lexer::Token.new("something", :NAME).string.should == "something" end it "should set the regex if the first argument is one" do Puppet::Parser::Lexer::Token.new(%r{something}, :NAME).regex.should == %r{something} end end describe Puppet::Parser::Lexer::TokenList do before do @list = Puppet::Parser::Lexer::TokenList.new end it "should have a method for retrieving tokens by the name" do token = @list.add_token :name, "whatever" @list[:name].should equal(token) end it "should have a method for retrieving string tokens by the string" do token = @list.add_token :name, "whatever" @list.lookup("whatever").should equal(token) end it "should add tokens to the list when directed" do token = @list.add_token :name, "whatever" @list[:name].should equal(token) end it "should have a method for adding multiple tokens at once" do @list.add_tokens "whatever" => :name, "foo" => :bar @list[:name].should_not be_nil @list[:bar].should_not be_nil end it "should fail to add tokens sharing a name with an existing token" do @list.add_token :name, "whatever" lambda { @list.add_token :name, "whatever" }.should raise_error(ArgumentError) end it "should set provided options on tokens being added" do token = @list.add_token :name, "whatever", :skip_text => true token.skip_text.should == true end it "should define any provided blocks as a :convert method" do token = @list.add_token(:name, "whatever") do "foo" end token.convert.should == "foo" end it "should store all string tokens in the :string_tokens list" do one = @list.add_token(:name, "1") @list.string_tokens.should be_include(one) end it "should store all regex tokens in the :regex_tokens list" do one = @list.add_token(:name, %r{one}) @list.regex_tokens.should be_include(one) end it "should not store string tokens in the :regex_tokens list" do one = @list.add_token(:name, "1") @list.regex_tokens.should_not be_include(one) end it "should not store regex tokens in the :string_tokens list" do one = @list.add_token(:name, %r{one}) @list.string_tokens.should_not be_include(one) end it "should sort the string tokens inversely by length when asked" do one = @list.add_token(:name, "1") two = @list.add_token(:other, "12") @list.sort_tokens @list.string_tokens.should == [two, one] end end describe Puppet::Parser::Lexer::TOKENS do before do @lexer = Puppet::Parser::Lexer.new end { :LBRACK => '[', :RBRACK => ']', :LBRACE => '{', :RBRACE => '}', :LPAREN => '(', :RPAREN => ')', :EQUALS => '=', :ISEQUAL => '==', :GREATEREQUAL => '>=', :GREATERTHAN => '>', :LESSTHAN => '<', :LESSEQUAL => '<=', :NOTEQUAL => '!=', :NOT => '!', :COMMA => ',', :DOT => '.', :COLON => ':', :AT => '@', :LLCOLLECT => '<<|', :RRCOLLECT => '|>>', :LCOLLECT => '<|', :RCOLLECT => '|>', :SEMIC => ';', :QMARK => '?', :BACKSLASH => '\\', :FARROW => '=>', :PARROW => '+>', :APPENDS => '+=', :PLUS => '+', :MINUS => '-', :DIV => '/', :TIMES => '*', :LSHIFT => '<<', :RSHIFT => '>>', :MATCH => '=~', :NOMATCH => '!~', :IN_EDGE => '->', :OUT_EDGE => '<-', :IN_EDGE_SUB => '~>', :OUT_EDGE_SUB => '<~', }.each do |name, string| it "should have a token named #{name.to_s}" do Puppet::Parser::Lexer::TOKENS[name].should_not be_nil end it "should match '#{string}' for the token #{name.to_s}" do Puppet::Parser::Lexer::TOKENS[name].string.should == string end end { "case" => :CASE, "class" => :CLASS, "default" => :DEFAULT, "define" => :DEFINE, "import" => :IMPORT, "if" => :IF, "elsif" => :ELSIF, "else" => :ELSE, "inherits" => :INHERITS, "node" => :NODE, "and" => :AND, "or" => :OR, "undef" => :UNDEF, "false" => :FALSE, "true" => :TRUE, "in" => :IN, }.each do |string, name| it "should have a keyword named #{name.to_s}" do Puppet::Parser::Lexer::KEYWORDS[name].should_not be_nil end it "should have the keyword for #{name.to_s} set to #{string}" do Puppet::Parser::Lexer::KEYWORDS[name].string.should == string end end # These tokens' strings don't matter, just that the tokens exist. [:STRING, :DQPRE, :DQMID, :DQPOST, :BOOLEAN, :NAME, :NUMBER, :COMMENT, :MLCOMMENT, :RETURN, :SQUOTE, :DQUOTE, :VARIABLE].each do |name| it "should have a token named #{name.to_s}" do Puppet::Parser::Lexer::TOKENS[name].should_not be_nil end end end describe Puppet::Parser::Lexer::TOKENS[:CLASSNAME] do before { @token = Puppet::Parser::Lexer::TOKENS[:CLASSNAME] } it "should match against lower-case alpha-numeric terms separated by double colons" do @token.regex.should =~ "one::two" end it "should match against many lower-case alpha-numeric terms separated by double colons" do @token.regex.should =~ "one::two::three::four::five" end it "should match against lower-case alpha-numeric terms prefixed by double colons" do @token.regex.should =~ "::one" end end describe Puppet::Parser::Lexer::TOKENS[:CLASSREF] do before { @token = Puppet::Parser::Lexer::TOKENS[:CLASSREF] } it "should match against single upper-case alpha-numeric terms" do @token.regex.should =~ "One" end it "should match against upper-case alpha-numeric terms separated by double colons" do @token.regex.should =~ "One::Two" end it "should match against many upper-case alpha-numeric terms separated by double colons" do @token.regex.should =~ "One::Two::Three::Four::Five" end it "should match against upper-case alpha-numeric terms prefixed by double colons" do @token.regex.should =~ "::One" end end describe Puppet::Parser::Lexer::TOKENS[:NAME] do before { @token = Puppet::Parser::Lexer::TOKENS[:NAME] } it "should match against lower-case alpha-numeric terms" do @token.regex.should =~ "one-two" end it "should return itself and the value if the matched term is not a keyword" do Puppet::Parser::Lexer::KEYWORDS.expects(:lookup).returns(nil) @token.convert(stub("lexer"), "myval").should == [Puppet::Parser::Lexer::TOKENS[:NAME], "myval"] end it "should return the keyword token and the value if the matched term is a keyword" do keyword = stub 'keyword', :name => :testing Puppet::Parser::Lexer::KEYWORDS.expects(:lookup).returns(keyword) @token.convert(stub("lexer"), "myval").should == [keyword, "myval"] end it "should return the BOOLEAN token and 'true' if the matched term is the string 'true'" do keyword = stub 'keyword', :name => :TRUE Puppet::Parser::Lexer::KEYWORDS.expects(:lookup).returns(keyword) @token.convert(stub('lexer'), "true").should == [Puppet::Parser::Lexer::TOKENS[:BOOLEAN], true] end it "should return the BOOLEAN token and 'false' if the matched term is the string 'false'" do keyword = stub 'keyword', :name => :FALSE Puppet::Parser::Lexer::KEYWORDS.expects(:lookup).returns(keyword) @token.convert(stub('lexer'), "false").should == [Puppet::Parser::Lexer::TOKENS[:BOOLEAN], false] end end describe Puppet::Parser::Lexer::TOKENS[:NUMBER] do before do @token = Puppet::Parser::Lexer::TOKENS[:NUMBER] @regex = @token.regex end it "should match against numeric terms" do @regex.should =~ "2982383139" end it "should match against float terms" do @regex.should =~ "29823.235" end it "should match against hexadecimal terms" do @regex.should =~ "0xBEEF0023" end it "should match against float with exponent terms" do @regex.should =~ "10e23" end it "should match against float terms with negative exponents" do @regex.should =~ "10e-23" end it "should match against float terms with fractional parts and exponent" do @regex.should =~ "1.234e23" end it "should return the NAME token and the value" do @token.convert(stub("lexer"), "myval").should == [Puppet::Parser::Lexer::TOKENS[:NAME], "myval"] end end describe Puppet::Parser::Lexer::TOKENS[:COMMENT] do before { @token = Puppet::Parser::Lexer::TOKENS[:COMMENT] } it "should match against lines starting with '#'" do @token.regex.should =~ "# this is a comment" end it "should be marked to get skipped" do @token.skip?.should be_true end it "should be marked to accumulate" do @token.accumulate?.should be_true end it "'s block should return the comment without the #" do @token.convert(@lexer,"# this is a comment")[1].should == "this is a comment" end end describe Puppet::Parser::Lexer::TOKENS[:MLCOMMENT] do before do @token = Puppet::Parser::Lexer::TOKENS[:MLCOMMENT] @lexer = stub 'lexer', :line => 0 end it "should match against lines enclosed with '/*' and '*/'" do @token.regex.should =~ "/* this is a comment */" end it "should match multiple lines enclosed with '/*' and '*/'" do @token.regex.should =~ """/* this is a comment */""" end it "should increase the lexer current line number by the amount of lines spanned by the comment" do @lexer.expects(:line=).with(2) @token.convert(@lexer, "1\n2\n3") end it "should not greedily match comments" do match = @token.regex.match("/* first */ word /* second */") match[1].should == " first " end it "should be marked to accumulate" do @token.accumulate?.should be_true end it "'s block should return the comment without the comment marks" do @lexer.stubs(:line=).with(0) @token.convert(@lexer,"/* this is a comment */")[1].should == "this is a comment" end end describe Puppet::Parser::Lexer::TOKENS[:RETURN] do before { @token = Puppet::Parser::Lexer::TOKENS[:RETURN] } it "should match against carriage returns" do @token.regex.should =~ "\n" end it "should be marked to initiate text skipping" do @token.skip_text.should be_true end it "should be marked to increment the line" do @token.incr_line.should be_true end end def tokens_scanned_from(s) lexer = Puppet::Parser::Lexer.new lexer.string = s lexer.fullscan[0..-2] end describe Puppet::Parser::Lexer,"when lexing strings" do { %q{'single quoted string')} => [[:STRING,'single quoted string']], %q{"double quoted string"} => [[:STRING,'double quoted string']], %q{'single quoted string with an escaped "\\'"'} => [[:STRING,'single quoted string with an escaped "\'"']], %q{'single quoted string with an escaped "\$"'} => [[:STRING,'single quoted string with an escaped "\$"']], %q{'single quoted string with an escaped "\."'} => [[:STRING,'single quoted string with an escaped "\."']], %q{'single quoted string with an escaped "\n"'} => [[:STRING,'single quoted string with an escaped "\n"']], %q{'single quoted string with an escaped "\\\\"'} => [[:STRING,'single quoted string with an escaped "\\\\"']], %q{"string with an escaped '\\"'"} => [[:STRING,"string with an escaped '\"'"]], %q{"string with an escaped '\\$'"} => [[:STRING,"string with an escaped '$'"]], %Q{"string with a line ending with a backslash: \\\nfoo"} => [[:STRING,"string with a line ending with a backslash: foo"]], %q{"string with $v (but no braces)"} => [[:DQPRE,"string with "],[:VARIABLE,'v'],[:DQPOST,' (but no braces)']], %q["string with ${v} in braces"] => [[:DQPRE,"string with "],[:VARIABLE,'v'],[:DQPOST,' in braces']], %q["string with ${qualified::var} in braces"] => [[:DQPRE,"string with "],[:VARIABLE,'qualified::var'],[:DQPOST,' in braces']], %q{"string with $v and $v (but no braces)"} => [[:DQPRE,"string with "],[:VARIABLE,"v"],[:DQMID," and "],[:VARIABLE,"v"],[:DQPOST," (but no braces)"]], %q["string with ${v} and ${v} in braces"] => [[:DQPRE,"string with "],[:VARIABLE,"v"],[:DQMID," and "],[:VARIABLE,"v"],[:DQPOST," in braces"]], %q["string with ${'a nested single quoted string'} inside it."] => [[:DQPRE,"string with "],[:STRING,'a nested single quoted string'],[:DQPOST,' inside it.']], %q["string with ${['an array ',$v2]} in it."] => [[:DQPRE,"string with "],:LBRACK,[:STRING,"an array "],:COMMA,[:VARIABLE,"v2"],:RBRACK,[:DQPOST," in it."]], %q{a simple "scanner" test} => [[:NAME,"a"],[:NAME,"simple"], [:STRING,"scanner"],[:NAME,"test"]], %q{a simple 'single quote scanner' test} => [[:NAME,"a"],[:NAME,"simple"], [:STRING,"single quote scanner"],[:NAME,"test"]], %q{a harder 'a $b \c"'} => [[:NAME,"a"],[:NAME,"harder"], [:STRING,'a $b \c"']], %q{a harder "scanner test"} => [[:NAME,"a"],[:NAME,"harder"], [:STRING,"scanner test"]], %q{a hardest "scanner \"test\""} => [[:NAME,"a"],[:NAME,"hardest"],[:STRING,'scanner "test"']], %Q{a hardestest "scanner \\"test\\"\n"} => [[:NAME,"a"],[:NAME,"hardestest"],[:STRING,%Q{scanner "test"\n}]], %q{function("call")} => [[:NAME,"function"],[:LPAREN,"("],[:STRING,'call'],[:RPAREN,")"]], %q["string with ${(3+5)/4} nested math."] => [[:DQPRE,"string with "],:LPAREN,[:NAME,"3"],:PLUS,[:NAME,"5"],:RPAREN,:DIV,[:NAME,"4"],[:DQPOST," nested math."]], %q["$$$$"] => [[:STRING,"$$$$"]], %q["$variable"] => [[:DQPRE,""],[:VARIABLE,"variable"],[:DQPOST,""]], %q["$var$other"] => [[:DQPRE,""],[:VARIABLE,"var"],[:DQMID,""],[:VARIABLE,"other"],[:DQPOST,""]], %q["foo$bar$"] => [[:DQPRE,"foo"],[:VARIABLE,"bar"],[:DQPOST,"$"]], %q["foo$$bar"] => [[:DQPRE,"foo$"],[:VARIABLE,"bar"],[:DQPOST,""]], %q[""] => [[:STRING,""]], }.each { |src,expected_result| it "should handle #{src} correctly" do tokens_scanned_from(src).should be_like(*expected_result) end } end describe Puppet::Parser::Lexer::TOKENS[:DOLLAR_VAR] do before { @token = Puppet::Parser::Lexer::TOKENS[:DOLLAR_VAR] } it "should match against alpha words prefixed with '$'" do @token.regex.should =~ '$this_var' end it "should return the VARIABLE token and the variable name stripped of the '$'" do @token.convert(stub("lexer"), "$myval").should == [Puppet::Parser::Lexer::TOKENS[:VARIABLE], "myval"] end end describe Puppet::Parser::Lexer::TOKENS[:REGEX] do before { @token = Puppet::Parser::Lexer::TOKENS[:REGEX] } it "should match against any expression enclosed in //" do @token.regex.should =~ '/this is a regex/' end it 'should not match if there is \n in the regex' do @token.regex.should_not =~ "/this is \n a regex/" end describe "when scanning" do it "should not consider escaped slashes to be the end of a regex" do tokens_scanned_from("$x =~ /this \\/ foo/").should be_like(__,__,[:REGEX,%r{this / foo}]) end it "should not lex chained division as a regex" do tokens_scanned_from("$x = $a/$b/$c").collect { |name, data| name }.should_not be_include( :REGEX ) end it "should accept a regular expression after NODE" do tokens_scanned_from("node /www.*\.mysite\.org/").should be_like(__,[:REGEX,Regexp.new("www.*\.mysite\.org")]) end it "should accept regular expressions in a CASE" do s = %q{case $variable { "something": {$othervar = 4096 / 2} /regex/: {notice("this notably sucks")} } } tokens_scanned_from(s).should be_like( :CASE,:VARIABLE,:LBRACE,:STRING,:COLON,:LBRACE,:VARIABLE,:EQUALS,:NAME,:DIV,:NAME,:RBRACE,[:REGEX,/regex/],:COLON,:LBRACE,:NAME,:LPAREN,:STRING,:RPAREN,:RBRACE,:RBRACE ) end end it "should return the REGEX token and a Regexp" do @token.convert(stub("lexer"), "/myregex/").should == [Puppet::Parser::Lexer::TOKENS[:REGEX], Regexp.new(/myregex/)] end end describe Puppet::Parser::Lexer, "when lexing comments" do before { @lexer = Puppet::Parser::Lexer.new } it "should accumulate token in munge_token" do token = stub 'token', :skip => true, :accumulate? => true, :incr_line => nil, :skip_text => false token.stubs(:convert).with(@lexer, "# this is a comment").returns([token, " this is a comment"]) @lexer.munge_token(token, "# this is a comment") @lexer.munge_token(token, "# this is a comment") @lexer.getcomment.should == " this is a comment\n this is a comment\n" end it "should add a new comment stack level on LBRACE" do @lexer.string = "{" @lexer.expects(:commentpush) @lexer.fullscan end it "should return the current comments on getcomment" do @lexer.string = "# comment" @lexer.fullscan @lexer.getcomment.should == "comment\n" end it "should discard the previous comments on blank line" do @lexer.string = "# 1\n\n# 2" @lexer.fullscan @lexer.getcomment.should == "2\n" end it "should skip whitespace before lexing the next token after a non-token" do tokens_scanned_from("/* 1\n\n */ \ntest").should be_like([:NAME, "test"]) end it "should not return comments seen after the current line" do @lexer.string = "# 1\n\n# 2" @lexer.fullscan @lexer.getcomment(1).should == "" end it "should return a comment seen before the current line" do @lexer.string = "# 1\n# 2" @lexer.fullscan @lexer.getcomment(2).should == "1\n2\n" end end # FIXME: We need to rewrite all of these tests, but I just don't want to take the time right now. describe "Puppet::Parser::Lexer in the old tests" do before { @lexer = Puppet::Parser::Lexer.new } it "should do simple lexing" do { %q{\\} => [[:BACKSLASH,"\\"]], %q{simplest scanner test} => [[:NAME,"simplest"],[:NAME,"scanner"],[:NAME,"test"]], %Q{returned scanner test\n} => [[:NAME,"returned"],[:NAME,"scanner"],[:NAME,"test"]] }.each { |source,expected| tokens_scanned_from(source).should be_like(*expected) } end it "should fail usefully" do lambda { tokens_scanned_from('^') }.should raise_error(RuntimeError) end it "should fail if the string is not set" do lambda { @lexer.fullscan }.should raise_error(Puppet::LexError) end it "should correctly identify keywords" do tokens_scanned_from("case").should be_like([:CASE, "case"]) end it "should correctly parse class references" do %w{Many Different Words A Word}.each { |t| tokens_scanned_from(t).should be_like([:CLASSREF,t])} end # #774 it "should correctly parse namespaced class refernces token" do %w{Foo ::Foo Foo::Bar ::Foo::Bar}.each { |t| tokens_scanned_from(t).should be_like([:CLASSREF, t]) } end it "should correctly parse names" do %w{this is a bunch of names}.each { |t| tokens_scanned_from(t).should be_like([:NAME,t]) } end it "should correctly parse names with numerals" do %w{1name name1 11names names11}.each { |t| tokens_scanned_from(t).should be_like([:NAME,t]) } end it "should correctly parse empty strings" do lambda { tokens_scanned_from('$var = ""') }.should_not raise_error end it "should correctly parse virtual resources" do tokens_scanned_from("@type {").should be_like([:AT, "@"], [:NAME, "type"], [:LBRACE, "{"]) end it "should correctly deal with namespaces" do @lexer.string = %{class myclass} @lexer.fullscan @lexer.namespace.should == "myclass" @lexer.namepop @lexer.namespace.should == "" @lexer.string = "class base { class sub { class more" @lexer.fullscan @lexer.namespace.should == "base::sub::more" @lexer.namepop @lexer.namespace.should == "base::sub" end it "should not put class instantiation on the namespace" do @lexer.string = "class base { class sub { class { mode" @lexer.fullscan @lexer.namespace.should == "base::sub" end it "should correctly handle fully qualified names" do @lexer.string = "class base { class sub::more {" @lexer.fullscan @lexer.namespace.should == "base::sub::more" @lexer.namepop @lexer.namespace.should == "base" end it "should correctly lex variables" do ["$variable", "$::variable", "$qualified::variable", "$further::qualified::variable"].each do |string| tokens_scanned_from(string).should be_like([:VARIABLE,string.sub(/^\$/,'')]) end end end -require 'puppettest/support/utils' +require File.dirname(__FILE__) + '/../../../test/lib/puppettest' +require File.dirname(__FILE__) + '/../../../test/lib/puppettest/support/utils' describe "Puppet::Parser::Lexer in the old tests when lexing example files" do extend PuppetTest::Support::Utils textfiles do |file| it "should correctly lex #{file}" do lexer = Puppet::Parser::Lexer.new lexer.file = file lambda { lexer.fullscan }.should_not raise_error end end end diff --git a/spec/unit/provider/mount/parsed_spec.rb b/spec/unit/provider/mount/parsed_spec.rb index 7d2e8a84c..5a1c986b1 100755 --- a/spec/unit/provider/mount/parsed_spec.rb +++ b/spec/unit/provider/mount/parsed_spec.rb @@ -1,193 +1,192 @@ #!/usr/bin/env ruby # # Created by Luke Kanies on 2007-9-12. # Copyright (c) 2006. All rights reserved. require File.dirname(__FILE__) + '/../../../spec_helper' require 'puppettest/support/utils' require 'puppettest/fileparsing' module ParsedMountTesting include PuppetTest::Support::Utils include PuppetTest::FileParsing def fake_fstab os = Facter['operatingsystem'] if os == "Solaris" name = "solaris.fstab" elsif os == "FreeBSD" name = "freebsd.fstab" else # Catchall for other fstabs name = "linux.fstab" end oldpath = @provider_class.default_target fakefile(File::join("data/types/mount", name)) end def mkmountargs mount = nil if defined?(@pcount) @pcount += 1 else @pcount = 1 end args = { :name => "/fspuppet#{@pcount}", :device => "/dev/dsk#{@pcount}", } @provider_class.fields(:parsed).each do |field| args[field] = "fake#{field}#{@pcount}" unless args.include? field end args end def mkmount hash = mkmountargs #hash[:provider] = @provider_class.name fakeresource = stub :type => :mount, :name => hash[:name] fakeresource.stubs(:[]).with(:name).returns(hash[:name]) fakeresource.stubs(:should).with(:target).returns(nil) mount = @provider_class.new(fakeresource) hash[:record_type] = :parsed hash[:ensure] = :present mount.property_hash = hash mount end # Here we just create a fake host type that answers to all of the methods # but does not modify our actual system. def mkfaketype @provider.stubs(:filetype).returns(Puppet::Util::FileType.filetype(:ram)) end end provider_class = Puppet::Type.type(:mount).provider(:parsed) describe provider_class do before :each do @mount_class = Puppet::Type.type(:mount) @provider_class = @mount_class.provider(:parsed) end describe provider_class do include ParsedMountTesting it "should be able to parse all of the example mount tabs" do tab = fake_fstab @provider = @provider_class # LAK:FIXME Again, a relatively bad test, but I don't know how to rspec-ify this. # I suppose this is more of an integration test? I dunno. fakedataparse(tab) do # Now just make we've got some mounts we know will be there hashes = @provider_class.target_records(tab).find_all { |i| i.is_a? Hash } (hashes.length > 0).should be_true root = hashes.find { |i| i[:name] == "/" } proc { @provider_class.to_file(hashes) }.should_not raise_error end end # LAK:FIXME I can't mock Facter because this test happens at parse-time. it "should default to /etc/vfstab on Solaris and /etc/fstab everywhere else" do should = case Facter.value(:operatingsystem) when "Solaris"; "/etc/vfstab" else "/etc/fstab" end Puppet::Type.type(:mount).provider(:parsed).default_target.should == should end it "should not crash on incomplete lines in fstab" do parse = @provider_class.parse <<-FSTAB /dev/incomplete /dev/device name FSTAB lambda{ @provider_class.to_line(parse[0]) }.should_not raise_error end end describe provider_class, " when mounting an absent filesystem" do include ParsedMountTesting # #730 - Make sure 'flush' is called when a mount is moving from absent to mounted it "should flush the fstab to disk" do mount = mkmount # Mark the mount as absent mount.property_hash[:ensure] = :absent mount.stubs(:mountcmd) # just so we don't actually try to mount anything mount.expects(:flush) mount.mount end end describe provider_class, " when modifying the filesystem tab" do include ParsedMountTesting before do Puppet.settings.stubs(:use) # Never write to disk, only to RAM. #@provider_class.stubs(:filetype).returns(Puppet::Util::FileType.filetype(:ram)) @provider_class.stubs(:target_object).returns(Puppet::Util::FileType.filetype(:ram).new("eh")) @provider_class.clear @mount = mkmount @target = @provider_class.default_target end it "should write the mount to disk when :flush is called" do old_text = @provider_class.target_object(@provider_class.default_target).read @mount.flush text = @provider_class.target_object(@provider_class.default_target).read text.should == old_text + @mount.class.to_line(@mount.property_hash) + "\n" end end - describe provider_class, " when parsing information about the root filesystem" do - confine "Mount type not tested on Darwin" => Facter["operatingsystem"].value != "Darwin" + describe provider_class, " when parsing information about the root filesystem", :if => Facter["operatingsystem"].value != "Darwin" do include ParsedMountTesting before do @mount = @mount_class.new :name => "/" @provider = @mount.provider end it "should have a filesystem tab" do FileTest.should be_exist(@provider_class.default_target) end it "should find the root filesystem" do @provider_class.prefetch("/" => @mount) @mount.provider.property_hash[:ensure].should == :present end it "should determine that the root fs is mounted" do @provider_class.prefetch("/" => @mount) @mount.provider.should be_mounted end end describe provider_class, " when mounting and unmounting" do include ParsedMountTesting it "should call the 'mount' command to mount the filesystem" it "should call the 'unmount' command to unmount the filesystem" it "should specify the filesystem when remounting a filesystem" end end diff --git a/spec/unit/rails/host_spec.rb b/spec/unit/rails/host_spec.rb index 324a673a9..4244f117f 100755 --- a/spec/unit/rails/host_spec.rb +++ b/spec/unit/rails/host_spec.rb @@ -1,163 +1,161 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../spec_helper' -describe "Puppet::Rails::Host" do - confine "Cannot test without ActiveRecord" => Puppet.features.rails? - +describe "Puppet::Rails::Host", :if => Puppet.features.rails? do def column(name, type) ActiveRecord::ConnectionAdapters::Column.new(name, nil, type, false) end before do require 'puppet/rails/host' # Stub this so we don't need access to the DB. Puppet::Rails::Host.stubs(:columns).returns([column("name", "string"), column("environment", "string"), column("ip", "string")]) @node = Puppet::Node.new("foo") @node.environment = "production" @node.ipaddress = "127.0.0.1" @host = stub 'host', :environment= => nil, :ip= => nil end describe "when converting a Puppet::Node instance into a Rails instance" do it "should modify any existing instance in the database" do Puppet::Rails::Host.expects(:find_by_name).with("foo").returns @host Puppet::Rails::Host.from_puppet(@node) end it "should create a new instance in the database if none can be found" do Puppet::Rails::Host.expects(:find_by_name).with("foo").returns nil Puppet::Rails::Host.expects(:new).with(:name => "foo").returns @host Puppet::Rails::Host.from_puppet(@node) end it "should copy the environment from the Puppet instance" do Puppet::Rails::Host.expects(:find_by_name).with("foo").returns @host @node.environment = "production" @host.expects(:environment=).with {|x| x.name.to_s == 'production' } Puppet::Rails::Host.from_puppet(@node) end it "should copy the ipaddress from the Puppet instance" do Puppet::Rails::Host.expects(:find_by_name).with("foo").returns @host @node.ipaddress = "192.168.0.1" @host.expects(:ip=).with "192.168.0.1" Puppet::Rails::Host.from_puppet(@node) end it "should not save the Rails instance" do Puppet::Rails::Host.expects(:find_by_name).with("foo").returns @host @host.expects(:save).never Puppet::Rails::Host.from_puppet(@node) end end describe "when converting a Puppet::Rails::Host instance into a Puppet::Node instance" do before do @host = Puppet::Rails::Host.new(:name => "foo", :environment => "production", :ip => "127.0.0.1") @node = Puppet::Node.new("foo") Puppet::Node.stubs(:new).with("foo").returns @node end it "should create a new instance with the correct name" do Puppet::Node.expects(:new).with("foo").returns @node @host.to_puppet end it "should copy the environment from the Rails instance" do @host.environment = "prod" @node.expects(:environment=).with "prod" @host.to_puppet end it "should copy the ipaddress from the Rails instance" do @host.ip = "192.168.0.1" @node.expects(:ipaddress=).with "192.168.0.1" @host.to_puppet end end describe "when merging catalog resources and database resources" do before :each do Puppet.settings.stubs(:[]).with(:thin_storeconfigs).returns(false) @resource1 = stub_everything 'res1' @resource2 = stub_everything 'res2' @resources = [ @resource1, @resource2 ] @dbresource1 = stub_everything 'dbres1' @dbresource2 = stub_everything 'dbres2' @dbresources = { 1 => @dbresource1, 2 => @dbresource2 } @host = Puppet::Rails::Host.new(:name => "foo", :environment => "production", :ip => "127.0.0.1") @host.stubs(:find_resources).returns(@dbresources) @host.stubs(:find_resources_parameters_tags) @host.stubs(:compare_to_catalog) @host.stubs(:id).returns(1) end it "should find all database resources" do @host.expects(:find_resources) @host.merge_resources(@resources) end it "should find all paramaters and tags for those database resources" do @host.expects(:find_resources_parameters_tags).with(@dbresources) @host.merge_resources(@resources) end it "should compare all database resources to catalog" do @host.expects(:compare_to_catalog).with(@dbresources, @resources) @host.merge_resources(@resources) end it "should compare only exported resources in thin_storeconfigs mode" do Puppet.settings.stubs(:[]).with(:thin_storeconfigs).returns(true) @resource1.stubs(:exported?).returns(true) @host.expects(:compare_to_catalog).with(@dbresources, [ @resource1 ]) @host.merge_resources(@resources) end end describe "when searching the database for host resources" do before :each do Puppet.settings.stubs(:[]).with(:thin_storeconfigs).returns(false) @resource1 = stub_everything 'res1', :id => 1 @resource2 = stub_everything 'res2', :id => 2 @resources = [ @resource1, @resource2 ] @dbresources = stub 'resources' @dbresources.stubs(:find).returns(@resources) @host = Puppet::Rails::Host.new(:name => "foo", :environment => "production", :ip => "127.0.0.1") @host.stubs(:resources).returns(@dbresources) end it "should return a hash keyed by id of all resources" do @host.find_resources.should == { 1 => @resource1, 2 => @resource2 } end it "should return a hash keyed by id of only exported resources in thin_storeconfigs mode" do Puppet.settings.stubs(:[]).with(:thin_storeconfigs).returns(true) @dbresources.expects(:find).with { |*h| h[1][:conditions] == { :exported => true } }.returns([]) @host.find_resources end end end diff --git a/spec/unit/rails/param_value_spec.rb b/spec/unit/rails/param_value_spec.rb index 243456e89..f67022a14 100755 --- a/spec/unit/rails/param_value_spec.rb +++ b/spec/unit/rails/param_value_spec.rb @@ -1,50 +1,48 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../spec_helper' require 'puppet/rails' -describe "Puppet::Rails::ParamValue" do - confine "Cannot test without ActiveRecord" => Puppet.features.rails? - +describe "Puppet::Rails::ParamValue", :if => Puppet.features.rails? do def column(name, type) ActiveRecord::ConnectionAdapters::Column.new(name, nil, type, false) end before do require 'puppet/rails/param_value' name = stub 'param_name', :name => "foo" # Stub this so we don't need access to the DB. Puppet::Rails::ParamValue.stubs(:columns).returns([column("value", "string")]) Puppet::Rails::ParamName.stubs(:find_or_create_by_name).returns(name) end describe "when creating initial parameter values" do it "should return an array of hashes" do Puppet::Rails::ParamValue.from_parser_param(:myparam, %w{a b})[0].should be_instance_of(Hash) end it "should return hashes for each value with the parameter name set as the ParamName instance" do name = stub 'param_name', :name => "foo" Puppet::Rails::ParamName.expects(:find_or_create_by_name).returns(name) result = Puppet::Rails::ParamValue.from_parser_param(:myparam, "a")[0] result[:value].should == "a" result[:param_name].should == name end it "should return an array of hashes even when only one parameter is provided" do Puppet::Rails::ParamValue.from_parser_param(:myparam, "a")[0].should be_instance_of(Hash) end it "should convert all arguments into strings" do Puppet::Rails::ParamValue.from_parser_param(:myparam, 50)[0][:value].should == "50" end it "should not convert Resource References into strings" do ref = Puppet::Resource.new(:file, "/file") Puppet::Rails::ParamValue.from_parser_param(:myparam, ref)[0][:value].should == ref end end end diff --git a/spec/unit/rails/resource_spec.rb b/spec/unit/rails/resource_spec.rb index 6e23d2020..e5bd8a6c9 100755 --- a/spec/unit/rails/resource_spec.rb +++ b/spec/unit/rails/resource_spec.rb @@ -1,124 +1,122 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../spec_helper' require 'puppet/rails' -describe "Puppet::Rails::Resource" do - confine "Cannot test without ActiveRecord" => Puppet.features.rails? - +describe "Puppet::Rails::Resource", :if => Puppet.features.rails? do def column(name, type) ActiveRecord::ConnectionAdapters::Column.new(name, nil, type, false) end before do require 'puppet/rails/resource' # Stub this so we don't need access to the DB. Puppet::Rails::Resource.stubs(:columns).returns([column("title", "string"), column("restype", "string"), column("exported", "boolean")]) end describe "when creating initial resource arguments" do it "should set the restype to the resource's type" do Puppet::Rails::Resource.rails_resource_initial_args(Puppet::Resource.new(:file, "/file"))[:restype].should == "File" end it "should set the title to the resource's title" do Puppet::Rails::Resource.rails_resource_initial_args(Puppet::Resource.new(:file, "/file"))[:title].should == "/file" end it "should set the line to the resource's line if one is available" do resource = Puppet::Resource.new(:file, "/file") resource.line = 50 Puppet::Rails::Resource.rails_resource_initial_args(resource)[:line].should == 50 end it "should set 'exported' to true of the resource is exported" do resource = Puppet::Resource.new(:file, "/file") resource.exported = true Puppet::Rails::Resource.rails_resource_initial_args(resource)[:exported].should be_true end it "should set 'exported' to false of the resource is not exported" do resource = Puppet::Resource.new(:file, "/file") resource.exported = false Puppet::Rails::Resource.rails_resource_initial_args(resource)[:exported].should be_false resource = Puppet::Resource.new(:file, "/file") resource.exported = nil Puppet::Rails::Resource.rails_resource_initial_args(resource)[:exported].should be_false end end describe "when merging in a parser resource" do before do @parser = mock 'parser resource' @resource = Puppet::Rails::Resource.new [:merge_attributes, :merge_parameters, :merge_tags, :save].each { |m| @resource.stubs(m) } end it "should merge the attributes" do @resource.expects(:merge_attributes).with(@parser) @resource.merge_parser_resource(@parser) end it "should merge the parameters" do @resource.expects(:merge_parameters).with(@parser) @resource.merge_parser_resource(@parser) end it "should merge the tags" do @resource.expects(:merge_tags).with(@parser) @resource.merge_parser_resource(@parser) end it "should save itself" do @resource.expects(:save) @resource.merge_parser_resource(@parser) end end describe "merge_parameters" do it "should replace values that have changed" do @resource = Puppet::Rails::Resource.new @resource.params_list = [{"name" => "replace", "value" => 1, "id" => 100 }] Puppet::Rails::ParamValue.expects(:delete).with([100]) param_values = stub "param_values" param_values.expects(:build).with({:value=>nil, :param_name=>nil, :line=>{"replace"=>2}}) @resource.stubs(:param_values).returns(param_values) Puppet::Rails::ParamName.stubs(:accumulate_by_name) merge_resource = stub "merge_resource" merge_resource.expects(:line).returns({ "replace" => 2 }) merge_resource.stubs(:each).yields([["replace", 2]]) @resource.merge_parameters(merge_resource) end end describe "#to_resource" do it "should instantiate a Puppet::Parser::Resource" do scope = stub "scope", :source => nil, :environment => nil, :namespaces => nil @resource = Puppet::Rails::Resource.new @resource.stubs(:attributes).returns({ "restype" => 'notify', "title" => 'hello' }) @resource.stubs(:param_names).returns([]) @resource.to_resource(scope).should be_a(Puppet::Parser::Resource) end end end diff --git a/spec/unit/rails_spec.rb b/spec/unit/rails_spec.rb index 24248e622..02b54efef 100755 --- a/spec/unit/rails_spec.rb +++ b/spec/unit/rails_spec.rb @@ -1,324 +1,314 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../spec_helper' require 'puppet/rails' -describe Puppet::Rails, "when initializing any connection" do - confine "Cannot test without ActiveRecord" => Puppet.features.rails? - +describe Puppet::Rails, "when initializing any connection", :if => Puppet.features.rails? do before do Puppet.settings.stubs(:use) @logger = mock 'logger' @logger.stub_everything Logger.stubs(:new).returns(@logger) ActiveRecord::Base.stubs(:logger).returns(@logger) ActiveRecord::Base.stubs(:connected?).returns(false) end it "should use settings" do Puppet.settings.expects(:use).with(:main, :rails, :master) Puppet::Rails.connect end it "should set up a logger with the appropriate Rails log file" do logger = mock 'logger' Logger.expects(:new).with(Puppet[:railslog]).returns(logger) ActiveRecord::Base.expects(:logger=).with(logger) Puppet::Rails.connect end it "should set the log level to whatever the value is in the settings" do Puppet.settings.stubs(:use) Puppet.settings.stubs(:value).with(:rails_loglevel).returns("debug") Puppet.settings.stubs(:value).with(:railslog).returns("/my/file") logger = mock 'logger' Logger.stubs(:new).returns(logger) ActiveRecord::Base.stubs(:logger).returns(logger) logger.expects(:level=).with(Logger::DEBUG) ActiveRecord::Base.stubs(:allow_concurrency=) ActiveRecord::Base.stubs(:verify_active_connections!) ActiveRecord::Base.stubs(:establish_connection) Puppet::Rails.stubs(:database_arguments).returns({}) Puppet::Rails.connect end describe "ActiveRecord Version" do it "should set ActiveRecord::Base.allow_concurrency if ActiveRecord is 2.1" do Puppet::Util.stubs(:activerecord_version).returns(2.1) ActiveRecord::Base.expects(:allow_concurrency=).with(true) Puppet::Rails.connect end it "should not set ActiveRecord::Base.allow_concurrency if ActiveRecord is >= 2.2" do Puppet::Util.stubs(:activerecord_version).returns(2.2) ActiveRecord::Base.expects(:allow_concurrency=).never Puppet::Rails.connect end end it "should call ActiveRecord::Base.verify_active_connections!" do ActiveRecord::Base.expects(:verify_active_connections!) Puppet::Rails.connect end it "should call ActiveRecord::Base.establish_connection with database_arguments" do Puppet::Rails.expects(:database_arguments).returns({}) ActiveRecord::Base.expects(:establish_connection) Puppet::Rails.connect end end -describe Puppet::Rails, "when initializing a sqlite3 connection" do - confine "Cannot test without ActiveRecord" => Puppet.features.rails? - +describe Puppet::Rails, "when initializing a sqlite3 connection", :if => Puppet.features.rails? do it "should provide the adapter, log_level, and database arguments" do Puppet.settings.expects(:value).with(:dbadapter).returns("sqlite3") Puppet.settings.expects(:value).with(:rails_loglevel).returns("testlevel") Puppet.settings.expects(:value).with(:dblocation).returns("testlocation") Puppet::Rails.database_arguments.should == { :adapter => "sqlite3", :log_level => "testlevel", :database => "testlocation" } end end -describe Puppet::Rails, "when initializing a mysql connection" do - confine "Cannot test without ActiveRecord" => Puppet.features.rails? - +describe Puppet::Rails, "when initializing a mysql connection", :if => Puppet.features.rails? do it "should provide the adapter, log_level, and host, port, username, password, database, and reconnect arguments" do Puppet.settings.stubs(:value).with(:dbadapter).returns("mysql") Puppet.settings.stubs(:value).with(:rails_loglevel).returns("testlevel") Puppet.settings.stubs(:value).with(:dbserver).returns("testserver") Puppet.settings.stubs(:value).with(:dbport).returns("") Puppet.settings.stubs(:value).with(:dbuser).returns("testuser") Puppet.settings.stubs(:value).with(:dbpassword).returns("testpassword") Puppet.settings.stubs(:value).with(:dbconnections).returns((pool_size = 45).to_s) Puppet.settings.stubs(:value).with(:dbname).returns("testname") Puppet.settings.stubs(:value).with(:dbsocket).returns("") Puppet::Rails.database_arguments.should == { :adapter => "mysql", :log_level => "testlevel", :host => "testserver", :username => "testuser", :password => "testpassword", :pool => pool_size, :database => "testname", :reconnect => true } end it "should provide the adapter, log_level, and host, port, username, password, database, socket, connections, and reconnect arguments" do Puppet.settings.stubs(:value).with(:dbadapter).returns("mysql") Puppet.settings.stubs(:value).with(:rails_loglevel).returns("testlevel") Puppet.settings.stubs(:value).with(:dbserver).returns("testserver") Puppet.settings.stubs(:value).with(:dbport).returns("9999") Puppet.settings.stubs(:value).with(:dbuser).returns("testuser") Puppet.settings.stubs(:value).with(:dbpassword).returns("testpassword") Puppet.settings.stubs(:value).with(:dbconnections).returns((pool_size = 12).to_s) Puppet.settings.stubs(:value).with(:dbname).returns("testname") Puppet.settings.stubs(:value).with(:dbsocket).returns("testsocket") Puppet::Rails.database_arguments.should == { :adapter => "mysql", :log_level => "testlevel", :host => "testserver", :port => "9999", :username => "testuser", :password => "testpassword", :pool => pool_size, :database => "testname", :socket => "testsocket", :reconnect => true } end it "should provide the adapter, log_level, and host, port, username, password, database, socket, and connections arguments" do Puppet.settings.stubs(:value).with(:dbadapter).returns("mysql") Puppet.settings.stubs(:value).with(:rails_loglevel).returns("testlevel") Puppet.settings.stubs(:value).with(:dbserver).returns("testserver") Puppet.settings.stubs(:value).with(:dbport).returns("9999") Puppet.settings.stubs(:value).with(:dbuser).returns("testuser") Puppet.settings.stubs(:value).with(:dbpassword).returns("testpassword") Puppet.settings.stubs(:value).with(:dbconnections).returns((pool_size = 23).to_s) Puppet.settings.stubs(:value).with(:dbname).returns("testname") Puppet.settings.stubs(:value).with(:dbsocket).returns("testsocket") Puppet::Rails.database_arguments.should == { :adapter => "mysql", :log_level => "testlevel", :host => "testserver", :port => "9999", :username => "testuser", :password => "testpassword", :pool => pool_size, :database => "testname", :socket => "testsocket", :reconnect => true } end it "should not provide the pool if dbconnections is 0, '0', or ''" do Puppet.settings.stubs(:value).with(:dbadapter).returns("mysql") Puppet.settings.stubs(:value).with(:rails_loglevel).returns("testlevel") Puppet.settings.stubs(:value).with(:dbserver).returns("testserver") Puppet.settings.stubs(:value).with(:dbport).returns("9999") Puppet.settings.stubs(:value).with(:dbuser).returns("testuser") Puppet.settings.stubs(:value).with(:dbpassword).returns("testpassword") Puppet.settings.stubs(:value).with(:dbname).returns("testname") Puppet.settings.stubs(:value).with(:dbsocket).returns("testsocket") Puppet.settings.stubs(:value).with(:dbconnections).returns(0) Puppet::Rails.database_arguments.should_not be_include(:pool) Puppet.settings.stubs(:value).with(:dbconnections).returns('0') Puppet::Rails.database_arguments.should_not be_include(:pool) Puppet.settings.stubs(:value).with(:dbconnections).returns('') Puppet::Rails.database_arguments.should_not be_include(:pool) end end -describe Puppet::Rails, "when initializing a postgresql connection" do - confine "Cannot test without ActiveRecord" => Puppet.features.rails? - +describe Puppet::Rails, "when initializing a postgresql connection", :if => Puppet.features.rails? do it "should provide the adapter, log_level, and host, port, username, password, connections, and database arguments" do Puppet.settings.stubs(:value).with(:dbadapter).returns("postgresql") Puppet.settings.stubs(:value).with(:rails_loglevel).returns("testlevel") Puppet.settings.stubs(:value).with(:dbserver).returns("testserver") Puppet.settings.stubs(:value).with(:dbport).returns("9999") Puppet.settings.stubs(:value).with(:dbuser).returns("testuser") Puppet.settings.stubs(:value).with(:dbpassword).returns("testpassword") Puppet.settings.stubs(:value).with(:dbconnections).returns((pool_size = 200).to_s) Puppet.settings.stubs(:value).with(:dbname).returns("testname") Puppet.settings.stubs(:value).with(:dbsocket).returns("") Puppet::Rails.database_arguments.should == { :adapter => "postgresql", :log_level => "testlevel", :host => "testserver", :port => "9999", :username => "testuser", :password => "testpassword", :pool => pool_size, :database => "testname", :reconnect => true } end it "should provide the adapter, log_level, and host, port, username, password, database, connections, and socket arguments" do Puppet.settings.stubs(:value).with(:dbadapter).returns("postgresql") Puppet.settings.stubs(:value).with(:rails_loglevel).returns("testlevel") Puppet.settings.stubs(:value).with(:dbserver).returns("testserver") Puppet.settings.stubs(:value).with(:dbport).returns("9999") Puppet.settings.stubs(:value).with(:dbuser).returns("testuser") Puppet.settings.stubs(:value).with(:dbpassword).returns("testpassword") Puppet.settings.stubs(:value).with(:dbconnections).returns((pool_size = 122).to_s) Puppet.settings.stubs(:value).with(:dbname).returns("testname") Puppet.settings.stubs(:value).with(:dbsocket).returns("testsocket") Puppet::Rails.database_arguments.should == { :adapter => "postgresql", :log_level => "testlevel", :host => "testserver", :port => "9999", :username => "testuser", :password => "testpassword", :pool => pool_size, :database => "testname", :socket => "testsocket", :reconnect => true } end it "should not provide the pool if dbconnections is 0, '0', or ''" do Puppet.settings.stubs(:value).with(:dbadapter).returns("mysql") Puppet.settings.stubs(:value).with(:rails_loglevel).returns("testlevel") Puppet.settings.stubs(:value).with(:dbserver).returns("testserver") Puppet.settings.stubs(:value).with(:dbport).returns("9999") Puppet.settings.stubs(:value).with(:dbuser).returns("testuser") Puppet.settings.stubs(:value).with(:dbpassword).returns("testpassword") Puppet.settings.stubs(:value).with(:dbname).returns("testname") Puppet.settings.stubs(:value).with(:dbsocket).returns("testsocket") Puppet.settings.stubs(:value).with(:dbconnections).returns(0) Puppet::Rails.database_arguments.should_not be_include(:pool) Puppet.settings.stubs(:value).with(:dbconnections).returns('0') Puppet::Rails.database_arguments.should_not be_include(:pool) Puppet.settings.stubs(:value).with(:dbconnections).returns('') Puppet::Rails.database_arguments.should_not be_include(:pool) end end -describe Puppet::Rails, "when initializing an Oracle connection" do - confine "Cannot test without ActiveRecord" => Puppet.features.rails? - +describe Puppet::Rails, "when initializing an Oracle connection", :if => Puppet.features.rails? do it "should provide the adapter, log_level, and username, password, and database arguments" do Puppet.settings.stubs(:value).with(:dbadapter).returns("oracle_enhanced") Puppet.settings.stubs(:value).with(:rails_loglevel).returns("testlevel") Puppet.settings.stubs(:value).with(:dbuser).returns("testuser") Puppet.settings.stubs(:value).with(:dbpassword).returns("testpassword") Puppet.settings.stubs(:value).with(:dbconnections).returns((pool_size = 123).to_s) Puppet.settings.stubs(:value).with(:dbname).returns("testname") Puppet::Rails.database_arguments.should == { :adapter => "oracle_enhanced", :log_level => "testlevel", :username => "testuser", :password => "testpassword", :pool => pool_size, :database => "testname" } end it "should provide the adapter, log_level, and host, username, password, database and socket arguments" do Puppet.settings.stubs(:value).with(:dbadapter).returns("oracle_enhanced") Puppet.settings.stubs(:value).with(:rails_loglevel).returns("testlevel") Puppet.settings.stubs(:value).with(:dbuser).returns("testuser") Puppet.settings.stubs(:value).with(:dbpassword).returns("testpassword") Puppet.settings.stubs(:value).with(:dbconnections).returns((pool_size = 124).to_s) Puppet.settings.stubs(:value).with(:dbname).returns("testname") Puppet::Rails.database_arguments.should == { :adapter => "oracle_enhanced", :log_level => "testlevel", :username => "testuser", :password => "testpassword", :pool => pool_size, :database => "testname" } end it "should not provide the pool if dbconnections is 0, '0', or ''" do Puppet.settings.stubs(:value).with(:dbadapter).returns("mysql") Puppet.settings.stubs(:value).with(:rails_loglevel).returns("testlevel") Puppet.settings.stubs(:value).with(:dbserver).returns("testserver") Puppet.settings.stubs(:value).with(:dbport).returns("9999") Puppet.settings.stubs(:value).with(:dbuser).returns("testuser") Puppet.settings.stubs(:value).with(:dbpassword).returns("testpassword") Puppet.settings.stubs(:value).with(:dbname).returns("testname") Puppet.settings.stubs(:value).with(:dbsocket).returns("testsocket") Puppet.settings.stubs(:value).with(:dbconnections).returns(0) Puppet::Rails.database_arguments.should_not be_include(:pool) Puppet.settings.stubs(:value).with(:dbconnections).returns('0') Puppet::Rails.database_arguments.should_not be_include(:pool) Puppet.settings.stubs(:value).with(:dbconnections).returns('') Puppet::Rails.database_arguments.should_not be_include(:pool) end end diff --git a/spec/unit/relationship_spec.rb b/spec/unit/relationship_spec.rb index 4586cd0f3..9ce6c56e7 100755 --- a/spec/unit/relationship_spec.rb +++ b/spec/unit/relationship_spec.rb @@ -1,236 +1,232 @@ #!/usr/bin/env ruby # # Created by Luke Kanies on 2007-11-1. # Copyright (c) 2006. All rights reserved. require File.dirname(__FILE__) + '/../spec_helper' require 'puppet/relationship' describe Puppet::Relationship do before do @edge = Puppet::Relationship.new(:a, :b) end it "should have a :source attribute" do @edge.should respond_to(:source) end it "should have a :target attribute" do @edge.should respond_to(:target) end it "should have a :callback attribute" do @edge.callback = :foo @edge.callback.should == :foo end it "should have an :event attribute" do @edge.event = :NONE @edge.event.should == :NONE end it "should require a callback if a non-NONE event is specified" do proc { @edge.event = :something }.should raise_error(ArgumentError) end it "should have a :label attribute" do @edge.should respond_to(:label) end it "should provide a :ref method that describes the edge" do @edge = Puppet::Relationship.new("a", "b") @edge.ref.should == "a => b" end it "should be able to produce a label as a hash with its event and callback" do @edge.callback = :foo @edge.event = :bar @edge.label.should == {:callback => :foo, :event => :bar} end it "should work if nil options are provided" do lambda { Puppet::Relationship.new("a", "b", nil) }.should_not raise_error end end describe Puppet::Relationship, " when initializing" do before do @edge = Puppet::Relationship.new(:a, :b) end it "should use the first argument as the source" do @edge.source.should == :a end it "should use the second argument as the target" do @edge.target.should == :b end it "should set the rest of the arguments as the event and callback" do @edge = Puppet::Relationship.new(:a, :b, :callback => :foo, :event => :bar) @edge.callback.should == :foo @edge.event.should == :bar end it "should accept events specified as strings" do @edge = Puppet::Relationship.new(:a, :b, "event" => :NONE) @edge.event.should == :NONE end it "should accept callbacks specified as strings" do @edge = Puppet::Relationship.new(:a, :b, "callback" => :foo) @edge.callback.should == :foo end end describe Puppet::Relationship, " when matching edges with no specified event" do before do @edge = Puppet::Relationship.new(:a, :b) end it "should not match :NONE" do @edge.should_not be_match(:NONE) end it "should not match :ALL_EVENTS" do @edge.should_not be_match(:NONE) end it "should not match any other events" do @edge.should_not be_match(:whatever) end end describe Puppet::Relationship, " when matching edges with :NONE as the event" do before do @edge = Puppet::Relationship.new(:a, :b, :event => :NONE) end it "should not match :NONE" do @edge.should_not be_match(:NONE) end it "should not match :ALL_EVENTS" do @edge.should_not be_match(:ALL_EVENTS) end it "should not match other events" do @edge.should_not be_match(:yayness) end end describe Puppet::Relationship, " when matching edges with :ALL as the event" do before do @edge = Puppet::Relationship.new(:a, :b, :event => :ALL_EVENTS, :callback => :whatever) end it "should not match :NONE" do @edge.should_not be_match(:NONE) end it "should match :ALL_EVENTS" do @edge.should be_match(:ALLEVENTS) end it "should match all other events" do @edge.should be_match(:foo) end end describe Puppet::Relationship, " when matching edges with a non-standard event" do before do @edge = Puppet::Relationship.new(:a, :b, :event => :random, :callback => :whatever) end it "should not match :NONE" do @edge.should_not be_match(:NONE) end it "should not match :ALL_EVENTS" do @edge.should_not be_match(:ALL_EVENTS) end it "should match events with the same name" do @edge.should be_match(:random) end end -describe Puppet::Relationship, "when converting to pson" do - confine "Missing 'pson' library" => Puppet.features.pson? - +describe Puppet::Relationship, "when converting to pson", :if => Puppet.features.pson? do before do @edge = Puppet::Relationship.new(:a, :b, :event => :random, :callback => :whatever) end it "should store the stringified source as the source in the data" do PSON.parse(@edge.to_pson)["source"].should == "a" end it "should store the stringified target as the target in the data" do PSON.parse(@edge.to_pson)['target'].should == "b" end it "should store the psonified event as the event in the data" do PSON.parse(@edge.to_pson)["event"].should == "random" end it "should not store an event when none is set" do @edge.event = nil PSON.parse(@edge.to_pson)["event"].should be_nil end it "should store the psonified callback as the callback in the data" do @edge.callback = "whatever" PSON.parse(@edge.to_pson)["callback"].should == "whatever" end it "should not store a callback when none is set in the edge" do @edge.callback = nil PSON.parse(@edge.to_pson)["callback"].should be_nil end end -describe Puppet::Relationship, "when converting from pson" do - confine "Missing 'pson' library" => Puppet.features.pson? - +describe Puppet::Relationship, "when converting from pson", :if => Puppet.features.pson? do before do @event = "random" @callback = "whatever" @data = { "source" => "mysource", "target" => "mytarget", "event" => @event, "callback" => @callback } @pson = { "type" => "Puppet::Relationship", "data" => @data } end def pson_result_should Puppet::Relationship.expects(:new).with { |*args| yield args } end it "should be extended with the PSON utility module" do Puppet::Relationship.singleton_class.ancestors.should be_include(Puppet::Util::Pson) end # LAK:NOTE For all of these tests, we convert back to the edge so we can # trap the actual data structure then. it "should pass the source in as the first argument" do Puppet::Relationship.from_pson("source" => "mysource", "target" => "mytarget").source.should == "mysource" end it "should pass the target in as the second argument" do Puppet::Relationship.from_pson("source" => "mysource", "target" => "mytarget").target.should == "mytarget" end it "should pass the event as an argument if it's provided" do Puppet::Relationship.from_pson("source" => "mysource", "target" => "mytarget", "event" => "myevent", "callback" => "eh").event.should == "myevent" end it "should pass the callback as an argument if it's provided" do Puppet::Relationship.from_pson("source" => "mysource", "target" => "mytarget", "callback" => "mycallback").callback.should == "mycallback" end end diff --git a/spec/unit/reports/http_spec.rb b/spec/unit/reports/http_spec.rb index c814975df..70742f7dc 100644 --- a/spec/unit/reports/http_spec.rb +++ b/spec/unit/reports/http_spec.rb @@ -1,56 +1,56 @@ #!/usr/bin/env ruby Dir.chdir(File.dirname(__FILE__)) { (s = lambda { |f| File.exist?(f) ? require(f) : Dir.chdir("..") { s.call(f) } }).call("spec/spec_helper.rb") } require 'puppet/reports' # FakeHTTP fakes the behavior of Net::HTTP#request and acts as a sensor for an # otherwise difficult to trace method call. # class FakeHTTP REQUESTS = {} def self.request(req) REQUESTS[req.path] = req end end processor = Puppet::Reports.report(:http) describe processor do before { Net::HTTP.any_instance.stubs(:start).yields(FakeHTTP) } - subject { Puppet::Transaction::Report.new.extend(processor) } + subject { Puppet::Transaction::Report.new("apply").extend(processor) } it { should respond_to(:process) } it "should use the reporturl setting's host and port" do uri = URI.parse(Puppet[:reporturl]) Net::HTTP.expects(:new).with(uri.host, uri.port).returns(stub_everything('http')) subject.process end describe "request" do before { subject.process } describe "path" do it "should use the path specified by the 'reporturl' setting" do reports_request.path.should == URI.parse(Puppet[:reporturl]).path end end describe "body" do it "should be the report as YAML" do reports_request.body.should == subject.to_yaml end end describe "content type" do it "should be 'application/x-yaml'" do reports_request.content_type.should == "application/x-yaml" end end end private def reports_request; FakeHTTP::REQUESTS[URI.parse(Puppet[:reporturl]).path] end end diff --git a/spec/unit/reports/tagmail_spec.rb b/spec/unit/reports/tagmail_spec.rb index bdb16600e..1dadfc7cd 100755 --- a/spec/unit/reports/tagmail_spec.rb +++ b/spec/unit/reports/tagmail_spec.rb @@ -1,95 +1,95 @@ #!/usr/bin/env ruby Dir.chdir(File.dirname(__FILE__)) { (s = lambda { |f| File.exist?(f) ? require(f) : Dir.chdir("..") { s.call(f) } }).call("spec/spec_helper.rb") } require 'puppet/reports' require 'puppettest/support/utils' tagmail = Puppet::Reports.report(:tagmail) describe tagmail do extend PuppetTest::Support::Utils before do - @processor = Puppet::Transaction::Report.new + @processor = Puppet::Transaction::Report.new("apply") @processor.extend(Puppet::Reports.report(:tagmail)) end passers = File.join(datadir, "reports", "tagmail_passers.conf") File.readlines(passers).each do |line| it "should be able to parse '#{line.inspect}'" do @processor.parse(line) end end failers = File.join(datadir, "reports", "tagmail_failers.conf") File.readlines(failers).each do |line| it "should not be able to parse '#{line.inspect}'" do lambda { @processor.parse(line) }.should raise_error(ArgumentError) end end { "tag: abuse@domain.com" => [%w{abuse@domain.com}, %w{tag}, []], "tag.localhost: abuse@domain.com" => [%w{abuse@domain.com}, %w{tag.localhost}, []], "tag, other: abuse@domain.com" => [%w{abuse@domain.com}, %w{tag other}, []], "tag-other: abuse@domain.com" => [%w{abuse@domain.com}, %w{tag-other}, []], "tag, !other: abuse@domain.com" => [%w{abuse@domain.com}, %w{tag}, %w{other}], "tag, !other, one, !two: abuse@domain.com" => [%w{abuse@domain.com}, %w{tag one}, %w{other two}], "tag: abuse@domain.com, other@domain.com" => [%w{abuse@domain.com other@domain.com}, %w{tag}, []] }.each do |line, results| it "should parse '#{line}' as #{results.inspect}" do @processor.parse(line).shift.should == results end end describe "when matching logs" do before do @processor << Puppet::Util::Log.new(:level => :notice, :message => "first", :tags => %w{one}) @processor << Puppet::Util::Log.new(:level => :notice, :message => "second", :tags => %w{one two}) @processor << Puppet::Util::Log.new(:level => :notice, :message => "third", :tags => %w{one two three}) end def match(pos = [], neg = []) pos = Array(pos) neg = Array(neg) result = @processor.match([[%w{abuse@domain.com}, pos, neg]]) actual_result = result.shift if actual_result actual_result[1] else nil end end it "should match all messages when provided the 'all' tag as a positive matcher" do results = match("all") %w{first second third}.each do |str| results.should be_include(str) end end it "should remove messages that match a negated tag" do match("all", "three").should_not be_include("third") end it "should find any messages tagged with a provided tag" do results = match("two") results.should be_include("second") results.should be_include("third") results.should_not be_include("first") end it "should allow negation of specific tags from a specific tag list" do results = match("two", "three") results.should be_include("second") results.should_not be_include("third") end it "should allow a tag to negate all matches" do results = match([], "one") results.should be_nil end end end diff --git a/spec/unit/resource/catalog_spec.rb b/spec/unit/resource/catalog_spec.rb index 2b6beb5e9..942721464 100755 --- a/spec/unit/resource/catalog_spec.rb +++ b/spec/unit/resource/catalog_spec.rb @@ -1,1078 +1,1074 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../spec_helper' describe Puppet::Resource::Catalog, "when compiling" do before do @basepath = Puppet.features.posix? ? "/somepath" : "C:/somepath" # stub this to not try to create state.yaml Puppet::Util::Storage.stubs(:store) end it "should be an Expirer" do Puppet::Resource::Catalog.ancestors.should be_include(Puppet::Util::Cacher::Expirer) end it "should always be expired if it's not applying" do @catalog = Puppet::Resource::Catalog.new("host") @catalog.expects(:applying?).returns false @catalog.should be_dependent_data_expired(Time.now) end it "should not be expired if it's applying and the timestamp is late enough" do @catalog = Puppet::Resource::Catalog.new("host") @catalog.expire @catalog.expects(:applying?).returns true @catalog.should_not be_dependent_data_expired(Time.now) end it "should be able to write its list of classes to the class file" do @catalog = Puppet::Resource::Catalog.new("host") @catalog.add_class "foo", "bar" Puppet.settings.expects(:value).with(:classfile).returns "/class/file" fh = mock 'filehandle' File.expects(:open).with("/class/file", "w").yields fh fh.expects(:puts).with "foo\nbar" @catalog.write_class_file end it "should have a client_version attribute" do @catalog = Puppet::Resource::Catalog.new("host") @catalog.client_version = 5 @catalog.client_version.should == 5 end it "should have a server_version attribute" do @catalog = Puppet::Resource::Catalog.new("host") @catalog.server_version = 5 @catalog.server_version.should == 5 end describe "when compiling" do it "should accept tags" do config = Puppet::Resource::Catalog.new("mynode") config.tag("one") config.tags.should == %w{one} end it "should accept multiple tags at once" do config = Puppet::Resource::Catalog.new("mynode") config.tag("one", "two") config.tags.should == %w{one two} end it "should convert all tags to strings" do config = Puppet::Resource::Catalog.new("mynode") config.tag("one", :two) config.tags.should == %w{one two} end it "should tag with both the qualified name and the split name" do config = Puppet::Resource::Catalog.new("mynode") config.tag("one::two") config.tags.include?("one").should be_true config.tags.include?("one::two").should be_true end it "should accept classes" do config = Puppet::Resource::Catalog.new("mynode") config.add_class("one") config.classes.should == %w{one} config.add_class("two", "three") config.classes.should == %w{one two three} end it "should tag itself with passed class names" do config = Puppet::Resource::Catalog.new("mynode") config.add_class("one") config.tags.should == %w{one} end end describe "when extracting transobjects" do def mkscope @node = Puppet::Node.new("mynode") @compiler = Puppet::Parser::Compiler.new(@node) # XXX This is ridiculous. @compiler.send(:evaluate_main) @scope = @compiler.topscope end def mkresource(type, name) Puppet::Parser::Resource.new(type, name, :source => @source, :scope => @scope) end it "should fail if no 'main' stage can be found" do lambda { Puppet::Resource::Catalog.new("mynode").extract }.should raise_error(Puppet::DevError) end it "should warn if any non-main stages are present" do config = Puppet::Resource::Catalog.new("mynode") @scope = mkscope @source = mock 'source' main = mkresource("stage", "main") config.add_resource(main) other = mkresource("stage", "other") config.add_resource(other) Puppet.expects(:warning) config.extract end it "should always create a TransBucket for the 'main' stage" do config = Puppet::Resource::Catalog.new("mynode") @scope = mkscope @source = mock 'source' main = mkresource("stage", "main") config.add_resource(main) result = config.extract result.type.should == "Stage" result.name.should == "main" end # Now try it with a more complicated graph -- a three tier graph, each tier it "should transform arbitrarily deep graphs into isomorphic trees" do config = Puppet::Resource::Catalog.new("mynode") @scope = mkscope @scope.stubs(:tags).returns([]) @source = mock 'source' # Create our scopes. top = mkresource "stage", "main" config.add_resource top topbucket = [] topbucket.expects(:classes=).with([]) top.expects(:to_trans).returns(topbucket) topres = mkresource "file", "/top" topres.expects(:to_trans).returns(:topres) config.add_edge top, topres middle = mkresource "class", "middle" middle.expects(:to_trans).returns([]) config.add_edge top, middle midres = mkresource "file", "/mid" midres.expects(:to_trans).returns(:midres) config.add_edge middle, midres bottom = mkresource "class", "bottom" bottom.expects(:to_trans).returns([]) config.add_edge middle, bottom botres = mkresource "file", "/bot" botres.expects(:to_trans).returns(:botres) config.add_edge bottom, botres toparray = config.extract # This is annoying; it should look like: # [[[:botres], :midres], :topres] # but we can't guarantee sort order. toparray.include?(:topres).should be_true midarray = toparray.find { |t| t.is_a?(Array) } midarray.include?(:midres).should be_true botarray = midarray.find { |t| t.is_a?(Array) } botarray.include?(:botres).should be_true end end describe " when converting to a Puppet::Resource catalog" do before do @original = Puppet::Resource::Catalog.new("mynode") @original.tag(*%w{one two three}) @original.add_class *%w{four five six} @top = Puppet::TransObject.new 'top', "class" @topobject = Puppet::TransObject.new '/topobject', "file" @middle = Puppet::TransObject.new 'middle', "class" @middleobject = Puppet::TransObject.new '/middleobject', "file" @bottom = Puppet::TransObject.new 'bottom', "class" @bottomobject = Puppet::TransObject.new '/bottomobject', "file" @resources = [@top, @topobject, @middle, @middleobject, @bottom, @bottomobject] @original.add_resource(*@resources) @original.add_edge(@top, @topobject) @original.add_edge(@top, @middle) @original.add_edge(@middle, @middleobject) @original.add_edge(@middle, @bottom) @original.add_edge(@bottom, @bottomobject) @catalog = @original.to_resource end it "should copy over the version" do @original.version = "foo" @original.to_resource.version.should == "foo" end it "should convert parser resources to plain resources" do resource = Puppet::Parser::Resource.new(:file, "foo", :scope => stub("scope", :environment => nil, :namespaces => nil), :source => stub("source")) catalog = Puppet::Resource::Catalog.new("whev") catalog.add_resource(resource) new = catalog.to_resource new.resource(:file, "foo").class.should == Puppet::Resource end it "should add all resources as Puppet::Resource instances" do @resources.each { |resource| @catalog.resource(resource.ref).should be_instance_of(Puppet::Resource) } end it "should copy the tag list to the new catalog" do @catalog.tags.sort.should == @original.tags.sort end it "should copy the class list to the new catalog" do @catalog.classes.should == @original.classes end it "should duplicate the original edges" do @original.edges.each do |edge| @catalog.edge?(@catalog.resource(edge.source.ref), @catalog.resource(edge.target.ref)).should be_true end end it "should set itself as the catalog for each converted resource" do @catalog.vertices.each { |v| v.catalog.object_id.should equal(@catalog.object_id) } end end describe "when converting to a RAL catalog" do before do @original = Puppet::Resource::Catalog.new("mynode") @original.tag(*%w{one two three}) @original.add_class *%w{four five six} @top = Puppet::Resource.new :class, 'top' @topobject = Puppet::Resource.new :file, @basepath+'/topobject' @middle = Puppet::Resource.new :class, 'middle' @middleobject = Puppet::Resource.new :file, @basepath+'/middleobject' @bottom = Puppet::Resource.new :class, 'bottom' @bottomobject = Puppet::Resource.new :file, @basepath+'/bottomobject' @resources = [@top, @topobject, @middle, @middleobject, @bottom, @bottomobject] @original.add_resource(*@resources) @original.add_edge(@top, @topobject) @original.add_edge(@top, @middle) @original.add_edge(@middle, @middleobject) @original.add_edge(@middle, @bottom) @original.add_edge(@bottom, @bottomobject) @catalog = @original.to_ral end it "should add all resources as RAL instances" do @resources.each { |resource| @catalog.resource(resource.ref).should be_instance_of(Puppet::Type) } end it "should copy the tag list to the new catalog" do @catalog.tags.sort.should == @original.tags.sort end it "should copy the class list to the new catalog" do @catalog.classes.should == @original.classes end it "should duplicate the original edges" do @original.edges.each do |edge| @catalog.edge?(@catalog.resource(edge.source.ref), @catalog.resource(edge.target.ref)).should be_true end end it "should set itself as the catalog for each converted resource" do @catalog.vertices.each { |v| v.catalog.object_id.should equal(@catalog.object_id) } end # This tests #931. it "should not lose track of resources whose names vary" do changer = Puppet::TransObject.new 'changer', 'test' config = Puppet::Resource::Catalog.new('test') config.add_resource(changer) config.add_resource(@top) config.add_edge(@top, changer) resource = stub 'resource', :name => "changer2", :title => "changer2", :ref => "Test[changer2]", :catalog= => nil, :remove => nil #changer is going to get duplicated as part of a fix for aliases 1094 changer.expects(:dup).returns(changer) changer.expects(:to_ral).returns(resource) newconfig = nil proc { @catalog = config.to_ral }.should_not raise_error @catalog.resource("Test[changer2]").should equal(resource) end after do # Remove all resource instances. @catalog.clear(true) end end describe "when filtering" do before :each do @original = Puppet::Resource::Catalog.new("mynode") @original.tag(*%w{one two three}) @original.add_class *%w{four five six} @r1 = stub_everything 'r1', :ref => "File[/a]" @r1.stubs(:respond_to?).with(:ref).returns(true) @r1.stubs(:dup).returns(@r1) @r1.stubs(:is_a?).returns(Puppet::Resource).returns(true) @r2 = stub_everything 'r2', :ref => "File[/b]" @r2.stubs(:respond_to?).with(:ref).returns(true) @r2.stubs(:dup).returns(@r2) @r2.stubs(:is_a?).returns(Puppet::Resource).returns(true) @resources = [@r1,@r2] @original.add_resource(@r1,@r2) end it "should transform the catalog to a resource catalog" do @original.expects(:to_catalog).with { |h,b| h == :to_resource } @original.filter end it "should scan each catalog resource in turn and apply filtering block" do @resources.each { |r| r.expects(:test?) } @original.filter do |r| r.test? end end it "should filter out resources which produce true when the filter block is evaluated" do @original.filter do |r| r == @r1 end.resource("File[/a]").should be_nil end it "should not consider edges against resources that were filtered out" do @original.add_edge(@r1,@r2) @original.filter do |r| r == @r1 end.edge(@r1,@r2).should be_empty end end describe "when functioning as a resource container" do before do @catalog = Puppet::Resource::Catalog.new("host") @one = Puppet::Type.type(:notify).new :name => "one" @two = Puppet::Type.type(:notify).new :name => "two" @dupe = Puppet::Type.type(:notify).new :name => "one" end it "should provide a method to add one or more resources" do @catalog.add_resource @one, @two @catalog.resource(@one.ref).should equal(@one) @catalog.resource(@two.ref).should equal(@two) end it "should add resources to the relationship graph if it exists" do relgraph = @catalog.relationship_graph @catalog.add_resource @one relgraph.should be_vertex(@one) end it "should yield added resources if a block is provided" do yielded = [] @catalog.add_resource(@one, @two) { |r| yielded << r } yielded.length.should == 2 end it "should set itself as the resource's catalog if it is not a relationship graph" do @one.expects(:catalog=).with(@catalog) @catalog.add_resource @one end it "should make all vertices available by resource reference" do @catalog.add_resource(@one) @catalog.resource(@one.ref).should equal(@one) @catalog.vertices.find { |r| r.ref == @one.ref }.should equal(@one) end it "should canonize how resources are referred to during retrieval when both type and title are provided" do @catalog.add_resource(@one) @catalog.resource("notify", "one").should equal(@one) end it "should canonize how resources are referred to during retrieval when just the title is provided" do @catalog.add_resource(@one) @catalog.resource("notify[one]", nil).should equal(@one) end it "should not allow two resources with the same resource reference" do @catalog.add_resource(@one) proc { @catalog.add_resource(@dupe) }.should raise_error(Puppet::Resource::Catalog::DuplicateResourceError) end it "should not store objects that do not respond to :ref" do proc { @catalog.add_resource("thing") }.should raise_error(ArgumentError) end it "should remove all resources when asked" do @catalog.add_resource @one @catalog.add_resource @two @one.expects :remove @two.expects :remove @catalog.clear(true) end it "should support a mechanism for finishing resources" do @one.expects :finish @two.expects :finish @catalog.add_resource @one @catalog.add_resource @two @catalog.finalize end it "should make default resources when finalizing" do @catalog.expects(:make_default_resources) @catalog.finalize end it "should add default resources to the catalog upon creation" do @catalog.make_default_resources @catalog.resource(:schedule, "daily").should_not be_nil end it "should optionally support an initialization block and should finalize after such blocks" do @one.expects :finish @two.expects :finish config = Puppet::Resource::Catalog.new("host") do |conf| conf.add_resource @one conf.add_resource @two end end it "should inform the resource that it is the resource's catalog" do @one.expects(:catalog=).with(@catalog) @catalog.add_resource @one end it "should be able to find resources by reference" do @catalog.add_resource @one @catalog.resource(@one.ref).should equal(@one) end it "should be able to find resources by reference or by type/title tuple" do @catalog.add_resource @one @catalog.resource("notify", "one").should equal(@one) end it "should have a mechanism for removing resources" do @catalog.add_resource @one @one.expects :remove @catalog.remove_resource(@one) @catalog.resource(@one.ref).should be_nil @catalog.vertex?(@one).should be_false end it "should have a method for creating aliases for resources" do @catalog.add_resource @one @catalog.alias(@one, "other") @catalog.resource("notify", "other").should equal(@one) end it "should ignore conflicting aliases that point to the aliased resource" do @catalog.alias(@one, "other") lambda { @catalog.alias(@one, "other") }.should_not raise_error end it "should create aliases for resources isomorphic resources whose names do not match their titles" do resource = Puppet::Type::File.new(:title => "testing", :path => @basepath+"/something") @catalog.add_resource(resource) @catalog.resource(:file, @basepath+"/something").should equal(resource) end it "should not create aliases for resources non-isomorphic resources whose names do not match their titles" do resource = Puppet::Type.type(:exec).new(:title => "testing", :command => "echo", :path => %w{/bin /usr/bin /usr/local/bin}) @catalog.add_resource(resource) # Yay, I've already got a 'should' method @catalog.resource(:exec, "echo").object_id.should == nil.object_id end # This test is the same as the previous, but the behaviour should be explicit. it "should alias using the class name from the resource reference, not the resource class name" do @catalog.add_resource @one @catalog.alias(@one, "other") @catalog.resource("notify", "other").should equal(@one) end it "should ignore conflicting aliases that point to the aliased resource" do @catalog.alias(@one, "other") lambda { @catalog.alias(@one, "other") }.should_not raise_error end it "should fail to add an alias if the aliased name already exists" do @catalog.add_resource @one proc { @catalog.alias @two, "one" }.should raise_error(ArgumentError) end it "should not fail when a resource has duplicate aliases created" do @catalog.add_resource @one proc { @catalog.alias @one, "one" }.should_not raise_error end it "should not create aliases that point back to the resource" do @catalog.alias(@one, "one") @catalog.resource(:notify, "one").should be_nil end it "should be able to look resources up by their aliases" do @catalog.add_resource @one @catalog.alias @one, "two" @catalog.resource(:notify, "two").should equal(@one) end it "should remove resource aliases when the target resource is removed" do @catalog.add_resource @one @catalog.alias(@one, "other") @one.expects :remove @catalog.remove_resource(@one) @catalog.resource("notify", "other").should be_nil end it "should add an alias for the namevar when the title and name differ on isomorphic resource types" do resource = Puppet::Type.type(:file).new :path => @basepath+"/something", :title => "other", :content => "blah" resource.expects(:isomorphic?).returns(true) @catalog.add_resource(resource) @catalog.resource(:file, "other").should equal(resource) @catalog.resource(:file, @basepath+"/something").ref.should == resource.ref end it "should not add an alias for the namevar when the title and name differ on non-isomorphic resource types" do resource = Puppet::Type.type(:file).new :path => @basepath+"/something", :title => "other", :content => "blah" resource.expects(:isomorphic?).returns(false) @catalog.add_resource(resource) @catalog.resource(:file, resource.title).should equal(resource) # We can't use .should here, because the resources respond to that method. raise "Aliased non-isomorphic resource" if @catalog.resource(:file, resource.name) end it "should provide a method to create additional resources that also registers the resource" do args = {:name => "/yay", :ensure => :file} resource = stub 'file', :ref => "File[/yay]", :catalog= => @catalog, :title => "/yay", :[] => "/yay" Puppet::Type.type(:file).expects(:new).with(args).returns(resource) @catalog.create_resource :file, args @catalog.resource("File[/yay]").should equal(resource) end end describe "when applying" do before :each do @catalog = Puppet::Resource::Catalog.new("host") @transaction = mock 'transaction' Puppet::Transaction.stubs(:new).returns(@transaction) @transaction.stubs(:evaluate) @transaction.stubs(:add_times) Puppet.settings.stubs(:use) end it "should create and evaluate a transaction" do @transaction.expects(:evaluate) @catalog.apply end it "should provide the catalog retrieval time to the transaction" do @catalog.retrieval_duration = 5 @transaction.expects(:add_times).with(:config_retrieval => 5) @catalog.apply end it "should use a retrieval time of 0 if none is set in the catalog" do @catalog.retrieval_duration = nil @transaction.expects(:add_times).with(:config_retrieval => 0) @catalog.apply end it "should return the transaction" do @catalog.apply.should equal(@transaction) end it "should yield the transaction if a block is provided" do @catalog.apply do |trans| trans.should equal(@transaction) end end it "should default to being a host catalog" do @catalog.host_config.should be_true end it "should be able to be set to a non-host_config" do @catalog.host_config = false @catalog.host_config.should be_false end it "should pass supplied tags on to the transaction" do @transaction.expects(:tags=).with(%w{one two}) @catalog.apply(:tags => %w{one two}) end it "should set ignoreschedules on the transaction if specified in apply()" do @transaction.expects(:ignoreschedules=).with(true) @catalog.apply(:ignoreschedules => true) end it "should expire cached data in the resources both before and after the transaction" do @catalog.expects(:expire).times(2) @catalog.apply end describe "host catalogs" do # super() doesn't work in the setup method for some reason before do @catalog.host_config = true Puppet::Util::Storage.stubs(:store) end it "should initialize the state database before applying a catalog" do Puppet::Util::Storage.expects(:load) # Short-circuit the apply, so we know we're loading before the transaction Puppet::Transaction.expects(:new).raises ArgumentError proc { @catalog.apply }.should raise_error(ArgumentError) end it "should sync the state database after applying" do Puppet::Util::Storage.expects(:store) @transaction.stubs :any_failed? => false @catalog.apply end after { Puppet.settings.clear } end describe "non-host catalogs" do before do @catalog.host_config = false end it "should never send reports" do Puppet[:report] = true Puppet[:summarize] = true @catalog.apply end it "should never modify the state database" do Puppet::Util::Storage.expects(:load).never Puppet::Util::Storage.expects(:store).never @catalog.apply end after { Puppet.settings.clear } end end describe "when creating a relationship graph" do before do Puppet::Type.type(:component) @catalog = Puppet::Resource::Catalog.new("host") @compone = Puppet::Type::Component.new :name => "one" @comptwo = Puppet::Type::Component.new :name => "two", :require => "Class[one]" @file = Puppet::Type.type(:file) @one = @file.new :path => @basepath+"/one" @two = @file.new :path => @basepath+"/two" @sub = @file.new :path => @basepath+"/two/subdir" @catalog.add_edge @compone, @one @catalog.add_edge @comptwo, @two @three = @file.new :path => @basepath+"/three" @four = @file.new :path => @basepath+"/four", :require => "File[#{@basepath}/three]" @five = @file.new :path => @basepath+"/five" @catalog.add_resource @compone, @comptwo, @one, @two, @three, @four, @five, @sub @relationships = @catalog.relationship_graph end it "should be able to create a relationship graph" do @relationships.should be_instance_of(Puppet::SimpleGraph) end it "should not have any components" do @relationships.vertices.find { |r| r.instance_of?(Puppet::Type::Component) }.should be_nil end it "should have all non-component resources from the catalog" do # The failures print out too much info, so i just do a class comparison @relationships.vertex?(@five).should be_true end it "should have all resource relationships set as edges" do @relationships.edge?(@three, @four).should be_true end it "should copy component relationships to all contained resources" do @relationships.edge?(@one, @two).should be_true end it "should add automatic relationships to the relationship graph" do @relationships.edge?(@two, @sub).should be_true end it "should get removed when the catalog is cleaned up" do @relationships.expects(:clear) @catalog.clear @catalog.instance_variable_get("@relationship_graph").should be_nil end it "should write :relationships and :expanded_relationships graph files if the catalog is a host catalog" do @catalog.clear graph = Puppet::SimpleGraph.new Puppet::SimpleGraph.expects(:new).returns graph graph.expects(:write_graph).with(:relationships) graph.expects(:write_graph).with(:expanded_relationships) @catalog.host_config = true @catalog.relationship_graph end it "should not write graph files if the catalog is not a host catalog" do @catalog.clear graph = Puppet::SimpleGraph.new Puppet::SimpleGraph.expects(:new).returns graph graph.expects(:write_graph).never @catalog.host_config = false @catalog.relationship_graph end it "should create a new relationship graph after clearing the old one" do @relationships.expects(:clear) @catalog.clear @catalog.relationship_graph.should be_instance_of(Puppet::SimpleGraph) end it "should remove removed resources from the relationship graph if it exists" do @catalog.remove_resource(@one) @catalog.relationship_graph.vertex?(@one).should be_false end end describe "when writing dot files" do before do @catalog = Puppet::Resource::Catalog.new("host") @name = :test @file = File.join(Puppet[:graphdir], @name.to_s + ".dot") end it "should only write when it is a host catalog" do File.expects(:open).with(@file).never @catalog.host_config = false Puppet[:graph] = true @catalog.write_graph(@name) end after do Puppet.settings.clear end end describe "when indirecting" do before do @real_indirection = Puppet::Resource::Catalog.indirection @indirection = stub 'indirection', :name => :catalog Puppet::Util::Cacher.expire end it "should redirect to the indirection for retrieval" do Puppet::Resource::Catalog.stubs(:indirection).returns(@indirection) @indirection.expects(:find) Puppet::Resource::Catalog.find(:myconfig) end it "should use the value of the 'catalog_terminus' setting to determine its terminus class" do # Puppet only checks the terminus setting the first time you ask # so this returns the object to the clean state # at the expense of making this test less pure Puppet::Resource::Catalog.indirection.reset_terminus_class Puppet.settings[:catalog_terminus] = "rest" Puppet::Resource::Catalog.indirection.terminus_class.should == :rest end it "should allow the terminus class to be set manually" do Puppet::Resource::Catalog.indirection.terminus_class = :rest Puppet::Resource::Catalog.indirection.terminus_class.should == :rest end after do Puppet::Util::Cacher.expire @real_indirection.reset_terminus_class end end describe "when converting to yaml" do before do @catalog = Puppet::Resource::Catalog.new("me") @catalog.add_edge("one", "two") end it "should be able to be dumped to yaml" do YAML.dump(@catalog).should be_instance_of(String) end end describe "when converting from yaml" do before do @catalog = Puppet::Resource::Catalog.new("me") @catalog.add_edge("one", "two") text = YAML.dump(@catalog) @newcatalog = YAML.load(text) end it "should get converted back to a catalog" do @newcatalog.should be_instance_of(Puppet::Resource::Catalog) end it "should have all vertices" do @newcatalog.vertex?("one").should be_true @newcatalog.vertex?("two").should be_true end it "should have all edges" do @newcatalog.edge?("one", "two").should be_true end end end -describe Puppet::Resource::Catalog, "when converting to pson" do - confine "Missing 'pson' library" => Puppet.features.pson? - +describe Puppet::Resource::Catalog, "when converting to pson", :if => Puppet.features.pson? do before do @catalog = Puppet::Resource::Catalog.new("myhost") end def pson_output_should @catalog.class.expects(:pson_create).with { |hash| yield hash }.returns(:something) end # LAK:NOTE For all of these tests, we convert back to the resource so we can # trap the actual data structure then. it "should set its document_type to 'Catalog'" do pson_output_should { |hash| hash['document_type'] == "Catalog" } PSON.parse @catalog.to_pson end it "should set its data as a hash" do pson_output_should { |hash| hash['data'].is_a?(Hash) } PSON.parse @catalog.to_pson end [:name, :version, :tags, :classes].each do |param| it "should set its #{param} to the #{param} of the resource" do @catalog.send(param.to_s + "=", "testing") unless @catalog.send(param) pson_output_should { |hash| hash['data'][param.to_s] == @catalog.send(param) } PSON.parse @catalog.to_pson end end it "should convert its resources to a PSON-encoded array and store it as the 'resources' data" do one = stub 'one', :to_pson_data_hash => "one_resource", :ref => "Foo[one]" two = stub 'two', :to_pson_data_hash => "two_resource", :ref => "Foo[two]" @catalog.add_resource(one) @catalog.add_resource(two) # TODO this should really guarantee sort order PSON.parse(@catalog.to_pson,:create_additions => false)['data']['resources'].sort.should == ["one_resource", "two_resource"].sort end it "should convert its edges to a PSON-encoded array and store it as the 'edges' data" do one = stub 'one', :to_pson_data_hash => "one_resource", :ref => 'Foo[one]' two = stub 'two', :to_pson_data_hash => "two_resource", :ref => 'Foo[two]' three = stub 'three', :to_pson_data_hash => "three_resource", :ref => 'Foo[three]' @catalog.add_edge(one, two) @catalog.add_edge(two, three) @catalog.edge(one, two ).expects(:to_pson_data_hash).returns "one_two_pson" @catalog.edge(two, three).expects(:to_pson_data_hash).returns "two_three_pson" PSON.parse(@catalog.to_pson,:create_additions => false)['data']['edges'].sort.should == %w{one_two_pson two_three_pson}.sort end end -describe Puppet::Resource::Catalog, "when converting from pson" do - confine "Missing 'pson' library" => Puppet.features.pson? - +describe Puppet::Resource::Catalog, "when converting from pson", :if => Puppet.features.pson? do def pson_result_should Puppet::Resource::Catalog.expects(:new).with { |hash| yield hash } end before do @data = { 'name' => "myhost" } @pson = { 'document_type' => 'Puppet::Resource::Catalog', 'data' => @data, 'metadata' => {} } @catalog = Puppet::Resource::Catalog.new("myhost") Puppet::Resource::Catalog.stubs(:new).returns @catalog end it "should be extended with the PSON utility module" do Puppet::Resource::Catalog.singleton_class.ancestors.should be_include(Puppet::Util::Pson) end it "should create it with the provided name" do Puppet::Resource::Catalog.expects(:new).with('myhost').returns @catalog PSON.parse @pson.to_pson end it "should set the provided version on the catalog if one is set" do @data['version'] = 50 PSON.parse @pson.to_pson @catalog.version.should == @data['version'] end it "should set any provided tags on the catalog" do @data['tags'] = %w{one two} PSON.parse @pson.to_pson @catalog.tags.should == @data['tags'] end it "should set any provided classes on the catalog" do @data['classes'] = %w{one two} PSON.parse @pson.to_pson @catalog.classes.should == @data['classes'] end it 'should convert the resources list into resources and add each of them' do @data['resources'] = [Puppet::Resource.new(:file, "/foo"), Puppet::Resource.new(:file, "/bar")] @catalog.expects(:add_resource).times(2).with { |res| res.type == "File" } PSON.parse @pson.to_pson end it 'should convert resources even if they do not include "type" information' do @data['resources'] = [Puppet::Resource.new(:file, "/foo")] @data['resources'][0].expects(:to_pson).returns '{"title":"/foo","tags":["file"],"type":"File"}' @catalog.expects(:add_resource).with { |res| res.type == "File" } PSON.parse @pson.to_pson end it 'should convert the edges list into edges and add each of them' do one = Puppet::Relationship.new("osource", "otarget", :event => "one", :callback => "refresh") two = Puppet::Relationship.new("tsource", "ttarget", :event => "two", :callback => "refresh") @data['edges'] = [one, two] @catalog.stubs(:resource).returns("eh") @catalog.expects(:add_edge).with { |edge| edge.event == "one" } @catalog.expects(:add_edge).with { |edge| edge.event == "two" } PSON.parse @pson.to_pson end it "should be able to convert relationships that do not include 'type' information" do one = Puppet::Relationship.new("osource", "otarget", :event => "one", :callback => "refresh") one.expects(:to_pson).returns "{\"event\":\"one\",\"callback\":\"refresh\",\"source\":\"osource\",\"target\":\"otarget\"}" @data['edges'] = [one] @catalog.stubs(:resource).returns("eh") @catalog.expects(:add_edge).with { |edge| edge.event == "one" } PSON.parse @pson.to_pson end it "should set the source and target for each edge to the actual resource" do edge = Puppet::Relationship.new("source", "target") @data['edges'] = [edge] @catalog.expects(:resource).with("source").returns("source_resource") @catalog.expects(:resource).with("target").returns("target_resource") @catalog.expects(:add_edge).with { |edge| edge.source == "source_resource" and edge.target == "target_resource" } PSON.parse @pson.to_pson end it "should fail if the source resource cannot be found" do edge = Puppet::Relationship.new("source", "target") @data['edges'] = [edge] @catalog.expects(:resource).with("source").returns(nil) @catalog.stubs(:resource).with("target").returns("target_resource") lambda { PSON.parse @pson.to_pson }.should raise_error(ArgumentError) end it "should fail if the target resource cannot be found" do edge = Puppet::Relationship.new("source", "target") @data['edges'] = [edge] @catalog.stubs(:resource).with("source").returns("source_resource") @catalog.expects(:resource).with("target").returns(nil) lambda { PSON.parse @pson.to_pson }.should raise_error(ArgumentError) end describe "#title_key_for_ref" do it "should parse a resource ref string into a pair" do @catalog.title_key_for_ref("Title[name]").should == ["Title", "name"] end it "should parse a resource ref string into a pair, even if there's a newline inside the name" do @catalog.title_key_for_ref("Title[na\nme]").should == ["Title", "na\nme"] end end end diff --git a/spec/unit/resource/status_spec.rb b/spec/unit/resource/status_spec.rb index 425015a13..4e76fa463 100755 --- a/spec/unit/resource/status_spec.rb +++ b/spec/unit/resource/status_spec.rb @@ -1,103 +1,153 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../spec_helper' require 'puppet/resource/status' describe Puppet::Resource::Status do before do @resource = Puppet::Type.type(:file).new :path => "/my/file" @status = Puppet::Resource::Status.new(@resource) end - [:node, :version, :file, :line, :current_values, :skipped_reason, :status, :evaluation_time, :change_count].each do |attr| + it "should compute type and title correctly" do + @status.resource_type.should == "File" + @status.title.should == "/my/file" + end + + [:node, :file, :line, :current_values, :status, :evaluation_time].each do |attr| it "should support #{attr}" do @status.send(attr.to_s + "=", "foo") @status.send(attr).should == "foo" end end [:skipped, :failed, :restarted, :failed_to_restart, :changed, :out_of_sync, :scheduled].each do |attr| it "should support #{attr}" do @status.send(attr.to_s + "=", "foo") @status.send(attr).should == "foo" end it "should have a boolean method for determining whehter it was #{attr}" do @status.send(attr.to_s + "=", "foo") @status.should send("be_#{attr}") end end it "should accept a resource at initialization" do Puppet::Resource::Status.new(@resource).resource.should_not be_nil end it "should set its source description to the resource's path" do @resource.expects(:path).returns "/my/path" Puppet::Resource::Status.new(@resource).source_description.should == "/my/path" end - [:file, :line, :version].each do |attr| + [:file, :line].each do |attr| it "should copy the resource's #{attr}" do @resource.expects(attr).returns "foo" Puppet::Resource::Status.new(@resource).send(attr).should == "foo" end end it "should copy the resource's tags" do @resource.expects(:tags).returns %w{foo bar} Puppet::Resource::Status.new(@resource).tags.should == %w{foo bar} end it "should always convert the resource to a string" do @resource.expects(:to_s).returns "foo" Puppet::Resource::Status.new(@resource).resource.should == "foo" end it "should support tags" do Puppet::Resource::Status.ancestors.should include(Puppet::Util::Tagging) end it "should create a timestamp at its creation time" do @status.time.should be_instance_of(Time) end describe "when sending logs" do before do Puppet::Util::Log.stubs(:new) end it "should set the tags to the event tags" do Puppet::Util::Log.expects(:new).with { |args| args[:tags] == %w{one two} } @status.stubs(:tags).returns %w{one two} @status.send_log :notice, "my message" end - [:file, :line, :version].each do |attr| + [:file, :line].each do |attr| it "should pass the #{attr}" do Puppet::Util::Log.expects(:new).with { |args| args[attr] == "my val" } @status.send(attr.to_s + "=", "my val") @status.send_log :notice, "my message" end end it "should use the source description as the source" do Puppet::Util::Log.expects(:new).with { |args| args[:source] == "my source" } @status.stubs(:source_description).returns "my source" @status.send_log :notice, "my message" end end it "should support adding events" do event = Puppet::Transaction::Event.new(:name => :foobar) @status.add_event(event) @status.events.should == [event] end it "should use '<<' to add events" do event = Puppet::Transaction::Event.new(:name => :foobar) (@status << event).should equal(@status) @status.events.should == [event] end + + it "should count the number of successful events and set changed" do + 3.times{ @status << Puppet::Transaction::Event.new(:status => 'success') } + @status.change_count.should == 3 + + @status.changed.should == true + @status.out_of_sync.should == true + end + + it "should not start with any changes" do + @status.change_count.should == 0 + + @status.changed.should == false + @status.out_of_sync.should == false + end + + it "should not treat failure, audit, or noop events as changed" do + ['failure', 'audit', 'noop'].each do |s| @status << Puppet::Transaction::Event.new(:status => s) end + @status.change_count.should == 0 + @status.changed.should == false + end + + it "should not treat audit events as out of sync" do + @status << Puppet::Transaction::Event.new(:status => 'audit') + @status.out_of_sync_count.should == 0 + @status.out_of_sync.should == false + end + + ['failure', 'noop', 'success'].each do |event_status| + it "should treat #{event_status} events as out of sync" do + 3.times do @status << Puppet::Transaction::Event.new(:status => event_status) end + @status.out_of_sync_count.should == 3 + @status.out_of_sync.should == true + end + end + + describe "When converting to YAML" do + it "should include only documented attributes" do + @status.file = "/foo.rb" + @status.line = 27 + @status.evaluation_time = 2.7 + @status.tags = %w{one two} + @status.to_yaml_properties.should == Puppet::Resource::Status::YAML_ATTRIBUTES.sort + end + end end diff --git a/spec/unit/resource_spec.rb b/spec/unit/resource_spec.rb index e65e8a13a..877b6b6b0 100755 --- a/spec/unit/resource_spec.rb +++ b/spec/unit/resource_spec.rb @@ -1,782 +1,778 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../spec_helper' require 'puppet/resource' describe Puppet::Resource do before do @basepath = Puppet.features.posix? ? "/somepath" : "C:/somepath" end [:catalog, :file, :line].each do |attr| it "should have an #{attr} attribute" do resource = Puppet::Resource.new("file", "/my/file") resource.should respond_to(attr) resource.should respond_to(attr.to_s + "=") end end it "should have a :title attribute" do Puppet::Resource.new(:user, "foo").title.should == "foo" end it "should require the type and title" do lambda { Puppet::Resource.new }.should raise_error(ArgumentError) end it "should canonize types to capitalized strings" do Puppet::Resource.new(:user, "foo").type.should == "User" end it "should canonize qualified types so all strings are capitalized" do Puppet::Resource.new("foo::bar", "foo").type.should == "Foo::Bar" end it "should tag itself with its type" do Puppet::Resource.new("file", "/f").should be_tagged("file") end it "should tag itself with its title if the title is a valid tag" do Puppet::Resource.new("user", "bar").should be_tagged("bar") end it "should not tag itself with its title if the title is a not valid tag" do Puppet::Resource.new("file", "/bar").should_not be_tagged("/bar") end it "should allow setting of attributes" do Puppet::Resource.new("file", "/bar", :file => "/foo").file.should == "/foo" Puppet::Resource.new("file", "/bar", :exported => true).should be_exported end it "should set its type to 'Class' and its title to the passed title if the passed type is :component and the title has no square brackets in it" do ref = Puppet::Resource.new(:component, "foo") ref.type.should == "Class" ref.title.should == "Foo" end it "should interpret the title as a reference and assign appropriately if the type is :component and the title contains square brackets" do ref = Puppet::Resource.new(:component, "foo::bar[yay]") ref.type.should == "Foo::Bar" ref.title.should == "yay" end it "should set the type to 'Class' if it is nil and the title contains no square brackets" do ref = Puppet::Resource.new(nil, "yay") ref.type.should == "Class" ref.title.should == "Yay" end it "should interpret the title as a reference and assign appropriately if the type is nil and the title contains square brackets" do ref = Puppet::Resource.new(nil, "foo::bar[yay]") ref.type.should == "Foo::Bar" ref.title.should == "yay" end it "should interpret the title as a reference and assign appropriately if the type is nil and the title contains nested square brackets" do ref = Puppet::Resource.new(nil, "foo::bar[baz[yay]]") ref.type.should == "Foo::Bar" ref.title.should =="baz[yay]" end it "should interpret the type as a reference and assign appropriately if the title is nil and the type contains square brackets" do ref = Puppet::Resource.new("foo::bar[baz]") ref.type.should == "Foo::Bar" ref.title.should =="baz" end it "should be able to extract its information from a Puppet::Type instance" do ral = Puppet::Type.type(:file).new :path => @basepath+"/foo" ref = Puppet::Resource.new(ral) ref.type.should == "File" ref.title.should == @basepath+"/foo" end it "should fail if the title is nil and the type is not a valid resource reference string" do lambda { Puppet::Resource.new("foo") }.should raise_error(ArgumentError) end it "should fail if the title is a hash and the type is not a valid resource reference string" do lambda { Puppet::Resource.new({:type => "foo", :title => "bar"}) }.should raise_error(ArgumentError, 'Puppet::Resource.new does not take a hash as the first argument. Did you mean ("foo", "bar") ?' ) end it "should be able to produce a backward-compatible reference array" do Puppet::Resource.new("foobar", "/f").to_trans_ref.should == %w{Foobar /f} end it "should be taggable" do Puppet::Resource.ancestors.should be_include(Puppet::Util::Tagging) end it "should have an 'exported' attribute" do resource = Puppet::Resource.new("file", "/f") resource.exported = true resource.exported.should == true resource.should be_exported end it "should support an environment attribute" do Puppet::Resource.new("file", "/my/file", :environment => :foo).environment.name.should == :foo end describe "and munging its type and title" do describe "when modeling a builtin resource" do it "should be able to find the resource type" do Puppet::Resource.new("file", "/my/file").resource_type.should equal(Puppet::Type.type(:file)) end it "should set its type to the capitalized type name" do Puppet::Resource.new("file", "/my/file").type.should == "File" end end describe "when modeling a defined resource" do describe "that exists" do before do @type = Puppet::Resource::Type.new(:definition, "foo::bar") Puppet::Node::Environment.new.known_resource_types.add @type end it "should set its type to the capitalized type name" do Puppet::Resource.new("foo::bar", "/my/file").type.should == "Foo::Bar" end it "should be able to find the resource type" do Puppet::Resource.new("foo::bar", "/my/file").resource_type.should equal(@type) end it "should set its title to the provided title" do Puppet::Resource.new("foo::bar", "/my/file").title.should == "/my/file" end end describe "that does not exist" do it "should set its resource type to the capitalized resource type name" do Puppet::Resource.new("foo::bar", "/my/file").type.should == "Foo::Bar" end end end describe "when modeling a node" do # Life's easier with nodes, because they can't be qualified. it "should set its type to 'Node' and its title to the provided title" do node = Puppet::Resource.new("node", "foo") node.type.should == "Node" node.title.should == "foo" end end describe "when modeling a class" do it "should set its type to 'Class'" do Puppet::Resource.new("class", "foo").type.should == "Class" end describe "that exists" do before do @type = Puppet::Resource::Type.new(:hostclass, "foo::bar") Puppet::Node::Environment.new.known_resource_types.add @type end it "should set its title to the capitalized, fully qualified resource type" do Puppet::Resource.new("class", "foo::bar").title.should == "Foo::Bar" end it "should be able to find the resource type" do Puppet::Resource.new("class", "foo::bar").resource_type.should equal(@type) end end describe "that does not exist" do it "should set its type to 'Class' and its title to the capitalized provided name" do klass = Puppet::Resource.new("class", "foo::bar") klass.type.should == "Class" klass.title.should == "Foo::Bar" end end describe "and its name is set to the empty string" do it "should set its title to :main" do Puppet::Resource.new("class", "").title.should == :main end describe "and a class exists whose name is the empty string" do # this was a bit tough to track down it "should set its title to :main" do @type = Puppet::Resource::Type.new(:hostclass, "") Puppet::Node::Environment.new.known_resource_types.add @type Puppet::Resource.new("class", "").title.should == :main end end end describe "and its name is set to :main" do it "should set its title to :main" do Puppet::Resource.new("class", :main).title.should == :main end describe "and a class exists whose name is the empty string" do # this was a bit tough to track down it "should set its title to :main" do @type = Puppet::Resource::Type.new(:hostclass, "") Puppet::Node::Environment.new.known_resource_types.add @type Puppet::Resource.new("class", :main).title.should == :main end end end end end it "should return nil when looking up resource types that don't exist" do Puppet::Resource.new("foobar", "bar").resource_type.should be_nil end it "should not fail when an invalid parameter is used and strict mode is disabled" do type = Puppet::Resource::Type.new(:definition, "foobar") Puppet::Node::Environment.new.known_resource_types.add type resource = Puppet::Resource.new("foobar", "/my/file") resource[:yay] = true end it "should be considered equivalent to another resource if their type and title match and no parameters are set" do Puppet::Resource.new("file", "/f").should == Puppet::Resource.new("file", "/f") end it "should be considered equivalent to another resource if their type, title, and parameters are equal" do Puppet::Resource.new("file", "/f", :parameters => {:foo => "bar"}).should == Puppet::Resource.new("file", "/f", :parameters => {:foo => "bar"}) end it "should not be considered equivalent to another resource if their type and title match but parameters are different" do Puppet::Resource.new("file", "/f", :parameters => {:fee => "baz"}).should_not == Puppet::Resource.new("file", "/f", :parameters => {:foo => "bar"}) end it "should not be considered equivalent to a non-resource" do Puppet::Resource.new("file", "/f").should_not == "foo" end it "should not be considered equivalent to another resource if their types do not match" do Puppet::Resource.new("file", "/f").should_not == Puppet::Resource.new("exec", "/f") end it "should not be considered equivalent to another resource if their titles do not match" do Puppet::Resource.new("file", "/foo").should_not == Puppet::Resource.new("file", "/f") end describe "when referring to a resource with name canonicalization" do it "should canonicalize its own name" do res = Puppet::Resource.new("file", "/path/") res.uniqueness_key.should == ["/path"] res.ref.should == "File[/path/]" end end describe "when running in strict mode" do it "should be strict" do Puppet::Resource.new("file", "/path", :strict => true).should be_strict end it "should fail if invalid parameters are used" do lambda { Puppet::Resource.new("file", "/path", :strict => true, :parameters => {:nosuchparam => "bar"}) }.should raise_error end it "should fail if the resource type cannot be resolved" do lambda { Puppet::Resource.new("nosuchtype", "/path", :strict => true) }.should raise_error end end describe "when managing parameters" do before do @resource = Puppet::Resource.new("file", "/my/file") end it "should correctly detect when provided parameters are not valid for builtin types" do Puppet::Resource.new("file", "/my/file").should_not be_valid_parameter("foobar") end it "should correctly detect when provided parameters are valid for builtin types" do Puppet::Resource.new("file", "/my/file").should be_valid_parameter("mode") end it "should correctly detect when provided parameters are not valid for defined resource types" do type = Puppet::Resource::Type.new(:definition, "foobar") Puppet::Node::Environment.new.known_resource_types.add type Puppet::Resource.new("foobar", "/my/file").should_not be_valid_parameter("myparam") end it "should correctly detect when provided parameters are valid for defined resource types" do type = Puppet::Resource::Type.new(:definition, "foobar", :arguments => {"myparam" => nil}) Puppet::Node::Environment.new.known_resource_types.add type Puppet::Resource.new("foobar", "/my/file").should be_valid_parameter("myparam") end it "should allow setting and retrieving of parameters" do @resource[:foo] = "bar" @resource[:foo].should == "bar" end it "should allow setting of parameters at initialization" do Puppet::Resource.new("file", "/my/file", :parameters => {:foo => "bar"})[:foo].should == "bar" end it "should canonicalize retrieved parameter names to treat symbols and strings equivalently" do @resource[:foo] = "bar" @resource["foo"].should == "bar" end it "should canonicalize set parameter names to treat symbols and strings equivalently" do @resource["foo"] = "bar" @resource[:foo].should == "bar" end it "should set the namevar when asked to set the name" do resource = Puppet::Resource.new("user", "bob") Puppet::Type.type(:user).stubs(:key_attributes).returns [:myvar] resource[:name] = "bob" resource[:myvar].should == "bob" end it "should return the namevar when asked to return the name" do resource = Puppet::Resource.new("user", "bob") Puppet::Type.type(:user).stubs(:key_attributes).returns [:myvar] resource[:myvar] = "test" resource[:name].should == "test" end it "should be able to set the name for non-builtin types" do resource = Puppet::Resource.new(:foo, "bar") resource[:name] = "eh" lambda { resource[:name] = "eh" }.should_not raise_error end it "should be able to return the name for non-builtin types" do resource = Puppet::Resource.new(:foo, "bar") resource[:name] = "eh" resource[:name].should == "eh" end it "should be able to iterate over parameters" do @resource[:foo] = "bar" @resource[:fee] = "bare" params = {} @resource.each do |key, value| params[key] = value end params.should == {:foo => "bar", :fee => "bare"} end it "should include Enumerable" do @resource.class.ancestors.should be_include(Enumerable) end it "should have a method for testing whether a parameter is included" do @resource[:foo] = "bar" @resource.should be_has_key(:foo) @resource.should_not be_has_key(:eh) end it "should have a method for providing the list of parameters" do @resource[:foo] = "bar" @resource[:bar] = "foo" keys = @resource.keys keys.should be_include(:foo) keys.should be_include(:bar) end it "should have a method for providing the number of parameters" do @resource[:foo] = "bar" @resource.length.should == 1 end it "should have a method for deleting parameters" do @resource[:foo] = "bar" @resource.delete(:foo) @resource[:foo].should be_nil end it "should have a method for testing whether the parameter list is empty" do @resource.should be_empty @resource[:foo] = "bar" @resource.should_not be_empty end it "should be able to produce a hash of all existing parameters" do @resource[:foo] = "bar" @resource[:fee] = "yay" hash = @resource.to_hash hash[:foo].should == "bar" hash[:fee].should == "yay" end it "should not provide direct access to the internal parameters hash when producing a hash" do hash = @resource.to_hash hash[:foo] = "bar" @resource[:foo].should be_nil end it "should use the title as the namevar to the hash if no namevar is present" do resource = Puppet::Resource.new("user", "bob") Puppet::Type.type(:user).stubs(:key_attributes).returns [:myvar] resource.to_hash[:myvar].should == "bob" end it "should set :name to the title if :name is not present for non-builtin types" do krt = Puppet::Resource::TypeCollection.new("myenv") krt.add Puppet::Resource::Type.new(:definition, :foo) resource = Puppet::Resource.new :foo, "bar" resource.stubs(:known_resource_types).returns krt resource.to_hash[:name].should == "bar" end end describe "when serializing" do before do @resource = Puppet::Resource.new("file", "/my/file") @resource["one"] = "test" @resource["two"] = "other" end it "should be able to be dumped to yaml" do proc { YAML.dump(@resource) }.should_not raise_error end it "should produce an equivalent yaml object" do text = YAML.dump(@resource) newresource = YAML.load(text) newresource.title.should == @resource.title newresource.type.should == @resource.type %w{one two}.each do |param| newresource[param].should == @resource[param] end end end describe "when converting to a RAL resource" do it "should use the resource type's :new method to create the resource if the resource is of a builtin type" do resource = Puppet::Resource.new("file", @basepath+"/my/file") result = resource.to_ral result.should be_instance_of(Puppet::Type.type(:file)) result[:path].should == @basepath+"/my/file" end it "should convert to a component instance if the resource type is not of a builtin type" do resource = Puppet::Resource.new("foobar", "somename") result = resource.to_ral result.should be_instance_of(Puppet::Type.type(:component)) result.title.should == "Foobar[somename]" end end it "should be able to convert itself to Puppet code" do Puppet::Resource.new("one::two", "/my/file").should respond_to(:to_manifest) end describe "when converting to puppet code" do before do @resource = Puppet::Resource.new("one::two", "/my/file", :parameters => {:noop => true, :foo => %w{one two}}) end it "should print the type and title" do @resource.to_manifest.should be_include("one::two { '/my/file':\n") end it "should print each parameter, with the value single-quoted" do @resource.to_manifest.should be_include(" noop => 'true'") end it "should print array values appropriately" do @resource.to_manifest.should be_include(" foo => ['one','two']") end end it "should be able to convert itself to a TransObject instance" do Puppet::Resource.new("one::two", "/my/file").should respond_to(:to_trans) end describe "when converting to a TransObject" do describe "and the resource is not an instance of a builtin type" do before do @resource = Puppet::Resource.new("foo", "bar") end it "should return a simple TransBucket if it is not an instance of a builtin type" do bucket = @resource.to_trans bucket.should be_instance_of(Puppet::TransBucket) bucket.type.should == @resource.type bucket.name.should == @resource.title end it "should return a simple TransBucket if it is a stage" do @resource = Puppet::Resource.new("stage", "bar") bucket = @resource.to_trans bucket.should be_instance_of(Puppet::TransBucket) bucket.type.should == @resource.type bucket.name.should == @resource.title end it "should copy over the resource's file" do @resource.file = "/foo/bar" @resource.to_trans.file.should == "/foo/bar" end it "should copy over the resource's line" do @resource.line = 50 @resource.to_trans.line.should == 50 end end describe "and the resource is an instance of a builtin type" do before do @resource = Puppet::Resource.new("file", "bar") end it "should return a TransObject if it is an instance of a builtin resource type" do trans = @resource.to_trans trans.should be_instance_of(Puppet::TransObject) trans.type.should == "file" trans.name.should == @resource.title end it "should copy over the resource's file" do @resource.file = "/foo/bar" @resource.to_trans.file.should == "/foo/bar" end it "should copy over the resource's line" do @resource.line = 50 @resource.to_trans.line.should == 50 end # Only TransObjects support tags, annoyingly it "should copy over the resource's tags" do @resource.tag "foo" @resource.to_trans.tags.should == @resource.tags end it "should copy the resource's parameters into the transobject and convert the parameter name to a string" do @resource[:foo] = "bar" @resource.to_trans["foo"].should == "bar" end it "should be able to copy arrays of values" do @resource[:foo] = %w{yay fee} @resource.to_trans["foo"].should == %w{yay fee} end it "should reduce single-value arrays to just a value" do @resource[:foo] = %w{yay} @resource.to_trans["foo"].should == "yay" end it "should convert resource references into the backward-compatible form" do @resource[:foo] = Puppet::Resource.new(:file, "/f") @resource.to_trans["foo"].should == %w{File /f} end it "should convert resource references into the backward-compatible form even when within arrays" do @resource[:foo] = ["a", Puppet::Resource.new(:file, "/f")] @resource.to_trans["foo"].should == ["a", %w{File /f}] end end end - describe "when converting to pson" do - confine "Missing 'pson' library" => Puppet.features.pson? - + describe "when converting to pson", :if => Puppet.features.pson? do def pson_output_should @resource.class.expects(:pson_create).with { |hash| yield hash } end it "should include the pson util module" do Puppet::Resource.singleton_class.ancestors.should be_include(Puppet::Util::Pson) end # LAK:NOTE For all of these tests, we convert back to the resource so we can # trap the actual data structure then. it "should set its type to the provided type" do Puppet::Resource.from_pson(PSON.parse(Puppet::Resource.new("File", "/foo").to_pson)).type.should == "File" end it "should set its title to the provided title" do Puppet::Resource.from_pson(PSON.parse(Puppet::Resource.new("File", "/foo").to_pson)).title.should == "/foo" end it "should include all tags from the resource" do resource = Puppet::Resource.new("File", "/foo") resource.tag("yay") Puppet::Resource.from_pson(PSON.parse(resource.to_pson)).tags.should == resource.tags end it "should include the file if one is set" do resource = Puppet::Resource.new("File", "/foo") resource.file = "/my/file" Puppet::Resource.from_pson(PSON.parse(resource.to_pson)).file.should == "/my/file" end it "should include the line if one is set" do resource = Puppet::Resource.new("File", "/foo") resource.line = 50 Puppet::Resource.from_pson(PSON.parse(resource.to_pson)).line.should == 50 end it "should include the 'exported' value if one is set" do resource = Puppet::Resource.new("File", "/foo") resource.exported = true Puppet::Resource.from_pson(PSON.parse(resource.to_pson)).exported.should be_true end it "should set 'exported' to false if no value is set" do resource = Puppet::Resource.new("File", "/foo") Puppet::Resource.from_pson(PSON.parse(resource.to_pson)).exported.should be_false end it "should set all of its parameters as the 'parameters' entry" do resource = Puppet::Resource.new("File", "/foo") resource[:foo] = %w{bar eh} resource[:fee] = %w{baz} result = Puppet::Resource.from_pson(PSON.parse(resource.to_pson)) result["foo"].should == %w{bar eh} result["fee"].should == %w{baz} end it "should serialize relationships as reference strings" do resource = Puppet::Resource.new("File", "/foo") resource[:requires] = Puppet::Resource.new("File", "/bar") result = Puppet::Resource.from_pson(PSON.parse(resource.to_pson)) result[:requires].should == "File[/bar]" end it "should serialize multiple relationships as arrays of reference strings" do resource = Puppet::Resource.new("File", "/foo") resource[:requires] = [Puppet::Resource.new("File", "/bar"), Puppet::Resource.new("File", "/baz")] result = Puppet::Resource.from_pson(PSON.parse(resource.to_pson)) result[:requires].should == [ "File[/bar]", "File[/baz]" ] end end - describe "when converting from pson" do - confine "Missing 'pson' library" => Puppet.features.pson? - + describe "when converting from pson", :if => Puppet.features.pson? do def pson_result_should Puppet::Resource.expects(:new).with { |hash| yield hash } end before do @data = { 'type' => "file", 'title' => @basepath+"/yay", } end it "should set its type to the provided type" do Puppet::Resource.from_pson(@data).type.should == "File" end it "should set its title to the provided title" do Puppet::Resource.from_pson(@data).title.should == @basepath+"/yay" end it "should tag the resource with any provided tags" do @data['tags'] = %w{foo bar} resource = Puppet::Resource.from_pson(@data) resource.tags.should be_include("foo") resource.tags.should be_include("bar") end it "should set its file to the provided file" do @data['file'] = "/foo/bar" Puppet::Resource.from_pson(@data).file.should == "/foo/bar" end it "should set its line to the provided line" do @data['line'] = 50 Puppet::Resource.from_pson(@data).line.should == 50 end it "should 'exported' to true if set in the pson data" do @data['exported'] = true Puppet::Resource.from_pson(@data).exported.should be_true end it "should 'exported' to false if not set in the pson data" do Puppet::Resource.from_pson(@data).exported.should be_false end it "should fail if no title is provided" do @data.delete('title') lambda { Puppet::Resource.from_pson(@data) }.should raise_error(ArgumentError) end it "should fail if no type is provided" do @data.delete('type') lambda { Puppet::Resource.from_pson(@data) }.should raise_error(ArgumentError) end it "should set each of the provided parameters" do @data['parameters'] = {'foo' => %w{one two}, 'fee' => %w{three four}} resource = Puppet::Resource.from_pson(@data) resource['foo'].should == %w{one two} resource['fee'].should == %w{three four} end it "should convert single-value array parameters to normal values" do @data['parameters'] = {'foo' => %w{one}} resource = Puppet::Resource.from_pson(@data) resource['foo'].should == %w{one} end end describe "it should implement to_resource" do resource = Puppet::Resource.new("file", "/my/file") resource.to_resource.should == resource end describe "because it is an indirector model" do it "should include Puppet::Indirector" do Puppet::Resource.should be_is_a(Puppet::Indirector) end it "should have a default terminus" do Puppet::Resource.indirection.terminus_class.should == :ral end it "should have a name" do Puppet::Resource.new("file", "/my/file").name.should == "File//my/file" end end describe "when resolving resources with a catalog" do it "should resolve all resources using the catalog" do catalog = mock 'catalog' resource = Puppet::Resource.new("foo::bar", "yay") resource.catalog = catalog catalog.expects(:resource).with("Foo::Bar[yay]").returns(:myresource) resource.resolve.should == :myresource end end describe "when generating the uniqueness key" do it "should include all of the key_attributes in alphabetical order by attribute name" do Puppet::Type.type(:file).stubs(:key_attributes).returns [:myvar, :owner, :path] Puppet::Type.type(:file).stubs(:title_patterns).returns( [ [ /(.*)/, [ [:path, lambda{|x| x} ] ] ] ] ) res = Puppet::Resource.new("file", "/my/file", :parameters => {:owner => 'root', :content => 'hello'}) res.uniqueness_key.should == [ nil, 'root', '/my/file'] end end end diff --git a/spec/unit/ssl/certificate_authority/interface_spec.rb b/spec/unit/ssl/certificate_authority/interface_spec.rb index d8c351ae2..5cf4073df 100755 --- a/spec/unit/ssl/certificate_authority/interface_spec.rb +++ b/spec/unit/ssl/certificate_authority/interface_spec.rb @@ -1,333 +1,333 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../../spec_helper' require 'puppet/ssl/certificate_authority' -describe "a normal interface method", :shared => true do +shared_examples_for "a normal interface method" do it "should call the method on the CA for each host specified if an array was provided" do @ca.expects(@method).with("host1") @ca.expects(@method).with("host2") @applier = Puppet::SSL::CertificateAuthority::Interface.new(@method, :to => %w{host1 host2}) @applier.apply(@ca) end it "should call the method on the CA for all existing certificates if :all was provided" do @ca.expects(:list).returns %w{host1 host2} @ca.expects(@method).with("host1") @ca.expects(@method).with("host2") @applier = Puppet::SSL::CertificateAuthority::Interface.new(@method, :to => :all) @applier.apply(@ca) end end describe Puppet::SSL::CertificateAuthority::Interface do before do @class = Puppet::SSL::CertificateAuthority::Interface end describe "when initializing" do it "should set its method using its settor" do @class.any_instance.expects(:method=).with(:generate) @class.new(:generate, :to => :all) end it "should set its subjects using the settor" do @class.any_instance.expects(:subjects=).with(:all) @class.new(:generate, :to => :all) end it "should set the digest if given" do interface = @class.new(:generate, :to => :all, :digest => :digest) interface.digest.should == :digest end it "should set the digest to md5 if none given" do interface = @class.new(:generate, :to => :all) interface.digest.should == :MD5 end end describe "when setting the method" do it "should set the method" do @class.new(:generate, :to => :all).method.should == :generate end it "should fail if the method isn't a member of the INTERFACE_METHODS array" do Puppet::SSL::CertificateAuthority::Interface::INTERFACE_METHODS.expects(:include?).with(:thing).returns false lambda { @class.new(:thing, :to => :all) }.should raise_error(ArgumentError) end end describe "when setting the subjects" do it "should set the subjects" do @class.new(:generate, :to => :all).subjects.should == :all end it "should fail if the subjects setting isn't :all or an array" do lambda { @class.new(:generate, "other") }.should raise_error(ArgumentError) end end it "should have a method for triggering the application" do @class.new(:generate, :to => :all).should respond_to(:apply) end describe "when applying" do before do # We use a real object here, because :verify can't be stubbed, apparently. @ca = Object.new end it "should raise InterfaceErrors" do @applier = @class.new(:revoke, :to => :all) @ca.expects(:list).raises Puppet::SSL::CertificateAuthority::Interface::InterfaceError lambda { @applier.apply(@ca) }.should raise_error(Puppet::SSL::CertificateAuthority::Interface::InterfaceError) end it "should log non-Interface failures rather than failing" do @applier = @class.new(:revoke, :to => :all) @ca.expects(:list).raises ArgumentError Puppet.expects(:err) lambda { @applier.apply(@ca) }.should_not raise_error end describe "with an empty array specified and the method is not list" do it "should fail" do @applier = @class.new(:sign, :to => []) lambda { @applier.apply(@ca) }.should raise_error(ArgumentError) end end describe ":generate" do it "should fail if :all was specified" do @applier = @class.new(:generate, :to => :all) lambda { @applier.apply(@ca) }.should raise_error(ArgumentError) end it "should call :generate on the CA for each host specified" do @applier = @class.new(:generate, :to => %w{host1 host2}) @ca.expects(:generate).with("host1") @ca.expects(:generate).with("host2") @applier.apply(@ca) end end describe ":verify" do before { @method = :verify } #it_should_behave_like "a normal interface method" it "should call the method on the CA for each host specified if an array was provided" do # LAK:NOTE Mocha apparently doesn't allow you to mock :verify, but I'm confident this works in real life. end it "should call the method on the CA for all existing certificates if :all was provided" do # LAK:NOTE Mocha apparently doesn't allow you to mock :verify, but I'm confident this works in real life. end end describe ":destroy" do before { @method = :destroy } it_should_behave_like "a normal interface method" end describe ":revoke" do before { @method = :revoke } it_should_behave_like "a normal interface method" end describe ":sign" do describe "and an array of names was provided" do before do @applier = @class.new(:sign, :to => %w{host1 host2}) end it "should sign the specified waiting certificate requests" do @ca.expects(:sign).with("host1") @ca.expects(:sign).with("host2") @applier.apply(@ca) end end describe "and :all was provided" do it "should sign all waiting certificate requests" do @ca.stubs(:waiting?).returns(%w{cert1 cert2}) @ca.expects(:sign).with("cert1") @ca.expects(:sign).with("cert2") @applier = @class.new(:sign, :to => :all) @applier.apply(@ca) end it "should fail if there are no waiting certificate requests" do @ca.stubs(:waiting?).returns([]) @applier = @class.new(:sign, :to => :all) lambda { @applier.apply(@ca) }.should raise_error(Puppet::SSL::CertificateAuthority::Interface::InterfaceError) end end end describe ":list" do describe "and an empty array was provided" do it "should print a string containing all certificate requests" do @ca.expects(:waiting?).returns %w{host1 host2} @ca.stubs(:verify) @applier = @class.new(:list, :to => []) @applier.expects(:puts).with "host1\nhost2" @applier.apply(@ca) end end describe "and :all was provided" do it "should print a string containing all certificate requests and certificates" do @ca.expects(:waiting?).returns %w{host1 host2} @ca.expects(:list).returns %w{host3 host4} @ca.stubs(:verify) @ca.stubs(:fingerprint).returns "fingerprint" @ca.expects(:verify).with("host3").raises(Puppet::SSL::CertificateAuthority::CertificateVerificationError.new(23), "certificate revoked") @applier = @class.new(:list, :to => :all) @applier.expects(:puts).with "host1 (fingerprint)" @applier.expects(:puts).with "host2 (fingerprint)" @applier.expects(:puts).with "- host3 (fingerprint) (certificate revoked)" @applier.expects(:puts).with "+ host4 (fingerprint)" @applier.apply(@ca) end end describe "and :signed was provided" do it "should print a string containing all signed certificate requests and certificates" do @ca.expects(:list).returns %w{host1 host2} @applier = @class.new(:list, :to => :signed) @applier.apply(@ca) end end describe "and an array of names was provided" do it "should print a string of all named hosts that have a waiting request" do @ca.expects(:waiting?).returns %w{host1 host2} @ca.expects(:list).returns %w{host3 host4} @ca.stubs(:fingerprint).returns "fingerprint" @ca.stubs(:verify) @applier = @class.new(:list, :to => %w{host1 host2 host3 host4}) @applier.expects(:puts).with "host1 (fingerprint)" @applier.expects(:puts).with "host2 (fingerprint)" @applier.expects(:puts).with "+ host3 (fingerprint)" @applier.expects(:puts).with "+ host4 (fingerprint)" @applier.apply(@ca) end end end describe ":print" do describe "and :all was provided" do it "should print all certificates" do @ca.expects(:list).returns %w{host1 host2} @applier = @class.new(:print, :to => :all) @ca.expects(:print).with("host1").returns "h1" @applier.expects(:puts).with "h1" @ca.expects(:print).with("host2").returns "h2" @applier.expects(:puts).with "h2" @applier.apply(@ca) end end describe "and an array of names was provided" do it "should print each named certificate if found" do @applier = @class.new(:print, :to => %w{host1 host2}) @ca.expects(:print).with("host1").returns "h1" @applier.expects(:puts).with "h1" @ca.expects(:print).with("host2").returns "h2" @applier.expects(:puts).with "h2" @applier.apply(@ca) end it "should log any named but not found certificates" do @applier = @class.new(:print, :to => %w{host1 host2}) @ca.expects(:print).with("host1").returns "h1" @applier.expects(:puts).with "h1" @ca.expects(:print).with("host2").returns nil Puppet.expects(:err).with { |msg| msg.include?("host2") } @applier.apply(@ca) end end end describe ":fingerprint" do it "should fingerprint with the set digest algorithm" do @applier = @class.new(:fingerprint, :to => %w{host1}, :digest => :digest) @ca.expects(:fingerprint).with("host1", :digest).returns "fingerprint1" @applier.expects(:puts).with "host1 fingerprint1" @applier.apply(@ca) end describe "and :all was provided" do it "should fingerprint all certificates (including waiting ones)" do @ca.expects(:list).returns %w{host1} @ca.expects(:waiting?).returns %w{host2} @applier = @class.new(:fingerprint, :to => :all) @ca.expects(:fingerprint).with("host1", :MD5).returns "fingerprint1" @applier.expects(:puts).with "host1 fingerprint1" @ca.expects(:fingerprint).with("host2", :MD5).returns "fingerprint2" @applier.expects(:puts).with "host2 fingerprint2" @applier.apply(@ca) end end describe "and an array of names was provided" do it "should print each named certificate if found" do @applier = @class.new(:fingerprint, :to => %w{host1 host2}) @ca.expects(:fingerprint).with("host1", :MD5).returns "fingerprint1" @applier.expects(:puts).with "host1 fingerprint1" @ca.expects(:fingerprint).with("host2", :MD5).returns "fingerprint2" @applier.expects(:puts).with "host2 fingerprint2" @applier.apply(@ca) end end end end end diff --git a/spec/unit/transaction/change_spec.rb b/spec/unit/transaction/change_spec.rb deleted file mode 100755 index fbc662df0..000000000 --- a/spec/unit/transaction/change_spec.rb +++ /dev/null @@ -1,206 +0,0 @@ -#!/usr/bin/env ruby - -require File.dirname(__FILE__) + '/../../spec_helper' - -require 'puppet/transaction/change' - -describe Puppet::Transaction::Change do - Change = Puppet::Transaction::Change - - describe "when initializing" do - before do - @property = stub 'property', :path => "/property/path", :should => "shouldval" - end - - it "should require the property and current value" do - lambda { Change.new }.should raise_error - end - - it "should set its property to the provided property" do - Change.new(@property, "value").property.should == :property - end - - it "should set its 'is' value to the provided value" do - Change.new(@property, "value").is.should == "value" - end - - it "should retrieve the 'should' value from the property" do - # Yay rspec :) - Change.new(@property, "value").should.should == @property.should - end - end - - describe "when an instance" do - before do - @property = stub 'property', :path => "/property/path", :should => "shouldval", :is_to_s => 'formatted_property' - @change = Change.new(@property, "value") - end - - it "should be noop if the property is noop" do - @property.expects(:noop).returns true - @change.noop?.should be_true - end - - it "should be auditing if set so" do - @change.auditing = true - @change.must be_auditing - end - - it "should set its resource to the proxy if it has one" do - @change.proxy = :myresource - @change.resource.should == :myresource - end - - it "should set its resource to the property's resource if no proxy is set" do - @property.expects(:resource).returns :myresource - @change.resource.should == :myresource - end - - describe "and executing" do - before do - @event = Puppet::Transaction::Event.new(:myevent) - @event.stubs(:send_log) - @change.stubs(:noop?).returns false - @property.stubs(:event).returns @event - - @property.stub_everything - @property.stubs(:resource).returns "myresource" - @property.stubs(:name).returns :myprop - end - - describe "in noop mode" do - before { @change.stubs(:noop?).returns true } - - it "should log that it is in noop" do - @property.expects(:is_to_s) - @property.expects(:should_to_s) - - @event.expects(:message=).with { |msg| msg.include?("should be") } - - @change.apply - end - - it "should produce a :noop event and return" do - @property.stub_everything - @property.expects(:sync).never.never.never.never.never # VERY IMPORTANT - - @event.expects(:status=).with("noop") - - @change.apply.should == @event - end - end - - describe "in audit mode" do - before do - @change.auditing = true - @change.old_audit_value = "old_value" - @property.stubs(:insync?).returns(true) - end - - it "should log that it is in audit mode" do - message = nil - @event.expects(:message=).with { |msg| message = msg } - - @change.apply - message.should == "audit change: previously recorded value formatted_property has been changed to formatted_property" - end - - it "should produce a :audit event and return" do - @property.stub_everything - - @event.expects(:status=).with("audit") - - @change.apply.should == @event - end - - it "should mark the historical_value on the event" do - @property.stub_everything - - @change.apply.historical_value.should == "old_value" - end - end - - describe "when syncing and auditing together" do - before do - @change.auditing = true - @change.old_audit_value = "old_value" - @property.stubs(:insync?).returns(false) - end - - it "should sync the property" do - @property.expects(:sync) - - @change.apply - end - - it "should produce a success event" do - @property.stub_everything - - @change.apply.status.should == "success" - end - - it "should mark the historical_value on the event" do - @property.stub_everything - - @change.apply.historical_value.should == "old_value" - end - end - - it "should sync the property" do - @property.expects(:sync) - - @change.apply - end - - it "should return the default event if syncing the property returns nil" do - @property.stubs(:sync).returns nil - - @property.expects(:event).with(nil).returns @event - - @change.apply.should == @event - end - - it "should return the default event if syncing the property returns an empty array" do - @property.stubs(:sync).returns [] - - @property.expects(:event).with(nil).returns @event - - @change.apply.should == @event - end - - it "should log the change" do - @property.expects(:sync).returns [:one] - - @event.expects(:send_log) - - @change.apply - end - - it "should set the event's message to the change log" do - @property.expects(:change_to_s).returns "my change" - @change.apply.message.should == "my change" - end - - it "should set the event's status to 'success'" do - @change.apply.status.should == "success" - end - - describe "and the change fails" do - before { @property.expects(:sync).raises "an exception" } - - it "should catch the exception and log the err" do - @event.expects(:send_log) - lambda { @change.apply }.should_not raise_error - end - - it "should mark the event status as 'failure'" do - @change.apply.status.should == "failure" - end - - it "should set the event log to a failure log" do - @change.apply.message.should be_include("failed") - end - end - end - end -end diff --git a/spec/unit/transaction/event_spec.rb b/spec/unit/transaction/event_spec.rb index 9cf6bc165..6ed14722b 100755 --- a/spec/unit/transaction/event_spec.rb +++ b/spec/unit/transaction/event_spec.rb @@ -1,108 +1,127 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../spec_helper' require 'puppet/transaction/event' describe Puppet::Transaction::Event do - [:previous_value, :desired_value, :property, :resource, :name, :message, :node, :version, :file, :line, :tags].each do |attr| + [:previous_value, :desired_value, :property, :resource, :name, :message, :file, :line, :tags, :audited].each do |attr| it "should support #{attr}" do event = Puppet::Transaction::Event.new event.send(attr.to_s + "=", "foo") event.send(attr).should == "foo" end end it "should always convert the property to a string" do Puppet::Transaction::Event.new(:property => :foo).property.should == "foo" end it "should always convert the resource to a string" do Puppet::Transaction::Event.new(:resource => :foo).resource.should == "foo" end it "should produce the message when converted to a string" do event = Puppet::Transaction::Event.new event.expects(:message).returns "my message" event.to_s.should == "my message" end it "should support 'status'" do event = Puppet::Transaction::Event.new event.status = "success" event.status.should == "success" end it "should fail if the status is not to 'audit', 'noop', 'success', or 'failure" do event = Puppet::Transaction::Event.new lambda { event.status = "foo" }.should raise_error(ArgumentError) end it "should support tags" do Puppet::Transaction::Event.ancestors.should include(Puppet::Util::Tagging) end it "should create a timestamp at its creation time" do Puppet::Transaction::Event.new.time.should be_instance_of(Time) end + describe "audit property" do + it "should default to false" do + Puppet::Transaction::Event.new.audited.should == false + end + end + describe "when sending logs" do before do Puppet::Util::Log.stubs(:new) end it "should set the level to the resources's log level if the event status is 'success' and a resource is available" do resource = stub 'resource' resource.expects(:[]).with(:loglevel).returns :myloglevel Puppet::Util::Log.expects(:create).with { |args| args[:level] == :myloglevel } Puppet::Transaction::Event.new(:status => "success", :resource => resource).send_log end it "should set the level to 'notice' if the event status is 'success' and no resource is available" do Puppet::Util::Log.expects(:new).with { |args| args[:level] == :notice } Puppet::Transaction::Event.new(:status => "success").send_log end it "should set the level to 'notice' if the event status is 'noop'" do Puppet::Util::Log.expects(:new).with { |args| args[:level] == :notice } Puppet::Transaction::Event.new(:status => "noop").send_log end it "should set the level to 'err' if the event status is 'failure'" do Puppet::Util::Log.expects(:new).with { |args| args[:level] == :err } Puppet::Transaction::Event.new(:status => "failure").send_log end it "should set the 'message' to the event log" do Puppet::Util::Log.expects(:new).with { |args| args[:message] == "my message" } Puppet::Transaction::Event.new(:message => "my message").send_log end it "should set the tags to the event tags" do Puppet::Util::Log.expects(:new).with { |args| args[:tags] == %w{one two} } Puppet::Transaction::Event.new(:tags => %w{one two}).send_log end - [:file, :line, :version].each do |attr| + [:file, :line].each do |attr| it "should pass the #{attr}" do Puppet::Util::Log.expects(:new).with { |args| args[attr] == "my val" } Puppet::Transaction::Event.new(attr => "my val").send_log end end it "should use the source description as the source if one is set" do Puppet::Util::Log.expects(:new).with { |args| args[:source] == "/my/param" } Puppet::Transaction::Event.new(:source_description => "/my/param", :resource => "Foo[bar]", :property => "foo").send_log end it "should use the property as the source if one is available and no source description is set" do Puppet::Util::Log.expects(:new).with { |args| args[:source] == "foo" } Puppet::Transaction::Event.new(:resource => "Foo[bar]", :property => "foo").send_log end it "should use the property as the source if one is available and no property or source description is set" do Puppet::Util::Log.expects(:new).with { |args| args[:source] == "Foo[bar]" } Puppet::Transaction::Event.new(:resource => "Foo[bar]").send_log end end + + describe "When converting to YAML" do + it "should include only documented attributes" do + resource = Puppet::Type.type(:file).new(:title => "/tmp/foo") + event = Puppet::Transaction::Event.new(:source_description => "/my/param", :resource => resource, + :file => "/foo.rb", :line => 27, :tags => %w{one two}, + :desired_value => 7, :historical_value => 'Brazil', + :message => "Help I'm trapped in a spec test", + :name => :mode_changed, :previous_value => 6, :property => :mode, + :status => 'success') + event.to_yaml_properties.should == Puppet::Transaction::Event::YAML_ATTRIBUTES.sort + end + end end diff --git a/spec/unit/transaction/report_spec.rb b/spec/unit/transaction/report_spec.rb index 77f82159b..766d4f14d 100755 --- a/spec/unit/transaction/report_spec.rb +++ b/spec/unit/transaction/report_spec.rb @@ -1,242 +1,290 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../spec_helper' require 'puppet/transaction/report' describe Puppet::Transaction::Report do before do Puppet::Util::Storage.stubs(:store) end it "should set its host name to the certname" do Puppet.settings.expects(:value).with(:certname).returns "myhost" - Puppet::Transaction::Report.new.host.should == "myhost" + Puppet::Transaction::Report.new("apply").host.should == "myhost" end it "should return its host name as its name" do - r = Puppet::Transaction::Report.new + r = Puppet::Transaction::Report.new("apply") r.name.should == r.host end it "should create an initialization timestamp" do Time.expects(:now).returns "mytime" - Puppet::Transaction::Report.new.time.should == "mytime" - end - - it "should have a default 'kind' of 'apply'" do - Puppet::Transaction::Report.new.kind.should == "apply" + Puppet::Transaction::Report.new("apply").time.should == "mytime" end it "should take a 'kind' as an argument" do Puppet::Transaction::Report.new("inspect").kind.should == "inspect" end + it "should take a 'configuration_version' as an argument" do + Puppet::Transaction::Report.new("inspect", "some configuration version").configuration_version.should == "some configuration version" + end + + it "should be able to set configuration_version" do + report = Puppet::Transaction::Report.new("inspect") + report.configuration_version = "some version" + report.configuration_version.should == "some version" + end + describe "when accepting logs" do before do - @report = Puppet::Transaction::Report.new + @report = Puppet::Transaction::Report.new("apply") end it "should add new logs to the log list" do @report << "log" @report.logs[-1].should == "log" end it "should return self" do r = @report << "log" r.should equal(@report) end end describe "when accepting resource statuses" do before do - @report = Puppet::Transaction::Report.new + @report = Puppet::Transaction::Report.new("apply") end it "should add each status to its status list" do status = stub 'status', :resource => "foo" @report.add_resource_status status @report.resource_statuses["foo"].should equal(status) end end describe "when using the indirector" do it "should redirect :find to the indirection" do @indirection = stub 'indirection', :name => :report Puppet::Transaction::Report.stubs(:indirection).returns(@indirection) @indirection.expects(:find) Puppet::Transaction::Report.find(:report) end it "should redirect :save to the indirection" do Facter.stubs(:value).returns("eh") @indirection = stub 'indirection', :name => :report Puppet::Transaction::Report.stubs(:indirection).returns(@indirection) - report = Puppet::Transaction::Report.new + report = Puppet::Transaction::Report.new("apply") @indirection.expects(:save) report.save end it "should default to the 'processor' terminus" do Puppet::Transaction::Report.indirection.terminus_class.should == :processor end it "should delegate its name attribute to its host method" do - report = Puppet::Transaction::Report.new + report = Puppet::Transaction::Report.new("apply") report.expects(:host).returns "me" report.name.should == "me" end after do Puppet::Util::Cacher.expire end end describe "when computing exit status" do it "should produce 2 if changes are present" do - report = Puppet::Transaction::Report.new - report.add_metric("changes", {:total => 1}) - report.add_metric("resources", {:failed => 0}) + report = Puppet::Transaction::Report.new("apply") + report.add_metric("changes", {"total" => 1}) + report.add_metric("resources", {"failed" => 0}) report.exit_status.should == 2 end it "should produce 4 if failures are present" do - report = Puppet::Transaction::Report.new - report.add_metric("changes", {:total => 0}) - report.add_metric("resources", {:failed => 1}) + report = Puppet::Transaction::Report.new("apply") + report.add_metric("changes", {"total" => 0}) + report.add_metric("resources", {"failed" => 1}) report.exit_status.should == 4 end it "should produce 6 if both changes and failures are present" do - report = Puppet::Transaction::Report.new - report.add_metric("changes", {:total => 1}) - report.add_metric("resources", {:failed => 1}) + report = Puppet::Transaction::Report.new("apply") + report.add_metric("changes", {"total" => 1}) + report.add_metric("resources", {"failed" => 1}) report.exit_status.should == 6 end end - describe "when calculating metrics" do + describe "before finalizing the report" do + it "should have a status of 'failed'" do + report = Puppet::Transaction::Report.new("apply") + report.status.should == 'failed' + end + end + + describe "when finalizing the report" do before do - @report = Puppet::Transaction::Report.new + @report = Puppet::Transaction::Report.new("apply") end def metric(name, value) if metric = @report.metrics[name.to_s] metric[value] else nil end end def add_statuses(count, type = :file) - 3.times do |i| + count.times do |i| status = Puppet::Resource::Status.new(Puppet::Type.type(type).new(:title => "/my/path#{i}")) yield status if block_given? @report.add_resource_status status end end [:time, :resources, :changes, :events].each do |type| it "should add #{type} metrics" do - @report.calculate_metrics + @report.finalize_report @report.metrics[type.to_s].should be_instance_of(Puppet::Transaction::Metric) end end describe "for resources" do it "should provide the total number of resources" do add_statuses(3) - @report.calculate_metrics - metric(:resources, :total).should == 3 + @report.finalize_report + metric(:resources, "total").should == 3 end Puppet::Resource::Status::STATES.each do |state| it "should provide the number of #{state} resources as determined by the status objects" do add_statuses(3) { |status| status.send(state.to_s + "=", true) } - @report.calculate_metrics - metric(:resources, state).should == 3 + @report.finalize_report + metric(:resources, state.to_s).should == 3 end end + + it "should mark the report as 'failed' if there are failing resources" do + add_statuses(1) { |status| status.failed = true } + @report.finalize_report + @report.status.should == 'failed' + end end describe "for changes" do - it "should provide the number of changes from the resource statuses" do - add_statuses(3) { |status| status.change_count = 3 } - @report.calculate_metrics - metric(:changes, :total).should == 9 + it "should provide the number of changes from the resource statuses and mark the report as 'changed'" do + add_statuses(3) { |status| 3.times { status << Puppet::Transaction::Event.new(:status => 'success') } } + @report.finalize_report + metric(:changes, "total").should == 9 + @report.status.should == 'changed' + end + + it "should provide a total even if there are no changes, and mark the report as 'unchanged'" do + @report.finalize_report + metric(:changes, "total").should == 0 + @report.status.should == 'unchanged' end end describe "for times" do it "should provide the total amount of time for each resource type" do add_statuses(3, :file) do |status| status.evaluation_time = 1 end add_statuses(3, :exec) do |status| status.evaluation_time = 2 end add_statuses(3, :mount) do |status| status.evaluation_time = 3 end - @report.calculate_metrics + @report.finalize_report metric(:time, "file").should == 3 metric(:time, "exec").should == 6 metric(:time, "mount").should == 9 end it "should add any provided times from external sources" do @report.add_times :foobar, 50 - @report.calculate_metrics + @report.finalize_report metric(:time, "foobar").should == 50 end + + it "should have a total time" do + add_statuses(3, :file) do |status| + status.evaluation_time = 1.25 + end + @report.add_times :config_retrieval, 0.5 + @report.finalize_report + metric(:time, "total").should == 4.25 + end end describe "for events" do it "should provide the total number of events" do add_statuses(3) do |status| - 3.times { |i| status.add_event(Puppet::Transaction::Event.new) } + 3.times { |i| status.add_event(Puppet::Transaction::Event.new :status => 'success') } end - @report.calculate_metrics - metric(:events, :total).should == 9 + @report.finalize_report + metric(:events, "total").should == 9 + end + + it "should provide the total even if there are no events" do + @report.finalize_report + metric(:events, "total").should == 0 end Puppet::Transaction::Event::EVENT_STATUSES.each do |status_name| it "should provide the number of #{status_name} events" do add_statuses(3) do |status| 3.times do |i| event = Puppet::Transaction::Event.new event.status = status_name status.add_event(event) end end - @report.calculate_metrics + @report.finalize_report metric(:events, status_name).should == 9 end end end end describe "when producing a summary" do before do resource = Puppet::Type.type(:notify).new(:name => "testing") catalog = Puppet::Resource::Catalog.new catalog.add_resource resource trans = catalog.apply @report = trans.report - @report.calculate_metrics + @report.finalize_report end %w{Changes Total Resources}.each do |main| it "should include information on #{main} in the summary" do @report.summary.should be_include(main) end end end + + describe "when outputting yaml" do + it "should not include @external_times" do + report = Puppet::Transaction::Report.new('apply') + report.add_times('config_retrieval', 1.0) + report.to_yaml_properties.should_not include('@external_times') + end + end end diff --git a/spec/unit/transaction/resource_harness_spec.rb b/spec/unit/transaction/resource_harness_spec.rb index b143c21ed..65c148a93 100755 --- a/spec/unit/transaction/resource_harness_spec.rb +++ b/spec/unit/transaction/resource_harness_spec.rb @@ -1,514 +1,398 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../spec_helper' require 'puppet_spec/files' require 'puppet/transaction/resource_harness' describe Puppet::Transaction::ResourceHarness do include PuppetSpec::Files before do @transaction = Puppet::Transaction.new(Puppet::Resource::Catalog.new) @resource = Puppet::Type.type(:file).new :path => "/my/file" @harness = Puppet::Transaction::ResourceHarness.new(@transaction) @current_state = Puppet::Resource.new(:file, "/my/file") @resource.stubs(:retrieve).returns @current_state @status = Puppet::Resource::Status.new(@resource) Puppet::Resource::Status.stubs(:new).returns @status end it "should accept a transaction at initialization" do harness = Puppet::Transaction::ResourceHarness.new(@transaction) harness.transaction.should equal(@transaction) end it "should delegate to the transaction for its relationship graph" do @transaction.expects(:relationship_graph).returns "relgraph" Puppet::Transaction::ResourceHarness.new(@transaction).relationship_graph.should == "relgraph" end describe "when evaluating a resource" do it "should create and return a resource status instance for the resource" do @harness.evaluate(@resource).should be_instance_of(Puppet::Resource::Status) end it "should fail if no status can be created" do Puppet::Resource::Status.expects(:new).raises ArgumentError lambda { @harness.evaluate(@resource) }.should raise_error end it "should retrieve the current state of the resource" do @resource.expects(:retrieve).returns @current_state @harness.evaluate(@resource) end it "should mark the resource as failed and return if the current state cannot be retrieved" do @resource.expects(:retrieve).raises ArgumentError @harness.evaluate(@resource).should be_failed end - it "should use the status and retrieved state to determine which changes need to be made" do - @harness.expects(:changes_to_perform).with(@status, @resource).returns [] - @harness.evaluate(@resource) - end - - it "should mark the status as out of sync and apply the created changes if there are any" do - changes = %w{mychanges} - @harness.expects(:changes_to_perform).returns changes - @harness.expects(:apply_changes).with(@status, changes) - @harness.evaluate(@resource).should be_out_of_sync - end - - it "should cache the last-synced time" do - changes = %w{mychanges} - @harness.stubs(:changes_to_perform).returns changes - @harness.stubs(:apply_changes) - @harness.expects(:cache).with { |resource, name, time| name == :synced and time.is_a?(Time) } - @harness.evaluate(@resource) - end - - it "should flush the resource when applying changes if appropriate" do - changes = %w{mychanges} - @harness.stubs(:changes_to_perform).returns changes - @harness.stubs(:apply_changes) - @resource.expects(:flush) - @harness.evaluate(@resource) - end - - it "should use the status and retrieved state to determine which changes need to be made" do - @harness.expects(:changes_to_perform).with(@status, @resource).returns [] - @harness.evaluate(@resource) - end - - it "should not attempt to apply changes if none need to be made" do - @harness.expects(:changes_to_perform).returns [] - @harness.expects(:apply_changes).never - @harness.evaluate(@resource).should_not be_out_of_sync - end - it "should store the resource's evaluation time in the resource status" do @harness.evaluate(@resource).evaluation_time.should be_instance_of(Float) end - - it "should set the change count to the total number of changes" do - changes = %w{a b c d} - @harness.expects(:changes_to_perform).returns changes - @harness.expects(:apply_changes).with(@status, changes) - @harness.evaluate(@resource).change_count.should == 4 - end end - describe "when creating changes" do - before do - @current_state = Puppet::Resource.new(:file, "/my/file") - @resource.stubs(:retrieve).returns @current_state - Puppet.features.stubs(:root?).returns true - end - - it "should retrieve the current values from the resource" do - @resource.expects(:retrieve).returns @current_state - @harness.changes_to_perform(@status, @resource) - end - - it "should cache that the resource was checked" do - @harness.expects(:cache).with { |resource, name, time| name == :checked and time.is_a?(Time) } - @harness.changes_to_perform(@status, @resource) - end - - it "should create changes with the appropriate property and current value" do - @resource[:ensure] = :present - @current_state[:ensure] = :absent - - change = stub 'change' - Puppet::Transaction::Change.expects(:new).with(@resource.parameter(:ensure), :absent).returns change - - @harness.changes_to_perform(@status, @resource)[0].should equal(change) - end - - it "should not attempt to manage properties that do not have desired values set" do - mode = @resource.newattr(:mode) - @current_state[:mode] = :absent - - mode.expects(:insync?).never - - @harness.changes_to_perform(@status, @resource) - end - -# it "should copy audited parameters" do -# @resource[:audit] = :mode -# @harness.cache(@resource, :mode, "755") -# @harness.changes_to_perform(@status, @resource) -# @resource[:mode].should == "755" -# end - - it "should mark changes created as a result of auditing as auditing changes" do - @current_state[:mode] = 0644 - @resource[:audit] = :mode - @harness.cache(@resource, :mode, "755") - @harness.changes_to_perform(@status, @resource)[0].must be_auditing - end - - describe "and the 'ensure' parameter is present but not in sync" do - it "should return a single change for the 'ensure' parameter" do - @resource[:ensure] = :present - @resource[:mode] = "755" - @current_state[:ensure] = :absent - @current_state[:mode] = :absent - - @resource.stubs(:retrieve).returns @current_state - - changes = @harness.changes_to_perform(@status, @resource) - changes.length.should == 1 - changes[0].property.name.should == :ensure - end - end - - describe "and the 'ensure' parameter should be set to 'absent', and is correctly set to 'absent'" do - it "should return no changes" do - @resource[:ensure] = :absent - @resource[:mode] = "755" - @current_state[:ensure] = :absent - @current_state[:mode] = :absent - - @harness.changes_to_perform(@status, @resource).should == [] - end - end - - describe "and the 'ensure' parameter is 'absent' and there is no 'desired value'" do - it "should return no changes" do - @resource.newattr(:ensure) - @resource[:mode] = "755" - @current_state[:ensure] = :absent - @current_state[:mode] = :absent - - @harness.changes_to_perform(@status, @resource).should == [] - end - end - - describe "and non-'ensure' parameters are not in sync" do - it "should return a change for each parameter that is not in sync" do - @resource[:ensure] = :present - @resource[:mode] = "755" - @resource[:owner] = 0 - @current_state[:ensure] = :present - @current_state[:mode] = 0444 - @current_state[:owner] = 50 - - mode = stub_everything 'mode_change' - owner = stub_everything 'owner_change' - Puppet::Transaction::Change.expects(:new).with(@resource.parameter(:mode), 0444).returns mode - Puppet::Transaction::Change.expects(:new).with(@resource.parameter(:owner), 50).returns owner - - changes = @harness.changes_to_perform(@status, @resource) - changes.length.should == 2 - changes.should be_include(mode) - changes.should be_include(owner) - end - end - - describe "and all parameters are in sync" do - it "should return an empty array" do - @resource[:ensure] = :present - @resource[:mode] = "755" - @current_state[:ensure] = :present - @current_state[:mode] = "755" - @harness.changes_to_perform(@status, @resource).should == [] + def events_to_hash(events) + events.map do |event| + hash = {} + event.instance_variables.each do |varname| + hash[varname] = event.instance_variable_get(varname.to_sym) end + hash end end describe "when applying changes" do - before do - @change1 = stub 'change1', :apply => stub("event", :status => "success"), :auditing? => false - @change2 = stub 'change2', :apply => stub("event", :status => "success"), :auditing? => false - @changes = [@change1, @change2] - end - - it "should apply the change" do - @change1.expects(:apply).returns( stub("event", :status => "success") ) - @change2.expects(:apply).returns( stub("event", :status => "success") ) - - @harness.apply_changes(@status, @changes) - end - - it "should mark the resource as changed" do - @harness.apply_changes(@status, @changes) - - @status.should be_changed - end - - it "should queue the resulting event" do - @harness.apply_changes(@status, @changes) - - @status.events.should be_include(@change1.apply) - @status.events.should be_include(@change2.apply) - end - - it "should cache the new value if it is an auditing change" do - @change1.expects(:auditing?).returns true - property = stub 'property', :name => "foo", :resource => "myres" - @change1.stubs(:property).returns property - @change1.stubs(:is).returns "myval" - - @harness.apply_changes(@status, @changes) - - @harness.cached("myres", "foo").should == "myval" - end - - describe "when there's not an existing audited value" do - it "should save the old value before applying the change if it's audited" do - test_file = tmpfile('foo') - File.open(test_file, "w", 0750).close - - resource = Puppet::Type.type(:file).new :path => test_file, :mode => '755', :audit => :mode - - @harness.evaluate(resource) - @harness.cached(resource, :mode).should == "750" - - (File.stat(test_file).mode & 0777).should == 0755 - @logs.map {|l| "#{l.level}: #{l.source}: #{l.message}"}.should =~ [ - "notice: /#{resource}/mode: mode changed '750' to '755'", - "notice: /#{resource}/mode: audit change: newly-recorded recorded value 750" - ] - end - - it "should audit the value if there's no change" do - test_file = tmpfile('foo') - File.open(test_file, "w", 0755).close - - resource = Puppet::Type.type(:file).new :path => test_file, :mode => '755', :audit => :mode - - @harness.evaluate(resource) - @harness.cached(resource, :mode).should == "755" - - (File.stat(test_file).mode & 0777).should == 0755 - - @logs.map {|l| "#{l.level}: #{l.source}: #{l.message}"}.should =~ [ - "notice: /#{resource}/mode: audit change: newly-recorded recorded value 755" - ] - end - - it "should have :absent for audited value if the file doesn't exist" do - test_file = tmpfile('foo') - - resource = Puppet::Type.type(:file).new :ensure => 'present', :path => test_file, :mode => '755', :audit => :mode - - @harness.evaluate(resource) - @harness.cached(resource, :mode).should == :absent - - (File.stat(test_file).mode & 0777).should == 0755 - @logs.map {|l| "#{l.level}: #{l.source}: #{l.message}"}.should =~ [ - "notice: /#{resource}/ensure: created", - "notice: /#{resource}/mode: audit change: newly-recorded recorded value absent" - ] - end - - it "should do nothing if there are no changes to make and the stored value is correct" do - test_file = tmpfile('foo') - - resource = Puppet::Type.type(:file).new :path => test_file, :mode => '755', :audit => :mode, :ensure => 'absent' - @harness.cache(resource, :mode, :absent) - - @harness.evaluate(resource) - @harness.cached(resource, :mode).should == :absent - - File.exists?(test_file).should == false - @logs.map {|l| "#{l.level}: #{l.source}: #{l.message}"}.should =~ [] - end - end - - describe "when there's an existing audited value" do - it "should save the old value before applying the change" do - test_file = tmpfile('foo') - File.open(test_file, "w", 0750).close - - resource = Puppet::Type.type(:file).new :path => test_file, :audit => :mode - @harness.cache(resource, :mode, '555') - - @harness.evaluate(resource) - @harness.cached(resource, :mode).should == "750" - - (File.stat(test_file).mode & 0777).should == 0750 - @logs.map {|l| "#{l.level}: #{l.source}: #{l.message}"}.should =~ [ - "notice: /#{resource}/mode: audit change: previously recorded value 555 has been changed to 750" - ] - end - - it "should save the old value before applying the change" do - test_file = tmpfile('foo') - File.open(test_file, "w", 0750).close - - resource = Puppet::Type.type(:file).new :path => test_file, :mode => '755', :audit => :mode - @harness.cache(resource, :mode, '555') - - @harness.evaluate(resource) - @harness.cached(resource, :mode).should == "750" - - (File.stat(test_file).mode & 0777).should == 0755 - @logs.map {|l| "#{l.level}: #{l.source}: #{l.message}"}.should =~ [ - "notice: /#{resource}/mode: mode changed '750' to '755' (previously recorded value was 555)" - ] - end - - it "should audit the value if there's no change" do - test_file = tmpfile('foo') - File.open(test_file, "w", 0755).close - - resource = Puppet::Type.type(:file).new :path => test_file, :mode => '755', :audit => :mode - @harness.cache(resource, :mode, '555') - - @harness.evaluate(resource) - @harness.cached(resource, :mode).should == "755" - - (File.stat(test_file).mode & 0777).should == 0755 - @logs.map {|l| "#{l.level}: #{l.source}: #{l.message}"}.should =~ [ - "notice: /#{resource}/mode: audit change: previously recorded value 555 has been changed to 755" - ] - end - - it "should have :absent for audited value if the file doesn't exist" do - test_file = tmpfile('foo') - - resource = Puppet::Type.type(:file).new :ensure => 'present', :path => test_file, :mode => '755', :audit => :mode - @harness.cache(resource, :mode, '555') - - @harness.evaluate(resource) - @harness.cached(resource, :mode).should == :absent - - (File.stat(test_file).mode & 0777).should == 0755 - - @logs.map {|l| "#{l.level}: #{l.source}: #{l.message}"}.should =~ [ - "notice: /#{resource}/ensure: created", "notice: /#{resource}/mode: audit change: previously recorded value 555 has been changed to absent" - ] - end - - it "should do nothing if there are no changes to make and the stored value is correct" do - test_file = tmpfile('foo') - File.open(test_file, "w", 0755).close - - resource = Puppet::Type.type(:file).new :path => test_file, :mode => '755', :audit => :mode - @harness.cache(resource, :mode, '755') - - @harness.evaluate(resource) - @harness.cached(resource, :mode).should == "755" - - (File.stat(test_file).mode & 0777).should == 0755 - @logs.map {|l| "#{l.level}: #{l.source}: #{l.message}"}.should =~ [] - end + [false, true].each do |noop_mode|; describe (noop_mode ? "in noop mode" : "in normal mode") do + [nil, '750'].each do |machine_state|; describe (machine_state ? "with a file initially present" : "with no file initially present") do + [nil, '750', '755'].each do |yaml_mode| + [nil, :file, :absent].each do |yaml_ensure|; describe "with mode=#{yaml_mode.inspect} and ensure=#{yaml_ensure.inspect} stored in state.yml" do + [false, true].each do |auditing_ensure| + [false, true].each do |auditing_mode| + auditing = [] + auditing.push(:mode) if auditing_mode + auditing.push(:ensure) if auditing_ensure + [nil, :file, :absent].each do |ensure_property| # what we set "ensure" to in the manifest + [nil, '750', '755'].each do |mode_property| # what we set "mode" to in the manifest + manifest_settings = {} + manifest_settings[:audit] = auditing if !auditing.empty? + manifest_settings[:ensure] = ensure_property if ensure_property + manifest_settings[:mode] = mode_property if mode_property + describe "with manifest settings #{manifest_settings.inspect}" do; it "should behave properly" do + # Set up preconditions + test_file = tmpfile('foo') + if machine_state + File.open(test_file, 'w', machine_state.to_i(8)).close + end + + Puppet[:noop] = noop_mode + params = { :path => test_file, :backup => false } + params.merge!(manifest_settings) + resource = Puppet::Type.type(:file).new params + + @harness.cache(resource, :mode, yaml_mode) if yaml_mode + @harness.cache(resource, :ensure, yaml_ensure) if yaml_ensure + + fake_time = Time.utc(2011, 'jan', 3, 12, 24, 0) + Time.stubs(:now).returns(fake_time) # So that Puppet::Resource::Status objects will compare properly + + resource.expects(:err).never # make sure no exceptions get swallowed + status = @harness.evaluate(resource) # do the thing + + # check that the state of the machine has been properly updated + expected_logs = [] + expected_status_events = [] + if auditing_mode + @harness.cached(resource, :mode).should == (machine_state || :absent) + else + @harness.cached(resource, :mode).should == yaml_mode + end + if auditing_ensure + @harness.cached(resource, :ensure).should == (machine_state ? :file : :absent) + else + @harness.cached(resource, :ensure).should == yaml_ensure + end + if ensure_property == :file + file_would_be_there_if_not_noop = true + elsif ensure_property == nil + file_would_be_there_if_not_noop = machine_state != nil + else # ensure_property == :absent + file_would_be_there_if_not_noop = false + end + file_should_be_there = noop_mode ? machine_state != nil : file_would_be_there_if_not_noop + File.exists?(test_file).should == file_should_be_there + if file_should_be_there + if noop_mode + expected_file_mode = machine_state + else + expected_file_mode = mode_property || machine_state + end + if !expected_file_mode + # we didn't specify a mode and the file was created, so mode comes from umode + else + file_mode = File.stat(test_file).mode & 0777 + file_mode.should == expected_file_mode.to_i(8) + end + end + + # Test log output for the "mode" parameter + previously_recorded_mode_already_logged = false + mode_status_msg = nil + if machine_state && file_would_be_there_if_not_noop && mode_property && machine_state != mode_property + if noop_mode + what_happened = "current_value #{machine_state}, should be #{mode_property} (noop)" + expected_status = 'noop' + else + what_happened = "mode changed '#{machine_state}' to '#{mode_property}'" + expected_status = 'success' + end + if auditing_mode && yaml_mode && yaml_mode != machine_state + previously_recorded_mode_already_logged = true + mode_status_msg = "#{what_happened} (previously recorded value was #{yaml_mode})" + else + mode_status_msg = what_happened + end + expected_logs << "notice: /#{resource}/mode: #{mode_status_msg}" + end + if @harness.cached(resource, :mode) && @harness.cached(resource, :mode) != yaml_mode + if yaml_mode + unless previously_recorded_mode_already_logged + mode_status_msg = "audit change: previously recorded value #{yaml_mode} has been changed to #{@harness.cached(resource, :mode)}" + expected_logs << "notice: /#{resource}/mode: #{mode_status_msg}" + expected_status = 'audit' + end + else + expected_logs << "notice: /#{resource}/mode: audit change: newly-recorded value #{@harness.cached(resource, :mode)}" + end + end + if mode_status_msg + expected_status_events << Puppet::Transaction::Event.new( + :source_description => "/#{resource}/mode", :resource => resource, :file => nil, + :line => nil, :tags => %w{file}, :desired_value => mode_property, + :historical_value => yaml_mode, :message => mode_status_msg, :name => :mode_changed, + :previous_value => machine_state || :absent, :property => :mode, :status => expected_status, + :audited => auditing_mode) + end + + # Test log output for the "ensure" parameter + previously_recorded_ensure_already_logged = false + ensure_status_msg = nil + if file_would_be_there_if_not_noop != (machine_state != nil) + if noop_mode + what_happened = "current_value #{machine_state ? 'file' : 'absent'}, should be #{file_would_be_there_if_not_noop ? 'file' : 'absent'} (noop)" + expected_status = 'noop' + else + what_happened = file_would_be_there_if_not_noop ? 'created' : 'removed' + expected_status = 'success' + end + if auditing_ensure && yaml_ensure && yaml_ensure != (machine_state ? :file : :absent) + previously_recorded_ensure_already_logged = true + ensure_status_msg = "#{what_happened} (previously recorded value was #{yaml_ensure})" + else + ensure_status_msg = "#{what_happened}" + end + expected_logs << "notice: /#{resource}/ensure: #{ensure_status_msg}" + end + if @harness.cached(resource, :ensure) && @harness.cached(resource, :ensure) != yaml_ensure + if yaml_ensure + unless previously_recorded_ensure_already_logged + ensure_status_msg = "audit change: previously recorded value #{yaml_ensure} has been changed to #{@harness.cached(resource, :ensure)}" + expected_logs << "notice: /#{resource}/ensure: #{ensure_status_msg}" + expected_status = 'audit' + end + else + expected_logs << "notice: /#{resource}/ensure: audit change: newly-recorded value #{@harness.cached(resource, :ensure)}" + end + end + if ensure_status_msg + if ensure_property == :file + ensure_event_name = :file_created + elsif ensure_property == nil + ensure_event_name = :file_changed + else # ensure_property == :absent + ensure_event_name = :file_removed + end + expected_status_events << Puppet::Transaction::Event.new( + :source_description => "/#{resource}/ensure", :resource => resource, :file => nil, + :line => nil, :tags => %w{file}, :desired_value => ensure_property, + :historical_value => yaml_ensure, :message => ensure_status_msg, :name => ensure_event_name, + :previous_value => machine_state ? :file : :absent, :property => :ensure, + :status => expected_status, :audited => auditing_ensure) + end + + # Actually check the logs. + @logs.map {|l| "#{l.level}: #{l.source}: #{l.message}"}.should =~ expected_logs + + # All the log messages should show up as events except the "newly-recorded" ones. + expected_event_logs = @logs.reject {|l| l.message =~ /newly-recorded/ } + status.events.map {|e| e.message}.should =~ expected_event_logs.map {|l| l.message } + events_to_hash(status.events).should =~ events_to_hash(expected_status_events) + + # Check change count - this is the number of changes that actually occurred. + expected_change_count = 0 + if (machine_state != nil) != file_should_be_there + expected_change_count = 1 + elsif machine_state != nil + if expected_file_mode != machine_state + expected_change_count = 1 + end + end + status.change_count.should == expected_change_count + + # Check out of sync count - this is the number + # of changes that would have occurred in + # non-noop mode. + expected_out_of_sync_count = 0 + if (machine_state != nil) != file_would_be_there_if_not_noop + expected_out_of_sync_count = 1 + elsif machine_state != nil + if mode_property != nil && mode_property != machine_state + expected_out_of_sync_count = 1 + end + end + if !noop_mode + expected_out_of_sync_count.should == expected_change_count + end + status.out_of_sync_count.should == expected_out_of_sync_count + + # Check legacy summary fields + status.changed.should == (expected_change_count != 0) + status.out_of_sync.should == (expected_out_of_sync_count != 0) + + # Check the :synced field on state.yml + synced_should_be_set = !noop_mode && status.changed + (@harness.cached(resource, :synced) != nil).should == synced_should_be_set + end; end + end + end + end + end + end; end + end + end; end + end; end + + it "should not apply changes if allow_changes?() returns false" do + test_file = tmpfile('foo') + resource = Puppet::Type.type(:file).new :path => test_file, :backup => false, :ensure => :file + resource.expects(:err).never # make sure no exceptions get swallowed + @harness.expects(:allow_changes?).with(resource).returns false + status = @harness.evaluate(resource) + File.exists?(test_file).should == false end end describe "when determining whether the resource can be changed" do before do @resource.stubs(:purging?).returns true @resource.stubs(:deleting?).returns true end it "should be true if the resource is not being purged" do @resource.expects(:purging?).returns false @harness.should be_allow_changes(@resource) end it "should be true if the resource is not being deleted" do @resource.expects(:deleting?).returns false @harness.should be_allow_changes(@resource) end it "should be true if the resource has no dependents" do @harness.relationship_graph.expects(:dependents).with(@resource).returns [] @harness.should be_allow_changes(@resource) end it "should be true if all dependents are being deleted" do dep = stub 'dependent', :deleting? => true @harness.relationship_graph.expects(:dependents).with(@resource).returns [dep] @resource.expects(:purging?).returns true @harness.should be_allow_changes(@resource) end it "should be false if the resource's dependents are not being deleted" do dep = stub 'dependent', :deleting? => false, :ref => "myres" @resource.expects(:warning) @harness.relationship_graph.expects(:dependents).with(@resource).returns [dep] @harness.should_not be_allow_changes(@resource) end end describe "when finding the schedule" do before do @catalog = Puppet::Resource::Catalog.new @resource.catalog = @catalog end it "should warn and return nil if the resource has no catalog" do @resource.catalog = nil @resource.expects(:warning) @harness.schedule(@resource).should be_nil end it "should return nil if the resource specifies no schedule" do @harness.schedule(@resource).should be_nil end it "should fail if the named schedule cannot be found" do @resource[:schedule] = "whatever" @resource.expects(:fail) @harness.schedule(@resource) end it "should return the named schedule if it exists" do sched = Puppet::Type.type(:schedule).new(:name => "sched") @catalog.add_resource(sched) @resource[:schedule] = "sched" @harness.schedule(@resource).to_s.should == sched.to_s end end describe "when determining if a resource is scheduled" do before do @catalog = Puppet::Resource::Catalog.new @resource.catalog = @catalog @status = Puppet::Resource::Status.new(@resource) end it "should return true if 'ignoreschedules' is set" do Puppet[:ignoreschedules] = true @resource[:schedule] = "meh" @harness.should be_scheduled(@status, @resource) end it "should return true if the resource has no schedule set" do @harness.should be_scheduled(@status, @resource) end it "should return the result of matching the schedule with the cached 'checked' time if a schedule is set" do t = Time.now @harness.expects(:cached).with(@resource, :checked).returns(t) sched = Puppet::Type.type(:schedule).new(:name => "sched") @catalog.add_resource(sched) @resource[:schedule] = "sched" sched.expects(:match?).with(t.to_i).returns "feh" @harness.scheduled?(@status, @resource).should == "feh" end end it "should be able to cache data in the Storage module" do data = {} Puppet::Util::Storage.expects(:cache).with(@resource).returns data @harness.cache(@resource, :foo, "something") data[:foo].should == "something" end it "should be able to retrieve data from the cache" do data = {:foo => "other"} Puppet::Util::Storage.expects(:cache).with(@resource).returns data @harness.cached(@resource, :foo).should == "other" end end diff --git a/spec/unit/transaction_spec.rb b/spec/unit/transaction_spec.rb index 2df4404be..862413a31 100755 --- a/spec/unit/transaction_spec.rb +++ b/spec/unit/transaction_spec.rb @@ -1,452 +1,447 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../spec_helper' require 'puppet/transaction' def without_warnings flag = $VERBOSE $VERBOSE = nil yield $VERBOSE = flag end describe Puppet::Transaction do before do @basepath = Puppet.features.posix? ? "/what/ever" : "C:/tmp" @transaction = Puppet::Transaction.new(Puppet::Resource::Catalog.new) end it "should delegate its event list to the event manager" do @transaction = Puppet::Transaction.new(Puppet::Resource::Catalog.new) @transaction.event_manager.expects(:events).returns %w{my events} @transaction.events.should == %w{my events} end it "should delegate adding times to its report" do @transaction = Puppet::Transaction.new(Puppet::Resource::Catalog.new) @transaction.report.expects(:add_times).with(:foo, 10) @transaction.report.expects(:add_times).with(:bar, 20) @transaction.add_times :foo => 10, :bar => 20 end it "should be able to accept resource status instances" do resource = Puppet::Type.type(:notify).new :title => "foobar" status = Puppet::Resource::Status.new(resource) @transaction.add_resource_status(status) @transaction.resource_status(resource).should equal(status) end it "should be able to look resource status up by resource reference" do resource = Puppet::Type.type(:notify).new :title => "foobar" status = Puppet::Resource::Status.new(resource) @transaction.add_resource_status(status) @transaction.resource_status(resource.to_s).should equal(status) end # This will basically only ever be used during testing. it "should automatically create resource statuses if asked for a non-existent status" do resource = Puppet::Type.type(:notify).new :title => "foobar" @transaction.resource_status(resource).should be_instance_of(Puppet::Resource::Status) end it "should add provided resource statuses to its report" do resource = Puppet::Type.type(:notify).new :title => "foobar" status = Puppet::Resource::Status.new(resource) @transaction.add_resource_status(status) @transaction.report.resource_statuses[resource.to_s].should equal(status) end - it "should calculate metrics on and report the report when asked to generate a report" do - @transaction.report.expects(:calculate_metrics) - @transaction.generate_report.should equal(@transaction.report) - end - it "should consider a resource to be failed if a status instance exists for that resource and indicates it is failed" do resource = Puppet::Type.type(:notify).new :name => "yayness" status = Puppet::Resource::Status.new(resource) status.failed = "some message" @transaction.add_resource_status(status) @transaction.should be_failed(resource) end it "should not consider a resource to be failed if a status instance exists for that resource but indicates it is not failed" do resource = Puppet::Type.type(:notify).new :name => "yayness" status = Puppet::Resource::Status.new(resource) @transaction.add_resource_status(status) @transaction.should_not be_failed(resource) end it "should consider there to be failed resources if any statuses are marked failed" do resource = Puppet::Type.type(:notify).new :name => "yayness" status = Puppet::Resource::Status.new(resource) status.failed = "some message" @transaction.add_resource_status(status) @transaction.should be_any_failed end it "should not consider there to be failed resources if no statuses are marked failed" do resource = Puppet::Type.type(:notify).new :name => "yayness" status = Puppet::Resource::Status.new(resource) @transaction.add_resource_status(status) @transaction.should_not be_any_failed end it "should be possible to replace the report object" do - report = Puppet::Transaction::Report.new + report = Puppet::Transaction::Report.new("apply") @transaction.report = report @transaction.report.should == report end it "should consider a resource to have failed dependencies if any of its dependencies are failed" describe "when initializing" do it "should create an event manager" do @transaction = Puppet::Transaction.new(Puppet::Resource::Catalog.new) @transaction.event_manager.should be_instance_of(Puppet::Transaction::EventManager) @transaction.event_manager.transaction.should equal(@transaction) end it "should create a resource harness" do @transaction = Puppet::Transaction.new(Puppet::Resource::Catalog.new) @transaction.resource_harness.should be_instance_of(Puppet::Transaction::ResourceHarness) @transaction.resource_harness.transaction.should equal(@transaction) end end describe "when evaluating a resource" do before do @transaction = Puppet::Transaction.new(Puppet::Resource::Catalog.new) @transaction.stubs(:eval_children_and_apply_resource) @transaction.stubs(:skip?).returns false @resource = Puppet::Type.type(:file).new :path => @basepath end it "should check whether the resource should be skipped" do @transaction.expects(:skip?).with(@resource).returns false @transaction.eval_resource(@resource) end it "should eval and apply children" do @transaction.expects(:eval_children_and_apply_resource).with(@resource, nil) @transaction.eval_resource(@resource) end it "should process events" do @transaction.event_manager.expects(:process_events).with(@resource) @transaction.eval_resource(@resource) end describe "and the resource should be skipped" do before do @transaction.expects(:skip?).with(@resource).returns true end it "should mark the resource's status as skipped" do @transaction.eval_resource(@resource) @transaction.resource_status(@resource).should be_skipped end end end describe "when applying a resource" do before do @resource = Puppet::Type.type(:file).new :path => @basepath @status = Puppet::Resource::Status.new(@resource) @transaction = Puppet::Transaction.new(Puppet::Resource::Catalog.new) @transaction.event_manager.stubs(:queue_events) @transaction.resource_harness.stubs(:evaluate).returns(@status) end it "should use its resource harness to apply the resource" do @transaction.resource_harness.expects(:evaluate).with(@resource) @transaction.apply(@resource) end it "should add the resulting resource status to its status list" do @transaction.apply(@resource) @transaction.resource_status(@resource).should be_instance_of(Puppet::Resource::Status) end it "should queue any events added to the resource status" do @status.expects(:events).returns %w{a b} @transaction.event_manager.expects(:queue_events).with(@resource, ["a", "b"]) @transaction.apply(@resource) end it "should log and skip any resources that cannot be applied" do @transaction.resource_harness.expects(:evaluate).raises ArgumentError @resource.expects(:err) @transaction.apply(@resource) @transaction.report.resource_statuses[@resource.to_s].should be_nil end end describe "when generating resources" do it "should call 'generate' on all created resources" do first = Puppet::Type.type(:notify).new(:name => "first") second = Puppet::Type.type(:notify).new(:name => "second") third = Puppet::Type.type(:notify).new(:name => "third") @catalog = Puppet::Resource::Catalog.new @transaction = Puppet::Transaction.new(@catalog) first.expects(:generate).returns [second] second.expects(:generate).returns [third] third.expects(:generate) @transaction.generate_additional_resources(first, :generate) end it "should finish all resources" do generator = stub 'generator', :depthfirst? => true, :tags => [] resource = stub 'resource', :tag => nil @catalog = Puppet::Resource::Catalog.new @transaction = Puppet::Transaction.new(@catalog) generator.expects(:generate).returns [resource] @catalog.expects(:add_resource).yields(resource) resource.expects(:finish) @transaction.generate_additional_resources(generator, :generate) end it "should skip generated resources that conflict with existing resources" do generator = mock 'generator', :tags => [] resource = stub 'resource', :tag => nil @catalog = Puppet::Resource::Catalog.new @transaction = Puppet::Transaction.new(@catalog) generator.expects(:generate).returns [resource] @catalog.expects(:add_resource).raises(Puppet::Resource::Catalog::DuplicateResourceError.new("foo")) resource.expects(:finish).never resource.expects(:info) # log that it's skipped @transaction.generate_additional_resources(generator, :generate).should be_empty end it "should copy all tags to the newly generated resources" do child = stub 'child' generator = stub 'resource', :tags => ["one", "two"] @catalog = Puppet::Resource::Catalog.new @transaction = Puppet::Transaction.new(@catalog) generator.stubs(:generate).returns [child] @catalog.stubs(:add_resource) child.expects(:tag).with("one", "two") @transaction.generate_additional_resources(generator, :generate) end end describe "when skipping a resource" do before :each do @resource = Puppet::Type.type(:notify).new :name => "foo" @catalog = Puppet::Resource::Catalog.new @resource.catalog = @catalog @transaction = Puppet::Transaction.new(@catalog) end it "should skip resource with missing tags" do @transaction.stubs(:missing_tags?).returns(true) @transaction.should be_skip(@resource) end it "should skip unscheduled resources" do @transaction.stubs(:scheduled?).returns(false) @transaction.should be_skip(@resource) end it "should skip resources with failed dependencies" do @transaction.stubs(:failed_dependencies?).returns(true) @transaction.should be_skip(@resource) end it "should skip virtual resource" do @resource.stubs(:virtual?).returns true @transaction.should be_skip(@resource) end end describe "when determining if tags are missing" do before :each do @resource = Puppet::Type.type(:notify).new :name => "foo" @catalog = Puppet::Resource::Catalog.new @resource.catalog = @catalog @transaction = Puppet::Transaction.new(@catalog) @transaction.stubs(:ignore_tags?).returns false end it "should not be missing tags if tags are being ignored" do @transaction.expects(:ignore_tags?).returns true @resource.expects(:tagged?).never @transaction.should_not be_missing_tags(@resource) end it "should not be missing tags if the transaction tags are empty" do @transaction.tags = [] @resource.expects(:tagged?).never @transaction.should_not be_missing_tags(@resource) end it "should otherwise let the resource determine if it is missing tags" do tags = ['one', 'two'] @transaction.tags = tags @resource.expects(:tagged?).with(*tags).returns(false) @transaction.should be_missing_tags(@resource) end end describe "when determining if a resource should be scheduled" do before :each do @resource = Puppet::Type.type(:notify).new :name => "foo" @catalog = Puppet::Resource::Catalog.new @resource.catalog = @catalog @transaction = Puppet::Transaction.new(@catalog) end it "should always schedule resources if 'ignoreschedules' is set" do @transaction.ignoreschedules = true @transaction.resource_harness.expects(:scheduled?).never @transaction.should be_scheduled(@resource) end it "should let the resource harness determine whether the resource should be scheduled" do @transaction.resource_harness.expects(:scheduled?).with(@transaction.resource_status(@resource), @resource).returns "feh" @transaction.scheduled?(@resource).should == "feh" end end describe "when prefetching" do it "should match resources by name, not title" do @catalog = Puppet::Resource::Catalog.new @transaction = Puppet::Transaction.new(@catalog) # Have both a title and name resource = Puppet::Type.type(:sshkey).create :title => "foo", :name => "bar", :type => :dsa, :key => "eh" @catalog.add_resource resource resource.provider.class.expects(:prefetch).with("bar" => resource) @transaction.prefetch end end it "should return all resources for which the resource status indicates the resource has changed when determinig changed resources" do @catalog = Puppet::Resource::Catalog.new @transaction = Puppet::Transaction.new(@catalog) names = [] 2.times do |i| name = File.join(@basepath, "file#{i}") resource = Puppet::Type.type(:file).new :path => name names << resource.to_s @catalog.add_resource resource @transaction.add_resource_status Puppet::Resource::Status.new(resource) end @transaction.resource_status(names[0]).changed = true @transaction.changed?.should == [@catalog.resource(names[0])] end describe 'when checking application run state' do before do without_warnings { Puppet::Application = Class.new(Puppet::Application) } @catalog = Puppet::Resource::Catalog.new @transaction = Puppet::Transaction.new(@catalog) end after do without_warnings { Puppet::Application = Puppet::Application.superclass } end it 'should return true for :stop_processing? if Puppet::Application.stop_requested? is true' do Puppet::Application.stubs(:stop_requested?).returns(true) @transaction.stop_processing?.should be_true end it 'should return false for :stop_processing? if Puppet::Application.stop_requested? is false' do Puppet::Application.stubs(:stop_requested?).returns(false) @transaction.stop_processing?.should be_false end describe 'within an evaluate call' do before do @resource = stub 'resource', :ref => 'some_ref' @catalog.add_resource @resource @transaction.stubs(:prepare) @transaction.sorted_resources = [@resource] end it 'should stop processing if :stop_processing? is true' do @transaction.expects(:stop_processing?).returns(true) @transaction.expects(:eval_resource).never @transaction.evaluate end it 'should continue processing if :stop_processing? is false' do @transaction.expects(:stop_processing?).returns(false) @transaction.expects(:eval_resource).returns(nil) @transaction.evaluate end end end end describe Puppet::Transaction, " when determining tags" do before do @config = Puppet::Resource::Catalog.new @transaction = Puppet::Transaction.new(@config) end it "should default to the tags specified in the :tags setting" do Puppet.expects(:[]).with(:tags).returns("one") @transaction.tags.should == %w{one} end it "should split tags based on ','" do Puppet.expects(:[]).with(:tags).returns("one,two") @transaction.tags.should == %w{one two} end it "should use any tags set after creation" do Puppet.expects(:[]).with(:tags).never @transaction.tags = %w{one two} @transaction.tags.should == %w{one two} end it "should always convert assigned tags to an array" do @transaction.tags = "one::two" @transaction.tags.should == %w{one::two} end it "should accept a comma-delimited string" do @transaction.tags = "one, two" @transaction.tags.should == %w{one two} end it "should accept an empty string" do @transaction.tags = "" @transaction.tags.should == [] end end diff --git a/spec/unit/type/augeas_spec.rb b/spec/unit/type/augeas_spec.rb index e426fbeed..d2e40f0f6 100644 --- a/spec/unit/type/augeas_spec.rb +++ b/spec/unit/type/augeas_spec.rb @@ -1,122 +1,120 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../spec_helper' augeas = Puppet::Type.type(:augeas) describe augeas do - describe "when augeas is present" do - confine "Augeas is unavailable" => Puppet.features.augeas? - + describe "when augeas is present", :if => Puppet.features.augeas? do it "should have a default provider inheriting from Puppet::Provider" do augeas.defaultprovider.ancestors.should be_include(Puppet::Provider) end it "should have a valid provider" do augeas.new(:name => "foo").provider.class.ancestors.should be_include(Puppet::Provider) end end describe "basic structure" do it "should be able to create a instance" do provider_class = Puppet::Type::Augeas.provider(Puppet::Type::Augeas.providers[0]) Puppet::Type::Augeas.expects(:defaultprovider).returns provider_class augeas.new(:name => "bar").should_not be_nil end it "should have an parse_commands feature" do augeas.provider_feature(:parse_commands).should_not be_nil end it "should have an need_to_run? feature" do augeas.provider_feature(:need_to_run?).should_not be_nil end it "should have an execute_changes feature" do augeas.provider_feature(:execute_changes).should_not be_nil end properties = [:returns] params = [:name, :context, :onlyif, :changes, :root, :load_path, :type_check] properties.each do |property| it "should have a #{property} property" do augeas.attrclass(property).ancestors.should be_include(Puppet::Property) end it "should have documentation for its #{property} property" do augeas.attrclass(property).doc.should be_instance_of(String) end end params.each do |param| it "should have a #{param} parameter" do augeas.attrclass(param).ancestors.should be_include(Puppet::Parameter) end it "should have documentation for its #{param} parameter" do augeas.attrclass(param).doc.should be_instance_of(String) end end end describe "default values" do before do provider_class = augeas.provider(augeas.providers[0]) augeas.expects(:defaultprovider).returns provider_class end it "should be blank for context" do augeas.new(:name => :context)[:context].should == "" end it "should be blank for onlyif" do augeas.new(:name => :onlyif)[:onlyif].should == "" end it "should be blank for load_path" do augeas.new(:name => :load_path)[:load_path].should == "" end it "should be / for root" do augeas.new(:name => :root)[:root].should == "/" end it "should be false for type_check" do augeas.new(:name => :type_check)[:type_check].should == :false end end describe "provider interaction" do it "should return 0 if it does not need to run" do provider = stub("provider", :need_to_run? => false) resource = stub('resource', :resource => nil, :provider => provider, :line => nil, :file => nil) changes = augeas.attrclass(:returns).new(:resource => resource) changes.retrieve.should == 0 end it "should return :need_to_run if it needs to run" do provider = stub("provider", :need_to_run? => true) resource = stub('resource', :resource => nil, :provider => provider, :line => nil, :file => nil) changes = augeas.attrclass(:returns).new(:resource => resource) changes.retrieve.should == :need_to_run end end describe "loading specific files" do it "should require lens when incl is used" do lambda { augeas.new(:name => :no_lens, :incl => "/etc/hosts")}.should raise_error(Puppet::Error) end it "should require incl when lens is used" do lambda { augeas.new(:name => :no_incl, :lens => "Hosts.lns") }.should raise_error(Puppet::Error) end it "should set the context when a specific file is used" do fake_provider = stub_everything "fake_provider" augeas.stubs(:defaultprovider).returns fake_provider augeas.new(:name => :no_incl, :lens => "Hosts.lns", :incl => "/etc/hosts")[:context].should == "/files/etc/hosts" end end end diff --git a/spec/unit/type/file/ctime.rb b/spec/unit/type/file/ctime.rb new file mode 100644 index 000000000..6145cbfdc --- /dev/null +++ b/spec/unit/type/file/ctime.rb @@ -0,0 +1,35 @@ +#!/usr/bin/env ruby + +Dir.chdir(File.dirname(__FILE__)) { (s = lambda { |f| File.exist?(f) ? require(f) : Dir.chdir("..") { s.call(f) } }).call("spec/spec_helper.rb") } + +describe Puppet::Type.type(:file).attrclass(:ctime) do + require 'puppet_spec/files' + include PuppetSpec::Files + + before do + @filename = tmpfile('ctime') + @resource = Puppet::Type.type(:file).new({:name => @filename}) + end + + it "should be able to audit the file's ctime" do + File.open(@filename, "w"){ } + + @resource[:audit] = [:ctime] + + # this .to_resource audit behavior is magical :-( + @resource.to_resource[:ctime].should == File.stat(@filename).ctime + end + + it "should return absent if auditing an absent file" do + @resource[:audit] = [:ctime] + + @resource.to_resource[:ctime].should == :absent + end + + it "should prevent the user from trying to set the ctime" do + lambda { + @resource[:ctime] = Time.now.to_s + }.should raise_error(Puppet::Error, /ctime is read-only/) + end + +end diff --git a/spec/unit/type/file/mtime.rb b/spec/unit/type/file/mtime.rb new file mode 100644 index 000000000..043156ceb --- /dev/null +++ b/spec/unit/type/file/mtime.rb @@ -0,0 +1,35 @@ +#!/usr/bin/env ruby + +Dir.chdir(File.dirname(__FILE__)) { (s = lambda { |f| File.exist?(f) ? require(f) : Dir.chdir("..") { s.call(f) } }).call("spec/spec_helper.rb") } + +describe Puppet::Type.type(:file).attrclass(:mtime) do + require 'puppet_spec/files' + include PuppetSpec::Files + + before do + @filename = tmpfile('mtime') + @resource = Puppet::Type.type(:file).new({:name => @filename}) + end + + it "should be able to audit the file's mtime" do + File.open(@filename, "w"){ } + + @resource[:audit] = [:mtime] + + # this .to_resource audit behavior is magical :-( + @resource.to_resource[:mtime].should == File.stat(@filename).mtime + end + + it "should return absent if auditing an absent file" do + @resource[:audit] = [:mtime] + + @resource.to_resource[:mtime].should == :absent + end + + it "should prevent the user from trying to set the mtime" do + lambda { + @resource[:mtime] = Time.now.to_s + }.should raise_error(Puppet::Error, /mtime is read-only/) + end + +end diff --git a/spec/unit/type/file/type.rb b/spec/unit/type/file/type.rb new file mode 100644 index 000000000..e46f0e0b0 --- /dev/null +++ b/spec/unit/type/file/type.rb @@ -0,0 +1,20 @@ +#!/usr/bin/env ruby + +Dir.chdir(File.dirname(__FILE__)) { (s = lambda { |f| File.exist?(f) ? require(f) : Dir.chdir("..") { s.call(f) } }).call("spec/spec_helper.rb") } + +describe Puppet::Type.type(:file).attrclass(:type) do + require 'puppet_spec/files' + include PuppetSpec::Files + + before do + @filename = tmpfile('type') + @resource = Puppet::Type.type(:file).new({:name => @filename}) + end + + it "should prevent the user from trying to set the type" do + lambda { + @resource[:type] = "fifo" + }.should raise_error(Puppet::Error, /type is read-only/) + end + +end diff --git a/spec/unit/type/file_spec.rb b/spec/unit/type/file_spec.rb index 4fcad07e1..22921d85a 100755 --- a/spec/unit/type/file_spec.rb +++ b/spec/unit/type/file_spec.rb @@ -1,1070 +1,1068 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../spec_helper' describe Puppet::Type.type(:file) do before do Puppet.settings.stubs(:use) @real_posix = Puppet.features.posix? Puppet.features.stubs("posix?").returns(true) @path = Tempfile.new("puppetspec") pathname = @path.path @path.close!() @path = pathname @file = Puppet::Type::File.new(:name => @path) @catalog = Puppet::Resource::Catalog.new @file.catalog = @catalog end describe "when determining if recursion is enabled" do it "should default to recursion being disabled" do @file.should_not be_recurse end [true, "true", 10, "inf", "remote"].each do |value| it "should consider #{value} to enable recursion" do @file[:recurse] = value @file.must be_recurse end end [false, "false", 0].each do |value| it "should consider #{value} to disable recursion" do @file[:recurse] = value @file.should_not be_recurse end end end describe "#write" do it "should propagate failures encountered when renaming the temporary file" do File.stubs(:open) File.expects(:rename).raises ArgumentError file = Puppet::Type::File.new(:name => "/my/file", :backup => "puppet") file.stubs(:validate_checksum?).returns(false) property = stub('content_property', :actual_content => "something", :length => "something".length) file.stubs(:property).with(:content).returns(property) lambda { file.write(:content) }.should raise_error(Puppet::Error) end it "should delegate writing to the content property" do filehandle = stub_everything 'fh' File.stubs(:open).yields(filehandle) File.stubs(:rename) property = stub('content_property', :actual_content => "something", :length => "something".length) file = Puppet::Type::File.new(:name => "/my/file", :backup => "puppet") file.stubs(:validate_checksum?).returns(false) file.stubs(:property).with(:content).returns(property) property.expects(:write).with(filehandle) file.write(:content) end describe "when validating the checksum" do before { @file.stubs(:validate_checksum?).returns(true) } it "should fail if the checksum parameter and content checksums do not match" do checksum = stub('checksum_parameter', :sum => 'checksum_b', :sum_file => 'checksum_b') @file.stubs(:parameter).with(:checksum).returns(checksum) property = stub('content_property', :actual_content => "something", :length => "something".length, :write => 'checksum_a') @file.stubs(:property).with(:content).returns(property) lambda { @file.write :NOTUSED }.should raise_error(Puppet::Error) end end describe "when not validating the checksum" do before { @file.stubs(:validate_checksum?).returns(false) } it "should not fail if the checksum property and content checksums do not match" do checksum = stub('checksum_parameter', :sum => 'checksum_b') @file.stubs(:parameter).with(:checksum).returns(checksum) property = stub('content_property', :actual_content => "something", :length => "something".length, :write => 'checksum_a') @file.stubs(:property).with(:content).returns(property) lambda { @file.write :NOTUSED }.should_not raise_error(Puppet::Error) end end end it "should have a method for determining if the file is present" do @file.must respond_to(:exist?) end it "should be considered existent if it can be stat'ed" do @file.expects(:stat).returns mock('stat') @file.must be_exist end it "should be considered nonexistent if it can not be stat'ed" do @file.expects(:stat).returns nil @file.must_not be_exist end it "should have a method for determining if the file should be a normal file" do @file.must respond_to(:should_be_file?) end it "should be a file if :ensure is set to :file" do @file[:ensure] = :file @file.must be_should_be_file end it "should be a file if :ensure is set to :present and the file exists as a normal file" do @file.stubs(:stat).returns(mock('stat', :ftype => "file")) @file[:ensure] = :present @file.must be_should_be_file end it "should not be a file if :ensure is set to something other than :file" do @file[:ensure] = :directory @file.must_not be_should_be_file end it "should not be a file if :ensure is set to :present and the file exists but is not a normal file" do @file.stubs(:stat).returns(mock('stat', :ftype => "directory")) @file[:ensure] = :present @file.must_not be_should_be_file end it "should be a file if :ensure is not set and :content is" do @file[:content] = "foo" @file.must be_should_be_file end it "should be a file if neither :ensure nor :content is set but the file exists as a normal file" do @file.stubs(:stat).returns(mock("stat", :ftype => "file")) @file.must be_should_be_file end it "should not be a file if neither :ensure nor :content is set but the file exists but not as a normal file" do @file.stubs(:stat).returns(mock("stat", :ftype => "directory")) @file.must_not be_should_be_file end describe "when using POSIX filenames" do describe "on POSIX systems" do before do Puppet.features.stubs(:posix?).returns(true) Puppet.features.stubs(:microsoft_windows?).returns(false) end it "should autorequire its parent directory" do file = Puppet::Type::File.new(:path => "/foo/bar") dir = Puppet::Type::File.new(:path => "/foo") @catalog.add_resource file @catalog.add_resource dir reqs = file.autorequire reqs[0].source.must == dir reqs[0].target.must == file end it "should not autorequire its parent dir if its parent dir is itself" do file = Puppet::Type::File.new(:path => "/") @catalog.add_resource file file.autorequire.should be_empty end it "should remove trailing slashes" do file = Puppet::Type::File.new(:path => "/foo/bar/baz/") file[:path].should == "/foo/bar/baz" end it "should remove double slashes" do file = Puppet::Type::File.new(:path => "/foo/bar//baz") file[:path].should == "/foo/bar/baz" end it "should remove trailing double slashes" do file = Puppet::Type::File.new(:path => "/foo/bar/baz//") file[:path].should == "/foo/bar/baz" end it "should leave a single slash alone" do file = Puppet::Type::File.new(:path => "/") file[:path].should == "/" end end describe "on Microsoft Windows systems" do before do Puppet.features.stubs(:posix?).returns(false) Puppet.features.stubs(:microsoft_windows?).returns(true) end it "should refuse to work" do lambda { Puppet::Type::File.new(:path => "/foo/bar") }.should raise_error(Puppet::Error) end end end - describe "when using Microsoft Windows filenames" do - confine "Only works on Microsoft Windows" => Puppet.features.microsoft_windows? + describe "when using Microsoft Windows filenames", :if => Puppet.features.microsoft_windows? do describe "on Microsoft Windows systems" do before do Puppet.features.stubs(:posix?).returns(false) Puppet.features.stubs(:microsoft_windows?).returns(true) end it "should autorequire its parent directory" do file = Puppet::Type::File.new(:path => "X:/foo/bar") dir = Puppet::Type::File.new(:path => "X:/foo") @catalog.add_resource file @catalog.add_resource dir reqs = file.autorequire reqs[0].source.must == dir reqs[0].target.must == file end it "should not autorequire its parent dir if its parent dir is itself" do file = Puppet::Type::File.new(:path => "X:/") @catalog.add_resource file file.autorequire.should be_empty end it "should remove trailing slashes" do file = Puppet::Type::File.new(:path => "X:/foo/bar/baz/") file[:path].should == "X:/foo/bar/baz" end it "should remove double slashes" do file = Puppet::Type::File.new(:path => "X:/foo/bar//baz") file[:path].should == "X:/foo/bar/baz" end it "should remove trailing double slashes" do file = Puppet::Type::File.new(:path => "X:/foo/bar/baz//") file[:path].should == "X:/foo/bar/baz" end it "should leave a drive letter with a slash alone" do file = Puppet::Type::File.new(:path => "X:/") file[:path].should == "X:/" end it "should add a slash to a drive letter" do file = Puppet::Type::File.new(:path => "X:") file[:path].should == "X:/" end end describe "on POSIX systems" do before do Puppet.features.stubs(:posix?).returns(true) Puppet.features.stubs(:microsoft_windows?).returns(false) end it "should refuse to work" do lambda { Puppet::Type::File.new(:path => "X:/foo/bar") }.should raise_error(Puppet::Error) end end end describe "when using UNC filenames" do - describe "on Microsoft Windows systems" do - confine "Only works on Microsoft Windows" => Puppet.features.microsoft_windows? + describe "on Microsoft Windows systems", :if => Puppet.features.microsoft_windows? do before do Puppet.features.stubs(:posix?).returns(false) Puppet.features.stubs(:microsoft_windows?).returns(true) end it "should autorequire its parent directory" do file = Puppet::Type::File.new(:path => "//server/foo/bar") dir = Puppet::Type::File.new(:path => "//server/foo") @catalog.add_resource file @catalog.add_resource dir reqs = file.autorequire reqs[0].source.must == dir reqs[0].target.must == file end it "should not autorequire its parent dir if its parent dir is itself" do file = Puppet::Type::File.new(:path => "//server/foo") @catalog.add_resource file puts file.autorequire file.autorequire.should be_empty end it "should remove trailing slashes" do file = Puppet::Type::File.new(:path => "//server/foo/bar/baz/") file[:path].should == "//server/foo/bar/baz" end it "should remove double slashes" do file = Puppet::Type::File.new(:path => "//server/foo/bar//baz") file[:path].should == "//server/foo/bar/baz" end it "should remove trailing double slashes" do file = Puppet::Type::File.new(:path => "//server/foo/bar/baz//") file[:path].should == "//server/foo/bar/baz" end it "should remove a trailing slash from a sharename" do file = Puppet::Type::File.new(:path => "//server/foo/") file[:path].should == "//server/foo" end it "should not modify a sharename" do file = Puppet::Type::File.new(:path => "//server/foo") file[:path].should == "//server/foo" end end describe "on POSIX systems" do before do Puppet.features.stubs(:posix?).returns(true) Puppet.features.stubs(:microsoft_windows?).returns(false) end it "should refuse to work" do lambda { Puppet::Type::File.new(:path => "X:/foo/bar") }.should raise_error(Puppet::Error) end end end describe "when initializing" do it "should set a desired 'ensure' value if none is set and 'content' is set" do file = Puppet::Type::File.new(:name => "/my/file", :content => "/foo/bar") file[:ensure].should == :file end it "should set a desired 'ensure' value if none is set and 'target' is set" do file = Puppet::Type::File.new(:name => "/my/file", :target => "/foo/bar") file[:ensure].should == :symlink end end describe "when validating attributes" do %w{path checksum backup recurse recurselimit source replace force ignore links purge sourceselect}.each do |attr| it "should have a '#{attr}' parameter" do Puppet::Type.type(:file).attrtype(attr.intern).should == :param end end %w{content target ensure owner group mode type}.each do |attr| it "should have a '#{attr}' property" do Puppet::Type.type(:file).attrtype(attr.intern).should == :property end end it "should have its 'path' attribute set as its namevar" do Puppet::Type.type(:file).key_attributes.should == [:path] end end describe "when managing links" do require 'puppettest/support/assertions' include PuppetTest require 'tempfile' if @real_posix describe "on POSIX systems" do before do @basedir = tempfile Dir.mkdir(@basedir) @file = File.join(@basedir, "file") @link = File.join(@basedir, "link") File.open(@file, "w", 0644) { |f| f.puts "yayness"; f.flush } File.symlink(@file, @link) @resource = Puppet::Type.type(:file).new( - + :path => @link, - + :mode => "755" ) @catalog.add_resource @resource end after do remove_tmp_files end it "should default to managing the link" do @catalog.apply # I convert them to strings so they display correctly if there's an error. ("%o" % (File.stat(@file).mode & 007777)).should == "%o" % 0644 end it "should be able to follow links" do @resource[:links] = :follow @catalog.apply ("%o" % (File.stat(@file).mode & 007777)).should == "%o" % 0755 end end else # @real_posix # should recode tests using expectations instead of using the filesystem end describe "on Microsoft Windows systems" do before do Puppet.features.stubs(:posix?).returns(false) Puppet.features.stubs(:microsoft_windows?).returns(true) end it "should refuse to work with links" end end it "should be able to retrieve a stat instance for the file it is managing" do Puppet::Type.type(:file).new(:path => "/foo/bar", :source => "/bar/foo").should respond_to(:stat) end describe "when stat'ing its file" do before do @resource = Puppet::Type.type(:file).new(:path => "/foo/bar") @resource[:links] = :manage # so we always use :lstat end it "should use :stat if it is following links" do @resource[:links] = :follow File.expects(:stat) @resource.stat end it "should use :lstat if is it not following links" do @resource[:links] = :manage File.expects(:lstat) @resource.stat end it "should stat the path of the file" do File.expects(:lstat).with("/foo/bar") @resource.stat end # This only happens in testing. it "should return nil if the stat does not exist" do File.expects(:lstat).returns nil @resource.stat.should be_nil end it "should return nil if the file does not exist" do File.expects(:lstat).raises(Errno::ENOENT) @resource.stat.should be_nil end it "should return nil if the file cannot be stat'ed" do File.expects(:lstat).raises(Errno::EACCES) @resource.stat.should be_nil end it "should return the stat instance" do File.expects(:lstat).returns "mystat" @resource.stat.should == "mystat" end it "should cache the stat instance if it has a catalog and is applying" do stat = mock 'stat' File.expects(:lstat).returns stat catalog = Puppet::Resource::Catalog.new @resource.catalog = catalog catalog.stubs(:applying?).returns true @resource.stat.should equal(@resource.stat) end end describe "when flushing" do it "should flush all properties that respond to :flush" do @resource = Puppet::Type.type(:file).new(:path => "/foo/bar", :source => "/bar/foo") @resource.parameter(:source).expects(:flush) @resource.flush end it "should reset its stat reference" do @resource = Puppet::Type.type(:file).new(:path => "/foo/bar") File.expects(:lstat).times(2).returns("stat1").then.returns("stat2") @resource.stat.should == "stat1" @resource.flush @resource.stat.should == "stat2" end end it "should have a method for performing recursion" do @file.must respond_to(:perform_recursion) end describe "when executing a recursive search" do it "should use Metadata to do its recursion" do Puppet::FileServing::Metadata.expects(:search) @file.perform_recursion(@file[:path]) end it "should use the provided path as the key to the search" do Puppet::FileServing::Metadata.expects(:search).with { |key, options| key == "/foo" } @file.perform_recursion("/foo") end it "should return the results of the metadata search" do Puppet::FileServing::Metadata.expects(:search).returns "foobar" @file.perform_recursion(@file[:path]).should == "foobar" end it "should pass its recursion value to the search" do @file[:recurse] = true Puppet::FileServing::Metadata.expects(:search).with { |key, options| options[:recurse] == true } @file.perform_recursion(@file[:path]) end it "should pass true if recursion is remote" do @file[:recurse] = :remote Puppet::FileServing::Metadata.expects(:search).with { |key, options| options[:recurse] == true } @file.perform_recursion(@file[:path]) end it "should pass its recursion limit value to the search" do @file[:recurselimit] = 10 Puppet::FileServing::Metadata.expects(:search).with { |key, options| options[:recurselimit] == 10 } @file.perform_recursion(@file[:path]) end it "should configure the search to ignore or manage links" do @file[:links] = :manage Puppet::FileServing::Metadata.expects(:search).with { |key, options| options[:links] == :manage } @file.perform_recursion(@file[:path]) end it "should pass its 'ignore' setting to the search if it has one" do @file[:ignore] = %w{.svn CVS} Puppet::FileServing::Metadata.expects(:search).with { |key, options| options[:ignore] == %w{.svn CVS} } @file.perform_recursion(@file[:path]) end end it "should have a method for performing local recursion" do @file.must respond_to(:recurse_local) end describe "when doing local recursion" do before do @metadata = stub 'metadata', :relative_path => "my/file" end it "should pass its path to the :perform_recursion method" do @file.expects(:perform_recursion).with(@file[:path]).returns [@metadata] @file.stubs(:newchild) @file.recurse_local end it "should return an empty hash if the recursion returns nothing" do @file.expects(:perform_recursion).returns nil @file.recurse_local.should == {} end it "should create a new child resource with each generated metadata instance's relative path" do @file.expects(:perform_recursion).returns [@metadata] @file.expects(:newchild).with(@metadata.relative_path).returns "fiebar" @file.recurse_local end it "should not create a new child resource for the '.' directory" do @metadata.stubs(:relative_path).returns "." @file.expects(:perform_recursion).returns [@metadata] @file.expects(:newchild).never @file.recurse_local end it "should return a hash of the created resources with the relative paths as the hash keys" do @file.expects(:perform_recursion).returns [@metadata] @file.expects(:newchild).with("my/file").returns "fiebar" @file.recurse_local.should == {"my/file" => "fiebar"} end it "should set checksum_type to none if this file checksum is none" do @file[:checksum] = :none Puppet::FileServing::Metadata.expects(:search).with { |path,params| params[:checksum_type] == :none }.returns [@metadata] @file.expects(:newchild).with("my/file").returns "fiebar" @file.recurse_local end end it "should have a method for performing link recursion" do @file.must respond_to(:recurse_link) end describe "when doing link recursion" do before do @first = stub 'first', :relative_path => "first", :full_path => "/my/first", :ftype => "directory" @second = stub 'second', :relative_path => "second", :full_path => "/my/second", :ftype => "file" @resource = stub 'file', :[]= => nil end it "should pass its target to the :perform_recursion method" do @file[:target] = "mylinks" @file.expects(:perform_recursion).with("mylinks").returns [@first] @file.stubs(:newchild).returns @resource @file.recurse_link({}) end it "should ignore the recursively-found '.' file and configure the top-level file to create a directory" do @first.stubs(:relative_path).returns "." @file[:target] = "mylinks" @file.expects(:perform_recursion).with("mylinks").returns [@first] @file.stubs(:newchild).never @file.expects(:[]=).with(:ensure, :directory) @file.recurse_link({}) end it "should create a new child resource for each generated metadata instance's relative path that doesn't already exist in the children hash" do @file.expects(:perform_recursion).returns [@first, @second] @file.expects(:newchild).with(@first.relative_path).returns @resource @file.recurse_link("second" => @resource) end it "should not create a new child resource for paths that already exist in the children hash" do @file.expects(:perform_recursion).returns [@first] @file.expects(:newchild).never @file.recurse_link("first" => @resource) end it "should set the target to the full path of discovered file and set :ensure to :link if the file is not a directory" do file = stub 'file' file.expects(:[]=).with(:target, "/my/second") file.expects(:[]=).with(:ensure, :link) @file.stubs(:perform_recursion).returns [@first, @second] @file.recurse_link("first" => @resource, "second" => file) end it "should :ensure to :directory if the file is a directory" do file = stub 'file' file.expects(:[]=).with(:ensure, :directory) @file.stubs(:perform_recursion).returns [@first, @second] @file.recurse_link("first" => file, "second" => @resource) end it "should return a hash with both created and existing resources with the relative paths as the hash keys" do file = stub 'file', :[]= => nil @file.expects(:perform_recursion).returns [@first, @second] @file.stubs(:newchild).returns file @file.recurse_link("second" => @resource).should == {"second" => @resource, "first" => file} end end it "should have a method for performing remote recursion" do @file.must respond_to(:recurse_remote) end describe "when doing remote recursion" do before do @file[:source] = "puppet://foo/bar" @first = Puppet::FileServing::Metadata.new("/my", :relative_path => "first") @second = Puppet::FileServing::Metadata.new("/my", :relative_path => "second") @first.stubs(:ftype).returns "directory" @second.stubs(:ftype).returns "directory" @parameter = stub 'property', :metadata= => nil @resource = stub 'file', :[]= => nil, :parameter => @parameter end it "should pass its source to the :perform_recursion method" do data = Puppet::FileServing::Metadata.new("/whatever", :relative_path => "foobar") @file.expects(:perform_recursion).with("puppet://foo/bar").returns [data] @file.stubs(:newchild).returns @resource @file.recurse_remote({}) end it "should not recurse when the remote file is not a directory" do data = Puppet::FileServing::Metadata.new("/whatever", :relative_path => ".") data.stubs(:ftype).returns "file" @file.expects(:perform_recursion).with("puppet://foo/bar").returns [data] @file.expects(:newchild).never @file.recurse_remote({}) end it "should set the source of each returned file to the searched-for URI plus the found relative path" do @first.expects(:source=).with File.join("puppet://foo/bar", @first.relative_path) @file.expects(:perform_recursion).returns [@first] @file.stubs(:newchild).returns @resource @file.recurse_remote({}) end it "should create a new resource for any relative file paths that do not already have a resource" do @file.stubs(:perform_recursion).returns [@first] @file.expects(:newchild).with("first").returns @resource @file.recurse_remote({}).should == {"first" => @resource} end it "should not create a new resource for any relative file paths that do already have a resource" do @file.stubs(:perform_recursion).returns [@first] @file.expects(:newchild).never @file.recurse_remote("first" => @resource) end it "should set the source of each resource to the source of the metadata" do @file.stubs(:perform_recursion).returns [@first] @resource.stubs(:[]=) @resource.expects(:[]=).with(:source, File.join("puppet://foo/bar", @first.relative_path)) @file.recurse_remote("first" => @resource) end # LAK:FIXME This is a bug, but I can't think of a fix for it. Fortunately it's already # filed, and when it's fixed, we'll just fix the whole flow. it "should set the checksum type to :md5 if the remote file is a file" do @first.stubs(:ftype).returns "file" @file.stubs(:perform_recursion).returns [@first] @resource.stubs(:[]=) @resource.expects(:[]=).with(:checksum, :md5) @file.recurse_remote("first" => @resource) end it "should store the metadata in the source property for each resource so the source does not have to requery the metadata" do @file.stubs(:perform_recursion).returns [@first] @resource.expects(:parameter).with(:source).returns @parameter @parameter.expects(:metadata=).with(@first) @file.recurse_remote("first" => @resource) end it "should not create a new resource for the '.' file" do @first.stubs(:relative_path).returns "." @file.stubs(:perform_recursion).returns [@first] @file.expects(:newchild).never @file.recurse_remote({}) end it "should store the metadata in the main file's source property if the relative path is '.'" do @first.stubs(:relative_path).returns "." @file.stubs(:perform_recursion).returns [@first] @file.parameter(:source).expects(:metadata=).with @first @file.recurse_remote("first" => @resource) end describe "and multiple sources are provided" do describe "and :sourceselect is set to :first" do it "should create file instances for the results for the first source to return any values" do data = Puppet::FileServing::Metadata.new("/whatever", :relative_path => "foobar") @file[:source] = %w{/one /two /three /four} @file.expects(:perform_recursion).with("/one").returns nil @file.expects(:perform_recursion).with("/two").returns [] @file.expects(:perform_recursion).with("/three").returns [data] @file.expects(:perform_recursion).with("/four").never @file.expects(:newchild).with("foobar").returns @resource @file.recurse_remote({}) end end describe "and :sourceselect is set to :all" do before do @file[:sourceselect] = :all end it "should return every found file that is not in a previous source" do klass = Puppet::FileServing::Metadata @file[:source] = %w{/one /two /three /four} @file.stubs(:newchild).returns @resource one = [klass.new("/one", :relative_path => "a")] @file.expects(:perform_recursion).with("/one").returns one @file.expects(:newchild).with("a").returns @resource two = [klass.new("/two", :relative_path => "a"), klass.new("/two", :relative_path => "b")] @file.expects(:perform_recursion).with("/two").returns two @file.expects(:newchild).with("b").returns @resource three = [klass.new("/three", :relative_path => "a"), klass.new("/three", :relative_path => "c")] @file.expects(:perform_recursion).with("/three").returns three @file.expects(:newchild).with("c").returns @resource @file.expects(:perform_recursion).with("/four").returns [] @file.recurse_remote({}) end end end end describe "when returning resources with :eval_generate" do before do @graph = stub 'graph', :add_edge => nil @catalog.stubs(:relationship_graph).returns @graph @file.catalog = @catalog @file[:recurse] = true end it "should recurse if recursion is enabled" do resource = stub('resource', :[] => "resource") @file.expects(:recurse?).returns true @file.expects(:recurse).returns [resource] @file.eval_generate.should == [resource] end it "should not recurse if recursion is disabled" do @file.expects(:recurse?).returns false @file.expects(:recurse).never @file.eval_generate.should == [] end it "should return each resource found through recursion" do foo = stub 'foo', :[] => "/foo" bar = stub 'bar', :[] => "/bar" bar2 = stub 'bar2', :[] => "/bar" @file.expects(:recurse).returns [foo, bar] @file.eval_generate.should == [foo, bar] end end describe "when recursing" do before do @file[:recurse] = true @metadata = Puppet::FileServing::Metadata end describe "and a source is set" do before { @file[:source] = "/my/source" } it "should pass the already-discovered resources to recurse_remote" do @file.stubs(:recurse_local).returns(:foo => "bar") @file.expects(:recurse_remote).with(:foo => "bar").returns [] @file.recurse end end describe "and a target is set" do before { @file[:target] = "/link/target" } it "should use recurse_link" do @file.stubs(:recurse_local).returns(:foo => "bar") @file.expects(:recurse_link).with(:foo => "bar").returns [] @file.recurse end end it "should use recurse_local if recurse is not remote" do @file.expects(:recurse_local).returns({}) @file.recurse end it "should not use recurse_local if recurse remote" do @file[:recurse] = :remote @file.expects(:recurse_local).never @file.recurse end it "should return the generated resources as an array sorted by file path" do one = stub 'one', :[] => "/one" two = stub 'two', :[] => "/one/two" three = stub 'three', :[] => "/three" @file.expects(:recurse_local).returns(:one => one, :two => two, :three => three) @file.recurse.should == [one, two, three] end describe "and purging is enabled" do before do @file[:purge] = true end it "should configure each file to be removed" do local = stub 'local' local.stubs(:[]).with(:source).returns nil # Thus, a local file local.stubs(:[]).with(:path).returns "foo" @file.expects(:recurse_local).returns("local" => local) local.expects(:[]=).with(:ensure, :absent) @file.recurse end it "should not remove files that exist in the remote repository" do @file["source"] = "/my/file" @file.expects(:recurse_local).returns({}) remote = stub 'remote' remote.stubs(:[]).with(:source).returns "/whatever" # Thus, a remote file remote.stubs(:[]).with(:path).returns "foo" @file.expects(:recurse_remote).with { |hash| hash["remote"] = remote } remote.expects(:[]=).with(:ensure, :absent).never @file.recurse end end describe "and making a new child resource" do it "should not copy the parent resource's parent" do Puppet::Type.type(:file).expects(:new).with { |options| ! options.include?(:parent) } @file.newchild("my/path") end {:recurse => true, :target => "/foo/bar", :ensure => :present, :alias => "yay", :source => "/foo/bar"}.each do |param, value| it "should not pass on #{param} to the sub resource" do @file = Puppet::Type::File.new(:name => @path, param => value, :catalog => @catalog) @file.class.expects(:new).with { |params| params[param].nil? } @file.newchild("sub/file") end end it "should copy all of the parent resource's 'should' values that were set at initialization" do file = @file.class.new(:path => "/foo/bar", :owner => "root", :group => "wheel") @catalog.add_resource(file) file.class.expects(:new).with { |options| options[:owner] == "root" and options[:group] == "wheel" } file.newchild("my/path") end it "should not copy default values to the new child" do @file.class.expects(:new).with { |params| params[:backup].nil? } @file.newchild("my/path") end it "should not copy values to the child which were set by the source" do @file[:source] = "/foo/bar" metadata = stub 'metadata', :owner => "root", :group => "root", :mode => 0755, :ftype => "file", :checksum => "{md5}whatever" @file.parameter(:source).stubs(:metadata).returns metadata @file.parameter(:source).copy_source_values @file.class.expects(:new).with { |params| params[:group].nil? } @file.newchild("my/path") end end end describe "when setting the backup" do it "should default to 'puppet'" do Puppet::Type::File.new(:name => "/my/file")[:backup].should == "puppet" end it "should allow setting backup to 'false'" do (!Puppet::Type::File.new(:name => "/my/file", :backup => false)[:backup]).should be_true end it "should set the backup to '.puppet-bak' if it is set to true" do Puppet::Type::File.new(:name => "/my/file", :backup => true)[:backup].should == ".puppet-bak" end it "should support any other backup extension" do Puppet::Type::File.new(:name => "/my/file", :backup => ".bak")[:backup].should == ".bak" end it "should set the filebucket when backup is set to a string matching the name of a filebucket in the catalog" do catalog = Puppet::Resource::Catalog.new bucket_resource = Puppet::Type.type(:filebucket).new :name => "foo", :path => "/my/file/bucket" catalog.add_resource bucket_resource file = Puppet::Type::File.new(:name => "/my/file") catalog.add_resource file file[:backup] = "foo" file.bucket.should == bucket_resource.bucket end it "should find filebuckets added to the catalog after the file resource was created" do catalog = Puppet::Resource::Catalog.new file = Puppet::Type::File.new(:name => "/my/file", :backup => "foo") catalog.add_resource file bucket_resource = Puppet::Type.type(:filebucket).new :name => "foo", :path => "/my/file/bucket" catalog.add_resource bucket_resource file.bucket.should == bucket_resource.bucket end it "should have a nil filebucket if backup is false" do catalog = Puppet::Resource::Catalog.new bucket_resource = Puppet::Type.type(:filebucket).new :name => "foo", :path => "/my/file/bucket" catalog.add_resource bucket_resource file = Puppet::Type::File.new(:name => "/my/file", :backup => false) catalog.add_resource file file.bucket.should be_nil end it "should have a nil filebucket if backup is set to a string starting with '.'" do catalog = Puppet::Resource::Catalog.new bucket_resource = Puppet::Type.type(:filebucket).new :name => "foo", :path => "/my/file/bucket" catalog.add_resource bucket_resource file = Puppet::Type::File.new(:name => "/my/file", :backup => ".foo") catalog.add_resource file file.bucket.should be_nil end it "should fail if there's no catalog and backup is not false" do file = Puppet::Type::File.new(:name => "/my/file", :backup => "foo") lambda { file.bucket }.should raise_error(Puppet::Error) end it "should fail if a non-existent catalog is specified" do file = Puppet::Type::File.new(:name => "/my/file", :backup => "foo") catalog = Puppet::Resource::Catalog.new catalog.add_resource file lambda { file.bucket }.should raise_error(Puppet::Error) end it "should be able to use the default filebucket without a catalog" do file = Puppet::Type::File.new(:name => "/my/file", :backup => "puppet") file.bucket.should be_instance_of(Puppet::FileBucket::Dipper) end it "should look up the filebucket during finish()" do file = Puppet::Type::File.new(:name => "/my/file", :backup => ".foo") file.expects(:bucket) file.finish end end describe "when retrieving the current file state" do it "should copy the source values if the 'source' parameter is set" do file = Puppet::Type::File.new(:name => "/my/file", :source => "/foo/bar") file.parameter(:source).expects(:copy_source_values) file.retrieve end end describe ".title_patterns" do before do @type_class = Puppet::Type.type(:file) end - + it "should have a regexp that captures the entire string, except for a terminating slash" do patterns = @type_class.title_patterns string = "abc/\n\tdef/" patterns[0][0] =~ string $1.should == "abc/\n\tdef" end end end diff --git a/spec/unit/type/package_spec.rb b/spec/unit/type/package_spec.rb index b0c5d2252..662fe4798 100755 --- a/spec/unit/type/package_spec.rb +++ b/spec/unit/type/package_spec.rb @@ -1,240 +1,240 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../spec_helper' describe Puppet::Type.type(:package) do before do Puppet::Util::Storage.stubs(:store) end it "should have an :installable feature that requires the :install method" do Puppet::Type.type(:package).provider_feature(:installable).methods.should == [:install] end it "should have an :uninstallable feature that requires the :uninstall method" do Puppet::Type.type(:package).provider_feature(:uninstallable).methods.should == [:uninstall] end it "should have an :upgradeable feature that requires :update and :latest methods" do Puppet::Type.type(:package).provider_feature(:upgradeable).methods.should == [:update, :latest] end it "should have a :purgeable feature that requires the :purge latest method" do Puppet::Type.type(:package).provider_feature(:purgeable).methods.should == [:purge] end it "should have a :versionable feature" do Puppet::Type.type(:package).provider_feature(:versionable).should_not be_nil end it "should default to being installed" do pkg = Puppet::Type.type(:package).new(:name => "yay") pkg.should(:ensure).should == :present end describe "when validating attributes" do [:name, :source, :instance, :status, :adminfile, :responsefile, :configfiles, :category, :platform, :root, :vendor, :description, :allowcdrom].each do |param| it "should have a #{param} parameter" do Puppet::Type.type(:package).attrtype(param).should == :param end end it "should have an ensure property" do Puppet::Type.type(:package).attrtype(:ensure).should == :property end end describe "when validating attribute values" do before do @provider = stub 'provider', :class => Puppet::Type.type(:package).defaultprovider, :clear => nil Puppet::Type.type(:package).defaultprovider.expects(:new).returns(@provider) end it "should support :present as a value to :ensure" do Puppet::Type.type(:package).new(:name => "yay", :ensure => :present) end it "should alias :installed to :present as a value to :ensure" do pkg = Puppet::Type.type(:package).new(:name => "yay", :ensure => :installed) pkg.should(:ensure).should == :present end it "should support :absent as a value to :ensure" do Puppet::Type.type(:package).new(:name => "yay", :ensure => :absent) end it "should support :purged as a value to :ensure if the provider has the :purgeable feature" do @provider.expects(:satisfies?).with([:purgeable]).returns(true) Puppet::Type.type(:package).new(:name => "yay", :ensure => :purged) end it "should not support :purged as a value to :ensure if the provider does not have the :purgeable feature" do @provider.expects(:satisfies?).with([:purgeable]).returns(false) proc { Puppet::Type.type(:package).new(:name => "yay", :ensure => :purged) }.should raise_error(Puppet::Error) end it "should support :latest as a value to :ensure if the provider has the :upgradeable feature" do @provider.expects(:satisfies?).with([:upgradeable]).returns(true) Puppet::Type.type(:package).new(:name => "yay", :ensure => :latest) end it "should not support :latest as a value to :ensure if the provider does not have the :upgradeable feature" do @provider.expects(:satisfies?).with([:upgradeable]).returns(false) proc { Puppet::Type.type(:package).new(:name => "yay", :ensure => :latest) }.should raise_error(Puppet::Error) end it "should support version numbers as a value to :ensure if the provider has the :versionable feature" do @provider.expects(:satisfies?).with([:versionable]).returns(true) Puppet::Type.type(:package).new(:name => "yay", :ensure => "1.0") end it "should not support version numbers as a value to :ensure if the provider does not have the :versionable feature" do @provider.expects(:satisfies?).with([:versionable]).returns(false) proc { Puppet::Type.type(:package).new(:name => "yay", :ensure => "1.0") }.should raise_error(Puppet::Error) end it "should accept any string as an argument to :source" do proc { Puppet::Type.type(:package).new(:name => "yay", :source => "stuff") }.should_not raise_error(Puppet::Error) end end module PackageEvaluationTesting def setprops(properties) @provider.stubs(:properties).returns(properties) end end describe Puppet::Type.type(:package) do before :each do @provider = stub 'provider', :class => Puppet::Type.type(:package).defaultprovider, :clear => nil, :satisfies? => true, :name => :mock Puppet::Type.type(:package).defaultprovider.stubs(:new).returns(@provider) Puppet::Type.type(:package).defaultprovider.stubs(:instances).returns([]) @package = Puppet::Type.type(:package).new(:name => "yay") @catalog = Puppet::Resource::Catalog.new @catalog.add_resource(@package) end describe Puppet::Type.type(:package), "when it should be purged" do include PackageEvaluationTesting before { @package[:ensure] = :purged } it "should do nothing if it is :purged" do - @provider.expects(:properties).returns(:ensure => :purged) + @provider.expects(:properties).returns(:ensure => :purged).at_least_once @catalog.apply end [:absent, :installed, :present, :latest].each do |state| it "should purge if it is #{state.to_s}" do @provider.stubs(:properties).returns(:ensure => state) @provider.expects(:purge) @catalog.apply end end end describe Puppet::Type.type(:package), "when it should be absent" do include PackageEvaluationTesting before { @package[:ensure] = :absent } [:purged, :absent].each do |state| it "should do nothing if it is #{state.to_s}" do - @provider.expects(:properties).returns(:ensure => state) + @provider.expects(:properties).returns(:ensure => state).at_least_once @catalog.apply end end [:installed, :present, :latest].each do |state| it "should uninstall if it is #{state.to_s}" do @provider.stubs(:properties).returns(:ensure => state) @provider.expects(:uninstall) @catalog.apply end end end describe Puppet::Type.type(:package), "when it should be present" do include PackageEvaluationTesting before { @package[:ensure] = :present } [:present, :latest, "1.0"].each do |state| it "should do nothing if it is #{state.to_s}" do - @provider.expects(:properties).returns(:ensure => state) + @provider.expects(:properties).returns(:ensure => state).at_least_once @catalog.apply end end [:purged, :absent].each do |state| it "should install if it is #{state.to_s}" do @provider.stubs(:properties).returns(:ensure => state) @provider.expects(:install) @catalog.apply end end end describe Puppet::Type.type(:package), "when it should be latest" do include PackageEvaluationTesting before { @package[:ensure] = :latest } [:purged, :absent].each do |state| it "should upgrade if it is #{state.to_s}" do @provider.stubs(:properties).returns(:ensure => state) @provider.expects(:update) @catalog.apply end end it "should upgrade if the current version is not equal to the latest version" do @provider.stubs(:properties).returns(:ensure => "1.0") @provider.stubs(:latest).returns("2.0") @provider.expects(:update) @catalog.apply end it "should do nothing if it is equal to the latest version" do @provider.stubs(:properties).returns(:ensure => "1.0") @provider.stubs(:latest).returns("1.0") @provider.expects(:update).never @catalog.apply end it "should do nothing if the provider returns :present as the latest version" do @provider.stubs(:properties).returns(:ensure => :present) @provider.stubs(:latest).returns("1.0") @provider.expects(:update).never @catalog.apply end end describe Puppet::Type.type(:package), "when it should be a specific version" do include PackageEvaluationTesting before { @package[:ensure] = "1.0" } [:purged, :absent].each do |state| it "should install if it is #{state.to_s}" do @provider.stubs(:properties).returns(:ensure => state) @provider.expects(:install) @catalog.apply end end it "should do nothing if the current version is equal to the desired version" do @provider.stubs(:properties).returns(:ensure => "1.0") @provider.expects(:install).never @catalog.apply end it "should install if the current version is not equal to the specified version" do @provider.stubs(:properties).returns(:ensure => "2.0") @provider.expects(:install) @catalog.apply end end end end diff --git a/spec/unit/type_spec.rb b/spec/unit/type_spec.rb index 48b00ec4a..b7a08977e 100755 --- a/spec/unit/type_spec.rb +++ b/spec/unit/type_spec.rb @@ -1,573 +1,573 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../spec_helper' describe Puppet::Type do it "should include the Cacher module" do Puppet::Type.ancestors.should be_include(Puppet::Util::Cacher) end it "should consider a parameter to be valid if it is a valid parameter" do Puppet::Type.type(:mount).should be_valid_parameter(:path) end it "should consider a parameter to be valid if it is a valid property" do Puppet::Type.type(:mount).should be_valid_parameter(:fstype) end it "should consider a parameter to be valid if it is a valid metaparam" do Puppet::Type.type(:mount).should be_valid_parameter(:noop) end it "should use its catalog as its expirer" do catalog = Puppet::Resource::Catalog.new resource = Puppet::Type.type(:mount).new(:name => "foo", :fstype => "bar", :pass => 1, :ensure => :present) resource.catalog = catalog resource.expirer.should equal(catalog) end it "should do nothing when asked to expire when it has no catalog" do resource = Puppet::Type.type(:mount).new(:name => "foo", :fstype => "bar", :pass => 1, :ensure => :present) lambda { resource.expire }.should_not raise_error end it "should be able to retrieve a property by name" do resource = Puppet::Type.type(:mount).new(:name => "foo", :fstype => "bar", :pass => 1, :ensure => :present) resource.property(:fstype).must be_instance_of(Puppet::Type.type(:mount).attrclass(:fstype)) end it "should be able to retrieve a parameter by name" do resource = Puppet::Type.type(:mount).new(:name => "foo", :fstype => "bar", :pass => 1, :ensure => :present) resource.parameter(:name).must be_instance_of(Puppet::Type.type(:mount).attrclass(:name)) end it "should be able to retrieve a property by name using the :parameter method" do resource = Puppet::Type.type(:mount).new(:name => "foo", :fstype => "bar", :pass => 1, :ensure => :present) resource.parameter(:fstype).must be_instance_of(Puppet::Type.type(:mount).attrclass(:fstype)) end it "should be able to retrieve all set properties" do resource = Puppet::Type.type(:mount).new(:name => "foo", :fstype => "bar", :pass => 1, :ensure => :present) props = resource.properties props.should_not be_include(nil) [:fstype, :ensure, :pass].each do |name| props.should be_include(resource.parameter(name)) end end it "should have a method for setting default values for resources" do Puppet::Type.type(:mount).new(:name => "foo").should respond_to(:set_default) end it "should do nothing for attributes that have no defaults and no specified value" do Puppet::Type.type(:mount).new(:name => "foo").parameter(:noop).should be_nil end it "should have a method for adding tags" do Puppet::Type.type(:mount).new(:name => "foo").should respond_to(:tags) end it "should use the tagging module" do Puppet::Type.type(:mount).ancestors.should be_include(Puppet::Util::Tagging) end it "should delegate to the tagging module when tags are added" do resource = Puppet::Type.type(:mount).new(:name => "foo") resource.stubs(:tag).with(:mount) resource.expects(:tag).with(:tag1, :tag2) resource.tags = [:tag1,:tag2] end it "should add the current type as tag" do resource = Puppet::Type.type(:mount).new(:name => "foo") resource.stubs(:tag) resource.expects(:tag).with(:mount) resource.tags = [:tag1,:tag2] end it "should have a method to know if the resource is exported" do Puppet::Type.type(:mount).new(:name => "foo").should respond_to(:exported?) end it "should have a method to know if the resource is virtual" do Puppet::Type.type(:mount).new(:name => "foo").should respond_to(:virtual?) end it "should consider its version to be its catalog version" do resource = Puppet::Type.type(:mount).new(:name => "foo") catalog = Puppet::Resource::Catalog.new catalog.version = 50 catalog.add_resource resource resource.version.should == 50 end it "should consider its version to be zero if it has no catalog" do Puppet::Type.type(:mount).new(:name => "foo").version.should == 0 end it "should provide source_descriptors" do resource = Puppet::Type.type(:mount).new(:name => "foo") catalog = Puppet::Resource::Catalog.new catalog.version = 50 catalog.add_resource resource - resource.source_descriptors.should == {:version=>50, :tags=>["mount", "foo"], :path=>"/Mount[foo]"} + resource.source_descriptors.should == {:tags=>["mount", "foo"], :path=>"/Mount[foo]"} end it "should consider its type to be the name of its class" do Puppet::Type.type(:mount).new(:name => "foo").type.should == :mount end it "should use any provided noop value" do Puppet::Type.type(:mount).new(:name => "foo", :noop => true).must be_noop end it "should use the global noop value if none is provided" do Puppet[:noop] = true Puppet::Type.type(:mount).new(:name => "foo").must be_noop end it "should not be noop if in a non-host_config catalog" do resource = Puppet::Type.type(:mount).new(:name => "foo") catalog = Puppet::Resource::Catalog.new catalog.add_resource resource resource.should_not be_noop end describe "when creating an event" do before do @resource = Puppet::Type.type(:mount).new :name => "foo" end it "should have the resource's reference as the resource" do @resource.event.resource.should == "Mount[foo]" end it "should have the resource's log level as the default log level" do @resource[:loglevel] = :warning @resource.event.default_log_level.should == :warning end - {:file => "/my/file", :line => 50, :tags => %{foo bar}, :version => 50}.each do |attr, value| + {:file => "/my/file", :line => 50, :tags => %{foo bar}}.each do |attr, value| it "should set the #{attr}" do @resource.stubs(attr).returns value @resource.event.send(attr).should == value end end it "should allow specification of event attributes" do @resource.event(:status => "noop").status.should == "noop" end end describe "when choosing a default provider" do it "should choose the provider with the highest specificity" do # Make a fake type type = Puppet::Type.newtype(:defaultprovidertest) do newparam(:name) do end end basic = type.provide(:basic) {} greater = type.provide(:greater) {} basic.stubs(:specificity).returns 1 greater.stubs(:specificity).returns 2 type.defaultprovider.should equal(greater) end end describe "when initializing" do describe "and passed a TransObject" do it "should fail" do trans = Puppet::TransObject.new("/foo", :mount) lambda { Puppet::Type.type(:mount).new(trans) }.should raise_error(Puppet::DevError) end end describe "and passed a Puppet::Resource instance" do it "should set its title to the title of the resource if the resource type is equal to the current type" do resource = Puppet::Resource.new(:mount, "/foo", :parameters => {:name => "/other"}) Puppet::Type.type(:mount).new(resource).title.should == "/foo" end it "should set its title to the resource reference if the resource type is not equal to the current type" do resource = Puppet::Resource.new(:user, "foo") Puppet::Type.type(:mount).new(resource).title.should == "User[foo]" end [:line, :file, :catalog, :exported, :virtual].each do |param| it "should copy '#{param}' from the resource if present" do resource = Puppet::Resource.new(:mount, "/foo") resource.send(param.to_s + "=", "foo") resource.send(param.to_s + "=", "foo") Puppet::Type.type(:mount).new(resource).send(param).should == "foo" end end it "should copy any tags from the resource" do resource = Puppet::Resource.new(:mount, "/foo") resource.tag "one", "two" tags = Puppet::Type.type(:mount).new(resource).tags tags.should be_include("one") tags.should be_include("two") end it "should copy the resource's parameters as its own" do resource = Puppet::Resource.new(:mount, "/foo", :parameters => {:atboot => true, :fstype => "boo"}) params = Puppet::Type.type(:mount).new(resource).to_hash params[:fstype].should == "boo" params[:atboot].should == true end end describe "and passed a Hash" do it "should extract the title from the hash" do Puppet::Type.type(:mount).new(:title => "/yay").title.should == "/yay" end it "should work when hash keys are provided as strings" do Puppet::Type.type(:mount).new("title" => "/yay").title.should == "/yay" end it "should work when hash keys are provided as symbols" do Puppet::Type.type(:mount).new(:title => "/yay").title.should == "/yay" end it "should use the name from the hash as the title if no explicit title is provided" do Puppet::Type.type(:mount).new(:name => "/yay").title.should == "/yay" end it "should use the Resource Type's namevar to determine how to find the name in the hash" do Puppet::Type.type(:file).new(:path => "/yay").title.should == "/yay" end [:catalog].each do |param| it "should extract '#{param}' from the hash if present" do Puppet::Type.type(:mount).new(:name => "/yay", param => "foo").send(param).should == "foo" end end it "should use any remaining hash keys as its parameters" do resource = Puppet::Type.type(:mount).new(:title => "/foo", :catalog => "foo", :atboot => true, :fstype => "boo") resource[:fstype].must == "boo" resource[:atboot].must == true end end it "should fail if any invalid attributes have been provided" do lambda { Puppet::Type.type(:mount).new(:title => "/foo", :nosuchattr => "whatever") }.should raise_error(Puppet::Error) end it "should set its name to the resource's title if the resource does not have a :name or namevar parameter set" do resource = Puppet::Resource.new(:mount, "/foo") Puppet::Type.type(:mount).new(resource).name.should == "/foo" end it "should fail if no title, name, or namevar are provided" do lambda { Puppet::Type.type(:file).new(:atboot => true) }.should raise_error(Puppet::Error) end it "should set the attributes in the order returned by the class's :allattrs method" do Puppet::Type.type(:mount).stubs(:allattrs).returns([:name, :atboot, :noop]) resource = Puppet::Resource.new(:mount, "/foo", :parameters => {:name => "myname", :atboot => "myboot", :noop => "whatever"}) set = [] Puppet::Type.type(:mount).any_instance.stubs(:newattr).with do |param, hash| set << param true end.returns(stub_everything("a property")) Puppet::Type.type(:mount).new(resource) set[-1].should == :noop set[-2].should == :atboot end it "should always set the name and then default provider before anything else" do Puppet::Type.type(:mount).stubs(:allattrs).returns([:provider, :name, :atboot]) resource = Puppet::Resource.new(:mount, "/foo", :parameters => {:name => "myname", :atboot => "myboot"}) set = [] Puppet::Type.type(:mount).any_instance.stubs(:newattr).with do |param, hash| set << param true end.returns(stub_everything("a property")) Puppet::Type.type(:mount).new(resource) set[0].should == :name set[1].should == :provider end # This one is really hard to test :/ it "should each default immediately if no value is provided" do defaults = [] Puppet::Type.type(:package).any_instance.stubs(:set_default).with { |value| defaults << value; true } Puppet::Type.type(:package).new :name => "whatever" defaults[0].should == :provider end it "should retain a copy of the originally provided parameters" do Puppet::Type.type(:mount).new(:name => "foo", :atboot => true, :noop => false).original_parameters.should == {:atboot => true, :noop => false} end it "should delete the name via the namevar from the originally provided parameters" do Puppet::Type.type(:file).new(:name => "/foo").original_parameters[:path].should be_nil end end it "should have a class method for converting a hash into a Puppet::Resource instance" do Puppet::Type.type(:mount).must respond_to(:hash2resource) end describe "when converting a hash to a Puppet::Resource instance" do before do @type = Puppet::Type.type(:mount) end it "should treat a :title key as the title of the resource" do @type.hash2resource(:name => "/foo", :title => "foo").title.should == "foo" end it "should use the name from the hash as the title if no explicit title is provided" do @type.hash2resource(:name => "foo").title.should == "foo" end it "should use the Resource Type's namevar to determine how to find the name in the hash" do @type.stubs(:key_attributes).returns([ :myname ]) @type.hash2resource(:myname => "foo").title.should == "foo" end [:catalog].each do |attr| it "should use any provided #{attr}" do @type.hash2resource(:name => "foo", attr => "eh").send(attr).should == "eh" end end it "should set all provided parameters on the resource" do @type.hash2resource(:name => "foo", :fstype => "boo", :boot => "fee").to_hash.should == {:name => "foo", :fstype => "boo", :boot => "fee"} end it "should not set the title as a parameter on the resource" do @type.hash2resource(:name => "foo", :title => "eh")[:title].should be_nil end it "should not set the catalog as a parameter on the resource" do @type.hash2resource(:name => "foo", :catalog => "eh")[:catalog].should be_nil end it "should treat hash keys equivalently whether provided as strings or symbols" do resource = @type.hash2resource("name" => "foo", "title" => "eh", "fstype" => "boo") resource.title.should == "eh" resource[:name].should == "foo" resource[:fstype].should == "boo" end end describe "when retrieving current property values" do before do @resource = Puppet::Type.type(:mount).new(:name => "foo", :fstype => "bar", :pass => 1, :ensure => :present) @resource.property(:ensure).stubs(:retrieve).returns :absent end it "should fail if its provider is unsuitable" do @resource = Puppet::Type.type(:mount).new(:name => "foo", :fstype => "bar", :pass => 1, :ensure => :present) @resource.provider.class.expects(:suitable?).returns false lambda { @resource.retrieve_resource }.should raise_error(Puppet::Error) end it "should return a Puppet::Resource instance with its type and title set appropriately" do result = @resource.retrieve_resource result.should be_instance_of(Puppet::Resource) result.type.should == "Mount" result.title.should == "foo" end it "should set the name of the returned resource if its own name and title differ" do @resource[:name] = "my name" @resource.title = "other name" @resource.retrieve_resource[:name].should == "my name" end it "should provide a value for all set properties" do values = @resource.retrieve_resource [:ensure, :fstype, :pass].each { |property| values[property].should_not be_nil } end it "should provide a value for 'ensure' even if no desired value is provided" do @resource = Puppet::Type.type(:file).new(:path => "/my/file/that/can't/exist") end it "should not call retrieve on non-ensure properties if the resource is absent and should consider the property absent" do @resource.property(:ensure).expects(:retrieve).returns :absent @resource.property(:fstype).expects(:retrieve).never @resource.retrieve_resource[:fstype].should == :absent end it "should include the result of retrieving each property's current value if the resource is present" do @resource.property(:ensure).expects(:retrieve).returns :present @resource.property(:fstype).expects(:retrieve).returns 15 @resource.retrieve_resource[:fstype] == 15 end end describe ".title_patterns" do describe "when there's one namevar" do before do @type_class = Puppet::Type.type(:notify) @type_class.stubs(:key_attributes).returns([:one]) end it "should have a default pattern for when there's one namevar" do patterns = @type_class.title_patterns patterns.length.should == 1 patterns[0].length.should == 2 end it "should have a regexp that captures the entire string" do patterns = @type_class.title_patterns string = "abc\n\tdef" patterns[0][0] =~ string $1.should == "abc\n\tdef" end end end describe "when in a catalog" do before do @catalog = Puppet::Resource::Catalog.new @container = Puppet::Type.type(:component).new(:name => "container") @one = Puppet::Type.type(:file).new(:path => "/file/one") @two = Puppet::Type.type(:file).new(:path => "/file/two") @catalog.add_resource @container @catalog.add_resource @one @catalog.add_resource @two @catalog.add_edge @container, @one @catalog.add_edge @container, @two end it "should have no parent if there is no in edge" do @container.parent.should be_nil end it "should set its parent to its in edge" do @one.parent.ref.should == @container.ref end after do @catalog.clear(true) end end it "should have a 'stage' metaparam" do Puppet::Type.metaparamclass(:stage).should be_instance_of(Class) end end describe Puppet::Type::RelationshipMetaparam do it "should be a subclass of Puppet::Parameter" do Puppet::Type::RelationshipMetaparam.superclass.should equal(Puppet::Parameter) end it "should be able to produce a list of subclasses" do Puppet::Type::RelationshipMetaparam.should respond_to(:subclasses) end describe "when munging relationships" do before do @resource = Puppet::Type.type(:mount).new :name => "/foo" @metaparam = Puppet::Type.metaparamclass(:require).new :resource => @resource end it "should accept Puppet::Resource instances" do ref = Puppet::Resource.new(:file, "/foo") @metaparam.munge(ref)[0].should equal(ref) end it "should turn any string into a Puppet::Resource" do @metaparam.munge("File[/ref]")[0].should be_instance_of(Puppet::Resource) end end it "should be able to validate relationships" do Puppet::Type.metaparamclass(:require).new(:resource => mock("resource")).should respond_to(:validate_relationship) end it "should fail if any specified resource is not found in the catalog" do catalog = mock 'catalog' resource = stub 'resource', :catalog => catalog, :ref => "resource" param = Puppet::Type.metaparamclass(:require).new(:resource => resource, :value => %w{Foo[bar] Class[test]}) catalog.expects(:resource).with("Foo[bar]").returns "something" catalog.expects(:resource).with("Class[Test]").returns nil param.expects(:fail).with { |string| string.include?("Class[Test]") } param.validate_relationship end end describe Puppet::Type.metaparamclass(:check) do it "should warn and create an instance of ':audit'" do file = Puppet::Type.type(:file).new :path => "/foo" file.expects(:warning) file[:check] = :mode file[:audit].should == [:mode] end end describe Puppet::Type.metaparamclass(:audit) do before do @resource = Puppet::Type.type(:file).new :path => "/foo" end it "should default to being nil" do @resource[:audit].should be_nil end it "should specify all possible properties when asked to audit all properties" do @resource[:audit] = :all list = @resource.class.properties.collect { |p| p.name } @resource[:audit].should == list end it "should accept the string 'all' to specify auditing all possible properties" do @resource[:audit] = 'all' list = @resource.class.properties.collect { |p| p.name } @resource[:audit].should == list end it "should fail if asked to audit an invalid property" do lambda { @resource[:audit] = :foobar }.should raise_error(Puppet::Error) end it "should create an attribute instance for each auditable property" do @resource[:audit] = :mode @resource.parameter(:mode).should_not be_nil end it "should accept properties specified as a string" do @resource[:audit] = "mode" @resource.parameter(:mode).should_not be_nil end it "should not create attribute instances for parameters, only properties" do @resource[:audit] = :noop @resource.parameter(:noop).should be_nil end end diff --git a/spec/unit/util/checksums_spec.rb b/spec/unit/util/checksums_spec.rb index e018581af..a8bc12be2 100755 --- a/spec/unit/util/checksums_spec.rb +++ b/spec/unit/util/checksums_spec.rb @@ -1,153 +1,161 @@ #!/usr/bin/env ruby # # Created by Luke Kanies on 2007-9-22. # Copyright (c) 2007. All rights reserved. require File.dirname(__FILE__) + '/../../spec_helper' require 'puppet/util/checksums' describe Puppet::Util::Checksums do before do @summer = Object.new @summer.extend(Puppet::Util::Checksums) end content_sums = [:md5, :md5lite, :sha1, :sha1lite] file_only = [:ctime, :mtime, :none] content_sums.each do |sumtype| it "should be able to calculate #{sumtype} sums from strings" do @summer.should be_respond_to(sumtype) end end [content_sums, file_only].flatten.each do |sumtype| it "should be able to calculate #{sumtype} sums from files" do @summer.should be_respond_to(sumtype.to_s + "_file") end end [content_sums, file_only].flatten.each do |sumtype| it "should be able to calculate #{sumtype} sums from stream" do @summer.should be_respond_to(sumtype.to_s + "_stream") end end it "should have a method for determining whether a given string is a checksum" do @summer.should respond_to(:checksum?) end %w{{md5}asdfasdf {sha1}asdfasdf {ctime}asdasdf {mtime}asdfasdf}.each do |sum| it "should consider #{sum} to be a checksum" do @summer.should be_checksum(sum) end end %w{{nosuchsum}asdfasdf {a}asdfasdf {ctime}}.each do |sum| it "should not consider #{sum} to be a checksum" do @summer.should_not be_checksum(sum) end end it "should have a method for stripping a sum type from an existing checksum" do @summer.sumtype("{md5}asdfasdfa").should == "md5" end it "should have a method for stripping the data from a checksum" do @summer.sumdata("{md5}asdfasdfa").should == "asdfasdfa" end it "should return a nil sumtype if the checksum does not mention a checksum type" do @summer.sumtype("asdfasdfa").should be_nil end {:md5 => Digest::MD5, :sha1 => Digest::SHA1}.each do |sum, klass| describe("when using #{sum}") do it "should use #{klass} to calculate string checksums" do klass.expects(:hexdigest).with("mycontent").returns "whatever" @summer.send(sum, "mycontent").should == "whatever" end it "should use incremental #{klass} sums to calculate file checksums" do digest = mock 'digest' klass.expects(:new).returns digest file = "/path/to/my/file" fh = mock 'filehandle' fh.expects(:read).with(4096).times(3).returns("firstline").then.returns("secondline").then.returns(nil) #fh.expects(:read).with(512).returns("secondline") #fh.expects(:read).with(512).returns(nil) File.expects(:open).with(file, "r").yields(fh) digest.expects(:<<).with "firstline" digest.expects(:<<).with "secondline" digest.expects(:hexdigest).returns :mydigest @summer.send(sum.to_s + "_file", file).should == :mydigest end it "should yield #{klass} to the given block to calculate stream checksums" do digest = mock 'digest' klass.expects(:new).returns digest digest.expects(:hexdigest).returns :mydigest @summer.send(sum.to_s + "_stream") do |sum| sum.should == digest end.should == :mydigest end end end {:md5lite => Digest::MD5, :sha1lite => Digest::SHA1}.each do |sum, klass| describe("when using #{sum}") do it "should use #{klass} to calculate string checksums from the first 512 characters of the string" do content = "this is a test" * 100 klass.expects(:hexdigest).with(content[0..511]).returns "whatever" @summer.send(sum, content).should == "whatever" end it "should use #{klass} to calculate a sum from the first 512 characters in the file" do digest = mock 'digest' klass.expects(:new).returns digest file = "/path/to/my/file" fh = mock 'filehandle' fh.expects(:read).with(512).returns('my content') File.expects(:open).with(file, "r").yields(fh) digest.expects(:<<).with "my content" digest.expects(:hexdigest).returns :mydigest @summer.send(sum.to_s + "_file", file).should == :mydigest end end end [:ctime, :mtime].each do |sum| describe("when using #{sum}") do it "should use the '#{sum}' on the file to determine the ctime" do file = "/my/file" stat = mock 'stat', sum => "mysum" File.expects(:stat).with(file).returns(stat) @summer.send(sum.to_s + "_file", file).should == "mysum" end it "should return nil for streams" do - @summer.send(sum.to_s + "_stream").should be_nil + expectation = stub "expectation" + expectation.expects(:do_something!).at_least_once + @summer.send(sum.to_s + "_stream"){ |checksum| checksum << "anything" ; expectation.do_something! }.should be_nil end end end describe "when using the none checksum" do it "should return an empty string" do @summer.none_file("/my/file").should == "" end + + it "should return an empty string for streams" do + expectation = stub "expectation" + expectation.expects(:do_something!).at_least_once + @summer.none_stream{ |checksum| checksum << "anything" ; expectation.do_something! }.should == "" + end end end diff --git a/spec/unit/util/log_spec.rb b/spec/unit/util/log_spec.rb index 7d96fe190..f3fd1b051 100755 --- a/spec/unit/util/log_spec.rb +++ b/spec/unit/util/log_spec.rb @@ -1,215 +1,222 @@ #!/usr/bin/env ruby Dir.chdir(File.dirname(__FILE__)) { (s = lambda { |f| File.exist?(f) ? require(f) : Dir.chdir("..") { s.call(f) } }).call("spec/spec_helper.rb") } require 'puppet/util/log' describe Puppet::Util::Log do it "should write a given message to the specified destination" do arraydest = [] Puppet::Util::Log.newdestination(arraydest) Puppet::Util::Log.new(:level => :notice, :message => "foo") message = arraydest.last.message message.should == "foo" end describe Puppet::Util::Log::DestConsole do before do @console = Puppet::Util::Log::DestConsole.new end it "should colorize if Puppet[:color] is :ansi" do Puppet[:color] = :ansi @console.colorize(:alert, "abc").should == "\e[0;31mabc\e[0m" end it "should colorize if Puppet[:color] is 'yes'" do Puppet[:color] = "yes" @console.colorize(:alert, "abc").should == "\e[0;31mabc\e[0m" end it "should htmlize if Puppet[:color] is :html" do Puppet[:color] = :html @console.colorize(:alert, "abc").should == "abc" end it "should do nothing if Puppet[:color] is false" do Puppet[:color] = false @console.colorize(:alert, "abc").should == "abc" end it "should do nothing if Puppet[:color] is invalid" do Puppet[:color] = "invalid option" @console.colorize(:alert, "abc").should == "abc" end end describe "instances" do before do Puppet::Util::Log.stubs(:newmessage) end [:level, :message, :time, :remote].each do |attr| it "should have a #{attr} attribute" do log = Puppet::Util::Log.new :level => :notice, :message => "A test message" log.should respond_to(attr) log.should respond_to(attr.to_s + "=") end end it "should fail if created without a level" do lambda { Puppet::Util::Log.new(:message => "A test message") }.should raise_error(ArgumentError) end it "should fail if created without a message" do lambda { Puppet::Util::Log.new(:level => :notice) }.should raise_error(ArgumentError) end it "should make available the level passed in at initialization" do Puppet::Util::Log.new(:level => :notice, :message => "A test message").level.should == :notice end it "should make available the message passed in at initialization" do Puppet::Util::Log.new(:level => :notice, :message => "A test message").message.should == "A test message" end # LAK:NOTE I don't know why this behavior is here, I'm just testing what's in the code, # at least at first. it "should always convert messages to strings" do Puppet::Util::Log.new(:level => :notice, :message => :foo).message.should == "foo" end it "should flush the log queue when the first destination is specified" do Puppet::Util::Log.close_all Puppet::Util::Log.expects(:flushqueue) Puppet::Util::Log.newdestination([]) end it "should convert the level to a symbol if it's passed in as a string" do Puppet::Util::Log.new(:level => "notice", :message => :foo).level.should == :notice end it "should fail if the level is not a symbol or string" do lambda { Puppet::Util::Log.new(:level => 50, :message => :foo) }.should raise_error(ArgumentError) end it "should fail if the provided level is not valid" do Puppet::Util::Log.expects(:validlevel?).with(:notice).returns false lambda { Puppet::Util::Log.new(:level => :notice, :message => :foo) }.should raise_error(ArgumentError) end it "should set its time to the initialization time" do time = mock 'time' Time.expects(:now).returns time Puppet::Util::Log.new(:level => "notice", :message => :foo).time.should equal(time) end it "should make available any passed-in tags" do log = Puppet::Util::Log.new(:level => "notice", :message => :foo, :tags => %w{foo bar}) log.tags.should be_include("foo") log.tags.should be_include("bar") end it "should use an passed-in source" do Puppet::Util::Log.any_instance.expects(:source=).with "foo" Puppet::Util::Log.new(:level => "notice", :message => :foo, :source => "foo") end - [:file, :line, :version].each do |attr| + [:file, :line].each do |attr| it "should use #{attr} if provided" do Puppet::Util::Log.any_instance.expects(attr.to_s + "=").with "foo" Puppet::Util::Log.new(:level => "notice", :message => :foo, attr => "foo") end end it "should default to 'Puppet' as its source" do Puppet::Util::Log.new(:level => "notice", :message => :foo).source.should == "Puppet" end it "should register itself with Log" do Puppet::Util::Log.expects(:newmessage) Puppet::Util::Log.new(:level => "notice", :message => :foo) end it "should have a method for determining if a tag is present" do Puppet::Util::Log.new(:level => "notice", :message => :foo).should respond_to(:tagged?) end it "should match a tag if any of the tags are equivalent to the passed tag as a string" do Puppet::Util::Log.new(:level => "notice", :message => :foo, :tags => %w{one two}).should be_tagged(:one) end it "should tag itself with its log level" do Puppet::Util::Log.new(:level => "notice", :message => :foo).should be_tagged(:notice) end it "should return its message when converted to a string" do Puppet::Util::Log.new(:level => "notice", :message => :foo).to_s.should == "foo" end it "should include its time, source, level, and message when prepared for reporting" do log = Puppet::Util::Log.new(:level => "notice", :message => :foo) report = log.to_report report.should be_include("notice") report.should be_include("foo") report.should be_include(log.source) report.should be_include(log.time.to_s) end describe "when setting the source as a RAL object" do it "should tag itself with any tags the source has" do source = Puppet::Type.type(:file).new :path => "/foo/bar" log = Puppet::Util::Log.new(:level => "notice", :message => :foo, :source => source) source.tags.each do |tag| log.tags.should be_include(tag) end end it "should use the source_descriptors" do source = stub "source" source.stubs(:source_descriptors).returns(:tags => ["tag","tag2"], :path => "path", :version => 100) log = Puppet::Util::Log.new(:level => "notice", :message => :foo) log.expects(:tag).with("tag") log.expects(:tag).with("tag2") - log.expects(:version=).with(100) log.source = source log.source.should == "path" end - it "should copy over any version information" do - catalog = Puppet::Resource::Catalog.new - catalog.version = 25 - source = Puppet::Type.type(:file).new :path => "/foo/bar" - catalog.add_resource source - - log = Puppet::Util::Log.new(:level => "notice", :message => :foo, :source => source) - log.version.should == 25 - end - it "should copy over any file and line information" do source = Puppet::Type.type(:file).new :path => "/foo/bar" source.file = "/my/file" source.line = 50 log = Puppet::Util::Log.new(:level => "notice", :message => :foo, :source => source) log.file.should == "/my/file" log.line.should == 50 end end describe "when setting the source as a non-RAL object" do it "should not try to copy over file, version, line, or tag information" do source = Puppet::Module.new("foo") source.expects(:file).never log = Puppet::Util::Log.new(:level => "notice", :message => :foo, :source => source) end end end + + describe "to_yaml" do + it "should not include the @version attribute" do + log = Puppet::Util::Log.new(:level => "notice", :message => :foo, :version => 100) + log.to_yaml_properties.should_not include('@version') + end + + it "should include attributes @level, @message, @source, @tags, and @time" do + log = Puppet::Util::Log.new(:level => "notice", :message => :foo, :version => 100) + log.to_yaml_properties.should == %w{@level @message @source @tags @time} + end + + it "should include attributes @file and @line if specified" do + log = Puppet::Util::Log.new(:level => "notice", :message => :foo, :file => "foo", :line => 35) + log.to_yaml_properties.should include('@file') + log.to_yaml_properties.should include('@line') + end + end end diff --git a/spec/unit/util/logging_spec.rb b/spec/unit/util/logging_spec.rb index 46ae5386f..411cd17a9 100755 --- a/spec/unit/util/logging_spec.rb +++ b/spec/unit/util/logging_spec.rb @@ -1,95 +1,95 @@ #!/usr/bin/env ruby Dir.chdir(File.dirname(__FILE__)) { (s = lambda { |f| File.exist?(f) ? require(f) : Dir.chdir("..") { s.call(f) } }).call("spec/spec_helper.rb") } require 'puppet/util/logging' class LoggingTester include Puppet::Util::Logging end describe Puppet::Util::Logging do before do @logger = LoggingTester.new end Puppet::Util::Log.eachlevel do |level| it "should have a method for sending '#{level}' logs" do @logger.should respond_to(level) end end it "should have a method for sending a log with a specified log level" do @logger.expects(:to_s).returns "I'm a string!" Puppet::Util::Log.expects(:create).with { |args| args[:source] == "I'm a string!" and args[:level] == "loglevel" and args[:message] == "mymessage" } @logger.send_log "loglevel", "mymessage" end describe "when sending a log" do it "should use the Log's 'create' entrance method" do Puppet::Util::Log.expects(:create) @logger.notice "foo" end it "should send itself converted to a string as the log source" do @logger.expects(:to_s).returns "I'm a string!" Puppet::Util::Log.expects(:create).with { |args| args[:source] == "I'm a string!" } @logger.notice "foo" end it "should queue logs sent without a specified destination" do Puppet::Util::Log.close_all Puppet::Util::Log.expects(:queuemessage) @logger.notice "foo" end it "should use the path of any provided resource type" do resource = Puppet::Type.type(:mount).new :name => "foo" resource.expects(:path).returns "/path/to/mount".to_sym Puppet::Util::Log.expects(:create).with { |args| args[:source] == "/path/to/mount" } resource.notice "foo" end it "should use the path of any provided resource parameter" do resource = Puppet::Type.type(:mount).new :name => "foo" param = resource.parameter(:name) param.expects(:path).returns "/path/to/param".to_sym Puppet::Util::Log.expects(:create).with { |args| args[:source] == "/path/to/param" } param.notice "foo" end it "should send the provided argument as the log message" do Puppet::Util::Log.expects(:create).with { |args| args[:message] == "foo" } @logger.notice "foo" end it "should join any provided arguments into a single string for the message" do Puppet::Util::Log.expects(:create).with { |args| args[:message] == "foo bar baz" } @logger.notice ["foo", "bar", "baz"] end - [:file, :line, :version, :tags].each do |attr| + [:file, :line, :tags].each do |attr| it "should include #{attr} if available" do @logger.singleton_class.send(:attr_accessor, attr) @logger.send(attr.to_s + "=", "myval") Puppet::Util::Log.expects(:create).with { |args| args[attr] == "myval" } @logger.notice "foo" end end end end diff --git a/spec/unit/util/metric_spec.rb b/spec/unit/util/metric_spec.rb index 72571ee4a..600b88f85 100755 --- a/spec/unit/util/metric_spec.rb +++ b/spec/unit/util/metric_spec.rb @@ -1,95 +1,95 @@ #!/usr/bin/env ruby Dir.chdir(File.dirname(__FILE__)) { (s = lambda { |f| File.exist?(f) ? require(f) : Dir.chdir("..") { s.call(f) } }).call("spec/spec_helper.rb") } require 'puppet/util/metric' describe Puppet::Util::Metric do before do @metric = Puppet::Util::Metric.new("foo") end it "should be aliased to Puppet::Metric" do Puppet::Util::Metric.should equal(Puppet::Metric) end [:type, :name, :value, :label, :basedir].each do |name| it "should have a #{name} attribute" do @metric.should respond_to(name) @metric.should respond_to(name.to_s + "=") end end it "should default to the :rrdir as the basedir "do Puppet.settings.expects(:value).with(:rrddir).returns "myrrd" @metric.basedir.should == "myrrd" end it "should use any provided basedir" do @metric.basedir = "foo" @metric.basedir.should == "foo" end it "should require a name at initialization" do lambda { Puppet::Util::Metric.new }.should raise_error(ArgumentError) end it "should always convert its name to a string" do Puppet::Util::Metric.new(:foo).name.should == "foo" end it "should support a label" do Puppet::Util::Metric.new("foo", "mylabel").label.should == "mylabel" end it "should autogenerate a label if none is provided" do Puppet::Util::Metric.new("foo_bar").label.should == "Foo bar" end it "should have a method for adding values" do @metric.should respond_to(:newvalue) end it "should have a method for returning values" do @metric.should respond_to(:values) end it "should require a name and value for its values" do lambda { @metric.newvalue }.should raise_error(ArgumentError) end it "should support a label for values" do - @metric.newvalue(:foo, 10, "label") + @metric.newvalue("foo", 10, "label") @metric.values[0][1].should == "label" end it "should autogenerate value labels if none is provided" do @metric.newvalue("foo_bar", 10) @metric.values[0][1].should == "Foo bar" end it "should return its values sorted by label" do - @metric.newvalue(:foo, 10, "b") - @metric.newvalue(:bar, 10, "a") + @metric.newvalue("foo", 10, "b") + @metric.newvalue("bar", 10, "a") - @metric.values.should == [[:bar, "a", 10], [:foo, "b", 10]] + @metric.values.should == [["bar", "a", 10], ["foo", "b", 10]] end it "should use an array indexer method to retrieve individual values" do - @metric.newvalue(:foo, 10) - @metric[:foo].should == 10 + @metric.newvalue("foo", 10) + @metric["foo"].should == 10 end it "should return nil if the named value cannot be found" do - @metric[:foo].should == 0 + @metric["foo"].should == 0 end # LAK: I'm not taking the time to develop these tests right now. # I expect they should actually be extracted into a separate class # anyway. it "should be able to graph metrics using RRDTool" it "should be able to create a new RRDTool database" it "should be able to store metrics into an RRDTool database" end diff --git a/spec/unit/util/queue/stomp_spec.rb b/spec/unit/util/queue/stomp_spec.rb index 9f1d28448..c33f1a670 100755 --- a/spec/unit/util/queue/stomp_spec.rb +++ b/spec/unit/util/queue/stomp_spec.rb @@ -1,140 +1,136 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../../spec_helper' require 'puppet/util/queue' -describe Puppet::Util::Queue do - confine "Missing Stomp" => Puppet.features.stomp? - +describe Puppet::Util::Queue, :if => Puppet.features.stomp? do it 'should load :stomp client appropriately' do Puppet.settings.stubs(:value).returns 'faux_queue_source' Puppet::Util::Queue.queue_type_to_class(:stomp).name.should == 'Puppet::Util::Queue::Stomp' end end -describe 'Puppet::Util::Queue::Stomp' do - confine "Missing Stomp" => Puppet.features.stomp? - +describe 'Puppet::Util::Queue::Stomp', :if => Puppet.features.stomp? do before do # So we make sure we never create a real client instance. # Otherwise we'll try to connect, and that's bad. Stomp::Client.stubs(:new).returns stub("client") end it 'should be registered with Puppet::Util::Queue as :stomp type' do Puppet::Util::Queue.queue_type_to_class(:stomp).should == Puppet::Util::Queue::Stomp end describe "when initializing" do it "should create a Stomp client instance" do Stomp::Client.expects(:new).returns stub("stomp_client") Puppet::Util::Queue::Stomp.new end it "should provide helpful failures when the queue source is not a valid source" do # Stub rather than expect, so we can include the source in the error Puppet.settings.stubs(:value).with(:queue_source).returns "-----" lambda { Puppet::Util::Queue::Stomp.new }.should raise_error(ArgumentError) end it "should fail unless the queue source is a stomp URL" do # Stub rather than expect, so we can include the source in the error Puppet.settings.stubs(:value).with(:queue_source).returns "http://foo/bar" lambda { Puppet::Util::Queue::Stomp.new }.should raise_error(ArgumentError) end it "should fail somewhat helpfully if the Stomp client cannot be created" do Stomp::Client.expects(:new).raises RuntimeError lambda { Puppet::Util::Queue::Stomp.new }.should raise_error(ArgumentError) end list = %w{user password host port} {"user" => "myuser", "password" => "mypass", "host" => "foohost", "port" => 42}.each do |name, value| it "should use the #{name} from the queue source as the queueing #{name}" do Puppet.settings.expects(:value).with(:queue_source).returns "stomp://myuser:mypass@foohost:42/" Stomp::Client.expects(:new).with { |*args| args[list.index(name)] == value } Puppet::Util::Queue::Stomp.new end end it "should create a reliable client instance" do Puppet.settings.expects(:value).with(:queue_source).returns "stomp://myuser@foohost:42/" Stomp::Client.expects(:new).with { |*args| args[4] == true } Puppet::Util::Queue::Stomp.new end end describe "when sending a message" do before do @client = stub 'client' Stomp::Client.stubs(:new).returns @client @queue = Puppet::Util::Queue::Stomp.new end it "should send it to the queue client instance" do @client.expects(:send).with { |queue, msg, options| msg == "Smite!" } @queue.send_message('fooqueue', 'Smite!') end it "should send it to the transformed queue name" do @client.expects(:send).with { |queue, msg, options| queue == "/queue/fooqueue" } @queue.send_message('fooqueue', 'Smite!') end it "should send it as a persistent message" do @client.expects(:send).with { |queue, msg, options| options[:persistent] == true } @queue.send_message('fooqueue', 'Smite!') end end describe "when subscribing to a queue" do before do @client = stub 'client', :acknowledge => true Stomp::Client.stubs(:new).returns @client @queue = Puppet::Util::Queue::Stomp.new end it "should subscribe via the queue client instance" do @client.expects(:subscribe) @queue.subscribe('fooqueue') end it "should subscribe to the transformed queue name" do @client.expects(:subscribe).with { |queue, options| queue == "/queue/fooqueue" } @queue.subscribe('fooqueue') end it "should specify that its messages should be acknowledged" do @client.expects(:subscribe).with { |queue, options| options[:ack] == :client } @queue.subscribe('fooqueue') end it "should yield the body of any received message" do message = mock 'message' message.expects(:body).returns "mybody" @client.expects(:subscribe).yields(message) body = nil @queue.subscribe('fooqueue') { |b| body = b } body.should == "mybody" end it "should acknowledge all successfully processed messages" do message = stub 'message', :body => "mybode" @client.stubs(:subscribe).yields(message) @client.expects(:acknowledge).with(message) @queue.subscribe('fooqueue') { |b| "eh" } end end it 'should transform the simple queue name to "/queue/"' do Puppet::Util::Queue::Stomp.new.stompify_target('blah').should == '/queue/blah' end end diff --git a/spec/unit/util/settings/file_setting_spec.rb b/spec/unit/util/settings/file_setting_spec.rb index 2870fbb57..dcfb6e3b1 100755 --- a/spec/unit/util/settings/file_setting_spec.rb +++ b/spec/unit/util/settings/file_setting_spec.rb @@ -1,256 +1,254 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../../spec_helper' require 'puppet/util/settings' require 'puppet/util/settings/file_setting' describe Puppet::Util::Settings::FileSetting do FileSetting = Puppet::Util::Settings::FileSetting before do @basepath = Puppet.features.posix? ? "/somepath" : "C:/somepath" end describe "when determining whether the service user should be used" do before do @settings = mock 'settings' @settings.stubs(:[]).with(:mkusers).returns false @settings.stubs(:service_user_available?).returns true end it "should be true if the service user is available" do @settings.expects(:service_user_available?).returns true setting = FileSetting.new(:settings => @settings, :owner => "root", :desc => "a setting") setting.should be_use_service_user end it "should be true if 'mkusers' is set" do @settings.expects(:[]).with(:mkusers).returns true setting = FileSetting.new(:settings => @settings, :owner => "root", :desc => "a setting") setting.should be_use_service_user end it "should be false if the service user is not available and 'mkusers' is unset" do setting = FileSetting.new(:settings => @settings, :owner => "root", :desc => "a setting") setting.should be_use_service_user end end describe "when setting the owner" do it "should allow the file to be owned by root" do root_owner = lambda { FileSetting.new(:settings => mock("settings"), :owner => "root", :desc => "a setting") } root_owner.should_not raise_error end it "should allow the file to be owned by the service user" do service_owner = lambda { FileSetting.new(:settings => mock("settings"), :owner => "service", :desc => "a setting") } service_owner.should_not raise_error end it "should allow the ownership of the file to be unspecified" do no_owner = lambda { FileSetting.new(:settings => mock("settings"), :desc => "a setting") } no_owner.should_not raise_error end it "should not allow other owners" do invalid_owner = lambda { FileSetting.new(:settings => mock("settings"), :owner => "invalid", :desc => "a setting") } invalid_owner.should raise_error(FileSetting::SettingError) end end describe "when reading the owner" do it "should be root when the setting specifies root" do setting = FileSetting.new(:settings => mock("settings"), :owner => "root", :desc => "a setting") setting.owner.should == "root" end it "should be the owner of the service when the setting specifies service and the service user should be used" do settings = mock("settings") settings.stubs(:[]).returns "the_service" setting = FileSetting.new(:settings => settings, :owner => "service", :desc => "a setting") setting.expects(:use_service_user?).returns true setting.owner.should == "the_service" end it "should be the root when the setting specifies service and the service user should not be used" do settings = mock("settings") settings.stubs(:[]).returns "the_service" setting = FileSetting.new(:settings => settings, :owner => "service", :desc => "a setting") setting.expects(:use_service_user?).returns false setting.owner.should == "root" end it "should be nil when the owner is unspecified" do FileSetting.new(:settings => mock("settings"), :desc => "a setting").owner.should be_nil end end describe "when setting the group" do it "should allow the group to be service" do service_group = lambda { FileSetting.new(:settings => mock("settings"), :group => "service", :desc => "a setting") } service_group.should_not raise_error end it "should allow the group to be unspecified" do no_group = lambda { FileSetting.new(:settings => mock("settings"), :desc => "a setting") } no_group.should_not raise_error end it "should not allow invalid groups" do invalid_group = lambda { FileSetting.new(:settings => mock("settings"), :group => "invalid", :desc => "a setting") } invalid_group.should raise_error(FileSetting::SettingError) end end describe "when reading the group" do it "should be service when the setting specifies service" do setting = FileSetting.new(:settings => mock("settings", :[] => "the_service"), :group => "service", :desc => "a setting") setting.group.should == "the_service" end it "should be nil when the group is unspecified" do FileSetting.new(:settings => mock("settings"), :desc => "a setting").group.should be_nil end end it "should be able to be converted into a resource" do FileSetting.new(:settings => mock("settings"), :desc => "eh").should respond_to(:to_resource) end describe "when being converted to a resource" do before do @settings = mock 'settings' @file = Puppet::Util::Settings::FileSetting.new(:settings => @settings, :desc => "eh", :name => :mydir, :section => "mysect") @settings.stubs(:value).with(:mydir).returns @basepath end it "should skip files that cannot determine their types" do @file.expects(:type).returns nil @file.to_resource.should be_nil end it "should skip non-existent files if 'create_files' is not enabled" do @file.expects(:create_files?).returns false @file.expects(:type).returns :file File.expects(:exist?).with(@basepath).returns false @file.to_resource.should be_nil end it "should manage existent files even if 'create_files' is not enabled" do @file.expects(:create_files?).returns false @file.expects(:type).returns :file File.expects(:exist?).with(@basepath).returns true @file.to_resource.should be_instance_of(Puppet::Resource) end - describe "on POSIX systems" do - confine "no /dev on Microsoft Windows" => Puppet.features.posix? - + describe "on POSIX systems", :if => Puppet.features.posix? do it "should skip files in /dev" do @settings.stubs(:value).with(:mydir).returns "/dev/file" @file.to_resource.should be_nil end end it "should skip files whose paths are not strings" do @settings.stubs(:value).with(:mydir).returns :foo @file.to_resource.should be_nil end it "should return a file resource with the path set appropriately" do resource = @file.to_resource resource.type.should == "File" resource.title.should == @basepath end it "should fully qualified returned files if necessary (#795)" do @settings.stubs(:value).with(:mydir).returns "myfile" @file.to_resource.title.should == File.join(Dir.getwd, "myfile") end it "should set the mode on the file if a mode is provided" do @file.mode = 0755 @file.to_resource[:mode].should == 0755 end it "should not set the mode on a the file if manage_internal_file_permissions is disabled" do Puppet[:manage_internal_file_permissions] = false @file.stubs(:mode).returns(0755) @file.to_resource[:mode].should == nil end it "should set the owner if running as root and the owner is provided" do Puppet.features.expects(:root?).returns true @file.stubs(:owner).returns "foo" @file.to_resource[:owner].should == "foo" end it "should not set the owner if manage_internal_file_permissions is disabled" do Puppet[:manage_internal_file_permissions] = false Puppet.features.stubs(:root?).returns true @file.stubs(:owner).returns "foo" @file.to_resource[:owner].should == nil end it "should set the group if running as root and the group is provided" do Puppet.features.expects(:root?).returns true @file.stubs(:group).returns "foo" @file.to_resource[:group].should == "foo" end it "should not set the group if manage_internal_file_permissions is disabled" do Puppet[:manage_internal_file_permissions] = false Puppet.features.stubs(:root?).returns true @file.stubs(:group).returns "foo" @file.to_resource[:group].should == nil end it "should not set owner if not running as root" do Puppet.features.expects(:root?).returns false @file.stubs(:owner).returns "foo" @file.to_resource[:owner].should be_nil end it "should not set group if not running as root" do Puppet.features.expects(:root?).returns false @file.stubs(:group).returns "foo" @file.to_resource[:group].should be_nil end it "should set :ensure to the file type" do @file.expects(:type).returns :directory @file.to_resource[:ensure].should == :directory end it "should set the loglevel to :debug" do @file.to_resource[:loglevel].should == :debug end it "should set the backup to false" do @file.to_resource[:backup].should be_false end it "should tag the resource with the settings section" do @file.expects(:section).returns "mysect" @file.to_resource.should be_tagged("mysect") end it "should tag the resource with the setting name" do @file.to_resource.should be_tagged("mydir") end it "should tag the resource with 'settings'" do @file.to_resource.should be_tagged("settings") end end end diff --git a/test/lib/puppettest/fileparsing.rb b/test/lib/puppettest/fileparsing.rb index 914c4bcb3..bd4f9e152 100644 --- a/test/lib/puppettest/fileparsing.rb +++ b/test/lib/puppettest/fileparsing.rb @@ -1,26 +1,28 @@ +require 'test/unit' + module PuppetTest::FileParsing # Run an isomorphism test on our parsing process. def fakedataparse(*files) files.each do |file| @provider.stubs(:default_target).returns(file) @provider.prefetch text = @provider.to_file(@provider.target_records(file)) text.gsub!(/^# HEADER.+\n/, '') yield if block_given? oldlines = File.readlines(file) newlines = text.chomp.split "\n" oldlines.zip(newlines).each do |old, new| if self.is_a?(Test::Unit::TestCase) assert_equal(old.chomp.gsub(/\s+/, ''), new.gsub(/\s+/, ''), "File was not written back out correctly") else new.gsub(/\s+/, '').should == old.chomp.gsub(/\s+/, '') end end end end end diff --git a/test/lib/puppettest/reporttesting.rb b/test/lib/puppettest/reporttesting.rb index 448a6a9d8..54a2f6799 100644 --- a/test/lib/puppettest/reporttesting.rb +++ b/test/lib/puppettest/reporttesting.rb @@ -1,16 +1,16 @@ module PuppetTest::Reporttesting def fakereport # Create a bunch of log messages in an array. - report = Puppet::Transaction::Report.new + report = Puppet::Transaction::Report.new("apply") 3.times { |i| # We have to use warning so that the logs always happen log = Puppet.warning("Report test message #{i}") report << log } report end end diff --git a/test/other/report.rb b/test/other/report.rb index 8a909b41c..eacf1632b 100755 --- a/test/other/report.rb +++ b/test/other/report.rb @@ -1,135 +1,135 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../lib/puppettest' require 'puppet' require 'puppet/reports' require 'puppet/transaction/report' require 'puppettest' require 'puppettest/reporttesting' class TestReports < Test::Unit::TestCase include PuppetTest include PuppetTest::Reporttesting def mkreport # First do some work objects = [] 6.times do |i| file = tempfile # Make every third file File.open(file, "w") { |f| f.puts "" } if i % 3 == 0 objects << Puppet::Type.type(:file).new( :path => file, :ensure => "file" ) end config = mk_catalog(*objects) # So the report works out. config.retrieval_duration = 0.001 trans = config.apply trans.generate_report end # Make sure we can use reports as log destinations. def test_reports_as_log_destinations report = fakereport assert_nothing_raised { Puppet::Util::Log.newdestination(report) } # Now make a file for testing logging file = Puppet::Type.type(:file).new(:path => tempfile, :ensure => "file") file.finish log = nil assert_nothing_raised { log = file.log "This is a message, yo" } assert(report.logs.include?(log), "Report did not get log message") assert_nothing_raised { Puppet::Util::Log.close(report) } log = file.log "This is another message, yo" assert(! report.logs.include?(log), "Report got log message after close") end def test_store_report # Create a bunch of log messages in an array. - report = Puppet::Transaction::Report.new + report = Puppet::Transaction::Report.new("apply") # We have to reuse reporting here because of something going on in the # server/report.rb file Puppet.settings.use(:main, :master) 3.times { |i| log = Puppet.warning("Report test message #{i}") report << log } assert_nothing_raised do report.extend(Puppet::Reports.report(:store)) end yaml = YAML.dump(report) file = report.process assert(FileTest.exists?(file), "report file did not get created") assert_equal(yaml, File.read(file), "File did not get written") end if Puppet.features.rrd? || Puppet.features.rrd_legacy? def test_rrdgraph_report Puppet.settings.use(:main, :metrics) report = mkreport assert(! report.metrics.empty?, "Did not receive any metrics") assert_nothing_raised do report.extend(Puppet::Reports.report(:rrdgraph)) end assert_nothing_raised { report.process } hostdir = nil assert_nothing_raised do hostdir = report.hostdir end assert(hostdir, "Did not get hostdir back") assert(FileTest.directory?(hostdir), "Host rrd dir did not get created") index = File.join(hostdir, "index.html") assert(FileTest.exists?(index), "index file was not created") # Now make sure it creaets each of the rrd files %w{changes resources time}.each do |type| file = File.join(hostdir, "#{type}.rrd") assert(FileTest.exists?(file), "Did not create rrd file for #{type}") daily = file.sub ".rrd", "-daily.png" assert(FileTest.exists?(daily), "Did not make daily graph for #{type}") end end else $stderr.puts "Install RRD for metric reporting tests" end end diff --git a/test/ral/providers/cron/crontab.rb b/test/ral/providers/cron/crontab.rb index 0c87a5bba..be2af1e16 100755 --- a/test/ral/providers/cron/crontab.rb +++ b/test/ral/providers/cron/crontab.rb @@ -1,648 +1,651 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../../lib/puppettest' require 'puppettest' require 'mocha' require 'puppettest/fileparsing' class TestCronParsedProvider < Test::Unit::TestCase include PuppetTest include PuppetTest::FileParsing FIELDS = { :crontab => %w{command minute hour month monthday weekday}.collect { |o| o.intern }, :freebsd_special => %w{special command}.collect { |o| o.intern }, :environment => [:line], :blank => [:line], :comment => [:line], } # These are potentially multi-line records; there's no one-to-one map, but they model # a full cron job. These tests assume individual record types will always be correctly # parsed, so all they def sample_crons @sample_crons ||= YAML.load(File.read(File.join(@crondir, "crontab_collections.yaml"))) end # These are simple lines that can appear in the files; there is a one to one # mapping between records and lines. We have plenty of redundancy here because # we use these records to build up our complex, multi-line cron jobs below. def sample_records @sample_records ||= YAML.load(File.read(File.join(@crondir, "crontab_sample_records.yaml"))) end def setup super @type = Puppet::Type.type(:cron) @provider = @type.provider(:crontab) @provider.initvars @crondir = datadir(File.join(%w{providers cron})) @oldfiletype = @provider.filetype end def teardown Puppet::Util::FileType.filetype(:ram).clear @provider.clear super end # Make sure a cron job matches up. Any non-passed fields are considered absent. def assert_cron_equal(msg, cron, options) assert_instance_of(@provider, cron, "not an instance of provider in #{msg}") options.each do |param, value| assert_equal(value, cron.send(param), "#{param} was not equal in #{msg}") end %w{command environment minute hour month monthday weekday}.each do |var| assert_equal(:absent, cron.send(var), "#{var} was not parsed absent in #{msg}") unless options.include?(var.intern) end end # Make sure a cron record matches. This only works for crontab records. def assert_record_equal(msg, record, options) raise ArgumentError, "You must pass the required record type" unless options.include?(:record_type) assert_instance_of(Hash, record, "not an instance of a hash in #{msg}") options.each do |param, value| assert_equal(value, record[param], "#{param} was not equal in #{msg}") end FIELDS[record[:record_type]].each do |var| assert_equal(:absent, record[var], "#{var} was not parsed absent in #{msg}") unless options.include?(var) end end def assert_header(file) header = [] file.gsub! /^(# HEADER: .+$)\n/ do header << $1 '' end assert_equal(4, header.length, "Did not get four header lines") end # This handles parsing every possible iteration of cron records. Note that this is only # single-line stuff and doesn't include multi-line values (e.g., with names and/or envs). # Those have separate tests. def test_parse_line # First just do each sample record one by one sample_records.each do |name, options| result = nil assert_nothing_raised("Could not parse #{name}: '#{options[:text]}'") do result = @provider.parse_line(options[:text]) end assert_record_equal("record for #{name}", result, options[:record]) end # Then do them all at once. records = [] text = "" - sample_records.each do |name, options| + # Sort sample_records so that the :empty entry does not come last + # (if it does, the test will fail because the empty last line will + # be ignored) + sample_records.sort { |a, b| a.first.to_s <=> b.first.to_s }.each do |name, options| records << options[:record] text += options[:text] + "\n" end result = nil assert_nothing_raised("Could not match all records in one file") do result = @provider.parse(text) end records.zip(result).each do |should, record| assert_record_equal("record for #{should.inspect} in full match", record, should) end end # Here we test that each record generates to the correct text. def test_generate_line # First just do each sample record one by one sample_records.each do |name, options| result = nil assert_nothing_raised("Could not generate #{name}: '#{options[:record]}'") do result = @provider.to_line(options[:record]) end assert_equal(options[:text], result, "Did not generate correct text for #{name}") end # Then do them all at once. records = [] text = "" sample_records.each do |name, options| records << options[:record] text += options[:text] + "\n" end result = nil assert_nothing_raised("Could not match all records in one file") do result = @provider.to_file(records) end assert_header(result) assert_equal(text, result, "Did not generate correct full crontab") end # Test cronjobs that are made up from multiple records. def test_multi_line_cronjobs fulltext = "" all_records = [] sample_crons.each do |name, record_names| records = record_names.collect do |record_name| unless record = sample_records[record_name] raise "Could not find sample record #{record_name}" end record end text = records.collect { |r| r[:text] }.join("\n") + "\n" record_list = records.collect { |r| r[:record] } # Add it to our full collection all_records += record_list fulltext += text # First make sure we generate each one correctly result = nil assert_nothing_raised("Could not generate multi-line cronjob #{name}") do result = @provider.to_file(record_list) end assert_header(result) assert_equal(text, result, "Did not generate correct text for multi-line cronjob #{name}") # Now make sure we parse each one correctly assert_nothing_raised("Could not parse multi-line cronjob #{name}") do result = @provider.parse(text) end record_list.zip(result).each do |should, record| assert_record_equal("multiline cronjob #{name}", record, should) end end # Make sure we can generate it all correctly result = nil assert_nothing_raised("Could not generate all multi-line cronjobs") do result = @provider.to_file(all_records) end assert_header(result) assert_equal(fulltext, result, "Did not generate correct text for all multi-line cronjobs") # Now make sure we parse them all correctly assert_nothing_raised("Could not parse multi-line cronjobs") do result = @provider.parse(fulltext) end all_records.zip(result).each do |should, record| assert_record_equal("multiline cronjob %s", record, should) end end # Take our sample files, and make sure we can entirely parse them, # then that we can generate them again and we get the same data. def test_parse_and_generate_sample_files @provider.stubs(:filetype).returns(Puppet::Util::FileType.filetype(:ram)) crondir = datadir(File.join(%w{providers cron})) files = Dir.glob("#{crondir}/crontab.*") setme @provider.default_target = @me target = @provider.target_object(@me) files.each do |file| str = args = nil assert_nothing_raised("could not load #{file}") do str, args = YAML.load(File.read(file)) end # Stupid old yaml args.each do |hash| hash.each do |param, value| if param.is_a?(String) and param =~ /^:/ hash.delete(param) param = param.sub(/^:/,'').intern hash[param] = value end if value.is_a?(String) and value =~ /^:/ value = value.sub(/^:/,'').intern hash[param] = value end end end target.write(str) assert_nothing_raised("could not parse #{file}") do @provider.prefetch end records = @provider.send(:instance_variable_get, "@records") args.zip(records) do |should, sis| # Make the values a bit more equal. should[:target] = @me should[:ensure] = :present #should[:environment] ||= [] should[:on_disk] = true is = sis.dup sis.dup.each do |p,v| is.delete(p) if v == :absent end assert_equal( should, is, "Did not parse #{file} correctly") end assert_nothing_raised("could not generate #{file}") do @provider.flush_target(@me) end assert_equal(str, target.read, "#{file} changed") @provider.clear end end # A simple test to see if we can load the cron from disk. def test_load setme records = nil assert_nothing_raised { records = @provider.retrieve(@me) } assert_instance_of(Array, records, "did not get correct response") end # Test that a cron job turns out as expected, by creating one and generating # it directly def test_simple_to_cron # make the cron setme name = "yaytest" args = {:name => name, :command => "date > /dev/null", :minute => "30", :user => @me, :record_type => :crontab } # generate the text str = nil assert_nothing_raised { str = @provider.to_line(args) } assert_equal( "# Puppet Name: #{name}\n30 * * * * date > /dev/null", str, "Cron did not generate correctly") end # Test that comments are correctly retained def test_retain_comments str = "# this is a comment\n#and another comment\n" user = "fakeuser" records = nil @provider.stubs(:filetype).returns(Puppet::Util::FileType.filetype(:ram)) target = @provider.target_object(user) target.write(str) assert_nothing_raised { @provider.prefetch } assert_nothing_raised { newstr = @provider.flush_target(user) assert(target.read.include?(str), "Comments were lost") } end def test_simpleparsing @provider.stubs(:filetype).returns(Puppet::Util::FileType.filetype(:ram)) text = "5 1,2 * 1 0 /bin/echo funtest" records = nil assert_nothing_raised { records = @provider.parse(text) } should = { :minute => %w{5}, :hour => %w{1 2}, :monthday => :absent, :month => %w{1}, :weekday => %w{0}, :command => "/bin/echo funtest" } is = records.shift assert(is, "Did not get record") should.each do |p, v| assert_equal(v, is[p], "did not parse #{p} correctly") end end # Make sure we can create a cron in an empty tab. # LAK:FIXME This actually modifies the user's crontab, # which is pretty heinous. def test_mkcron_if_empty setme @provider.filetype = @oldfiletype records = @provider.retrieve(@me) target = @provider.target_object(@me) cleanup do if records.length == 0 target.remove else target.write(@provider.to_file(records)) end end # Now get rid of it assert_nothing_raised("Could not remove cron tab") do target.remove end @provider.flush :target => @me, :command => "/do/something", :record_type => :crontab created = @provider.retrieve(@me) assert(created.detect { |r| r[:command] == "/do/something" }, "Did not create cron tab") end # Make sure we correctly bidirectionally parse things. def test_records_and_strings @provider.stubs(:filetype).returns(Puppet::Util::FileType.filetype(:ram)) setme target = @provider.target_object(@me) [ "* * * * * /some/command", "0,30 * * * * /some/command", "0-30 * * * * /some/command", "# Puppet Name: name\n0-30 * * * * /some/command", "# Puppet Name: name\nVAR=VALUE\n0-30 * * * * /some/command", "# Puppet Name: name\nVAR=VALUE\nC=D\n0-30 * * * * /some/command", "0 * * * * /some/command" ].each do |str| @provider.initvars str += "\n" target.write(str) assert_equal( str, target.read, "Did not write correctly") assert_nothing_raised("Could not prefetch with #{str.inspect}") do @provider.prefetch end assert_nothing_raised("Could not flush with #{str.inspect}") do @provider.flush_target(@me) end assert_equal( str, target.read, "Changed in read/write") @provider.clear end end # Test that a specified cron job will be matched against an existing job # with no name, as long as all fields match def test_matchcron mecron = "0,30 * * * * date * * * * * funtest # a comment 0,30 * * 1 * date " youcron = "0,30 * * * * date * * * * * yaytest # a comment 0,30 * * 1 * fooness " setme @provider.stubs(:filetype).returns(Puppet::Util::FileType.filetype(:ram)) you = "you" # Write the same tab to multiple targets @provider.target_object(@me).write(mecron.gsub(/^\s+/, '')) @provider.target_object(you).write(youcron.gsub(/^\s+/, '')) # Now make some crons that should match matchers = [ @type.new( :name => "yaycron", :minute => [0, 30], :command => "date", :user => @me ), @type.new( :name => "youtest", :command => "yaytest", :user => you ) ] nonmatchers = [ @type.new( :name => "footest", :minute => [0, 30], :hour => 1, :command => "fooness", :user => @me # wrong target ), @type.new( :name => "funtest2", :command => "funtest", :user => you # wrong target for this cron ) ] # Create another cron so we prefetch two of them @type.new(:name => "testing", :minute => 30, :command => "whatever", :user => "you") assert_nothing_raised("Could not prefetch cron") do @provider.prefetch([matchers, nonmatchers].flatten.inject({}) { |crons, cron| crons[cron.name] = cron; crons }) end matchers.each do |cron| assert_equal(:present, cron.provider.ensure, "Cron #{cron.name} was not matched") if value = cron.value(:minute) and value == "*" value = :absent end assert_equal(value, cron.provider.minute, "Minutes were not retrieved, so cron was not matched") assert_equal(cron.value(:target), cron.provider.target, "Cron #{cron.name} was matched from the wrong target") end nonmatchers.each do |cron| assert_equal(:absent, cron.provider.ensure, "Cron #{cron.name} was incorrectly matched") end end def test_data setme @provider.stubs(:filetype).returns(Puppet::Util::FileType.filetype(:ram)) target = @provider.target_object(@me) fakedata("data/providers/cron/examples").each do |file| text = File.read(file) target.write(text) assert_nothing_raised("Could not parse #{file}") do @provider.prefetch end # mark the provider modified @provider.modified(@me) # and zero the text target.write("") result = nil assert_nothing_raised("Could not generate #{file}") do @provider.flush_target(@me) end # Ignore whitespace differences, since those don't affect function. modtext = text.gsub(/[ \t]+/, " ") modtarget = target.read.gsub(/[ \t]+/, " ") assert_equal(modtext, modtarget, "File was not rewritten the same") @provider.clear end end # Match freebsd's annoying @daily stuff. def test_match_freebsd_special @provider.stubs(:filetype).returns(Puppet::Util::FileType.filetype(:ram)) setme target = @provider.target_object(@me) [ "@daily /some/command", "@daily /some/command more" ].each do |str| @provider.initvars str += "\n" target.write(str) assert_nothing_raised("Could not prefetch with #{str.inspect}") do @provider.prefetch end records = @provider.send(:instance_variable_get, "@records") records.each do |r| assert_equal( :freebsd_special, r[:record_type], "Did not create lines as freebsd lines") end assert_nothing_raised("Could not flush with #{str.inspect}") do @provider.flush_target(@me) end assert_equal( str, target.read, "Changed in read/write") @provider.clear end end # #707 def test_write_freebsd_special assert_equal(@provider.to_line(:record_type => :crontab, :ensure => :present, :special => "reboot", :command => "/bin/echo something"), "@reboot /bin/echo something") end def test_prefetch cron = @type.new :command => "/bin/echo yay", :name => "test", :hour => 4 assert_nothing_raised("Could not prefetch cron") do cron.provider.class.prefetch("test" => cron) end end # Testing #669. def test_environment_settings @provider.stubs(:filetype).returns(Puppet::Util::FileType.filetype(:ram)) setme target = @provider.target_object(@me) # First with no env settings resource = @type.new :command => "/bin/echo yay", :name => "test", :hour => 4 cron = resource.provider cron.ensure = :present cron.command = "/bin/echo yay" cron.hour = %w{4} cron.flush result = target.read assert_equal("# Puppet Name: test\n* 4 * * * /bin/echo yay\n", result, "Did not write cron out correctly") # Now set the env cron.environment = "TEST=foo" cron.flush result = target.read assert_equal("# Puppet Name: test\nTEST=foo\n* 4 * * * /bin/echo yay\n", result, "Did not write out environment setting") # Modify it cron.environment = ["TEST=foo", "BLAH=yay"] cron.flush result = target.read assert_equal("# Puppet Name: test\nTEST=foo\nBLAH=yay\n* 4 * * * /bin/echo yay\n", result, "Did not write out environment setting") # And remove it cron.environment = :absent cron.flush result = target.read assert_equal("# Puppet Name: test\n* 4 * * * /bin/echo yay\n", result, "Did not write out environment setting") end # Testing #1216 def test_strange_lines @provider.stubs(:filetype).returns(Puppet::Util::FileType.filetype(:ram)) text = " 5 \t\t 1,2 * 1 0 /bin/echo funtest" records = nil assert_nothing_raised { records = @provider.parse(text) } should = { :minute => %w{5}, :hour => %w{1 2}, :monthday => :absent, :month => %w{1}, :weekday => %w{0}, :command => "/bin/echo funtest" } is = records.shift assert(is, "Did not get record") should.each do |p, v| assert_equal(v, is[p], "did not parse #{p} correctly") end end end