chefspec 3.0.2 → 3.1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5c8e999b2df276e377b7c7939430db34e9ba082b
4
- data.tar.gz: 08c074ed6d9633de4e7ea9b83c0f3709c7c91704
3
+ metadata.gz: ecd2e2844bf5859e413abf186d1f71ed0f5af281
4
+ data.tar.gz: 72f1818ef26032cc0cb039bf3f51923eaae47800
5
5
  SHA512:
6
- metadata.gz: b7f58af30026df43e1c60515e1ba01e6d0dea37443878d3ea10bcec2e47aa4ea91e23c40db8ab8e2a5660255afd6fb6b559d98621bed48999d732bec641bfa2d
7
- data.tar.gz: 1598aefcab47c574882ade3bb99a9f2a4241e26e0c86b791cdce0dabfc7a85a56a7383c148ec2e05568a8a585b865cd54aa69b8c0dac0894ff2998ade28c4701
6
+ metadata.gz: ac4ce6148c62de28889fd679ca8ba7306ec0f9be180b3bb81fbeb752c7103285fc47a7091865bbc4c28e27d608bd3d94d485caaeb56a2998b12727294c83c47b
7
+ data.tar.gz: ca086443be533996952f5dd17aa634bf520a8a46e4f972f0d1374481365642eca928915e1713c30cea40b92d4409c7183a314466f372d98e008ae6fc37913e77
@@ -7,6 +7,8 @@ require_relative 'chefspec/extensions/chef/lwrp_base'
7
7
  require_relative 'chefspec/extensions/chef/resource'
8
8
  require_relative 'chefspec/extensions/chef/securable'
9
9
 
10
+ require_relative 'chefspec/mixins/normalize'
11
+
10
12
  require_relative 'chefspec/stubs/command_registry'
11
13
  require_relative 'chefspec/stubs/command_stub'
12
14
  require_relative 'chefspec/stubs/data_bag_item_registry'
@@ -19,6 +21,7 @@ require_relative 'chefspec/stubs/search_registry'
19
21
  require_relative 'chefspec/stubs/search_stub'
20
22
 
21
23
  require_relative 'chefspec/api'
24
+ require_relative 'chefspec/coverage'
22
25
  require_relative 'chefspec/errors'
23
26
  require_relative 'chefspec/expect_exception'
24
27
  require_relative 'chefspec/formatter'
@@ -28,7 +31,3 @@ require_relative 'chefspec/renderer'
28
31
  require_relative 'chefspec/rspec'
29
32
  require_relative 'chefspec/runner'
30
33
  require_relative 'chefspec/version'
31
-
32
- # Load deprecations module last, so it can monkey patch and print out nasty
33
- # deprecation warnings for us :)
34
- require_relative 'chefspec/deprecations'
@@ -0,0 +1,59 @@
1
+ module ChefSpec
2
+ #
3
+ # The cacher module allows for ultra-fast tests by caching the results of a
4
+ # CCR in memory across an example group. In testing, this can reduce the
5
+ # total testing time by a factor of 10x. This strategy is _not_ the default
6
+ # behavior, because it has implications surrounding stubbing and is _not_
7
+ # threadsafe!
8
+ #
9
+ # The credit for this approach and code belongs to Juri Timošin (DracoAter).
10
+ # Please see his original blog post below for an in-depth explanation of how
11
+ # and why this approach is faster.
12
+ #
13
+ # @example Using the Cacher module
14
+ # First, require the Cacher module in your +spec_helper.rb+:
15
+ #
16
+ # RSpec.configure do |config|
17
+ # config.extend(ChefSpec::Cacher)
18
+ # end
19
+ #
20
+ # Next, change your +let+ blocks to +cached+ blocks:
21
+ #
22
+ # let(:chef_run) { ... } #=> cached(:chef_run) { ... }
23
+ #
24
+ # Finally, celebrate!
25
+ #
26
+ # @warn
27
+ # This strategy is only recommended for advanced users, as it makes
28
+ # stubbing slightly more difficult and indirect!
29
+ #
30
+ # @see http://dracoater.blogspot.com/2013/12/testing-chef-cookbooks-part-25-speeding.html
31
+ #
32
+ module Cacher
33
+ @@cache = {}
34
+ FINALIZER = lambda { |id| @@cache.delete(id) }
35
+
36
+ def cached(name, &block)
37
+ location = ancestors.first.metadata[:example_group][:location]
38
+
39
+ define_method(name) do
40
+ key = [location, name.to_s].join('.')
41
+ unless @@cache.has_key?(Thread.current.object_id)
42
+ ObjectSpace.define_finalizer(Thread.current, FINALIZER)
43
+ end
44
+ @@cache[Thread.current.object_id] ||= {}
45
+ @@cache[Thread.current.object_id][key] ||= instance_eval(&block)
46
+ end
47
+ end
48
+
49
+ def cached!(name, &block)
50
+ cached(name, &block)
51
+
52
+ before { send(name) }
53
+ end
54
+ end
55
+ end
56
+
57
+ RSpec.configure do |config|
58
+ config.extend(ChefSpec::Cacher)
59
+ end
@@ -0,0 +1,144 @@
1
+ module ChefSpec
2
+ class Coverage
3
+ class << self
4
+ extend Forwardable
5
+ def_delegators :instance, :add, :cover!, :report!
6
+ end
7
+
8
+ include Singleton
9
+
10
+ #
11
+ # Create a new coverage object singleton.
12
+ #
13
+ def initialize
14
+ @collection = {}
15
+ end
16
+
17
+ #
18
+ # Add a resource to the resource collection.
19
+ #
20
+ # @param [Chef::Resource] resource
21
+ #
22
+ def add(resource)
23
+ @collection[resource.to_s] = ResourceWrapper.new(resource)
24
+ end
25
+
26
+ #
27
+ # Called when a resource is matched to indicate it has been tested.
28
+ #
29
+ # @param [Chef::Resource] resource
30
+ #
31
+ def cover!(resource)
32
+ if wrapper = find(resource)
33
+ wrapper.touch!
34
+ end
35
+ end
36
+
37
+ #
38
+ # Generate a coverage report. This report **must** be generated +at_exit+
39
+ # or else the entire resource collection may not be complete!
40
+ #
41
+ # @example Generating a report
42
+ #
43
+ # at_exit { ChefSpec::Coverage.report! }
44
+ #
45
+ # @example Generating a custom report without announcing
46
+ #
47
+ # at_exit { ChefSpec::Coverage.report!('/custom/path', false) }
48
+ #
49
+ #
50
+ # @param [String] output
51
+ # the path to output the report on disk (default: '.coverage/results.json')
52
+ # @param [Boolean] announce
53
+ # print the results to standard out
54
+ #
55
+ def report!(output = '.coverage/results.json', announce = true)
56
+ report = {}
57
+
58
+ report[:total] = @collection.size
59
+ report[:touched] = @collection.count { |_, resource| resource.touched? }
60
+ report[:untouched] = report[:total] - report[:touched]
61
+ report[:coverage] = ((report[:touched].to_f/report[:total].to_f)*100).round(2)
62
+
63
+ report[:detailed] = Hash[*@collection.map do |name, wrapper|
64
+ [name, wrapper.to_hash]
65
+ end.flatten]
66
+
67
+ output = File.expand_path(output)
68
+ FileUtils.mkdir_p(File.dirname(output))
69
+ File.open(File.join(output), 'w') do |f|
70
+ f.write(JSON.pretty_generate(report) + "\n")
71
+ end
72
+
73
+ if announce
74
+ puts <<-EOH.gsub(/^ {10}/, '')
75
+
76
+ WARNING: ChefSpec Coverage reporting is in beta. Please use with caution.
77
+
78
+ ChefSpec Coverage report generated at '#{output}':
79
+
80
+ Total Resources: #{report[:total]}
81
+ Touched Resources: #{report[:touched]}
82
+ Touch Coverage: #{report[:coverage]}%
83
+
84
+ Untouched Resources:
85
+
86
+ #{
87
+ report[:detailed]
88
+ .select { |_, resource| !resource[:touched] }
89
+ .sort_by { |_, resource| [resource[:source][:file], resource[:source][:line]] }
90
+ .map do |name, resource|
91
+ " #{name} #{resource[:source][:file]}:#{resource[:source][:line]}"
92
+ end
93
+ .flatten
94
+ .join("\n")
95
+ }
96
+
97
+ EOH
98
+ end
99
+ end
100
+
101
+ private
102
+
103
+ def find(resource)
104
+ @collection[resource.to_s]
105
+ end
106
+
107
+ class ResourceWrapper
108
+ attr_reader :resource
109
+
110
+ def initialize(resource = nil)
111
+ @resource = resource
112
+ end
113
+
114
+ def to_s
115
+ @resource.to_s
116
+ end
117
+
118
+ def source
119
+ return {} unless @resource.source_line
120
+ file, line, *_ = @resource.source_line.split(':')
121
+
122
+ {
123
+ file: file,
124
+ line: line.to_i,
125
+ }
126
+ end
127
+
128
+ def to_hash
129
+ {
130
+ source: source,
131
+ touched: touched?,
132
+ }
133
+ end
134
+
135
+ def touch!
136
+ @touched = true
137
+ end
138
+
139
+ def touched?
140
+ !!@touched
141
+ end
142
+ end
143
+ end
144
+ end
@@ -50,6 +50,9 @@ module ChefSpec
50
50
  end
51
51
 
52
52
  module ChefSpec::API
53
+ #
54
+ # @todo Remove in v4.0.0
55
+ #
53
56
  module DeprecatedMatchers
54
57
  def be_owned_by(user, group)
55
58
  deprecated "The `be_owned_by` matcher is deprecated. Please use:" \
@@ -2,7 +2,10 @@ require 'chef/dsl/data_query'
2
2
 
3
3
  module Chef::DSL::DataQuery
4
4
  # @see Chef::DSL::DataQuery#search
5
+ alias_method :old_search, :search
5
6
  def search(*args, &block)
7
+ return old_search(*args, &block) unless Chef::Config[:solo]
8
+
6
9
  type = args[0]
7
10
  query = args[1] || '*:*'
8
11
  stub = ChefSpec::Stubs::SearchRegistry.stub_for(type, query)
@@ -12,7 +15,10 @@ module Chef::DSL::DataQuery
12
15
  end
13
16
 
14
17
  # @see Chef::DSL::DataQuery#data_bag
18
+ alias_method :old_data_bag, :data_bag
15
19
  def data_bag(bag)
20
+ return old_data_bag(bag) unless Chef::Config[:solo]
21
+
16
22
  stub = ChefSpec::Stubs::DataBagRegistry.stub_for(bag)
17
23
  raise ChefSpec::DataBagNotStubbedError.new(bag) if stub.nil?
18
24
 
@@ -20,7 +26,10 @@ module Chef::DSL::DataQuery
20
26
  end
21
27
 
22
28
  # @see Chef::DSL::DataQuery#data_bag_item
29
+ alias_method :old_data_bag_item, :data_bag_item
23
30
  def data_bag_item(bag, id)
31
+ return old_data_bag_item(bag, id) unless Chef::Config[:solo]
32
+
24
33
  stub = ChefSpec::Stubs::DataBagItemRegistry.stub_for(bag, id)
25
34
  raise ChefSpec::DataBagItemNotStubbedError.new(bag, id) if stub.nil?
26
35
 
@@ -1,46 +1,54 @@
1
1
  # Override Chef LWRP creation to remove existing class to avoid redefinition warnings.
2
2
  class Chef
3
- class Provider
4
- # Chef provider for a resource
5
- class LWRPBase < Provider
6
- class << self
7
- alias_method :old_build_from_file, :build_from_file
3
+ module RemoveExistingLWRP
4
+ def self.extended(klass)
5
+ class << klass
6
+ alias_method :build_from_file_without_removal, :build_from_file
7
+ alias_method :build_from_file, :build_from_file_with_removal
8
+ end
9
+ end
8
10
 
9
- #
10
- # Override Opscode provider to remove any existing LWRPs to suppress
11
- # constant re-definition warnings.
12
- #
13
- # @param [String] cookbook_name
14
- # the name of the cookbook
15
- # @param [String] filename
16
- # file to load as a LWRP
17
- # @param [Chef::RunContext] run_context
18
- # context of a Chef Run
19
- #
20
- # @return [Chef::Provider]
21
- #
22
- def build_from_file(cookbook_name, filename, run_context)
23
- provider_name = filename_to_qualified_string(cookbook_name, filename)
24
- class_name = convert_to_class_name(provider_name)
11
+ #
12
+ # Override Opscode provider to remove any existing LWRPs to suppress
13
+ # constant re-definition warnings.
14
+ #
15
+ # @param [String] cookbook_name
16
+ # the name of the cookbook
17
+ # @param [String] filename
18
+ # file to load as a LWRP
19
+ # @param [Chef::RunContext] run_context
20
+ # context of a Chef Run
21
+ #
22
+ # @return [Chef::Provider]
23
+ #
24
+ def build_from_file_with_removal(cookbook_name, filename, run_context)
25
+ provider_name = filename_to_qualified_string(cookbook_name, filename)
26
+ class_name = convert_to_class_name(provider_name)
25
27
 
26
- remove_existing_lwrp(class_name)
27
- old_build_from_file(cookbook_name, filename, run_context)
28
- end
28
+ remove_existing_lwrp(class_name)
29
+ build_from_file_without_removal(cookbook_name, filename, run_context)
30
+ end
29
31
 
30
- #
31
- # Remove any existing Chef provider or resource with the specified name.
32
- #
33
- # @param [String] class_name
34
- # The class name. Must be a valid constant name.
35
- #
36
- def remove_existing_lwrp(class_name)
37
- [Chef::Resource::LWRPBase, Chef::Provider::LWRPBase, Chef::Resource, Chef::Provider].each do |resource_holder|
38
- if resource_holder.const_defined? class_name, false
39
- resource_holder.send(:remove_const, class_name)
40
- end
41
- end
32
+ #
33
+ # Remove any existing Chef provider or resource with the specified name.
34
+ #
35
+ # @param [String] class_name
36
+ # The class name. Must be a valid constant name.
37
+ #
38
+ def remove_existing_lwrp(class_name)
39
+ [self, superclass].each do |resource_holder|
40
+ look_in_parents = false
41
+ if resource_holder.const_defined?(class_name, look_in_parents)
42
+ resource_holder.send(:remove_const, class_name)
42
43
  end
43
44
  end
45
+ end
46
+ end
47
+
48
+ class Provider
49
+ # Chef provider for a resource
50
+ class LWRPBase < Provider
51
+ extend RemoveExistingLWRP
44
52
 
45
53
  module InlineResources
46
54
  module ClassMethods
@@ -60,4 +68,11 @@ class Chef
60
68
  end
61
69
  end
62
70
  end
71
+
72
+ class Resource
73
+ # Chef provider for a resource
74
+ class LWRPBase < Resource
75
+ extend RemoveExistingLWRP
76
+ end
77
+ end
63
78
  end
@@ -14,6 +14,8 @@ class Chef::Resource
14
14
 
15
15
  Chef::Log.info("Processing #{self} action #{action} (#{defined_at})")
16
16
 
17
+ ChefSpec::Coverage.add(self)
18
+
17
19
  unless should_skip?(action)
18
20
  if node.runner.step_into?(self)
19
21
  instance_eval { @not_if = []; @only_if = [] }
@@ -0,0 +1,49 @@
1
+ begin
2
+ require 'librarian/chef/environment'
3
+ require 'librarian/action/resolve'
4
+ require 'librarian/action/install'
5
+ rescue LoadError
6
+ raise RuntimeError, "Librarian not found! You must have the librarian-chef" \
7
+ " gem installed on your system before requiring chefspec/librarian." \
8
+ " Install it by running:\n\n gem install librarian-chef\n\nor add" \
9
+ " Librarian to your Gemfile:\n\n gem 'librarian-chef'\n\n"
10
+ end
11
+
12
+ module ChefSpec
13
+ class Librarian
14
+ class << self
15
+ extend Forwardable
16
+ def_delegators :instance, :setup!, :teardown!
17
+ end
18
+
19
+ include Singleton
20
+
21
+ def initialize
22
+ @tmpdir = Dir.mktmpdir
23
+ end
24
+
25
+ #
26
+ # Setup and install the necessary dependencies in the temporary directory.
27
+ #
28
+ def setup!
29
+ env = ::Librarian::Chef::Environment.new(project_path: Dir.pwd)
30
+ env.config_db.local['path'] = @tmpdir
31
+ ::Librarian::Action::Resolve.new(env).run
32
+ ::Librarian::Action::Install.new(env).run
33
+
34
+ ::RSpec.configure { |config| config.cookbook_path = @tmpdir }
35
+ end
36
+
37
+ #
38
+ # Remove the temporary directory.
39
+ #
40
+ def teardown!
41
+ FileUtils.rm_rf(@tmpdir) if File.exists?(@tmpdir)
42
+ end
43
+ end
44
+ end
45
+
46
+ RSpec.configure do |config|
47
+ config.before(:suite) { ChefSpec::Librarian.setup! }
48
+ config.after(:suite) { ChefSpec::Librarian.teardown! }
49
+ end
@@ -1,5 +1,7 @@
1
1
  module ChefSpec::Matchers
2
2
  class NotificationsMatcher
3
+ include ChefSpec::Normalize
4
+
3
5
  def initialize(signature)
4
6
  signature.match(/^([^\[]*)\[(.*)\]$/)
5
7
  @expected_resource_type = $1
@@ -11,7 +13,7 @@ module ChefSpec::Matchers
11
13
 
12
14
  if @resource
13
15
  block = Proc.new do |notified|
14
- notified.resource.resource_name.to_s == @expected_resource_type &&
16
+ resource_name(notified.resource).to_s == @expected_resource_type &&
15
17
  (@expected_resource_name === notified.resource.identity.to_s || @expected_resource_name === notified.resource.name.to_s) &&
16
18
  matches_action?(notified)
17
19
  end
@@ -51,7 +53,7 @@ module ChefSpec::Matchers
51
53
 
52
54
  def failure_message_for_should
53
55
  if @resource
54
- message = %Q{expected "#{@resource.resource_name}[#{@resource.name}]" to notify "#{@expected_resource_type}[#{@expected_resource_name}]"}
56
+ message = %Q{expected "#{resource_name(@resource)}[#{@resource.name}]" to notify "#{@expected_resource_type}[#{@expected_resource_name}]"}
55
57
  message << " with action :#{@action}" if @action
56
58
  message << " immediately" if @immediately
57
59
  message << " delayed" if @delayed
@@ -98,7 +100,7 @@ module ChefSpec::Matchers
98
100
  resource = notification.resource
99
101
  type = notification.notifying_resource.immediate_notifications.include?(notification) ? :immediately : :delayed
100
102
 
101
- %Q{ "#{notifying_resource.to_s}" notifies "#{resource.resource_name}[#{resource.name}]" to :#{notification.action}, :#{type}}
103
+ %Q{ "#{notifying_resource.to_s}" notifies "#{resource_name(resource)}[#{resource.name}]" to :#{notification.action}, :#{type}}
102
104
  end
103
105
 
104
106
  def format_notifications
@@ -43,6 +43,7 @@ module ChefSpec::Matchers
43
43
  @runner = runner
44
44
 
45
45
  if resource
46
+ ChefSpec::Coverage.cover!(resource)
46
47
  resource.performed_action?(@expected_action) && unmatched_parameters.empty? && correct_phase?
47
48
  else
48
49
  false
@@ -0,0 +1,16 @@
1
+ module ChefSpec
2
+ module Normalize
3
+ #
4
+ # Calculate the name of a resource, replacing dashes with underscores
5
+ # and converting symbols to strings and back again.
6
+ #
7
+ # @param [String, Chef::Resource] thing
8
+ #
9
+ # @return [Symbol]
10
+ #
11
+ def resource_name(thing)
12
+ name = thing.respond_to?(:resource_name) ? thing.resource_name : thing
13
+ name.to_s.gsub('-', '_').to_sym
14
+ end
15
+ end
16
+ end
@@ -6,6 +6,8 @@ end
6
6
 
7
7
  module ChefSpec
8
8
  class Renderer
9
+ include ChefSpec::Normalize
10
+
9
11
  # @return [Chef::Runner]
10
12
  attr_reader :chef_run
11
13
 
@@ -34,12 +36,12 @@ module ChefSpec
34
36
  # does not contain or respond to a content renderer.
35
37
  #
36
38
  def content
37
- case resource.resource_name.to_s
38
- when 'template'
39
+ case resource_name(resource)
40
+ when :template
39
41
  content_from_template(chef_run, resource)
40
- when 'file'
42
+ when :file
41
43
  content_from_file(chef_run, resource)
42
- when 'cookbook_file'
44
+ when :cookbook_file
43
45
  content_from_cookbook_file(chef_run, resource)
44
46
  else
45
47
  nil
@@ -6,6 +6,8 @@ require 'chef/resources'
6
6
 
7
7
  module ChefSpec
8
8
  class Runner
9
+ include ChefSpec::Normalize
10
+
9
11
  #
10
12
  # Defines a new runner method on the +ChefSpec::Runner+.
11
13
  #
@@ -149,8 +151,12 @@ module ChefSpec
149
151
  expand_run_list!
150
152
 
151
153
  # Setup the run_context
154
+ client.register unless Chef::Config[:solo]
152
155
  @run_context = client.setup_run_context
153
156
 
157
+ # Allow stubbing/mocking after the cookbook has been compiled but before the converge
158
+ yield if block_given?
159
+
154
160
  @converging = true
155
161
  @client.converge(@run_context)
156
162
  self
@@ -201,7 +207,7 @@ module ChefSpec
201
207
  rescue Chef::Exceptions::ResourceNotFound; end
202
208
 
203
209
  resource_collection.all_resources.find do |resource|
204
- resource.resource_name.to_sym == type && (name === resource.identity || name === resource.name)
210
+ resource_name(resource) == type && (name === resource.identity || name === resource.name)
205
211
  end
206
212
  end
207
213
 
@@ -209,7 +215,7 @@ module ChefSpec
209
215
  # Find the resource with the declared type.
210
216
  #
211
217
  # @example Find all template resources
212
- # chef_run.find_resources('template') #=> [#<Chef::Resource::Template>, #...]
218
+ # chef_run.find_resources(:template) #=> [#<Chef::Resource::Template>, #...]
213
219
  #
214
220
  #
215
221
  # @param [Symbol] type
@@ -220,7 +226,7 @@ module ChefSpec
220
226
  #
221
227
  def find_resources(type)
222
228
  resource_collection.all_resources.select do |resource|
223
- resource.resource_name.to_s == type.to_s
229
+ resource_name(resource) == type.to_sym
224
230
  end
225
231
  end
226
232
 
@@ -249,8 +255,8 @@ module ChefSpec
249
255
  # @return [Boolean]
250
256
  #
251
257
  def step_into?(resource)
252
- key = resource.resource_name.to_s.gsub('-', '_').to_sym
253
- Array(options[:step_into]).map(&:to_sym).include?(key)
258
+ key = resource_name(resource)
259
+ Array(options[:step_into]).map(&method(:resource_name)).include?(key)
254
260
  end
255
261
 
256
262
  #
@@ -0,0 +1,200 @@
1
+ begin
2
+ require 'chef_zero/server'
3
+ rescue LoadError
4
+ raise RuntimeError, "chef-zero not found! You must have the chef-zero gem" \
5
+ " installed on your system before requiring chefspec/server. Install" \
6
+ " Chef Zero by running:\n\n gem install chef-zero\n\nor add Chef Zero" \
7
+ " to your Gemfile:\n\n gem 'chef-zero'\n\n"
8
+ end
9
+
10
+ require 'chef/cookbook_loader'
11
+ require 'chef/cookbook_uploader'
12
+
13
+ class Chef::CookbookUploader
14
+ #
15
+ # Don't validate uploaded cookbooks. Validating a cookbook takes *forever*
16
+ # to complete. It's just not worth it...
17
+ #
18
+ def validate_cookbooks
19
+ # noop
20
+ end
21
+ end
22
+
23
+ class ChefSpec::Runner
24
+ alias_method :old_initialize, :initialize
25
+
26
+ #
27
+ # Override the existing initialize method, setting the appropriate
28
+ # configuration to use a real Chef Server instead.
29
+ #
30
+ # @see ChefSpec::Runner#initialize
31
+ #
32
+ def initialize(options = {}, &block)
33
+ old_initialize(options, &block)
34
+
35
+ Chef::Config[:client_key] = ChefSpec::Server.client_key
36
+ Chef::Config[:client_name] = 'chefspec'
37
+ Chef::Config[:node_name] = 'chefspec'
38
+ Chef::Config[:file_cache_path] = Dir.mktmpdir
39
+ Chef::Config[:solo] = false
40
+
41
+ upload_cookbooks!
42
+ end
43
+
44
+ private
45
+
46
+ #
47
+ # Upload the cookbooks to the Chef Server.
48
+ #
49
+ def upload_cookbooks!
50
+ loader = Chef::CookbookLoader.new(Chef::Config[:cookbook_path])
51
+ loader.load_cookbooks
52
+
53
+ uploader = Chef::CookbookUploader.new(loader.cookbooks, loader.cookbook_paths)
54
+ uploader.upload_cookbooks
55
+ end
56
+ end
57
+
58
+ module ChefSpec
59
+ class Server
60
+ #
61
+ # Delegate all methods to the singleton instance.
62
+ #
63
+ def self.method_missing(m, *args, &block)
64
+ instance.send(m, *args, &block)
65
+ end
66
+
67
+ include Singleton
68
+
69
+ attr_reader :server
70
+
71
+ #
72
+ # Create a new instance of the +ChefSpec::Server+ singleton. This method
73
+ # also starts the Chef Zero server in the background.
74
+ #
75
+ def initialize
76
+ @server = ChefZero::Server.new(
77
+ log_level: RSpec.configuration.log_level || :warn,
78
+ )
79
+ end
80
+
81
+ #
82
+ # The path to the insecure Chef Zero private key on disk. Because Chef
83
+ # requires the path to a file instead of the contents of the key (why),
84
+ # this method dynamically writes the +ChefZero::PRIVATE_KEY+ to disk and
85
+ # then returns that path.
86
+ #
87
+ # @return [String]
88
+ # the path to the client key on disk
89
+ #
90
+ def client_key
91
+ @client_key ||= begin
92
+ path = File.join(cache_dir, 'client.pem')
93
+ File.open(path, 'w') { |f| f.write(ChefZero::PRIVATE_KEY) }
94
+ path
95
+ end
96
+ end
97
+
98
+ #
99
+ # Start the Chef Zero server in the background, updating the +Chef::Config+
100
+ # with the proper +chef_server_url+.
101
+ #
102
+ def start!
103
+ unless @server.running?
104
+ @server.start_background
105
+ Chef::Config[:chef_server_url] = @server.url
106
+ end
107
+ end
108
+
109
+ #
110
+ # Clear the contents of the server (used between examples)
111
+ #
112
+ def reset!
113
+ @server.clear_data
114
+ end
115
+
116
+ #
117
+ # Stop the Chef Zero server, if it is running. This method also runs any
118
+ # cleanup hooks, such as clearing the cache directories.
119
+ #
120
+ def stop!
121
+ @server.stop if @server.running?
122
+ FileUtils.rm_rf(cache_dir)
123
+ end
124
+
125
+ #
126
+ # Create a client on the Chef Server.
127
+ #
128
+ def create_client(name, data = {})
129
+ load_data(:clients, name, data)
130
+ end
131
+
132
+ #
133
+ # Create a data_bag on the Chef Server.
134
+ #
135
+ def create_data_bag(name, data = {})
136
+ @server.load_data('data' => { name => data })
137
+ end
138
+
139
+ #
140
+ # Create an environment on the Chef Server.
141
+ #
142
+ def create_environment(name, data = {})
143
+ load_data(:environments, name, data)
144
+ end
145
+
146
+ #
147
+ # Create a node on the Chef Server.
148
+ #
149
+ # @note
150
+ # The current node (chefspec) is automatically registered and added to
151
+ # the Chef Server
152
+ #
153
+ def create_node(name, data = {})
154
+ load_data(:nodes, name, data)
155
+ end
156
+
157
+ #
158
+ # Create a role on the Chef Server.
159
+ #
160
+ def create_role(name, data = {})
161
+ load_data(:roles, name, data)
162
+ end
163
+
164
+ private
165
+
166
+ #
167
+ # The directory where any cache information (such as private keys) should
168
+ # be stored. This cache is destroyed at the end of the run.
169
+ #
170
+ # @return [String]
171
+ # the path to the cache directory on disk
172
+ #
173
+ def cache_dir
174
+ @cache_dir ||= Dir.mktmpdir(['chefspec', 'cache'])
175
+ end
176
+
177
+ #
178
+ # Shortcut method for loading data into Chef Zero.
179
+ #
180
+ # @param [String, Symbol] key
181
+ # the key to load
182
+ # @param [String] name
183
+ # the name or id of the item to load
184
+ # @param [Hash] data
185
+ # the data for the object, which will be converted to JSON and uploaded
186
+ # to the server
187
+ #
188
+ def load_data(key, name, data = {})
189
+ @server.load_data(key.to_s => { name => JSON.fast_generate(data) })
190
+ end
191
+ end
192
+ end
193
+
194
+ ChefSpec::Server.start!
195
+
196
+ RSpec.configure do |config|
197
+ config.after(:each) { ChefSpec::Server.reset! }
198
+ end
199
+
200
+ at_exit { ChefSpec::Server.stop! }
@@ -1,3 +1,3 @@
1
1
  module ChefSpec
2
- VERSION = '3.0.2'
2
+ VERSION = '3.1.0.beta.1'
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: chefspec
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.2
4
+ version: 3.1.0.beta.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Crump
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-11-06 00:00:00.000000000 Z
12
+ date: 2013-12-16 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: chef
@@ -53,6 +53,20 @@ dependencies:
53
53
  - - ~>
54
54
  - !ruby/object:Gem::Version
55
55
  version: '2.14'
56
+ - !ruby/object:Gem::Dependency
57
+ name: chef-zero
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ~>
61
+ - !ruby/object:Gem::Version
62
+ version: '1.7'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ~>
68
+ - !ruby/object:Gem::Version
69
+ version: '1.7'
56
70
  - !ruby/object:Gem::Dependency
57
71
  name: rake
58
72
  requirement: !ruby/object:Gem::Requirement
@@ -165,6 +179,8 @@ files:
165
179
  - lib/chefspec/api/yum_package.rb
166
180
  - lib/chefspec/api.rb
167
181
  - lib/chefspec/berkshelf.rb
182
+ - lib/chefspec/cacher.rb
183
+ - lib/chefspec/coverage.rb
168
184
  - lib/chefspec/deprecations.rb
169
185
  - lib/chefspec/errors.rb
170
186
  - lib/chefspec/expect_exception.rb
@@ -175,6 +191,7 @@ files:
175
191
  - lib/chefspec/extensions/chef/resource.rb
176
192
  - lib/chefspec/extensions/chef/securable.rb
177
193
  - lib/chefspec/formatter.rb
194
+ - lib/chefspec/librarian.rb
178
195
  - lib/chefspec/macros.rb
179
196
  - lib/chefspec/matchers/include_recipe_matcher.rb
180
197
  - lib/chefspec/matchers/link_to_matcher.rb
@@ -183,9 +200,11 @@ files:
183
200
  - lib/chefspec/matchers/resource_matcher.rb
184
201
  - lib/chefspec/matchers/state_attrs_matcher.rb
185
202
  - lib/chefspec/matchers.rb
203
+ - lib/chefspec/mixins/normalize.rb
186
204
  - lib/chefspec/renderer.rb
187
205
  - lib/chefspec/rspec.rb
188
206
  - lib/chefspec/runner.rb
207
+ - lib/chefspec/server.rb
189
208
  - lib/chefspec/stubs/command_registry.rb
190
209
  - lib/chefspec/stubs/command_stub.rb
191
210
  - lib/chefspec/stubs/data_bag_item_registry.rb
@@ -213,14 +232,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
213
232
  version: '1.9'
214
233
  required_rubygems_version: !ruby/object:Gem::Requirement
215
234
  requirements:
216
- - - '>='
235
+ - - '>'
217
236
  - !ruby/object:Gem::Version
218
- version: '0'
237
+ version: 1.3.1
219
238
  requirements: []
220
239
  rubyforge_project:
221
- rubygems_version: 2.1.10
240
+ rubygems_version: 2.1.11
222
241
  signing_key:
223
242
  specification_version: 4
224
- summary: chefspec-3.0.2
243
+ summary: chefspec-3.1.0.beta.1
225
244
  test_files: []
226
245
  has_rdoc: