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.
Files changed (50) hide show
  1. data/Gemfile +8 -0
  2. data/LICENSE +20 -0
  3. data/README.md +357 -0
  4. data/Rakefile +7 -0
  5. data/lib/rspec-puppet.rb +47 -0
  6. data/lib/rspec-puppet/errors.rb +46 -0
  7. data/lib/rspec-puppet/example.rb +27 -0
  8. data/lib/rspec-puppet/example/class_example_group.rb +10 -0
  9. data/lib/rspec-puppet/example/define_example_group.rb +10 -0
  10. data/lib/rspec-puppet/example/function_example_group.rb +46 -0
  11. data/lib/rspec-puppet/example/host_example_group.rb +10 -0
  12. data/lib/rspec-puppet/matchers.rb +6 -0
  13. data/lib/rspec-puppet/matchers/compile.rb +133 -0
  14. data/lib/rspec-puppet/matchers/count_generic.rb +73 -0
  15. data/lib/rspec-puppet/matchers/create_generic.rb +156 -0
  16. data/lib/rspec-puppet/matchers/dynamic_matchers.rb +17 -0
  17. data/lib/rspec-puppet/matchers/include_class.rb +19 -0
  18. data/lib/rspec-puppet/matchers/parameter_matcher.rb +110 -0
  19. data/lib/rspec-puppet/matchers/run.rb +94 -0
  20. data/lib/rspec-puppet/setup.rb +167 -0
  21. data/lib/rspec-puppet/support.rb +165 -0
  22. data/rspec-puppet-womble.gemspec +18 -0
  23. data/spec/classes/array_spec.rb +74 -0
  24. data/spec/classes/boolean_regexp_spec.rb +13 -0
  25. data/spec/classes/boolean_spec.rb +11 -0
  26. data/spec/classes/cycle_bad_spec.rb +5 -0
  27. data/spec/classes/cycle_good_spec.rb +5 -0
  28. data/spec/classes/escape_spec.rb +7 -0
  29. data/spec/classes/hash_spec.rb +68 -0
  30. data/spec/classes/sysctl_common_spec.rb +83 -0
  31. data/spec/defines/escape_def_spec.rb +8 -0
  32. data/spec/defines/sysctl_before_spec.rb +25 -0
  33. data/spec/defines/sysctl_spec.rb +21 -0
  34. data/spec/fixtures/manifests/site.pp +26 -0
  35. data/spec/fixtures/modules/boolean/manifests/init.pp +12 -0
  36. data/spec/fixtures/modules/cycle/manifests/bad.pp +8 -0
  37. data/spec/fixtures/modules/cycle/manifests/good.pp +7 -0
  38. data/spec/fixtures/modules/cycle/manifests/init.pp +0 -0
  39. data/spec/fixtures/modules/escape/manifests/def.pp +6 -0
  40. data/spec/fixtures/modules/escape/manifests/init.pp +6 -0
  41. data/spec/fixtures/modules/structured_data/manifests/def.pp +5 -0
  42. data/spec/fixtures/modules/structured_data/manifests/init.pp +5 -0
  43. data/spec/fixtures/modules/sysctl/manifests/init.pp +39 -0
  44. data/spec/functions/split_spec.rb +30 -0
  45. data/spec/hosts/bad_dep_host_spec.rb +5 -0
  46. data/spec/hosts/foo_spec.rb +6 -0
  47. data/spec/hosts/good_dep_host_spec.rb +5 -0
  48. data/spec/hosts/testhost_spec.rb +5 -0
  49. data/spec/spec_helper.rb +6 -0
  50. 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,10 @@
1
+ module RSpec::Puppet
2
+ module ClassExampleGroup
3
+ include RSpec::Puppet::ManifestMatchers
4
+ include RSpec::Puppet::Support
5
+
6
+ def subject
7
+ @catalogue ||= catalogue(:class)
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ module RSpec::Puppet
2
+ module DefineExampleGroup
3
+ include RSpec::Puppet::ManifestMatchers
4
+ include RSpec::Puppet::Support
5
+
6
+ def subject
7
+ @catalogue ||= catalogue(:define)
8
+ end
9
+ end
10
+ 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,10 @@
1
+ module RSpec::Puppet
2
+ module HostExampleGroup
3
+ include RSpec::Puppet::ManifestMatchers
4
+ include RSpec::Puppet::Support
5
+
6
+ def subject
7
+ @catalogue ||= catalogue(:host)
8
+ end
9
+ end
10
+ 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