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 +4 -4
- data/lib/chefspec.rb +3 -4
- data/lib/chefspec/cacher.rb +59 -0
- data/lib/chefspec/coverage.rb +144 -0
- data/lib/chefspec/deprecations.rb +3 -0
- data/lib/chefspec/extensions/chef/data_query.rb +9 -0
- data/lib/chefspec/extensions/chef/lwrp_base.rb +51 -36
- data/lib/chefspec/extensions/chef/resource.rb +2 -0
- data/lib/chefspec/librarian.rb +49 -0
- data/lib/chefspec/matchers/notifications_matcher.rb +5 -3
- data/lib/chefspec/matchers/resource_matcher.rb +1 -0
- data/lib/chefspec/mixins/normalize.rb +16 -0
- data/lib/chefspec/renderer.rb +6 -4
- data/lib/chefspec/runner.rb +11 -5
- data/lib/chefspec/server.rb +200 -0
- data/lib/chefspec/version.rb +1 -1
- metadata +25 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ecd2e2844bf5859e413abf186d1f71ed0f5af281
|
4
|
+
data.tar.gz: 72f1818ef26032cc0cb039bf3f51923eaae47800
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ac4ce6148c62de28889fd679ca8ba7306ec0f9be180b3bb81fbeb752c7103285fc47a7091865bbc4c28e27d608bd3d94d485caaeb56a2998b12727294c83c47b
|
7
|
+
data.tar.gz: ca086443be533996952f5dd17aa634bf520a8a46e4f972f0d1374481365642eca928915e1713c30cea40b92d4409c7183a314466f372d98e008ae6fc37913e77
|
data/lib/chefspec.rb
CHANGED
@@ -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
|
@@ -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
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
alias_method :
|
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
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
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
|
-
|
27
|
-
|
28
|
-
|
28
|
+
remove_existing_lwrp(class_name)
|
29
|
+
build_from_file_without_removal(cookbook_name, filename, run_context)
|
30
|
+
end
|
29
31
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
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
|
@@ -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.
|
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
|
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
|
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
|
@@ -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
|
data/lib/chefspec/renderer.rb
CHANGED
@@ -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
|
38
|
-
when
|
39
|
+
case resource_name(resource)
|
40
|
+
when :template
|
39
41
|
content_from_template(chef_run, resource)
|
40
|
-
when
|
42
|
+
when :file
|
41
43
|
content_from_file(chef_run, resource)
|
42
|
-
when
|
44
|
+
when :cookbook_file
|
43
45
|
content_from_cookbook_file(chef_run, resource)
|
44
46
|
else
|
45
47
|
nil
|
data/lib/chefspec/runner.rb
CHANGED
@@ -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
|
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(
|
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
|
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 =
|
253
|
-
Array(options[:step_into]).map(
|
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! }
|
data/lib/chefspec/version.rb
CHANGED
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.
|
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-
|
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:
|
237
|
+
version: 1.3.1
|
219
238
|
requirements: []
|
220
239
|
rubyforge_project:
|
221
|
-
rubygems_version: 2.1.
|
240
|
+
rubygems_version: 2.1.11
|
222
241
|
signing_key:
|
223
242
|
specification_version: 4
|
224
|
-
summary: chefspec-3.0.
|
243
|
+
summary: chefspec-3.1.0.beta.1
|
225
244
|
test_files: []
|
226
245
|
has_rdoc:
|