chefspec 3.0.2 → 3.1.0.beta.1

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