diff --git a/lib/puppet/node/environment.rb b/lib/puppet/node/environment.rb index a6fe39b9c..69ab3ab6b 100644 --- a/lib/puppet/node/environment.rb +++ b/lib/puppet/node/environment.rb @@ -1,215 +1,216 @@ require 'puppet/util' require 'puppet/util/cacher' require 'monitor' # Just define it, so this class has fewer load dependencies. class Puppet::Node end # Model the environment that a node can operate in. This class just # provides a simple wrapper for the functionality around environments. class Puppet::Node::Environment module 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 end include Puppet::Util::Cacher @seen = {} # Return an existing environment instance, or create a new one. def self.new(name = nil) return name if name.is_a?(self) name ||= Puppet.settings.value(:environment) raise ArgumentError, "Environment name must be specified" unless name symbol = name.to_sym return @seen[symbol] if @seen[symbol] obj = self.allocate obj.send :initialize, symbol @seen[symbol] = obj end def self.current Thread.current[:environment] || root end def self.current=(env) Thread.current[:environment] = new(env) end def self.root @root end def self.clear @seen.clear + self.current = nil end attr_reader :name # Return an environment-specific setting. def [](param) Puppet.settings.value(param, self.name) end def initialize(name) @name = name extend MonitorMixin end def known_resource_types # This makes use of short circuit evaluation to get the right thread-safe # per environment semantics with an efficient most common cases; we almost # always just return our thread's known-resource types. Only at the start # of a compilation (after our thread var has been set to nil) or when the # environment has changed do we delve deeper. Thread.current[:known_resource_types] = nil if (krt = Thread.current[:known_resource_types]) && krt.environment != self Thread.current[:known_resource_types] ||= synchronize { if @known_resource_types.nil? or @known_resource_types.require_reparse? @known_resource_types = Puppet::Resource::TypeCollection.new(self) @known_resource_types.import_ast(perform_initial_import, '') end @known_resource_types } end def module(name) modules.find {|mod| mod.name == name} end def module_by_forge_name(forge_name) author, modname = forge_name.split('/') found_mod = self.module(modname) found_mod and found_mod.forge_name == forge_name ? found_mod : nil end # Cache the modulepath, so that we aren't searching through # all known directories all the time. cached_attr(:modulepath, Puppet[:filetimeout]) do dirs = self[:modulepath].split(File::PATH_SEPARATOR) dirs = ENV["PUPPETLIB"].split(File::PATH_SEPARATOR) + dirs if ENV["PUPPETLIB"] validate_dirs(dirs) end # Return all modules from this environment. # Cache the list, because it can be expensive to create. cached_attr(:modules, Puppet[:filetimeout]) do module_names = modulepath.collect { |path| Dir.entries(path) }.flatten.uniq module_names.collect do |path| begin Puppet::Module.new(path, :environment => self) rescue Puppet::Module::Error => e nil end end.compact end # Modules broken out by directory in the modulepath def modules_by_path modules_by_path = {} modulepath.each do |path| Dir.chdir(path) do module_names = Dir.glob('*').select do |d| FileTest.directory?(d) && (File.basename(d) =~ /^[\w]+([-]{1}[\w]+)*$/) end modules_by_path[path] = module_names.sort.map do |name| Puppet::Module.new(name, :environment => self, :path => File.join(path, name)) end end end modules_by_path end def module_requirements deps = {} modules.each do |mod| next unless mod.forge_name deps[mod.forge_name] ||= [] mod.dependencies and mod.dependencies.each do |mod_dep| deps[mod_dep['name']] ||= [] dep_details = { 'name' => mod.forge_name, 'version' => mod.version, 'version_requirement' => mod_dep['version_requirement'] } deps[mod_dep['name']] << dep_details end end deps.each do |mod, mod_deps| deps[mod] = mod_deps.sort_by {|d| d['name']} end deps end def to_s name.to_s end def to_sym to_s.to_sym end # The only thing we care about when serializing an environment is its # identity; everything else is ephemeral and should not be stored or # transmitted. def to_zaml(z) self.to_s.to_zaml(z) end def validate_dirs(dirs) dirs.collect do |dir| unless Puppet::Util.absolute_path?(dir) File.expand_path(File.join(Dir.getwd, dir)) else dir end end.find_all do |p| Puppet::Util.absolute_path?(p) && FileTest.directory?(p) end end private def perform_initial_import return empty_parse_result if Puppet.settings[:ignoreimport] parser = Puppet::Parser::Parser.new(self) if code = Puppet.settings.uninterpolated_value(:code, name.to_s) and code != "" parser.string = code else file = Puppet.settings.value(:manifest, name.to_s) parser.file = file end parser.parse rescue => detail known_resource_types.parse_failed = true msg = "Could not parse for environment #{self}: #{detail}" error = Puppet::Error.new(msg) error.set_backtrace(detail.backtrace) raise error end def empty_parse_result # Return an empty toplevel hostclass to use as the result of # perform_initial_import when no file was actually loaded. return Puppet::Parser::AST::Hostclass.new('') end @root = new(:'*root*') end diff --git a/lib/puppet/parser/functions.rb b/lib/puppet/parser/functions.rb index e19ac127f..df36e6349 100644 --- a/lib/puppet/parser/functions.rb +++ b/lib/puppet/parser/functions.rb @@ -1,119 +1,123 @@ require 'puppet/util/autoload' require 'puppet/parser/scope' require 'monitor' # A module for managing parser functions. Each specified function # is added to a central module that then gets included into the Scope # class. module Puppet::Parser::Functions - - (@functions = Hash.new { |h,k| h[k] = {} }).extend(MonitorMixin) - (@modules = {} ).extend(MonitorMixin) + Environment = Puppet::Node::Environment class << self include Puppet::Util end + # This is used by tests + def self.reset + @functions = Hash.new { |h,k| h[k] = {} }.extend(MonitorMixin) + @modules = Hash.new.extend(MonitorMixin) + end + def self.autoloader unless defined?(@autoloader) @autoloader = Puppet::Util::Autoload.new( self, "puppet/parser/functions", :wrap => false ) end @autoloader end - Environment = Puppet::Node::Environment - def self.environment_module(env = nil) @modules.synchronize { @modules[ env || Environment.current || Environment.root ] ||= Module.new } end # Create a new function type. def self.newfunction(name, options = {}, &block) name = symbolize(name) raise Puppet::DevError, "Function #{name} already defined" if functions.include?(name) ftype = options[:type] || :statement unless ftype == :statement or ftype == :rvalue raise Puppet::DevError, "Invalid statement type #{ftype.inspect}" end fname = "function_#{name}" environment_module.send(:define_method, fname, &block) # Someday we'll support specifying an arity, but for now, nope #functions[name] = {:arity => arity, :type => ftype} functions[name] = {:type => ftype, :name => fname} functions[name][:doc] = options[:doc] if options[:doc] end # Remove a function added by newfunction def self.rmfunction(name) name = symbolize(name) raise Puppet::DevError, "Function #{name} is not defined" unless functions.include? name functions.delete name fname = "function_#{name}" environment_module.send(:remove_method, fname) end # Determine if a given name is a function def self.function(name) name = symbolize(name) @functions.synchronize do unless functions.include?(name) or functions(Puppet::Node::Environment.root).include?(name) autoloader.load(name,Environment.current || Environment.root) end end ( functions(Environment.root)[name] || functions[name] || {:name => false} )[:name] end def self.functiondocs autoloader.loadall ret = "" functions.sort { |a,b| a[0].to_s <=> b[0].to_s }.each do |name, hash| ret += "#{name}\n#{"-" * name.to_s.length}\n" if hash[:doc] ret += Puppet::Util::Docs.scrub(hash[:doc]) else ret += "Undocumented.\n" end ret += "\n\n- *Type*: #{hash[:type]}\n\n" end ret end def self.functions(env = nil) @functions.synchronize { @functions[ env || Environment.current || Environment.root ] } end # Determine if a given function returns a value or not. def self.rvalue?(name) (functions[symbolize(name)] || {})[:type] == :rvalue end + reset # initialize the class instance variables + # Runs a newfunction to create a function for each of the log levels Puppet::Util::Log.levels.each do |level| newfunction(level, :doc => "Log a message on the server at level #{level.to_s}.") do |vals| send(level, vals.join(" ")) end end end diff --git a/lib/puppet/parser/scope.rb b/lib/puppet/parser/scope.rb index eccc01b9f..a92f3bcb4 100644 --- a/lib/puppet/parser/scope.rb +++ b/lib/puppet/parser/scope.rb @@ -1,494 +1,497 @@ # The scope class, which handles storing and retrieving variables and types and # such. require 'puppet/parser/parser' require 'puppet/parser/templatewrapper' require 'puppet/transportable' require 'strscan' require 'puppet/resource/type_collection_helper' class Puppet::Parser::Scope include Puppet::Resource::TypeCollectionHelper require 'puppet/parser/resource' AST = Puppet::Parser::AST Puppet::Util.logmethods(self) include Enumerable include Puppet::Util::Errors attr_accessor :source, :resource attr_accessor :base, :keyword attr_accessor :top, :translated, :compiler attr_accessor :parent attr_reader :namespaces # thin wrapper around an ephemeral # symbol table. # when a symbol class Ephemeral def initialize(parent=nil) @symbols = {} @parent = parent end [:include?, :delete, :[]=].each do |m| define_method(m) do |*args| @symbols.send(m, *args) end end def [](name) unless @symbols.include?(name) or @parent.nil? @parent[name] else @symbols[name] end end end # A demeterific shortcut to the catalog. def catalog compiler.catalog end def environment compiler.environment end # Proxy accessors def host @compiler.node.name end # Is the value true? This allows us to control the definition of truth # in one place. def self.true?(value) (value != false and value != "" and value != :undef) end # Is the value a number?, return the correct object or nil if not a number def self.number?(value) return nil unless value.is_a?(Fixnum) or value.is_a?(Bignum) or value.is_a?(Float) or value.is_a?(String) if value.is_a?(String) if value =~ /^-?\d+(:?\.\d+|(:?\.\d+)?e\d+)$/ return value.to_f elsif value =~ /^0x[0-9a-f]+$/i return value.to_i(16) elsif value =~ /^0[0-7]+$/ return value.to_i(8) elsif value =~ /^-?\d+$/ return value.to_i else return nil end end # it is one of Fixnum,Bignum or Float value end # Add to our list of namespaces. def add_namespace(ns) return false if @namespaces.include?(ns) if @namespaces == [""] @namespaces = [ns] else @namespaces << ns end end # Remove this when rebasing def environment compiler ? compiler.environment : nil end def find_hostclass(name) known_resource_types.find_hostclass(namespaces, name) end def find_definition(name) known_resource_types.find_definition(namespaces, name) end def findresource(string, name = nil) compiler.findresource(string, name) end # Initialize our new scope. Defaults to having no parent. def initialize(hash = {}) if hash.include?(:namespace) if n = hash[:namespace] @namespaces = [n] end hash.delete(:namespace) else @namespaces = [""] end hash.each { |name, val| method = name.to_s + "=" if self.respond_to? method self.send(method, val) else raise Puppet::DevError, "Invalid scope argument #{name}" end } extend_with_functions_module @tags = [] # The symbol table for this scope. This is where we store variables. @symtable = {} # the ephemeral symbol tables # those should not persist long, and are used for the moment only # for $0..$xy capture variables of regexes # this is actually implemented as a stack, with each ephemeral scope # shadowing the previous one @ephemeral = [ Ephemeral.new ] # All of the defaults set for types. It's a hash of hashes, # with the first key being the type, then the second key being # the parameter. @defaults = Hash.new { |dhash,type| dhash[type] = {} } # The table for storing class singletons. This will only actually # be used by top scopes and node scopes. @class_scopes = {} end # Store the fact that we've evaluated a class, and store a reference to # the scope in which it was evaluated, so that we can look it up later. def class_set(name, scope) return parent.class_set(name,scope) if parent @class_scopes[name] = scope end # Return the scope associated with a class. This is just here so # that subclasses can set their parent scopes to be the scope of # their parent class, and it's also used when looking up qualified # variables. def class_scope(klass) # They might pass in either the class or class name k = klass.respond_to?(:name) ? klass.name : klass @class_scopes[k] || (parent && parent.class_scope(k)) end # Collect all of the defaults set at any higher scopes. # This is a different type of lookup because it's additive -- # it collects all of the defaults, with defaults in closer scopes # overriding those in later scopes. def lookupdefaults(type) values = {} # first collect the values from the parents unless parent.nil? parent.lookupdefaults(type).each { |var,value| values[var] = value } end # then override them with any current values # this should probably be done differently if @defaults.include?(type) @defaults[type].each { |var,value| values[var] = value } end #Puppet.debug "Got defaults for %s: %s" % # [type,values.inspect] values end # Look up a defined type. def lookuptype(name) find_definition(name) || find_hostclass(name) end def undef_as(x,v) (v == :undefined) ? x : (v == :undef) ? x : v end def qualified_scope(classname) raise "class #{classname} could not be found" unless klass = find_hostclass(classname) raise "class #{classname} has not been evaluated" unless kscope = class_scope(klass) kscope end private :qualified_scope # Look up a variable with traditional scoping and then with new scoping. If # the answers differ then print a deprecation warning. def lookupvar(name, options = {}) dynamic_value = dynamic_lookupvar(name,options) twoscope_value = twoscope_lookupvar(name,options) if dynamic_value != twoscope_value location = (options[:file] && options[:line]) ? " at #{options[:file]}:#{options[:line]}" : '' Puppet.deprecation_warning("Dynamic lookup of $#{name}#{location} is deprecated. For more information, see http://docs.puppetlabs.com/guides/scope_and_puppet.html. To see the change in behavior, use the --debug flag.") Puppet.debug("Currently $#{name} is #{dynamic_value.inspect}") Puppet.debug("In the future $#{name} will be #{twoscope_value == :undefined ? "undefined" : twoscope_value.inspect}") end dynamic_value end # Look up a variable. The simplest value search we do. def twoscope_lookupvar(name, options = {}) # Save the originating scope for the request options[:origin] = self unless options[:origin] table = ephemeral?(name) ? @ephemeral.last : @symtable if name =~ /^(.*)::(.+)$/ begin qualified_scope($1).twoscope_lookupvar($2, options.merge({:origin => nil})) rescue RuntimeError => e location = (options[:file] && options[:line]) ? " at #{options[:file]}:#{options[:line]}" : '' warning "Could not look up qualified variable '#{name}'; #{e.message}#{location}" :undefined end # If the value is present and either we are top/node scope or originating scope... elsif (ephemeral_include?(name) or table.include?(name)) and (compiler and self == compiler.topscope or (resource and resource.type == "Node") or self == options[:origin]) table[name] elsif resource and resource.type == "Class" and parent_type = resource.resource_type.parent qualified_scope(parent_type).twoscope_lookupvar(name,options.merge({:origin => nil})) elsif parent parent.twoscope_lookupvar(name, options) else :undefined end end # Look up a variable. The simplest value search we do. def dynamic_lookupvar(name, options = {}) table = ephemeral?(name) ? @ephemeral.last : @symtable # If the variable is qualified, then find the specified scope and look the variable up there instead. if name =~ /^(.*)::(.+)$/ begin qualified_scope($1).dynamic_lookupvar($2,options) rescue RuntimeError => e location = (options[:file] && options[:line]) ? " at #{options[:file]}:#{options[:line]}" : '' warning "Could not look up qualified variable '#{name}'; #{e.message}#{location}" :undefined end elsif ephemeral_include?(name) or table.include?(name) # We can't use "if table[name]" here because the value might be false table[name] elsif parent parent.dynamic_lookupvar(name,options) else :undefined end end # Return a hash containing our variables and their values, optionally (and # by default) including the values defined in our parent. Local values # shadow parent values. def to_hash(recursive = true) target = parent.to_hash(recursive) if recursive and parent target ||= Hash.new @symtable.keys.each { |name| value = @symtable[name] if value == :undef target.delete(name) else target[name] = value end } target end def namespaces @namespaces.dup end # Create a new scope and set these options. def newscope(options = {}) compiler.newscope(self, options) end def parent_module_name return nil unless @parent return nil unless @parent.source @parent.source.module_name end # Return the list of scopes up to the top scope, ordered with our own first. # This is used for looking up variables and defaults. def scope_path if parent [self, parent.scope_path].flatten.compact else [self] end end # Set defaults for a type. The typename should already be downcased, # so that the syntax is isolated. We don't do any kind of type-checking # here; instead we let the resource do it when the defaults are used. def setdefaults(type, params) table = @defaults[type] # if we got a single param, it'll be in its own array params = [params] unless params.is_a?(Array) params.each { |param| #Puppet.debug "Default for %s is %s => %s" % # [type,ary[0].inspect,ary[1].inspect] if table.include?(param.name) raise Puppet::ParseError.new("Default already defined for #{type} { #{param.name} }; cannot redefine", param.line, param.file) end table[param.name] = param } end # Set a variable in the current scope. This will override settings # in scopes above, but will not allow variables in the current scope # to be reassigned. def setvar(name,value, options = {}) table = options[:ephemeral] ? @ephemeral.last : @symtable if table.include?(name) unless options[:append] error = Puppet::ParseError.new("Cannot reassign variable #{name}") else error = Puppet::ParseError.new("Cannot append, variable #{name} is defined in this scope") end error.file = options[:file] if options[:file] error.line = options[:line] if options[:line] raise error end unless options[:append] table[name] = value else # append case # lookup the value in the scope if it exists and insert the var table[name] = undef_as('',lookupvar(name)) # concatenate if string, append if array, nothing for other types case value when Array table[name] += value when Hash raise ArgumentError, "Trying to append to a hash with something which is not a hash is unsupported" unless value.is_a?(Hash) table[name].merge!(value) else table[name] << value end end end # Return the tags associated with this scope. It's basically # just our parents' tags, plus our type. We don't cache this value # because our parent tags might change between calls. def tags resource.tags end # Used mainly for logging def to_s "Scope(#{@resource})" end # Undefine a variable; only used for testing. def unsetvar(var) table = ephemeral?(var) ? @ephemeral.last : @symtable table.delete(var) if table.include?(var) end # remove ephemeral scope up to level def unset_ephemeral_var(level=:all) if level == :all @ephemeral = [ Ephemeral.new ] else (@ephemeral.size - level).times do @ephemeral.pop end end end # check if name exists in one of the ephemeral scope. def ephemeral_include?(name) @ephemeral.reverse_each do |eph| return true if eph.include?(name) end false end # is name an ephemeral variable? def ephemeral?(name) name =~ /^\d+$/ end def ephemeral_level @ephemeral.size end def new_ephemeral @ephemeral.push(Ephemeral.new(@ephemeral.last)) end def ephemeral_from(match, file = nil, line = nil) raise(ArgumentError,"Invalid regex match data") unless match.is_a?(MatchData) new_ephemeral setvar("0", match[0], :file => file, :line => line, :ephemeral => true) match.captures.each_with_index do |m,i| setvar("#{i+1}", m, :file => file, :line => line, :ephemeral => true) end end def find_resource_type(type) # It still works fine without the type == 'class' short-cut, but it is a lot slower. return nil if ["class", "node"].include? type.to_s.downcase find_builtin_resource_type(type) || find_defined_resource_type(type) end def find_builtin_resource_type(type) Puppet::Type.type(type.to_s.downcase.to_sym) end def find_defined_resource_type(type) environment.known_resource_types.find_definition(namespaces, type.to_s.downcase) end def method_missing(method, *args, &block) method.to_s =~ /^function_(.*)$/ super unless $1 super unless Puppet::Parser::Functions.function($1) - - # Calling .function(name) adds "function_#{name}" as a callable method on - # self if it's found, so now we can just send it - send(method, *args) + # In odd circumstances, this might not end up defined by the previous + # method, so we might as well be certain. + if respond_to? method + send(method, *args) + else + raise Puppet::DevError, "Function #{$1} not defined despite being loaded!" + end end def resolve_type_and_titles(type, titles) raise ArgumentError, "titles must be an array" unless titles.is_a?(Array) case type.downcase when "class" # resolve the titles titles = titles.collect do |a_title| hostclass = find_hostclass(a_title) hostclass ? hostclass.name : a_title end when "node" # no-op else # resolve the type resource_type = find_resource_type(type) type = resource_type.name if resource_type end return [type, titles] end private def extend_with_functions_module extend Puppet::Parser::Functions.environment_module(Puppet::Node::Environment.root) extend Puppet::Parser::Functions.environment_module(environment) end end diff --git a/lib/puppet/test/test_helper.rb b/lib/puppet/test/test_helper.rb index 9a05b4db3..f8fe15379 100644 --- a/lib/puppet/test/test_helper.rb +++ b/lib/puppet/test/test_helper.rb @@ -1,139 +1,141 @@ module Puppet::Test # This class is intended to provide an API to be used by external projects # when they are running tests that depend on puppet core. This should # allow us to vary the implementation details of managing puppet's state # for testing, from one version of puppet to the next--without forcing # the external projects to do any of that state management or be aware of # the implementation details. # # For now, this consists of a few very simple signatures. The plan is # that it should be the responsibility of the puppetlabs_spec_helper # to broker between external projects and this API; thus, if any # hacks are required (e.g. to determine whether or not a particular) # version of puppet supports this API, those hacks will be consolidated in # one place and won't need to be duplicated in every external project. # # This should also alleviate the anti-pattern that we've been following, # wherein each external project starts off with a copy of puppet core's # test_helper.rb and is exposed to risk of that code getting out of # sync with core. # # Since this class will be "library code" that ships with puppet, it does # not use API from any existing test framework such as rspec. This should # theoretically allow it to be used with other unit test frameworks in the # future, if desired. # # Note that in the future this API could potentially be expanded to handle # other features such as "around_test", but we didn't see a compelling # reason to deal with that right now. class TestHelper # Call this method once, when beginning a test run--prior to running # any individual tests. # @return nil def self.before_all_tests() end # Call this method once, at the end of a test run, when no more tests # will be run. # @return nil def self.after_all_tests() end # Call this method once per test, prior to execution of each invididual test. # @return nil def self.before_each_test() # We need to preserve the current state of all our indirection cache and # terminus classes. This is pretty important, because changes to these # are global and lead to order dependencies in our testing. # # We go direct to the implementation because there is no safe, sane public # API to manage restoration of these to their default values. This # should, once the value is proved, be moved to a standard API on the # indirector. # # To make things worse, a number of the tests stub parts of the # indirector. These stubs have very specific expectations that what # little of the public API we could use is, well, likely to explode # randomly in some tests. So, direct access. --daniel 2011-08-30 $saved_indirection_state = {} indirections = Puppet::Indirector::Indirection.send(:class_variable_get, :@@indirections) indirections.each do |indirector| $saved_indirection_state[indirector.name] = { :@terminus_class => indirector.instance_variable_get(:@terminus_class), :@cache_class => indirector.instance_variable_get(:@cache_class) } end initialize_settings_before_each() # Longer keys are secure, but they sure make for some slow testing - both # in terms of generating keys, and in terms of anything the next step down # the line doing validation or whatever. Most tests don't care how long # or secure it is, just that it exists, so these are better and faster # defaults, in testing only. # # I would make these even shorter, but OpenSSL doesn't support anything # below 512 bits. Sad, really, because a 0 bit key would be just fine. Puppet[:req_bits] = 512 Puppet[:keylength] = 512 + Puppet::Node::Environment.clear + Puppet::Parser::Functions.reset + Puppet.clear_deprecation_warnings end # Call this method once per test, after execution of each individual test. # @return nil def self.after_each_test() clear_settings_after_each() - Puppet::Node::Environment.clear Puppet::Util::Storage.clear Puppet::Util::ExecutionStub.reset # Restore the indirector configuration. See before hook. indirections = Puppet::Indirector::Indirection.send(:class_variable_get, :@@indirections) indirections.each do |indirector| $saved_indirection_state.fetch(indirector.name, {}).each do |variable, value| indirector.instance_variable_set(variable, value) end end $saved_indirection_state = nil # Some tests can cause us to connect, in which case the lingering # connection is a resource that can cause unexpected failure in later # tests, as well as sharing state accidentally. # We're testing if ActiveRecord::Base is defined because some test cases # may stub Puppet.features.rails? which is how we should normally # introspect for this functionality. ActiveRecord::Base.remove_connection if defined?(ActiveRecord::Base) end ######################################################################################### # PRIVATE METHODS (not part of the public TestHelper API--do not call these from outside # of this class!) ######################################################################################### def self.initialize_settings_before_each() # 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[:bindaddress] = "127.0.0.1" end private_class_method :initialize_settings_before_each def self.clear_settings_after_each() Puppet.settings.clear end private_class_method :clear_settings_after_each end end diff --git a/spec/integration/defaults_spec.rb b/spec/integration/defaults_spec.rb index 6e0b504a2..8e1bb5f93 100755 --- a/spec/integration/defaults_spec.rb +++ b/spec/integration/defaults_spec.rb @@ -1,317 +1,317 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/defaults' require 'puppet/rails' describe "Puppet defaults" do include Puppet::Util::Execution after { Puppet.settings.clear } describe "when setting the :factpath" do it "should add the :factpath to Facter's search paths" do Facter.expects(:search).with("/my/fact/path") Puppet.settings[:factpath] = "/my/fact/path" end end describe "when setting the :certname" do it "should fail if the certname is not downcased" do expect { Puppet.settings[:certname] = "Host.Domain.Com" }.to raise_error(ArgumentError) end end describe "when setting :node_name_value" do it "should default to the value of :certname" do Puppet.settings[:certname] = 'blargle' Puppet.settings[:node_name_value].should == 'blargle' end end describe "when setting the :node_name_fact" do it "should fail when also setting :node_name_value" do lambda do Puppet.settings[:node_name_value] = "some value" Puppet.settings[:node_name_fact] = "some_fact" end.should raise_error("Cannot specify both the node_name_value and node_name_fact settings") end it "should not fail when using the default for :node_name_value" do lambda do Puppet.settings[:node_name_fact] = "some_fact" end.should_not raise_error end end describe "when :certdnsnames is set" do it "should not fail" do expect { Puppet[:certdnsnames] = 'fred:wilma' }.to_not raise_error end it "should warn the value is ignored" do Puppet.expects(:warning).with {|msg| msg =~ /CVE-2011-3872/ } Puppet[:certdnsnames] = 'fred:wilma' end end describe "when configuring the :crl" do it "should warn if :cacrl is set to false" do Puppet.expects(:warning) Puppet.settings[:cacrl] = 'false' end end describe "when setting the :catalog_format" do it "should log a deprecation notice" do Puppet.expects(:warning) Puppet.settings[:catalog_format] = 'marshal' end it "should copy the value to :preferred_serialization_format" do Puppet.settings[:catalog_format] = 'marshal' Puppet.settings[:preferred_serialization_format].should == 'marshal' end end it "should have a clientyamldir setting" do Puppet.settings[:clientyamldir].should_not be_nil end it "should have different values for the yamldir and clientyamldir" do Puppet.settings[:yamldir].should_not == Puppet.settings[:clientyamldir] end it "should have a client_datadir setting" do Puppet.settings[:client_datadir].should_not be_nil end it "should have different values for the server_datadir and client_datadir" do Puppet.settings[:server_datadir].should_not == Puppet.settings[:client_datadir] end # See #1232 it "should not specify a user or group for the clientyamldir" do Puppet.settings.setting(:clientyamldir).owner.should be_nil Puppet.settings.setting(:clientyamldir).group.should be_nil end it "should use the service user and group for the yamldir" do Puppet.settings.stubs(:service_user_available?).returns true Puppet.settings.setting(:yamldir).owner.should == Puppet.settings[:user] Puppet.settings.setting(:yamldir).group.should == Puppet.settings[:group] end # See #1232 it "should not specify a user or group for the rundir" do Puppet.settings.setting(:rundir).owner.should be_nil Puppet.settings.setting(:rundir).group.should be_nil end it "should specify that the host private key should be owned by the service user" do Puppet.settings.stubs(:service_user_available?).returns true Puppet.settings.setting(:hostprivkey).owner.should == Puppet.settings[:user] end it "should specify that the host certificate should be owned by the service user" do Puppet.settings.stubs(:service_user_available?).returns true Puppet.settings.setting(:hostcert).owner.should == Puppet.settings[:user] end it "should use a bind address of ''" do Puppet.settings.clear Puppet.settings[:bindaddress].should == "" end [:factdest].each do |setting| it "should force the :factdest to be a directory" do Puppet.settings[setting].should =~ /\/$/ end end [:modulepath, :factpath].each do |setting| it "should configure '#{setting}' not to be a file setting, so multi-directory settings are acceptable" do Puppet.settings.setting(setting).should be_instance_of(Puppet::Util::Settings::Setting) end end it "should add /usr/sbin and /sbin to the path if they're not there" do withenv("PATH" => "/usr/bin:/usr/local/bin") do Puppet.settings[:path] = "none" # this causes it to ignore the setting ENV["PATH"].split(File::PATH_SEPARATOR).should be_include("/usr/sbin") ENV["PATH"].split(File::PATH_SEPARATOR).should be_include("/sbin") end end it "should default to pson for the preferred serialization format" do Puppet.settings.value(:preferred_serialization_format).should == "pson" end describe "when enabling storeconfigs" do before do Puppet::Resource::Catalog.indirection.stubs(:cache_class=) Puppet::Node::Facts.indirection.stubs(:cache_class=) Puppet::Node.indirection.stubs(:cache_class=) Puppet.features.stubs(:rails?).returns true end it "should set the Catalog cache class to :store_configs" do Puppet::Resource::Catalog.indirection.expects(:cache_class=).with(:store_configs) Puppet.settings[:storeconfigs] = true end it "should not set the Catalog cache class to :store_configs if asynchronous storeconfigs is enabled" do Puppet::Resource::Catalog.indirection.expects(:cache_class=).with(:store_configs).never - Puppet.settings.expects(:value).with(:async_storeconfigs).returns true + Puppet.settings[:async_storeconfigs] = true Puppet.settings[:storeconfigs] = true end it "should set the Facts cache class to :store_configs" do Puppet::Node::Facts.indirection.expects(:cache_class=).with(:store_configs) Puppet.settings[:storeconfigs] = true end it "should set the Node cache class to :store_configs" do Puppet::Node.indirection.expects(:cache_class=).with(:store_configs) Puppet.settings[:storeconfigs] = true end end describe "when enabling asynchronous storeconfigs" do before do Puppet::Resource::Catalog.indirection.stubs(:cache_class=) Puppet::Node::Facts.indirection.stubs(:cache_class=) Puppet::Node.indirection.stubs(:cache_class=) Puppet.features.stubs(:rails?).returns true end it "should set storeconfigs to true" do Puppet.settings[:async_storeconfigs] = true Puppet.settings[:storeconfigs].should be_true end it "should set the Catalog cache class to :queue" do Puppet::Resource::Catalog.indirection.expects(:cache_class=).with(:queue) Puppet.settings[:async_storeconfigs] = true end it "should set the Facts cache class to :store_configs" do Puppet::Node::Facts.indirection.expects(:cache_class=).with(:store_configs) Puppet.settings[:storeconfigs] = true end it "should set the Node cache class to :store_configs" do Puppet::Node.indirection.expects(:cache_class=).with(:store_configs) Puppet.settings[:storeconfigs] = true end end describe "when enabling thin storeconfigs" do before do Puppet::Resource::Catalog.indirection.stubs(:cache_class=) Puppet::Node::Facts.indirection.stubs(:cache_class=) Puppet::Node.indirection.stubs(:cache_class=) Puppet.features.stubs(:rails?).returns true end it "should set storeconfigs to true" do Puppet.settings[:thin_storeconfigs] = true Puppet.settings[:storeconfigs].should be_true end end it "should have a setting for determining the configuration version and should default to an empty string" do Puppet.settings[:config_version].should == "" end describe "when enabling reports" do it "should use the default server value when report server is unspecified" do Puppet.settings[:server] = "server" Puppet.settings[:report_server].should == "server" end it "should use the default masterport value when report port is unspecified" do Puppet.settings[:masterport] = "1234" Puppet.settings[:report_port].should == "1234" end it "should set report_server when reportserver is set" do Puppet.settings[:reportserver] = "reportserver" Puppet.settings[:report_server].should == "reportserver" end it "should use report_port when set" do Puppet.settings[:masterport] = "1234" Puppet.settings[:report_port] = "5678" Puppet.settings[:report_port].should == "5678" end it "should prefer report_server over reportserver" do Puppet.settings[:reportserver] = "reportserver" Puppet.settings[:report_server] = "report_server" Puppet.settings[:report_server].should == "report_server" end end it "should have a :caname setting that defaults to the cert name" do Puppet.settings[:certname] = "foo" Puppet.settings[:ca_name].should == "Puppet CA: foo" end it "should have a 'prerun_command' that defaults to the empty string" do Puppet.settings[:prerun_command].should == "" end it "should have a 'postrun_command' that defaults to the empty string" do Puppet.settings[:postrun_command].should == "" end it "should have a 'certificate_revocation' setting that defaults to true" do Puppet.settings[:certificate_revocation].should be_true end it "should have an http_compression setting that defaults to false" do Puppet.settings[:http_compression].should be_false end describe "reportdir" do subject { Puppet.settings[:reportdir] } it { should == "#{Puppet[:vardir]}/reports" } end describe "reporturl" do subject { Puppet.settings[:reporturl] } it { should == "http://localhost:3000/reports/upload" } end describe "when configuring color" do subject { Puppet.settings[:color] } it { should == "ansi" } end describe "daemonize" do it "should default to true", :unless => Puppet.features.microsoft_windows? do Puppet.settings[:daemonize].should == true end describe "on Windows", :if => Puppet.features.microsoft_windows? do it "should default to false" do Puppet.settings[:daemonize].should == false end it "should raise an error if set to true" do expect { Puppet.settings[:daemonize] = true }.to raise_error(/Cannot daemonize on Windows/) end end end describe "diff" do it "should default to 'diff' on POSIX", :unless => Puppet.features.microsoft_windows? do Puppet.settings[:diff].should == 'diff' end it "should default to '' on Windows", :if => Puppet.features.microsoft_windows? do Puppet.settings[:diff].should == '' end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 29b7c0963..3635a1304 100755 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,103 +1,102 @@ # NOTE: a lot of the stuff in this file is duplicated in the "puppet_spec_helper" in the project # puppetlabs_spec_helper. We should probably eat our own dog food and get rid of most of this from here, # and have the puppet core itself use puppetlabs_spec_helper dir = File.expand_path(File.dirname(__FILE__)) $LOAD_PATH.unshift File.join(dir, 'lib') # Don't want puppet getting the command line arguments for rake or autotest ARGV.clear require 'puppet' -require 'mocha' gem 'rspec', '>=2.0.0' require 'rspec/expectations' # 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 require 'pathname' require 'tmpdir' require 'puppet_spec/verbose' require 'puppet_spec/files' require 'puppet_spec/fixtures' require 'puppet_spec/matchers' require 'puppet_spec/database' require 'monkey_patches/alias_should_to_must' require 'monkey_patches/publicize_methods' require 'puppet/test/test_helper' Pathname.glob("#{dir}/shared_contexts/*.rb") do |file| require file.relative_path_from(Pathname.new(dir)) end Pathname.glob("#{dir}/shared_behaviours/**/*.rb") do |behaviour| require behaviour.relative_path_from(Pathname.new(dir)) end RSpec.configure do |config| include PuppetSpec::Fixtures config.mock_with :mocha config.before :all do Puppet::Test::TestHelper.before_all_tests() end config.after :all do Puppet::Test::TestHelper.after_all_tests() end config.before :each do # Disabling garbage collection inside each test, and only running it at # the end of each block, gives us an ~ 15 percent speedup, and more on # some platforms *cough* windows *cough* that are a little slower. GC.disable # REVISIT: I think this conceals other bad tests, but I don't have time to # fully diagnose those right now. When you read this, please come tell me # I suck for letting this float. --daniel 2011-04-21 Signal.stubs(:trap) # TODO: in a more sane world, we'd move this logging redirection into our TestHelper class. # Without doing so, external projects will all have to roll their own solution for # redirecting logging, and for validating expected log messages. However, because the # current implementation of this involves creating an instance variable "@logs" on # EVERY SINGLE TEST CLASS, and because there are over 1300 tests that are written to expect # this instance variable to be available--we can't easily solve this problem right now. # # redirecting logging away from console, because otherwise the test output will be # obscured by all of the log output @logs = [] Puppet::Util::Log.newdestination(Puppet::Test::LogCollector.new(@logs)) @log_level = Puppet::Util::Log.level Puppet::Test::TestHelper.before_each_test() end config.after :each do Puppet::Test::TestHelper.after_each_test() # TODO: would like to move this into puppetlabs_spec_helper, but there are namespace issues at the moment. PuppetSpec::Files.cleanup # TODO: this should be abstracted in the future--see comments above the '@logs' block in the # "before" code above. # # clean up after the logging changes that we made before each test. @logs.clear Puppet::Util::Log.close_all Puppet::Util::Log.level = @log_level # This will perform a GC between tests, but only if actually required. We # experimented with forcing a GC run, and that was less efficient than # just letting it run all the time. GC.enable end end diff --git a/spec/unit/application/queue_spec.rb b/spec/unit/application/queue_spec.rb index 28378d804..712f64aa6 100755 --- a/spec/unit/application/queue_spec.rb +++ b/spec/unit/application/queue_spec.rb @@ -1,172 +1,173 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/application/queue' require 'puppet/indirector/catalog/queue' describe Puppet::Application::Queue, :unless => Puppet.features.microsoft_windows? do before :each do @queue = Puppet::Application[:queue] @queue.stubs(:puts) - @daemon = stub_everything 'daemon', :daemonize => nil + @daemon = stub_everything 'daemon' Puppet::Util::Log.stubs(:newdestination) Puppet::Resource::Catalog.indirection.stubs(:terminus_class=) end it "should ask Puppet::Application to parse Puppet configuration file" do @queue.should_parse_config?.should be_true end it "should declare a main command" do @queue.should respond_to(:main) end it "should declare a preinit block" do @queue.should respond_to(:preinit) end describe "in preinit" do it "should catch INT" do Signal.expects(:trap).with { |arg,block| arg == :INT } @queue.preinit end it "should init :verbose to false" do @queue.preinit @queue.options[:verbose].should be_false end it "should init :debug to false" do @queue.preinit @queue.options[:debug].should be_false end it "should create a Daemon instance and copy ARGV to it" do ARGV.expects(:dup).returns "eh" daemon = mock("daemon") daemon.expects(:argv=).with("eh") Puppet::Daemon.expects(:new).returns daemon @queue.preinit end end describe "when handling options" do [:debug, :verbose].each do |option| it "should declare handle_#{option} method" do @queue.should respond_to("handle_#{option}".to_sym) end it "should store argument value when calling handle_#{option}" do @queue.options.expects(:[]=).with(option, 'arg') @queue.send("handle_#{option}".to_sym, 'arg') end end end describe "during setup" do before :each do + @queue.preinit @queue.options.stubs(:[]) @queue.daemon.stubs(:daemonize) Puppet.stubs(:info) Puppet.features.stubs(:stomp?).returns true Puppet::Resource::Catalog.indirection.stubs(:terminus_class=) Puppet.stubs(:settraps) Puppet.settings.stubs(:print_config?) Puppet.settings.stubs(:print_config) end it "should fail if the stomp feature is missing" do Puppet.features.expects(:stomp?).returns false lambda { @queue.setup }.should raise_error(ArgumentError) end it "should print puppet config if asked to in Puppet config" do Puppet.settings.stubs(:print_configs?).returns(true) Puppet.settings.expects(:print_configs).returns(true) expect { @queue.setup }.to exit_with 0 end it "should exit after printing puppet config if asked to in Puppet config" do Puppet.settings.stubs(:print_configs?).returns(true) expect { @queue.setup }.to exit_with 1 end it "should call setup_logs" do @queue.expects(:setup_logs) @queue.setup end describe "when setting up logs" do before :each do Puppet::Util::Log.stubs(:newdestination) end it "should set log level to debug if --debug was passed" do @queue.options.stubs(:[]).with(:debug).returns(true) @queue.setup_logs Puppet::Util::Log.level.should == :debug end it "should set log level to info if --verbose was passed" do @queue.options.stubs(:[]).with(:verbose).returns(true) @queue.setup_logs Puppet::Util::Log.level.should == :info end [:verbose, :debug].each do |level| it "should set console as the log destination with level #{level}" do @queue.options.stubs(:[]).with(level).returns(true) Puppet::Util::Log.expects(:newdestination).with(:console) @queue.setup_logs end end end it "should configure the Catalog class to use StoreConfigs" do Puppet::Resource::Catalog.indirection.expects(:terminus_class=).with(:store_configs) @queue.setup end it "should daemonize if needed" do Puppet.expects(:[]).with(:daemonize).returns(true) @queue.daemon.expects(:daemonize) @queue.setup end end describe "when running" do before :each do @queue.stubs(:sleep_forever) Puppet::Resource::Catalog::Queue.stubs(:subscribe) Thread.list.each { |t| t.stubs(:join) } end it "should subscribe to the queue" do Puppet::Resource::Catalog::Queue.expects(:subscribe) @queue.main end it "should log and save each catalog passed by the queue" do catalog = Puppet::Resource::Catalog.new('eh') Puppet::Resource::Catalog.indirection.expects(:save).with(catalog) Puppet::Resource::Catalog::Queue.expects(:subscribe).yields(catalog) Puppet.expects(:notice).times(2) @queue.main end it "should join all of the running threads" do Thread.list.each { |t| t.expects(:join) } @queue.main end end end diff --git a/spec/unit/face/instrumentation_data.rb b/spec/unit/face/instrumentation_data_spec.rb similarity index 100% rename from spec/unit/face/instrumentation_data.rb rename to spec/unit/face/instrumentation_data_spec.rb diff --git a/spec/unit/face/instrumentation_listener.rb b/spec/unit/face/instrumentation_listener_spec.rb similarity index 100% rename from spec/unit/face/instrumentation_listener.rb rename to spec/unit/face/instrumentation_listener_spec.rb diff --git a/spec/unit/face/instrumentation_probe.rb b/spec/unit/face/instrumentation_probe_spec.rb similarity index 100% rename from spec/unit/face/instrumentation_probe.rb rename to spec/unit/face/instrumentation_probe_spec.rb diff --git a/spec/unit/network/http/webrick_spec.rb b/spec/unit/network/http/webrick_spec.rb index 0cd31ae62..eddabc679 100755 --- a/spec/unit/network/http/webrick_spec.rb +++ b/spec/unit/network/http/webrick_spec.rb @@ -1,340 +1,333 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/network/handler' require 'puppet/network/http' require 'puppet/network/http/webrick' describe Puppet::Network::HTTP::WEBrick, "after initializing" do it "should not be listening" do Puppet::Network::HTTP::WEBrick.new.should_not be_listening end end describe Puppet::Network::HTTP::WEBrick, "when turning on listening" do before do @mock_webrick = stub('webrick', :[] => {}, :listeners => [], :status => :Running) [:mount, :start, :shutdown].each {|meth| @mock_webrick.stubs(meth)} WEBrick::HTTPServer.stubs(:new).returns(@mock_webrick) @server = Puppet::Network::HTTP::WEBrick.new [:setup_logger, :setup_ssl].each {|meth| @server.stubs(meth).returns({})} # the empty hash is required because of how we're merging @listen_params = { :address => "127.0.0.1", :port => 31337, :xmlrpc_handlers => [], :protocols => [ :rest ] } 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 webrick server to start in a separate thread" do - @mock_webrick.expects(:start) - # If you remove this you'll sometimes get race condition problems - Thread.expects(:new).yields - @server.listen(@listen_params) - end - it "should tell webrick to listen on the specified address and port" do WEBrick::HTTPServer.expects(:new).with {|args| args[:Port] == 31337 and args[:BindAddress] == "127.0.0.1" }.returns(@mock_webrick) @server.listen(@listen_params) end it "should configure a logger for webrick" do @server.expects(:setup_logger).returns(:Logger => :mylogger) WEBrick::HTTPServer.expects(:new).with {|args| args[:Logger] == :mylogger }.returns(@mock_webrick) @server.listen(@listen_params) end it "should configure SSL for webrick" do @server.expects(:setup_ssl).returns(:Ssl => :testing, :Other => :yay) WEBrick::HTTPServer.expects(:new).with {|args| args[:Ssl] == :testing and args[:Other] == :yay }.returns(@mock_webrick) @server.listen(@listen_params) end it "should be listening" do @server.listen(@listen_params) @server.should be_listening end describe "when the REST protocol is requested" do it "should register the REST handler at /" do # We don't care about the options here. @mock_webrick.expects(:mount).with { |path, klass, options| path == "/" and klass == Puppet::Network::HTTP::WEBrickREST } @server.listen(@listen_params.merge(:protocols => [:rest])) end end describe "when the XMLRPC protocol is requested" do before do @servlet = mock 'servlet' Puppet::Network::XMLRPC::WEBrickServlet.stubs(:new).returns @servlet @master_handler = mock('master_handler') @file_handler = mock('file_handler') @master = mock 'master' @file = mock 'file' @master_handler.stubs(:new).returns @master @file_handler.stubs(:new).returns @file Puppet::Network::Handler.stubs(:handler).with(:master).returns @master_handler Puppet::Network::Handler.stubs(:handler).with(:fileserver).returns @file_handler end it "should do nothing if no xmlrpc handlers have been specified" do Puppet::Network::Handler.expects(:handler).never @server.listen(@listen_params.merge(:protocols => [:xmlrpc], :xmlrpc_handlers => [])) end it "should look the handler classes up via their base class" do Puppet::Network::Handler.expects(:handler).with(:master).returns @master_handler Puppet::Network::Handler.expects(:handler).with(:fileserver).returns @file_handler @server.listen(@listen_params.merge(:protocols => [:xmlrpc], :xmlrpc_handlers => [:master, :fileserver])) end it "should create an instance for each requested xmlrpc handler" do @master_handler.expects(:new).returns @master @file_handler.expects(:new).returns @file @server.listen(@listen_params.merge(:protocols => [:xmlrpc], :xmlrpc_handlers => [:master, :fileserver])) end it "should create a webrick servlet with the xmlrpc handler instances" do Puppet::Network::XMLRPC::WEBrickServlet.expects(:new).with([@master, @file]).returns @servlet @server.listen(@listen_params.merge(:protocols => [:xmlrpc], :xmlrpc_handlers => [:master, :fileserver])) end it "should mount the webrick servlet at /RPC2" do @mock_webrick.stubs(:mount) @mock_webrick.expects(:mount).with("/RPC2", @servlet) @server.listen(@listen_params.merge(:protocols => [:xmlrpc], :xmlrpc_handlers => [:master, :fileserver])) end end end describe Puppet::Network::HTTP::WEBrick, "when looking up the class to handle a protocol" do it "should require a protocol" do lambda { Puppet::Network::HTTP::WEBrick.class_for_protocol }.should raise_error(ArgumentError) end it "should accept a protocol" do lambda { Puppet::Network::HTTP::WEBrick.class_for_protocol("bob") }.should_not raise_error(ArgumentError) end it "should use a WEBrick + REST class when a REST protocol is specified" do Puppet::Network::HTTP::WEBrick.class_for_protocol("rest").should == Puppet::Network::HTTP::WEBrickREST end it "should fail when an unknown protocol is specified" do lambda { Puppet::Network::HTTP::WEBrick.class_for_protocol("abcdefg") }.should raise_error end end describe Puppet::Network::HTTP::WEBrick, "when turning off listening" do before do @mock_webrick = stub('webrick', :[] => {}, :listeners => [], :status => :Running) [:mount, :start, :shutdown].each {|meth| @mock_webrick.stubs(meth)} WEBrick::HTTPServer.stubs(:new).returns(@mock_webrick) @server = Puppet::Network::HTTP::WEBrick.new [:setup_logger, :setup_ssl].each {|meth| @server.stubs(meth).returns({})} # the empty hash is required because of how we're merging @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 webrick server to stop" do @mock_webrick.expects(:shutdown) @server.listen(@listen_params) @server.unlisten end it "should no longer be listening" do @server.listen(@listen_params) @server.unlisten @server.should_not be_listening end end describe Puppet::Network::HTTP::WEBrick do before do @mock_webrick = stub('webrick', :[] => {}) [:mount, :start, :shutdown].each {|meth| @mock_webrick.stubs(meth)} WEBrick::HTTPServer.stubs(:new).returns(@mock_webrick) @server = Puppet::Network::HTTP::WEBrick.new end describe "when configuring an http logger" do before do Puppet.settings.stubs(:value).returns "something" Puppet.settings.stubs(:use) @filehandle = stub 'handle', :fcntl => nil, :sync= => nil File.stubs(:open).returns @filehandle end it "should use the settings for :main, :ssl, and the process name" do Puppet.settings.stubs(:value).with(:name).returns "myname" Puppet.settings.expects(:use).with(:main, :ssl, "myname") @server.setup_logger end it "should use the masterlog if the run_mode is master" do Puppet.run_mode.stubs(:master?).returns(true) Puppet.settings.expects(:value).with(:masterhttplog).returns "/master/log" File.expects(:open).with("/master/log", "a+").returns @filehandle @server.setup_logger end it "should use the httplog if the run_mode is not master" do Puppet.run_mode.stubs(:master?).returns(false) Puppet.settings.expects(:value).with(:httplog).returns "/other/log" File.expects(:open).with("/other/log", "a+").returns @filehandle @server.setup_logger end describe "and creating the logging filehandle" do it "should set the close-on-exec flag if supported" do if defined? Fcntl::FD_CLOEXEC @filehandle.expects(:fcntl).with(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) else @filehandle.expects(:fcntl).never end @server.setup_logger end it "should sync the filehandle" do @filehandle.expects(:sync=).with(true) @server.setup_logger end end it "should create a new WEBrick::Log instance with the open filehandle" do WEBrick::Log.expects(:new).with(@filehandle) @server.setup_logger end it "should set debugging if the current loglevel is :debug" do Puppet::Util::Log.expects(:level).returns :debug WEBrick::Log.expects(:new).with { |handle, debug| debug == WEBrick::Log::DEBUG } @server.setup_logger end it "should return the logger as the main log" do logger = mock 'logger' WEBrick::Log.expects(:new).returns logger @server.setup_logger[:Logger].should == logger end it "should return the logger as the access log using both the Common and Referer log format" do logger = mock 'logger' WEBrick::Log.expects(:new).returns logger @server.setup_logger[:AccessLog].should == [ [logger, WEBrick::AccessLog::COMMON_LOG_FORMAT], [logger, WEBrick::AccessLog::REFERER_LOG_FORMAT] ] end end describe "when configuring ssl" do before do @key = stub 'key', :content => "mykey" @cert = stub 'cert', :content => "mycert" @host = stub 'host', :key => @key, :certificate => @cert, :name => "yay", :ssl_store => "mystore" Puppet::SSL::Certificate.indirection.stubs(:find).with('ca').returns @cert Puppet::SSL::Host.stubs(:localhost).returns @host end it "should use the key from the localhost SSL::Host instance" do Puppet::SSL::Host.expects(:localhost).returns @host @host.expects(:key).returns @key @server.setup_ssl[:SSLPrivateKey].should == "mykey" end it "should configure the certificate" do @server.setup_ssl[:SSLCertificate].should == "mycert" end it "should fail if no CA certificate can be found" do Puppet::SSL::Certificate.indirection.stubs(:find).with('ca').returns nil lambda { @server.setup_ssl }.should raise_error(Puppet::Error) end it "should specify the path to the CA certificate" do Puppet.settings.stubs(:value).returns "whatever" Puppet.settings.stubs(:value).with(:hostcrl).returns 'false' Puppet.settings.stubs(:value).with(:localcacert).returns '/ca/crt' @server.setup_ssl[:SSLCACertificateFile].should == "/ca/crt" end it "should start ssl immediately" do @server.setup_ssl[:SSLStartImmediately].should be_true end it "should enable ssl" do @server.setup_ssl[:SSLEnable].should be_true end it "should configure the verification method as 'OpenSSL::SSL::VERIFY_PEER'" do @server.setup_ssl[:SSLVerifyClient].should == OpenSSL::SSL::VERIFY_PEER end it "should add an x509 store" do Puppet.settings.stubs(:value).returns "whatever" Puppet.settings.stubs(:value).with(:hostcrl).returns '/my/crl' @host.expects(:ssl_store).returns "mystore" @server.setup_ssl[:SSLCertificateStore].should == "mystore" end it "should set the certificate name to 'nil'" do @server.setup_ssl[:SSLCertName].should be_nil end end end diff --git a/spec/unit/node/environment_spec.rb b/spec/unit/node/environment_spec.rb index 28c35e542..f14082d6c 100755 --- a/spec/unit/node/environment_spec.rb +++ b/spec/unit/node/environment_spec.rb @@ -1,452 +1,452 @@ #!/usr/bin/env rspec require 'spec_helper' require 'tmpdir' require 'puppet/node/environment' require 'puppet/util/execution' require 'puppet_spec/modules' describe Puppet::Node::Environment do let(:env) { Puppet::Node::Environment.new("testing") } include PuppetSpec::Files after do Puppet::Node::Environment.clear end it "should use the filetimeout for the ttl for the modulepath" do Puppet::Node::Environment.attr_ttl(:modulepath).should == Integer(Puppet[:filetimeout]) end it "should use the filetimeout for the ttl for the module list" do Puppet::Node::Environment.attr_ttl(:modules).should == Integer(Puppet[:filetimeout]) end it "should use the default environment if no name is provided while initializing an environment" do - Puppet.settings.expects(:value).with(:environment).returns("one") + Puppet[:environment] = "one" Puppet::Node::Environment.new.name.should == :one end it "should treat environment instances as singletons" do Puppet::Node::Environment.new("one").should equal(Puppet::Node::Environment.new("one")) end it "should treat an environment specified as names or strings as equivalent" do Puppet::Node::Environment.new(:one).should equal(Puppet::Node::Environment.new("one")) end it "should return its name when converted to a string" do Puppet::Node::Environment.new(:one).to_s.should == "one" end it "should just return any provided environment if an environment is provided as the name" do one = Puppet::Node::Environment.new(:one) Puppet::Node::Environment.new(one).should equal(one) end describe "when managing known resource types" do before do @collection = Puppet::Resource::TypeCollection.new(env) env.stubs(:perform_initial_import).returns(Puppet::Parser::AST::Hostclass.new('')) Thread.current[:known_resource_types] = nil end it "should create a resource type collection if none exists" do Puppet::Resource::TypeCollection.expects(:new).with(env).returns @collection env.known_resource_types.should equal(@collection) end it "should reuse any existing resource type collection" do env.known_resource_types.should equal(env.known_resource_types) end it "should perform the initial import when creating a new collection" do env.expects(:perform_initial_import).returns(Puppet::Parser::AST::Hostclass.new('')) env.known_resource_types end it "should return the same collection even if stale if it's the same thread" do Puppet::Resource::TypeCollection.stubs(:new).returns @collection env.known_resource_types.stubs(:stale?).returns true env.known_resource_types.should equal(@collection) end it "should return the current thread associated collection if there is one" do Thread.current[:known_resource_types] = @collection env.known_resource_types.should equal(@collection) end it "should give to all threads using the same environment the same collection if the collection isn't stale" do - original_thread_type_collection = Puppet::Resource::TypeCollection.new(env) - Puppet::Resource::TypeCollection.expects(:new).with(env).returns original_thread_type_collection - env.known_resource_types.should equal(original_thread_type_collection) + @original_thread_type_collection = Puppet::Resource::TypeCollection.new(env) + Puppet::Resource::TypeCollection.expects(:new).with(env).returns @original_thread_type_collection + env.known_resource_types.should equal(@original_thread_type_collection) - original_thread_type_collection.expects(:require_reparse?).returns(false) + @original_thread_type_collection.expects(:require_reparse?).returns(false) Puppet::Resource::TypeCollection.stubs(:new).with(env).returns @collection t = Thread.new { - env.known_resource_types.should equal(original_thread_type_collection) + env.known_resource_types.should equal(@original_thread_type_collection) } t.join end it "should generate a new TypeCollection if the current one requires reparsing" do old_type_collection = env.known_resource_types old_type_collection.stubs(:require_reparse?).returns true Thread.current[:known_resource_types] = nil new_type_collection = env.known_resource_types new_type_collection.should be_a Puppet::Resource::TypeCollection new_type_collection.should_not equal(old_type_collection) end end it "should validate the modulepath directories" do real_file = tmpdir('moduledir') path = %W[/one /two #{real_file}].join(File::PATH_SEPARATOR) Puppet[:modulepath] = path env.modulepath.should == [real_file] end it "should prefix the value of the 'PUPPETLIB' environment variable to the module path if present" do Puppet::Util::Execution.withenv("PUPPETLIB" => %w{/l1 /l2}.join(File::PATH_SEPARATOR)) do module_path = %w{/one /two}.join(File::PATH_SEPARATOR) env.expects(:validate_dirs).with(%w{/l1 /l2 /one /two}).returns %w{/l1 /l2 /one /two} env.expects(:[]).with(:modulepath).returns module_path env.modulepath.should == %w{/l1 /l2 /one /two} end end describe "when validating modulepath or manifestdir directories" do before :each do @path_one = tmpdir("path_one") @path_two = tmpdir("path_one") sep = File::PATH_SEPARATOR Puppet[:modulepath] = "#{@path_one}#{sep}#{@path_two}" end it "should not return non-directories" do FileTest.expects(:directory?).with(@path_one).returns true FileTest.expects(:directory?).with(@path_two).returns false env.validate_dirs([@path_one, @path_two]).should == [@path_one] end it "should use the current working directory to fully-qualify unqualified paths" do FileTest.stubs(:directory?).returns true two = File.expand_path(File.join(Dir.getwd, "two")) env.validate_dirs([@path_one, 'two']).should == [@path_one, two] end end describe "when modeling a specific environment" do it "should have a method for returning the environment name" do Puppet::Node::Environment.new("testing").name.should == :testing end it "should provide an array-like accessor method for returning any environment-specific setting" do env.should respond_to(:[]) end it "should ask the Puppet settings instance for the setting qualified with the environment name" do - Puppet.settings.expects(:value).with("myvar", :testing).returns("myval") - env["myvar"].should == "myval" + Puppet.settings.set_value(:server, "myval", :testing) + env[:server].should == "myval" end it "should be able to return an individual module that exists in its module path" do env.stubs(:modules).returns [Puppet::Module.new('one'), Puppet::Module.new('two'), Puppet::Module.new('three')] mod = env.module('one') mod.should be_a(Puppet::Module) mod.name.should == 'one' end it "should not return a module if the module doesn't exist" do env.stubs(:modules).returns [Puppet::Module.new('one'), Puppet::Module.new('two'), Puppet::Module.new('three')] env.module('four').should be_nil end it "should return nil if asked for a module that does not exist in its path" do modpath = tmpdir('modpath') env.modulepath = [modpath] env.module("one").should be_nil end describe "module data" do before do dir = tmpdir("deep_path") @first = File.join(dir, "first") @second = File.join(dir, "second") Puppet[:modulepath] = "#{@first}#{File::PATH_SEPARATOR}#{@second}" FileUtils.mkdir_p(@first) FileUtils.mkdir_p(@second) end describe "#modules_by_path" do it "should return an empty list if there are no modules" do env.modules_by_path.should == { @first => [], @second => [] } end it "should include modules even if they exist in multiple dirs in the modulepath" do modpath1 = File.join(@first, "foo") FileUtils.mkdir_p(modpath1) modpath2 = File.join(@second, "foo") FileUtils.mkdir_p(modpath2) env.modules_by_path.should == { @first => [Puppet::Module.new('foo', :environment => env, :path => modpath1)], @second => [Puppet::Module.new('foo', :environment => env, :path => modpath2)] } end it "should ignore modules with invalid names" do FileUtils.mkdir_p(File.join(@first, 'foo')) FileUtils.mkdir_p(File.join(@first, 'foo2')) FileUtils.mkdir_p(File.join(@first, 'foo-bar')) FileUtils.mkdir_p(File.join(@first, 'foo_bar')) FileUtils.mkdir_p(File.join(@first, 'foo=bar')) FileUtils.mkdir_p(File.join(@first, 'foo bar')) FileUtils.mkdir_p(File.join(@first, 'foo.bar')) FileUtils.mkdir_p(File.join(@first, '-foo')) FileUtils.mkdir_p(File.join(@first, 'foo-')) FileUtils.mkdir_p(File.join(@first, 'foo--bar')) env.modules_by_path[@first].collect{|mod| mod.name}.sort.should == %w{foo foo-bar foo2 foo_bar} end end describe "#module_requirements" do it "should return a list of what modules depend on other modules" do PuppetSpec::Modules.create( 'foo', @first, :metadata => { :author => 'puppetlabs', :dependencies => [{ 'name' => 'puppetlabs/bar', "version_requirement" => ">= 1.0.0" }] } ) PuppetSpec::Modules.create( 'bar', @second, :metadata => { :author => 'puppetlabs', :dependencies => [{ 'name' => 'puppetlabs/foo', "version_requirement" => "<= 2.0.0" }] } ) PuppetSpec::Modules.create( 'baz', @first, :metadata => { :author => 'puppetlabs', :dependencies => [{ 'name' => 'puppetlabs/bar', "version_requirement" => "3.0.0" }] } ) PuppetSpec::Modules.create( 'alpha', @first, :metadata => { :author => 'puppetlabs', :dependencies => [{ 'name' => 'puppetlabs/bar', "version_requirement" => "~3.0.0" }] } ) env.module_requirements.should == { 'puppetlabs/alpha' => [], 'puppetlabs/foo' => [ { "name" => "puppetlabs/bar", "version" => "9.9.9", "version_requirement" => "<= 2.0.0" } ], 'puppetlabs/bar' => [ { "name" => "puppetlabs/alpha", "version" => "9.9.9", "version_requirement" => "~3.0.0" }, { "name" => "puppetlabs/baz", "version" => "9.9.9", "version_requirement" => "3.0.0" }, { "name" => "puppetlabs/foo", "version" => "9.9.9", "version_requirement" => ">= 1.0.0" } ], 'puppetlabs/baz' => [] } end end describe ".module_by_forge_name" do it "should find modules by forge_name" do mod = PuppetSpec::Modules.create( 'baz', @first, :metadata => {:author => 'puppetlabs'}, :environment => env ) env.module_by_forge_name('puppetlabs/baz').should == mod end it "should not find modules with same name by the wrong author" do mod = PuppetSpec::Modules.create( 'baz', @first, :metadata => {:author => 'sneakylabs'}, :environment => env ) env.module_by_forge_name('puppetlabs/baz').should == nil end it "should return nil when the module can't be found" do env.module_by_forge_name('ima/nothere').should be_nil end end describe ".modules" do it "should return an empty list if there are no modules" do env.modules.should == [] end it "should return a module named for every directory in each module path" do %w{foo bar}.each do |mod_name| FileUtils.mkdir_p(File.join(@first, mod_name)) end %w{bee baz}.each do |mod_name| FileUtils.mkdir_p(File.join(@second, mod_name)) end env.modules.collect{|mod| mod.name}.sort.should == %w{foo bar bee baz}.sort end it "should remove duplicates" do FileUtils.mkdir_p(File.join(@first, 'foo')) FileUtils.mkdir_p(File.join(@second, 'foo')) env.modules.collect{|mod| mod.name}.sort.should == %w{foo} end it "should ignore modules with invalid names" do FileUtils.mkdir_p(File.join(@first, 'foo')) FileUtils.mkdir_p(File.join(@first, 'foo2')) FileUtils.mkdir_p(File.join(@first, 'foo-bar')) FileUtils.mkdir_p(File.join(@first, 'foo_bar')) FileUtils.mkdir_p(File.join(@first, 'foo=bar')) FileUtils.mkdir_p(File.join(@first, 'foo bar')) env.modules.collect{|mod| mod.name}.sort.should == %w{foo foo-bar foo2 foo_bar} end it "should create modules with the correct environment" do FileUtils.mkdir_p(File.join(@first, 'foo')) env.modules.each {|mod| mod.environment.should == env } end end end it "should cache the module list" do env.modulepath = %w{/a} Dir.expects(:entries).once.with("/a").returns %w{foo} env.modules env.modules end end describe Puppet::Node::Environment::Helper do before do @helper = Object.new @helper.extend(Puppet::Node::Environment::Helper) end it "should be able to set and retrieve the environment as a symbol" do @helper.environment = :foo @helper.environment.name.should == :foo end it "should accept an environment directly" do @helper.environment = Puppet::Node::Environment.new(:foo) @helper.environment.name.should == :foo end it "should accept an environment as a string" do @helper.environment = 'foo' @helper.environment.name.should == :foo end end describe "when performing initial import" do before do @parser = Puppet::Parser::Parser.new("test") Puppet::Parser::Parser.stubs(:new).returns @parser end it "should set the parser's string to the 'code' setting and parse if code is available" do Puppet.settings[:code] = "my code" @parser.expects(:string=).with "my code" @parser.expects(:parse) env.instance_eval { perform_initial_import } end it "should set the parser's file to the 'manifest' setting and parse if no code is available and the manifest is available" do filename = tmpfile('myfile') File.open(filename, 'w'){|f| } Puppet.settings[:manifest] = filename @parser.expects(:file=).with filename @parser.expects(:parse) env.instance_eval { perform_initial_import } end it "should pass the manifest file to the parser even if it does not exist on disk" do filename = tmpfile('myfile') Puppet.settings[:code] = "" Puppet.settings[:manifest] = filename @parser.expects(:file=).with(filename).once @parser.expects(:parse).once env.instance_eval { perform_initial_import } end it "should fail helpfully if there is an error importing" do File.stubs(:exist?).returns true env.stubs(:known_resource_types).returns Puppet::Resource::TypeCollection.new(env) @parser.expects(:file=).once @parser.expects(:parse).raises ArgumentError lambda { env.instance_eval { perform_initial_import } }.should raise_error(Puppet::Error) end it "should not do anything if the ignore_import settings is set" do Puppet.settings[:ignoreimport] = true @parser.expects(:string=).never @parser.expects(:file=).never @parser.expects(:parse).never env.instance_eval { perform_initial_import } end it "should mark the type collection as needing a reparse when there is an error parsing" do @parser.expects(:parse).raises Puppet::ParseError.new("Syntax error at ...") env.stubs(:known_resource_types).returns Puppet::Resource::TypeCollection.new(env) lambda { env.instance_eval { perform_initial_import } }.should raise_error(Puppet::Error, /Syntax error at .../) env.known_resource_types.require_reparse?.should be_true end end end diff --git a/spec/unit/parser/collector_spec.rb b/spec/unit/parser/collector_spec.rb index 251fec78a..1bb68a0f7 100755 --- a/spec/unit/parser/collector_spec.rb +++ b/spec/unit/parser/collector_spec.rb @@ -1,437 +1,438 @@ #!/usr/bin/env rspec require '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' + @compiler = mock 'compiler' @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", :if => can_use_scratch_database? do include PuppetSpec::Files before do @compiler = Puppet::Parser::Compiler.new(Puppet::Node.new("mynode")) @scope = Puppet::Parser::Scope.new :compiler => @compiler @resource_type = "notify" @equery = ["title", "!=", ""] @vquery = proc { |r| true } @collector = Puppet::Parser::Collector.new(@scope, @resource_type, @equery, @vquery, :exported) end it "should just return false if :storeconfigs is not enabled" do Puppet[:storeconfigs] = false @collector.evaluate.should be_false end context "with storeconfigs enabled" do before :each do setup_scratch_database Puppet[:storeconfigs] = true Puppet[:environment] = "production" Puppet[:storeconfigs_backend] = "active_record" end it "should return all matching resources from the current compile and mark them non-virtual and non-exported" do one = Puppet::Parser::Resource.new('notify', 'one', :virtual => true, :exported => true, :scope => @scope) two = Puppet::Parser::Resource.new('notify', 'two', :virtual => true, :exported => true, :scope => @scope) @compiler.resources << one @compiler.resources << two @collector.evaluate.should == [one, two] one.should_not be_virtual two.should_not be_virtual # REVISIT: Apparently we never actually marked local resources as # non-exported. So, this is what the previous test asserted, and checking # what it claims to do causes test failures. --daniel 2011-08-23 end it "should mark all returned resources as not virtual" do one = Puppet::Parser::Resource.new('notify', 'one', :virtual => true, :exported => true, :scope => @scope) @compiler.resources << one @collector.evaluate.should == [one] one.should_not be_virtual end it "should convert all found resources into parser resources if necessary" do host = Puppet::Rails::Host.create!(:name => 'one.local') Puppet::Rails::Resource. create!(:host => host, :restype => 'Notify', :title => 'whammo', :exported => true) result = @collector.evaluate result.length.should == 1 result.first.should be_an_instance_of Puppet::Parser::Resource result.first.type.should == 'Notify' result.first.title.should == 'whammo' end it "should leave parser resources alone" do resource = Puppet::Parser::Resource.new(:file, "/tmp/foo", :scope => @scope) resource2 = Puppet::Parser::Resource.new(:file, "/tmp/bar", :scope => @scope) resource.expects(:to_resource).never resource2.expects(:to_resource).never resources = [resource, resource2] Puppet::Resource.indirection.stubs(:search).returns resources @collector.evaluate.should == resources end it "should override all exported collected resources if collector has an override" do host = Puppet::Rails::Host.create!(:name => 'one.local') Puppet::Rails::Resource. create!(:host => host, :restype => 'Notify', :title => 'whammo', :exported => true) param = Puppet::Parser::Resource::Param. new(:name => 'message', :value => 'howdy') @collector.add_override(:parameters => [param], :scope => @scope) got = @collector.evaluate got.first[:message].should == param.value end it "should store converted resources in the compile's resource list" do host = Puppet::Rails::Host.create!(:name => 'one.local') Puppet::Rails::Resource. create!(:host => host, :restype => 'Notify', :title => 'whammo', :exported => true) @compiler.expects(:add_resource).with do |scope, resource| scope.should be_an_instance_of Puppet::Parser::Scope resource.type.should == 'Notify' resource.title.should == 'whammo' true end @collector.evaluate 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 host = Puppet::Rails::Host.create!(:name => 'one.local') Puppet::Rails::Resource. create!(:host => host, :restype => 'Notify', :title => 'whammo', :exported => true) got = @collector.evaluate got.length.should == 1 got.first.type.should == "Notify" got.first.title.should == "whammo" got.first.should_not be_exported end it "should fail if an equivalent resource already exists in the compile" do host = Puppet::Rails::Host.create!(:name => 'one.local') Puppet::Rails::Resource. create!(:host => host, :restype => 'Notify', :title => 'whammo', :exported => true) local = Puppet::Parser::Resource.new('notify', 'whammo', :scope => @scope) @compiler.add_resource(@scope, local) expect { @collector.evaluate }. to raise_error Puppet::ParseError, /exists with the type and title/ end it "should ignore exported resources that match already-collected resources" do host = Puppet::Rails::Host.create!(:name => 'one.local') # One that we already collected... db = Puppet::Rails::Resource. create!(:host => host, :restype => 'Notify', :title => 'whammo', :exported => true) # ...and one we didn't. Puppet::Rails::Resource. create!(:host => host, :restype => 'Notify', :title => 'boingy-boingy', :exported => true) local = Puppet::Parser::Resource.new('notify', 'whammo', :scope => @scope, :collector_id => db.id) @compiler.add_resource(@scope, local) got = nil expect { got = @collector.evaluate }.not_to raise_error(Puppet::ParseError) got.length.should == 1 got.first.type.should == "Notify" got.first.title.should == "boingy-boingy" end end end diff --git a/spec/unit/parser/files_spec.rb b/spec/unit/parser/files_spec.rb index 1bf75e623..dc79984c8 100755 --- a/spec/unit/parser/files_spec.rb +++ b/spec/unit/parser/files_spec.rb @@ -1,203 +1,203 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/parser/files' describe Puppet::Parser::Files do include PuppetSpec::Files before do @basepath = make_absolute("/somepath") end it "should have a method for finding a template" do Puppet::Parser::Files.should respond_to(:find_template) end it "should have a method for finding manifests" do Puppet::Parser::Files.should respond_to(:find_manifests) end describe "when searching for templates" do it "should return fully-qualified templates directly" do Puppet::Parser::Files.expects(:modulepath).never Puppet::Parser::Files.find_template(@basepath + "/my/template").should == @basepath + "/my/template" end it "should return the template from the first found module" do mod = mock 'module' Puppet::Node::Environment.new.expects(:module).with("mymod").returns mod mod.expects(:template).returns("/one/mymod/templates/mytemplate") Puppet::Parser::Files.find_template("mymod/mytemplate").should == "/one/mymod/templates/mytemplate" end it "should return the file in the templatedir if it exists" do - Puppet.settings.expects(:value).with(:templatedir, nil).returns("/my/templates") + Puppet[:templatedir] = "/my/templates" Puppet[:modulepath] = "/one:/two" File.stubs(:directory?).returns(true) FileTest.stubs(:exist?).returns(true) - Puppet::Parser::Files.find_template("mymod/mytemplate").should == "/my/templates/mymod/mytemplate" + Puppet::Parser::Files.find_template("mymod/mytemplate").should == File.join(Puppet[:templatedir], "mymod/mytemplate") end it "should not raise an error if no valid templatedir exists and the template exists in a module" do mod = mock 'module' Puppet::Node::Environment.new.expects(:module).with("mymod").returns mod mod.expects(:template).returns("/one/mymod/templates/mytemplate") Puppet::Parser::Files.stubs(:templatepath).with(nil).returns(nil) Puppet::Parser::Files.find_template("mymod/mytemplate").should == "/one/mymod/templates/mytemplate" end it "should return unqualified templates if they exist in the template dir" do FileTest.stubs(:exist?).returns true Puppet::Parser::Files.stubs(:templatepath).with(nil).returns(["/my/templates"]) Puppet::Parser::Files.find_template("mytemplate").should == "/my/templates/mytemplate" end it "should only return templates if they actually exist" do FileTest.expects(:exist?).with("/my/templates/mytemplate").returns true Puppet::Parser::Files.stubs(:templatepath).with(nil).returns(["/my/templates"]) Puppet::Parser::Files.find_template("mytemplate").should == "/my/templates/mytemplate" end it "should return nil when asked for a template that doesn't exist" do FileTest.expects(:exist?).with("/my/templates/mytemplate").returns false Puppet::Parser::Files.stubs(:templatepath).with(nil).returns(["/my/templates"]) Puppet::Parser::Files.find_template("mytemplate").should be_nil end it "should search in the template directories before modules" do FileTest.stubs(:exist?).returns true Puppet::Parser::Files.stubs(:templatepath).with(nil).returns(["/my/templates"]) Puppet::Module.expects(:find).never Puppet::Parser::Files.find_template("mytemplate") end it "should accept relative templatedirs" do FileTest.stubs(:exist?).returns true Puppet[:templatedir] = "my/templates" # We expand_path to normalize backslashes and slashes on Windows File.expects(:directory?).with(File.expand_path(File.join(Dir.getwd,"my/templates"))).returns(true) Puppet::Parser::Files.find_template("mytemplate").should == File.expand_path(File.join(Dir.getwd,"my/templates/mytemplate")) end it "should use the environment templatedir if no module is found and an environment is specified" do FileTest.stubs(:exist?).returns true Puppet::Parser::Files.stubs(:templatepath).with("myenv").returns(["/myenv/templates"]) Puppet::Parser::Files.find_template("mymod/mytemplate", "myenv").should == "/myenv/templates/mymod/mytemplate" end it "should use first dir from environment templatedir if no module is found and an environment is specified" do FileTest.stubs(:exist?).returns true Puppet::Parser::Files.stubs(:templatepath).with("myenv").returns(["/myenv/templates", "/two/templates"]) Puppet::Parser::Files.find_template("mymod/mytemplate", "myenv").should == "/myenv/templates/mymod/mytemplate" end it "should use a valid dir when templatedir is a path for unqualified templates and the first dir contains template" do Puppet::Parser::Files.stubs(:templatepath).returns(["/one/templates", "/two/templates"]) FileTest.expects(:exist?).with("/one/templates/mytemplate").returns(true) Puppet::Parser::Files.find_template("mytemplate").should == "/one/templates/mytemplate" end it "should use a valid dir when templatedir is a path for unqualified templates and only second dir contains template" do Puppet::Parser::Files.stubs(:templatepath).returns(["/one/templates", "/two/templates"]) FileTest.expects(:exist?).with("/one/templates/mytemplate").returns(false) FileTest.expects(:exist?).with("/two/templates/mytemplate").returns(true) Puppet::Parser::Files.find_template("mytemplate").should == "/two/templates/mytemplate" end it "should use the node environment if specified" do mod = mock 'module' Puppet::Node::Environment.new("myenv").expects(:module).with("mymod").returns mod mod.expects(:template).returns("/my/modules/mymod/templates/envtemplate") Puppet::Parser::Files.find_template("mymod/envtemplate", "myenv").should == "/my/modules/mymod/templates/envtemplate" end it "should return nil if no template can be found" do Puppet::Parser::Files.find_template("foomod/envtemplate", "myenv").should be_nil end after { Puppet.settings.clear } end describe "when searching for manifests" do it "should ignore invalid modules" do mod = mock 'module' Puppet::Node::Environment.new.expects(:module).with("mymod").raises(Puppet::Module::InvalidName, "name is invalid") Puppet.expects(:value).with(:modulepath).never Dir.stubs(:glob).returns %w{foo} Puppet::Parser::Files.find_manifests("mymod/init.pp") end end describe "when searching for manifests when no module is found" do before do File.stubs(:find).returns(nil) end it "should not look for modules when paths are fully qualified" do Puppet.expects(:value).with(:modulepath).never file = @basepath + "/fully/qualified/file.pp" Dir.stubs(:glob).with(file).returns([file]) Puppet::Parser::Files.find_manifests(file) end it "should return nil and an array of fully qualified files" do file = @basepath + "/fully/qualified/file.pp" Dir.stubs(:glob).with(file).returns([file]) Puppet::Parser::Files.find_manifests(file).should == [nil, [file]] end it "should match against provided fully qualified patterns" do pattern = @basepath + "/fully/qualified/pattern/*" Dir.expects(:glob).with(pattern+'{.pp,.rb}').returns(%w{my file list}) Puppet::Parser::Files.find_manifests(pattern)[1].should == %w{my file list} end it "should look for files relative to the current directory" do # We expand_path to normalize backslashes and slashes on Windows cwd = File.expand_path(Dir.getwd) Dir.expects(:glob).with("#{cwd}/foobar/init.pp").returns(["#{cwd}/foobar/init.pp"]) Puppet::Parser::Files.find_manifests("foobar/init.pp")[1].should == ["#{cwd}/foobar/init.pp"] end it "should only return files, not directories" do pattern = @basepath + "/fully/qualified/pattern/*" file = @basepath + "/my/file" dir = @basepath + "/my/directory" Dir.expects(:glob).with(pattern+'{.pp,.rb}').returns([file, dir]) FileTest.expects(:directory?).with(file).returns(false) FileTest.expects(:directory?).with(dir).returns(true) Puppet::Parser::Files.find_manifests(pattern)[1].should == [file] end it "should return files once only" do pattern = @basepath + "/fully/qualified/pattern/*" Dir.expects(:glob).with(pattern+'{.pp,.rb}').returns(%w{one two one}) Puppet::Parser::Files.find_manifests(pattern)[1].should == %w{one two} end end describe "when searching for manifests in a found module" do it "should return the name of the module and the manifests from the first found module" do mod = Puppet::Module.new("mymod") Puppet::Node::Environment.new.expects(:module).with("mymod").returns mod mod.expects(:match_manifests).with("init.pp").returns(%w{/one/mymod/manifests/init.pp}) Puppet::Parser::Files.find_manifests("mymod/init.pp").should == ["mymod", ["/one/mymod/manifests/init.pp"]] end it "should use the node environment if specified" do mod = Puppet::Module.new("mymod") Puppet::Node::Environment.new("myenv").expects(:module).with("mymod").returns mod mod.expects(:match_manifests).with("init.pp").returns(%w{/one/mymod/manifests/init.pp}) Puppet::Parser::Files.find_manifests("mymod/init.pp", :environment => "myenv")[1].should == ["/one/mymod/manifests/init.pp"] end after { Puppet.settings.clear } end end diff --git a/spec/unit/parser/functions/require_spec.rb b/spec/unit/parser/functions/require_spec.rb index 692b35305..403392ff5 100755 --- a/spec/unit/parser/functions/require_spec.rb +++ b/spec/unit/parser/functions/require_spec.rb @@ -1,76 +1,70 @@ #!/usr/bin/env rspec require 'spec_helper' describe "the require function" do before :all do Puppet::Parser::Functions.autoloader.loadall end before :each do @catalog = stub 'catalog' @compiler = stub 'compiler', :catalog => @catalog, :environment => nil @scope = Puppet::Parser::Scope.new @scope.stubs(:findresource) @scope.stubs(:compiler).returns(@compiler) @klass = stub 'class', :name => "myclass" @scope.stubs(:find_hostclass).returns(@klass) @resource = Puppet::Parser::Resource.new(:file, "/my/file", :scope => @scope, :source => "source") @resource.stubs(:metaparam_compatibility_mode?).returns false @scope.stubs(:resource).returns @resource end it "should exist" do Puppet::Parser::Functions.function("require").should == "function_require" end it "should delegate to the 'include' puppet function" do @scope.expects(:function_include).with("myclass") @scope.function_require("myclass") end it "should set the 'require' prarameter on the resource to a resource reference" do @scope.stubs(:function_include) @scope.function_require("myclass") @resource["require"].should be_instance_of(Array) @resource["require"][0].should be_instance_of(Puppet::Resource) end - it "should verify the 'include' function is loaded" do - Puppet::Parser::Functions.expects(:function).with(:include).returns(:function_include) - @scope.stubs(:function_include) - @scope.function_require("myclass") - end - it "should include the class but not add a dependency if used on a client not at least version 0.25" do @resource.expects(:metaparam_compatibility_mode?).returns true @scope.expects(:warning) @resource.expects(:set_parameter).never @scope.expects(:function_include) @scope.function_require("myclass") end it "should lookup the absolute class path" do @scope.stubs(:function_include) @scope.expects(:find_hostclass).with("myclass").returns(@klass) @klass.expects(:name).returns("myclass") @scope.function_require("myclass") end it "should append the required class to the require parameter" do @scope.stubs(:function_include) one = Puppet::Resource.new(:file, "/one") @resource[:require] = one @scope.function_require("myclass") @resource[:require].should be_include(one) @resource[:require].detect { |r| r.to_s == "Class[Myclass]" }.should be_instance_of(Puppet::Resource) end end diff --git a/spec/unit/parser/functions/template_spec.rb b/spec/unit/parser/functions/template_spec.rb index 6bce69534..71250fee0 100755 --- a/spec/unit/parser/functions/template_spec.rb +++ b/spec/unit/parser/functions/template_spec.rb @@ -1,64 +1,71 @@ #!/usr/bin/env rspec require 'spec_helper' describe "the template function", :'fails_on_ruby_1.9.2' => true do before :all do Puppet::Parser::Functions.autoloader.loadall end before :each do @scope = Puppet::Parser::Scope.new + s = Puppet::Parser::Scope.new + node = stub 'node' + node.stubs(:name).returns 'test_node' + node.stubs(:classes).returns [] + env = Puppet::Node::Environment.new('production') + node.stubs(:environment).returns env + @scope.compiler = Puppet::Parser::Compiler.new(node) end it "should exist" do Puppet::Parser::Functions.function("template").should == "function_template" end it "should create a TemplateWrapper when called" do tw = stub_everything 'template_wrapper' Puppet::Parser::TemplateWrapper.expects(:new).returns(tw) @scope.function_template("test") end it "should give the template filename to the TemplateWrapper" do tw = stub_everything 'template_wrapper' Puppet::Parser::TemplateWrapper.stubs(:new).returns(tw) tw.expects(:file=).with("test") @scope.function_template("test") end it "should return what TemplateWrapper.result returns" do tw = stub_everything 'template_wrapper' Puppet::Parser::TemplateWrapper.stubs(:new).returns(tw) tw.stubs(:file=).with("test") tw.expects(:result).returns("template contents evaluated") @scope.function_template("test").should == "template contents evaluated" end it "should concatenate template wrapper outputs for multiple templates" do tw1 = stub_everything "template_wrapper1" tw2 = stub_everything "template_wrapper2" Puppet::Parser::TemplateWrapper.stubs(:new).returns(tw1,tw2) tw1.stubs(:file=).with("1") tw2.stubs(:file=).with("2") tw1.stubs(:result).returns("result1") tw2.stubs(:result).returns("result2") @scope.function_template(["1","2"]).should == "result1result2" end it "should raise an error if the template raises an error" do tw = stub_everything 'template_wrapper' Puppet::Parser::TemplateWrapper.stubs(:new).returns(tw) tw.stubs(:result).raises lambda { @scope.function_template("1") }.should raise_error(Puppet::ParseError) end end diff --git a/spec/unit/type/file/content_spec.rb b/spec/unit/type/file/content_spec.rb index 025741f29..4641964a6 100755 --- a/spec/unit/type/file/content_spec.rb +++ b/spec/unit/type/file/content_spec.rb @@ -1,437 +1,439 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/network/http_pool' content = Puppet::Type.type(:file).attrclass(:content) describe content do include PuppetSpec::Files before do @filename = tmpfile('testfile') @resource = Puppet::Type.type(:file).new :path => @filename File.open(@filename, 'w') {|f| f.write "initial file content"} content.stubs(:standalone?).returns(false) end describe "when determining the checksum type" do it "should use the type specified in the source checksum if a source is set" do @resource[:source] = File.expand_path("/foo") @resource.parameter(:source).expects(:checksum).returns "{md5lite}eh" @content = content.new(:resource => @resource) @content.checksum_type.should == :md5lite end it "should use the type specified by the checksum parameter if no source is set" do @resource[:checksum] = :md5lite @content = content.new(:resource => @resource) @content.checksum_type.should == :md5lite end end describe "when determining the actual content to write" do it "should use the set content if available" do @content = content.new(:resource => @resource) @content.should = "ehness" @content.actual_content.should == "ehness" end it "should not use the content from the source if the source is set" do source = mock 'source' @resource.expects(:parameter).never.with(:source).returns source @content = content.new(:resource => @resource) @content.actual_content.should be_nil end end describe "when setting the desired content" do it "should make the actual content available via an attribute" do @content = content.new(:resource => @resource) @content.stubs(:checksum_type).returns "md5" @content.should = "this is some content" @content.actual_content.should == "this is some content" end it "should store the checksum as the desired content" do @content = content.new(:resource => @resource) digest = Digest::MD5.hexdigest("this is some content") @content.stubs(:checksum_type).returns "md5" @content.should = "this is some content" @content.should.must == "{md5}#{digest}" end it "should not checksum 'absent'" do @content = content.new(:resource => @resource) @content.should = :absent @content.should.must == :absent end it "should accept a checksum as the desired content" do @content = content.new(:resource => @resource) digest = Digest::MD5.hexdigest("this is some content") string = "{md5}#{digest}" @content.should = string @content.should.must == string end end describe "when retrieving the current content" do it "should return :absent if the file does not exist" do @content = content.new(:resource => @resource) @resource.expects(:stat).returns nil @content.retrieve.should == :absent end it "should not manage content on directories" do @content = content.new(:resource => @resource) stat = mock 'stat', :ftype => "directory" @resource.expects(:stat).returns stat @content.retrieve.should be_nil end it "should not manage content on links" do @content = content.new(:resource => @resource) stat = mock 'stat', :ftype => "link" @resource.expects(:stat).returns stat @content.retrieve.should be_nil end it "should always return the checksum as a string" do @content = content.new(:resource => @resource) @resource[:checksum] = :mtime stat = mock 'stat', :ftype => "file" @resource.expects(:stat).returns stat time = Time.now @resource.parameter(:checksum).expects(:mtime_file).with(@resource[:path]).returns time @content.retrieve.should == "{mtime}#{time}" end it "should return the checksum of the file if it exists and is a normal file" do @content = content.new(:resource => @resource) stat = mock 'stat', :ftype => "file" @resource.expects(:stat).returns stat @resource.parameter(:checksum).expects(:md5_file).with(@resource[:path]).returns "mysum" @content.retrieve.should == "{md5}mysum" end end describe "when testing whether the content is in sync" do before do @resource[:ensure] = :file @content = content.new(:resource => @resource) end it "should return true if the resource shouldn't be a regular file" do @resource.expects(:should_be_file?).returns false @content.should = "foo" @content.must be_safe_insync("whatever") end it "should return false if the current content is :absent" do @content.should = "foo" @content.should_not be_safe_insync(:absent) end it "should return false if the file should be a file but is not present" do @resource.expects(:should_be_file?).returns true @content.should = "foo" @content.should_not be_safe_insync(:absent) end describe "and the file exists" do before do @resource.stubs(:stat).returns mock("stat") end it "should return false if the current contents are different from the desired content" do @content.should = "some content" @content.should_not be_safe_insync("other content") end it "should return true if the sum for the current contents is the same as the sum for the desired content" do @content.should = "some content" @content.must be_safe_insync("{md5}" + Digest::MD5.hexdigest("some content")) end describe "and Puppet[:show_diff] is set" do before do Puppet[:show_diff] = true end it "should display a diff if the current contents are different from the desired content" do @content.should = "some content" @content.expects(:diff).returns("my diff").once @content.expects(:notice).with("\nmy diff").once @content.safe_insync?("other content") end it "should not display a diff if the sum for the current contents is the same as the sum for the desired content" do @content.should = "some content" @content.expects(:diff).never @content.safe_insync?("{md5}" + Digest::MD5.hexdigest("some content")) end end end describe "and :replace is false" do before do @resource.stubs(:replace?).returns false end it "should be insync if the file exists and the content is different" do @resource.stubs(:stat).returns mock('stat') @content.must be_safe_insync("whatever") end it "should be insync if the file exists and the content is right" do @resource.stubs(:stat).returns mock('stat') @content.must be_safe_insync("something") end it "should not be insync if the file does not exist" do @content.should = "foo" @content.should_not be_safe_insync(:absent) end end end describe "when changing the content" do before do @content = content.new(:resource => @resource) @content.should = "some content" @resource.stubs(:[]).with(:path).returns "/boo" @resource.stubs(:stat).returns "eh" end it "should use the file's :write method to write the content" do @resource.expects(:write).with(:content) @content.sync end it "should return :file_changed if the file already existed" do @resource.expects(:stat).returns "something" @resource.stubs(:write) @content.sync.should == :file_changed end it "should return :file_created if the file did not exist" do @resource.expects(:stat).returns nil @resource.stubs(:write) @content.sync.should == :file_created end end describe "when writing" do before do @content = content.new(:resource => @resource) end it "should attempt to read from the filebucket if no actual content nor source exists" do @fh = File.open(@filename, 'wb') @content.should = "{md5}foo" @content.resource.bucket.class.any_instance.stubs(:getfile).returns "foo" @content.write(@fh) @fh.close end describe "from actual content" do before(:each) do @content.stubs(:actual_content).returns("this is content") end it "should write to the given file handle" do - @fh.expects(:print).with("this is content") - @content.write(@fh) + fh = mock 'filehandle' + fh.expects(:print).with("this is content") + @content.write(fh) end it "should return the current checksum value" do @resource.parameter(:checksum).expects(:sum_stream).returns "checksum" @content.write(@fh).should == "checksum" end end describe "from a file bucket" do it "should fail if a file bucket cannot be retrieved" do @content.should = "{md5}foo" @content.resource.expects(:bucket).returns nil lambda { @content.write(@fh) }.should raise_error(Puppet::Error) end it "should fail if the file bucket cannot find any content" do @content.should = "{md5}foo" bucket = stub 'bucket' @content.resource.expects(:bucket).returns bucket bucket.expects(:getfile).with("foo").raises "foobar" lambda { @content.write(@fh) }.should raise_error(Puppet::Error) end it "should write the returned content to the file" do @content.should = "{md5}foo" bucket = stub 'bucket' @content.resource.expects(:bucket).returns bucket bucket.expects(:getfile).with("foo").returns "mycontent" - @fh.expects(:print).with("mycontent") - @content.write(@fh) + fh = mock 'filehandle' + fh.expects(:print).with("mycontent") + @content.write(fh) end end describe "from local source" do before(:each) do @sourcename = tmpfile('source') @resource = Puppet::Type.type(:file).new :path => @filename, :backup => false, :source => @sourcename @source_content = "source file content\r\n"*10000 @sourcefile = File.open(@sourcename, 'wb') {|f| f.write @source_content} @content = @resource.newattr(:content) @source = @resource.parameter :source #newattr(:source) end it "should copy content from the source to the file" do @resource.write(@source) IO.binread(@filename).should == @source_content end it "should return the checksum computed" do File.open(@filename, 'wb') do |file| @content.write(file).should == "{md5}#{Digest::MD5.hexdigest(@source_content)}" end end end describe "from remote source" do before(:each) do @resource = Puppet::Type.type(:file).new :path => @filename, :backup => false @response = stub_everything 'response', :code => "200" @source_content = "source file content\n"*10000 @response.stubs(:read_body).multiple_yields(*(["source file content\n"]*10000)) @conn = stub_everything 'connection' @conn.stubs(:request_get).yields(@response) Puppet::Network::HttpPool.stubs(:http_instance).returns @conn @content = @resource.newattr(:content) @sourcename = "puppet:///test/foo" @source = @resource.newattr(:source) @source.stubs(:metadata).returns stub_everything('metadata', :source => @sourcename, :ftype => 'file') end it "should write the contents to the file" do @resource.write(@source) IO.binread(@filename).should == @source_content end it "should not write anything if source is not found" do @response.stubs(:code).returns("404") lambda {@resource.write(@source)}.should raise_error(Net::HTTPError) { |e| e.message =~ /404/ } File.read(@filename).should == "initial file content" end it "should raise an HTTP error in case of server error" do @response.stubs(:code).returns("500") lambda { @content.write(@fh) }.should raise_error { |e| e.message.include? @source_content } end it "should return the checksum computed" do File.open(@filename, 'w') do |file| @content.write(file).should == "{md5}#{Digest::MD5.hexdigest(@source_content)}" end end end # These are testing the implementation rather than the desired behaviour; while that bites, there are a whole # pile of other methods in the File type that depend on intimate details of this implementation and vice-versa. # If these blow up, you are gonna have to review the callers to make sure they don't explode! --daniel 2011-02-01 describe "each_chunk_from should work" do before do @content = content.new(:resource => @resource) end it "when content is a string" do @content.each_chunk_from('i_am_a_string') { |chunk| chunk.should == 'i_am_a_string' } end # The following manifest is a case where source and content.should are both set # file { "/tmp/mydir" : # source => '/tmp/sourcedir', # recurse => true, # } it "when content checksum comes from source" do source_param = Puppet::Type.type(:file).attrclass(:source) source = source_param.new(:resource => @resource) @content.should = "{md5}123abcd" @content.expects(:chunk_file_from_source).returns('from_source') @content.each_chunk_from(source) { |chunk| chunk.should == 'from_source' } end it "when no content, source, but ensure present" do @resource[:ensure] = :present @content.each_chunk_from(nil) { |chunk| chunk.should == '' } end # you might do this if you were just auditing it "when no content, source, but ensure file" do @resource[:ensure] = :file @content.each_chunk_from(nil) { |chunk| chunk.should == '' } end it "when source_or_content is nil and content not a checksum" do @content.each_chunk_from(nil) { |chunk| chunk.should == '' } end # the content is munged so that if it's a checksum nil gets passed in it "when content is a checksum it should try to read from filebucket" do @content.should = "{md5}123abcd" @content.expects(:read_file_from_filebucket).once.returns('im_a_filebucket') @content.each_chunk_from(nil) { |chunk| chunk.should == 'im_a_filebucket' } end it "when running as puppet apply" do @content.class.expects(:standalone?).returns true source_or_content = stubs('source_or_content') source_or_content.expects(:content).once.returns :whoo @content.each_chunk_from(source_or_content) { |chunk| chunk.should == :whoo } end it "when running from source with a local file" do source_or_content = stubs('source_or_content') source_or_content.expects(:local?).returns true @content.expects(:chunk_file_from_disk).with(source_or_content).once.yields 'woot' @content.each_chunk_from(source_or_content) { |chunk| chunk.should == 'woot' } end it "when running from source with a remote file" do source_or_content = stubs('source_or_content') source_or_content.expects(:local?).returns false @content.expects(:chunk_file_from_source).with(source_or_content).once.yields 'woot' @content.each_chunk_from(source_or_content) { |chunk| chunk.should == 'woot' } end end end end diff --git a/spec/unit/type/file/ctime.rb b/spec/unit/type/file/ctime_spec.rb similarity index 100% rename from spec/unit/type/file/ctime.rb rename to spec/unit/type/file/ctime_spec.rb diff --git a/spec/unit/type/file/mtime.rb b/spec/unit/type/file/mtime_spec.rb similarity index 100% rename from spec/unit/type/file/mtime.rb rename to spec/unit/type/file/mtime_spec.rb diff --git a/spec/unit/type/file/type.rb b/spec/unit/type/file/type_spec.rb similarity index 100% rename from spec/unit/type/file/type.rb rename to spec/unit/type/file/type_spec.rb