rspec-puppet-womble 0.1.6.womble1 → 0.1.6.womble2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Gemfile +8 -0
- data/LICENSE +20 -0
- data/README.md +357 -0
- data/Rakefile +7 -0
- data/lib/rspec-puppet.rb +47 -0
- data/lib/rspec-puppet/errors.rb +46 -0
- data/lib/rspec-puppet/example.rb +27 -0
- data/lib/rspec-puppet/example/class_example_group.rb +10 -0
- data/lib/rspec-puppet/example/define_example_group.rb +10 -0
- data/lib/rspec-puppet/example/function_example_group.rb +46 -0
- data/lib/rspec-puppet/example/host_example_group.rb +10 -0
- data/lib/rspec-puppet/matchers.rb +6 -0
- data/lib/rspec-puppet/matchers/compile.rb +133 -0
- data/lib/rspec-puppet/matchers/count_generic.rb +73 -0
- data/lib/rspec-puppet/matchers/create_generic.rb +156 -0
- data/lib/rspec-puppet/matchers/dynamic_matchers.rb +17 -0
- data/lib/rspec-puppet/matchers/include_class.rb +19 -0
- data/lib/rspec-puppet/matchers/parameter_matcher.rb +110 -0
- data/lib/rspec-puppet/matchers/run.rb +94 -0
- data/lib/rspec-puppet/setup.rb +167 -0
- data/lib/rspec-puppet/support.rb +165 -0
- data/rspec-puppet-womble.gemspec +18 -0
- data/spec/classes/array_spec.rb +74 -0
- data/spec/classes/boolean_regexp_spec.rb +13 -0
- data/spec/classes/boolean_spec.rb +11 -0
- data/spec/classes/cycle_bad_spec.rb +5 -0
- data/spec/classes/cycle_good_spec.rb +5 -0
- data/spec/classes/escape_spec.rb +7 -0
- data/spec/classes/hash_spec.rb +68 -0
- data/spec/classes/sysctl_common_spec.rb +83 -0
- data/spec/defines/escape_def_spec.rb +8 -0
- data/spec/defines/sysctl_before_spec.rb +25 -0
- data/spec/defines/sysctl_spec.rb +21 -0
- data/spec/fixtures/manifests/site.pp +26 -0
- data/spec/fixtures/modules/boolean/manifests/init.pp +12 -0
- data/spec/fixtures/modules/cycle/manifests/bad.pp +8 -0
- data/spec/fixtures/modules/cycle/manifests/good.pp +7 -0
- data/spec/fixtures/modules/cycle/manifests/init.pp +0 -0
- data/spec/fixtures/modules/escape/manifests/def.pp +6 -0
- data/spec/fixtures/modules/escape/manifests/init.pp +6 -0
- data/spec/fixtures/modules/structured_data/manifests/def.pp +5 -0
- data/spec/fixtures/modules/structured_data/manifests/init.pp +5 -0
- data/spec/fixtures/modules/sysctl/manifests/init.pp +39 -0
- data/spec/functions/split_spec.rb +30 -0
- data/spec/hosts/bad_dep_host_spec.rb +5 -0
- data/spec/hosts/foo_spec.rb +6 -0
- data/spec/hosts/good_dep_host_spec.rb +5 -0
- data/spec/hosts/testhost_spec.rb +5 -0
- data/spec/spec_helper.rb +6 -0
- metadata +50 -1
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'rspec-puppet/support'
|
2
|
+
require 'rspec-puppet/example/define_example_group'
|
3
|
+
require 'rspec-puppet/example/class_example_group'
|
4
|
+
require 'rspec-puppet/example/function_example_group'
|
5
|
+
require 'rspec-puppet/example/host_example_group'
|
6
|
+
|
7
|
+
RSpec::configure do |c|
|
8
|
+
def c.escaped_path(*parts)
|
9
|
+
Regexp.compile(parts.join('[\\\/]'))
|
10
|
+
end
|
11
|
+
|
12
|
+
c.include RSpec::Puppet::DefineExampleGroup, :type => :define, :example_group => {
|
13
|
+
:file_path => c.escaped_path(%w[spec defines])
|
14
|
+
}
|
15
|
+
|
16
|
+
c.include RSpec::Puppet::ClassExampleGroup, :type => :class, :example_group => {
|
17
|
+
:file_path => c.escaped_path(%w[spec classes])
|
18
|
+
}
|
19
|
+
|
20
|
+
c.include RSpec::Puppet::FunctionExampleGroup, :type => :puppet_function, :example_group => {
|
21
|
+
:file_path => c.escaped_path(%w[spec functions])
|
22
|
+
}
|
23
|
+
|
24
|
+
c.include RSpec::Puppet::HostExampleGroup, :type => :host, :example_group => {
|
25
|
+
:file_path => c.escaped_path(%w[spec hosts])
|
26
|
+
}
|
27
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module RSpec::Puppet
|
2
|
+
module FunctionExampleGroup
|
3
|
+
include RSpec::Puppet::FunctionMatchers
|
4
|
+
include RSpec::Puppet::ManifestMatchers
|
5
|
+
include RSpec::Puppet::Support
|
6
|
+
|
7
|
+
def subject
|
8
|
+
function_name = self.class.top_level_description.downcase
|
9
|
+
|
10
|
+
vardir = setup_puppet
|
11
|
+
|
12
|
+
node_name = nodename(:function)
|
13
|
+
|
14
|
+
facts_val = facts_hash(node_name)
|
15
|
+
|
16
|
+
# if we specify a pre_condition, we should ensure that we compile that code
|
17
|
+
# into a catalog that is accessible from the scope where the function is called
|
18
|
+
Puppet[:code] = pre_cond
|
19
|
+
|
20
|
+
compiler = build_compiler(node_name, facts_val)
|
21
|
+
|
22
|
+
function_scope = scope(compiler, node_name)
|
23
|
+
|
24
|
+
# Return the method instance for the function. This can be used with
|
25
|
+
# method.call
|
26
|
+
return nil unless Puppet::Parser::Functions.function(function_name)
|
27
|
+
FileUtils.rm_rf(vardir) if File.directory?(vardir)
|
28
|
+
function_scope.method("function_#{function_name}".intern)
|
29
|
+
end
|
30
|
+
|
31
|
+
# get a compiler with an attached compiled catalog
|
32
|
+
def build_compiler(node_name, fact_values)
|
33
|
+
node_options = {
|
34
|
+
:parameters => fact_values,
|
35
|
+
}
|
36
|
+
|
37
|
+
stub_facts! fact_values
|
38
|
+
|
39
|
+
node = build_node(node_name, node_options)
|
40
|
+
|
41
|
+
compiler = Puppet::Parser::Compiler.new(node)
|
42
|
+
compiler.compile
|
43
|
+
compiler
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,6 @@
|
|
1
|
+
require 'rspec-puppet/matchers/create_generic'
|
2
|
+
require 'rspec-puppet/matchers/include_class'
|
3
|
+
require 'rspec-puppet/matchers/compile'
|
4
|
+
require 'rspec-puppet/matchers/run'
|
5
|
+
require 'rspec-puppet/matchers/count_generic'
|
6
|
+
require 'rspec-puppet/matchers/dynamic_matchers'
|
@@ -0,0 +1,133 @@
|
|
1
|
+
module RSpec::Puppet
|
2
|
+
module ManifestMatchers
|
3
|
+
class Compile
|
4
|
+
def initialize
|
5
|
+
@failed_resource = ""
|
6
|
+
@check_deps = false
|
7
|
+
@cycles = []
|
8
|
+
end
|
9
|
+
|
10
|
+
def with_all_deps
|
11
|
+
@check_deps = true
|
12
|
+
self
|
13
|
+
end
|
14
|
+
|
15
|
+
def matches?(catalogue)
|
16
|
+
@catalogue = catalogue
|
17
|
+
if cycles_found?
|
18
|
+
false
|
19
|
+
elsif @check_deps == true && missing_dependencies?
|
20
|
+
false
|
21
|
+
else
|
22
|
+
true
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def description
|
27
|
+
"compile the catalogue without cycles"
|
28
|
+
end
|
29
|
+
|
30
|
+
def failure_message_for_should
|
31
|
+
unless @cycles.empty?
|
32
|
+
"dependency cycles found: #{@cycles.join('; ')}"
|
33
|
+
else
|
34
|
+
"expected that the catalogue would include #{@failed_resource}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def failure_message_for_should_not
|
39
|
+
"expected that the catalogue would not compile but it does"
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
def missing_dependencies?
|
44
|
+
retval = false
|
45
|
+
|
46
|
+
resource_vertices = @catalogue.vertices.select { |v| v.is_a? Puppet::Resource }
|
47
|
+
resource_vertices.each do |vertex|
|
48
|
+
vertex.each do |param,value|
|
49
|
+
if [:require, :subscribe, :notify, :before].include? param
|
50
|
+
value = Array[value] unless value.is_a? Array
|
51
|
+
value.each do |val|
|
52
|
+
if val.is_a? Puppet::Resource
|
53
|
+
retval = true unless resource_exists?(val, vertex)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
retval
|
61
|
+
end
|
62
|
+
|
63
|
+
def resource_hash
|
64
|
+
@resource_hash ||= Proc.new do
|
65
|
+
res_hash = {}
|
66
|
+
@catalogue.vertices.each do |vertex|
|
67
|
+
if vertex.is_a? Puppet::Resource
|
68
|
+
res_hash[vertex.ref] = 1
|
69
|
+
if vertex[:alias]
|
70
|
+
res_hash["#{vertex.type.to_s}[#{vertex[:alias]}]"] = 1
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
res_hash
|
75
|
+
end.call
|
76
|
+
end
|
77
|
+
|
78
|
+
def check_resource(res)
|
79
|
+
if resource_hash[res.ref]
|
80
|
+
true
|
81
|
+
elsif res[:alias] && resource_hash["#{res.type.to_s}[#{res[:alias]}]"]
|
82
|
+
true
|
83
|
+
else
|
84
|
+
false
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def resource_exists?(res, vertex)
|
89
|
+
unless check_resource(res)
|
90
|
+
@failed_resource = "#{res.ref} used at #{vertex.file}:#{vertex.line} in #{vertex.ref}"
|
91
|
+
false
|
92
|
+
else
|
93
|
+
true
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def cycles_found?
|
98
|
+
retval = false
|
99
|
+
begin
|
100
|
+
cat = @catalogue.to_ral.relationship_graph
|
101
|
+
cat.write_graph(:resources)
|
102
|
+
if cat.respond_to? :find_cycles_in_graph
|
103
|
+
find_cycles(cat)
|
104
|
+
else
|
105
|
+
find_cycles_legacy(cat)
|
106
|
+
end
|
107
|
+
retval = true unless @cycles.empty?
|
108
|
+
rescue Puppet::Error
|
109
|
+
retval = true
|
110
|
+
end
|
111
|
+
retval
|
112
|
+
end
|
113
|
+
|
114
|
+
def find_cycles(catalogue)
|
115
|
+
cycles = catalogue.find_cycles_in_graph
|
116
|
+
if cycles.length > 0
|
117
|
+
cycles.each do |cycle|
|
118
|
+
paths = catalogue.paths_in_cycle(cycle)
|
119
|
+
@cycles << (paths.map{ |path| '(' + path.join(" => ") + ')'}.join("\n") + "\n")
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def find_cycles_legacy(catalogue)
|
125
|
+
begin
|
126
|
+
catalogue.topsort
|
127
|
+
rescue Puppet::Error => e
|
128
|
+
@cycles = [e.message.rpartition(';').first.partition(':').last]
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module RSpec::Puppet
|
2
|
+
module ManifestMatchers
|
3
|
+
class CountGeneric
|
4
|
+
def initialize(type, count, *method)
|
5
|
+
if type.nil?
|
6
|
+
@type = method[0].to_s.gsub(/^have_(.+)_resource_count$/, '\1')
|
7
|
+
else
|
8
|
+
@type = type
|
9
|
+
end
|
10
|
+
@referenced_type = referenced_type(@type)
|
11
|
+
@expected_number = count.to_i
|
12
|
+
end
|
13
|
+
|
14
|
+
def matches?(catalogue)
|
15
|
+
if @type == "resource"
|
16
|
+
@actual_number = catalogue.resources.count do |res|
|
17
|
+
!(['Class', 'Node'].include? res.type)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Puppet automatically adds Stage[main]
|
21
|
+
@actual_number = @actual_number - 1
|
22
|
+
else
|
23
|
+
@actual_number = catalogue.resources.count do |res|
|
24
|
+
res.type == @referenced_type
|
25
|
+
end
|
26
|
+
|
27
|
+
# Puppet automatically adds Class[main] and Class[Settings]
|
28
|
+
@actual_number = @actual_number - 2 if @type == "class"
|
29
|
+
end
|
30
|
+
|
31
|
+
@actual_number == @expected_number
|
32
|
+
end
|
33
|
+
|
34
|
+
def description
|
35
|
+
desc = []
|
36
|
+
|
37
|
+
desc << "contain exactly #{@expected_number}"
|
38
|
+
if @type == "class"
|
39
|
+
desc << "#{@expected_number == 1 ? "class" : "classes" }"
|
40
|
+
else
|
41
|
+
unless @type == "resource"
|
42
|
+
desc << "#{@referenced_type}"
|
43
|
+
end
|
44
|
+
desc << "#{@expected_number == 1 ? "resource" : "resources" }"
|
45
|
+
end
|
46
|
+
|
47
|
+
desc.join(" ")
|
48
|
+
end
|
49
|
+
|
50
|
+
def failure_message_for_should
|
51
|
+
"expected that the catalogue would " + description + " but it contains #{@actual_number}"
|
52
|
+
end
|
53
|
+
|
54
|
+
def failure_message_for_should_not
|
55
|
+
"expected that the catalogue would not " + description + " but it does"
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def referenced_type(type)
|
61
|
+
type.split('__').map { |r| r.capitalize }.join('::')
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def have_class_count(count)
|
66
|
+
RSpec::Puppet::ManifestMatchers::CountGeneric.new('class', count)
|
67
|
+
end
|
68
|
+
|
69
|
+
def have_resource_count(count)
|
70
|
+
RSpec::Puppet::ManifestMatchers::CountGeneric.new('resource', count)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,156 @@
|
|
1
|
+
require 'rspec-puppet/matchers/parameter_matcher'
|
2
|
+
module RSpec::Puppet
|
3
|
+
module ManifestMatchers
|
4
|
+
class CreateGeneric
|
5
|
+
include RSpec::Puppet::Errors
|
6
|
+
|
7
|
+
def initialize(*args, &block)
|
8
|
+
@exp_resource_type = args.shift.to_s.gsub(/^(create|contain)_/, '')
|
9
|
+
@args = args
|
10
|
+
@block = block
|
11
|
+
@referenced_type = referenced_type(@exp_resource_type)
|
12
|
+
@title = args[0]
|
13
|
+
|
14
|
+
@errors = []
|
15
|
+
@expected_params = []
|
16
|
+
@expected_undef_params = []
|
17
|
+
end
|
18
|
+
|
19
|
+
def with(*args, &block)
|
20
|
+
params = args.shift
|
21
|
+
@expected_params = @expected_params | params.to_a
|
22
|
+
self
|
23
|
+
end
|
24
|
+
|
25
|
+
def only_with(*args, &block)
|
26
|
+
params = args.shift
|
27
|
+
@expected_params_count = (@expected_params_count || 0) + params.size
|
28
|
+
self.with(params, &block)
|
29
|
+
end
|
30
|
+
|
31
|
+
def without(*args, &block)
|
32
|
+
params = args.shift
|
33
|
+
@expected_undef_params = @expected_undef_params | Array(params)
|
34
|
+
self
|
35
|
+
end
|
36
|
+
|
37
|
+
def method_missing(method, *args, &block)
|
38
|
+
if method.to_s =~ /^with_/
|
39
|
+
param = method.to_s.gsub(/^with_/, '')
|
40
|
+
@expected_params << [param, args[0]]
|
41
|
+
self
|
42
|
+
elsif method.to_s =~ /^only_with_/
|
43
|
+
param = method.to_s.gsub(/^only_with_/, '')
|
44
|
+
@expected_params_count = (@expected_params_count || 0) + 1
|
45
|
+
@expected_params << [param, args[0]]
|
46
|
+
self
|
47
|
+
elsif method.to_s =~ /^without_/
|
48
|
+
param = method.to_s.gsub(/^without_/, '')
|
49
|
+
@expected_undef_params << [param, args[0]]
|
50
|
+
self
|
51
|
+
else
|
52
|
+
super
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def matches?(catalogue)
|
57
|
+
ret = true
|
58
|
+
resource = catalogue.resource(@referenced_type, @title)
|
59
|
+
|
60
|
+
if resource.nil?
|
61
|
+
false
|
62
|
+
else
|
63
|
+
rsrc_hsh = resource.to_hash
|
64
|
+
if @expected_params_count
|
65
|
+
unless rsrc_hsh.size == @expected_params_count
|
66
|
+
ret = false
|
67
|
+
(@errors ||= []) << "exactly #{@expected_params_count} parameters but the catalogue contains #{rsrc_hsh.size}"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
check_params(rsrc_hsh, @expected_params, :should) if @expected_params.any?
|
72
|
+
check_params(rsrc_hsh, @expected_undef_params, :not) if @expected_undef_params.any?
|
73
|
+
|
74
|
+
@errors.empty?
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def failure_message_for_should
|
79
|
+
"expected that the catalogue would contain #{@referenced_type}[#{@title}]#{errors}"
|
80
|
+
end
|
81
|
+
|
82
|
+
def failure_message_for_should_not
|
83
|
+
"expected that the catalogue would not contain #{@referenced_type}[#{@title}]#{errors}"
|
84
|
+
end
|
85
|
+
|
86
|
+
def description
|
87
|
+
values = []
|
88
|
+
|
89
|
+
if @expected_params_count
|
90
|
+
values << "exactly #{@expected_params_count} parameters"
|
91
|
+
end
|
92
|
+
|
93
|
+
if @expected_params.any?
|
94
|
+
values.concat(generate_param_list(@expected_params, :should))
|
95
|
+
end
|
96
|
+
|
97
|
+
if @expected_undef_params.any?
|
98
|
+
values.concat(generate_param_list(@expected_undef_params, :not))
|
99
|
+
end
|
100
|
+
|
101
|
+
unless values.empty?
|
102
|
+
if values.length == 1
|
103
|
+
value_str = " with #{values.first}"
|
104
|
+
else
|
105
|
+
value_str = " with #{values[0..-2].join(", ")} and #{values[-1]}"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
"contain #{@referenced_type}[#{@title}]#{value_str}"
|
110
|
+
end
|
111
|
+
|
112
|
+
private
|
113
|
+
def referenced_type(type)
|
114
|
+
type.split('__').map { |r| r.capitalize }.join('::')
|
115
|
+
end
|
116
|
+
|
117
|
+
def errors
|
118
|
+
@errors.empty? ? "" : " with #{@errors.join(', and parameter ')}"
|
119
|
+
end
|
120
|
+
|
121
|
+
def generate_param_list(list, type)
|
122
|
+
output = []
|
123
|
+
list.each do |param, value|
|
124
|
+
if value.nil?
|
125
|
+
output << "#{param.to_s} #{type == :not ? 'un' : ''}defined"
|
126
|
+
else
|
127
|
+
a = type == :not ? '!' : '='
|
128
|
+
b = value.is_a?(Regexp) ? '~' : '>'
|
129
|
+
output << "#{param.to_s} #{a}#{b} #{value.inspect}"
|
130
|
+
end
|
131
|
+
end
|
132
|
+
output
|
133
|
+
end
|
134
|
+
|
135
|
+
# @param resource [Hash<Symbol, Object>] The resource in the catalog
|
136
|
+
# @param list [Array<String, Object>] The expected values of the resource
|
137
|
+
# @param type [:should, :not] Whether the given parameters should/not match
|
138
|
+
def check_params(resource, list, type)
|
139
|
+
list.each do |param, value|
|
140
|
+
param = param.to_sym
|
141
|
+
|
142
|
+
if value.nil? then
|
143
|
+
unless resource[param].nil?
|
144
|
+
@errors << "#{param} undefined"
|
145
|
+
end
|
146
|
+
else
|
147
|
+
m = ParameterMatcher.new(param, value, type)
|
148
|
+
unless m.matches?(resource)
|
149
|
+
@errors.concat m.errors
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|