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.
data/README.md ADDED
@@ -0,0 +1,759 @@
1
+ # rspec-puppet-yaml
2
+
3
+ [![Build Status](https://travis-ci.org/wwkimball/rspec-puppet-yaml.svg?branch=master)](https://travis-ci.org/wwkimball/rspec-puppet-yaml) [![Documentation Coverage](https://inch-ci.org/github/wwkimball/rspec-puppet-yaml.svg?branch=master)](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).