chefspec-ohai 0.1.1 → 0.2.0

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: 11e687172fecfd2d53cc1ef2673016f514ec63d9
4
- data.tar.gz: 98a404207ad5fd570eef99d05a0d00d574d38ff5
3
+ metadata.gz: dc24aef40b78fd3ccb6d3d0d56d9a8bc4fee80f6
4
+ data.tar.gz: c78deb2e5878b3607fdb82f5cf38992a9053f364
5
5
  SHA512:
6
- metadata.gz: 745621819bab095aff4f0ba16901530cf50aac836b74fb2055af4105dc6eb0bb5f9eb5e0656cc1b79e2f0e59f8799c9a77e01deb702c0eecf20a8a1c0e743d5a
7
- data.tar.gz: 9c9ee91f577b6f7d25477a905d2797b83de2a028c838bba3c4da457ed64fc3e828037a203d33b46623b32accdad250190595218e12dafcdbfa73227aabde8e1a
6
+ metadata.gz: 57c1d88dc767c76a638a042c7e18e495a720937f0b134164d07b26ca5c55e2c06c7a158d8fcd197a001a1d64f242e4f6c605190db0df455c515caa1b04efe1eb
7
+ data.tar.gz: 9f3d6ce35c6dcac9e3763a281dce0ab1ef4a871a0c470b7e490ec039a61ec337ce345b215477125d357b4cd397df9d114921964431b7ffe66e148a3cd1f9a011
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --require spec_helper
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2016 Franklin Webber
3
+ Copyright (c) 2018 Franklin Webber
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -38,7 +38,21 @@ Or install it yourself as:
38
38
 
39
39
  ## Usage
40
40
 
41
- First you will want to load the helpers within `spec/spec_helper.rb` file:
41
+ Define your plugin within your cookbook in `cookbook/files/httpd_modules.rb`:
42
+
43
+ ```ruby
44
+ Ohai.plugin(:Apache) do
45
+ provides 'apache/modules'
46
+
47
+ collect_data(:default) do
48
+ apache Mash.new
49
+ modules_cmd = shell_out('apachectl -t -D DUMP_MODULES')
50
+ apache[:modules] = modules_cmd.stdout
51
+ end
52
+ end
53
+ ```
54
+
55
+ First, you will want to load the helpers within `spec/spec_helper.rb` file:
42
56
 
43
57
  ```ruby
44
58
  require 'chefspec' # Auto-generated with cookbook
@@ -53,7 +67,7 @@ plugin tests within the cookbook. I have chosen to create the directory
53
67
 
54
68
  I often choose to name the specification after the name of the file that stores
55
69
  the Ohai plugin. I were testing an Ohai plugin stored in `files/default/httpd_modules.rb`
56
- I would create a specifiation named `spec/unit/plugins/httpd_modules_spec.rb`.
70
+ I would create a specification named `spec/unit/plugins/httpd_modules_spec.rb`.
57
71
  The most important thing is that the file ends with `_spec.rb` so that RSpec
58
72
  will automatically find this file and load it appropriately.
59
73
 
@@ -65,15 +79,6 @@ This gem provides an alias of RSpec's `describe` named `describe_ohai_plugin`
65
79
  which loads some additional helper methods to assist you with expressing your
66
80
  examples and the expectations within your examples.
67
81
 
68
- You must specify a let helper, named `:plugin_file`, which is a relative path
69
- within the cookbook to the file that contains the Ohai plugin.
70
-
71
- When creating this gem there are two examples that I thought that you would
72
- want to assert when testing your Ohai plugin:
73
-
74
- * I expect the Ohai plugin to provide particular attributes
75
- * I expect the Ohai plugin, when run, to set those attributes
76
-
77
82
  Here is a sample specification file that asserts that an Ohai plugin provides
78
83
  particular attributes and those attributes, when run (and stubbing the environment),
79
84
  are set properly.
@@ -82,28 +87,83 @@ are set properly.
82
87
  require 'spec_helper'
83
88
 
84
89
  describe_ohai_plugin :Apache do
85
- let(:plugin_file) { "files/default/httpd_modules.rb" }
90
+ let(:plugin_file) { 'files/default/httpd_modules.rb' }
86
91
 
87
- context "default collect data" do
92
+ context 'default collect data' do
88
93
  it "provides 'apache/modules'" do
89
- expect(plugin).to provides_attribute("apache/modules")
94
+ expect(plugin).to provide_attribute('apache/modules')
90
95
  end
91
96
 
92
97
  it "correctly captures output" do
93
- stub_plugin_shell_out('apachectl -t -D DUMP_MODULES','OUTPUT')
94
- expect(plugin_attribute('apache/modules')).to eq("OUTPUT")
98
+ allow(plugin).to receive(:shell_out).with('apachectl -t -D DUMP_MODULES') { double(stderror: 0, stdout: 'unit tests do not like to run on systems') }
99
+ expect(plugin_attribute('apache/modules')).to eq('unit tests do not like to run on systems')
95
100
  end
96
101
  end
97
102
  end
98
103
  ```
99
104
 
100
- Here is the plugin that is being tested with that above specification:
105
+ Run the tests within the cookbook directory:
106
+
107
+ ```
108
+ $ rspec
109
+ ..
110
+ Finished in 0.04775 seconds (files took 3.38 seconds to load)
111
+ 2 examples, 0 failures
112
+ ```
113
+
114
+ ## Testing other than the `collect_data :default`
115
+
116
+ A plugin may collect data on different platforms. For example the following plugin:
117
+
118
+ ```ruby
119
+ Ohai.plugin(:NonDefaultCollectData) do
120
+ provides 'application/version'
121
+
122
+ collect_data(:linux) do
123
+ application Mash.new
124
+ application[:version] = '3.0.0'
125
+ end
126
+
127
+ collect_data(:windows) do
128
+ application Mash.new
129
+ application[:version] = '9.0.0'
130
+ end
131
+ end
132
+ ```
133
+
134
+ You write tests for each platform:
135
+
136
+ ```ruby
137
+ context 'linux data collection' do
138
+ let(:platform) { 'linux' }
139
+
140
+ it 'the attribute is correctly set' do
141
+ expect(plugin_attribute(attribute_name)).to eq '3.0.0'
142
+ end
143
+ end
144
+
145
+ context 'windows data collection' do
146
+ let(:platform) { 'windows' }
147
+
148
+ it 'the attribute is correctly set' do
149
+ expect(plugin_attribute(attribute_name)).to eq '9.0.0'
150
+ end
151
+ end
152
+ ```
153
+
154
+ ## Ohai plugins written as Cookbook Templates
155
+
156
+ Ohai plugins that are templates and not cookbook files require a little more setup. As templates require the presence of template variables or the node object that are passed to the temple you will need to define another helper in your specification `template_variables`.
157
+
158
+ The following plugin defined in a template `templates/httpd_modules.rb.erb`:
101
159
 
102
160
  ```ruby
103
161
  Ohai.plugin(:Apache) do
104
162
  provides 'apache/modules'
105
163
 
106
164
  collect_data(:default) do
165
+ puts "<%= @template_variable %>"
166
+ puts "<%= node['attribute'] %>"
107
167
  apache Mash.new
108
168
  modules_cmd = shell_out('apachectl -t -D DUMP_MODULES')
109
169
  apache[:modules] = modules_cmd.stdout
@@ -111,6 +171,104 @@ Ohai.plugin(:Apache) do
111
171
  end
112
172
  ```
113
173
 
174
+ Using a template for a plugin requires that you provide the template variables to
175
+ populate.
176
+
177
+
178
+ ```ruby
179
+ require 'spec_helper'
180
+
181
+ describe_ohai_plugin :Apache do
182
+ let(:plugin_file) { 'templates/apache_modules.rb.erb' }
183
+
184
+ let(:template_variables) do
185
+ { template_variable: 'Free Me!', node: { 'attribute' => 'something' } }
186
+ end
187
+
188
+ context 'default collect data' do
189
+ it "provides 'apache/modules'" do
190
+ expect(plugin).to provide_attribute('apache/modules')
191
+ # OR
192
+ expect(plugin).to provides_attribute('apache/modules')
193
+ end
194
+
195
+ it 'correctly captures output' do
196
+ allow(plugin).to receive(:shell_out).with('apachectl -t -D DUMP_MODULES') { double(stderror: 0, stdout: 'unit tests do not like to run on systems') }
197
+ expect(plugin_attribute('apache/modules')).to eq('unit tests do not like to run on systems')
198
+ end
199
+ end
200
+ end
201
+ ```
202
+
203
+ ## Testing a plugin with a dependency on another plugin
204
+
205
+ A plugin may have a dependency on another plugin. A plugin may be defined as:
206
+
207
+ ```ruby
208
+ Ohai.plugin(:FirstNetwork) do
209
+ provides 'first_network/ipv4', 'first_network/interface'
210
+
211
+ depends 'other_plugin'
212
+
213
+ collect_data(:default) do
214
+ first_network Mash.new
215
+
216
+ name, data = network['interfaces'].first
217
+ first_network[:interface] = name
218
+ first_network[:ipv4] = data['addresses'].first.first
219
+ end
220
+ end
221
+ ```
222
+
223
+ Within the test you can write expectations that the the plugin has a dependency on the plugin. When it comes to creating the test data, that requires stubbing the plugin calls to the dependent content.
224
+
225
+ ```ruby
226
+
227
+ describe_ohai_plugin :FirstNetwork do
228
+ let(:plugin_file) { 'files/first_network.rb' }
229
+
230
+ it 'provides the first network address' do
231
+ expect(plugin).to provide_attribute('first_network/ipv4')
232
+ end
233
+
234
+ it 'provides the first network interface' do
235
+ expect(plugin).to provide_attribute('first_network/interface')
236
+ end
237
+
238
+ it 'depends on another plugin' do
239
+ expect(plugin).to depend_on_attribute('network')
240
+ end
241
+
242
+ context 'default data collection' do
243
+ before do
244
+ allow(plugin).to receive(:network).and_return(network_data)
245
+ end
246
+
247
+ let(:network_data) do
248
+ {
249
+ 'interfaces' => {
250
+ 'lo' => {
251
+ 'state' => 'unknown',
252
+ 'addresses' => {
253
+ '127.0.0.1' => {
254
+ 'family' => 'inet'
255
+ }
256
+ }
257
+ }
258
+ }
259
+ }
260
+ end
261
+
262
+ it 'the first network interface is correctly set' do
263
+ expect(plugin_attribute('first_network/interface')).to eq('lo')
264
+ end
265
+
266
+ it 'the first network address is correctly set' do
267
+ expect(plugin_attribute('first_network/ipv4')).to eq('127.0.0.1')
268
+ end
269
+ end
270
+ ```
271
+
114
272
  ## Development
115
273
 
116
274
  This gem only provides a subset of the features that are possible to specify when
@@ -23,4 +23,7 @@ This gem provides additional helpers to RSpec to be used in conjunction with Che
23
23
 
24
24
  spec.add_development_dependency "bundler", "~> 1.9"
25
25
  spec.add_development_dependency "rake", "~> 10.0"
26
+
27
+ spec.add_dependency "chefspec", "> 5.0.0"
28
+ spec.add_dependency "ohai", "> 14.0.0"
26
29
  end
@@ -20,7 +20,7 @@ shared_context 'Ohai Plugins', type: :ohai_plugin do
20
20
  # for use within the specifications.
21
21
  let(:plugin) do
22
22
  ohai_plugin_class = Ohai.plugin(subject) {}
23
- ohai_plugin_class.new(plugin_data)
23
+ ohai_plugin_class.new(plugin_data, Ohai::Log)
24
24
  end
25
25
 
26
26
  # When an Ohai plugin is created there is a Hash of data that must be provided.
@@ -29,7 +29,87 @@ shared_context 'Ohai Plugins', type: :ohai_plugin do
29
29
  let(:plugin_data) { Hash.new }
30
30
 
31
31
  # Loads the plugin source from the specified plugin_file helper
32
- let(:plugin_source) { File.read(plugin_file) }
32
+ let(:plugin_source) do
33
+ source = File.read(plugin_file)
34
+ if plugin_file.start_with?('templates')
35
+ ERB.new(source).result(plugin_template_binding)
36
+ else
37
+ source
38
+ end
39
+ end
40
+
41
+ # This helper is required to be defined by any Ohai plugin that is defined
42
+ # as a template. This hash of values will used to create the instance variables
43
+ # and define the conents of the node object which are used when generating the
44
+ # resulting template.
45
+ let(:template_variables) do
46
+ raise %(
47
+ When you define a plugin_source based on a template a hash of template variables needs to be provided.
48
+
49
+ Example:
50
+ let(:template_variables) do
51
+ { name: 'value_inserted_into_plugin', node: { 'ipaddress' : '127.0.0.1' } }
52
+ end
53
+ )
54
+ end
55
+
56
+ # Return a binding created with the hash of values defined in the template_variables
57
+ # helper.
58
+ let(:plugin_template_binding) do
59
+ PluginBinding.new(template_variables).expose_binding!
60
+ end
61
+
62
+
63
+ # To provide a space to instance variables that will be used by any Ohai
64
+ # plugins that use ERB templating to insert content.
65
+ class PluginBinding
66
+
67
+ # Create a new object that will define instance variables out of the hash's
68
+ # key-value pairs. This will create a safe place to define instance variables
69
+ # within a binding that can used when rendering the content of the ERB.
70
+ def initialize(template_variables)
71
+ Hash(template_variables).each do |key,value|
72
+ instance_variable_set("@#{key}",value)
73
+ end
74
+ end
75
+
76
+ # Within a template you can use `node[:attribute]`. This method provides
77
+ # support for that by creating this method that maps to the @node instance
78
+ # variable. This means that if you want to define a Ohai plugin that uses
79
+ # the node object and test it, then you are required to provide the values
80
+ # to make that work for the test.
81
+ #
82
+ # NOTE: A consideration here would be to use Fauxhai instead of requiring a
83
+ # user to provide each attribute. This would get time consumsing to
84
+ # create tests if the Ohai plugin used a lot of node attributes.
85
+ def node
86
+ if @node.nil?
87
+ raise NoNodeDataProvidedToTemplatePlugin
88
+ end
89
+ @node
90
+ end
91
+
92
+ # This exposes the private #binding method to allow it to be used as the specified
93
+ # binding when parsing ERB template.
94
+ def expose_binding!
95
+ binding
96
+ end
97
+
98
+ class NoNodeDataProvidedToTemplatePlugin < StandardError
99
+
100
+ def message
101
+ "The Ohai plugin attempted to retrieve an attribute from the `node` object. However, no node attributes were defined in the template_variables helper.
102
+ Add a template_variables helper within your ohai plugin specification and include a `node` key that returns the data you need to satisfy the requirements of your plugin.
103
+
104
+ Example:
105
+ let(:template_variables) do
106
+ { node: { 'ipaddress' => '127.0.0.1' } }
107
+ end"
108
+ end
109
+ end
110
+
111
+ end
112
+
33
113
  # Determine the plugin_path from the specified plugin_file
34
114
  let(:plugin_path) { File.dirname(plugin_file) }
35
115
  # This helper defines the path to the plugin file. This must be specified
@@ -45,9 +125,12 @@ Example:
45
125
  )
46
126
  end
47
127
 
128
+
129
+ let(:platform) { 'default' }
130
+
48
131
  # A Loader requires a controller. This controller may need to be overriden so
49
132
  # it is provided as a helper.
50
- let(:plugin_controller) { double('plugin_controller') }
133
+ let(:plugin_controller) { Ohai::System.new }
51
134
  # The plugin_loader will evaluate the source of the plugin with the controller
52
135
  let(:plugin_loader) { Ohai::Loader.new(plugin_controller) }
53
136
 
@@ -61,6 +144,9 @@ Example:
61
144
  ps = plugin_source
62
145
  pp = plugin_path
63
146
  plugin_loader.instance_eval { load_v7_plugin_class(ps,pp) }
147
+ if platform != 'default'
148
+ allow(plugin).to receive(:collect_os).and_return(platform)
149
+ end
64
150
  end
65
151
 
66
152
  after :each do
@@ -89,16 +175,45 @@ Example:
89
175
  # does provide the correct body of attributes. The Ohai plugin class returns
90
176
  # an array of attributes that it provides through `#provides_attrs` which
91
177
  # is evaluated to ensure that the expected value is within that list.
92
- RSpec::Matchers.define :provides_attribute do |expected|
178
+
179
+ # @see https://relishapp.com/rspec/rspec-expectations/v/2-4/docs/custom-matchers/define-matcher
180
+ RSpec::Matchers.define :provide_attribute do |expected|
93
181
  match do |plugin|
94
182
  expect(plugin.class.provides_attrs).to include(expected)
95
183
  end
184
+
185
+ failure_message do |actual|
186
+ "Expected the plugin to provide '#{expected}'. Plugin's defined attributes: #{plugin.class.provides_attrs.map { |p| "'#{p}'" }.join(', ')}"
187
+ end
188
+
189
+ failure_message_when_negated do |actual|
190
+ "Expected the plugin to NOT provide '#{expected}'. Plugin's defined attributes: #{plugin.class.provides_attrs.map { |p| "'#{p}'" }.join(', ')}"
191
+ end
96
192
  end
97
193
 
98
- # Provides a simplier way to stub out the shell_out that is probably going on
99
- # within the Ohai plugin.
100
- def stub_plugin_shell_out(command,result)
101
- allow(plugin).to receive(:shell_out).with(command) { double(stdout: result) }
194
+ # Provide support for the plural provides_attribute so that the authors
195
+ # of the specification can choose what they think makes the most sense
196
+ alias_method :provides_attribute, :provide_attribute
197
+
198
+
199
+ # This provides a new matcher when wanting to make assertions that the plugin
200
+ # has the correct dependencies. The Ohai plugin returns an array of dependencies
201
+ # that it provides through `#dependencies` which is evaluated to ensure that
202
+ # the expected value is within that list.
203
+
204
+ # @see https://relishapp.com/rspec/rspec-expectations/v/2-4/docs/custom-matchers/define-matcher
205
+ RSpec::Matchers.define :depend_on_attribute do |expected|
206
+ match do |plugin|
207
+ expect(plugin.dependencies).to include(expected)
208
+ end
209
+
210
+ failure_message do |actual|
211
+ "Expected the plugin to depend on '#{expected}'. Plugin's dependencies: #{plugin.dependencies.map { |d| "'#{d}'" }.join(', ')}"
212
+ end
213
+
214
+ failure_message_when_negated do |actual|
215
+ "Expected the plugin to NOT depend on '#{expected}'. Plugin's dependencies: #{plugin.dependencies.map { |d| "'#{d}'" }.join(', ')}"
216
+ end
102
217
  end
103
218
 
104
219
  # To make the process of verifying the attributes a little more streamlined
@@ -112,14 +227,63 @@ Example:
112
227
  # that must be traversed to get to the value desired. That is done here and
113
228
  # then returned.
114
229
  def plugin_attribute(attribute)
115
- plugin.run
230
+ begin
231
+ plugin.run
232
+ rescue Exception => e
233
+ raise PluginFailedToRunError.new(plugin.name,plugin_file,e)
234
+ end
116
235
 
117
236
  components = attribute.split('/')
118
237
  top_level_mash_name = components.first
119
238
  attributes = components[1..-1]
120
239
 
121
240
  top_level_mash = plugin.send(top_level_mash_name)
122
- attributes.inject(top_level_mash) { |mash,child| mash[child] }
241
+
242
+ if top_level_mash.nil? && !attributes.empty?
243
+ raise PluginAttributeUndefinedError.new(attribute,{})
244
+ end
245
+
246
+ attributes.inject(top_level_mash) do |mash,child|
247
+ begin
248
+ mash[child]
249
+ rescue Exception => e
250
+ raise PluginAttributeUndefinedError.new(attribute, { top_level_mash_name => top_level_mash })
251
+ end
252
+ end
253
+ end
254
+
255
+ class PluginFailedToRunError < RuntimeError
256
+ def initialize(plugin_name,plugin_path,exception)
257
+ @plugin_name = plugin_name
258
+ @plugin_path = plugin_path
259
+ @exception = exception
260
+
261
+ # Fix the backtrace path from the fixtures directory to the plugin file
262
+ exception.backtrace.unshift exception.backtrace.shift.gsub('spec/fixtures',plugin_path)
263
+ end
264
+
265
+ attr_reader :plugin_name, :plugin_path, :exception
266
+
267
+ def message
268
+ "Plugin #{plugin_name} #{plugin_path} failed:\n#{exception.message}"
269
+ end
270
+ end
271
+
272
+ class PluginAttributeUndefinedError < RuntimeError
273
+ def initialize(desired_attribute,plugin_attribute_data)
274
+ @desired_attribute = desired_attribute
275
+ @plugin_attribute_data = plugin_attribute_data
276
+
277
+ end
278
+
279
+ attr_reader :desired_attribute, :plugin_attribute_data
280
+
281
+ def message
282
+ "Plugin does not define attribute path '#{desired_attribute}'. Does the definition or test have a misspelling? Does the plugin properly initialize the entire attribute path?
283
+
284
+ Plugin Attribute Data:
285
+ #{plugin_attribute_data.to_yaml}\n---"
286
+ end
123
287
  end
124
288
 
125
289
  end
@@ -1,5 +1,5 @@
1
1
  module Chefspec
2
2
  module Ohai
3
- VERSION = "0.1.1"
3
+ VERSION = "0.2.0"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: chefspec-ohai
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Franklin Webber
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-12-07 00:00:00.000000000 Z
11
+ date: 2018-04-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -38,6 +38,34 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: chefspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">"
46
+ - !ruby/object:Gem::Version
47
+ version: 5.0.0
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">"
53
+ - !ruby/object:Gem::Version
54
+ version: 5.0.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: ohai
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">"
60
+ - !ruby/object:Gem::Version
61
+ version: 14.0.0
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">"
67
+ - !ruby/object:Gem::Version
68
+ version: 14.0.0
41
69
  description: |-
42
70
  Ohai plugins often go untested within the cookbooks that we create.
43
71
  When an Ohai plugin fails it is often a trial-and-error process that requires us to redeploy it.
@@ -49,6 +77,7 @@ extensions: []
49
77
  extra_rdoc_files: []
50
78
  files:
51
79
  - ".gitignore"
80
+ - ".rspec"
52
81
  - CODE_OF_CONDUCT.md
53
82
  - Gemfile
54
83
  - LICENSE.txt