rspec-puppet 0.1.6 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +71 -6
- data/lib/rspec-puppet.rb +45 -0
- data/lib/rspec-puppet/errors.rb +83 -0
- data/lib/rspec-puppet/example/class_example_group.rb +1 -55
- data/lib/rspec-puppet/example/define_example_group.rb +1 -57
- data/lib/rspec-puppet/example/function_example_group.rb +21 -35
- data/lib/rspec-puppet/example/host_example_group.rb +1 -26
- data/lib/rspec-puppet/matchers.rb +3 -1
- 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 +155 -52
- data/lib/rspec-puppet/matchers/dynamic_matchers.rb +17 -0
- data/lib/rspec-puppet/matchers/include_class.rb +2 -1
- data/lib/rspec-puppet/matchers/parameter_matcher.rb +110 -0
- data/lib/rspec-puppet/matchers/run.rb +49 -31
- data/lib/rspec-puppet/setup.rb +57 -34
- data/lib/rspec-puppet/support.rb +154 -7
- metadata +18 -32
- data/LICENSE +0 -20
- data/Rakefile +0 -7
- data/lib/rspec-puppet/matchers/create_resource.rb +0 -53
- data/rspec-puppet.gemspec +0 -47
- data/spec/classes/boolean_regexp_spec.rb +0 -10
- data/spec/classes/boolean_spec.rb +0 -11
- data/spec/classes/sysctl_common_spec.rb +0 -40
- data/spec/defines/sysctl_before_spec.rb +0 -26
- data/spec/defines/sysctl_spec.rb +0 -14
- data/spec/fixtures/manifests/site.pp +0 -7
- data/spec/fixtures/modules/boolean/manifests/init.pp +0 -12
- data/spec/fixtures/modules/sysctl/manifests/init.pp +0 -39
- data/spec/functions/split_spec.rb +0 -17
- data/spec/hosts/foo_spec.rb +0 -6
- data/spec/hosts/testhost_spec.rb +0 -5
- data/spec/spec_helper.rb +0 -6
@@ -4,32 +4,7 @@ module RSpec::Puppet
|
|
4
4
|
include RSpec::Puppet::Support
|
5
5
|
|
6
6
|
def subject
|
7
|
-
@catalogue ||= catalogue
|
8
|
-
end
|
9
|
-
|
10
|
-
def catalogue
|
11
|
-
vardir = Dir.mktmpdir
|
12
|
-
Puppet[:vardir] = vardir
|
13
|
-
Puppet[:hiera_config] = File.join(vardir, "hiera.yaml") if Puppet[:hiera_config] == File.expand_path("/dev/null")
|
14
|
-
Puppet[:modulepath] = self.respond_to?(:module_path) ? module_path : RSpec.configuration.module_path
|
15
|
-
Puppet[:manifestdir] = self.respond_to?(:manifest_dir) ? manifest_dir : RSpec.configuration.manifest_dir
|
16
|
-
Puppet[:manifest] = self.respond_to?(:manifest) ? manifest : RSpec.configuration.manifest
|
17
|
-
Puppet[:templatedir] = self.respond_to?(:template_dir) ? template_dir : RSpec.configuration.template_dir
|
18
|
-
Puppet[:config] = self.respond_to?(:config) ? config : RSpec.configuration.config
|
19
|
-
code = ""
|
20
|
-
|
21
|
-
nodename = self.class.top_level_description.downcase
|
22
|
-
|
23
|
-
facts_val = {
|
24
|
-
'hostname' => nodename.split('.').first,
|
25
|
-
'fqdn' => nodename,
|
26
|
-
'domain' => nodename.split('.').last,
|
27
|
-
}
|
28
|
-
facts_val.merge!(munge_facts(facts)) if self.respond_to?(:facts)
|
29
|
-
|
30
|
-
catalogue = build_catalog(nodename, facts_val, code)
|
31
|
-
FileUtils.rm_rf(vardir) if File.directory?(vardir)
|
32
|
-
catalogue
|
7
|
+
@catalogue ||= catalogue(:host)
|
33
8
|
end
|
34
9
|
end
|
35
10
|
end
|
@@ -1,4 +1,6 @@
|
|
1
1
|
require 'rspec-puppet/matchers/create_generic'
|
2
|
-
require 'rspec-puppet/matchers/create_resource'
|
3
2
|
require 'rspec-puppet/matchers/include_class'
|
3
|
+
require 'rspec-puppet/matchers/compile'
|
4
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
|
@@ -1,34 +1,76 @@
|
|
1
|
+
require 'rspec-puppet/matchers/parameter_matcher'
|
1
2
|
module RSpec::Puppet
|
2
3
|
module ManifestMatchers
|
3
4
|
class CreateGeneric
|
5
|
+
include RSpec::Puppet::Errors
|
6
|
+
|
4
7
|
def initialize(*args, &block)
|
5
8
|
@exp_resource_type = args.shift.to_s.gsub(/^(create|contain)_/, '')
|
6
9
|
@args = args
|
7
10
|
@block = block
|
8
11
|
@referenced_type = referenced_type(@exp_resource_type)
|
9
12
|
@title = args[0]
|
13
|
+
|
14
|
+
@errors = []
|
15
|
+
@expected_params = []
|
16
|
+
@expected_undef_params = []
|
17
|
+
@notifies = []
|
18
|
+
@subscribes = []
|
19
|
+
@requires = []
|
20
|
+
@befores = []
|
10
21
|
end
|
11
22
|
|
12
23
|
def with(*args, &block)
|
13
24
|
params = args.shift
|
14
|
-
@expected_params =
|
25
|
+
@expected_params = @expected_params | params.to_a
|
15
26
|
self
|
16
27
|
end
|
17
28
|
|
29
|
+
def only_with(*args, &block)
|
30
|
+
params = args.shift
|
31
|
+
@expected_params_count = (@expected_params_count || 0) + params.size
|
32
|
+
self.with(params, &block)
|
33
|
+
end
|
34
|
+
|
18
35
|
def without(*args, &block)
|
19
36
|
params = args.shift
|
20
|
-
@expected_undef_params =
|
37
|
+
@expected_undef_params = @expected_undef_params | Array(params)
|
38
|
+
self
|
39
|
+
end
|
40
|
+
|
41
|
+
def that_notifies(resource)
|
42
|
+
@notifies << resource
|
43
|
+
self
|
44
|
+
end
|
45
|
+
|
46
|
+
def that_subscribes_to(resource)
|
47
|
+
@subscribes << resource
|
48
|
+
self
|
49
|
+
end
|
50
|
+
|
51
|
+
def that_requires(resource)
|
52
|
+
@requires << resource
|
53
|
+
self
|
54
|
+
end
|
55
|
+
|
56
|
+
def that_comes_before(resource)
|
57
|
+
@befores << resource
|
21
58
|
self
|
22
59
|
end
|
23
60
|
|
24
61
|
def method_missing(method, *args, &block)
|
25
62
|
if method.to_s =~ /^with_/
|
26
63
|
param = method.to_s.gsub(/^with_/, '')
|
27
|
-
|
64
|
+
@expected_params << [param, args[0]]
|
65
|
+
self
|
66
|
+
elsif method.to_s =~ /^only_with_/
|
67
|
+
param = method.to_s.gsub(/^only_with_/, '')
|
68
|
+
@expected_params_count = (@expected_params_count || 0) + 1
|
69
|
+
@expected_params << [param, args[0]]
|
28
70
|
self
|
29
71
|
elsif method.to_s =~ /^without_/
|
30
72
|
param = method.to_s.gsub(/^without_/, '')
|
31
|
-
|
73
|
+
@expected_undef_params << [param, args[0]]
|
32
74
|
self
|
33
75
|
else
|
34
76
|
super
|
@@ -40,41 +82,25 @@ module RSpec::Puppet
|
|
40
82
|
resource = catalogue.resource(@referenced_type, @title)
|
41
83
|
|
42
84
|
if resource.nil?
|
43
|
-
|
85
|
+
false
|
44
86
|
else
|
45
87
|
rsrc_hsh = resource.to_hash
|
46
|
-
if @
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
ret = false
|
51
|
-
(@errors ||= []) << "#{name.to_s} matching `#{value.inspect}` but its value of `#{rsrc_hsh[name.to_sym].inspect}` does not"
|
52
|
-
end
|
53
|
-
elsif value.kind_of?(Array) then
|
54
|
-
unless Array(rsrc_hsh[name.to_sym]).flatten.join == value.flatten.join
|
55
|
-
ret = false
|
56
|
-
(@errors ||= []) << "#{name.to_s} set to `#{value.inspect}` but it is set to `#{rsrc_hsh[name.to_sym].inspect}` in the catalogue"
|
57
|
-
end
|
58
|
-
else
|
59
|
-
unless rsrc_hsh[name.to_sym].to_s == value.to_s
|
60
|
-
ret = false
|
61
|
-
(@errors ||= []) << "#{name.to_s} set to `#{value.inspect}` but it is set to `#{rsrc_hsh[name.to_sym].inspect}` in the catalogue"
|
62
|
-
end
|
63
|
-
end
|
88
|
+
if @expected_params_count
|
89
|
+
unless rsrc_hsh.size == @expected_params_count
|
90
|
+
ret = false
|
91
|
+
(@errors ||= []) << "exactly #{@expected_params_count} parameters but the catalogue contains #{rsrc_hsh.size}"
|
64
92
|
end
|
65
93
|
end
|
66
94
|
|
67
|
-
if @
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
end
|
74
|
-
end
|
75
|
-
end
|
95
|
+
check_params(rsrc_hsh, @expected_params, :should) if @expected_params.any?
|
96
|
+
check_params(rsrc_hsh, @expected_undef_params, :not) if @expected_undef_params.any?
|
97
|
+
check_befores(catalogue, resource) if @befores.any?
|
98
|
+
check_requires(catalogue, resource) if @requires.any?
|
99
|
+
check_notifies(catalogue, resource) if @notifies.any?
|
100
|
+
check_subscribes(catalogue, resource) if @subscribes.any?
|
76
101
|
|
77
|
-
|
102
|
+
@errors.empty?
|
103
|
+
end
|
78
104
|
end
|
79
105
|
|
80
106
|
def failure_message_for_should
|
@@ -87,20 +113,17 @@ module RSpec::Puppet
|
|
87
113
|
|
88
114
|
def description
|
89
115
|
values = []
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
values << "#{name.to_s} matching #{value.inspect}"
|
94
|
-
else
|
95
|
-
values << "#{name.to_s} => #{value.inspect}"
|
96
|
-
end
|
97
|
-
end
|
116
|
+
|
117
|
+
if @expected_params_count
|
118
|
+
values << "exactly #{@expected_params_count} parameters"
|
98
119
|
end
|
99
120
|
|
100
|
-
if @
|
101
|
-
@
|
102
|
-
|
103
|
-
|
121
|
+
if @expected_params.any?
|
122
|
+
values.concat(generate_param_list(@expected_params, :should))
|
123
|
+
end
|
124
|
+
|
125
|
+
if @expected_undef_params.any?
|
126
|
+
values.concat(generate_param_list(@expected_undef_params, :not))
|
104
127
|
end
|
105
128
|
|
106
129
|
unless values.empty?
|
@@ -114,20 +137,100 @@ module RSpec::Puppet
|
|
114
137
|
"contain #{@referenced_type}[#{@title}]#{value_str}"
|
115
138
|
end
|
116
139
|
|
117
|
-
|
118
|
-
|
140
|
+
private
|
119
141
|
def referenced_type(type)
|
120
142
|
type.split('__').map { |r| r.capitalize }.join('::')
|
121
143
|
end
|
122
144
|
|
123
145
|
def errors
|
124
|
-
@errors.
|
146
|
+
@errors.empty? ? "" : " with #{@errors.join(', and parameter ')}"
|
125
147
|
end
|
126
|
-
end
|
127
148
|
|
128
|
-
|
129
|
-
|
130
|
-
|
149
|
+
def generate_param_list(list, type)
|
150
|
+
output = []
|
151
|
+
list.each do |param, value|
|
152
|
+
if value.nil?
|
153
|
+
output << "#{param.to_s} #{type == :not ? 'un' : ''}defined"
|
154
|
+
else
|
155
|
+
a = type == :not ? '!' : '='
|
156
|
+
b = value.is_a?(Regexp) ? '~' : '>'
|
157
|
+
output << "#{param.to_s} #{a}#{b} #{value.inspect}"
|
158
|
+
end
|
159
|
+
end
|
160
|
+
output
|
161
|
+
end
|
162
|
+
|
163
|
+
def check_befores(catalogue, resource)
|
164
|
+
@befores.each do |ref|
|
165
|
+
unless precedes?(resource, catalogue.resource(ref))
|
166
|
+
@errors << BeforeRelationshipError.new(resource.to_ref, ref)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def check_requires(catalogue, resource)
|
172
|
+
@requires.each do |ref|
|
173
|
+
unless precedes?(catalogue.resource(ref), resource)
|
174
|
+
@errors << RequireRelationshipError.new(resource.to_ref, ref)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
def check_notifies(catalogue, resource)
|
180
|
+
@notifies.each do |ref|
|
181
|
+
unless notifies?(resource, catalogue.resource(ref))
|
182
|
+
@errors << NotifyRelationshipError.new(resource.to_ref, ref)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
def check_subscribes(catalogue, resource)
|
188
|
+
@subscribes.each do |ref|
|
189
|
+
unless notifies?(catalogue.resource(ref), resource)
|
190
|
+
@errors << SubscribeRelationshipError.new(resource.to_ref, ref)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
def relationship_refs(array)
|
196
|
+
Array[array].flatten.map do |resource|
|
197
|
+
resource.respond_to?(:to_ref) ? resource.to_ref : resource
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
def precedes?(first, second)
|
202
|
+
before_refs = relationship_refs(first[:before])
|
203
|
+
require_refs = relationship_refs(second[:require])
|
204
|
+
|
205
|
+
before_refs.include?(second.to_ref) || require_refs.include?(first.to_ref)
|
206
|
+
end
|
207
|
+
|
208
|
+
def notifies?(first, second)
|
209
|
+
notify_refs = relationship_refs(first[:notify])
|
210
|
+
subscribe_refs = relationship_refs(second[:subscribe])
|
211
|
+
|
212
|
+
notify_refs.include?(second.to_ref) || subscribe_refs.include?(first.to_ref)
|
213
|
+
end
|
214
|
+
|
215
|
+
# @param resource [Hash<Symbol, Object>] The resource in the catalog
|
216
|
+
# @param list [Array<String, Object>] The expected values of the resource
|
217
|
+
# @param type [:should, :not] Whether the given parameters should/not match
|
218
|
+
def check_params(resource, list, type)
|
219
|
+
list.each do |param, value|
|
220
|
+
param = param.to_sym
|
221
|
+
|
222
|
+
if value.nil? then
|
223
|
+
unless resource[param].nil?
|
224
|
+
@errors << "#{param} undefined"
|
225
|
+
end
|
226
|
+
else
|
227
|
+
m = ParameterMatcher.new(param, value, type)
|
228
|
+
unless m.matches?(resource)
|
229
|
+
@errors.concat m.errors
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
131
234
|
end
|
132
235
|
end
|
133
236
|
end
|