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.
- 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
|