diff --git a/README.md b/README.md index b5df55d..46db526 100644 --- a/README.md +++ b/README.md @@ -20,3 +20,79 @@ In your `spec/spec_helper.rb` ```ruby require 'voxpupuli/test/spec_helper' ``` + +# Fact handling + +The recommended method is using [rspec-puppet-facts](https://github.com/mcanevet/rspec-puppet-facts) and is set up by default. This means the tests are writting as follows: + +```ruby +require 'spec_helper' + +describe 'myclass' do + on_supported_os.each do |os, os_facts| + context "on #{os}" do + let(:facts) { os_facts } + + it { is_expected.to compile.with_all_deps } + end + end +end +``` + +Now a common case is to override facts in tests. Let's take the example of SELinux with legacy facts. + +```ruby +require 'spec_helper' + +describe 'mytool' do + on_supported_os.each do |os, os_facts| + context "on #{os}" do + let(:facts) { os_facts } + + it { is_expected.to compile.with_all_deps } + + describe 'with SELinux enabled' do + let(:facts) { super().merge(selinux: true) } + + it { is_expected.to contain_package('mytool-selinux') } + end + + describe 'with SELinux disabled' do + let(:facts) { super().merge(selinux: false) } + + it { is_expected.not_to contain_package('mytool-selinux') } + end + end + end +end +``` + +This is all fairly straight forward, but it gets more complex when using modern facts. Modern facts are nested which means you need to do deep merging. There is [deep_merge](https://rubygems.org/gems/deep_merge) but its results are not at all useful for spec testing. That's why voxpupuli-test has an `override_facts` helper. + +```ruby +require 'spec_helper' + +describe 'mytool' do + on_supported_os.each do |os, os_facts| + context "on #{os}" do + let(:facts) { os_facts } + + it { is_expected.to compile.with_all_deps } + + describe 'with SELinux enabled' do + let(:facts) { override_facts(super(), os: {selinux: {enabled: true}}) } + + it { is_expected.to contain_package('mytool-selinux') } + end + + describe 'with SELinux disabled' do + let(:facts) { override_facts(super(), os: {selinux: {enabled: false}}) } + + it { is_expected.not_to contain_package('mytool-selinux') } + end + end + end +end +``` + +Note that this helper deals with symbols/strings for you as well. diff --git a/lib/voxpupuli/test/facts.rb b/lib/voxpupuli/test/facts.rb new file mode 100644 index 0000000..23a2551 --- /dev/null +++ b/lib/voxpupuli/test/facts.rb @@ -0,0 +1,34 @@ +# Override facts +# +# This doesn't use deep_merge because that's highly unpredictable. It can merge +# nested hashes in place, modifying the original. It's also unable to override +# true to false. +# +# A deep copy is obtained by using Marshal so it can be modified in place. Then +# it recursively overrides values. If the result is a hash, it's recursed into. +# +# A typical example: +# +# let(:facts) do +# override_facts(super(), os: {'selinux' => {'enabled' => false}}) +# end +def override_facts(base_facts, **overrides) + facts = Marshal.load(Marshal.dump(base_facts)) + apply_overrides!(facts, overrides, false) + facts +end + +# A private helper to override_facts +def apply_overrides!(facts, overrides, enforce_strings) + overrides.each do |key, value| + # Nested facts are strings + key = key.to_s if enforce_strings + + if value.is_a?(Hash) + facts[key] = {} unless facts.key?(key) + apply_overrides!(facts[key], value, true) + else + facts[key] = value + end + end +end diff --git a/lib/voxpupuli/test/spec_helper.rb b/lib/voxpupuli/test/spec_helper.rb index ccc3518..9ddcc89 100644 --- a/lib/voxpupuli/test/spec_helper.rb +++ b/lib/voxpupuli/test/spec_helper.rb @@ -24,6 +24,7 @@ def suggest_facter_version end end +require 'voxpupuli/test/facts' require 'puppetlabs_spec_helper/module_spec_helper' require 'rspec-puppet-facts' include RspecPuppetFacts diff --git a/spec/facts_spec.rb b/spec/facts_spec.rb new file mode 100644 index 0000000..a418583 --- /dev/null +++ b/spec/facts_spec.rb @@ -0,0 +1,93 @@ +require 'spec_helper' +require 'voxpupuli/test/facts' + +describe 'override_facts' do + let(:base_facts) do + { + os: { + 'family' => 'RedHat', + 'name' => 'CentOS', + 'release' => { + 'full' => '7.7.1908', + 'major' => '7', + 'minor' => '7' + }, + } + } + end + + describe 'no overrides' do + let(:expected) do + { + os: { + 'family' => 'RedHat', + 'name' => 'CentOS', + 'release' => { + 'full' => '7.7.1908', + 'major' => '7', + 'minor' => '7' + }, + } + } + end + + it { expect(override_facts(base_facts)).to eq(expected) } + end + + describe 'with addition at the top level' do + let(:expected) do + { + os: { + 'family' => 'RedHat', + 'name' => 'CentOS', + 'release' => { + 'full' => '7.7.1908', + 'major' => '7', + 'minor' => '7' + }, + }, + ruby: { + 'sitedir' => '/usr/local/share/ruby/site_ruby', + } + } + end + + it { expect(override_facts(base_facts, ruby: {sitedir: '/usr/local/share/ruby/site_ruby'})).to eq(expected) } + end + + describe 'with deep merging' do + let(:expected) do + { + os: { + 'family' => 'RedHat', + 'name' => 'CentOS', + 'release' => { + 'full' => '7.7.1908', + 'major' => '7', + 'minor' => '8' + }, + } + } + end + + it { expect(override_facts(base_facts, os: {release: {minor: '8'}})).to eq(expected) } + end + + describe 'with strings' do + let(:expected) do + { + os: { + 'family' => 'RedHat', + 'name' => 'CentOS', + 'release' => { + 'full' => '7.7.1908', + 'major' => '7', + 'minor' => '8' + }, + } + } + end + + it { expect(override_facts(base_facts, os: {'release' => {minor: '8'}})).to eq(expected) } + end +end