From f64132ae1b86e8585a006cbf2f8d4d518e63865a Mon Sep 17 00:00:00 2001 From: Ken Barber Date: Sat, 17 Sep 2011 23:40:53 +0100 Subject: [PATCH] (#9576) Align spec framework with Puppet core. The point of this change is to align the spec testing behaviour with Puppet core so we can get similar behaviour and less problems if we ever want to move this type into core. --- spec/monkey_patches/alias_should_to_must.rb | 8 ++ spec/monkey_patches/publicize_methods.rb | 11 +++ spec/puppet_spec/files.rb | 53 +++++++++++++ spec/puppet_spec/fixtures.rb | 28 +++++++ spec/puppet_spec/matchers.rb | 87 +++++++++++++++++++++ spec/puppet_spec/verbose.rb | 9 +++ spec/spec_helper.rb | 79 ++++++++++++++++--- 7 files changed, 266 insertions(+), 9 deletions(-) create mode 100755 spec/monkey_patches/alias_should_to_must.rb create mode 100755 spec/monkey_patches/publicize_methods.rb create mode 100755 spec/puppet_spec/files.rb create mode 100755 spec/puppet_spec/fixtures.rb create mode 100644 spec/puppet_spec/matchers.rb create mode 100755 spec/puppet_spec/verbose.rb diff --git a/spec/monkey_patches/alias_should_to_must.rb b/spec/monkey_patches/alias_should_to_must.rb new file mode 100755 index 0000000..1a11117 --- /dev/null +++ b/spec/monkey_patches/alias_should_to_must.rb @@ -0,0 +1,8 @@ +require 'rspec' + +class Object + # This is necessary because the RAL has a 'should' + # method. + alias :must :should + alias :must_not :should_not +end diff --git a/spec/monkey_patches/publicize_methods.rb b/spec/monkey_patches/publicize_methods.rb new file mode 100755 index 0000000..b39e9c0 --- /dev/null +++ b/spec/monkey_patches/publicize_methods.rb @@ -0,0 +1,11 @@ +# Some monkey-patching to allow us to test private methods. +class Class + def publicize_methods(*methods) + saved_private_instance_methods = methods.empty? ? self.private_instance_methods : methods + + self.class_eval { public(*saved_private_instance_methods) } + yield + self.class_eval { private(*saved_private_instance_methods) } + end +end + diff --git a/spec/puppet_spec/files.rb b/spec/puppet_spec/files.rb new file mode 100755 index 0000000..30fb4fc --- /dev/null +++ b/spec/puppet_spec/files.rb @@ -0,0 +1,53 @@ +require 'fileutils' +require 'tempfile' + +# A support module for testing files. +module PuppetSpec::Files + # This code exists only to support tests that run as root, pretty much. + # Once they have finally been eliminated this can all go... --daniel 2011-04-08 + if Puppet.features.posix? then + def self.in_tmp(path) + path =~ /^\/tmp/ or path =~ /^\/var\/folders/ + end + elsif Puppet.features.microsoft_windows? + def self.in_tmp(path) + tempdir = File.expand_path(File.join(Dir::LOCAL_APPDATA, "Temp")) + path =~ /^#{tempdir}/ + end + else + fail "Help! Can't find in_tmp for this platform" + end + + def self.cleanup + $global_tempfiles ||= [] + while path = $global_tempfiles.pop do + fail "Not deleting tmpfile #{path} outside regular tmpdir" unless in_tmp(path) + + begin + FileUtils.rm_r path, :secure => true + rescue Errno::ENOENT + # nothing to do + end + end + end + + def tmpfile(name) + # Generate a temporary file, just for the name... + source = Tempfile.new(name) + path = source.path + source.close! + + # ...record it for cleanup, + $global_tempfiles ||= [] + $global_tempfiles << File.expand_path(path) + + # ...and bam. + path + end + + def tmpdir(name) + path = tmpfile(name) + FileUtils.mkdir_p(path) + path + end +end diff --git a/spec/puppet_spec/fixtures.rb b/spec/puppet_spec/fixtures.rb new file mode 100755 index 0000000..7f6bc2a --- /dev/null +++ b/spec/puppet_spec/fixtures.rb @@ -0,0 +1,28 @@ +module PuppetSpec::Fixtures + def fixtures(*rest) + File.join(PuppetSpec::FIXTURE_DIR, *rest) + end + def my_fixture_dir + callers = caller + while line = callers.shift do + next unless found = line.match(%r{/spec/(.*)_spec\.rb:}) + return fixtures(found[1]) + end + fail "sorry, I couldn't work out your path from the caller stack!" + end + def my_fixture(name) + file = File.join(my_fixture_dir, name) + unless File.readable? file then + fail Puppet::DevError, "fixture '#{name}' for #{my_fixture_dir} is not readable" + end + return file + end + def my_fixtures(glob = '*', flags = 0) + files = Dir.glob(File.join(my_fixture_dir, glob), flags) + unless files.length > 0 then + fail Puppet::DevError, "fixture '#{glob}' for #{my_fixture_dir} had no files!" + end + block_given? and files.each do |file| yield file end + files + end +end diff --git a/spec/puppet_spec/matchers.rb b/spec/puppet_spec/matchers.rb new file mode 100644 index 0000000..77f5803 --- /dev/null +++ b/spec/puppet_spec/matchers.rb @@ -0,0 +1,87 @@ +require 'stringio' + +######################################################################## +# Backward compatibility for Jenkins outdated environment. +module RSpec + module Matchers + module BlockAliases + alias_method :to, :should unless method_defined? :to + alias_method :to_not, :should_not unless method_defined? :to_not + alias_method :not_to, :should_not unless method_defined? :not_to + end + end +end + + +######################################################################## +# Custom matchers... +RSpec::Matchers.define :have_matching_element do |expected| + match do |actual| + actual.any? { |item| item =~ expected } + end +end + + +RSpec::Matchers.define :exit_with do |expected| + actual = nil + match do |block| + begin + block.call + rescue SystemExit => e + actual = e.status + end + actual and actual == expected + end + failure_message_for_should do |block| + "expected exit with code #{expected} but " + + (actual.nil? ? " exit was not called" : "we exited with #{actual} instead") + end + failure_message_for_should_not do |block| + "expected that exit would not be called with #{expected}" + end + description do + "expect exit with #{expected}" + end +end + + +RSpec::Matchers.define :have_printed do |expected| + match do |block| + $stderr = $stdout = StringIO.new + + begin + block.call + ensure + $stdout.rewind + @actual = $stdout.read + + $stdout = STDOUT + $stderr = STDERR + end + + if @actual then + case expected + when String + @actual.include? expected + when Regexp + expected.match @actual + else + raise ArgumentError, "No idea how to match a #{@actual.class.name}" + end + end + end + + failure_message_for_should do |actual| + if actual.nil? then + "expected #{expected.inspect}, but nothing was printed" + else + "expected #{expected.inspect} to be printed; got:\n#{actual}" + end + end + + description do + "expect #{expected.inspect} to be printed" + end + + diffable +end diff --git a/spec/puppet_spec/verbose.rb b/spec/puppet_spec/verbose.rb new file mode 100755 index 0000000..d9834f2 --- /dev/null +++ b/spec/puppet_spec/verbose.rb @@ -0,0 +1,9 @@ +# Support code for running stuff with warnings disabled. +module Kernel + def with_verbose_disabled + verbose, $VERBOSE = $VERBOSE, nil + result = yield + $VERBOSE = verbose + return result + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 85bf96d..fdc04bc 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,17 +1,78 @@ -require 'rspec' +dir = File.expand_path(File.dirname(__FILE__)) +$LOAD_PATH.unshift File.join(dir, 'lib') + +p dir + +# Don't want puppet getting the command line arguments for rake or autotest +ARGV.clear + require 'puppet' -$LOAD_PATH.unshift('../../lib', __FILE__) +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 'monkey_patches/alias_should_to_must' +require 'monkey_patches/publicize_methods' + +Pathname.glob("#{dir}/shared_behaviours/**/*.rb") do |behaviour| + require behaviour.relative_path_from(Pathname.new(dir)) +end RSpec.configure do |config| - def setup_provider(type, prov) - @provider = Puppet::Type.type(type).provider(prov) - end + include PuppetSpec::Fixtures - def setup_resource(type, options={}) - @resource = Puppet::Type.type(type).new(options) + config.mock_with :mocha + + config.before :each do + GC.disable + + # these globals are set by Application + $puppet_application_mode = nil + $puppet_application_name = nil + + # 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) + + # Set the confdir and vardir to gibberish so that tests + # have to be correctly mocked. + Puppet[:confdir] = "/dev/null" + Puppet[:vardir] = "/dev/null" + + # Avoid opening ports to the outside world + Puppet.settings[:bindaddress] = "127.0.0.1" + + @logs = [] + Puppet::Util::Log.newdestination(Puppet::Test::LogCollector.new(@logs)) + + @log_level = Puppet::Util::Log.level end - def setup_instance(prov, type) - @instance = prov.new(type) + config.after :each do + Puppet.settings.clear + Puppet::Node::Environment.clear + Puppet::Util::Storage.clear + Puppet::Util::ExecutionStub.reset + + PuppetSpec::Files.cleanup + + @logs.clear + Puppet::Util::Log.close_all + Puppet::Util::Log.level = @log_level + + GC.enable end end -- 2.45.2