rspec-puppet 0.1.6 → 1.0.0

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 (35) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +71 -6
  3. data/lib/rspec-puppet.rb +45 -0
  4. data/lib/rspec-puppet/errors.rb +83 -0
  5. data/lib/rspec-puppet/example/class_example_group.rb +1 -55
  6. data/lib/rspec-puppet/example/define_example_group.rb +1 -57
  7. data/lib/rspec-puppet/example/function_example_group.rb +21 -35
  8. data/lib/rspec-puppet/example/host_example_group.rb +1 -26
  9. data/lib/rspec-puppet/matchers.rb +3 -1
  10. data/lib/rspec-puppet/matchers/compile.rb +133 -0
  11. data/lib/rspec-puppet/matchers/count_generic.rb +73 -0
  12. data/lib/rspec-puppet/matchers/create_generic.rb +155 -52
  13. data/lib/rspec-puppet/matchers/dynamic_matchers.rb +17 -0
  14. data/lib/rspec-puppet/matchers/include_class.rb +2 -1
  15. data/lib/rspec-puppet/matchers/parameter_matcher.rb +110 -0
  16. data/lib/rspec-puppet/matchers/run.rb +49 -31
  17. data/lib/rspec-puppet/setup.rb +57 -34
  18. data/lib/rspec-puppet/support.rb +154 -7
  19. metadata +18 -32
  20. data/LICENSE +0 -20
  21. data/Rakefile +0 -7
  22. data/lib/rspec-puppet/matchers/create_resource.rb +0 -53
  23. data/rspec-puppet.gemspec +0 -47
  24. data/spec/classes/boolean_regexp_spec.rb +0 -10
  25. data/spec/classes/boolean_spec.rb +0 -11
  26. data/spec/classes/sysctl_common_spec.rb +0 -40
  27. data/spec/defines/sysctl_before_spec.rb +0 -26
  28. data/spec/defines/sysctl_spec.rb +0 -14
  29. data/spec/fixtures/manifests/site.pp +0 -7
  30. data/spec/fixtures/modules/boolean/manifests/init.pp +0 -12
  31. data/spec/fixtures/modules/sysctl/manifests/init.pp +0 -39
  32. data/spec/functions/split_spec.rb +0 -17
  33. data/spec/hosts/foo_spec.rb +0 -6
  34. data/spec/hosts/testhost_spec.rb +0 -5
  35. data/spec/spec_helper.rb +0 -6
@@ -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
@@ -4,7 +4,8 @@ module RSpec::Puppet
4
4
 
5
5
  matcher :include_class do |expected_class|
6
6
  match do |catalogue|
7
- catalogue.classes.include? expected_class
7
+ RSpec.deprecate(:include_class, :contain_class)
8
+ catalogue.classes.include?(expected_class)
8
9
  end
9
10
 
10
11
  description do
@@ -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
@@ -1,27 +1,35 @@
1
1
  module RSpec::Puppet
2
2
  module FunctionMatchers
3
- extend RSpec::Matchers::DSL
4
-
5
- matcher :run do
6
- match do |func_obj|
3
+ class Run
4
+ def matches?(func_obj)
7
5
  if @params
8
6
  @func = lambda { func_obj.call(@params) }
9
7
  else
10
8
  @func = lambda { func_obj.call }
11
9
  end
12
10
 
13
- if @expected_error
11
+ unless @expected_error.nil?
12
+ result = false
14
13
  begin
15
14
  @func.call
16
- rescue @expected_error
17
- #XXX check error string here
18
- true
19
- rescue
20
- false
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
21
27
  end
28
+ result
22
29
  else
23
- if @expected_return
24
- @func.call == @expected_return
30
+ unless @expected_return.nil?
31
+ @actual_return = @func.call
32
+ @actual_return == @expected_return
25
33
  else
26
34
  begin
27
35
  @func.call
@@ -33,43 +41,53 @@ module RSpec::Puppet
33
41
  end
34
42
  end
35
43
 
36
- chain :with_params do |*params|
44
+ def with_params(*params)
37
45
  @params = params
46
+ self
38
47
  end
39
48
 
40
- chain :and_return do |value|
49
+ def and_return(value)
41
50
  @expected_return = value
51
+ self
42
52
  end
43
53
 
44
- # XXX support error string and regexp
45
- chain :and_raise_error do |value|
46
- @expected_error = value
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
47
62
  end
48
63
 
49
- failure_message_for_should do |func_obj|
50
- func_name = func_obj.name.to_s.gsub(/^function_/, '')
51
- func_params = @params.inspect[1..-2]
64
+ def failure_message_for_should(func_obj)
65
+ failure_message_generic(:should, func_obj)
66
+ end
52
67
 
53
- if @expected_return
54
- "expected #{func_name}(#{func_params}) to have returned #{@expected_return.inspect} instead of #{@func.call.inspect}"
55
- elsif @expected_error
56
- "expected #{func_name}(#{func_params}) to have raised #{@expected_error.inspect}"
57
- else
58
- "expected #{func_name}(#{func_params}) to have run successfully"
59
- end
68
+ def failure_message_for_should_not(func_obj)
69
+ failure_message_generic(:should_not, func_obj)
60
70
  end
61
71
 
62
- failure_message_for_should_not do |func_obj|
72
+ private
73
+ def failure_message_generic(type, func_obj)
63
74
  func_name = func_obj.name.gsub(/^function_/, '')
64
75
  func_params = @params.inspect[1..-2]
65
76
 
77
+ message = "expected #{func_name}(#{func_params}) to "
78
+ message << "not " if type == :should_not
79
+
66
80
  if @expected_return
67
- "expected #{func_name}(#{func_params}) to not have returned #{@expected_return.inspect}"
81
+ message << "have returned #{@expected_return.inspect}"
82
+ if type == :should
83
+ message << " instead of #{@actual_return.inspect}"
84
+ end
68
85
  elsif @expected_error
69
- "expected #{func_name}(#{func_params}) to not have raised #{@expected_error.inspect}"
86
+ message << "have raised #{@expected_error.inspect}"
70
87
  else
71
- "expected #{func_name}(#{func_params}) to not have run successfully"
88
+ message << "have run successfully"
72
89
  end
90
+ message
73
91
  end
74
92
  end
75
93
  end
@@ -43,17 +43,25 @@ module RSpec::Puppet
43
43
 
44
44
  protected
45
45
  def self.get_module_name
46
- p = Puppet::Parser::Lexer.new
47
46
  module_name = nil
48
47
  Dir["manifests/*.pp"].entries.each do |manifest|
49
- p.string = File.read(manifest)
50
- tokens = p.fullscan
51
- i = tokens.index { |token| [:CLASS, :DEFINE].include? token.first }
52
- unless i.nil?
53
- module_name = tokens[i + 1].last[:value].split('::').first
54
- break
55
- end
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
56
63
  end
64
+
57
65
  module_name
58
66
  end
59
67
 
@@ -83,6 +91,20 @@ module RSpec::Puppet
83
91
  end
84
92
  end
85
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
+
86
108
  def self.safe_create_spec_helper
87
109
  content = <<-EOF
88
110
  require 'rspec-puppet'
@@ -94,17 +116,7 @@ RSpec.configure do |c|
94
116
  c.manifest_dir = File.join(fixture_path, 'manifests')
95
117
  end
96
118
  EOF
97
- if File.exists? 'spec/spec_helper.rb'
98
- old_content = File.read('spec/spec_helper.rb')
99
- if old_content != content
100
- $stderr.puts "!! spec/spec_helper.rb already exists and differs from template"
101
- end
102
- else
103
- File.open('spec/spec_helper.rb', 'w') do |f|
104
- f.puts content
105
- end
106
- puts ' + spec/spec_helper.rb'
107
- end
119
+ safe_create_file('spec/spec_helper.rb', content)
108
120
  end
109
121
 
110
122
  def self.safe_make_symlink(source, target)
@@ -119,26 +131,37 @@ EOF
119
131
  end
120
132
 
121
133
  def self.safe_create_rakefile
122
- content = <<-EOF
134
+ content = <<-'EOF'
123
135
  require 'rake'
124
-
125
136
  require 'rspec/core/rake_task'
126
137
 
127
- RSpec::Core::RakeTask.new(:spec) do |t|
128
- t.pattern = 'spec/*/*_spec.rb'
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
129
162
  end
130
163
  EOF
131
- if File.exists? 'Rakefile'
132
- old_content = File.read('Rakefile')
133
- if old_content != content
134
- $stderr.puts "!! Rakefile already exists and differs from template"
135
- end
136
- else
137
- File.open('Rakefile', 'w') do |f|
138
- f.puts content
139
- end
140
- puts ' + Rakefile'
141
- end
164
+ safe_create_file('Rakefile', content)
142
165
  end
143
166
  end
144
167
  end
@@ -3,15 +3,132 @@ module RSpec::Puppet
3
3
 
4
4
  @@cache = {}
5
5
 
6
- protected
7
- def build_catalog_without_cache(nodename, facts_val, code)
8
- if Integer(Puppet.version.split('.').first) >= 3
9
- Puppet.initialize_settings
6
+ def catalogue(type)
7
+ vardir = setup_puppet
8
+
9
+ code = import_str + 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 import_str
18
+ klass_name = self.class.top_level_description.downcase
19
+
20
+ if File.exists?(File.join(Puppet[:modulepath], 'manifests', 'init.pp'))
21
+ path_to_manifest = File.join([
22
+ Puppet[:modulepath],
23
+ 'manifests',
24
+ klass_name.split('::')[1..-1]
25
+ ].flatten)
26
+ import_str = [
27
+ "import '#{Puppet[:modulepath]}/manifests/init.pp'",
28
+ "import '#{path_to_manifest}.pp'",
29
+ '',
30
+ ].join("\n")
31
+ elsif File.exists?(Puppet[:modulepath])
32
+ import_str = "import '#{Puppet[:manifest]}'\n"
33
+ else
34
+ import_str = ""
35
+ end
36
+
37
+ import_str
38
+ end
39
+
40
+ def test_manifest(type)
41
+ klass_name = self.class.top_level_description.downcase
42
+
43
+ if type == :class
44
+ if !self.respond_to?(:params) || params == {}
45
+ "include #{klass_name}"
46
+ else
47
+ "class { '#{klass_name}': #{param_str} }"
48
+ end
49
+ elsif type == :define
50
+ if self.respond_to? :params
51
+ "#{klass_name} { '#{title}': #{param_str} }"
52
+ else
53
+ "#{klass_name} { '#{title}': }"
54
+ end
55
+ elsif type == :host
56
+ ""
57
+ end
58
+ end
59
+
60
+ def nodename(type)
61
+ if [:class, :define, :function].include? type
62
+ self.respond_to?(:node) ? node : Puppet[:certname]
63
+ else
64
+ self.class.top_level_description.downcase
65
+ end
66
+ end
67
+
68
+
69
+ def pre_cond
70
+ if self.respond_to?(:pre_condition) && !pre_condition.nil?
71
+ if pre_condition.is_a? Array
72
+ pre_condition.join("\n")
73
+ else
74
+ pre_condition
75
+ end
76
+ else
77
+ ''
78
+ end
79
+ end
80
+
81
+ def facts_hash(node)
82
+ facts_val = {
83
+ 'hostname' => node.split('.').first,
84
+ 'fqdn' => node,
85
+ 'domain' => node.split('.', 2).last,
86
+ }
87
+
88
+ if RSpec.configuration.default_facts.any?
89
+ facts_val.merge!(munge_facts(RSpec.configuration.default_facts))
90
+ end
91
+
92
+ facts_val.merge!(munge_facts(facts)) if self.respond_to?(:facts)
93
+ facts_val
94
+ end
95
+
96
+ def param_str
97
+ params.keys.map do |r|
98
+ param_val = escape_special_chars(params[r].inspect)
99
+ "#{r.to_s} => #{param_val}"
100
+ end.join(', ')
101
+ end
102
+
103
+ def setup_puppet
104
+ vardir = Dir.mktmpdir
105
+ Puppet[:vardir] = vardir
106
+
107
+ [
108
+ [:modulepath, :module_path],
109
+ [:manifestdir, :manifest_dir],
110
+ [:manifest, :manifest],
111
+ [:templatedir, :template_dir],
112
+ [:config, :config],
113
+ [:confdir, :confdir],
114
+ [:hiera_config, :hiera_config],
115
+ ].each do |a, b|
116
+ value = self.respond_to?(b) ? self.send(b) : RSpec.configuration.send(b)
117
+ begin
118
+ Puppet[a] = value
119
+ rescue ArgumentError
120
+ Puppet.settings.setdefaults(:main, {a => {:default => value, :desc => a.to_s}})
121
+ end
10
122
  end
11
123
 
124
+ Puppet[:libdir] = Dir["#{Puppet[:modulepath]}/*/lib"].entries.join(File::PATH_SEPARATOR)
125
+ vardir
126
+ end
127
+
128
+ def build_catalog_without_cache(nodename, facts_val, code)
12
129
  Puppet[:code] = code
13
130
 
14
- facts_val.each { |k, v| Facter.add(k) { setcode { v } } }
131
+ stub_facts! facts_val
15
132
 
16
133
  node_obj = Puppet::Node.new(nodename)
17
134
 
@@ -25,8 +142,11 @@ module RSpec::Puppet
25
142
  end
26
143
  end
27
144
 
28
- public
29
- def build_catalog *args
145
+ def stub_facts!(facts)
146
+ facts.each { |k, v| Facter.add(k) { setcode { v } } }
147
+ end
148
+
149
+ def build_catalog(*args)
30
150
  @@cache[args] ||= self.build_catalog_without_cache(*args)
31
151
  end
32
152
 
@@ -35,5 +155,32 @@ module RSpec::Puppet
35
155
  facts.keys.each { |key| output[key.to_s] = facts[key] }
36
156
  output
37
157
  end
158
+
159
+ def escape_special_chars(string)
160
+ string.gsub!(/\$/, "\\$")
161
+ string
162
+ end
163
+
164
+ def scope(compiler, node_name)
165
+ if Puppet.version =~ /^2\.[67]/
166
+ # loadall should only be necessary prior to 3.x
167
+ # Please note, loadall needs to happen first when creating a scope, otherwise
168
+ # you might receive undefined method `function_*' errors
169
+ Puppet::Parser::Functions.autoloader.loadall
170
+ scope = Puppet::Parser::Scope.new(:compiler => compiler)
171
+ else
172
+ scope = Puppet::Parser::Scope.new(compiler)
173
+ end
174
+
175
+ scope.source = Puppet::Resource::Type.new(:node, node_name)
176
+ scope.parent = compiler.topscope
177
+ scope
178
+ end
179
+
180
+ def build_node(name, opts = {})
181
+ node_environment = Puppet::Node::Environment.new('test')
182
+ opts.merge!({:environment => node_environment})
183
+ Puppet::Node.new(name, opts)
184
+ end
38
185
  end
39
186
  end