Skip to content

Commit

Permalink
YamlTestDirGenerator (#606)
Browse files Browse the repository at this point in the history
Import the class and documentation from stacker_blueprints.
  • Loading branch information
Lowercases authored and phobologic committed Jun 19, 2018
1 parent d4eccc3 commit 4fbc220
Show file tree
Hide file tree
Showing 2 changed files with 152 additions and 0 deletions.
30 changes: 30 additions & 0 deletions docs/blueprints.rst
Original file line number Diff line number Diff line change
Expand Up @@ -411,3 +411,33 @@ stacker_blueprints repo. For example, see the tests used to test the
.. _output results: https://github.com/cloudtools/stacker_blueprints/tree/master/tests/fixtures/blueprints
.. _Resource Type: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html
.. _Property Type: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-product-property-reference.html

Yaml (stacker) format tests
---------------------------

In order to wrap the `BlueprintTestCase` tests in a format similar to stacker's
stack format, the `YamlDirTestGenerator` class is provided. When subclassed in
a directory, it will search for yaml files in that directory with certain
structure and execute a test case for it. As an example:

.. code-block:: yaml
---
namespace: test
stacks:
- name: test_stack
class_path: stacker_blueprints.s3.Buckets
variables:
var1: val1
When run from nosetests, this will create a template fixture file called
test_stack.json containing the output from the `stacker_blueprints.s3.Buckets`
template.

Examples of using the `YamlDirTestGenerator` class can be found in the
stacker_blueprints repo. For example, see the tests used to test the
`s3.Buckets`_ class and the accompanying `fixture`_. These are
generated from a `subclass of YamlDirTestGenerator`_.

.. _s3.Buckets: https://github.com/cloudtools/stacker_blueprints/blob/yaml-tests/tests/test_s3.yaml
.. _fixture: https://github.com/cloudtools/stacker_blueprints/tree/yaml-tests/tests/fixtures/blueprints/s3_static_website.json
.. _subclass of YamlDirTestGenerator: https://github.com/cloudtools/stacker_blueprints/tree/yaml-tests/tests/__init__.py
122 changes: 122 additions & 0 deletions stacker/blueprints/testutil.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import difflib
import json
import unittest
import os.path
from glob import glob

from stacker.config import parse as parse_config
from stacker.context import Context
from stacker.util import load_object_from_string
from stacker.variables import Variable


def diff(a, b):
Expand Down Expand Up @@ -33,3 +40,118 @@ def assertRenderedBlueprint(self, blueprint): # noqa: N802

self.assertEquals(rendered_dict, expected_dict,
diff(rendered_text, expected_text))


class YamlDirTestGenerator(object):
"""Generate blueprint tests from yaml config files.
This class creates blueprint tests from yaml files with a syntax similar to
stackers' configuration syntax. For example,
---
namespace: test
stacks:
- name: test_sample
class_path: stacker_blueprints.test.Sample
variables:
var1: value1
will create a test for the specified blueprint, passing that variable as
part of the test.
The test will generate a .json file for this blueprint, and compare it with
the stored result.
By default, the generator looks for files named 'test_*.yaml' in its same
directory. In order to use it, subclass it in a directory containing such
tests, and name the class with a pattern that will include it in nosetests'
tests (for example, TestGenerator).
The subclass may override some properties:
@property base_class: by default, the generated tests are subclasses of
stacker.blueprints.testutil.BlueprintTestCase. In order to change this,
set this property to the desired base class.
@property yaml_dirs: by default, the directory where the generator is
subclassed is searched for test files. Override this array for specifying
more directories. These must be relative to the directory in which the
subclass lives in. Globs may be used.
Default: [ '.' ]. Example override: [ '.', 'tests/*/' ]
@property yaml_filename: by default, the generator looks for files named
'test_*.yaml'. Use this to change this pattern. Globs may be used.
There's an example of this use in the tests/ subdir of stacker_blueprints.
"""

def __init__(self):
self.classdir = os.path.relpath(
self.__class__.__module__.replace('.', '/'))
if not os.path.isdir(self.classdir):
self.classdir = os.path.dirname(self.classdir)

# These properties can be overriden from the test generator subclass.
@property
def base_class(self):
return BlueprintTestCase

@property
def yaml_dirs(self):
return ['.']

@property
def yaml_filename(self):
return 'test_*.yaml'

def test_generator(self):
# Search for tests in given paths
configs = []
for d in self.yaml_dirs:
configs.extend(
glob('%s/%s/%s' % (self.classdir, d, self.yaml_filename)))

class ConfigTest(self.base_class):
def __init__(self, config, stack, filepath):
self.config = config
self.stack = stack
self.description = "%s (%s)" % (stack.name, filepath)

def __call__(self):
# Use the context property of the baseclass, if present.
# If not, default to a basic context.
try:
ctx = self.context
except AttributeError:
ctx = Context(config=self.config,
environment={'environment': 'test'})

configvars = self.stack.variables or {}
variables = [Variable(k, v) for k, v in configvars.iteritems()]

blueprint_class = load_object_from_string(
self.stack.class_path)
blueprint = blueprint_class(self.stack.name, ctx)
blueprint.resolve_variables(variables or [])
blueprint.setup_parameters()
blueprint.create_template()
self.assertRenderedBlueprint(blueprint)

def assertEquals(self, a, b, msg): # noqa: N802
assert a == b, msg

for f in configs:
with open(f) as test:
config = parse_config(test.read())
config.validate()

for stack in config.stacks:
# Nosetests supports "test generators", which allows us to
# yield a callable object which will be wrapped as a test
# case.
#
# http://nose.readthedocs.io/en/latest/writing_tests.html#test-generators
yield ConfigTest(config, stack, filepath=f)

0 comments on commit 4fbc220

Please sign in to comment.