Monday, December 23, 2013

Puppet rspec testing and hiera

Rspec testing your puppet modules supports you in having stable and functional modules.
For a couple of weeks there is hiera integration in rspec-puppet.



Basic Spec setup


You will need a couple of files for puppet module rspec testing:

Gemfile
# Install via
# bundle install --path vendor/gems
#
source "https://rubygems.org"

gem "mocha", :require => false
gem 'puppet',       '>= 3.1.1'
gem 'puppet-lint'
gem 'facter',       '>= 1.6.10'
gem 'rspec-puppet', :git => "https://github.com/rodjek/rspec-puppet.git"
gem 'rake',         '>= 0.9.2'
gem 'puppetlabs_spec_helper', '0.3.0'
gem 'test-unit'
Rakefile
require 'rake'
require 'rake/tasklib'
require 'rspec/core/rake_task'
require 'rubygems'
require 'puppetlabs_spec_helper/rake_tasks'
require 'puppet-lint'

desc "Run the tests"
RSpec::Core::RakeTask.new(:test) do |t|
  t.rspec_opts = ['--color', '-f d']
  t.pattern = 'spec/*/*_spec.rb'
end

desc 'Run puppet-lint on the one manifests'
task :onelint do
  PuppetLint.configuration.with_filename = true

  linter = PuppetLint.new
  matched_files = FileList['spec/fixtures/modules/one/manifests/**/*.pp']

  matched_files.to_a.each do |puppet_file|
    linter.file = puppet_file
    linter.run
  end

  fail if linter.errors? || (
    linter.warnings? && PuppetLint.configuration.fail_on_warnings
    )
end

task :default => [:spec_prep, :test, :onelint, :spec_clean]
spec/spec_helper.rb
require 'test/unit'
require 'mocha/setup'
require 'puppetlabs_spec_helper/module_spec_helper'
require 'puppet/indirector/hiera'
require 'hiera'


fixture_path = File.expand_path(File.join(__FILE__, '..', 'fixtures'))

# include common helpers
support_path = File.expand_path(File.join(File.dirname(__FILE__), '..',
                                          'spec/support/*.rb'))
Dir[support_path].each {|f| require f}

RSpec.configure do |c|
  c.config = '/doesnotexist'
  c.manifest_dir = File.join(fixture_path, 'manifests')
  c.mock_with :mocha
end

Fixtures


If your module require other modules (e.g. stdlib functions) you have to include them using fixtures repositories. Add your own module as a symlink:

.fixtures.yaml
fixtures:
    repositories:
        stdlib: "https://github.com/puppetlabs/puppetlabs-stdlib.git"
        concat: "https://github.com/puppetlabs/puppetlabs-concat.git"
    symlinks:
        <your module name>: "#{source_dir}"
 This will create all required directory structures in spec/fixtures/modules.

Hiera Configuration

Add a hiera.yaml and a hiera data file to your spec/fixtures:

spec/fixtures/hiera/hiera.yaml
---
:backends:
  - yaml
:hierarchy:
  - test

:yaml:
    :datadir: 'spec/fixtures/hiera'

spec/fixtures/hiera/test.yaml
---
myvariable: 'myvalue'

Hiera Integration

Only minor changes are required to integrate hiera lookups into your spec tests.
The following example is taken from my stdmodule on github (https://github.com/tuxmea/tuxmea-stdmodule.git)

spec/classes/<classname>_spec.rb
require 'spec_helper'
hiera_file = 'spec/fixtures/hiera/hiera.yaml'   # <- required to find hiera configuration file

# test our main class (init.pp)
describe 'stdmodule', :type => :class  do     # <- set the class name

    # test without hiera lookup (using class defaults)
    context 'without hiera data' do
        it { should contain_class('stdmodule') }                        # <- check for classes included
        it { should contain_class('stdmodule').with_myparam('foo') }    # <- check for classes with parameters
        it { should contain_class('stdmodule::basic') }                 # <- check for subclasses
        it { should contain_class('stdmodule::package_file_service') }
        it { should contain_stdmodule__defines('foobar_define') }       # <- check for defines
    end

    # test with explicit params given on class definition
    context 'with explicit data' do
        let(:params) {{                                                 # <- set explizit params
            :myparam => 'newvalue'
        }}
        it { should contain_class('stdmodule').with_myparam('newvalue') }
    end

    # test with explicit hiera lookup
    context 'with explicit hiera lookup' do
        # set hiera_config
        let (:hiera_config) { hiera_file }

        hiera = Hiera.new(:config => hiera_file)                        # <- use Hiera ruby class
        variable = hiera.lookup('stdmodule::myparam', nil, nil)         # <- do hiera lookup
        let (:params) {{
            :myparam => variable                                        # <- set parameter to hiera lookup
        }}
        it { should contain_class('stdmodule').with_myparam(variable) }
    end
end