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