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,17 @@
1
+ module RSpec::Puppet
2
+ module ManifestMatchers
3
+ def method_missing(method, *args, &block)
4
+ return RSpec::Puppet::ManifestMatchers::CreateGeneric.new(method, *args, &block) if method.to_s =~ /^(create|contain)_/
5
+ return RSpec::Puppet::ManifestMatchers::CountGeneric.new(nil, args[0], method) if method.to_s =~ /^have_.+_count$/
6
+ return RSpec::Puppet::ManifestMatchers::Compile.new if method == :compile
7
+ super
8
+ end
9
+ end
10
+
11
+ module FunctionMatchers
12
+ def method_missing(method, *args, &block)
13
+ return RSpec::Puppet::FunctionMatchers::Run.new if method == :run
14
+ super
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,19 @@
1
+ module RSpec::Puppet
2
+ module ManifestMatchers
3
+ extend RSpec::Matchers::DSL
4
+
5
+ matcher :include_class do |expected_class|
6
+ match do |catalogue|
7
+ catalogue.classes.include? expected_class
8
+ end
9
+
10
+ description do
11
+ "include Class[#{expected_class}]"
12
+ end
13
+
14
+ failure_message_for_should do |actual|
15
+ "expected that the catalogue would include Class[#{expected_class}]"
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,110 @@
1
+ module RSpec::Puppet
2
+ module ManifestMatchers
3
+ class ParameterMatcher
4
+ include RSpec::Puppet::Errors
5
+
6
+ # @param parameter [Symbol] The specific parameter to check
7
+ # @param value [Object] The expected data to match the parameter against
8
+ # @param type [:should, :not] Whether the given parameter should match
9
+ def initialize(parameter, value, type)
10
+ @parameter, @value, @type = parameter, value, type
11
+
12
+ @should_match = (type == :should)
13
+
14
+ @errors = []
15
+ end
16
+
17
+ # Ensure that the actual parameter matches the expected parameter.
18
+ #
19
+ # @param resource [Hash<Symbol, Object>] A hash representing a Puppet
20
+ # resource in the catalog
21
+ #
22
+ # @return [true, false]
23
+ def matches?(resource)
24
+
25
+ @resource = resource
26
+
27
+ actual = @resource[@parameter]
28
+ expected = @value
29
+
30
+ retval = check(expected, actual)
31
+
32
+ unless retval
33
+ @errors << MatchError.new(@parameter, expected, actual, !@should_match)
34
+ end
35
+
36
+ retval
37
+ end
38
+
39
+ # @!attribute [r] errors
40
+ # @return [Array<Object < StandardError>] All expectation errors
41
+ # generated on this parameter.
42
+ attr_reader :errors
43
+
44
+ private
45
+
46
+ # Recursively check that the `expected` and `actual` data structures match
47
+ #
48
+ # @param expected [Object] The expected value of the given resource param
49
+ # @param actual [Object] The value of the resource as found in the catalogue
50
+ #
51
+ # @return [true, false] If the resource matched
52
+ def check(expected, actual)
53
+ case expected
54
+ when Proc
55
+ check_proc(expected, actual)
56
+ when Regexp
57
+ check_regexp(expected, actual)
58
+ when Hash
59
+ check_hash(expected, actual)
60
+ when Array
61
+ check_array(expected, actual)
62
+ else
63
+ check_string(expected, actual)
64
+ end
65
+ end
66
+
67
+ def check_proc(expected, actual)
68
+ expected_return = @should_match
69
+ actual_return = expected.call(actual)
70
+
71
+ actual_return == expected_return
72
+ end
73
+
74
+ def check_regexp(expected, actual)
75
+ !!(actual.to_s.match expected) == @should_match
76
+ end
77
+
78
+ # Ensure that two hashes have the same number of keys, and that for each
79
+ # key in the expected hash, there's a stringified key in the actual hash
80
+ # with a matching value.
81
+ def check_hash(expected, actual)
82
+ op = @should_match ? :"==" : :"!="
83
+
84
+ unless expected.keys.size.send(op, actual.keys.size)
85
+ return false
86
+ end
87
+
88
+ expected.keys.all? do |key|
89
+ check(expected[key], actual[key.to_s])
90
+ end
91
+ end
92
+
93
+ def check_array(expected, actual)
94
+ op = @should_match ? :"==" : :"!="
95
+
96
+ unless expected.size.send(op, actual.size)
97
+ return false
98
+ end
99
+
100
+ (0...expected.size).all? do |index|
101
+ check(expected[index], actual[index])
102
+ end
103
+ end
104
+
105
+ def check_string(expected, actual)
106
+ (expected.to_s == actual.to_s) == @should_match
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,94 @@
1
+ module RSpec::Puppet
2
+ module FunctionMatchers
3
+ class Run
4
+ def matches?(func_obj)
5
+ if @params
6
+ @func = lambda { func_obj.call(@params) }
7
+ else
8
+ @func = lambda { func_obj.call }
9
+ end
10
+
11
+ unless @expected_error.nil?
12
+ result = false
13
+ begin
14
+ @func.call
15
+ rescue Exception => e
16
+ @actual_error = e.class
17
+ if e.is_a?(@expected_error)
18
+ case @expected_error_message
19
+ when nil
20
+ result = true
21
+ when Regexp
22
+ result = @expected_error_message =~ e.message
23
+ else
24
+ result = @expected_error_message == e.message
25
+ end
26
+ end
27
+ end
28
+ result
29
+ else
30
+ unless @expected_return.nil?
31
+ @actual_return = @func.call
32
+ @actual_return == @expected_return
33
+ else
34
+ begin
35
+ @func.call
36
+ rescue
37
+ false
38
+ end
39
+ true
40
+ end
41
+ end
42
+ end
43
+
44
+ def with_params(*params)
45
+ @params = params
46
+ self
47
+ end
48
+
49
+ def and_return(value)
50
+ @expected_return = value
51
+ self
52
+ end
53
+
54
+ def and_raise_error(error_or_message, message=nil)
55
+ case error_or_message
56
+ when String, Regexp
57
+ @expected_error, @expected_error_message = Exception, error_or_message
58
+ else
59
+ @expected_error, @expected_error_message = error_or_message, message
60
+ end
61
+ self
62
+ end
63
+
64
+ def failure_message_for_should(func_obj)
65
+ failure_message_generic(:should, func_obj)
66
+ end
67
+
68
+ def failure_message_for_should_not(func_obj)
69
+ failure_message_generic(:should_not, func_obj)
70
+ end
71
+
72
+ private
73
+ def failure_message_generic(type, func_obj)
74
+ func_name = func_obj.name.gsub(/^function_/, '')
75
+ func_params = @params.inspect[1..-2]
76
+
77
+ message = "expected #{func_name}(#{func_params}) to "
78
+ message << "not " if type == :should_not
79
+
80
+ if @expected_return
81
+ message << "have returned #{@expected_return.inspect}"
82
+ if type == :should
83
+ message << " instead of #{@actual_return.inspect}"
84
+ end
85
+ elsif @expected_error
86
+ message << "have raised #{@expected_error.inspect}"
87
+ else
88
+ message << "have run successfully"
89
+ end
90
+ message
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,167 @@
1
+ require 'puppet'
2
+ require 'fileutils'
3
+
4
+ module RSpec::Puppet
5
+ class Setup
6
+ def self.run(module_name=nil)
7
+ unless is_module_dir?
8
+ $stderr.puts "Does not appear to be a Puppet module. Aborting"
9
+ return false
10
+ end
11
+
12
+ if module_name.nil?
13
+ module_name = get_module_name
14
+ if module_name.nil?
15
+ $stderr.puts "Unable to determine module name. Aborting"
16
+ return false
17
+ end
18
+ end
19
+
20
+ [
21
+ 'spec',
22
+ 'spec/classes',
23
+ 'spec/defines',
24
+ 'spec/functions',
25
+ 'spec/hosts',
26
+ 'spec/fixtures',
27
+ 'spec/fixtures/manifests',
28
+ 'spec/fixtures/modules',
29
+ "spec/fixtures/modules/#{module_name}",
30
+ ].each { |dir| safe_mkdir(dir) }
31
+
32
+ safe_touch('spec/fixtures/manifests/site.pp')
33
+
34
+ ['manifests','lib','files','templates'].each do |dir|
35
+ if File.exist? dir
36
+ safe_make_symlink("../../../../#{dir}", "spec/fixtures/modules/#{module_name}/#{dir}")
37
+ end
38
+ end
39
+
40
+ safe_create_spec_helper
41
+ safe_create_rakefile
42
+ end
43
+
44
+ protected
45
+ def self.get_module_name
46
+ module_name = nil
47
+ Dir["manifests/*.pp"].entries.each do |manifest|
48
+ module_name = get_module_name_from_file(manifest)
49
+ break unless module_name.nil?
50
+ end
51
+ module_name
52
+ end
53
+
54
+ def self.get_module_name_from_file(file)
55
+ p = Puppet::Parser::Lexer.new
56
+ module_name = nil
57
+ p.string = File.read(file)
58
+ tokens = p.fullscan
59
+
60
+ i = tokens.index { |token| [:CLASS, :DEFINE].include? token.first }
61
+ unless i.nil?
62
+ module_name = tokens[i + 1].last[:value].split('::').first
63
+ end
64
+
65
+ module_name
66
+ end
67
+
68
+ def self.is_module_dir?
69
+ Dir["*"].entries.include? "manifests"
70
+ end
71
+
72
+ def self.safe_mkdir(dir)
73
+ if File.exists? dir
74
+ unless File.directory? dir
75
+ $stderr.puts "!! #{dir} already exists and is not a directory"
76
+ end
77
+ else
78
+ FileUtils.mkdir dir
79
+ puts " + #{dir}/"
80
+ end
81
+ end
82
+
83
+ def self.safe_touch(file)
84
+ if File.exists? file
85
+ unless File.file? file
86
+ $stderr.puts "!! #{file} already exists and is not a regular file"
87
+ end
88
+ else
89
+ FileUtils.touch file
90
+ puts " + #{file}"
91
+ end
92
+ end
93
+
94
+ def self.safe_create_file(filename, content)
95
+ if File.exists? filename
96
+ old_content = File.read(filename)
97
+ if old_content != content
98
+ $stderr.puts "!! #{filename} already exists and differs from template"
99
+ end
100
+ else
101
+ File.open(filename, 'w') do |f|
102
+ f.puts content
103
+ end
104
+ puts " + #{filename}"
105
+ end
106
+ end
107
+
108
+ def self.safe_create_spec_helper
109
+ content = <<-EOF
110
+ require 'rspec-puppet'
111
+
112
+ fixture_path = File.expand_path(File.join(__FILE__, '..', 'fixtures'))
113
+
114
+ RSpec.configure do |c|
115
+ c.module_path = File.join(fixture_path, 'modules')
116
+ c.manifest_dir = File.join(fixture_path, 'manifests')
117
+ end
118
+ EOF
119
+ safe_create_file('spec/spec_helper.rb', content)
120
+ end
121
+
122
+ def self.safe_make_symlink(source, target)
123
+ if File.exists? target
124
+ unless File.symlink? target
125
+ $stderr.puts "!! #{file} already exists and is not a symlink"
126
+ end
127
+ else
128
+ FileUtils.ln_s(source, target)
129
+ puts " + #{target}"
130
+ end
131
+ end
132
+
133
+ def self.safe_create_rakefile
134
+ content = <<-'EOF'
135
+ require 'rake'
136
+ require 'rspec/core/rake_task'
137
+
138
+ desc "Run all RSpec code examples"
139
+ RSpec::Core::RakeTask.new(:rspec) do |t|
140
+ t.rspec_opts = File.read("spec/spec.opts").chomp || ""
141
+ end
142
+
143
+ SPEC_SUITES = (Dir.entries('spec') - ['.', '..','fixtures']).select {|e| File.directory? "spec/#{e}" }
144
+ namespace :rspec do
145
+ SPEC_SUITES.each do |suite|
146
+ desc "Run #{suite} RSpec code examples"
147
+ RSpec::Core::RakeTask.new(suite) do |t|
148
+ t.pattern = "spec/#{suite}/**/*_spec.rb"
149
+ t.rspec_opts = File.read("spec/spec.opts").chomp || ""
150
+ end
151
+ end
152
+ end
153
+ task :default => :rspec
154
+
155
+ begin
156
+ if Gem::Specification::find_by_name('puppet-lint')
157
+ require 'puppet-lint/tasks/puppet-lint'
158
+ PuppetLint.configuration.ignore_paths = ["spec/**/*.pp", "vendor/**/*.pp"]
159
+ task :default => [:rspec, :lint]
160
+ end
161
+ rescue Gem::LoadError
162
+ end
163
+ EOF
164
+ safe_create_file('Rakefile', content)
165
+ end
166
+ end
167
+ end
@@ -0,0 +1,165 @@
1
+ module RSpec::Puppet
2
+ module Support
3
+
4
+ @@cache = {}
5
+
6
+ def catalogue(type)
7
+ vardir = setup_puppet
8
+
9
+ code = pre_cond + test_manifest(type)
10
+ node_name = nodename(type)
11
+
12
+ catalogue = build_catalog(node_name, facts_hash(node_name), code)
13
+ FileUtils.rm_rf(vardir) if File.directory?(vardir)
14
+ catalogue
15
+ end
16
+
17
+ def test_manifest(type)
18
+ klass_name = self.class.top_level_description.downcase
19
+
20
+ if type == :class
21
+ if !self.respond_to?(:params) || params == {}
22
+ "include #{klass_name}"
23
+ else
24
+ "class { '#{klass_name}': #{param_str} }"
25
+ end
26
+ elsif type == :define
27
+ if self.respond_to? :params
28
+ "#{klass_name} { '#{title}': #{param_str} }"
29
+ else
30
+ "#{klass_name} { '#{title}': }"
31
+ end
32
+ elsif type == :host
33
+ ""
34
+ end
35
+ end
36
+
37
+ def nodename(type)
38
+ if [:class, :define, :function].include? type
39
+ self.respond_to?(:node) ? node : Puppet[:certname]
40
+ else
41
+ self.class.top_level_description.downcase
42
+ end
43
+ end
44
+
45
+
46
+ def pre_cond
47
+ if self.respond_to?(:pre_condition) && !pre_condition.nil?
48
+ if pre_condition.is_a? Array
49
+ pre_condition.join("\n")
50
+ else
51
+ pre_condition
52
+ end
53
+ else
54
+ ''
55
+ end
56
+ end
57
+
58
+ def facts_hash(node)
59
+ facts_val = {
60
+ 'hostname' => node.split('.').first,
61
+ 'fqdn' => node,
62
+ 'domain' => node.split('.').last,
63
+ }
64
+
65
+ if RSpec.configuration.default_facts.any?
66
+ facts_val.merge!(munge_facts(RSpec.configuration.default_facts))
67
+ end
68
+
69
+ facts_val.merge!(munge_facts(facts)) if self.respond_to?(:facts)
70
+ facts_val
71
+ end
72
+
73
+ def param_str
74
+ params.keys.map do |r|
75
+ param_val = escape_special_chars(params[r].inspect)
76
+ "#{r.to_s} => #{param_val}"
77
+ end.join(', ')
78
+ end
79
+
80
+ def setup_puppet
81
+ vardir = Dir.mktmpdir
82
+ Puppet[:vardir] = vardir
83
+
84
+ [
85
+ [:modulepath, :module_path],
86
+ [:manifestdir, :manifest_dir],
87
+ [:manifest, :manifest],
88
+ [:templatedir, :template_dir],
89
+ [:config, :config],
90
+ [:confdir, :confdir],
91
+ ].each do |a, b|
92
+ if self.respond_to? b
93
+ Puppet[a] = self.send(b)
94
+ else
95
+ Puppet[a] = RSpec.configuration.send(b)
96
+ end
97
+ end
98
+
99
+ if Puppet[:hiera_config] == File.expand_path('/dev/null')
100
+ Puppet[:hiera_config] = File.join(vardir, 'hiera.yaml')
101
+ end
102
+
103
+ Puppet[:libdir] = Dir["#{Puppet[:modulepath]}/*/lib"].entries.join(File::PATH_SEPARATOR)
104
+ vardir
105
+ end
106
+
107
+ def build_catalog_without_cache(nodename, facts_val, code)
108
+ Puppet[:code] = code
109
+
110
+ stub_facts! facts_val
111
+
112
+ node_obj = Puppet::Node.new(nodename)
113
+
114
+ node_obj.merge(facts_val)
115
+
116
+ # trying to be compatible with 2.7 as well as 2.6
117
+ if Puppet::Resource::Catalog.respond_to? :find
118
+ Puppet::Resource::Catalog.find(node_obj.name, :use_node => node_obj)
119
+ else
120
+ Puppet::Resource::Catalog.indirection.find(node_obj.name, :use_node => node_obj)
121
+ end
122
+ end
123
+
124
+ def stub_facts!(facts)
125
+ facts.each { |k, v| Facter.add(k) { setcode { v } } }
126
+ end
127
+
128
+ def build_catalog(*args)
129
+ @@cache[args] ||= self.build_catalog_without_cache(*args)
130
+ end
131
+
132
+ def munge_facts(facts)
133
+ output = {}
134
+ facts.keys.each { |key| output[key.to_s] = facts[key] }
135
+ output
136
+ end
137
+
138
+ def escape_special_chars(string)
139
+ string.gsub!(/\$/, "\\$")
140
+ string
141
+ end
142
+
143
+ def scope(compiler, node_name)
144
+ if Puppet.version =~ /^2\.[67]/
145
+ # loadall should only be necessary prior to 3.x
146
+ # Please note, loadall needs to happen first when creating a scope, otherwise
147
+ # you might receive undefined method `function_*' errors
148
+ Puppet::Parser::Functions.autoloader.loadall
149
+ scope = Puppet::Parser::Scope.new(:compiler => compiler)
150
+ else
151
+ scope = Puppet::Parser::Scope.new(compiler)
152
+ end
153
+
154
+ scope.source = Puppet::Resource::Type.new(:node, node_name)
155
+ scope.parent = compiler.topscope
156
+ scope
157
+ end
158
+
159
+ def build_node(name, opts = {})
160
+ node_environment = Puppet::Node::Environment.new('test')
161
+ opts.merge!({:environment => node_environment})
162
+ Puppet::Node.new(name, opts)
163
+ end
164
+ end
165
+ end