rspec-chef 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +8 -0
- data/Gemfile.lock +76 -0
- data/LICENSE +22 -0
- data/README.md +128 -0
- data/Rakefile +129 -0
- data/lib/rspec-chef.rb +17 -0
- data/lib/rspec-chef/chef_support.rb +18 -0
- data/lib/rspec-chef/examples.rb +11 -0
- data/lib/rspec-chef/examples/define_recipe_group.rb +23 -0
- data/lib/rspec-chef/json_support.rb +17 -0
- data/lib/rspec-chef/matchers.rb +1 -0
- data/lib/rspec-chef/matchers/contain_resource.rb +77 -0
- data/rspec-chef.gemspec +73 -0
- data/spec/fixtures/cookbooks/foo/recipes/default.rb +1 -0
- data/spec/fixtures/cookbooks/foo/recipes/install.rb +5 -0
- data/spec/rspec-chef/chef_support_spec.rb +17 -0
- data/spec/rspec-chef/examples/define_recipe_group_spec.rb +14 -0
- data/spec/rspec-chef/json_support_spec.rb +39 -0
- data/spec/rspec-chef/matchers/contain_resource_spec.rb +104 -0
- data/spec/spec_helper.rb +8 -0
- metadata +100 -0
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
rspec-chef (0.1.0)
|
5
|
+
chef
|
6
|
+
json
|
7
|
+
rspec
|
8
|
+
|
9
|
+
GEM
|
10
|
+
remote: http://rubygems.org/
|
11
|
+
specs:
|
12
|
+
bunny (0.7.8)
|
13
|
+
chef (0.10.8)
|
14
|
+
bunny (>= 0.6.0)
|
15
|
+
erubis
|
16
|
+
highline
|
17
|
+
json (>= 1.4.4, <= 1.6.1)
|
18
|
+
mixlib-authentication (>= 1.1.0)
|
19
|
+
mixlib-cli (>= 1.1.0)
|
20
|
+
mixlib-config (>= 1.1.2)
|
21
|
+
mixlib-log (>= 1.3.0)
|
22
|
+
moneta
|
23
|
+
net-ssh (~> 2.1.3)
|
24
|
+
net-ssh-multi (~> 1.1.0)
|
25
|
+
ohai (>= 0.6.0)
|
26
|
+
rest-client (>= 1.0.4, < 1.7.0)
|
27
|
+
treetop (~> 1.4.9)
|
28
|
+
uuidtools
|
29
|
+
diff-lcs (1.1.3)
|
30
|
+
erubis (2.7.0)
|
31
|
+
highline (1.6.9)
|
32
|
+
json (1.6.1)
|
33
|
+
mime-types (1.17.2)
|
34
|
+
mixlib-authentication (1.1.4)
|
35
|
+
mixlib-log
|
36
|
+
mixlib-cli (1.2.2)
|
37
|
+
mixlib-config (1.1.2)
|
38
|
+
mixlib-log (1.3.0)
|
39
|
+
moneta (0.6.0)
|
40
|
+
net-ssh (2.1.4)
|
41
|
+
net-ssh-gateway (1.1.0)
|
42
|
+
net-ssh (>= 1.99.1)
|
43
|
+
net-ssh-multi (1.1)
|
44
|
+
net-ssh (>= 2.1.4)
|
45
|
+
net-ssh-gateway (>= 0.99.0)
|
46
|
+
ohai (0.6.10)
|
47
|
+
mixlib-cli
|
48
|
+
mixlib-config
|
49
|
+
mixlib-log
|
50
|
+
systemu (~> 2.2.0)
|
51
|
+
yajl-ruby
|
52
|
+
polyglot (0.3.3)
|
53
|
+
rake (0.9.2.2)
|
54
|
+
rest-client (1.6.7)
|
55
|
+
mime-types (>= 1.16)
|
56
|
+
rspec (2.7.0)
|
57
|
+
rspec-core (~> 2.7.0)
|
58
|
+
rspec-expectations (~> 2.7.0)
|
59
|
+
rspec-mocks (~> 2.7.0)
|
60
|
+
rspec-core (2.7.1)
|
61
|
+
rspec-expectations (2.7.0)
|
62
|
+
diff-lcs (~> 1.1.2)
|
63
|
+
rspec-mocks (2.7.0)
|
64
|
+
systemu (2.2.0)
|
65
|
+
treetop (1.4.10)
|
66
|
+
polyglot
|
67
|
+
polyglot (>= 0.3.1)
|
68
|
+
uuidtools (2.1.2)
|
69
|
+
yajl-ruby (1.1.0)
|
70
|
+
|
71
|
+
PLATFORMS
|
72
|
+
ruby
|
73
|
+
|
74
|
+
DEPENDENCIES
|
75
|
+
rake (~> 0.9.2)
|
76
|
+
rspec-chef!
|
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
== rspec-chef
|
2
|
+
|
3
|
+
Copyright (c) 2011 David Calavera
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
# RSpec matchers and examples for your Chef recipes
|
2
|
+
|
3
|
+
Test your Chef cookbooks nicely.
|
4
|
+
|
5
|
+
This project is heavily inspired by RSpec-puppet.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
```
|
10
|
+
gem install rspec-chef
|
11
|
+
```
|
12
|
+
|
13
|
+
## Example groups
|
14
|
+
|
15
|
+
### Recipes
|
16
|
+
|
17
|
+
Follow this structure to organize your recipes:
|
18
|
+
|
19
|
+
```
|
20
|
+
library
|
21
|
+
|
|
22
|
+
+-- cookbooks
|
23
|
+
|
|
24
|
+
+-- specs
|
25
|
+
|
|
26
|
+
+-- recipes
|
27
|
+
|
|
28
|
+
+-- <recipe_name>_spec.rb
|
29
|
+
```
|
30
|
+
|
31
|
+
Or force the example group into the spec:
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
|
35
|
+
describe 'cookbook::recipe', :type => :recipe do
|
36
|
+
...
|
37
|
+
end
|
38
|
+
```
|
39
|
+
|
40
|
+
## Matchers
|
41
|
+
|
42
|
+
All of the standard RSpec matchers are available for you to use when testing Chef recipes.
|
43
|
+
|
44
|
+
*Checking if a recipe contains a resource*
|
45
|
+
|
46
|
+
Use the matcher `contain_<resource_type>` matcher:
|
47
|
+
|
48
|
+
```ruby
|
49
|
+
it { should contain_remote_file }
|
50
|
+
```
|
51
|
+
|
52
|
+
It can take the name of the resource as a parameter:
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
it { should contain_remote_file('/etc/chef/dna.json') }
|
56
|
+
```
|
57
|
+
|
58
|
+
It can take the parameters that the resource takes:
|
59
|
+
|
60
|
+
```ruby
|
61
|
+
it { should contain_remote_file('/etc/chef/dna.json', {:action => :nothing}) }
|
62
|
+
```
|
63
|
+
|
64
|
+
Use the chained method `with` to test the further attributes that the resource gets:
|
65
|
+
|
66
|
+
```ruby
|
67
|
+
it { should contain_remote_file('/etc/chef/dna.json').with(:source, 'dna.json.erb') }
|
68
|
+
```
|
69
|
+
|
70
|
+
Use the chained method `without` to the the resource doesn't get further unexpected attributes:
|
71
|
+
|
72
|
+
```ruby
|
73
|
+
it { should contain_remote_file('/etc/chef/dna.json').without(:owner, :group) }
|
74
|
+
```
|
75
|
+
|
76
|
+
## Settings
|
77
|
+
|
78
|
+
*Cookbooks path*
|
79
|
+
|
80
|
+
The path to the cookbooks can be specified using `let` for each group:
|
81
|
+
|
82
|
+
```ruby
|
83
|
+
describe 'foo::bar' do
|
84
|
+
let(:cookbook_path) { File.expand_path('../cookbooks', __FILE__) }
|
85
|
+
end
|
86
|
+
```
|
87
|
+
|
88
|
+
Or it can be set as a global RSpec setting:
|
89
|
+
|
90
|
+
```ruby
|
91
|
+
RSpec.configure do |c|
|
92
|
+
c.cookbook_path File.expand_path('../cookbooks', __FILE__)
|
93
|
+
end
|
94
|
+
```
|
95
|
+
|
96
|
+
*JSON attributes*
|
97
|
+
|
98
|
+
Each recipe includes a node. This node can be feeded with a hash using `let` in the description group:
|
99
|
+
|
100
|
+
```ruby
|
101
|
+
describe 'foo::bar' do
|
102
|
+
let(:json_attributes) { {:foo => :bar} }
|
103
|
+
end
|
104
|
+
```
|
105
|
+
|
106
|
+
With a file in our file system:
|
107
|
+
|
108
|
+
```ruby
|
109
|
+
describe 'foo::bar' do
|
110
|
+
let(:json_attributes) { '/etc/chef/dna.json' }
|
111
|
+
end
|
112
|
+
```
|
113
|
+
|
114
|
+
Or with plain JSON:
|
115
|
+
|
116
|
+
```ruby
|
117
|
+
describe 'foo::bar' do
|
118
|
+
let(:json_attributes) { '{"foo": "bar"}' }
|
119
|
+
end
|
120
|
+
```
|
121
|
+
|
122
|
+
The JSON attributes can alson be set as a global RSpec setting:
|
123
|
+
|
124
|
+
```ruby
|
125
|
+
RSpec.configure do |c|
|
126
|
+
c.json_attributes {}
|
127
|
+
end
|
128
|
+
```
|
data/Rakefile
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'date'
|
4
|
+
|
5
|
+
#############################################################################
|
6
|
+
#
|
7
|
+
# Helper functions
|
8
|
+
#
|
9
|
+
#############################################################################
|
10
|
+
|
11
|
+
def name
|
12
|
+
@name ||= Dir['*.gemspec'].first.split('.').first
|
13
|
+
end
|
14
|
+
|
15
|
+
def version
|
16
|
+
line = File.read("lib/#{name}.rb")[/^\s*VERSION\s*=\s*.*/]
|
17
|
+
line.match(/.*VERSION\s*=\s*['"](.*)['"]/)[1]
|
18
|
+
end
|
19
|
+
|
20
|
+
def date
|
21
|
+
Date.today.to_s
|
22
|
+
end
|
23
|
+
|
24
|
+
def rubyforge_project
|
25
|
+
name
|
26
|
+
end
|
27
|
+
|
28
|
+
def gemspec_file
|
29
|
+
"#{name}.gemspec"
|
30
|
+
end
|
31
|
+
|
32
|
+
def gem_file
|
33
|
+
"#{name}-#{version}.gem"
|
34
|
+
end
|
35
|
+
|
36
|
+
def replace_header(head, header_name)
|
37
|
+
head.sub!(/(\.#{header_name}\s*= ').*'/) { "#{$1}#{send(header_name)}'"}
|
38
|
+
end
|
39
|
+
|
40
|
+
#############################################################################
|
41
|
+
#
|
42
|
+
# Standard tasks
|
43
|
+
#
|
44
|
+
#############################################################################
|
45
|
+
|
46
|
+
require 'rspec/core/rake_task'
|
47
|
+
|
48
|
+
RSpec::Core::RakeTask.new(:rspec) do |spec|
|
49
|
+
spec.rspec_opts = ['--color', "--format documentation"]
|
50
|
+
end
|
51
|
+
|
52
|
+
task :default => :rspec
|
53
|
+
|
54
|
+
desc "Open an irb session preloaded with this library"
|
55
|
+
task :console do
|
56
|
+
sh "bundle exec irb -rubygems -I ./lib -r #{name}.rb"
|
57
|
+
end
|
58
|
+
|
59
|
+
#############################################################################
|
60
|
+
#
|
61
|
+
# Packaging tasks
|
62
|
+
#
|
63
|
+
#############################################################################
|
64
|
+
|
65
|
+
desc "release a new version of rspec-chef, tag the version and push the gem"
|
66
|
+
task :release => :build do
|
67
|
+
unless `git branch` =~ /^\* master$/
|
68
|
+
puts "You must be on the master branch to release!"
|
69
|
+
exit!
|
70
|
+
end
|
71
|
+
sh "git commit --allow-empty -a -m 'Release #{version}'"
|
72
|
+
sh "git tag v#{version}"
|
73
|
+
sh "git push origin master"
|
74
|
+
sh "git push --tags"
|
75
|
+
sh "gem push pkg/#{gem_file}"
|
76
|
+
end
|
77
|
+
|
78
|
+
desc "install rspec-chef gem in this box"
|
79
|
+
task :install => :build do
|
80
|
+
sh "gem install pkg/#{gem_file}"
|
81
|
+
end
|
82
|
+
|
83
|
+
desc "build rspec-chef gem"
|
84
|
+
task :build => 'gemspec' do
|
85
|
+
sh "mkdir -p pkg"
|
86
|
+
sh "gem build #{gemspec_file}"
|
87
|
+
sh "mv #{gem_file} pkg"
|
88
|
+
end
|
89
|
+
|
90
|
+
desc "generate a valid gemspec file"
|
91
|
+
task :gemspec => :validate do
|
92
|
+
# read spec file and split out manifest section
|
93
|
+
spec = File.read(gemspec_file)
|
94
|
+
head, manifest, tail = spec.split(" # = MANIFEST =\n")
|
95
|
+
|
96
|
+
# replace name version and date
|
97
|
+
replace_header(head, :name)
|
98
|
+
replace_header(head, :version)
|
99
|
+
replace_header(head, :date)
|
100
|
+
#comment this out if your rubyforge_project has a different name
|
101
|
+
replace_header(head, :rubyforge_project)
|
102
|
+
|
103
|
+
# determine file list from git ls-files
|
104
|
+
files = `git ls-files`.
|
105
|
+
split("\n").
|
106
|
+
sort.
|
107
|
+
reject { |file| file =~ /^\./ }.
|
108
|
+
reject { |file| file =~ /^(rdoc|pkg|src|racklib)/ }.
|
109
|
+
map { |file| " #{file}" }.
|
110
|
+
join("\n")
|
111
|
+
|
112
|
+
# piece file back together and write
|
113
|
+
manifest = " s.files = %w[\n#{files}\n ]\n"
|
114
|
+
spec = [head, manifest, tail].join(" # = MANIFEST =\n")
|
115
|
+
File.open(gemspec_file, 'w') { |io| io.write(spec) }
|
116
|
+
puts "Updated #{gemspec_file}"
|
117
|
+
end
|
118
|
+
|
119
|
+
task :validate do
|
120
|
+
libfiles = Dir['lib/*'] - ["lib/#{name}.rb", "lib/#{name}"]
|
121
|
+
unless libfiles.empty?
|
122
|
+
puts "Directory `lib` should only contain a `#{name}.rb` file and `#{name}` dir."
|
123
|
+
exit!
|
124
|
+
end
|
125
|
+
unless Dir['VERSION*'].empty?
|
126
|
+
puts "A `VERSION` file at root level violates Gem best practices."
|
127
|
+
exit!
|
128
|
+
end
|
129
|
+
end
|
data/lib/rspec-chef.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
require 'chef'
|
3
|
+
require 'rspec-chef/chef_support'
|
4
|
+
require 'rspec-chef/json_support'
|
5
|
+
require 'rspec-chef/matchers'
|
6
|
+
require 'rspec-chef/examples'
|
7
|
+
|
8
|
+
module RSpec
|
9
|
+
module Chef
|
10
|
+
VERSION = '0.1.0'
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
RSpec.configure do |c|
|
15
|
+
c.add_setting :cookbook_path, :default => '/etc/chef/recipes'
|
16
|
+
c.add_setting :json_attributes, :default => {}
|
17
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module RSpec
|
2
|
+
module Chef
|
3
|
+
module ChefSupport
|
4
|
+
def lookup_recipe(cookbook_name, cookbook_path, dna)
|
5
|
+
recipe_name = ::Chef::Recipe.parse_recipe_name(cookbook_name)
|
6
|
+
|
7
|
+
cookbook_collection = ::Chef::CookbookCollection.new(::Chef::CookbookLoader.new(cookbook_path))
|
8
|
+
node = ::Chef::Node.new
|
9
|
+
node.consume_attributes(dna)
|
10
|
+
|
11
|
+
run_context = ::Chef::RunContext.new(node, cookbook_collection)
|
12
|
+
|
13
|
+
cookbook = run_context.cookbook_collection[recipe_name[0]]
|
14
|
+
cookbook.load_recipe(recipe_name[1], run_context)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'rspec-chef/examples/define_recipe_group.rb'
|
2
|
+
|
3
|
+
RSpec.configure do |c|
|
4
|
+
def c.escaped_path(*parts)
|
5
|
+
Regexp.compile(parts.join('[\\\/]'))
|
6
|
+
end
|
7
|
+
|
8
|
+
c.include RSpec::Chef::DefineRecipeGroup, :type => :recipe, :example_group => {
|
9
|
+
:file_path => c.escaped_path(%w[spec recipes])
|
10
|
+
}
|
11
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module RSpec
|
2
|
+
module Chef
|
3
|
+
module DefineRecipeGroup
|
4
|
+
include RSpec::Chef::Matchers
|
5
|
+
include JSONSupport
|
6
|
+
include ChefSupport
|
7
|
+
|
8
|
+
def subject
|
9
|
+
@recipe ||= recipe
|
10
|
+
end
|
11
|
+
|
12
|
+
def recipe
|
13
|
+
::Chef::Config[:solo] = true
|
14
|
+
::Chef::Config[:cookbook_path] = self.respond_to?(:cookbook_path) ? cookbook_path : RSpec.configuration.cookbook_path
|
15
|
+
dna = json(self.respond_to?(:json_attributes) ? json_attributes : RSpec.configuration.json_attributes)
|
16
|
+
|
17
|
+
cookbook_name = self.class.top_level_description.downcase
|
18
|
+
|
19
|
+
lookup_recipe(cookbook_name, ::Chef::Config[:cookbook_path], dna)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module RSpec
|
2
|
+
module Chef
|
3
|
+
module JSONSupport
|
4
|
+
# Transform JSON data into a Hash
|
5
|
+
# If data is the path to a file it reads the file and transform its content.
|
6
|
+
def json(data)
|
7
|
+
if data.is_a?(Hash)
|
8
|
+
data
|
9
|
+
elsif File.file?(data)
|
10
|
+
::Chef::JSONCompat.from_json(File.read(data)) rescue {}
|
11
|
+
else
|
12
|
+
::Chef::JSONCompat.from_json(data) rescue {}
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'rspec-chef/matchers/contain_resource'
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module RSpec
|
2
|
+
module Chef
|
3
|
+
module Matchers
|
4
|
+
CONTAIN_PATTERN = /^contain_(.+)/
|
5
|
+
|
6
|
+
def method_missing(method, *args, &block)
|
7
|
+
return RSpec::Chef::Matchers::ContainResource.new(method, *args, &block) if method.to_s =~ CONTAIN_PATTERN
|
8
|
+
super
|
9
|
+
end
|
10
|
+
|
11
|
+
class ContainResource
|
12
|
+
def initialize(type, *args, &block)
|
13
|
+
@type = type.to_s[CONTAIN_PATTERN, 1]
|
14
|
+
@name = args.shift
|
15
|
+
@params = args.shift || {}
|
16
|
+
@expected_attributes = {}
|
17
|
+
@unexpected_attributes = []
|
18
|
+
@errors = []
|
19
|
+
end
|
20
|
+
|
21
|
+
def matches?(recipe)
|
22
|
+
lookup = @type
|
23
|
+
lookup << "[#{@name.to_s}]" if @name
|
24
|
+
|
25
|
+
resource = recipe.resources(lookup)
|
26
|
+
return false unless resource
|
27
|
+
|
28
|
+
matches = true
|
29
|
+
@params.each do |key, value|
|
30
|
+
unless (real_value = resource.params[key.to_sym]) == value
|
31
|
+
@errors << "#{key} expected to be #{value} but it is #{real_value}"
|
32
|
+
matches = false
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
@expected_attributes.each do |key, value|
|
37
|
+
unless (real_value = resource.send(key.to_sym)) == value
|
38
|
+
@errors << "#{key} expected to be #{value} but it is #{real_value}"
|
39
|
+
matches = false
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
@unexpected_attributes.flatten.each do |attr|
|
44
|
+
unless (real_value = resource.send(attr.to_sym)) == nil
|
45
|
+
@errors << "#{attr} expected to be nil but it is #{real_value}"
|
46
|
+
matches = false
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
matches
|
51
|
+
end
|
52
|
+
|
53
|
+
def description
|
54
|
+
%Q{include Resource[#{@type} @name="#{@name}"]}
|
55
|
+
end
|
56
|
+
|
57
|
+
def failure_message_for_should
|
58
|
+
%Q{expected that the recipe would #{description}#{errors}}
|
59
|
+
end
|
60
|
+
|
61
|
+
def errors
|
62
|
+
@errors.empty? ? "" : " with #{@errors.join(', and ')}"
|
63
|
+
end
|
64
|
+
|
65
|
+
def with(attribute, value)
|
66
|
+
@expected_attributes[attribute] = value
|
67
|
+
self
|
68
|
+
end
|
69
|
+
|
70
|
+
def without(*attributes)
|
71
|
+
@unexpected_attributes << attributes
|
72
|
+
self
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
data/rspec-chef.gemspec
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
## This is the rakegem gemspec template. Make sure you read and understand
|
2
|
+
## all of the comments. Some sections require modification, and others can
|
3
|
+
## be deleted if you don't need them. Once you understand the contents of
|
4
|
+
## this file, feel free to delete any comments that begin with two hash marks.
|
5
|
+
## You can find comprehensive Gem::Specification documentation, at
|
6
|
+
## http://docs.rubygems.org/read/chapter/20
|
7
|
+
Gem::Specification.new do |s|
|
8
|
+
s.specification_version = 2 if s.respond_to? :specification_version=
|
9
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
10
|
+
s.rubygems_version = '1.3.5'
|
11
|
+
|
12
|
+
## Leave these as is they will be modified for you by the rake gemspec task.
|
13
|
+
## If your rubyforge_project name is different, then edit it and comment out
|
14
|
+
## the sub! line in the Rakefile
|
15
|
+
s.name = 'rspec-chef'
|
16
|
+
s.version = '0.1.0'
|
17
|
+
s.date = '2011-12-30'
|
18
|
+
s.rubyforge_project = 'rspec-chef'
|
19
|
+
|
20
|
+
## Make sure your summary is short. The description may be as long
|
21
|
+
## as you like.
|
22
|
+
s.summary = "Rspec matchers and examples for your Chef recipes"
|
23
|
+
s.description = s.summary
|
24
|
+
|
25
|
+
## List the primary authors. If there are a bunch of authors, it's probably
|
26
|
+
## better to set the email to an email list or something. If you don't have
|
27
|
+
## a custom homepage, consider using your GitHub URL or the like.
|
28
|
+
s.authors = ["David Calavera"]
|
29
|
+
s.email = 'david.calavera@gmail.com'
|
30
|
+
s.homepage = 'http://github.com/calavera/rspec-chef'
|
31
|
+
|
32
|
+
## This gets added to the $LOAD_PATH so that 'lib/NAME.rb' can be required as
|
33
|
+
## require 'NAME.rb' or'/lib/NAME/file.rb' can be as require 'NAME/file.rb'
|
34
|
+
s.require_paths = %w[lib]
|
35
|
+
|
36
|
+
## List your runtime dependencies here. Runtime dependencies are those
|
37
|
+
## that are needed for an end user to actually USE your code.
|
38
|
+
s.add_dependency('chef')
|
39
|
+
s.add_dependency('rspec')
|
40
|
+
s.add_dependency('json')
|
41
|
+
|
42
|
+
## Leave this section as-is. It will be automatically generated from the
|
43
|
+
## contents of your Git repository via the gemspec task. DO NOT REMOVE
|
44
|
+
## THE MANIFEST COMMENTS, they are used as delimiters by the task.
|
45
|
+
# = MANIFEST =
|
46
|
+
s.files = %w[
|
47
|
+
Gemfile
|
48
|
+
Gemfile.lock
|
49
|
+
LICENSE
|
50
|
+
README.md
|
51
|
+
Rakefile
|
52
|
+
lib/rspec-chef.rb
|
53
|
+
lib/rspec-chef/chef_support.rb
|
54
|
+
lib/rspec-chef/examples.rb
|
55
|
+
lib/rspec-chef/examples/define_recipe_group.rb
|
56
|
+
lib/rspec-chef/json_support.rb
|
57
|
+
lib/rspec-chef/matchers.rb
|
58
|
+
lib/rspec-chef/matchers/contain_resource.rb
|
59
|
+
rspec-chef.gemspec
|
60
|
+
spec/fixtures/cookbooks/foo/recipes/default.rb
|
61
|
+
spec/fixtures/cookbooks/foo/recipes/install.rb
|
62
|
+
spec/rspec-chef/chef_support_spec.rb
|
63
|
+
spec/rspec-chef/examples/define_recipe_group_spec.rb
|
64
|
+
spec/rspec-chef/json_support_spec.rb
|
65
|
+
spec/rspec-chef/matchers/contain_resource_spec.rb
|
66
|
+
spec/spec_helper.rb
|
67
|
+
]
|
68
|
+
# = MANIFEST =
|
69
|
+
|
70
|
+
## Test files will be grabbed from the file list. Make sure the path glob
|
71
|
+
## matches what you actually use.
|
72
|
+
## s.test_files = s.files.select { |path| path =~ %r{^spec/.*_spec\.rb} }
|
73
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
remote_file '/tmp/foo'
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require File.expand_path('../spec_helper', File.dirname(__FILE__))
|
2
|
+
|
3
|
+
class RSpecChefSupport
|
4
|
+
include RSpec::Chef::ChefSupport
|
5
|
+
end
|
6
|
+
|
7
|
+
describe RSpecChefSupport do
|
8
|
+
it "returns the default recipe if we only provide the cookbook name" do
|
9
|
+
recipe = subject.lookup_recipe('foo', COOKBOOKS, {})
|
10
|
+
recipe.recipe_name.should == 'default'
|
11
|
+
end
|
12
|
+
|
13
|
+
it "returns the specific recipe if we provide its name" do
|
14
|
+
recipe = subject.lookup_recipe('foo::install', COOKBOOKS, {:path => 'foo'})
|
15
|
+
recipe.recipe_name.should == 'install'
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "foo", :type => :recipe do
|
4
|
+
let(:cookbook_path) { COOKBOOKS }
|
5
|
+
|
6
|
+
it { subject.should contain_remote_file('/tmp/foo') }
|
7
|
+
end
|
8
|
+
|
9
|
+
describe "foo::install", :type => :recipe do
|
10
|
+
let(:cookbook_path) { COOKBOOKS }
|
11
|
+
let(:json_attributes) { {:path => '/tmp/foo'} }
|
12
|
+
|
13
|
+
it { should contain_remote_file('/tmp/foo').with(:source, 'base_remote').with(:mode, 0755) }
|
14
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require File.expand_path('../spec_helper', File.dirname(__FILE__))
|
2
|
+
|
3
|
+
class ChefJSONSupport
|
4
|
+
include RSpec::Chef::JSONSupport
|
5
|
+
end
|
6
|
+
|
7
|
+
describe ChefJSONSupport do
|
8
|
+
require 'tempfile'
|
9
|
+
|
10
|
+
it "returns a hash if the parameter is a hash" do
|
11
|
+
subject.json({:foo => :bar}).should == {:foo => :bar}
|
12
|
+
end
|
13
|
+
|
14
|
+
it "returns the content of a dna file if the parameter is a file path" do
|
15
|
+
path = Tempfile.open('dna.json') do |file|
|
16
|
+
file.write('{"foo": "bar"}')
|
17
|
+
file.path
|
18
|
+
end
|
19
|
+
|
20
|
+
subject.json(path).should == {'foo' => 'bar'}
|
21
|
+
end
|
22
|
+
|
23
|
+
it "returns and empty hash if it cannot parse the file" do
|
24
|
+
path = Tempfile.open('dna.json') do |file|
|
25
|
+
file.write('foo: bar')
|
26
|
+
file.path
|
27
|
+
end
|
28
|
+
|
29
|
+
subject.json(path).should == {}
|
30
|
+
end
|
31
|
+
|
32
|
+
it "returns the json parsed when the parameter is a json string" do
|
33
|
+
subject.json('{"foo": "bar"}').should == {'foo' => 'bar'}
|
34
|
+
end
|
35
|
+
|
36
|
+
it "returns an empty hash is it cannot parse the json" do
|
37
|
+
subject.json('foo: bar').should == {}
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require File.expand_path('../../spec_helper', File.dirname(__FILE__))
|
2
|
+
|
3
|
+
|
4
|
+
class MockResource
|
5
|
+
attr_reader :params
|
6
|
+
|
7
|
+
def initialize(params = {}, attributes = {})
|
8
|
+
@params = params
|
9
|
+
@attributes = attributes
|
10
|
+
end
|
11
|
+
|
12
|
+
def method_missing(method, *attrs)
|
13
|
+
@attributes[method]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class MockRecipe
|
18
|
+
def initialize(resources = {})
|
19
|
+
@resources = resources
|
20
|
+
end
|
21
|
+
|
22
|
+
def resources(name)
|
23
|
+
@resources[name]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe RSpec::Chef::Matchers::ContainResource do
|
28
|
+
|
29
|
+
context "validating recipes" do
|
30
|
+
it "matches a resource with a name" do
|
31
|
+
recipe = MockRecipe.new('remote_file[foo]' => MockResource.new)
|
32
|
+
matcher = described_class.new(:contain_remote_file, :foo)
|
33
|
+
|
34
|
+
matcher.matches?(recipe).should be_true
|
35
|
+
end
|
36
|
+
|
37
|
+
it "matches a resource without a name" do
|
38
|
+
recipe = MockRecipe.new('remote_file' => MockResource.new)
|
39
|
+
matcher = described_class.new(:contain_remote_file)
|
40
|
+
|
41
|
+
matcher.matches?(recipe).should be_true
|
42
|
+
end
|
43
|
+
|
44
|
+
it "matches a resource with parameters" do
|
45
|
+
recipe = MockRecipe.new('remote_file[foo]' => MockResource.new(:bar => :baz))
|
46
|
+
matcher = described_class.new(:contain_remote_file, :foo, {:bar => :baz})
|
47
|
+
|
48
|
+
matcher.matches?(recipe).should be_true
|
49
|
+
end
|
50
|
+
|
51
|
+
it "doesn't match a resource which params don't match" do
|
52
|
+
recipe = MockRecipe.new('remote_file[foo]' => MockResource.new(:bar => :baz))
|
53
|
+
matcher = described_class.new(:contain_remote_file, :foo, {:bar => :koi})
|
54
|
+
|
55
|
+
matcher.matches?(recipe).should be_false
|
56
|
+
end
|
57
|
+
|
58
|
+
it "matches a resource with expected attributes" do
|
59
|
+
recipe = MockRecipe.new('remote_file[foo]' => MockResource.new({}, {:version => '0.1'}))
|
60
|
+
matcher = described_class.new(:contain_remote_file, :foo).with(:version, '0.1')
|
61
|
+
|
62
|
+
matcher.matches?(recipe).should be_true
|
63
|
+
end
|
64
|
+
|
65
|
+
it "doesn't match a resource which expected attributes don't match" do
|
66
|
+
recipe = MockRecipe.new('remote_file[foo]' => MockResource.new({}, {:version => '0.1'}))
|
67
|
+
matcher = described_class.new(:contain_remote_file, :foo).with(:version, '1.1')
|
68
|
+
|
69
|
+
matcher.matches?(recipe).should be_false
|
70
|
+
end
|
71
|
+
|
72
|
+
it "matches a resource without unexpected attributes" do
|
73
|
+
recipe = MockRecipe.new('remote_file[foo]' => MockResource.new)
|
74
|
+
matcher = described_class.new(:contain_remote_file, :foo).without(:version)
|
75
|
+
|
76
|
+
matcher.matches?(recipe).should be_true
|
77
|
+
end
|
78
|
+
|
79
|
+
it "mathes a resource without several unexpected attributes" do
|
80
|
+
recipe = MockRecipe.new('remote_file[foo]' => MockResource.new)
|
81
|
+
matcher = described_class.new(:contain_remote_file, :foo).without(:version, :notif)
|
82
|
+
|
83
|
+
matcher.matches?(recipe).should be_true
|
84
|
+
end
|
85
|
+
|
86
|
+
it "doesn't match a resource with unexpected attributes" do
|
87
|
+
recipe = MockRecipe.new('remote_file[foo]' => MockResource.new({}, {:version => '0.1'}))
|
88
|
+
matcher = described_class.new(:contain_remote_file, :foo).without(:version)
|
89
|
+
|
90
|
+
matcher.matches?(recipe).should be_false
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
it "includes a description" do
|
95
|
+
matcher = described_class.new(:contain_remote_file, :foo)
|
96
|
+
|
97
|
+
matcher.description.should == %q{include Resource[remote_file @name="foo"]}
|
98
|
+
end
|
99
|
+
|
100
|
+
it "includes a message for should failure" do
|
101
|
+
matcher = described_class.new(:contain_remote_file, :foo)
|
102
|
+
matcher.failure_message_for_should.should == %q{expected that the recipe would include Resource[remote_file @name="foo"]}
|
103
|
+
end
|
104
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rspec-chef
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- David Calavera
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-12-30 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: chef
|
16
|
+
requirement: &2156821580 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *2156821580
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rspec
|
27
|
+
requirement: &2156821120 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *2156821120
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: json
|
38
|
+
requirement: &2156820700 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :runtime
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *2156820700
|
47
|
+
description: Rspec matchers and examples for your Chef recipes
|
48
|
+
email: david.calavera@gmail.com
|
49
|
+
executables: []
|
50
|
+
extensions: []
|
51
|
+
extra_rdoc_files: []
|
52
|
+
files:
|
53
|
+
- Gemfile
|
54
|
+
- Gemfile.lock
|
55
|
+
- LICENSE
|
56
|
+
- README.md
|
57
|
+
- Rakefile
|
58
|
+
- lib/rspec-chef.rb
|
59
|
+
- lib/rspec-chef/chef_support.rb
|
60
|
+
- lib/rspec-chef/examples.rb
|
61
|
+
- lib/rspec-chef/examples/define_recipe_group.rb
|
62
|
+
- lib/rspec-chef/json_support.rb
|
63
|
+
- lib/rspec-chef/matchers.rb
|
64
|
+
- lib/rspec-chef/matchers/contain_resource.rb
|
65
|
+
- rspec-chef.gemspec
|
66
|
+
- spec/fixtures/cookbooks/foo/recipes/default.rb
|
67
|
+
- spec/fixtures/cookbooks/foo/recipes/install.rb
|
68
|
+
- spec/rspec-chef/chef_support_spec.rb
|
69
|
+
- spec/rspec-chef/examples/define_recipe_group_spec.rb
|
70
|
+
- spec/rspec-chef/json_support_spec.rb
|
71
|
+
- spec/rspec-chef/matchers/contain_resource_spec.rb
|
72
|
+
- spec/spec_helper.rb
|
73
|
+
homepage: http://github.com/calavera/rspec-chef
|
74
|
+
licenses: []
|
75
|
+
post_install_message:
|
76
|
+
rdoc_options: []
|
77
|
+
require_paths:
|
78
|
+
- lib
|
79
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
80
|
+
none: false
|
81
|
+
requirements:
|
82
|
+
- - ! '>='
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
version: '0'
|
85
|
+
segments:
|
86
|
+
- 0
|
87
|
+
hash: -2959191673565686387
|
88
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
requirements: []
|
95
|
+
rubyforge_project: rspec-chef
|
96
|
+
rubygems_version: 1.8.10
|
97
|
+
signing_key:
|
98
|
+
specification_version: 2
|
99
|
+
summary: Rspec matchers and examples for your Chef recipes
|
100
|
+
test_files: []
|