Kief
Kief

Reputation: 4493

How to test idempotency with chefspec

I would like to use chefspec to test the idempotency of my recipe.

Let's say I have a recipe that includes these two resource statements:

file '/etc/app.config' do
  action :create
  notifies :restart, "service[app]"
end

service 'app' do
  action :enable
end

How can I write a chefspec example that proves, given the file /etc/app.config already exists, then the app service will not be notified to restart?

Is there some mocking I can do so the 'file' resource thinks the file already exists? Can I run the ChefSpec::ServerRunner twice, keeping the state from the first run (I assume not, since the file won't have really been created)? Or will I be forced to use test-kitchen and Vagrant to make things happen for realz?

(Note: My actual cookbook has a custom LWRP that builds the configuration file. It gets info from a chef-server, which is why I'm using ServerRunner)

Upvotes: 1

Views: 958

Answers (3)

Artem Yarmoliuk
Artem Yarmoliuk

Reputation: 326

test-kitchen since version 1.18.0 has working support for idempotence check for chef_* provisioners (it was introduced earlier but than broken and only fixed in latest release). It's not documented but you can find some details here.

Basically, what you can do is add this two lines to your provisioner definition:

  provisioner:
    name: chef_zero
    ...
    enforce_idempotency: true # force idempotence check
    multiple_converge: 2 # how many times to run your provisioners
    ...

This will run your cookbook multiple times and check that the last run has 0 updated resources. It also gives you a nice list of resources that were updated on a second run.

Upvotes: 1

Kief
Kief

Reputation: 4493

It looks like different starting conditions can only be practically tested by actually converging. If so, the correct answer to my question is to use something like test-kitchen and Vagrant, rather than ChefSpec.

Upvotes: 0

Tensibai
Tensibai

Reputation: 15784

Warning This is untested.

I assume you can mock ::File.exist? to return true in your spec for this file with.

allow_any_instance_of(File).to receive('exists?').with('/etc/app.config').and_return(true)

According to the file provider this is the method called in the provider to get the current state.

This would work cleanly for a file resource like the one you described in your question.

However if the action is :create and the resource is defined with a checksum attribute or a content attribute you'll have to manage to mock the content of the file or the checksum like this

allow_any_instance_of(Chef::Digester).to receive('generate_checksum').with('/etc/app.config').and_return('The checksum of the file, do a sha256sum on your file to get this')

After that you'll have to follow ChefSpec documentation on notifications here

resource = chef_run.file('/etc/app.config')
expect(resource).to_not notify('service[app]').to(:restart)

As far as I know, Test-Kitchen aimed to test a one-shot run to ensure the cookbooks are correct, multiples chef-run in the same test are not handled and you end up having to decide what you wish to use for multiples run test.

There's a talk about this on the chef-mailing list about infrastructure testing, see the tag wiki for links to the ML (the talk is not a final answer and does not fit on SO).

Upvotes: 0

Related Questions