Some Puppet modules like puppet-module-keycloak have hundreds of unit tests. That is good for test coverage, but waiting for test results hurts your productivity when you're developing tests for your new code. There are at least two ways to (temporarily) limit the scope of the tests that you run. First method is baked in into the Puppet Development Kit command-line:
$ pdk test unit --tests=spec/classes/init_spec.rb
This runs only the tests in the specified file.
Puppet rspec tests are often run on all supported platforms defined in metadata.json. That can be easily done with help from rspec-puppet-facts:
on_supported_os.each do |os, facts|
context "on #{os}" do
--- snip ---
end
end
This pattern is convenient, but increases test count greatly. Fortunately you can modify the spec file to only run tests on a single platform:
bionic = { supported_os: [{ 'operatingsystem' => 'Ubuntu', 'operatingsystemrelease' => ['18.04'] }] }
on_supported_os(bionic).each do |os, facts|
context "on #{os}" do
--- snip ---
end
end
This allows you to target just one platform while developing your rspec tests and speed up your test runs considerably. There is a caveat with the above approach, though: it does not work as you'd expect if you try have different checks for different operating systems:
bionic = { supported_os: [{ 'operatingsystem' => 'Ubuntu', 'operatingsystemrelease' => ['18.04'] }] }
focal = { supported_os: [{ 'operatingsystem' => 'Ubuntu', 'operatingsystemrelease' => ['20.04'] }] }
on_supported_os(bionic).each do |os, facts|
context "bionic test on #{os}" do
--- snip ---
it { is_expected.to contain_file('/tmp/foo') }
end
end
on_supported_os(focal).each do |os, facts|
context "focal test on #{os}" do
--- snip ---
it { is_expected.to contain_file('/tmp/bar') }
end
end
While it seems to do the trick, at some point you end up with tests that runon wrong operating systems and may get test failures. And example using the above code:
$ pdk test unit -v
--- snip ---
bionic test on ubuntu-20.04-x86_64
is expected to contain File[/tmp/foo]
bionic test on ubuntu-18.04-x86_64
is expected to contain File[/tmp/foo]
focal test on ubuntu-20.04-x86_64
is expected to contain File[/tmp/bar]
focal test on ubuntu-18.04-x86_64
is expected to contain File[/tmp/bar]
If you set /tmp/foo and /tmp/bar to be present only on bionic and focal, respectively, a subset of the tests will fail. So, avoid the above pattern if the desired state varies a lot between different operating systems.
A better approach to handling test variance between operating systems is to always loop through all the supported operating systems, but only run tests for the applicable system. Here's an example from puppet-resolver:
# frozen_string_literal: true
require 'spec_helper'
describe 'resolver' do
default_params = { 'servers' => ['8.8.8.8', '8.8.4.4'],
'domains' => ['example.org', 'example.com'] }
on_supported_os.each do |os, os_facts|
context "compiles on #{os}" do
extra_facts = {}
extra_facts = { os: { distro: { codename: 'RedHat' } } } if os_facts[:osfamily] == 'RedHat'
let(:facts) { os_facts.merge(extra_facts) }
let(:params) { default_params }
it { is_expected.to compile }
end
end
on_supported_os.each do |os, os_facts|
context "ubuntu default resolver settings on #{os}" do
# extra_facts = {}
# extra_facts = { os: { distro: { codename: 'RedHat' } } } if os_facts[:osfamily] == 'RedHat'
# let(:facts) { os_facts.merge(extra_facts) }
let(:facts) { os_facts }
let(:params) { default_params }
if os_facts[:operatingsystem] == 'Ubuntu' && ['16.04'].include?(os_facts[:operatingsystemrelease])
it { is_expected.to contain_class('resolver::dhclient') }
it { is_expected.to contain_file('/etc/dhcp/dhclient.conf') }
it { is_expected.to contain_file_line('resolver_config').with('require' => 'File[/etc/dhcp/dhclient.conf]') }
it {
is_expected.to contain_exec('restart networking service').with('require' => 'File_line[resolver_config]',
'subscribe' => 'File_line[resolver_config]',
'command' => '/bin/systemctl restart networking',
'refreshonly' => true)
}
end
end
context "ubuntu default resolver settings on #{os}" do
# extra_facts = {}
# extra_facts = { os: { distro: { codename: 'RedHat' } } } if os_facts[:osfamily] == 'RedHat'
# let(:facts) { os_facts.merge(extra_facts) }
let(:facts) { os_facts }
let(:params) { default_params }
if os_facts[:operatingsystem] == 'Ubuntu' && ['18.04', '20.04', '22.04'].include?(os_facts[:operatingsystemrelease])
it { is_expected.to contain_class('resolver::systemd_resolved') }
it { is_expected.to contain_file('/etc/systemd/resolved.conf.d') }
it {
is_expected.to contain_file('/etc/systemd/resolved.conf.d/50_puppet_resolver.conf').with('notify' => 'Exec[restart-systemd-resolved]',
'require' => 'File[/etc/systemd/resolved.conf.d]')
}
it {
is_expected.to contain_exec('restart-systemd-resolved').with('command' => 'systemctl restart systemd-resolved',
'refreshonly' => true)
}
end
end
context "redhat default resolver settings on #{os}" do
extra_facts = {}
extra_facts = { os: { distro: { codename: 'RedHat' } } } if os_facts[:osfamily] == 'RedHat'
let(:facts) { os_facts.merge(extra_facts) }
let(:params) { default_params }
if os_facts[:operatingsystem] =~ %r{RedHat|CentOS|Rocky} && ['7', '8'].include?(os_facts[:operatingsystemmajrelease])
it { is_expected.to contain_class('resolver::dhclient') }
it {
is_expected.to contain_file('/etc/NetworkManager/conf.d/dns-dhclient.conf').with('ensure' => 'file',
'notify' => 'Exec[restart networking service]')
}
it {
is_expected.to contain_exec('restart networking service').with('require' => 'File_line[resolver_config]',
'subscribe' => 'File_line[resolver_config]',
'command' => '/bin/systemctl restart NetworkManager',
'refreshonly' => true)
}
end
end
end
end
It seems that "pdk test unit" does directly not support running a subset of tests using rspec tags, which is a shame.