rspec-puppet 0.1.6 → 1.0.0

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