rspec-puppet 0.1.6 → 1.0.0

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.
Files changed (35) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +71 -6
  3. data/lib/rspec-puppet.rb +45 -0
  4. data/lib/rspec-puppet/errors.rb +83 -0
  5. data/lib/rspec-puppet/example/class_example_group.rb +1 -55
  6. data/lib/rspec-puppet/example/define_example_group.rb +1 -57
  7. data/lib/rspec-puppet/example/function_example_group.rb +21 -35
  8. data/lib/rspec-puppet/example/host_example_group.rb +1 -26
  9. data/lib/rspec-puppet/matchers.rb +3 -1
  10. data/lib/rspec-puppet/matchers/compile.rb +133 -0
  11. data/lib/rspec-puppet/matchers/count_generic.rb +73 -0
  12. data/lib/rspec-puppet/matchers/create_generic.rb +155 -52
  13. data/lib/rspec-puppet/matchers/dynamic_matchers.rb +17 -0
  14. data/lib/rspec-puppet/matchers/include_class.rb +2 -1
  15. data/lib/rspec-puppet/matchers/parameter_matcher.rb +110 -0
  16. data/lib/rspec-puppet/matchers/run.rb +49 -31
  17. data/lib/rspec-puppet/setup.rb +57 -34
  18. data/lib/rspec-puppet/support.rb +154 -7
  19. metadata +18 -32
  20. data/LICENSE +0 -20
  21. data/Rakefile +0 -7
  22. data/lib/rspec-puppet/matchers/create_resource.rb +0 -53
  23. data/rspec-puppet.gemspec +0 -47
  24. data/spec/classes/boolean_regexp_spec.rb +0 -10
  25. data/spec/classes/boolean_spec.rb +0 -11
  26. data/spec/classes/sysctl_common_spec.rb +0 -40
  27. data/spec/defines/sysctl_before_spec.rb +0 -26
  28. data/spec/defines/sysctl_spec.rb +0 -14
  29. data/spec/fixtures/manifests/site.pp +0 -7
  30. data/spec/fixtures/modules/boolean/manifests/init.pp +0 -12
  31. data/spec/fixtures/modules/sysctl/manifests/init.pp +0 -39
  32. data/spec/functions/split_spec.rb +0 -17
  33. data/spec/hosts/foo_spec.rb +0 -6
  34. data/spec/hosts/testhost_spec.rb +0 -5
  35. 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 = (@expected_params || []) | params.to_a
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 = (@expected_undef_params || []) | Array(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
- (@expected_params ||= []) << [param, args[0]]
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
- (@expected_undef_params ||= []) << param
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
- ret = false
85
+ false
44
86
  else
45
87
  rsrc_hsh = resource.to_hash
46
- if @expected_params
47
- @expected_params.each do |name, value|
48
- if value.kind_of?(Regexp) then
49
- unless rsrc_hsh[name.to_sym].to_s =~ value
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 @expected_undef_params
68
- @expected_undef_params.each do |name|
69
- unless resource.send(:parameters)[name.to_sym].nil?
70
- ret = false
71
- (@errors ||= []) << "#{name.to_s} undefined"
72
- end
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
- ret
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
- if @expected_params
91
- @expected_params.each do |name, value|
92
- if value.kind_of?(Regexp)
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 @expected_undef_params
101
- @expected_undef_params.each do |name, value|
102
- values << "#{name.to_s} undefined"
103
- end
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
- private
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.nil? ? "" : " with #{@errors.join(', and parameter ')}"
146
+ @errors.empty? ? "" : " with #{@errors.join(', and parameter ')}"
125
147
  end
126
- end
127
148
 
128
- def method_missing(method, *args, &block)
129
- return RSpec::Puppet::ManifestMatchers::CreateGeneric.new(method, *args, &block) if method.to_s =~ /^(create|contain)_/
130
- super
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