rspec-puppet-1.0.1/0000755000004100000410000000000012257220206014166 5ustar www-datawww-datarspec-puppet-1.0.1/bin/0000755000004100000410000000000012257220206014736 5ustar www-datawww-datarspec-puppet-1.0.1/bin/rspec-puppet-init0000755000004100000410000000065312257220206020260 0ustar www-datawww-data#!/usr/bin/env ruby $:.unshift File.join(File.dirname(__FILE__), *%w[.. lib]) require 'rspec-puppet' require 'optparse' options = { :module_name => nil, } OptionParser.new do |opts| opts.banner = "Usage: rspec-puppet-init [options]" opts.on('-n', '--name NAME', 'The name of the module (override autodetection)') do |v| options[:module_name] = v end end.parse! RSpec::Puppet::Setup.run(options[:module_name]) rspec-puppet-1.0.1/lib/0000755000004100000410000000000012257220206014734 5ustar www-datawww-datarspec-puppet-1.0.1/lib/rspec-puppet/0000755000004100000410000000000012257220206017363 5ustar www-datawww-datarspec-puppet-1.0.1/lib/rspec-puppet/matchers.rb0000644000004100000410000000041012257220206021511 0ustar www-datawww-datarequire 'rspec-puppet/matchers/create_generic' require 'rspec-puppet/matchers/include_class' require 'rspec-puppet/matchers/compile' require 'rspec-puppet/matchers/run' require 'rspec-puppet/matchers/count_generic' require 'rspec-puppet/matchers/dynamic_matchers' rspec-puppet-1.0.1/lib/rspec-puppet/example.rb0000644000004100000410000000162012257220206021342 0ustar www-datawww-datarequire 'rspec-puppet/support' require 'rspec-puppet/example/define_example_group' require 'rspec-puppet/example/class_example_group' require 'rspec-puppet/example/function_example_group' require 'rspec-puppet/example/host_example_group' RSpec::configure do |c| def c.escaped_path(*parts) Regexp.compile(parts.join('[\\\/]')) end c.include RSpec::Puppet::DefineExampleGroup, :type => :define, :example_group => { :file_path => c.escaped_path(%w[spec defines]) } c.include RSpec::Puppet::ClassExampleGroup, :type => :class, :example_group => { :file_path => c.escaped_path(%w[spec classes]) } c.include RSpec::Puppet::FunctionExampleGroup, :type => :puppet_function, :example_group => { :file_path => c.escaped_path(%w[spec functions]) } c.include RSpec::Puppet::HostExampleGroup, :type => :host, :example_group => { :file_path => c.escaped_path(%w[spec hosts]) } end rspec-puppet-1.0.1/lib/rspec-puppet/errors.rb0000644000004100000410000000362012257220206021225 0ustar www-datawww-datamodule RSpec::Puppet module Errors class MatchError < StandardError attr_reader :param, :expected, :actual, :negative def initialize(param, expected, actual, negative) @param = param @expected = expected.inspect @actual = actual.inspect @negative = negative end def message if negative == true "#{param} not set to #{expected} but it is set to #{actual}" else "#{param} set to #{expected} but it is set to #{actual}" end end def to_s message end end class RegexpMatchError < MatchError def message if negative == true "#{param} not matching #{expected} but its value of #{actual} does" else "#{param} matching #{expected} but its value of #{actual} does not" end end end class ProcMatchError < MatchError def message if negative == true "#{param} passed to the block would not return `#{expected}` but it did" else "#{param} passed to the block would return `#{expected}` but it is `#{actual}`" end end end class RelationshipError < StandardError attr_reader :from, :to def initialize(from, to) @from = from @to = to end def to_s message end end class BeforeRelationshipError < RelationshipError def message "#{from} to come before #{to} in the graph" end end class RequireRelationshipError < RelationshipError def message "#{from} to require #{to} in the graph" end end class NotifyRelationshipError < RelationshipError def message "#{from} to notify #{to}" end end class SubscribeRelationshipError < RelationshipError def message "#{from} to be subscribed to #{to}" end end end end rspec-puppet-1.0.1/lib/rspec-puppet/support.rb0000644000004100000410000001165312257220206021432 0ustar www-datawww-datamodule RSpec::Puppet module Support @@cache = {} def catalogue(type) vardir = setup_puppet code = [import_str, pre_cond, test_manifest(type)].join("\n") node_name = nodename(type) catalogue = build_catalog(node_name, facts_hash(node_name), code) FileUtils.rm_rf(vardir) if File.directory?(vardir) catalogue end def import_str klass_name = self.class.top_level_description.downcase if File.exists?(File.join(Puppet[:modulepath], 'manifests', 'init.pp')) path_to_manifest = File.join([ Puppet[:modulepath], 'manifests', klass_name.split('::')[1..-1] ].flatten) import_str = [ "import '#{Puppet[:modulepath]}/manifests/init.pp'", "import '#{path_to_manifest}.pp'", '', ].join("\n") elsif File.exists?(Puppet[:modulepath]) import_str = "import '#{Puppet[:manifest]}'\n" else import_str = "" end import_str end def test_manifest(type) klass_name = self.class.top_level_description.downcase if type == :class if !self.respond_to?(:params) || params == {} "include #{klass_name}" else "class { '#{klass_name}': #{param_str} }" end elsif type == :define if self.respond_to? :params "#{klass_name} { '#{title}': #{param_str} }" else "#{klass_name} { '#{title}': }" end elsif type == :host "" end end def nodename(type) if [:class, :define, :function].include? type self.respond_to?(:node) ? node : Puppet[:certname] else self.class.top_level_description.downcase end end def pre_cond if self.respond_to?(:pre_condition) && !pre_condition.nil? if pre_condition.is_a? Array pre_condition.join("\n") else pre_condition end else '' end end def facts_hash(node) facts_val = { 'hostname' => node.split('.').first, 'fqdn' => node, 'domain' => node.split('.', 2).last, } if RSpec.configuration.default_facts.any? facts_val.merge!(munge_facts(RSpec.configuration.default_facts)) end facts_val.merge!(munge_facts(facts)) if self.respond_to?(:facts) facts_val end def param_str params.keys.map do |r| param_val = escape_special_chars(params[r].inspect) "#{r.to_s} => #{param_val}" end.join(', ') end def setup_puppet vardir = Dir.mktmpdir Puppet[:vardir] = vardir [ [:modulepath, :module_path], [:manifestdir, :manifest_dir], [:manifest, :manifest], [:templatedir, :template_dir], [:config, :config], [:confdir, :confdir], [:hiera_config, :hiera_config], ].each do |a, b| value = self.respond_to?(b) ? self.send(b) : RSpec.configuration.send(b) begin Puppet[a] = value rescue ArgumentError Puppet.settings.setdefaults(:main, {a => {:default => value, :desc => a.to_s}}) end end Puppet[:libdir] = Dir["#{Puppet[:modulepath]}/*/lib"].entries.join(File::PATH_SEPARATOR) vardir end def build_catalog_without_cache(nodename, facts_val, code) Puppet[:code] = code stub_facts! facts_val node_obj = Puppet::Node.new(nodename) node_obj.merge(facts_val) # trying to be compatible with 2.7 as well as 2.6 if Puppet::Resource::Catalog.respond_to? :find Puppet::Resource::Catalog.find(node_obj.name, :use_node => node_obj) else Puppet::Resource::Catalog.indirection.find(node_obj.name, :use_node => node_obj) end end def stub_facts!(facts) facts.each { |k, v| Facter.add(k) { setcode { v } } } end def build_catalog(*args) @@cache[args] ||= self.build_catalog_without_cache(*args) end def munge_facts(facts) output = {} facts.keys.each { |key| output[key.to_s] = facts[key] } output end def escape_special_chars(string) string.gsub!(/\$/, "\\$") string end def scope(compiler, node_name) if Puppet.version =~ /^2\.[67]/ # loadall should only be necessary prior to 3.x # Please note, loadall needs to happen first when creating a scope, otherwise # you might receive undefined method `function_*' errors Puppet::Parser::Functions.autoloader.loadall scope = Puppet::Parser::Scope.new(:compiler => compiler) else scope = Puppet::Parser::Scope.new(compiler) end scope.source = Puppet::Resource::Type.new(:node, node_name) scope.parent = compiler.topscope scope end def build_node(name, opts = {}) node_environment = Puppet::Node::Environment.new('test') opts.merge!({:environment => node_environment}) Puppet::Node.new(name, opts) end end end rspec-puppet-1.0.1/lib/rspec-puppet/matchers/0000755000004100000410000000000012257220206021171 5ustar www-datawww-datarspec-puppet-1.0.1/lib/rspec-puppet/matchers/count_generic.rb0000644000004100000410000000376312257220206024353 0ustar www-datawww-datamodule RSpec::Puppet module ManifestMatchers class CountGeneric def initialize(type, count, *method) if type.nil? @type = method[0].to_s.gsub(/^have_(.+)_resource_count$/, '\1') else @type = type end @referenced_type = referenced_type(@type) @expected_number = count.to_i end def matches?(catalogue) if @type == "resource" @actual_number = catalogue.resources.count do |res| !(['Class', 'Node'].include? res.type) end # Puppet automatically adds Stage[main] @actual_number = @actual_number - 1 else @actual_number = catalogue.resources.count do |res| res.type == @referenced_type end # Puppet automatically adds Class[main] and Class[Settings] @actual_number = @actual_number - 2 if @type == "class" end @actual_number == @expected_number end def description desc = [] desc << "contain exactly #{@expected_number}" if @type == "class" desc << "#{@expected_number == 1 ? "class" : "classes" }" else unless @type == "resource" desc << "#{@referenced_type}" end desc << "#{@expected_number == 1 ? "resource" : "resources" }" end desc.join(" ") end def failure_message_for_should "expected that the catalogue would " + description + " but it contains #{@actual_number}" end def failure_message_for_should_not "expected that the catalogue would not " + description + " but it does" end private def referenced_type(type) type.split('__').map { |r| r.capitalize }.join('::') end end def have_class_count(count) RSpec::Puppet::ManifestMatchers::CountGeneric.new('class', count) end def have_resource_count(count) RSpec::Puppet::ManifestMatchers::CountGeneric.new('resource', count) end end end rspec-puppet-1.0.1/lib/rspec-puppet/matchers/run.rb0000644000004100000410000000465112257220206022330 0ustar www-datawww-datamodule RSpec::Puppet module FunctionMatchers class Run def matches?(func_obj) if @params @func = lambda { func_obj.call(@params) } else @func = lambda { func_obj.call } end unless @expected_error.nil? result = false begin @func.call rescue Exception => e @actual_error = e.class if e.is_a?(@expected_error) case @expected_error_message when nil result = true when Regexp result = @expected_error_message =~ e.message else result = @expected_error_message == e.message end end end result else unless @expected_return.nil? @actual_return = @func.call @actual_return == @expected_return else begin @func.call rescue false end true end end end def with_params(*params) @params = params self end def and_return(value) @expected_return = value self end def and_raise_error(error_or_message, message=nil) case error_or_message when String, Regexp @expected_error, @expected_error_message = Exception, error_or_message else @expected_error, @expected_error_message = error_or_message, message end self end def failure_message_for_should(func_obj) failure_message_generic(:should, func_obj) end def failure_message_for_should_not(func_obj) failure_message_generic(:should_not, func_obj) end private def failure_message_generic(type, func_obj) func_name = func_obj.name.gsub(/^function_/, '') func_params = @params.inspect[1..-2] message = "expected #{func_name}(#{func_params}) to " message << "not " if type == :should_not if @expected_return message << "have returned #{@expected_return.inspect}" if type == :should message << " instead of #{@actual_return.inspect}" end elsif @expected_error message << "have raised #{@expected_error.inspect}" else message << "have run successfully" end message end end end end rspec-puppet-1.0.1/lib/rspec-puppet/matchers/parameter_matcher.rb0000644000004100000410000000655212257220206025211 0ustar www-datawww-datamodule RSpec::Puppet module ManifestMatchers class ParameterMatcher include RSpec::Puppet::Errors # @param parameter [Symbol] The specific parameter to check # @param value [Object] The expected data to match the parameter against # @param type [:should, :not] Whether the given parameter should match def initialize(parameter, value, type) @parameter, @value, @type = parameter, value, type @should_match = (type == :should) @errors = [] end # Ensure that the actual parameter matches the expected parameter. # # @param resource [Hash] A hash representing a Puppet # resource in the catalog # # @return [true, false] def matches?(resource) @resource = resource actual = @resource[@parameter] expected = @value # Puppet flattens an array with a single value into just the value and # this can cause confusion when testing as people expect when you put # an array in, you'll get an array out. if expected.is_a?(Array) && expected.length == 1 actual = Array[actual] unless actual.is_a?(Array) end retval = check(expected, actual) unless retval @errors << MatchError.new(@parameter, expected, actual, !@should_match) end retval end # @!attribute [r] errors # @return [Array] All expectation errors # generated on this parameter. attr_reader :errors private # Recursively check that the `expected` and `actual` data structures match # # @param expected [Object] The expected value of the given resource param # @param actual [Object] The value of the resource as found in the catalogue # # @return [true, false] If the resource matched def check(expected, actual) case expected when Proc check_proc(expected, actual) when Regexp check_regexp(expected, actual) when Hash check_hash(expected, actual) when Array check_array(expected, actual) else check_string(expected, actual) end end def check_proc(expected, actual) expected_return = @should_match actual_return = expected.call(actual) actual_return == expected_return end def check_regexp(expected, actual) !!(actual.to_s.match expected) == @should_match end # Ensure that two hashes have the same number of keys, and that for each # key in the expected hash, there's a stringified key in the actual hash # with a matching value. def check_hash(expected, actual) op = @should_match ? :"==" : :"!=" unless expected.keys.size.send(op, actual.keys.size) return false end expected.keys.all? do |key| check(expected[key], actual[key.to_s]) end end def check_array(expected, actual) op = @should_match ? :"==" : :"!=" unless expected.size.send(op, actual.size) return false end (0...expected.size).all? do |index| check(expected[index], actual[index]) end end def check_string(expected, actual) (expected.to_s == actual.to_s) == @should_match end end end end rspec-puppet-1.0.1/lib/rspec-puppet/matchers/compile.rb0000644000004100000410000000652212257220206023153 0ustar www-datawww-datamodule RSpec::Puppet module ManifestMatchers class Compile def initialize @failed_resource = "" @check_deps = false @cycles = [] end def with_all_deps @check_deps = true self end def matches?(catalogue) @catalogue = catalogue if cycles_found? false elsif @check_deps == true && missing_dependencies? false else true end end def description "compile the catalogue without cycles" end def failure_message_for_should unless @cycles.empty? "dependency cycles found: #{@cycles.join('; ')}" else "expected that the catalogue would include #{@failed_resource}" end end def failure_message_for_should_not "expected that the catalogue would not compile but it does" end private def missing_dependencies? retval = false resource_vertices = @catalogue.vertices.select { |v| v.is_a? Puppet::Resource } resource_vertices.each do |vertex| vertex.each do |param,value| if [:require, :subscribe, :notify, :before].include? param value = Array[value] unless value.is_a? Array value.each do |val| if val.is_a? Puppet::Resource retval = true unless resource_exists?(val, vertex) end end end end end retval end def resource_hash @resource_hash ||= Proc.new do res_hash = {} @catalogue.vertices.each do |vertex| if vertex.is_a? Puppet::Resource res_hash[vertex.ref] = 1 if vertex[:alias] res_hash["#{vertex.type.to_s}[#{vertex[:alias]}]"] = 1 end end end res_hash end.call end def check_resource(res) if resource_hash[res.ref] true elsif res[:alias] && resource_hash["#{res.type.to_s}[#{res[:alias]}]"] true else false end end def resource_exists?(res, vertex) unless check_resource(res) @failed_resource = "#{res.ref} used at #{vertex.file}:#{vertex.line} in #{vertex.ref}" false else true end end def cycles_found? retval = false begin cat = @catalogue.to_ral.relationship_graph cat.write_graph(:resources) if cat.respond_to? :find_cycles_in_graph find_cycles(cat) else find_cycles_legacy(cat) end retval = true unless @cycles.empty? rescue Puppet::Error retval = true end retval end def find_cycles(catalogue) cycles = catalogue.find_cycles_in_graph if cycles.length > 0 cycles.each do |cycle| paths = catalogue.paths_in_cycle(cycle) @cycles << (paths.map{ |path| '(' + path.join(" => ") + ')'}.join("\n") + "\n") end end end def find_cycles_legacy(catalogue) begin catalogue.topsort rescue Puppet::Error => e @cycles = [e.message.rpartition(';').first.partition(':').last] end end end end end rspec-puppet-1.0.1/lib/rspec-puppet/matchers/include_class.rb0000644000004100000410000000076112257220206024332 0ustar www-datawww-datamodule RSpec::Puppet module ManifestMatchers extend RSpec::Matchers::DSL matcher :include_class do |expected_class| match do |catalogue| RSpec.deprecate(:include_class, :contain_class) catalogue.classes.include?(expected_class) end description do "include Class[#{expected_class}]" end failure_message_for_should do |actual| "expected that the catalogue would include Class[#{expected_class}]" end end end end rspec-puppet-1.0.1/lib/rspec-puppet/matchers/create_generic.rb0000644000004100000410000001560512257220206024464 0ustar www-datawww-datarequire 'rspec-puppet/matchers/parameter_matcher' module RSpec::Puppet module ManifestMatchers class CreateGeneric include RSpec::Puppet::Errors def initialize(*args, &block) @exp_resource_type = args.shift.to_s.gsub(/^(create|contain)_/, '') @args = args @block = block @referenced_type = referenced_type(@exp_resource_type) @title = args[0] @errors = [] @expected_params = [] @expected_undef_params = [] @notifies = [] @subscribes = [] @requires = [] @befores = [] end def with(*args, &block) params = args.shift @expected_params = @expected_params | params.to_a self end def only_with(*args, &block) params = args.shift @expected_params_count = (@expected_params_count || 0) + params.size self.with(params, &block) end def without(*args, &block) params = args.shift @expected_undef_params = @expected_undef_params | Array(params) self end def that_notifies(resource) @notifies << resource self end def that_subscribes_to(resource) @subscribes << resource self end def that_requires(resource) @requires << resource self end def that_comes_before(resource) @befores << resource self end def method_missing(method, *args, &block) if method.to_s =~ /^with_/ param = method.to_s.gsub(/^with_/, '') @expected_params << [param, args[0]] self elsif method.to_s =~ /^only_with_/ param = method.to_s.gsub(/^only_with_/, '') @expected_params_count = (@expected_params_count || 0) + 1 @expected_params << [param, args[0]] self elsif method.to_s =~ /^without_/ param = method.to_s.gsub(/^without_/, '') @expected_undef_params << [param, args[0]] self else super end end def matches?(catalogue) ret = true resource = catalogue.resource(@referenced_type, @title) if resource.nil? false else rsrc_hsh = resource.to_hash if @expected_params_count unless rsrc_hsh.size == @expected_params_count ret = false (@errors ||= []) << "exactly #{@expected_params_count} parameters but the catalogue contains #{rsrc_hsh.size}" end end check_params(rsrc_hsh, @expected_params, :should) if @expected_params.any? check_params(rsrc_hsh, @expected_undef_params, :not) if @expected_undef_params.any? check_befores(catalogue, resource) if @befores.any? check_requires(catalogue, resource) if @requires.any? check_notifies(catalogue, resource) if @notifies.any? check_subscribes(catalogue, resource) if @subscribes.any? @errors.empty? end end def failure_message_for_should "expected that the catalogue would contain #{@referenced_type}[#{@title}]#{errors}" end def failure_message_for_should_not "expected that the catalogue would not contain #{@referenced_type}[#{@title}]#{errors}" end def description values = [] if @expected_params_count values << "exactly #{@expected_params_count} parameters" end if @expected_params.any? values.concat(generate_param_list(@expected_params, :should)) end if @expected_undef_params.any? values.concat(generate_param_list(@expected_undef_params, :not)) end unless values.empty? if values.length == 1 value_str = " with #{values.first}" else value_str = " with #{values[0..-2].join(", ")} and #{values[-1]}" end end "contain #{@referenced_type}[#{@title}]#{value_str}" end private def referenced_type(type) type.split('__').map { |r| r.capitalize }.join('::') end def errors @errors.empty? ? "" : " with #{@errors.join(', and parameter ')}" end def generate_param_list(list, type) output = [] list.each do |param, value| if value.nil? output << "#{param.to_s} #{type == :not ? 'un' : ''}defined" else a = type == :not ? '!' : '=' b = value.is_a?(Regexp) ? '~' : '>' output << "#{param.to_s} #{a}#{b} #{value.inspect}" end end output end def check_befores(catalogue, resource) @befores.each do |ref| unless precedes?(resource, catalogue.resource(ref)) @errors << BeforeRelationshipError.new(resource.to_ref, ref) end end end def check_requires(catalogue, resource) @requires.each do |ref| unless precedes?(catalogue.resource(ref), resource) @errors << RequireRelationshipError.new(resource.to_ref, ref) end end end def check_notifies(catalogue, resource) @notifies.each do |ref| unless notifies?(resource, catalogue.resource(ref)) @errors << NotifyRelationshipError.new(resource.to_ref, ref) end end end def check_subscribes(catalogue, resource) @subscribes.each do |ref| unless notifies?(catalogue.resource(ref), resource) @errors << SubscribeRelationshipError.new(resource.to_ref, ref) end end end def relationship_refs(array) Array[array].flatten.map do |resource| resource.respond_to?(:to_ref) ? resource.to_ref : resource end end def precedes?(first, second) before_refs = relationship_refs(first[:before]) require_refs = relationship_refs(second[:require]) before_refs.include?(second.to_ref) || require_refs.include?(first.to_ref) end def notifies?(first, second) notify_refs = relationship_refs(first[:notify]) subscribe_refs = relationship_refs(second[:subscribe]) notify_refs.include?(second.to_ref) || subscribe_refs.include?(first.to_ref) end # @param resource [Hash] The resource in the catalog # @param list [Array] The expected values of the resource # @param type [:should, :not] Whether the given parameters should/not match def check_params(resource, list, type) list.each do |param, value| param = param.to_sym if value.nil? then unless resource[param].nil? @errors << "#{param} undefined" end else m = ParameterMatcher.new(param, value, type) unless m.matches?(resource) @errors.concat m.errors end end end end end end end rspec-puppet-1.0.1/lib/rspec-puppet/matchers/dynamic_matchers.rb0000644000004100000410000000115512257220206025032 0ustar www-datawww-datamodule RSpec::Puppet module ManifestMatchers def method_missing(method, *args, &block) return RSpec::Puppet::ManifestMatchers::CreateGeneric.new(method, *args, &block) if method.to_s =~ /^(create|contain)_/ return RSpec::Puppet::ManifestMatchers::CountGeneric.new(nil, args[0], method) if method.to_s =~ /^have_.+_count$/ return RSpec::Puppet::ManifestMatchers::Compile.new if method == :compile super end end module FunctionMatchers def method_missing(method, *args, &block) return RSpec::Puppet::FunctionMatchers::Run.new if method == :run super end end end rspec-puppet-1.0.1/lib/rspec-puppet/example/0000755000004100000410000000000012257220206021016 5ustar www-datawww-datarspec-puppet-1.0.1/lib/rspec-puppet/example/define_example_group.rb0000644000004100000410000000031312257220206025521 0ustar www-datawww-datamodule RSpec::Puppet module DefineExampleGroup include RSpec::Puppet::ManifestMatchers include RSpec::Puppet::Support def subject @catalogue ||= catalogue(:define) end end end rspec-puppet-1.0.1/lib/rspec-puppet/example/class_example_group.rb0000644000004100000410000000031112257220206025372 0ustar www-datawww-datamodule RSpec::Puppet module ClassExampleGroup include RSpec::Puppet::ManifestMatchers include RSpec::Puppet::Support def subject @catalogue ||= catalogue(:class) end end end rspec-puppet-1.0.1/lib/rspec-puppet/example/function_example_group.rb0000644000004100000410000000246212257220206026123 0ustar www-datawww-datamodule RSpec::Puppet module FunctionExampleGroup include RSpec::Puppet::FunctionMatchers include RSpec::Puppet::ManifestMatchers include RSpec::Puppet::Support def subject function_name = self.class.top_level_description.downcase vardir = setup_puppet node_name = nodename(:function) facts_val = facts_hash(node_name) # if we specify a pre_condition, we should ensure that we compile that code # into a catalog that is accessible from the scope where the function is called Puppet[:code] = pre_cond compiler = build_compiler(node_name, facts_val) function_scope = scope(compiler, node_name) # Return the method instance for the function. This can be used with # method.call return nil unless Puppet::Parser::Functions.function(function_name) FileUtils.rm_rf(vardir) if File.directory?(vardir) function_scope.method("function_#{function_name}".intern) end # get a compiler with an attached compiled catalog def build_compiler(node_name, fact_values) node_options = { :parameters => fact_values, } stub_facts! fact_values node = build_node(node_name, node_options) compiler = Puppet::Parser::Compiler.new(node) compiler.compile compiler end end end rspec-puppet-1.0.1/lib/rspec-puppet/example/host_example_group.rb0000644000004100000410000000030712257220206025247 0ustar www-datawww-datamodule RSpec::Puppet module HostExampleGroup include RSpec::Puppet::ManifestMatchers include RSpec::Puppet::Support def subject @catalogue ||= catalogue(:host) end end end rspec-puppet-1.0.1/lib/rspec-puppet/setup.rb0000644000004100000410000001026712257220206021056 0ustar www-datawww-datarequire 'puppet' require 'fileutils' module RSpec::Puppet class Setup def self.run(module_name=nil) unless is_module_dir? $stderr.puts "Does not appear to be a Puppet module. Aborting" return false end if module_name.nil? module_name = get_module_name if module_name.nil? $stderr.puts "Unable to determine module name. Aborting" return false end end [ 'spec', 'spec/classes', 'spec/defines', 'spec/functions', 'spec/hosts', 'spec/fixtures', 'spec/fixtures/manifests', 'spec/fixtures/modules', "spec/fixtures/modules/#{module_name}", ].each { |dir| safe_mkdir(dir) } safe_touch('spec/fixtures/manifests/site.pp') ['manifests','lib','files','templates'].each do |dir| if File.exist? dir safe_make_symlink("../../../../#{dir}", "spec/fixtures/modules/#{module_name}/#{dir}") end end safe_create_spec_helper safe_create_rakefile end protected def self.get_module_name module_name = nil Dir["manifests/*.pp"].entries.each do |manifest| module_name = get_module_name_from_file(manifest) break unless module_name.nil? end module_name end def self.get_module_name_from_file(file) p = Puppet::Parser::Lexer.new module_name = nil p.string = File.read(file) tokens = p.fullscan i = tokens.index { |token| [:CLASS, :DEFINE].include? token.first } unless i.nil? module_name = tokens[i + 1].last[:value].split('::').first end module_name end def self.is_module_dir? Dir["*"].entries.include? "manifests" end def self.safe_mkdir(dir) if File.exists? dir unless File.directory? dir $stderr.puts "!! #{dir} already exists and is not a directory" end else FileUtils.mkdir dir puts " + #{dir}/" end end def self.safe_touch(file) if File.exists? file unless File.file? file $stderr.puts "!! #{file} already exists and is not a regular file" end else FileUtils.touch file puts " + #{file}" end end def self.safe_create_file(filename, content) if File.exists? filename old_content = File.read(filename) if old_content != content $stderr.puts "!! #{filename} already exists and differs from template" end else File.open(filename, 'w') do |f| f.puts content end puts " + #{filename}" end end def self.safe_create_spec_helper content = <<-EOF require 'rspec-puppet' fixture_path = File.expand_path(File.join(__FILE__, '..', 'fixtures')) RSpec.configure do |c| c.module_path = File.join(fixture_path, 'modules') c.manifest_dir = File.join(fixture_path, 'manifests') end EOF safe_create_file('spec/spec_helper.rb', content) end def self.safe_make_symlink(source, target) if File.exists? target unless File.symlink? target $stderr.puts "!! #{file} already exists and is not a symlink" end else FileUtils.ln_s(source, target) puts " + #{target}" end end def self.safe_create_rakefile content = <<-'EOF' require 'rake' require 'rspec/core/rake_task' desc "Run all RSpec code examples" RSpec::Core::RakeTask.new(:rspec) do |t| t.rspec_opts = File.read("spec/spec.opts").chomp || "" end SPEC_SUITES = (Dir.entries('spec') - ['.', '..','fixtures']).select {|e| File.directory? "spec/#{e}" } namespace :rspec do SPEC_SUITES.each do |suite| desc "Run #{suite} RSpec code examples" RSpec::Core::RakeTask.new(suite) do |t| t.pattern = "spec/#{suite}/**/*_spec.rb" t.rspec_opts = File.read("spec/spec.opts").chomp || "" end end end task :default => :rspec begin if Gem::Specification::find_by_name('puppet-lint') require 'puppet-lint/tasks/puppet-lint' PuppetLint.configuration.ignore_paths = ["spec/**/*.pp", "vendor/**/*.pp"] task :default => [:rspec, :lint] end rescue Gem::LoadError end EOF safe_create_file('Rakefile', content) end end end rspec-puppet-1.0.1/lib/rspec-puppet.rb0000644000004100000410000000244612257220206017716 0ustar www-datawww-datarequire 'puppet' require 'rspec' require 'fileutils' require 'tmpdir' require 'rspec-puppet/errors' require 'rspec-puppet/matchers' require 'rspec-puppet/example' require 'rspec-puppet/setup' begin require 'puppet/test/test_helper' rescue LoadError end RSpec.configure do |c| c.add_setting :module_path, :default => '/etc/puppet/modules' c.add_setting :manifest_dir, :default => nil c.add_setting :manifest, :default => nil c.add_setting :template_dir, :default => nil c.add_setting :config, :default => nil c.add_setting :confdir, :default => '/etc/puppet' c.add_setting :default_facts, :default => {} c.add_setting :hiera_config, :default => '/dev/null' if defined?(Puppet::Test::TestHelper) begin Puppet::Test::TestHelper.initialize rescue NoMethodError Puppet::Test::TestHelper.before_each_test end c.before :all do begin Puppet::Test::TestHelper.before_all_tests rescue end end c.after :all do begin Puppet::Test::TestHelper.after_all_tests rescue end end c.before :each do begin Puppet::Test::TestHelper.before_each_test rescue end end c.after :each do begin Puppet::Test::TestHelper.after_each_test rescue end end end end rspec-puppet-1.0.1/metadata.yml0000644000004100000410000000375112257220206016477 0ustar www-datawww-data--- !ruby/object:Gem::Specification name: rspec-puppet version: !ruby/object:Gem::Version version: 1.0.1 platform: ruby authors: - Tim Sharpe autorequire: bindir: bin cert_chain: [] date: 2013-12-06 00:00:00.000000000 Z dependencies: - !ruby/object:Gem::Dependency name: rspec requirement: !ruby/object:Gem::Requirement requirements: - - '>=' - !ruby/object:Gem::Version version: '0' type: :runtime prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - '>=' - !ruby/object:Gem::Version version: '0' description: RSpec tests for your Puppet manifests email: tim@sharpe.id.au executables: - rspec-puppet-init extensions: [] extra_rdoc_files: [] files: - README.md - lib/rspec-puppet/errors.rb - lib/rspec-puppet/example/class_example_group.rb - lib/rspec-puppet/example/define_example_group.rb - lib/rspec-puppet/example/function_example_group.rb - lib/rspec-puppet/example/host_example_group.rb - lib/rspec-puppet/example.rb - lib/rspec-puppet/matchers/compile.rb - lib/rspec-puppet/matchers/count_generic.rb - lib/rspec-puppet/matchers/create_generic.rb - lib/rspec-puppet/matchers/dynamic_matchers.rb - lib/rspec-puppet/matchers/include_class.rb - lib/rspec-puppet/matchers/parameter_matcher.rb - lib/rspec-puppet/matchers/run.rb - lib/rspec-puppet/matchers.rb - lib/rspec-puppet/setup.rb - lib/rspec-puppet/support.rb - lib/rspec-puppet.rb - bin/rspec-puppet-init homepage: https://github.com/rodjek/rspec-puppet/ licenses: - MIT metadata: {} post_install_message: rdoc_options: [] require_paths: - lib required_ruby_version: !ruby/object:Gem::Requirement requirements: - - '>=' - !ruby/object:Gem::Version version: '0' required_rubygems_version: !ruby/object:Gem::Requirement requirements: - - '>=' - !ruby/object:Gem::Version version: '0' requirements: [] rubyforge_project: rubygems_version: 2.0.3 signing_key: specification_version: 4 summary: RSpec tests for your Puppet manifests test_files: [] rspec-puppet-1.0.1/checksums.yaml.gz0000444000004100000410000000041612257220206017455 0ustar www-datawww-data}Re9r@}~`G<|^4!3 ~Ude^Ǐx>o?=w*T0{&θq|Zr"OM.HO*v[@*6iEYD)WGX]Z1 y2\6FZ(갌lP98١}! +Խ!d˝>{g.:$kL4]Ħ Arq>f*_rspec-puppet-1.0.1/README.md0000644000004100000410000001737412257220206015461 0ustar www-datawww-data# RSpec tests for your Puppet manifests & modules ## Installation gem install rspec-puppet ## Naming conventions For clarity and consistency, I recommend that you use the following directory structure and naming convention. module | +-- manifests | +-- lib | +-- spec | +-- spec_helper.rb | +-- classes | | | +-- _spec.rb | +-- defines | | | +-- _spec.rb | +-- functions | | | +-- _spec.rb | +-- hosts | +-- _spec.rb ## Example groups If you use the above directory structure, your examples will automatically be placed in the correct groups and have access to the custom matchers. *If you choose not to*, you can force the examples into the required groups as follows. ```ruby describe 'myclass', :type => :class do ... end describe 'mydefine', :type => :define do ... end describe 'myfunction', :type => :puppet_function do ... end describe 'myhost.example.com', :type => :host do ... end ``` ## Defined Types & Classes ### Matchers #### Checking if a resource exists You can test if a resource exists in the catalogue with the generic `contain_` matcher. ```ruby it { should contain_augeas('bleh') } ``` You can also test if a class has been included in the catalogue with the same matcher. ```ruby it { should contain_class('foo') } ``` If your resource type includes :: (e.g. `foo::bar` simply replace the :: with __ (two underscores). ```ruby it { should contain_foo__bar('baz') } ``` You can further test the parameters that have been passed to the resources with the generic `with_` chains. ```ruby it { should contain_package('mysql-server').with_ensure('present') } ``` If you want to specify that the given parameters should be the only ones passed to the resource, use the `only_with_` chains. ```ruby it { should contain_package('httpd').only_with_ensure('latest') } ``` You can use the `with` method to verify the value of multiple parameters. ```ruby it do should contain_service('keystone').with( 'ensure' => 'running', 'enable' => 'true', 'hasstatus' => 'true', 'hasrestart' => 'true' ) end ``` The same holds for the `only_with` method, which in addition verifies the exact set of parameters and values for the resource in the catalogue. ```ruby it do should contain_user('luke').only_with( 'ensure' => 'present', 'uid' => '501' ) end ``` You can also test that specific parameters have been left undefined with the generic `without_` chains. ```ruby it { should contain_file('/foo/bar').without_mode } ``` You can use the without method to verify that a list of parameters have not been defined ```ruby it { should contain_service('keystone').without( ['restart', 'status'] )} ``` #### Checking the number of resources You can test the number of resources in the catalogue with the `have_resource_count` matcher. ```ruby it { should have_resource_count(2) } ``` The number of classes in the catalogue can be checked with the `have_class_count` matcher. ```ruby it { should have_class_count(2) } ``` You can also test the number of a specific resource type, by using the generic `have__resource_count` matcher. ```ruby it { should have_exec_resource_count(1) } ``` This last matcher also works for defined types. If the resource type contains ::, you can replace it with __ (two underscores). ```ruby it { should have_logrotate__rule_resource_count(3) } ``` *NOTE*: when testing a class, the catalogue generated will always contain at least one class, the class under test. The same holds for defined types, the catalogue generated when testing a defined type will have at least one resource (the defined type itself). ### Writing tests #### Basic test structure To test that sysctl { 'baz' value => 'foo', } Will cause the following resource to be in included in catalogue for a host exec { 'sysctl/reload': command => '/sbin/sysctl -p /etc/sysctl.conf', } We can write the following testcase (in `spec/defines/sysctl_spec.rb`) ```ruby describe 'sysctl' do let(:title) { 'baz' } let(:params) { { :value => 'foo' } } it { should contain_exec('sysctl/reload').with_command("/sbin/sysctl -p /etc/sysctl.conf") } end ``` #### Specifying the title of a resource ```ruby let(:title) { 'foo' } ``` #### Specifying the parameters to pass to a resources or parametised class ```ruby let(:params) { {:ensure => 'present', ...} } ``` #### Specifying the FQDN of the test node If the manifest you're testing expects to run on host with a particular name, you can specify this as follows ```ruby let(:node) { 'testhost.example.com' } ``` #### Specifying the facts that should be available to your manifest By default, the test environment contains no facts for your manifest to use. You can set them with a hash ```ruby let(:facts) { {:operatingsystem => 'Debian', :kernel => 'Linux', ...} } ``` You can also create a set of default facts provided to all specs in your spec_helper: ``` ruby RSpec.configure do |c| c.default_facts = { :operatingsystem => 'Ubuntu' } end ``` Any facts you provide with `let(:facts)` in a spec will automatically be merged on top of the default facts. #### Specifying the path to find your modules I recommend setting a default module path by adding the following code to your `spec_helper.rb` ```ruby RSpec.configure do |c| c.module_path = '/path/to/your/module/dir' end ``` However, if you want to specify it in each example, you can do so ```ruby let(:module_path) { '/path/to/your/module/dir' } ``` ## Functions ### Matchers All of the standard RSpec matchers are available for you to use when testing Puppet functions. ```ruby it 'should be able to do something' do subject.call(['foo']) == 'bar' end ``` For your convenience though, a `run` matcher exists to provide easier to understand test cases. ```ruby it { should run.with_params('foo').and_return('bar') } ``` ### Writing tests #### Basic test structure ```ruby require 'spec_helper' describe '' do ... end ``` #### Specifying the name of the function to test The name of the function must be provided in the top level description, e.g. ```ruby describe 'split' do ``` #### Specifying the arguments to pass to the function You can specify the arguments to pass to your function during the test(s) using either the `with_params` chain method in the `run` matcher ```ruby it { should run.with_params('foo', 'bar', ['baz']) } ``` Or by using the `call` method on the subject directly ```ruby it 'something' do subject.call(['foo', 'bar', ['baz']]) end ``` #### Testing the results of the function You can test the result of a function (if it produces one) using either the `and_returns` chain method in the `run` matcher ```ruby it { should run.with_params('foo').and_return('bar') } ``` Or by using any of the existing RSpec matchers on the subject directly ```ruby it 'something' do subject.call(['foo']) == 'bar' subject.call(['baz']).should be_an Array end ``` #### Testing the errors thrown by the function You can test whether the function throws an exception using either the `and_raises_error` chain method in the `run` matcher ```ruby it { should run.with_params('a', 'b').and_raise_error(Puppet::ParseError) } it { should_not run.with_params('a').and_raise_error(Puppet::ParseError) } ``` Or by using the existing `raises_error` RSpec matcher ```ruby it 'something' do expect { subject.call(['a', 'b']) }.should raise_error(Puppet::ParseError) expect { subject.call(['a']) }.should_not raise_error(Puppet::ParseError) end ```