A few days ago, I blogged about using CSH.each_hardware to ensure you have code coverage for the various hardware platforms you support. I use the same methodology for testing all the OS/version combinations we support. In the CSH (ChefSpecHelper) gem the following code resides:

module CSH
  PLATFORMS = {
    supported: [
      %w[centos 5.8],
      %w[centos 6.3],
      %w[centos 6.4],
      %w[centos 6.5]
    ],
    unsupported: [
      %w[ubuntu 12.04]
    ]
  }

  def self.each_platform(*kinds, &block)
    array = []
    kinds = [:supported] if kinds.empty?
    kinds.each do |k|
      fail "Unknown kind #{k.inspect}" unless PLATFORMS.include?(k)
      array.concat PLATFORMS[k]
    end
    array.map do |platform, version|
      yield platform, version
    end
  end
end

Say you have a dummy::grub recipe that is platform specific:

package 'grubby' do
  package_name 'mkinitrd' if node['platform_version'].to_i < 6
end

The corresponding spec test would look like:

describe 'dummy::grub' do
  CSH.each_platform do |platform, version|
    context "on #{platform}-#{version}" do
      let :runner do
        ChefSpec::Runner.new(platform: platform, version: version).converge(described_recipe)
      end

      it 'converges' do
        expect(runner).to install_package('grubby')
        if version.to_i < 6
          expect(runner).to install_package('grubby').with(package_name: 'mkinitrd')
        end
      end
    end
  end
end

The great thing about controlling this centrally is that you don’t have to modify spec tests across your complete cookbook set. In the next weeks when we power off the remaining CentOS-6.3 machines, I’ll just remove the entry and we’ll stop executing those tests.

You can combine this methodology with CSH.each_hardware and ensure that all supported nodes are covered.

describe 'raid::utils' do
  CSH.each_platform do |platform, version|
    CSH.each_hardware do |machine, json|
      context "#{platform}-#{version} on #{machine}" do
        let :runner do
          ChefSpec::Runner.new(platform: platform, version: version) do |node|
            node.automatic.merge! json['automatic']
          end.converge(described_recipe)
        end

        it 'converges' do
          if machine =~ /^PowerEdge/ && version.to_i == 5
            expect(runner).to install_package('raidcfg')
          else
            expect(runner).not_to install_package('raidcfg')
          end

          if machine =~ /^PowerEdge/ && version.to_i == 6
            expect(runner).to install_package('MegaCli')
            expect(runner).to install_package('MegaLogR')
          else
            expect(runner).not_to install_package('MegaCli')
            expect(runner).not_to install_package('MegaLogR')
          end

          if machine =~ /^ProLiant/
            expect(runner).to install_package('hpacucli')
          else
            expect(runner).not_to install_package('hpacucli')
          end
        end
      end
    end
  end
end

Sure you could collapse these tests down into just the bare minimum needed to have coverage. But why? Since chefspec is so fast, you might as well take a more black box approach to these tests. In the future there may be more or different permutations to test. It is better to have the scaffolding in place instead of having to retrofit it later.