chefspec: setting global node attributes to be used in all tests in recipe - ruby

I'm currently writing a chefspec recipe which sets certain node attributes, which are necessary to complete my unit tests. I am currently setting these attributes in each test, which seems wasteful. I wish to carry this out in such a way that I am not repeating code, i.e. "global attributes?".
My current working recipe is as follows:
# encoding: UTF-8
require_relative '../spec_helper'
osd_device = '/ceph-1-osd'
describe 'ceph::per-host-osd' do
let(:runner) { ChefSpec::Runner.new }
let(:chef_run) { runner.converge(described_recipe) }
let(:node) { runner.node }
# Test whether directory is created with specifed attributes
# Node attributes are necessary to satisfy logic of parent recipe
it 'creates a directory with root ownership and permissions' do
node.automatic['fqdn'] = 'ceph-1'
node.set['ceph']['hosts']['ceph-1']
node.set['ceph']['hosts']['ceph-1']['osd_devices'] = [{device: "/ceph-1-osd", journal: "/ceph-1-journal/journal", type: "directory"}]
expect(chef_run).to create_directory("#{osd_device}").with(
user: 'root',
group: 'root',
)
end
it 'executes ceph-disk-prepare and ceph-disk-activate' do
node.automatic['fqdn'] = 'ceph-1'
node.set['ceph']['hosts']['ceph-1']
node.set['ceph']['hosts']['ceph-1']['osd_devices'] = [{device: "/ceph-1-osd", journal: "/ceph-1-journal/journal", type: "directory"}]
expect(chef_run).to run_execute("ceph-disk-prepare on #{osd_device}")
expect(chef_run).to run_execute("ceph-disk-activate #{osd_device}")
end
end
This chefspec test passes without issue:
.....
Finished in 4.99 seconds (files took 8.13 seconds to load)
5 examples, 0 failures
Coverage report generated for RSpec to /Users/joe.bloggs/workspace/cookbook_ceph/build/report/coverage/coverage.xml
However, I wish to set the 'node.automatic' and 'node.set' statements only once (outside of the tests) and then to reuse them in the subsequent tests.
My efforts to set these attributes "globally" looks like this:
# encoding: UTF-8
require_relative '../spec_helper'
osd_device = '/ceph-1-osd'
describe 'ceph::per-host-osd' do
let(:chef_run) do
ChefSpec::Runner.new do |node|
node.automatic['fqdn'] = 'ceph-1'
node.set['ceph']['hosts']['ceph-1']
node.set['ceph']['hosts']['ceph-1']['osd_devices'] = [{device: "/ceph-1-osd", journal: "/ceph-1-journal/journal", type: "directory"}]
end
end
# Test whether directory is created with specifed attributes
# Node attributes are necessary to satisfy logic of parent recipe
it 'creates a directory with root ownership and permissions' do
expect(chef_run).to create_directory("#{osd_device}").with(
user: 'root',
group: 'root',
)
end
it 'executes ceph-disk-prepare and ceph-disk-activate' do
expect(chef_run).to run_execute("ceph-disk-prepare on #{osd_device}")
expect(chef_run).to run_execute("ceph-disk-activate #{osd_device}")
end
end
It returns the following error:
...FF
Failures:
1) ceph::per-host-osd creates a directory with root ownership and permissions
Failure/Error: expect(chef_run).to create_directory("#{osd_device}").with(
NoMethodError:
undefined method `resource_collection' for nil:NilClass
# ./spec/unit/per-host-osd_spec.rb:17:in `block (2 levels) in <top (required)>'
2) ceph::per-host-osd executes ceph-disk-prepare and ceph-disk-activate
Failure/Error: expect(chef_run).to run_execute("ceph-disk-prepare on #{osd_device}")
NoMethodError:
undefined method `resource_collection' for nil:NilClass
# ./spec/unit/per-host-osd_spec.rb:23:in `block (2 levels) in <top (required)>'
Finished in 3.12 seconds (files took 8.46 seconds to load)
5 examples, 2 failures
Failed examples:
rspec ./spec/unit/per-host-osd_spec.rb:16 # ceph::per-host-osd creates a directory with root ownership and permissions
rspec ./spec/unit/per-host-osd_spec.rb:22 # ceph::per-host-osd executes ceph-disk-prepare and ceph-disk-activate
Coverage report generated for RSpec to /Users/joe.bloggs/workspace/cookbook_ceph/build/report/coverage/coverage.xml
I'm new to chefspec, so perhaps I'm missing something. Any help is greatly appreciated. Thanks.

In your second form you're never converging any recipe, so obviously there's no resource_collection to test against.
Add a converge(described_recipe) at the end of your runner definition.
let(:chef_run) do
ChefSpec::Runner.new do |node|
node.automatic['fqdn'] = 'ceph-1'
node.set['ceph']['hosts']['ceph-1']
node.set['ceph']['hosts']['ceph-1']['osd_devices'] = [{device: "/ceph-1-osd", journal: "/ceph-1-journal/journal", type: "directory"}]
end.converge(described_recipe)
end

Related

inspec resources not identified

i am using inspec test framework with ruby for infrastructure testing. I have written a test in the controls
Here is my test:
require 'aws-sdk'
credentials = Aws::AssumeRoleCredentials.new(
role_arn: 'some_value',
role_session_name: 'pipeline')
client_params = {
region: 'ap-southeast-2',
credentials: credentials
}
ec2_client = Aws::EC2::Resource.new(client_params)
instance = ec2_client.instances(filters: [{name:'tag:component', values: ['api', 'fxsnet', 'admin']}])
puts "ec2 Client is : #{ec2_client}"
puts "list of instances based on tag is: #{instance}"
instance.each do |i|
puts 'ID: ' + i.id
puts 'State: ' + i.state.name
#for each of the instance check if tmp file exist
describe file('/tmp') do # The actual test
it { should exist }
end
end
but on execution, i get below error
An error occurred while loading ./inspec-infra-tests/controls/apiInstances.rb.
Failure/Error:
describe file('/tmp') do # The actual test
it { should exist }
end
NoMethodError:
undefined method `file' for main:Object
Did you mean? fail
# ./inspec-infra-tests/controls/apiInstances.rb:46:in `block in <top (required)>'
# ./inspec-infra-tests/controls/apiInstances.rb:35:in `<top (required)>'
No examples found.
0 examples, 0 failures, 0 passed
file InSpec audit resource to test all system file types, including files, directories, symbolic links, named pipes, sockets etc..
#InspecWithRuby #inspec #inspecResourcesNotIdentified #InspecResourcesNotFound
It looks to me like you are attempting to run an inspec test by executing with ruby rather then running with inspec exec. I can reproduce locally by pasting your test in a file:
inspec_example.rb
describe file('/tmp') do # The actual test
it { should exist }
end
Executing with ruby directly
ruby inspec_example.rb gives:
Traceback (most recent call last):
inspec_example.rb:1:in `<main>': undefined method `file' for main:Object (NoMethodError)
Did you mean? fail
Executing with inspec exec works as expected:
inspec exec inspec_example.rb gives:
Profile: tests from inspec_example.rb (tests from inspec_example.rb)
Version: (not specified)
Target: local://
File /tmp
✔ should exist
I'm not familiar with the AWS SDK, but it you want to test for a set of files using InSpec, you can do so like this:
myfiles = %w(temp.err temp.out)
control 'tmp-files-1' do
title 'Test for a set of temp files.'
myfiles.each do |myfile|
describe file('/tmp/' + myfile) do
it { should exist }
end
end
end
If you execute this control, it will return the following (assuming that these files actually exist in your /tmp folder:
✔ tmp-files-1: Test for a set of temp files.
✔ File /tmp/temp.err should exist
✔ File /tmp/temp.out should exist
I'm hoping you can take this example and adapt it to your AWS needs.

If I'm testing an rspec extension, how do I suppress the results of tests which fail as part of the test?

I'm trying to write specs for an extension to rspec.
This is the gist of what I'm trying to test:
require 'rspec-let-and-after-extension'
RSpec.describe "let(...).and_after" do
it 'is called if the `let` is invoked even if the example fails' do
call_order = []
RSpec.describe do
let(:foo) { }.and_after { call_order << :and_after }
it { foo; call_order << :example; raise 'failed!' }
end.run
expect(call_order).to eq [:example, :and_after]
end
end
One of the important behaviours is that if running the example fails, the cleanup code still runs. So I test this by recording the order of the calls and raising an exception from the example.
Problem is, when I run it, it sees this block as a second example, which then fails with errors:
.F
Failures:
1)
Got 0 failures and 2 other errors:
1.1) Failure/Error: it { foo; call_order << :example; raise 'failed!' }
RuntimeError:
failed!
# ./spec/spec.rb:43:in `block (4 levels) in <top (required)>'
# ./spec/spec.rb:44:in `block (2 levels) in <top (required)>'
1.2) Failure/Error: it { foo; call_order << :example; raise 'failed!' }
RuntimeError:
failed!
# ./spec/spec.rb:43:in `block (4 levels) in <top (required)>'
Finished in 0.00167 seconds (files took 0.08011 seconds to load)
2 examples, 1 failure
Failed examples:
rspec ./spec/spec.rb:43 #
As you can see, the output did have one dot, so the actual example passed. But then there is an F, because it has seen the internal example, run that, and unsurprisingly that one failed.
How do I make rspec not see this nested example as one of the examples it's supposed to run, so that this example completes with a single dot?
(If you're wondering about what the rspec devs themselves do about their tests, it looks like they use cucumber. Do they use cucumber because they couldn't figure this out either? :))
You can use the new sandboxing API (available in 3.2+).
RSpec.configure do |rspec|
rspec.around do |ex|
RSpec::Core::Sandbox.sandboxed do |config|
# re-configure any configuration defined by your extension here
# before allowing the example to run. The sandbox runs with a fresh
# config instance, which means any configuration you have set in
# `rspec-let-and-after-extension` will not apply while the example
# is running.
# config.extend MyExtensionModule
ex.run
end
end
end

Chefspec delete file with windowslike path

Im trying to write test for file deletion:
test/recipes/default.rb
file 'C:/temp/1.txt' do
action :delete
end
cat spec/default_spec.rb
require_relative 'spec_helper'
describe 'test::default' do
#before do
# allow(File).to receive(:exist?).and_call_original
# allow(File).to receive(:exist?).with('C:/test/1.txt').and_return(true)
#end
let(:chef_run) do
ChefSpec::ServerRunner.new(platform: 'windows', version: '2008R2') do |runner|
runner.automatic_attrs['hostname'] = 'somehost'
end.converge(described_recipe)
end
it 'delete scripts' do
expect(chef_run).to delete_file("C:/test/1.txt")
end
end
But after invoke rspec command I got:
F
Failures:
1) test::default delete scripts
Failure/Error: expect(chef_run).to delete_file("C\:/test/1.txt")
expected "file[C:/test/1.txt]" with action :delete to be in Chef run. Other file resources:
file[C:/temp/1.txt]
# ./spec/default_spec.rb:29:in `block (2 levels) in <top (required)>'
Finished in 0.64011 seconds (files took 1.27 seconds to load)
1 example, 1 failure
If I set linuxlike path - everything ok, also as you can see I tries to stub file.
Any ideas what Im doing wrong?
P.S.: I'm running tests on linux, ruby 2.1, chefspec 4.1
It seems that you made mistake, in your recipe you have C:/temp/1.txt and in your test you check if file C:/test/1.txt is deleted.

rspec-puppet tests fail undefined method

I'm trying to write my first rspec test for a simple puppet class. Here's the class, rspec test and results. I'm new to rspec and would like to know what I'm doing wrong here. I follow the directions here http://rspec-puppet.com/setup/ to configure rspec-puppet for these tests. Thanks.
Class example for cron module init.pp
class cron {
service { 'crond' :
ensure => running,
enable => true
}
}
Rspec Test
require '/etc/puppetlabs/puppet/modules/cron/spec/spec_helper'
describe 'cron', :type => :module do
it { should contain_class('cron') }
it do should contain_service('crond').with(
'ensure' => 'running',
'enable' => 'true'
) end
end
Results
FF
Failures:
1) cron
Failure/Error: it { should contain_class('cron') }
NoMethodError:
undefined method `contain_class' for #<RSpec::Core::ExampleGroup::Nested_1:0x00000001c66d70>
# ./cron_spec.rb:5:in `block (2 levels) in <top (required)>'
2) cron
Failure/Error: it do should contain_service('crond').with(
NoMethodError:
undefined method `contain_service' for #<RSpec::Core::ExampleGroup::Nested_1:0x00000001c867b0>
# ./cron_spec.rb:6:in `block (2 levels) in <top (required)>'
Finished in 0.00237 seconds
2 examples, 2 failures
Failed examples:
rspec ./cron_spec.rb:5 # cron
rspec ./cron_spec.rb:6 # cron
Where did you pick up the
describe 'cron', :type => :module
syntax? That may be obsolete.
With current versions of rspec-puppet, you describe
classes
defined types
functions
hosts
You basically just want to put your spec right into spec/classes/cron_spec.rb, that should do half your work for you, e.g.
# spec/classes/cron.rb
require "#{File.join(File.dirname(__FILE__),'..','spec_helper.rb')}"
describe 'cron' do
it { should contain_service('crond').with('ensure' => 'running') }
it { should contain_service('crond').with('enable' => 'true') }
end
It is good practice to have distinct tests for each attribute value, so that possible future regressions can be identified more accurately.
Do see the README.
For a nice example of a well structured module test-suite see example42's modules.

Pure Ruby rspec test passes without method being defined

I have an rspec test on a pure Ruby model:
require 'spec_helper'
require 'organization'
describe Organization do
context '#is_root?' do
it "creates a root organization" do
org = Organization.new
expect { org.is_root?.to eq true }
end
end
end
My organization model looks like this:
class Organization
attr_accessor :parent
def initialize(parent = nil)
self.parent = parent
end
end
The output when running the tests:
bundle exec rspec spec/organization_spec.rb:6
Run options: include {:locations=>{"./spec/organization_spec.rb"=>[6]}}
.
Finished in 0.00051 seconds
1 example, 0 failures
When I run the test, it passes, despite the fact that the method is_root? doesn't exist on the model. I usually work in Rails, not pure Ruby, and I've never seen this happen. What is going on?
Thanks!
It should be:
expect(org.is_root?).to eq true
When you pass block to expect it is being wrapped in ExpectationTarget class (strictly speaking BlockExpectationTarget < ExpectationTarget). Since you didn't specify what you expect from this object, the block is never executed, hence no error is raised.
You are passing a block to expect, which is never being called. You can see this by setting an expectation on that block
expect { org.is_root?.to eq true }.to_not raise_error
1) Organization#is_root? creates a root organization
Failure/Error: expect { puts "HI";org.is_root?.to eq true }.to_not raise_error
expected no Exception, got #<NoMethodError: undefined method `is_root?' for #<Organization:0x007ffa798c2ed8 #parent=nil>> with backtrace:
# ./test_spec.rb:15:in `block (4 levels) in <top (required)>'
# ./test_spec.rb:15:in `block (3 levels) in <top (required)>'
# ./test_spec.rb:15:in `block (3 levels) in <top (required)>'
Or by just putting a plain raise or puts inside the block, neither of which will be called:
expect { puts "HI"; raise; org.is_root?.to eq true }
The block form is used for expecting that a piece of code raises an exception or not. The correct syntax for checking values is:
expect(org.is_root?).to eq(true)

Resources