chefspec 2.0.1 → 3.0.0.beta.1

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