{"id":14991356,"url":"https://github.com/wwkimball/rspec-puppet-yaml","last_synced_at":"2025-04-12T03:42:09.572Z","repository":{"id":45649431,"uuid":"101695324","full_name":"wwkimball/rspec-puppet-yaml","owner":"wwkimball","description":"Enable writing rspec-puppet tests in YAML rather than Ruby","archived":false,"fork":false,"pushed_at":"2021-12-02T17:55:29.000Z","size":164,"stargazers_count":3,"open_issues_count":4,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-03-25T23:22:13.541Z","etag":null,"topics":["puppet","rspec","rspec-puppet","ruby"],"latest_commit_sha":null,"homepage":null,"language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/wwkimball.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE.txt","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null},"funding":{"github":["wwkimball"]}},"created_at":"2017-08-28T22:56:39.000Z","updated_at":"2020-10-28T18:15:58.000Z","dependencies_parsed_at":"2022-08-20T16:10:39.047Z","dependency_job_id":null,"html_url":"https://github.com/wwkimball/rspec-puppet-yaml","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wwkimball%2Frspec-puppet-yaml","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wwkimball%2Frspec-puppet-yaml/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wwkimball%2Frspec-puppet-yaml/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wwkimball%2Frspec-puppet-yaml/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/wwkimball","download_url":"https://codeload.github.com/wwkimball/rspec-puppet-yaml/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248514221,"owners_count":21116899,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["puppet","rspec","rspec-puppet","ruby"],"created_at":"2024-09-24T14:27:30.406Z","updated_at":"2025-04-12T03:42:09.554Z","avatar_url":"https://github.com/wwkimball.png","language":"Ruby","funding_links":["https://github.com/sponsors/wwkimball"],"categories":[],"sub_categories":[],"readme":"# rspec-puppet-yaml\n\n[![Build Status](https://travis-ci.org/wwkimball/rspec-puppet-yaml.svg?branch=master)](https://travis-ci.org/wwkimball/rspec-puppet-yaml)\n[![Documentation Coverage](https://inch-ci.org/github/wwkimball/rspec-puppet-yaml.svg?branch=master)](https://inch-ci.org/github/wwkimball/rspec-puppet-yaml)\n\nThis gem enables Puppet module authors to write RSpec unit tests as YAML instead\nof Ruby (omitting the single, trivial line of code necessary to pass your YAML\nto RSpec).  It also adds a new capability:  a trivial means to create test\nvariants (repeat 0-N tests while tweaking any set of inputs and expectations to\ndetect unintended side-effects).  If you're more comfortable with YAML than\nRuby, or want to easily work with test variants, then you'll want this gem.\n\nWhile this gem spares you from needing to learn Ruby just to test your Puppet\nmodule, you still need to do a little research into what RSpec tests are\navailable to your YAML.  The good news is [that very knowledge is already\ndocumented for the rspec-puppet gem](https://github.com/rodjek/rspec-puppet/blob/master/README.md#matchers)\nand you just need to know how to translate it into YAML.  While not initially\nobvious, it really is very easy.\n\nThe source code examples in this document are expanded or direct translations of\neach of the matcher samples that are shown in the rspec-puppet README.\nCopyright for the original examples is owned by the maintainers of that gem and\nare duplicated here in good faith that these translations into YAML are *not*\ncompetitive and will benefit our common audience.  This rpsec-puppet-yaml gem\nextends the reach of the rspec-puppet gem to include users who speak YAML better\nthan Ruby; rpsec-puppet-yaml does not replace rspec-puppet but rather the\nwritten language that is necessary to interact with it (YAML rather than Ruby).\n\nAll following examples will be presented both in the original Ruby and in the\nnew YAML for comparison.  In the most general terms, you can express an existing\nRSpec entity in YAML simply by transcribing its name as a Hash key and its\nattributes or contents as its children.  Just know that I decided to use `tests`\ninstead of `it` to identify the RSpec examples and I abstracted `is_expected.to`\nand `is_expected.not_to` rather than exposing them directly to YAML.  This was\nboth an aesthetic decision and to reduce complexity.\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem 'rspec-puppet-yaml'\n```\n\nAnd then execute:\n\n    $ bundle\n\nOr install it yourself as:\n\n    $ gem install rspec-puppet-yaml\n\n## Usage\n\nAs per the [existing documentation for rspec-puppet](https://github.com/rodjek/rspec-puppet/blob/master/README.md#naming-conventions),\nyou will still create RSpec entry-point files at `your_module/spec/**/*_spec.rb`\n(else `rake` won't find your tests, YAML or otherwise).  However, these files\nnow need only two lines of Ruby code (usually) and no RSpec code.  You'll still\nrequire your `spec_helper` as always, and then just call the global-scope\nentry-point function to parse your YAML into RSpec.  The function shown here\nexpects to receive the fully-qualified path and name of your `*_spec.rb` file,\nnot your YAML file.  The function will search for your YAML files based on the\nautomatic value of the `__FILE__` variable.\n\nFor example:\n\n```ruby\nrequire 'spec_helper'\nparse_yaml_from_spec(__FILE__)\n```\n\nYes, that's it!  You can copy-paste those two lines into every `*_spec.rb` file\nand then spend your time writing RSpec Puppet tests in YAML rather than Ruby.\nTo do so, create another file in the same directory with the same base name as\nyour `*_spec.rb` file except change the extension to `.yaml` or `.yml`.  In\nfact, you don't even need the `_spec` part of the name, so for an RSpec file\nnamed `my_module_spec.rb`, you can use any of these YAML file-names:\n\n* `my_module_spec.yaml`\n* `my_module_spec.yml`\n* `my_module.yaml`\n* `my_module.yml`\n\nFair warning:  the parser will look for *all* of these file-names, so if you\ncreate more than one of them, they will all be processed, in turn.\n\n### Defining RSpec Puppet YAML Tests\n\nFor the most general-case example, this Ruby:\n\n```ruby\ndescribe 'my_class', :type =\u003e 'class' {\n  let(:params) { my_attribute =\u003e 'value' }\n\n  it { is_expected.to some_matcher.with_attribute('value').and_attribute }\n\n  it { is_expected.not_to another_matcher}\n}\n```\n\nbecomes this YAML:\n\n```yaml\ndescribe:\n  name: my_class\n  type: class\n  let:\n    params:\n      my_attribute: value\n  tests:\n    some_matcher:\n      with_attribute: value\n      and_attribute: nil      # nil indicates a matcher method with no arguments\n    '!another_matcher': {}    # ! negates the matcher and every matcher needs either a Hash or Array value\n```\n\n**NOTE**:  The top-most entity of every rspec-puppet-yaml file must be a\n`describe`.  Each `describe` must have a `name` attribute.  The top-most\n`describe` must additionally have a `type` attribute unless you arrange your\n`*_spec.rb` files according to the recommended rspec-puppet directory structure\n(in which case the class can be automatically derived from its file-system\nlocation).\n\n#### Setting custom facts, parameters, titles, and any other `let` setting\n\nRuby:\n\n```ruby\ndescribe 'my_module' do\n  let(:title) { 'baz' }\n\n  let(:params) do\n    { 'value'   =\u003e 'foo',\n      'user'    =\u003e :undef,\n      'require' =\u003e ref('Package', 'sudoku'),\n      'nodes'   =\u003e {\n        ref('Node', 'dbnode') =\u003e ref('Myapp::Mycomponent', 'myapp')\n      }\n    }\n  end\n\n  let(:node) { 'testhost.example.com' }\n\n  let(:environment) { 'production' }\n\n  let(:facts) do\n    { 'os' =\u003e {\n        'family'  =\u003e 'RedHat',\n        'release' =\u003e {\n          'major' =\u003e '7',\n          'minor' =\u003e '1',\n          'full'  =\u003e '7.1.1503'\n        }\n      }\n    }\n  end\n\n  let(:node_params) do\n    { 'hostgroup' =\u003e 'webservers',\n      'rack'      =\u003e 'KK04',\n      'status'    =\u003e 'maintenance' }\n  end\n\n  let(:pre_condition) { 'include other_class' }\n\n  let(:post_condition) { 'include another_class' }\n\n  let(:module_path) { '/path/to/your/module/dir' }\n\n  let(:trusted_facts) do\n    { 'pp_uuid' =\u003e 'ED803750-E3C7-44F5-BB08-41A04433FE2E',\n      '1.3.6.1.4.1.34380.1.2.1' =\u003e 'ssl-termination' }\n  end\nend\n```\n\nYAML:\n\n```yaml\ndescribe:\n  name: my_module\n  let:\n    title: baz\n    node: testhost.example.com\n    environment: production\n    params:\n      value: foo\n      user: !ruby/symbol undef  # The symbol-form of `undef` must be used to specify an :undef value\n      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 `%`.\n      nodes:\n        '%{eval:ref(\"Node\", \"dbnode\")}': '%{eval:ref(\"Myapp::Mycomponent\", \"myapp\")}'\n    facts:\n      os:\n        family: RedHat\n        release:\n          major: 7\n          minor: 1\n          full: 7.1.1503\n    node_params:\n      hostgroup: webservers\n      rack: KK04\n      status: maintenance\n    pre_condition: include other_class\n    post_condition: include another_class\n    module_path: /path/to/your/module/dir\n    trusted_facts:\n      pp_uuid: ED803750-E3C7-44F5-BB08-41A04433FE2E\n      '1.3.6.1.4.1.34380.1.2.1': ssl-termination\n```\n\n#### The compile matcher\n\nRuby:\n\n```ruby\n# Plain test to ensure the module compiles without error\ndescribe \"my_module\" {\n  it { is_expected.to compile }\n}\n\n# Expanded test to ensure it compiles with all dependencies\ndescribe \"my_module\" {\n  it { is_expected.to compile.with_all_deps }\n}\n\n# Ensure the module *fails* to compile, issuing an expected error message\ndescribe \"my_module\" {\n  it { is_expected.to compile.and_raise_error(/error message match/) }\n}\n```\n\nYAML:\n\n```yaml\n# Plain test to ensure the module compiles without error\ndescribe:\n  name: my_module\n  tests:\n    compile: {}\n\n\n# Expanded test to ensure it compiles with all dependencies\n# Multiple ways to achieve the same net effect!\ndescribe:\n  name: my_module\n  tests:\n    compile: true  # `with_all_deps` is the default test when the `compile` matcher is given any \"truthy\" value\n\ndescribe:\n  name: my_module\n  tests:\n    compile:  # Methods that don't accept arguments can be called from an Array\n      - with_all_deps\n\ndescribe:\n  name: my_module\n  tests:\n    compile:  # Methods that don't accept arguments can be called from a Hash with nil as their value\n      with_all_deps: nil\n\n\n# Ensure the module *fails* to compile, issuing an expected error message.\n# Method 1:  Use a Regular Expression to match part of the error message.  Note\n# that in this case, you must identify your value as being a Ruby Regular\n# Expression data type with the leading `!ruby/regexp` marker.\ndescribe:\n  name: my_module\n  tests:\n    compile:\n      and_raise_error: !ruby/regexp /error message match/\n\n# Method 2:  Match the *entire* error message, not just part of it.  This\n# employs a normal String value that requires no additional marker.\ndescribe:\n  name: my_module\n  tests:\n    compile:\n      and_raise_error: FULL text of the error message to match\n```\n\n#### The contain (or create) matcher\n\nRuby:\n\n```ruby\n# Ensure a set of resources exist in the manifest, some with specific attributes\ndescribe \"my_module\" {\n  it { is_expected.to contain_augeas('bleh') }\n\n  it { is_expected.to contain_class('foo') }\n\n  it { is_expected.to contain_foo__bar('baz') }\n\n  it { is_expected.to contain_package('mysql-server').with_ensure('present') }\n\n  it { is_expected.to contain_package('httpd').only_with_ensure('latest') }\n\n  it do\n    is_expected.to contain_service('keystone').with(\n      'ensure'     =\u003e 'running',\n      'enable'     =\u003e 'true',\n      'hasstatus'  =\u003e 'true',\n      'hasrestart' =\u003e 'true'\n    )\n  end\n\n  it do\n    is_expected.to contain_user('luke').only_with(\n      'ensure' =\u003e 'present',\n      'uid'    =\u003e '501'\n    )\n  end\n\n  it { is_expected.to contain_file('/foo/bar').without_mode }\n\n  it { is_expected.to contain_service('keystone_2').without(\n    ['restart', 'status']\n  )}\n\n  it { is_expected.to contain_file('/path/file').with_content(/foobar/) }\n\n  it do\n    is_expected.to contain_file('/other/path/file')\n      .with_content(/foobar/)\n      .with_content(/bazbar/)\n  end\n}\n```\n\nYAML:\n\n```yaml\n# Ensure a set of resources exist in the manifest, some with specific attributes\ndescribe:\n  name: my_module\n  tests:\n    contain_augeas:\n      bleh: {}\n    contain_class:\n      foo: {}\n    contain_foo__bar:\n      baz: {}\n    contain_package:\n      mysql-server: present  # `with_ensure` is the default test when packages are given any scalar value\n      httpd:\n        only_with_ensure: latest\n    contain_service:\n      keystone:\n        with:\n          ensure: running\n          enable: true\n          hasstatus: true\n          hasrestart: true\n      keystone_2:\n        without:\n          - restart\n          - status\n    contain_user:\n      luke:\n        only_with:\n          ensure: present\n          uid: 501\n    contain_file:\n      /foo/bar:\n        - without_mode\n      /path/file:\n        with_content: !ruby/regexp /foobar/\n      /other/path/file:\n        with_content:\n          - !ruby/regexp /foobar/\n          - !ruby/regexp /bazbar/\n```\n\n##### With stipulated resource relationships\n\nRuby:\n\n```ruby\ndescribe \"my_module\" {\n  # Ensure the file, foo, has specific relationships with other resources\n  it { is_expected.to contain_file('foo').that_requires('File[bar]') }\n\n  it { is_expected.to contain_file('foo').that_comes_before('File[baz]') }\n\n  it { is_expected.to contain_file('foo').that_notifies('File[bim]') }\n\n  it { is_expected.to contain_file('foo').that_subscribes_to('File[bom]') }\n\n\n  # Ensure the file, bar, has specific relationships with many other resources\n  it { is_expected.to contain_file('bar').that_requires(['File[fim]', 'File[fu]']) }\n\n  it { is_expected.to contain_file('bar').that_comes_before(['File[fam]','File[far]']) }\n\n  it { is_expected.to contain_file('bar').that_notifies(['File[fiz]', 'File[faz]']) }\n\n  it { is_expected.to contain_file('bar').that_subscribes_to(['File[fuz]', 'File[fez]']) }\n\n  # Other relationship example\n  it { is_expected.to contain_notify('bar').that_comes_before('Notify[foo]') }\n\n  it { is_expected.to contain_notify('foo').that_requires('Notify[bar]') }\n}\n```\n\nYAML:\n\n```yaml\ndescribe:\n  name: my_module\n  tests:\n    contain_file:\n      # Ensure the file, foo, has specific relationships with other resources\n      foo:\n        that_requires: File[bar]\n        that_comes_before: File[baz]\n        that_notifies: File[bim]\n        that_subscribes_to: File[bom]\n\n      # Ensure the file, bar, has specific relationships with many other resources\n      bar:\n        that_requires:\n          - File[fim]\n          - File[fu]\n        that_comes_before:\n          - File[fam]\n          - File[far]\n        that_notifies:\n          - File[fiz]\n          - File[faz]\n        that_subscribes_to:\n          - File[fuz]\n          - File[fez]\n\n    contain_notify:\n      bar:\n        that_comes_before: Notify[foo]\n      foo:\n        that_requires: Notify[bar]\n```\n\n#### The count matcher\n\nRuby:\n\n```ruby\n# Ensure certain resource counts are true\ndescribe \"my_module\" {\n  it { is_expected.to have_resource_count(2) }\n\n  it { is_expected.to have_class_count(2) }\n\n  it { is_expected.to have_exec_resource_count(1) }\n\n  it { is_expected.to have_logrotate__rule_resource_count(3) }\n}\n```\n\nYAML:\n\n```yaml\n# Ensure certain resource counts are true\ndescribe:\n  name: my_module\n  tests:\n    have_resource_count: 2\n    have_class_count: 2\n    have_exec_resource_count: 1\n    have_logrotate__rule_resource_count: 3\n```\n\n#### Type alias matchers\n\nRuby:\n\n```ruby\ndescribe 'MyModule::Shape' do\n  it { is_expected.to allow_value('square') }\n  it { is_expected.to allow_values('circle', 'triangle') }\n  it { is_expected.not_to allow_value('blue') }\nend\n```\n\nYAML:\n\n```yaml\ndescribe:\n  name: MyModule::Shape\n  tests:\n    be_valid_type:\n      allow_value: square\n      allow_values:\n          - circle\n          - triangle\n    '!be_valid_type':  # The leading ! switches is_expected.to to is_expected.not_to\n      allow_value: blue\n```\n\n#### Function matchers\n\nRuby:\n\n```ruby\ndescribe 'my_function' {\n  it { is_expected.to run.with_params('foo').and_return('bar') }\n}\n\ndescribe 'my_other_function' {\n  it { is_expected.to run.with_params('foo', 'bar', ['baz']) }\n}\n```\n\nYAML:\n\n```yaml\ndescribe:\n  'my_function':\n    tests:\n      run:\n        with_params: foo\n        and_return: bar\n      run:\n  'my_other_function':\n    tests:\n      run:\n        with_params:\n          - foo\n          - bar\n          - ['baz']\n```\n\n##### Negative tests and error matching\n\nRuby:\n\n```ruby\ndescribe 'my_function' {\n  it { is_expected.not_to run.with_params('a').and_raise_error(Puppet::ParseError) }\n}\n```\n\nYAML:\n\n```yaml\ndescribe:\n  name: my_function\n  tests:\n    '!run':  # Negate a test with a ! prefix, but `'` or `\"` demarcate the matcher name becaus a leading ! in YAML denotes a data-type specification.\n      with_params: a\n      and_raise_error: !ruby/exception Puppet::ParseError\n    run:\n```\n\n#### Using `before` and `after`\n\nRuby:\n\n```ruby\ndescribe 'my_function' {\n  before(:each) { scope.expects(:lookupvar).with('some_variable').returns('some_value') }\n  it { is_expected.to run.with_params('...').and_return('...') }\n}\n```\n\nYAML:\n\nYAML indirectly supports executable code in RSpec's `before` and `after` blocks.\nIt is emulated by first writing a *global* scope function in your `*_spec.rb`\nfile and then calling that function from the YAML file, as shown in these two\nsnippets:\n\nspec/function/my_function_spec.rb\n\n```ruby\nrequire 'spec_helper'\n\ndef my_before\n  scope.expects(:lookupvar).with('some_variable').returns('some_value')\nend\n\nparse_yaml_from_spec(__FILE__)\n```\n\nspec/function/my_function_spec.yaml\n\n```yaml\ndescribe:\n  name: my_function\n  before: my_before\n  tests:\n    run:\n      with_params: ...\n      and_return: ...\n```\n\nNote that `before:` can specify an Array of global-scope functions to call,\nthough it may be more intuitive to just define a global-scope function which\ncalls all of the other functions you need to chain together.  Or not.  It\ndepends on your logic.\n\nThis technique also helps define custom functions for your tests.\n\n#### Hiera integration\n\nApart from ensuring that your `metadata.json` file is valid, you just don't need\nto do anything at all to enable module-level Hiera data integration; it's\nalready built into rspec-puppet and it runs quite well without any additional\nconfiguration.  If however, you have a valid use-case for customizing Hiera in\norder to test out-of-module data -- which should be a really hard sell since you\nshould be testing your Module's code, not any out-of-module Hiera data -- the\nabove documentation should be adequate to express such custom Hiera\nconfiguration, whether via `let` settings or custom functions run during\n`begin`.  Should you find the existing support to be inadequate, feel free to\nopen a qualifying Pull Request that adds whatever additional support you need.\n\n#### Unsupported RSpec Puppet Features\n\nThis extension does not support the following features found in rspec-puppet:\n\n1. There is no way to create an example `it` that uses the `expect()` function\n   instead of `is_expected`.\n2. With the highest available version of rspec-puppet at the time of this\n   writing, there doesn't seem to be any way to use\n   `subject { exported_resources }` because rspec-puppet throws an error message\n   when you try, even when you follow its advice as to where to place the\n   `subject`.  You can still add `subject` to your YAML; it's just that\n   rspec-puppet's `exported_resources` doesn't seem to be accessible from any\n   `subject` today.\n3. In the Function matcher, lambdas are not known to be supported via YAML.  So,\n   the rspec-puppet example showing `run.with_lambda` has no obvious equivalent\n   in rspec-puppet-yaml.  A clever application of the `%{eval:...}` expander\n   might help in some cases, but feel free to experiment and share back if you\n   find a way to make this work.\n\n#### Variants\n\nThis gem adds a new feature that isn't present in the gem it extends:\n`variants`.  A variant in unit testing is a named repeat of its parent container\nwith certain inputs and expectations tweaked.  For example, imagine you have 10\nbase test examples and you want to test the limits of one input while\nsimultaneously ensuring there are no unintended side-effects (so, you want to\nre-run the other 9 tests for each iteration of changes to the input-under-test).\nWithout variants, you'd have to duplicate those other 9 test examples over and\nover.  Variants eliminate all that dupliation in your test definitions, handling\nthe repeating configuration for you.\n\nHere's an example of a classic `package.pp` that enables customization of the\nsingle package's `ensure` attribute.  The parent context defines two matcher\ntests, `have_package_resource_count` and `contain_package`.  Each variant\ninherits both, then tweaks one aspect or another of the parent examples.\nFurther, a separate context, `package.pp negative tests` does not inherit any\ntests from the `package.pp` context; they are peers rather than dependents.\n\n```yaml\ndescribe:\n  name: my-package\n  context:\n    'package.pp':\n      tests:\n        have_package_resource_count: 1\n        contain_package:\n          my-package: present\n      variants:  # These will all inherit the parent's tests, tweaking as needed\n        'uninstall':\n          let:\n            params:\n              package_ensure: absent\n          tests:\n            contain_package:\n              my-package: absent\n        'pinned package version':\n          let:\n            params:\n              package_ensure: '1.0.0.el7'\n          tests:\n            contain_package:\n              my-package: '1.0.0.el7'\n\n    'package.pp negative tests':\n      variants:\n        'bad package_ensure':\n          let:\n            params:\n              package_ensure: 2.10\n          tests:\n            compile:\n              and_raise_error: !ruby/regexp /parameter 'package_ensure' expects a String value, got Float/\n\n```\n\nNote that `variants` inherit everything from their parent `describe` or\n`context` except the following attributes:\n\n* variants\n* before\n* after\n* subject\n\n### Style Choices\n\nMany of the elements can be expressed in multiple ways to achieve the same\neffect.  For example, containers like `describe`, `context`, and `variants` can\nbe listed as more-than-one using either of these forms:\n\n```yaml\n# As implicity named Hash entities\ndescribe:\n  name: my_entity\n  context:\n    'context 1 name':\n      attributes...\n    'context 2 name':\n      attributes...\n\n# As explicitly named Hash entities\ndescribe:\n  name: my_entity\n  context:\n    - name: context 1 name\n      attributes...\n    - name: context 2 name\n      attributes...\n```\n\nKeys can also be expressed as symbols, strings, or any combination of them:\n\n```yaml\n# Keys as strings\ndescribe:\n  name: my_entity\n  context:\n    - name: context name\n      attribute: value\n\n# Keys as symbols\n:describe:\n  :name: my_entity\n  :context:\n    - :name: context name\n      :attribute: value\n```\n\n## Development\n\nAfter checking out the repo, run `bin/setup` to install dependencies.  Then, run\n`bundle install \u0026\u0026 bundle exec rake spec` to run the tests.  Run `yard` to\ngenerate HTML documentation files (these generated files are ignored by git, so\nthis is useful for local preview).  You can also run `bin/console` for an\ninteractive prompt that will allow you to experiment.\n\nTo install this gem onto your local machine, run `bundle exec rake install`.  To\nrelease a new version, update the version number in `version.rb`, and then run\n`bundle exec rake release`, which will create a git tag for the version, push\ngit commits and tags, and push the `.gem` file to\n[rubygems.org](https://rubygems.org).\n\n[API documentation](https://wwkimball.github.io/rspec-puppet-yaml/docs/index.html) is available at github.io.\n\n## Contributing\n\nBug reports and pull requests are welcome on GitHub at\nhttps://github.com/wwkimball/rspec-puppet-yaml.  Contributors are expected to\nadhere to the [Contributor Covenant](http://contributor-covenant.org).\n\n## License\n\nThe gem is available as open source under the terms of the\n[MIT License](http://opensource.org/licenses/MIT).\n\n## Code of Conduct\n\nEveryone interacting in the rspec-puppet-yaml project’s codebases, issue\ntrackers, chat rooms and mailing lists is expected to follow the\n[code of conduct](https://github.com/wwkimball/rspec-puppet-yaml/blob/master/CODE_OF_CONDUCT.md).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwwkimball%2Frspec-puppet-yaml","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwwkimball%2Frspec-puppet-yaml","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwwkimball%2Frspec-puppet-yaml/lists"}