chefspec 2.0.1 → 3.0.0.beta.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (110) hide show
  1. checksums.yaml +4 -4
  2. data/lib/chefspec.rb +30 -44
  3. data/lib/chefspec/api.rb +74 -0
  4. data/lib/chefspec/api/apt_package.rb +192 -0
  5. data/lib/chefspec/api/batch.rb +43 -0
  6. data/lib/chefspec/api/chef_gem.rb +191 -0
  7. data/lib/chefspec/api/cookbook_file.rb +166 -0
  8. data/lib/chefspec/api/cron.rb +80 -0
  9. data/lib/chefspec/api/deploy.rb +117 -0
  10. data/lib/chefspec/api/directory.rb +80 -0
  11. data/lib/chefspec/api/dpkg_package.rb +117 -0
  12. data/lib/chefspec/api/easy_install_package.rb +154 -0
  13. data/lib/chefspec/api/env.rb +117 -0
  14. data/lib/chefspec/api/erl_call.rb +43 -0
  15. data/lib/chefspec/api/execute.rb +43 -0
  16. data/lib/chefspec/api/file.rb +166 -0
  17. data/lib/chefspec/api/freebsd_package.rb +80 -0
  18. data/lib/chefspec/api/gem_package.rb +191 -0
  19. data/lib/chefspec/api/git.rb +117 -0
  20. data/lib/chefspec/api/group.rb +154 -0
  21. data/lib/chefspec/api/http_request.rb +228 -0
  22. data/lib/chefspec/api/ifconfig.rb +154 -0
  23. data/lib/chefspec/api/include_recipe.rb +26 -0
  24. data/lib/chefspec/api/ips_package.rb +117 -0
  25. data/lib/chefspec/api/link.rb +102 -0
  26. data/lib/chefspec/api/log.rb +43 -0
  27. data/lib/chefspec/api/macports_package.rb +154 -0
  28. data/lib/chefspec/api/mdadm.rb +117 -0
  29. data/lib/chefspec/api/mount.rb +192 -0
  30. data/lib/chefspec/api/notifications.rb +38 -0
  31. data/lib/chefspec/api/ohai.rb +43 -0
  32. data/lib/chefspec/api/package.rb +192 -0
  33. data/lib/chefspec/api/pacman_package.rb +155 -0
  34. data/lib/chefspec/api/portage_package.rb +155 -0
  35. data/lib/chefspec/api/powershell_script.rb +43 -0
  36. data/lib/chefspec/api/registry_key.rb +166 -0
  37. data/lib/chefspec/api/remote_directory.rb +120 -0
  38. data/lib/chefspec/api/remote_file.rb +166 -0
  39. data/lib/chefspec/api/render_file.rb +32 -0
  40. data/lib/chefspec/api/route.rb +80 -0
  41. data/lib/chefspec/api/rpm_package.rb +117 -0
  42. data/lib/chefspec/api/ruby_block.rb +37 -0
  43. data/lib/chefspec/api/script.rb +228 -0
  44. data/lib/chefspec/api/service.rb +246 -0
  45. data/lib/chefspec/api/smartos_package.rb +117 -0
  46. data/lib/chefspec/api/solaris_package.rb +80 -0
  47. data/lib/chefspec/api/subversion.rb +154 -0
  48. data/lib/chefspec/api/template.rb +166 -0
  49. data/lib/chefspec/api/user.rb +228 -0
  50. data/lib/chefspec/api/yum_package.rb +154 -0
  51. data/lib/chefspec/berkshelf.rb +37 -0
  52. data/lib/chefspec/deprecations.rb +151 -0
  53. data/lib/chefspec/errors.rb +99 -0
  54. data/lib/chefspec/expect_exception.rb +45 -0
  55. data/lib/chefspec/extensions/chef/client.rb +15 -0
  56. data/lib/chefspec/extensions/chef/conditional.rb +11 -0
  57. data/lib/chefspec/extensions/chef/data_query.rb +29 -0
  58. data/lib/chefspec/extensions/chef/lwrp_base.rb +44 -0
  59. data/lib/chefspec/extensions/chef/resource.rb +27 -0
  60. data/lib/chefspec/extensions/chef/securable.rb +19 -0
  61. data/lib/chefspec/formatter.rb +270 -0
  62. data/lib/chefspec/macros.rb +217 -0
  63. data/lib/chefspec/matchers.rb +9 -0
  64. data/lib/chefspec/matchers/include_recipe_matcher.rb +45 -0
  65. data/lib/chefspec/matchers/link_to_matcher.rb +28 -0
  66. data/lib/chefspec/matchers/notifications_matcher.rb +92 -0
  67. data/lib/chefspec/matchers/render_file_matcher.rb +72 -0
  68. data/lib/chefspec/matchers/resource_matcher.rb +143 -0
  69. data/lib/chefspec/renderer.rb +137 -0
  70. data/lib/chefspec/rspec.rb +17 -0
  71. data/lib/chefspec/runner.rb +274 -0
  72. data/lib/chefspec/stubs/command_registry.rb +11 -0
  73. data/lib/chefspec/stubs/command_stub.rb +37 -0
  74. data/lib/chefspec/stubs/data_bag_item_registry.rb +13 -0
  75. data/lib/chefspec/stubs/data_bag_item_stub.rb +25 -0
  76. data/lib/chefspec/stubs/data_bag_registry.rb +13 -0
  77. data/lib/chefspec/stubs/data_bag_stub.rb +23 -0
  78. data/lib/chefspec/stubs/registry.rb +32 -0
  79. data/lib/chefspec/stubs/search_registry.rb +13 -0
  80. data/lib/chefspec/stubs/search_stub.rb +25 -0
  81. data/lib/chefspec/stubs/stub.rb +37 -0
  82. data/lib/chefspec/version.rb +1 -2
  83. metadata +100 -103
  84. data/lib/chef/expect_exception.rb +0 -34
  85. data/lib/chef/formatters/chefspec.rb +0 -233
  86. data/lib/chef/knife/cookbook_create_specs.rb +0 -107
  87. data/lib/chefspec/chef_runner.rb +0 -275
  88. data/lib/chefspec/helpers/describe.rb +0 -17
  89. data/lib/chefspec/matchers/cron.rb +0 -7
  90. data/lib/chefspec/matchers/env.rb +0 -8
  91. data/lib/chefspec/matchers/execute.rb +0 -33
  92. data/lib/chefspec/matchers/file.rb +0 -83
  93. data/lib/chefspec/matchers/file_content.rb +0 -32
  94. data/lib/chefspec/matchers/group.rb +0 -8
  95. data/lib/chefspec/matchers/include_recipe.rb +0 -20
  96. data/lib/chefspec/matchers/link.rb +0 -14
  97. data/lib/chefspec/matchers/log.rb +0 -21
  98. data/lib/chefspec/matchers/notifications.rb +0 -43
  99. data/lib/chefspec/matchers/package.rb +0 -39
  100. data/lib/chefspec/matchers/python.rb +0 -7
  101. data/lib/chefspec/matchers/ruby_block.rb +0 -13
  102. data/lib/chefspec/matchers/script.rb +0 -34
  103. data/lib/chefspec/matchers/service.rb +0 -25
  104. data/lib/chefspec/matchers/shared.rb +0 -132
  105. data/lib/chefspec/matchers/user.rb +0 -8
  106. data/lib/chefspec/minitest.rb +0 -195
  107. data/lib/chefspec/monkey_patches/conditional.rb +0 -19
  108. data/lib/chefspec/monkey_patches/hash.rb +0 -23
  109. data/lib/chefspec/monkey_patches/lwrp_base.rb +0 -45
  110. data/lib/chefspec/monkey_patches/provider.rb +0 -43
@@ -0,0 +1,9 @@
1
+ module ChefSpec
2
+ module Matchers
3
+ require_relative 'matchers/include_recipe_matcher'
4
+ require_relative 'matchers/link_to_matcher'
5
+ require_relative 'matchers/notifications_matcher'
6
+ require_relative 'matchers/render_file_matcher'
7
+ require_relative 'matchers/resource_matcher'
8
+ end
9
+ end
@@ -0,0 +1,45 @@
1
+ module ChefSpec::Matchers
2
+ class IncludeRecipeMatcher
3
+ def initialize(recipe_name)
4
+ @recipe_name = with_default(recipe_name)
5
+ end
6
+
7
+ def matches?(runner)
8
+ @runner = runner
9
+ loaded_recipes.include?(@recipe_name)
10
+ end
11
+
12
+ def description
13
+ "include recipe '#{@recipe_name}'"
14
+ end
15
+
16
+ def failure_message_for_should
17
+ "expected #{loaded_recipes} to include '#{@recipe_name}'"
18
+ end
19
+
20
+ def failure_message_for_should_not
21
+ "expected '#{@recipe_name}' to not be included"
22
+ end
23
+
24
+ private
25
+ #
26
+ # Automatically appends "+::default+" to recipes that need them.
27
+ #
28
+ # @param [String] name
29
+ #
30
+ # @return [String]
31
+ #
32
+ def with_default(name)
33
+ name.include?('::') ? name : "#{name}::default"
34
+ end
35
+
36
+ #
37
+ # The list of loaded recipes on the Chef run (normalized)
38
+ #
39
+ # @return [Array<String>]
40
+ #
41
+ def loaded_recipes
42
+ @runner.run_context.loaded_recipes.map { |name| with_default(name) }
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,28 @@
1
+ module ChefSpec::Matchers
2
+ class LinkToMatcher
3
+ def initialize(path)
4
+ @path = path
5
+ end
6
+
7
+ def matches?(link)
8
+ @link = link
9
+ @link.is_a?(Chef::Resource::Link) && @path === @link.to
10
+ end
11
+
12
+ def description
13
+ "link to '#{@path}'"
14
+ end
15
+
16
+ def failure_message_for_should
17
+ if @link.nil?
18
+ "expected 'link[#{@path}]' with action ':create' to be in Chef run"
19
+ else
20
+ "expected '#{@link}' to link to '#{@path}' but was '#{@link.to}'"
21
+ end
22
+ end
23
+
24
+ def failure_message_for_should_not
25
+ "expected '#{@link}' to not link to '#{@path}'"
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,92 @@
1
+ module ChefSpec::Matchers
2
+ class NotificationsMatcher
3
+ def initialize(signature)
4
+ signature.match(/^([^\[]*)\[(.*)\]$/)
5
+ @expected_resource_type = $1
6
+ @expected_resource_name = $2
7
+ end
8
+
9
+ def matches?(resource)
10
+ @resource = resource
11
+
12
+ block = Proc.new do |notified|
13
+ notified.resource.resource_name.to_s == @expected_resource_type &&
14
+ (@expected_resource_name === notified.resource.identity.to_s || @expected_resource_name === notified.resource.name.to_s) &&
15
+ matches_action?(notified)
16
+ end
17
+
18
+ if @immediately
19
+ immediate_notifications.any?(&block)
20
+ elsif @delayed
21
+ delayed_notifications.any?(&block)
22
+ else
23
+ all_notifications.any?(&block)
24
+ end
25
+ end
26
+
27
+ def to(action)
28
+ @action = action.to_sym
29
+ self
30
+ end
31
+
32
+ def immediately
33
+ @immediately = true
34
+ self
35
+ end
36
+
37
+ def delayed
38
+ @delayed = true
39
+ self
40
+ end
41
+
42
+ def description
43
+ message = "notify #{@expected_resource_type}[#{@expected_resource_name}]"
44
+ message << " with action #{@action.inspect}" if @action
45
+ message << " immediately" if @immediately
46
+ message << " delayed" if @delayed
47
+ message
48
+ end
49
+
50
+ def failure_message_for_should
51
+ message = "expected '#{@resource.resource_name}[#{@resource.name}]' to notify '#{@expected_resource_type}[#{@expected_resource_name}]'"
52
+ message << " with action #{@action.inspect}" if @action
53
+ message << " immediately" if @immediately
54
+ message << " delayed" if @delayed
55
+ message << ", but did not."
56
+ message << "\n\n"
57
+ message << "Other notifications were:\n#{format_notifications}"
58
+ message
59
+ end
60
+
61
+ private
62
+ def all_notifications
63
+ immediate_notifications + delayed_notifications
64
+ end
65
+
66
+ def immediate_notifications
67
+ @resource.immediate_notifications
68
+ end
69
+
70
+ def delayed_notifications
71
+ @resource.delayed_notifications
72
+ end
73
+
74
+ def matches_action?(notification)
75
+ return true if @action.nil?
76
+ @action == notification.action.to_sym
77
+ end
78
+
79
+ def format_notification(notification)
80
+ resource = notification.resource
81
+ type = notification.notifying_resource.immediate_notifications.include?(notification) ? :immediately : :delayed
82
+
83
+ "notifies :#{notification.action}, '#{resource.resource_name}[#{resource.name}]', :#{type}"
84
+ end
85
+
86
+ def format_notifications
87
+ all_notifications.map do |notification|
88
+ ' ' + format_notification(notification)
89
+ end.join("\n")
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,72 @@
1
+ module ChefSpec::Matchers
2
+ class RenderFileMatcher
3
+ def initialize(path)
4
+ @path = path
5
+ end
6
+
7
+ def matches?(runner)
8
+ @runner = runner
9
+ resource && has_create_action? && matches_content?
10
+ end
11
+
12
+ def with_content(expected_content)
13
+ @expected_content = expected_content
14
+ self
15
+ end
16
+
17
+ def description
18
+ "render file '#{@path}'"
19
+ end
20
+
21
+ def failure_message_for_should
22
+ message = "expected Chef run to render '#{@path}'"
23
+ message << " with:\n\n#{@expected_content}\n\nbut got:\n\n#{@actual_content}" if @expected_content
24
+ message
25
+ end
26
+
27
+ def failure_message_for_should_not
28
+ message = "expected file '#{@path}'"
29
+ message << " with:\n\n#{@expected_content}\n\n" if @expected_content
30
+ message << " to not be in Chef run"
31
+ end
32
+
33
+ private
34
+ def resource
35
+ @resource ||= @runner.find_resource(:cookbook_file, @path) ||
36
+ @runner.find_resource(:file, @path) ||
37
+ @runner.find_resource(:template, @path)
38
+ end
39
+
40
+ #
41
+ # Determines if the given resource has a create-like action.
42
+ #
43
+ # @param [Chef::Resource] resource
44
+ #
45
+ # @return [Boolean]
46
+ #
47
+ def has_create_action?
48
+ !([:create, :create_if_missing] & Array(resource.action).map(&:to_sym)).empty?
49
+ end
50
+
51
+ #
52
+ # Determines if the resources content matches the expected content.
53
+ #
54
+ # @param [Chef::Resource] resource
55
+ #
56
+ # @return [Boolean]
57
+ #
58
+ def matches_content?
59
+ return true if @expected_content.nil?
60
+
61
+ @actual_content = ChefSpec::Renderer.new(@runner, resource).content
62
+
63
+ return false if @actual_content.nil?
64
+
65
+ if @expected_content.is_a?(Regexp)
66
+ @actual_content =~ @expected_content
67
+ else
68
+ @actual_content.include?(@expected_content)
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,143 @@
1
+ module ChefSpec::Matchers
2
+ class ResourceMatcher
3
+ def initialize(resource_name, expected_action, expected_identity)
4
+ @resource_name = resource_name
5
+ @expected_action = expected_action
6
+ @expected_identity = expected_identity
7
+ end
8
+
9
+ def with(parameters = {})
10
+ params.merge!(parameters)
11
+ self
12
+ end
13
+
14
+ #
15
+ # Allow users to specify fancy #with matchers.
16
+ #
17
+ def method_missing(m, *args, &block)
18
+ if m.to_s =~ /^with_(.+)$/
19
+ with($1.to_sym => args.first)
20
+ self
21
+ else
22
+ super
23
+ end
24
+ end
25
+
26
+ def matches?(runner)
27
+ @runner = runner
28
+
29
+ if resource
30
+ resource_actions.include?(@expected_action) && unmatched_parameters.empty?
31
+ else
32
+ false
33
+ end
34
+ end
35
+
36
+ def failure_message_for_should
37
+ if resource
38
+ if resource_actions.include?(@expected_action)
39
+ "expected '#{resource.to_s}' to have parameters:" \
40
+ "\n\n" \
41
+ " " + unmatched_parameters.collect { |parameter, h|
42
+ "#{parameter} #{h[:expected].inspect}, was #{h[:actual].inspect}"
43
+ }.join("\n ")
44
+
45
+ else
46
+ "expected '#{resource.to_s}' actions #{resource_actions.inspect}" \
47
+ " to include ':#{@expected_action}'"
48
+ end
49
+ else
50
+ "expected '#{@resource_name}[#{@expected_identity}]' with" \
51
+ " action ':#{@expected_action}' to be in Chef run. Other" \
52
+ " #{@resource_name} resources:" \
53
+ "\n\n" \
54
+ " " + similar_resources.map(&:to_s).join("\n ")
55
+ end
56
+ end
57
+
58
+ def failure_message_for_should_not
59
+ if resource
60
+ "expected '#{resource.to_s}' actions #{resource_actions.inspect} to not exist"
61
+ else
62
+ "expected '#{resource.to_s}' to not exist"
63
+ end
64
+ end
65
+
66
+ def description
67
+ "#{@expected_action} #{@resource_name}"
68
+ end
69
+
70
+ private
71
+ def unmatched_parameters
72
+ return @_unmatched_parameters if @_unmatched_parameters
73
+
74
+ @_unmatched_parameters = {}
75
+
76
+ params.each do |parameter, expected|
77
+ unless matches_parameter?(parameter, expected)
78
+ @_unmatched_parameters[parameter] = {
79
+ expected: expected,
80
+ actual: safe_send(parameter),
81
+ }
82
+ end
83
+ end
84
+
85
+ @_unmatched_parameters
86
+ end
87
+
88
+ def matches_parameter?(parameter, expected)
89
+ # Chef 11+ stores the source parameter internally as an Array
90
+ if parameter == :source
91
+ Array(expected) == Array(safe_send(parameter))
92
+ else
93
+ expected === safe_send(parameter)
94
+ end
95
+ end
96
+
97
+ def safe_send(parameter)
98
+ resource.send(parameter)
99
+ rescue NoMethodError
100
+ nil
101
+ end
102
+
103
+ #
104
+ # Any other resources in the Chef run that have the same resource
105
+ # type. Used by {failure_message} to be ultra helpful.
106
+ #
107
+ # @return [Array<Chef::Resource>]
108
+ #
109
+ def similar_resources
110
+ @_similar_resources ||= @runner.find_resources(@resource_name)
111
+ end
112
+
113
+ #
114
+ # Find the resource in the Chef run by the given class name and
115
+ # resource identity/name.
116
+ #
117
+ # @see ChefSpec::Runner#find_resource
118
+ #
119
+ # @return [Chef::Resource, nil]
120
+ #
121
+ def resource
122
+ @_resource ||= @runner.find_resource(@resource_name, @expected_identity)
123
+ end
124
+
125
+ #
126
+ # The list of actions on this resource.
127
+ #
128
+ # @return [Array<Symbol>]
129
+ #
130
+ def resource_actions
131
+ @_resource_actions ||= Array(resource.action).map(&:to_sym)
132
+ end
133
+
134
+ #
135
+ # The list of parameters passed to the {with} matcher.
136
+ #
137
+ # @return [Hash]
138
+ #
139
+ def params
140
+ @_params ||= {}
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,137 @@
1
+ begin
2
+ require 'chef/mixin/template'
3
+ require 'chef/provider/template_finder'
4
+ rescue LoadError
5
+ end
6
+
7
+ module ChefSpec
8
+ class Renderer
9
+ # @return [Chef::Runner]
10
+ attr_reader :chef_run
11
+
12
+ # @return [Chef::Resource]
13
+ attr_reader :resource
14
+
15
+ #
16
+ # Create a new Renderer for the given Chef run and resource.
17
+ #
18
+ # @param [Chef::Runner] chef_run
19
+ # the Chef run containing the resources
20
+ # @param [Chef::Resource] resource
21
+ # the resource to render content from
22
+ #
23
+ def initialize(chef_run, resource)
24
+ @chef_run = chef_run
25
+ @resource = resource
26
+ end
27
+
28
+ #
29
+ # The content of the resource (this method delegates to the)
30
+ # various private rendering methods.
31
+ #
32
+ # @return [String, nil]
33
+ # the contents of the file as a string, or nil if the resource
34
+ # does not contain or respond to a content renderer.
35
+ #
36
+ def content
37
+ case resource.resource_name.to_s
38
+ when 'template'
39
+ content_from_template(chef_run, resource)
40
+ when 'file'
41
+ content_from_file(chef_run, resource)
42
+ when 'cookbook_file'
43
+ content_from_cookbook_file(chef_run, resource)
44
+ else
45
+ nil
46
+ end
47
+ end
48
+
49
+ private
50
+ #
51
+ # Compute the contents of a template using Chef's templating logic.
52
+ #
53
+ # @param [Chef::RunContext] chef_run
54
+ # the run context for the node
55
+ # @param [Chef::Provider::Template] template
56
+ # the template resource
57
+ #
58
+ # @return [String]
59
+ #
60
+ def content_from_template(chef_run, template)
61
+ cookbook_name = template.cookbook || template.cookbook_name
62
+ template_location = cookbook_collection(chef_run.node)[cookbook_name].preferred_filename_on_disk_location(chef_run.node, :templates, template.source)
63
+
64
+ if Chef::Mixin::Template.const_defined?(:TemplateContext) # Chef 11+
65
+ template_context = Chef::Mixin::Template::TemplateContext.new([])
66
+ template_context.update({
67
+ :node => chef_run.node,
68
+ :template_finder => template_finder(chef_run, cookbook_name),
69
+ }.merge(template.variables))
70
+ template_context.render_template(template_location)
71
+ else
72
+ template.provider.new(template, chef_run.run_context).send(:render_with_context, template_location) do |file|
73
+ File.read(file.path)
74
+ end
75
+ end
76
+ end
77
+
78
+ #
79
+ # Get the contents of a file resource.
80
+ #
81
+ # @param [Chef::RunContext] chef_run
82
+ # the run context for the node
83
+ # @param [Chef::Provider::File] file
84
+ # the file resource
85
+ #
86
+ # @return [String]
87
+ #
88
+ def content_from_file(chef_run, file)
89
+ file.content
90
+ end
91
+
92
+ #
93
+ # Get the contents of a cookbook file using Chef.
94
+ #
95
+ # @param [Chef::RunContext] chef_run
96
+ # the run context for the node
97
+ # @param [Chef::Provider::CookbookFile] cookbook_file
98
+ # the file resource
99
+ #
100
+ def content_from_cookbook_file(chef_run, cookbook_file)
101
+ cookbook_name = cookbook_file.cookbook || cookbook_file.cookbook_name
102
+ cookbook = cookbook_collection(chef_run.node)[cookbook_name]
103
+ File.read(cookbook.preferred_filename_on_disk_location(chef_run.node, :files, cookbook_file.source, cookbook_file.path))
104
+ end
105
+
106
+ # The cookbook collection for the current Chef run context. Handles
107
+ # the differing cases between Chef 10 and Chef 11.
108
+ #
109
+ # @param [Chef::Node] node
110
+ # the Chef node to get the cookbook collection from
111
+ #
112
+ # @return [Array<Chef::Cookbook>]
113
+ def cookbook_collection(node)
114
+ if node.respond_to?(:run_context)
115
+ node.run_context.cookbook_collection # Chef 11+
116
+ else
117
+ node.cookbook_collection # Chef 10
118
+ end
119
+ end
120
+
121
+ # Return a new instance of the TemplateFinder if we are running on Chef 11.
122
+ #
123
+ # @param [Chef::RunContext] chef_run
124
+ # the run context for the noe
125
+ # @param [String] cookbook_name
126
+ # the name of the cookbook
127
+ #
128
+ # @return [Chef::Provider::TemplateFinder, nil]
129
+ def template_finder(chef_run, cookbook_name)
130
+ if Chef::Provider.const_defined?(:TemplateFinder) # Chef 11+
131
+ Chef::Provider::TemplateFinder.new(chef_run.run_context, cookbook_name, chef_run.node)
132
+ else
133
+ nil
134
+ end
135
+ end
136
+ end
137
+ end