chefspec-chef 9.3.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (120) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +30 -0
  3. data/LICENSE +22 -0
  4. data/Rakefile +85 -0
  5. data/chefspec-chef.gemspec +30 -0
  6. data/lib/chefspec/api/core.rb +217 -0
  7. data/lib/chefspec/api/described.rb +53 -0
  8. data/lib/chefspec/api/do_nothing.rb +26 -0
  9. data/lib/chefspec/api/include_any_recipe.rb +24 -0
  10. data/lib/chefspec/api/include_recipe.rb +28 -0
  11. data/lib/chefspec/api/link.rb +28 -0
  12. data/lib/chefspec/api/notifications.rb +40 -0
  13. data/lib/chefspec/api/reboot.rb +14 -0
  14. data/lib/chefspec/api/render_file.rb +37 -0
  15. data/lib/chefspec/api/state_attrs.rb +30 -0
  16. data/lib/chefspec/api/stubs.rb +183 -0
  17. data/lib/chefspec/api/stubs_for.rb +139 -0
  18. data/lib/chefspec/api/subscriptions.rb +37 -0
  19. data/lib/chefspec/api/user.rb +230 -0
  20. data/lib/chefspec/api.rb +39 -0
  21. data/lib/chefspec/berkshelf.rb +63 -0
  22. data/lib/chefspec/cacher.rb +64 -0
  23. data/lib/chefspec/coverage/filters.rb +82 -0
  24. data/lib/chefspec/coverage.rb +247 -0
  25. data/lib/chefspec/deprecations.rb +46 -0
  26. data/lib/chefspec/errors.rb +48 -0
  27. data/lib/chefspec/expect_exception.rb +51 -0
  28. data/lib/chefspec/extensions/chef/client.rb +21 -0
  29. data/lib/chefspec/extensions/chef/conditional.rb +16 -0
  30. data/lib/chefspec/extensions/chef/cookbook/gem_installer.rb +33 -0
  31. data/lib/chefspec/extensions/chef/cookbook_loader.rb +14 -0
  32. data/lib/chefspec/extensions/chef/cookbook_uploader.rb +12 -0
  33. data/lib/chefspec/extensions/chef/data_query.rb +49 -0
  34. data/lib/chefspec/extensions/chef/lwrp_base.rb +29 -0
  35. data/lib/chefspec/extensions/chef/provider.rb +39 -0
  36. data/lib/chefspec/extensions/chef/resource/freebsd_package.rb +17 -0
  37. data/lib/chefspec/extensions/chef/resource.rb +188 -0
  38. data/lib/chefspec/extensions/chef/run_context/cookbook_compiler.rb +84 -0
  39. data/lib/chefspec/extensions/chef/securable.rb +19 -0
  40. data/lib/chefspec/extensions/ohai/system.rb +11 -0
  41. data/lib/chefspec/extensions.rb +21 -0
  42. data/lib/chefspec/file_cache_path_proxy.rb +15 -0
  43. data/lib/chefspec/formatter.rb +282 -0
  44. data/lib/chefspec/librarian.rb +51 -0
  45. data/lib/chefspec/matchers/do_nothing_matcher.rb +52 -0
  46. data/lib/chefspec/matchers/include_any_recipe_matcher.rb +51 -0
  47. data/lib/chefspec/matchers/include_recipe_matcher.rb +46 -0
  48. data/lib/chefspec/matchers/link_to_matcher.rb +37 -0
  49. data/lib/chefspec/matchers/notifications_matcher.rb +143 -0
  50. data/lib/chefspec/matchers/render_file_matcher.rb +140 -0
  51. data/lib/chefspec/matchers/resource_matcher.rb +175 -0
  52. data/lib/chefspec/matchers/state_attrs_matcher.rb +71 -0
  53. data/lib/chefspec/matchers/subscribes_matcher.rb +72 -0
  54. data/lib/chefspec/matchers.rb +13 -0
  55. data/lib/chefspec/mixins/normalize.rb +22 -0
  56. data/lib/chefspec/policyfile.rb +69 -0
  57. data/lib/chefspec/renderer.rb +145 -0
  58. data/lib/chefspec/rspec.rb +21 -0
  59. data/lib/chefspec/runner.rb +8 -0
  60. data/lib/chefspec/server.rb +4 -0
  61. data/lib/chefspec/server_methods.rb +173 -0
  62. data/lib/chefspec/server_runner.rb +76 -0
  63. data/lib/chefspec/solo_runner.rb +516 -0
  64. data/lib/chefspec/stubs/command_registry.rb +11 -0
  65. data/lib/chefspec/stubs/command_stub.rb +37 -0
  66. data/lib/chefspec/stubs/data_bag_item_registry.rb +13 -0
  67. data/lib/chefspec/stubs/data_bag_item_stub.rb +25 -0
  68. data/lib/chefspec/stubs/data_bag_registry.rb +13 -0
  69. data/lib/chefspec/stubs/data_bag_stub.rb +23 -0
  70. data/lib/chefspec/stubs/registry.rb +32 -0
  71. data/lib/chefspec/stubs/search_registry.rb +13 -0
  72. data/lib/chefspec/stubs/search_stub.rb +25 -0
  73. data/lib/chefspec/stubs/stub.rb +38 -0
  74. data/lib/chefspec/util.rb +58 -0
  75. data/lib/chefspec/version.rb +3 -0
  76. data/lib/chefspec/zero_server.rb +142 -0
  77. data/lib/chefspec.rb +75 -0
  78. data/spec/spec_helper.rb +12 -0
  79. data/spec/support/hash.rb +35 -0
  80. data/spec/unit/cacher_spec.rb +70 -0
  81. data/spec/unit/coverage/filters_spec.rb +60 -0
  82. data/spec/unit/deprecations_spec.rb +52 -0
  83. data/spec/unit/errors_spec.rb +57 -0
  84. data/spec/unit/expect_exception_spec.rb +32 -0
  85. data/spec/unit/macros_spec.rb +119 -0
  86. data/spec/unit/matchers/do_nothing_matcher.rb +5 -0
  87. data/spec/unit/matchers/include_any_recipe_matcher_spec.rb +52 -0
  88. data/spec/unit/matchers/include_recipe_matcher_spec.rb +38 -0
  89. data/spec/unit/matchers/link_to_matcher_spec.rb +55 -0
  90. data/spec/unit/matchers/notifications_matcher_spec.rb +39 -0
  91. data/spec/unit/matchers/render_file_matcher_spec.rb +68 -0
  92. data/spec/unit/matchers/resource_matcher_spec.rb +5 -0
  93. data/spec/unit/matchers/state_attrs_matcher_spec.rb +68 -0
  94. data/spec/unit/matchers/subscribes_matcher_spec.rb +63 -0
  95. data/spec/unit/renderer_spec.rb +69 -0
  96. data/spec/unit/server_runner_spec.rb +28 -0
  97. data/spec/unit/solo_runner_spec.rb +171 -0
  98. data/spec/unit/stubs/command_registry_spec.rb +27 -0
  99. data/spec/unit/stubs/command_stub_spec.rb +61 -0
  100. data/spec/unit/stubs/data_bag_item_registry_spec.rb +39 -0
  101. data/spec/unit/stubs/data_bag_item_stub_spec.rb +36 -0
  102. data/spec/unit/stubs/data_bag_registry_spec.rb +39 -0
  103. data/spec/unit/stubs/data_bag_stub_spec.rb +35 -0
  104. data/spec/unit/stubs/registry_spec.rb +29 -0
  105. data/spec/unit/stubs/search_registry_spec.rb +39 -0
  106. data/spec/unit/stubs/search_stub_spec.rb +36 -0
  107. data/spec/unit/stubs/stub_spec.rb +64 -0
  108. data/templates/coverage/human.erb +22 -0
  109. data/templates/coverage/json.erb +8 -0
  110. data/templates/coverage/table.erb +14 -0
  111. data/templates/errors/cookbook_path_not_found.erb +3 -0
  112. data/templates/errors/erb_template_parse_error.erb +5 -0
  113. data/templates/errors/gem_load_error.erb +7 -0
  114. data/templates/errors/invalid_berkshelf_options.erb +4 -0
  115. data/templates/errors/may_need_to_specify_platform.erb +25 -0
  116. data/templates/errors/no_conversion_error.erb +1 -0
  117. data/templates/errors/not_stubbed.erb +7 -0
  118. data/templates/errors/shell_out_not_stubbed.erb +10 -0
  119. data/templates/errors/template_not_found.erb +9 -0
  120. metadata +221 -0
@@ -0,0 +1,516 @@
1
+ require "fauxhai"
2
+ require "chef/client"
3
+ require "chef/cookbook/metadata"
4
+ require "chef/mash"
5
+ require "chef/providers"
6
+ require "chef/resources"
7
+
8
+ module ChefSpec
9
+ class SoloRunner
10
+ #
11
+ # Handy class method for just converging a runner if you do not care about
12
+ # initializing the runner with custom options.
13
+ #
14
+ # @example
15
+ # ChefSpec::SoloRunner.converge('cookbook::recipe')
16
+ #
17
+ def self.converge(*recipe_names)
18
+ new.tap do |instance|
19
+ instance.converge(*recipe_names)
20
+ end
21
+ end
22
+
23
+ include ChefSpec::Normalize
24
+
25
+ # @return [Hash]
26
+ attr_reader :options
27
+
28
+ # @return [Chef::RunContext]
29
+ attr_reader :run_context
30
+
31
+ #
32
+ # Instantiate a new SoloRunner to run examples with.
33
+ #
34
+ # @example Instantiate a new Runner
35
+ # ChefSpec::SoloRunner.new
36
+ #
37
+ # @example Specifying the platform and version
38
+ # ChefSpec::SoloRunner.new(platform: 'ubuntu', version: '18.04')
39
+ #
40
+ # @example Specifying the cookbook path
41
+ # ChefSpec::SoloRunner.new(cookbook_path: ['/cookbooks'])
42
+ #
43
+ # @example Specifying the log level
44
+ # ChefSpec::SoloRunner.new(log_level: :info)
45
+ #
46
+ #
47
+ # @param [Hash] options
48
+ # The options for the new runner
49
+ #
50
+ # @option options [Symbol] :log_level
51
+ # The log level to use (default is :warn)
52
+ # @option options [String] :platform
53
+ # The platform to load Ohai attributes from (must be present in fauxhai)
54
+ # @option options [String] :version
55
+ # The version of the platform to load Ohai attributes from (must be present in fauxhai)
56
+ # @option options [String] :path
57
+ # Path of a json file that will be passed to fauxhai as :path option
58
+ # @option options [Array<String>] :step_into
59
+ # The list of LWRPs to evaluate
60
+ # @option options String] :file_cache_path
61
+ # File caching path, if absent ChefSpec will use a temporary directory generated on the fly
62
+ #
63
+ # @yield [node] Configuration block for Chef::Node
64
+ #
65
+ def initialize(options = {})
66
+ @options = with_default_options(options)
67
+ apply_chef_config!
68
+ yield node if block_given?
69
+ end
70
+
71
+ #
72
+ # Execute the given `run_list` on the node, without actually converging
73
+ # the node. Each time {#converge} is called, the `run_list` is reset to the
74
+ # new value (it is **not** additive).
75
+ #
76
+ # @example Converging a single recipe
77
+ # chef_run.converge('example::default')
78
+ #
79
+ # @example Converging multiple recipes
80
+ # chef_run.converge('example::default', 'example::secondary')
81
+ #
82
+ #
83
+ # @param [Array] recipe_names
84
+ # The names of the recipe or recipes to converge
85
+ #
86
+ # @return [ChefSpec::SoloRunner]
87
+ # A reference to the calling Runner (for chaining purposes)
88
+ #
89
+ def converge(*recipe_names)
90
+ # Re-apply the Chef config before converging in case something else
91
+ # called Config.reset too.
92
+ apply_chef_config!
93
+ @converging = false
94
+ node.run_list.reset!
95
+ recipe_names.each { |recipe_name| node.run_list.add(recipe_name) }
96
+
97
+ return self if dry_run?
98
+
99
+ # Expand the run_list
100
+ expand_run_list!
101
+
102
+ # Merge in provided node attributes. Default and override use the role_
103
+ # levels so they win over the relevant bits from cookbooks since otherwise
104
+ # they would not and that would be confusing.
105
+ node.attributes.role_default = Chef::Mixin::DeepMerge.merge(node.attributes.role_default, options[:default_attributes]) if options[:default_attributes]
106
+ node.attributes.normal = Chef::Mixin::DeepMerge.merge(node.attributes.normal, options[:normal_attributes]) if options[:normal_attributes]
107
+ node.attributes.role_override = Chef::Mixin::DeepMerge.merge(node.attributes.role_override, options[:override_attributes]) if options[:override_attributes]
108
+ node.attributes.automatic = Chef::Mixin::DeepMerge.merge(node.attributes.automatic, options[:automatic_attributes]) if options[:automatic_attributes]
109
+
110
+ # Setup the run_context, rescuing the exception that happens when a
111
+ # resource is not defined on a particular platform
112
+ begin
113
+ @run_context = client.setup_run_context
114
+ rescue Chef::Exceptions::NoSuchResourceType => e
115
+ raise Error::MayNeedToSpecifyPlatform.new(original_error: e.message)
116
+ end
117
+
118
+ # Allow stubbing/mocking after the cookbook has been compiled but before the converge
119
+ yield node if block_given?
120
+
121
+ @converging = true
122
+ converge_val = @client.converge(@run_context)
123
+ if converge_val.is_a?(Exception)
124
+ raise converge_val
125
+ end
126
+
127
+ self
128
+ end
129
+
130
+ #
131
+ # Execute a block of recipe code.
132
+ #
133
+ # @param [Proc] block
134
+ # A block containing Chef recipe code
135
+ #
136
+ # @return [ChefSpec::SoloRunner]
137
+ #
138
+ def converge_block(&block)
139
+ converge do
140
+ recipe = Chef::Recipe.new(cookbook_name, "_test", run_context)
141
+ recipe.instance_exec(&block)
142
+ end
143
+ end
144
+
145
+ #
146
+ # Run a static preload of the cookbook under test. This will load libraries
147
+ # and resources, but not attributes or recipes.
148
+ #
149
+ # @return [void]
150
+ #
151
+ def preload!
152
+ # Flag to disable preloading for situations where it doesn't make sense.
153
+ return if ENV["CHEFSPEC_NO_PRELOAD"]
154
+
155
+ begin
156
+ old_preload = $CHEFSPEC_PRELOAD
157
+ $CHEFSPEC_PRELOAD = true
158
+ converge("recipe[#{cookbook_name}]")
159
+ node.run_list.reset!
160
+ ensure
161
+ $CHEFSPEC_PRELOAD = old_preload
162
+ end
163
+ end
164
+
165
+ #
166
+ # The +Chef::Node+ corresponding to this Runner.
167
+ #
168
+ # @return [Chef::Node]
169
+ #
170
+ def node
171
+ runner = self
172
+ @node ||= begin
173
+ apply_chef_config!
174
+ client.build_node.tap do |node|
175
+ node.define_singleton_method(:runner) { runner }
176
+ end
177
+ end
178
+ end
179
+
180
+ #
181
+ # The full collection of resources for this Runner.
182
+ #
183
+ # @return [Hash<String, Chef::Resource>]
184
+ #
185
+ def resource_collection
186
+ @resource_collection ||= @run_context.resource_collection
187
+ end
188
+
189
+ #
190
+ # Find the resource with the declared type and resource name, and optionally match a performed action.
191
+ #
192
+ # If multiples match it returns the last (which more or less matches the chef last-inserter-wins semantics)
193
+ #
194
+ # @example Find a template at `/etc/foo`
195
+ # chef_run.find_resource(:template, '/etc/foo') #=> #<Chef::Resource::Template>
196
+ #
197
+ #
198
+ # @param [Symbol] type
199
+ # The type of resource (sometimes called `resource_name`) such as `file`
200
+ # or `directory`.
201
+ # @param [String, Regexp] name
202
+ # The value of the name attribute or identity attribute for the resource.
203
+ # @param [Symbol] action
204
+ # (optional) match only resources that performed the action.
205
+ #
206
+ # @return [Chef::Resource, nil]
207
+ # The matching resource, or nil if one is not found
208
+ #
209
+ def find_resource(type, name, action = nil)
210
+ resource_collection.all_resources.reverse_each.find do |resource|
211
+ resource.declared_type == type.to_sym && (name === resource.name || name === resource.identity) && (action.nil? || resource.performed_action?(action))
212
+ end
213
+ end
214
+
215
+ #
216
+ # Find the resource with the declared type.
217
+ #
218
+ # @example Find all template resources
219
+ # chef_run.find_resources(:template) #=> [#<Chef::Resource::Template>, #...]
220
+ #
221
+ #
222
+ # @param [Symbol] type
223
+ # The type of resource such as `:file` or `:directory`.
224
+ #
225
+ # @return [Array<Chef::Resource>]
226
+ # The matching resources
227
+ #
228
+ def find_resources(type)
229
+ resource_collection.all_resources.select do |resource|
230
+ resource_name(resource) == type.to_sym
231
+ end
232
+ end
233
+
234
+ #
235
+ # Boolean method to determine the current phase of the Chef run (compiling
236
+ # or converging)
237
+ #
238
+ # @return [true, false]
239
+ #
240
+ def compiling?
241
+ !@converging
242
+ end
243
+
244
+ #
245
+ # Determines if the runner should step into the given resource. The
246
+ # +step_into+ option takes a string, but this method coerces everything
247
+ # to symbols for safety.
248
+ #
249
+ # This method also substitutes any dashes (+-+) with underscores (+_+),
250
+ # because that's what Chef does under the hood. (See GitHub issue #254
251
+ # for more background)
252
+ #
253
+ # @param [Chef::Resource] resource
254
+ # the Chef resource to try and step in to
255
+ #
256
+ # @return [true, false]
257
+ #
258
+ def step_into?(resource)
259
+ key = resource_name(resource)
260
+ Array(options[:step_into]).map(&method(:resource_name)).include?(key)
261
+ end
262
+
263
+ #
264
+ # Boolean method to determine if this Runner is in `dry_run` mode.
265
+ #
266
+ # @return [true, false]
267
+ #
268
+ def dry_run?
269
+ !!options[:dry_run]
270
+ end
271
+
272
+ #
273
+ # This runner as a string.
274
+ #
275
+ # @return [String] Currently includes the run_list. Format of the string
276
+ # may change between versions of this gem.
277
+ #
278
+ def to_s
279
+ "#<#{self.class.name} run_list: [#{node.run_list}]>"
280
+ end
281
+
282
+ #
283
+ # The runner as a String with helpful output.
284
+ #
285
+ # @return [String]
286
+ #
287
+ def inspect
288
+ "#<#{self.class.name}" \
289
+ " options: #{options.inspect}," \
290
+ " run_list: [#{node.run_list}]>"
291
+ end
292
+
293
+ #
294
+ # Respond to custom matchers defined by the user.
295
+ #
296
+ def method_missing(m, *args, &block)
297
+ block = ChefSpec.matchers[resource_name(m.to_sym)]
298
+ if block
299
+ instance_exec(args.first, &block)
300
+ else
301
+ super
302
+ end
303
+ end
304
+
305
+ #
306
+ # Inform Ruby that we respond to methods that are defined as custom
307
+ # matchers.
308
+ #
309
+ def respond_to_missing?(m, include_private = false)
310
+ ChefSpec.matchers.key?(m.to_sym) || super
311
+ end
312
+
313
+ private
314
+
315
+ #
316
+ # The path to cache files on disk. This value is created using
317
+ # {Dir.mktmpdir}. The method adds a {Kernel.at_exit} handler to ensure the
318
+ # temporary directory is deleted when the system exits.
319
+ #
320
+ # **This method creates a new temporary directory on each call!** As such,
321
+ # you should cache the result to a variable inside you system.
322
+ #
323
+ def file_cache_path
324
+ path = Dir.mktmpdir
325
+ at_exit { FileUtils.rm_rf(path) }
326
+ path
327
+ end
328
+
329
+ #
330
+ # Set the default options, with the given options taking precedence.
331
+ #
332
+ # @param [Hash] options
333
+ # the list of options to take precedence
334
+ #
335
+ # @return [Hash] options
336
+ #
337
+ def with_default_options(options)
338
+ config = RSpec.configuration
339
+
340
+ {
341
+ cookbook_root: config.cookbook_root || calling_cookbook_root(options, caller),
342
+ cookbook_path: config.cookbook_path || calling_cookbook_path(options, caller),
343
+ role_path: config.role_path || default_role_path,
344
+ environment_path: config.environment_path || default_environment_path,
345
+ file_cache_path: config.file_cache_path,
346
+ log_level: config.log_level,
347
+ path: config.path,
348
+ platform: config.platform,
349
+ version: config.version,
350
+ }.merge(options)
351
+ end
352
+
353
+ #
354
+ # The inferred cookbook root from the calling spec.
355
+ #
356
+ # @param [Hash<Symbol, Object>] options
357
+ # initial runner options
358
+ # @param [Array<String>] kaller
359
+ # the calling trace
360
+ #
361
+ # @return [String]
362
+ #
363
+ def calling_cookbook_root(options, kaller)
364
+ calling_spec = options[:spec_declaration_locations] || kaller.find { |line| line =~ %r{/spec} }
365
+ raise Error::CookbookPathNotFound if calling_spec.nil?
366
+
367
+ bits = calling_spec.split(/:[0-9]/, 2).first.split(File::SEPARATOR)
368
+ spec_dir = bits.index("spec") || 0
369
+
370
+ File.join(bits.slice(0, spec_dir))
371
+ end
372
+
373
+ #
374
+ # The inferred path from the calling spec.
375
+ #
376
+ # @param [Hash<Symbol, Object>] options
377
+ # initial runner options
378
+ # @param [Array<String>] kaller
379
+ # the calling trace
380
+ #
381
+ # @return [String]
382
+ #
383
+ def calling_cookbook_path(options, kaller)
384
+ File.expand_path(File.join(calling_cookbook_root(options, kaller), ".."))
385
+ end
386
+
387
+ #
388
+ # The inferred path to roles.
389
+ #
390
+ # @return [String, nil]
391
+ #
392
+ def default_role_path
393
+ Pathname.new(Dir.pwd).ascend do |path|
394
+ possible = File.join(path, "roles")
395
+ return possible if File.exist?(possible)
396
+ end
397
+
398
+ nil
399
+ end
400
+
401
+ #
402
+ # The inferred path to environments.
403
+ #
404
+ # @return [String, nil]
405
+ #
406
+ def default_environment_path
407
+ Pathname.new(Dir.pwd).ascend do |path|
408
+ possible = File.join(path, "environments")
409
+ return possible if File.exist?(possible)
410
+ end
411
+
412
+ nil
413
+ end
414
+
415
+ #
416
+ # The +Chef::Client+ for this runner.
417
+ #
418
+ # @return [Chef::Runner]
419
+ #
420
+ def client
421
+ return @client if @client
422
+
423
+ @client = Chef::Client.new
424
+ @client.ohai.data = Mash.from_hash(Fauxhai.mock(options).data)
425
+ @client.load_node
426
+ @client.build_node
427
+ @client.save_updated_node
428
+ @client
429
+ end
430
+
431
+ #
432
+ # We really need a way to just expand the run_list, but that's done by
433
+ # +Chef::Client#build_node+. However, that same method also resets the
434
+ # automatic attributes, making it impossible to mock them. So we are
435
+ # stuck +instance_eval+ing against the client and manually expanding
436
+ # the mode object.
437
+ #
438
+ # @todo Remove in Chef 13
439
+ #
440
+ def expand_run_list!
441
+ # Recent versions of Chef include a method to expand the +run_list+,
442
+ # setting the correct instance variables on the policy builder. We use
443
+ # that, unless the user is running an older version of Chef which
444
+ # doesn't include this method.
445
+ if client.respond_to?(:expanded_run_list)
446
+ client.expanded_run_list
447
+ else
448
+ # Sadly, if we got this far, it means that the current Chef version
449
+ # does not include the +expanded_run_list+ method, so we need to
450
+ # manually expand the +run_list+. The following code has been known
451
+ # to make kittens cry, so please read with extreme caution.
452
+ client.instance_eval do
453
+ @run_list_expansion = expand_run_list
454
+ @expanded_run_list_with_versions = @run_list_expansion.recipes.with_version_constraints_strings
455
+ end
456
+ end
457
+ end
458
+
459
+ #
460
+ # Try to load the cookbook metadata for the cookbook under test.
461
+ #
462
+ # @return [Chef::Cookbook::Metadata]
463
+ #
464
+ def cookbook
465
+ @cookbook ||= Chef::Cookbook::Metadata.new.tap { |m| m.from_file("#{options[:cookbook_root]}/metadata.rb") }
466
+ end
467
+
468
+ #
469
+ # Try to figure out the name for the cookbook under test.
470
+ #
471
+ # @return [String]
472
+ #
473
+ def cookbook_name
474
+ # Try to figure out the name of this cookbook, pretending this block
475
+ # is in the name context as the cookbook under test.
476
+
477
+ cookbook.name
478
+ rescue IOError
479
+ # Old cookbook, has no metadata, use the folder name I guess.
480
+ File.basename(options[:cookbook_root])
481
+ end
482
+
483
+ #
484
+ # Apply the required options to {Chef::Config}.
485
+ #
486
+ # @api private
487
+ # @return [void]
488
+ #
489
+ def apply_chef_config!
490
+ Chef::Log.level = @options[:log_level]
491
+
492
+ Chef::Config.reset!
493
+ Chef::Config.formatters.clear
494
+ Chef::Config.add_formatter("chefspec")
495
+ Chef::Config[:cache_type] = "Memory"
496
+ Chef::Config[:client_key] = nil
497
+ Chef::Config[:client_name] = nil
498
+ Chef::Config[:node_name] = nil
499
+ Chef::Config[:file_cache_path] = @options[:file_cache_path] || file_cache_path
500
+ Chef::Config[:cookbook_path] = Array(@options[:cookbook_path])
501
+ # If the word cookbook is in the folder name, treat it as the path. Otherwise
502
+ # it's probably not a cookbook path and so we activate the gross hack mode.
503
+ if Chef::Config[:cookbook_path].size == 1 && Chef::Config[:cookbook_path].first !~ /cookbook/
504
+ Chef::Config[:chefspec_cookbook_root] = @options[:cookbook_root]
505
+ end
506
+ Chef::Config[:no_lazy_load] = true
507
+ Chef::Config[:role_path] = Array(@options[:role_path])
508
+ Chef::Config[:force_logger] = true
509
+ Chef::Config[:solo] = true
510
+ Chef::Config[:solo_legacy_mode] = true
511
+ Chef::Config[:use_policyfile] = false
512
+ Chef::Config[:environment_path] = @options[:environment_path]
513
+ end
514
+
515
+ end
516
+ end
@@ -0,0 +1,11 @@
1
+ require_relative "registry"
2
+
3
+ module ChefSpec
4
+ module Stubs
5
+ class CommandRegistry < Registry
6
+ def stub_for(command)
7
+ @stubs.find { |stub| stub.command === command }
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,37 @@
1
+ require_relative "stub"
2
+
3
+ module ChefSpec
4
+ module Stubs
5
+ class CommandStub < Stub
6
+ attr_reader :block
7
+ attr_reader :command
8
+ attr_reader :value
9
+
10
+ def initialize(command, &block)
11
+ @command = command
12
+ @block = block
13
+ end
14
+
15
+ def and_return(value)
16
+ @value = value
17
+ self
18
+ end
19
+
20
+ def result
21
+ if @block
22
+ @block.call
23
+ else
24
+ @value
25
+ end
26
+ end
27
+
28
+ def signature
29
+ if @block
30
+ "stub_command(#{@command.inspect}) { # Ruby code }"
31
+ else
32
+ "stub_command(#{@command.inspect}).and_return(#{@value})"
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,13 @@
1
+ require_relative "registry"
2
+
3
+ module ChefSpec
4
+ module Stubs
5
+ class DataBagItemRegistry < Registry
6
+ def stub_for(bag, id)
7
+ @stubs.find do |stub|
8
+ stub.bag.to_s == bag.to_s && stub.id === id
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,25 @@
1
+ require_relative "stub"
2
+
3
+ module ChefSpec
4
+ module Stubs
5
+ class DataBagItemStub < Stub
6
+ attr_reader :block
7
+ attr_reader :id
8
+ attr_reader :bag
9
+
10
+ def initialize(bag, id, &block)
11
+ @bag = bag.to_s
12
+ @id = id
13
+ @block = block
14
+ end
15
+
16
+ def signature
17
+ if @block
18
+ "stub_data_bag_item(#{@bag.inspect}, #{@id.inspect}) { # Ruby code }"
19
+ else
20
+ "stub_data_bag_item(#{@bag.inspect}, #{@id.inspect}).and_return(#{@value})"
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,13 @@
1
+ require_relative "registry"
2
+
3
+ module ChefSpec
4
+ module Stubs
5
+ class DataBagRegistry < Registry
6
+ def stub_for(bag)
7
+ @stubs.find do |stub|
8
+ stub.bag.to_s == bag.to_s
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,23 @@
1
+ require_relative "stub"
2
+
3
+ module ChefSpec
4
+ module Stubs
5
+ class DataBagStub < Stub
6
+ attr_reader :block
7
+ attr_reader :bag
8
+
9
+ def initialize(bag, &block)
10
+ @bag = bag.to_s
11
+ @block = block
12
+ end
13
+
14
+ def signature
15
+ if @block
16
+ "stub_data_bag(#{@bag.inspect}) { # Ruby code }"
17
+ else
18
+ "stub_data_bag(#{@bag.inspect}).and_return(#{@value})"
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,32 @@
1
+ module ChefSpec
2
+ module Stubs
3
+ class Registry
4
+ class << self
5
+ extend Forwardable
6
+ def_delegators :instance, :reset!, :register, :stubs, :stubs=, :stub_for
7
+ end
8
+
9
+ include Singleton
10
+
11
+ # @return [Hash<Symbol, Array<SearchStub>>]
12
+ attr_accessor :stubs
13
+
14
+ def initialize
15
+ reset!
16
+ end
17
+
18
+ def reset!
19
+ @stubs = []
20
+ end
21
+
22
+ def register(stub)
23
+ @stubs.insert(0, stub)
24
+ stub
25
+ end
26
+
27
+ def stub_for(*args)
28
+ raise ArgumentError, "#stub_for is an abstract function"
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,13 @@
1
+ require_relative "registry"
2
+
3
+ module ChefSpec
4
+ module Stubs
5
+ class SearchRegistry < Registry
6
+ def stub_for(type, query = "*:*")
7
+ @stubs.find do |stub|
8
+ stub.type.to_s == type.to_s && stub.query === query
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end