rspec-puppet-yaml 0.1.0
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 +7 -0
- data/.gitignore +16 -0
- data/.inch.yml +4 -0
- data/.rspec +2 -0
- data/.travis.yml +29 -0
- data/.yardopts +2 -0
- data/CODE_OF_CONDUCT.md +75 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +93 -0
- data/LICENSE.txt +21 -0
- data/README.md +759 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/rspec-puppet/matcher_helpers.rb +326 -0
- data/lib/rspec-puppet/support_copy.rb +66 -0
- data/lib/rspec-puppet-yaml/data_helpers.rb +176 -0
- data/lib/rspec-puppet-yaml/extenders.rb +28 -0
- data/lib/rspec-puppet-yaml/parser.rb +512 -0
- data/lib/rspec-puppet-yaml/version.rb +11 -0
- data/lib/rspec-puppet-yaml.rb +8 -0
- data/rspec-puppet-yaml.gemspec +45 -0
- metadata +208 -0
data/README.md
ADDED
|
@@ -0,0 +1,759 @@
|
|
|
1
|
+
# rspec-puppet-yaml
|
|
2
|
+
|
|
3
|
+
[](https://travis-ci.org/wwkimball/rspec-puppet-yaml) [](https://inch-ci.org/github/wwkimball/rspec-puppet-yaml)
|
|
4
|
+
|
|
5
|
+
This gem enables Puppet module authors to write RSpec unit tests as YAML instead
|
|
6
|
+
of Ruby (omitting the single, trivial line of code necessary to pass your YAML
|
|
7
|
+
to RSpec). It also adds a new capability: a trivial means to create test
|
|
8
|
+
variants (repeat 0-N tests while tweaking any set of inputs and expectations to
|
|
9
|
+
detect unintended side-effects). If you're more comfortable with YAML than
|
|
10
|
+
Ruby, or want to easily work with test variants, then you'll want this gem.
|
|
11
|
+
|
|
12
|
+
While this gem spares you from needing to learn Ruby just to test your Puppet
|
|
13
|
+
module, you still need to do a little research into what RSpec tests are
|
|
14
|
+
available to your YAML. The good news is [that very knowledge is already
|
|
15
|
+
documented for the rspec-puppet gem](https://github.com/rodjek/rspec-puppet/blob/master/README.md#matchers)
|
|
16
|
+
and you just need to know how to translate it into YAML. While not initially
|
|
17
|
+
obvious, it really is very easy.
|
|
18
|
+
|
|
19
|
+
The source code examples in this document are expanded or direct translations of
|
|
20
|
+
each of the matcher samples that are shown in the rspec-puppet README.
|
|
21
|
+
Copyright for the original examples is owned by the maintainers of that gem and
|
|
22
|
+
are duplicated here in good faith that these translations into YAML are *not*
|
|
23
|
+
competitive and will benefit our common audience. This rpsec-puppet-yaml gem
|
|
24
|
+
extends the reach of the rspec-puppet gem to include users who speak YAML better
|
|
25
|
+
than Ruby; rpsec-puppet-yaml does not replace rspec-puppet but rather the
|
|
26
|
+
written language that is necessary to interact with it (YAML rather than Ruby).
|
|
27
|
+
|
|
28
|
+
All following examples will be presented both in the original Ruby and in the
|
|
29
|
+
new YAML for comparison. In the most general terms, you can express an existing
|
|
30
|
+
RSpec entity in YAML simply by transcribing its name as a Hash key and its
|
|
31
|
+
attributes or contents as its children. Just know that I decided to use `tests`
|
|
32
|
+
instead of `it` to identify the RSpec examples and I abstracted `is_expected.to`
|
|
33
|
+
and `is_expected.not_to` rather than exposing them directly to YAML. This was
|
|
34
|
+
both an aesthetic decision and to reduce complexity.
|
|
35
|
+
|
|
36
|
+
## Installation
|
|
37
|
+
|
|
38
|
+
Add this line to your application's Gemfile:
|
|
39
|
+
|
|
40
|
+
```ruby
|
|
41
|
+
gem 'rspec-puppet-yaml'
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
And then execute:
|
|
45
|
+
|
|
46
|
+
$ bundle
|
|
47
|
+
|
|
48
|
+
Or install it yourself as:
|
|
49
|
+
|
|
50
|
+
$ gem install rspec-puppet-yaml
|
|
51
|
+
|
|
52
|
+
## Usage
|
|
53
|
+
|
|
54
|
+
As per the [existing documentation for rspec-puppet](https://github.com/rodjek/rspec-puppet/blob/master/README.md#naming-conventions),
|
|
55
|
+
you will still create RSpec entry-point files at `your_module/spec/**/*_spec.rb`
|
|
56
|
+
(else `rake` won't find your tests, YAML or otherwise). However, these files
|
|
57
|
+
now need only two lines of Ruby code (usually) and no RSpec code. You'll still
|
|
58
|
+
require your `spec_helper` as always, and then just call the global-scope
|
|
59
|
+
entry-point function to parse your YAML into RSpec. The function shown here
|
|
60
|
+
expects to receive the fully-qualified path and name of your `*_spec.rb` file,
|
|
61
|
+
not your YAML file. The function will search for your YAML files based on the
|
|
62
|
+
automatic value of the `__FILE__` variable.
|
|
63
|
+
|
|
64
|
+
For example:
|
|
65
|
+
|
|
66
|
+
```ruby
|
|
67
|
+
require 'spec_helper'
|
|
68
|
+
parse_yaml_from_spec(__FILE__)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Yes, that's it! You can copy-paste those two lines into every `*_spec.rb` file
|
|
72
|
+
and then spend your time writing RSpec Puppet tests in YAML rather than Ruby.
|
|
73
|
+
To do so, create another file in the same directory with the same base name as
|
|
74
|
+
your `*_spec.rb` file except change the extension to `.yaml` or `.yml`. In
|
|
75
|
+
fact, you don't even need the `_spec` part of the name, so for an RSpec file
|
|
76
|
+
named `my_module_spec.rb`, you can use any of these YAML file-names:
|
|
77
|
+
|
|
78
|
+
* `my_module_spec.yaml`
|
|
79
|
+
* `my_module_spec.yml`
|
|
80
|
+
* `my_module.yaml`
|
|
81
|
+
* `my_module.yml`
|
|
82
|
+
|
|
83
|
+
Fair warning: the parser will look for *all* of these file-names, so if you
|
|
84
|
+
create more than one of them, they will all be processed, in turn.
|
|
85
|
+
|
|
86
|
+
### Defining RSpec Puppet YAML Tests
|
|
87
|
+
|
|
88
|
+
For the most general-case example, this Ruby:
|
|
89
|
+
|
|
90
|
+
```ruby
|
|
91
|
+
describe 'my_class', :type => 'class' {
|
|
92
|
+
let(:params) { my_attribute => 'value' }
|
|
93
|
+
|
|
94
|
+
it { is_expected.to some_matcher.with_attribute('value').and_attribute }
|
|
95
|
+
|
|
96
|
+
it { is_expected.not_to another_matcher}
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
becomes this YAML:
|
|
101
|
+
|
|
102
|
+
```yaml
|
|
103
|
+
describe:
|
|
104
|
+
name: my_class
|
|
105
|
+
type: class
|
|
106
|
+
let:
|
|
107
|
+
params:
|
|
108
|
+
my_attribute: value
|
|
109
|
+
tests:
|
|
110
|
+
some_matcher:
|
|
111
|
+
with_attribute: value
|
|
112
|
+
and_attribute: nil # nil indicates a matcher method with no arguments
|
|
113
|
+
'!another_matcher': {} # ! negates the matcher and every matcher needs either a Hash or Array value
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
**NOTE**: The top-most entity of every rspec-puppet-yaml file must be a
|
|
117
|
+
`describe`. Each `describe` must have a `name` attribute. The top-most
|
|
118
|
+
`describe` must additionally have a `type` attribute unless you arrange your
|
|
119
|
+
`*_spec.rb` files according to the recommended rspec-puppet directory structure
|
|
120
|
+
(in which case the class can be automatically derived from its file-system
|
|
121
|
+
location).
|
|
122
|
+
|
|
123
|
+
#### Setting custom facts, parameters, titles, and any other `let` setting
|
|
124
|
+
|
|
125
|
+
Ruby:
|
|
126
|
+
|
|
127
|
+
```ruby
|
|
128
|
+
describe 'my_module' do
|
|
129
|
+
let(:title) { 'baz' }
|
|
130
|
+
|
|
131
|
+
let(:params) do
|
|
132
|
+
{ 'value' => 'foo',
|
|
133
|
+
'user' => :undef,
|
|
134
|
+
'require' => ref('Package', 'sudoku'),
|
|
135
|
+
'nodes' => {
|
|
136
|
+
ref('Node', 'dbnode') => ref('Myapp::Mycomponent', 'myapp')
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
let(:node) { 'testhost.example.com' }
|
|
142
|
+
|
|
143
|
+
let(:environment) { 'production' }
|
|
144
|
+
|
|
145
|
+
let(:facts) do
|
|
146
|
+
{ 'os' => {
|
|
147
|
+
'family' => 'RedHat',
|
|
148
|
+
'release' => {
|
|
149
|
+
'major' => '7',
|
|
150
|
+
'minor' => '1',
|
|
151
|
+
'full' => '7.1.1503'
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
let(:node_params) do
|
|
158
|
+
{ 'hostgroup' => 'webservers',
|
|
159
|
+
'rack' => 'KK04',
|
|
160
|
+
'status' => 'maintenance' }
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
let(:pre_condition) { 'include other_class' }
|
|
164
|
+
|
|
165
|
+
let(:post_condition) { 'include another_class' }
|
|
166
|
+
|
|
167
|
+
let(:module_path) { '/path/to/your/module/dir' }
|
|
168
|
+
|
|
169
|
+
let(:trusted_facts) do
|
|
170
|
+
{ 'pp_uuid' => 'ED803750-E3C7-44F5-BB08-41A04433FE2E',
|
|
171
|
+
'1.3.6.1.4.1.34380.1.2.1' => 'ssl-termination' }
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
YAML:
|
|
177
|
+
|
|
178
|
+
```yaml
|
|
179
|
+
describe:
|
|
180
|
+
name: my_module
|
|
181
|
+
let:
|
|
182
|
+
title: baz
|
|
183
|
+
node: testhost.example.com
|
|
184
|
+
environment: production
|
|
185
|
+
params:
|
|
186
|
+
value: foo
|
|
187
|
+
user: !ruby/symbol undef # The symbol-form of `undef` must be used to specify an :undef value
|
|
188
|
+
require: '%{eval:ref("Package", "my-package")}': # %{eval:...} expands into a command and its arguments which is run via `eval`, capturing its return value. `'` or `"` demarcation of `%{}` is required only because YAML values are forbidden from starting with a `%`.
|
|
189
|
+
nodes:
|
|
190
|
+
'%{eval:ref("Node", "dbnode")}': '%{eval:ref("Myapp::Mycomponent", "myapp")}'
|
|
191
|
+
facts:
|
|
192
|
+
os:
|
|
193
|
+
family: RedHat
|
|
194
|
+
release:
|
|
195
|
+
major: 7
|
|
196
|
+
minor: 1
|
|
197
|
+
full: 7.1.1503
|
|
198
|
+
node_params:
|
|
199
|
+
hostgroup: webservers
|
|
200
|
+
rack: KK04
|
|
201
|
+
status: maintenance
|
|
202
|
+
pre_condition: include other_class
|
|
203
|
+
post_condition: include another_class
|
|
204
|
+
module_path: /path/to/your/module/dir
|
|
205
|
+
trusted_facts:
|
|
206
|
+
pp_uuid: ED803750-E3C7-44F5-BB08-41A04433FE2E
|
|
207
|
+
'1.3.6.1.4.1.34380.1.2.1': ssl-termination
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
#### The compile matcher
|
|
211
|
+
|
|
212
|
+
Ruby:
|
|
213
|
+
|
|
214
|
+
```ruby
|
|
215
|
+
# Plain test to ensure the module compiles without error
|
|
216
|
+
describe "my_module" {
|
|
217
|
+
it { is_expected.to compile }
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
# Expanded test to ensure it compiles with all dependencies
|
|
221
|
+
describe "my_module" {
|
|
222
|
+
it { is_expected.to compile.with_all_deps }
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
# Ensure the module *fails* to compile, issuing an expected error message
|
|
226
|
+
describe "my_module" {
|
|
227
|
+
it { is_expected.to compile.and_raise_error(/error message match/) }
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
YAML:
|
|
232
|
+
|
|
233
|
+
```yaml
|
|
234
|
+
# Plain test to ensure the module compiles without error
|
|
235
|
+
describe:
|
|
236
|
+
name: my_module
|
|
237
|
+
tests:
|
|
238
|
+
compile: {}
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
# Expanded test to ensure it compiles with all dependencies
|
|
242
|
+
# Multiple ways to achieve the same net effect!
|
|
243
|
+
describe:
|
|
244
|
+
name: my_module
|
|
245
|
+
tests:
|
|
246
|
+
compile: true # `with_all_deps` is the default test when the `compile` matcher is given any "truthy" value
|
|
247
|
+
|
|
248
|
+
describe:
|
|
249
|
+
name: my_module
|
|
250
|
+
tests:
|
|
251
|
+
compile: # Methods that don't accept arguments can be called from an Array
|
|
252
|
+
- with_all_deps
|
|
253
|
+
|
|
254
|
+
describe:
|
|
255
|
+
name: my_module
|
|
256
|
+
tests:
|
|
257
|
+
compile: # Methods that don't accept arguments can be called from a Hash with nil as their value
|
|
258
|
+
with_all_deps: nil
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
# Ensure the module *fails* to compile, issuing an expected error message.
|
|
262
|
+
# Method 1: Use a Regular Expression to match part of the error message. Note
|
|
263
|
+
# that in this case, you must identify your value as being a Ruby Regular
|
|
264
|
+
# Expression data type with the leading `!ruby/regexp` marker.
|
|
265
|
+
describe:
|
|
266
|
+
name: my_module
|
|
267
|
+
tests:
|
|
268
|
+
compile:
|
|
269
|
+
and_raise_error: !ruby/regexp /error message match/
|
|
270
|
+
|
|
271
|
+
# Method 2: Match the *entire* error message, not just part of it. This
|
|
272
|
+
# employs a normal String value that requires no additional marker.
|
|
273
|
+
describe:
|
|
274
|
+
name: my_module
|
|
275
|
+
tests:
|
|
276
|
+
compile:
|
|
277
|
+
and_raise_error: FULL text of the error message to match
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
#### The contain (or create) matcher
|
|
281
|
+
|
|
282
|
+
Ruby:
|
|
283
|
+
|
|
284
|
+
```ruby
|
|
285
|
+
# Ensure a set of resources exist in the manifest, some with specific attributes
|
|
286
|
+
describe "my_module" {
|
|
287
|
+
it { is_expected.to contain_augeas('bleh') }
|
|
288
|
+
|
|
289
|
+
it { is_expected.to contain_class('foo') }
|
|
290
|
+
|
|
291
|
+
it { is_expected.to contain_foo__bar('baz') }
|
|
292
|
+
|
|
293
|
+
it { is_expected.to contain_package('mysql-server').with_ensure('present') }
|
|
294
|
+
|
|
295
|
+
it { is_expected.to contain_package('httpd').only_with_ensure('latest') }
|
|
296
|
+
|
|
297
|
+
it do
|
|
298
|
+
is_expected.to contain_service('keystone').with(
|
|
299
|
+
'ensure' => 'running',
|
|
300
|
+
'enable' => 'true',
|
|
301
|
+
'hasstatus' => 'true',
|
|
302
|
+
'hasrestart' => 'true'
|
|
303
|
+
)
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
it do
|
|
307
|
+
is_expected.to contain_user('luke').only_with(
|
|
308
|
+
'ensure' => 'present',
|
|
309
|
+
'uid' => '501'
|
|
310
|
+
)
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
it { is_expected.to contain_file('/foo/bar').without_mode }
|
|
314
|
+
|
|
315
|
+
it { is_expected.to contain_service('keystone_2').without(
|
|
316
|
+
['restart', 'status']
|
|
317
|
+
)}
|
|
318
|
+
}
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
YAML:
|
|
322
|
+
|
|
323
|
+
```yaml
|
|
324
|
+
# Ensure a set of resources exist in the manifest, some with specific attributes
|
|
325
|
+
describe:
|
|
326
|
+
name: my_module
|
|
327
|
+
tests:
|
|
328
|
+
contain_augeas:
|
|
329
|
+
bleh: {}
|
|
330
|
+
contain_class:
|
|
331
|
+
foo: {}
|
|
332
|
+
contain_foo__bar:
|
|
333
|
+
baz: {}
|
|
334
|
+
contain_package:
|
|
335
|
+
mysql-server: present # `with_ensure` is the default test when packages are given any scalar value
|
|
336
|
+
httpd:
|
|
337
|
+
only_with_ensure: latest
|
|
338
|
+
contain_service:
|
|
339
|
+
keystone:
|
|
340
|
+
with:
|
|
341
|
+
ensure: running
|
|
342
|
+
enable: true
|
|
343
|
+
hasstatus: true
|
|
344
|
+
hasrestart: true
|
|
345
|
+
keystone_2:
|
|
346
|
+
without:
|
|
347
|
+
- restart
|
|
348
|
+
- status
|
|
349
|
+
contain_user:
|
|
350
|
+
luke:
|
|
351
|
+
only_with:
|
|
352
|
+
ensure: present
|
|
353
|
+
uid: 501
|
|
354
|
+
contain_file:
|
|
355
|
+
/foo/bar:
|
|
356
|
+
- without_mode
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
##### With stipulated resource relationships
|
|
360
|
+
|
|
361
|
+
Ruby:
|
|
362
|
+
|
|
363
|
+
```ruby
|
|
364
|
+
describe "my_module" {
|
|
365
|
+
# Ensure the file, foo, has specific relationships with other resources
|
|
366
|
+
it { is_expected.to contain_file('foo').that_requires('File[bar]') }
|
|
367
|
+
|
|
368
|
+
it { is_expected.to contain_file('foo').that_comes_before('File[baz]') }
|
|
369
|
+
|
|
370
|
+
it { is_expected.to contain_file('foo').that_notifies('File[bim]') }
|
|
371
|
+
|
|
372
|
+
it { is_expected.to contain_file('foo').that_subscribes_to('File[bom]') }
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
# Ensure the file, bar, has specific relationships with many other resources
|
|
376
|
+
it { is_expected.to contain_file('bar').that_requires(['File[fim]', 'File[fu]']) }
|
|
377
|
+
|
|
378
|
+
it { is_expected.to contain_file('bar').that_comes_before(['File[fam]','File[far]']) }
|
|
379
|
+
|
|
380
|
+
it { is_expected.to contain_file('bar').that_notifies(['File[fiz]', 'File[faz]']) }
|
|
381
|
+
|
|
382
|
+
it { is_expected.to contain_file('bar').that_subscribes_to(['File[fuz]', 'File[fez]']) }
|
|
383
|
+
|
|
384
|
+
# Other relationship example
|
|
385
|
+
it { is_expected.to contain_notify('bar').that_comes_before('Notify[foo]') }
|
|
386
|
+
|
|
387
|
+
it { is_expected.to contain_notify('foo').that_requires('Notify[bar]') }
|
|
388
|
+
}
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
YAML:
|
|
392
|
+
|
|
393
|
+
```yaml
|
|
394
|
+
describe:
|
|
395
|
+
name: my_module
|
|
396
|
+
tests:
|
|
397
|
+
contain_file:
|
|
398
|
+
# Ensure the file, foo, has specific relationships with other resources
|
|
399
|
+
foo:
|
|
400
|
+
that_requires: File[bar]
|
|
401
|
+
that_comes_before: File[baz]
|
|
402
|
+
that_notifies: File[bim]
|
|
403
|
+
that_subscribes_to: File[bom]
|
|
404
|
+
|
|
405
|
+
# Ensure the file, bar, has specific relationships with many other resources
|
|
406
|
+
bar:
|
|
407
|
+
that_requires:
|
|
408
|
+
- File[fim]
|
|
409
|
+
- File[fu]
|
|
410
|
+
that_comes_before:
|
|
411
|
+
- File[fam]
|
|
412
|
+
- File[far]
|
|
413
|
+
that_notifies:
|
|
414
|
+
- File[fiz]
|
|
415
|
+
- File[faz]
|
|
416
|
+
that_subscribes_to:
|
|
417
|
+
- File[fuz]
|
|
418
|
+
- File[fez]
|
|
419
|
+
|
|
420
|
+
contain_notify:
|
|
421
|
+
bar:
|
|
422
|
+
that_comes_before: Notify[foo]
|
|
423
|
+
foo:
|
|
424
|
+
that_requires: Notify[bar]
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
#### The count matcher
|
|
428
|
+
|
|
429
|
+
Ruby:
|
|
430
|
+
|
|
431
|
+
```ruby
|
|
432
|
+
# Ensure certain resource counts are true
|
|
433
|
+
describe "my_module" {
|
|
434
|
+
it { is_expected.to have_resource_count(2) }
|
|
435
|
+
|
|
436
|
+
it { is_expected.to have_class_count(2) }
|
|
437
|
+
|
|
438
|
+
it { is_expected.to have_exec_resource_count(1) }
|
|
439
|
+
|
|
440
|
+
it { is_expected.to have_logrotate__rule_resource_count(3) }
|
|
441
|
+
}
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
YAML:
|
|
445
|
+
|
|
446
|
+
```yaml
|
|
447
|
+
# Ensure certain resource counts are true
|
|
448
|
+
describe:
|
|
449
|
+
name: my_module
|
|
450
|
+
tests:
|
|
451
|
+
have_resource_count: 2
|
|
452
|
+
have_class_count: 2
|
|
453
|
+
have_exec_resource_count: 1
|
|
454
|
+
have_logrotate__rule_resource_count: 3
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
#### Type alias matchers
|
|
458
|
+
|
|
459
|
+
Ruby:
|
|
460
|
+
|
|
461
|
+
```ruby
|
|
462
|
+
describe 'MyModule::Shape' do
|
|
463
|
+
it { is_expected.to allow_value('square') }
|
|
464
|
+
it { is_expected.to allow_values('circle', 'triangle') }
|
|
465
|
+
it { is_expected.not_to allow_value('blue') }
|
|
466
|
+
end
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
YAML:
|
|
470
|
+
|
|
471
|
+
```yaml
|
|
472
|
+
describe:
|
|
473
|
+
name: MyModule::Shape
|
|
474
|
+
tests:
|
|
475
|
+
be_valid_type:
|
|
476
|
+
allow_value: square
|
|
477
|
+
allow_values:
|
|
478
|
+
- circle
|
|
479
|
+
- triangle
|
|
480
|
+
'!be_valid_type': # The leading ! switches is_expected.to to is_expected.not_to
|
|
481
|
+
allow_value: blue
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
#### Function matchers
|
|
485
|
+
|
|
486
|
+
Ruby:
|
|
487
|
+
|
|
488
|
+
```ruby
|
|
489
|
+
describe 'my_function' {
|
|
490
|
+
it { is_expected.to run.with_params('foo').and_return('bar') }
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
describe 'my_other_function' {
|
|
494
|
+
it { is_expected.to run.with_params('foo', 'bar', ['baz']) }
|
|
495
|
+
}
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
YAML:
|
|
499
|
+
|
|
500
|
+
```yaml
|
|
501
|
+
describe:
|
|
502
|
+
'my_function':
|
|
503
|
+
tests:
|
|
504
|
+
run:
|
|
505
|
+
with_params: foo
|
|
506
|
+
and_return: bar
|
|
507
|
+
run:
|
|
508
|
+
'my_other_function':
|
|
509
|
+
tests:
|
|
510
|
+
run:
|
|
511
|
+
with_params:
|
|
512
|
+
- foo
|
|
513
|
+
- bar
|
|
514
|
+
- ['baz']
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
##### Negative tests and error matching
|
|
518
|
+
|
|
519
|
+
Ruby:
|
|
520
|
+
|
|
521
|
+
```ruby
|
|
522
|
+
describe 'my_function' {
|
|
523
|
+
it { is_expected.not_to run.with_params('a').and_raise_error(Puppet::ParseError) }
|
|
524
|
+
}
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
YAML:
|
|
528
|
+
|
|
529
|
+
```yaml
|
|
530
|
+
describe:
|
|
531
|
+
name: my_function
|
|
532
|
+
tests:
|
|
533
|
+
'!run': # Negate a test with a ! prefix, but `'` or `"` demarcate the matcher name becaus a leading ! in YAML denotes a data-type specification.
|
|
534
|
+
with_params: a
|
|
535
|
+
and_raise_error: !ruby/exception Puppet::ParseError
|
|
536
|
+
run:
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
#### Using `before` and `after`
|
|
540
|
+
|
|
541
|
+
Ruby:
|
|
542
|
+
|
|
543
|
+
```ruby
|
|
544
|
+
describe 'my_function' {
|
|
545
|
+
before(:each) { scope.expects(:lookupvar).with('some_variable').returns('some_value') }
|
|
546
|
+
it { is_expected.to run.with_params('...').and_return('...') }
|
|
547
|
+
}
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
YAML:
|
|
551
|
+
|
|
552
|
+
YAML indirectly supports executable code in RSpec's `before` and `after` blocks.
|
|
553
|
+
It is emulated by first writing a *global* scope function in your `*_spec.rb`
|
|
554
|
+
file and then calling that function from the YAML file, as shown in these two
|
|
555
|
+
snippets:
|
|
556
|
+
|
|
557
|
+
spec/function/my_function_spec.rb
|
|
558
|
+
|
|
559
|
+
```ruby
|
|
560
|
+
require 'spec_helper'
|
|
561
|
+
|
|
562
|
+
def my_before
|
|
563
|
+
scope.expects(:lookupvar).with('some_variable').returns('some_value')
|
|
564
|
+
end
|
|
565
|
+
|
|
566
|
+
parse_yaml_from_spec(__FILE__)
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
spec/function/my_function_spec.yaml
|
|
570
|
+
|
|
571
|
+
```yaml
|
|
572
|
+
describe:
|
|
573
|
+
name: my_function
|
|
574
|
+
before: my_before
|
|
575
|
+
tests:
|
|
576
|
+
run:
|
|
577
|
+
with_params: ...
|
|
578
|
+
and_return: ...
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
Note that `before:` can specify an Array of global-scope functions to call,
|
|
582
|
+
though it may be more intuitive to just define a global-scope function which
|
|
583
|
+
calls all of the other functions you need to chain together. Or not. It
|
|
584
|
+
depends on your logic.
|
|
585
|
+
|
|
586
|
+
This technique also helps define custom functions for your tests.
|
|
587
|
+
|
|
588
|
+
#### Hiera integration
|
|
589
|
+
|
|
590
|
+
Apart from ensuring that your `metadata.json` file is valid, you just don't need
|
|
591
|
+
to do anything at all to enable module-level Hiera data integration; it's
|
|
592
|
+
already built into rspec-puppet and it runs quite well without any additional
|
|
593
|
+
configuration. If however, you have a valid use-case for customizing Hiera in
|
|
594
|
+
order to test out-of-module data -- which should be a really hard sell since you
|
|
595
|
+
should be testing your Module's code, not any out-of-module Hiera data -- the
|
|
596
|
+
above documentation should be adequate to express such custom Hiera
|
|
597
|
+
configuration, whether via `let` settings or custom functions run during
|
|
598
|
+
`begin`. Should you find the existing support to be inadequate, feel free to
|
|
599
|
+
open a qualifying Pull Request that adds whatever additional support you need.
|
|
600
|
+
|
|
601
|
+
#### Unsupported RSpec Puppet Features
|
|
602
|
+
|
|
603
|
+
This extension does not support the following features found in rspec-puppet:
|
|
604
|
+
|
|
605
|
+
1. There is no way to create an example `it` that uses the `expect()` function
|
|
606
|
+
instead of `is_expected`.
|
|
607
|
+
2. With the highest available version of rspec-puppet at the time of this
|
|
608
|
+
writing, there doesn't seem to be any way to use
|
|
609
|
+
`subject { exported_resources }` because rspec-puppet throws an error message
|
|
610
|
+
when you try, even when you follow its advice as to where to place the
|
|
611
|
+
`subject`. You can still add `subject` to your YAML; it's just that
|
|
612
|
+
rspec-puppet's `exported_resources` doesn't seem to be accessible from any
|
|
613
|
+
`subject` today.
|
|
614
|
+
3. In the Function matcher, lambdas are not known to be supported via YAML. So,
|
|
615
|
+
the rspec-puppet example showing `run.with_lambda` has no obvious equivalent
|
|
616
|
+
in rspec-puppet-yaml. A clever application of the `%{eval:...}` expander
|
|
617
|
+
might help in some cases, but feel free to experiment and share back if you
|
|
618
|
+
find a way to make this work.
|
|
619
|
+
|
|
620
|
+
#### Variants
|
|
621
|
+
|
|
622
|
+
This gem adds a new feature that isn't present in the gem it extends:
|
|
623
|
+
`variants`. A variant in unit testing is a named repeat of its parent container
|
|
624
|
+
with certain inputs and expectations tweaked. For example, imagine you have 10
|
|
625
|
+
base test examples and you want to test the limits of one input while
|
|
626
|
+
simultaneously ensuring there are no unintended side-effects (so, you want to
|
|
627
|
+
re-run the other 9 tests for each iteration of changes to the input-under-test).
|
|
628
|
+
Without variants, you'd have to duplicate those other 9 test examples over and
|
|
629
|
+
over. Variants eliminate all that dupliation in your test definitions, handling
|
|
630
|
+
the repeating configuration for you.
|
|
631
|
+
|
|
632
|
+
Here's an example of a classic `package.pp` that enables customization of the
|
|
633
|
+
single package's `ensure` attribute. The parent context defines two matcher
|
|
634
|
+
tests, `have_package_resource_count` and `contain_package`. Each variant
|
|
635
|
+
inherits both, then tweaks one aspect or another of the parent examples.
|
|
636
|
+
Further, a separate context, `package.pp negative tests` does not inherit any
|
|
637
|
+
tests from the `package.pp` context; they are peers rather than dependents.
|
|
638
|
+
|
|
639
|
+
```yaml
|
|
640
|
+
describe:
|
|
641
|
+
name: my-package
|
|
642
|
+
context:
|
|
643
|
+
'package.pp':
|
|
644
|
+
tests:
|
|
645
|
+
have_package_resource_count: 1
|
|
646
|
+
contain_package:
|
|
647
|
+
my-package: present
|
|
648
|
+
variants: # These will all inherit the parent's tests, tweaking as needed
|
|
649
|
+
'uninstall':
|
|
650
|
+
let:
|
|
651
|
+
params:
|
|
652
|
+
package_ensure: absent
|
|
653
|
+
tests:
|
|
654
|
+
contain_package:
|
|
655
|
+
my-package: absent
|
|
656
|
+
'pinned package version':
|
|
657
|
+
let:
|
|
658
|
+
params:
|
|
659
|
+
package_ensure: '1.0.0.el7'
|
|
660
|
+
tests:
|
|
661
|
+
contain_package:
|
|
662
|
+
my-package: '1.0.0.el7'
|
|
663
|
+
|
|
664
|
+
'package.pp negative tests':
|
|
665
|
+
variants:
|
|
666
|
+
'bad package_ensure':
|
|
667
|
+
let:
|
|
668
|
+
params:
|
|
669
|
+
package_ensure: 2.10
|
|
670
|
+
tests:
|
|
671
|
+
compile:
|
|
672
|
+
and_raise_error: !ruby/regexp /parameter 'package_ensure' expects a String value, got Float/
|
|
673
|
+
|
|
674
|
+
```
|
|
675
|
+
|
|
676
|
+
Note that `variants` inherit everything from their parent `describe` or
|
|
677
|
+
`context` except the following attributes:
|
|
678
|
+
|
|
679
|
+
* variants
|
|
680
|
+
* before
|
|
681
|
+
* after
|
|
682
|
+
* subject
|
|
683
|
+
|
|
684
|
+
### Style Choices
|
|
685
|
+
|
|
686
|
+
Many of the elements can be expressed in multiple ways to achieve the same
|
|
687
|
+
effect. For example, containers like `describe`, `context`, and `variants` can
|
|
688
|
+
be listed as more-than-one using either of these forms:
|
|
689
|
+
|
|
690
|
+
```yaml
|
|
691
|
+
# As implicity named Hash entities
|
|
692
|
+
describe:
|
|
693
|
+
name: my_entity
|
|
694
|
+
context:
|
|
695
|
+
'context 1 name':
|
|
696
|
+
attributes...
|
|
697
|
+
'context 2 name':
|
|
698
|
+
attributes...
|
|
699
|
+
|
|
700
|
+
# As explicitly named Hash entities
|
|
701
|
+
describe:
|
|
702
|
+
name: my_entity
|
|
703
|
+
context:
|
|
704
|
+
- name: context 1 name
|
|
705
|
+
attributes...
|
|
706
|
+
- name: context 2 name
|
|
707
|
+
attributes...
|
|
708
|
+
```
|
|
709
|
+
|
|
710
|
+
Keys can also be expressed as symbols, strings, or any combination of them:
|
|
711
|
+
|
|
712
|
+
```yaml
|
|
713
|
+
# Keys as strings
|
|
714
|
+
describe:
|
|
715
|
+
name: my_entity
|
|
716
|
+
context:
|
|
717
|
+
- name: context name
|
|
718
|
+
attribute: value
|
|
719
|
+
|
|
720
|
+
# Keys as symbols
|
|
721
|
+
:describe:
|
|
722
|
+
:name: my_entity
|
|
723
|
+
:context:
|
|
724
|
+
- :name: context name
|
|
725
|
+
:attribute: value
|
|
726
|
+
```
|
|
727
|
+
|
|
728
|
+
## Development
|
|
729
|
+
|
|
730
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
|
731
|
+
`bundle install && bundle exec rake spec` to run the tests. Run `yard` to
|
|
732
|
+
generate HTML documentation files (these generated files are ignored by git, so
|
|
733
|
+
this is useful for local preview). You can also run `bin/console` for an
|
|
734
|
+
interactive prompt that will allow you to experiment.
|
|
735
|
+
|
|
736
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
737
|
+
release a new version, update the version number in `version.rb`, and then run
|
|
738
|
+
`bundle exec rake release`, which will create a git tag for the version, push
|
|
739
|
+
git commits and tags, and push the `.gem` file to
|
|
740
|
+
[rubygems.org](https://rubygems.org).
|
|
741
|
+
|
|
742
|
+
[API documentation](https://wwkimball.github.io/rspec-puppet-yaml/docs/index.html) is available at github.io.
|
|
743
|
+
|
|
744
|
+
## Contributing
|
|
745
|
+
|
|
746
|
+
Bug reports and pull requests are welcome on GitHub at
|
|
747
|
+
https://github.com/wwkimball/rspec-puppet-yaml. Contributors are expected to
|
|
748
|
+
adhere to the [Contributor Covenant](http://contributor-covenant.org).
|
|
749
|
+
|
|
750
|
+
## License
|
|
751
|
+
|
|
752
|
+
The gem is available as open source under the terms of the
|
|
753
|
+
[MIT License](http://opensource.org/licenses/MIT).
|
|
754
|
+
|
|
755
|
+
## Code of Conduct
|
|
756
|
+
|
|
757
|
+
Everyone interacting in the rspec-puppet-yaml project’s codebases, issue
|
|
758
|
+
trackers, chat rooms and mailing lists is expected to follow the
|
|
759
|
+
[code of conduct](https://github.com/wwkimball/rspec-puppet-yaml/blob/master/CODE_OF_CONDUCT.md).
|