rspec-puppet-womble 0.1.6.womble1 → 0.1.6.womble2

Sign up to get free protection for your applications and to get access to all the features.
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