Skip to content

Commit

Permalink
Bring back tests key in test yaml spec
Browse files Browse the repository at this point in the history
  • Loading branch information
juliocc committed Dec 5, 2022
1 parent 4ca7f12 commit e05fc12
Show file tree
Hide file tree
Showing 7 changed files with 284 additions and 248 deletions.
18 changes: 13 additions & 5 deletions tests/collectors.py
Expand Up @@ -11,6 +11,13 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Pytest plugin to discover tests specified in YAML files.
This plugin uses the pytest_collect_file hook to collect all files
matching tftest*.yaml and runs plan_validate for each test found.
See FabricTestFile for details on the file structure.
"""

import pytest
import yaml
Expand All @@ -23,11 +30,11 @@ class FabricTestFile(pytest.File):
def collect(self):
"""Read yaml test spec and yield test items for each test definition.
Test spec should contain a `module` key with the path of the
The test spec should contain a `module` key with the path of the
terraform module to test, relative to the root of the repository
All other top-level keys in the yaml are taken as test names, and
should have the following structure:
Tests are defined within the top-level `tests` key, and should
have the following structure:
test-name:
tfvars:
Expand All @@ -42,20 +49,21 @@ def collect(self):
will be taken from the file test-name.yaml
"""

try:
raw = yaml.safe_load(self.path.open())
module = raw.pop('module')
except (IOError, OSError, yaml.YAMLError) as e:
raise Exception(f'cannot read test spec {self.path}: {e}')
except KeyError as e:
raise Exception(f'`module` key not found in {self.path}: {e}')
for test_name, spec in raw.items():
for test_name, spec in raw.get('tests', {}).items():
inventories = spec.get('inventory', [f'{test_name}.yaml'])
try:
tfvars = spec['tfvars']
except KeyError:
raise Exception(
f'test definition `{test_name}` in {self.path} does not contain a `tfvars` key'
f'test `{test_name}` in {self.path} does not contain a `tfvars` key'
)
for i in inventories:
name = test_name
Expand Down
147 changes: 6 additions & 141 deletions tests/conftest.py
Expand Up @@ -11,147 +11,12 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
'Shared fixtures.'

import inspect
import os
import shutil
import tempfile
'Pytest configuration.'

import pytest
import tftest
import yaml

BASEDIR = os.path.dirname(os.path.dirname(__file__))

pytest_plugins = ('tests.fixtures', 'tests.collectors')


@pytest.fixture(scope='session')
def _plan_runner():
'Return a function to run Terraform plan on a fixture.'

def run_plan(fixture_path=None, extra_files=None, tf_var_file=None,
targets=None, refresh=True, tmpdir=True, **tf_vars):
'Run Terraform plan and returns parsed output.'
if fixture_path is None:
# find out the fixture directory from the caller's directory
caller = inspect.stack()[2]
fixture_path = os.path.join(os.path.dirname(caller.filename), 'fixture')

fixture_parent = os.path.dirname(fixture_path)
fixture_prefix = os.path.basename(fixture_path) + '_'
with tempfile.TemporaryDirectory(prefix=fixture_prefix,
dir=fixture_parent) as tmp_path:
# copy fixture to a temporary directory so we can execute
# multiple tests in parallel
if tmpdir:
shutil.copytree(fixture_path, tmp_path, dirs_exist_ok=True)
tf = tftest.TerraformTest(tmp_path if tmpdir else fixture_path, BASEDIR,
os.environ.get('TERRAFORM', 'terraform'))
tf.setup(extra_files=extra_files, upgrade=True)
plan = tf.plan(output=True, refresh=refresh, tf_var_file=tf_var_file,
tf_vars=tf_vars, targets=targets)
return plan

return run_plan


@pytest.fixture(scope='session')
def plan_runner(_plan_runner):
'Return a function to run Terraform plan on a module fixture.'

def run_plan(fixture_path=None, extra_files=None, tf_var_file=None,
targets=None, **tf_vars):
'Run Terraform plan and returns plan and module resources.'
plan = _plan_runner(fixture_path, extra_files=extra_files,
tf_var_file=tf_var_file, targets=targets, **tf_vars)
# skip the fixture
root_module = plan.root_module['child_modules'][0]
return plan, root_module['resources']

return run_plan


@pytest.fixture(scope='session')
def e2e_plan_runner(_plan_runner):
'Return a function to run Terraform plan on an end-to-end fixture.'

def run_plan(fixture_path=None, tf_var_file=None, targets=None, refresh=True,
include_bare_resources=False, **tf_vars):
'Run Terraform plan on an end-to-end module using defaults, returns data.'
plan = _plan_runner(fixture_path, tf_var_file=tf_var_file, targets=targets,
refresh=refresh, **tf_vars)
# skip the fixture
root_module = plan.root_module['child_modules'][0]
modules = dict((mod['address'], mod['resources'])
for mod in root_module['child_modules'])
resources = [r for m in modules.values() for r in m]
if include_bare_resources:
bare_resources = root_module['resources']
resources.extend(bare_resources)
return modules, resources

return run_plan


@pytest.fixture(scope='session')
def recursive_e2e_plan_runner(_plan_runner):
"""
Plan runner for end-to-end root module, returns total number of
(nested) modules and resources
"""

def walk_plan(node, modules, resources):
new_modules = node.get('child_modules', [])
resources += node.get('resources', [])
modules += new_modules
for module in new_modules:
walk_plan(module, modules, resources)

def run_plan(fixture_path=None, tf_var_file=None, targets=None, refresh=True,
include_bare_resources=False, compute_sums=True, tmpdir=True,
**tf_vars):
'Run Terraform plan on a root module using defaults, returns data.'
plan = _plan_runner(fixture_path, tf_var_file=tf_var_file, targets=targets,
refresh=refresh, tmpdir=tmpdir, **tf_vars)
modules = []
resources = []
walk_plan(plan.root_module, modules, resources)
return len(modules), len(resources)

return run_plan


@pytest.fixture(scope='session')
def apply_runner():
'Return a function to run Terraform apply on a fixture.'

def run_apply(fixture_path=None, **tf_vars):
'Run Terraform plan and returns parsed output.'
if fixture_path is None:
# find out the fixture directory from the caller's directory
caller = inspect.stack()[1]
fixture_path = os.path.join(os.path.dirname(caller.filename), 'fixture')

fixture_parent = os.path.dirname(fixture_path)
fixture_prefix = os.path.basename(fixture_path) + '_'

with tempfile.TemporaryDirectory(prefix=fixture_prefix,
dir=fixture_parent) as tmp_path:
# copy fixture to a temporary directory so we can execute
# multiple tests in parallel
shutil.copytree(fixture_path, tmp_path, dirs_exist_ok=True)
tf = tftest.TerraformTest(tmp_path, BASEDIR,
os.environ.get('TERRAFORM', 'terraform'))
tf.setup(upgrade=True)
apply = tf.apply(tf_vars=tf_vars)
output = tf.output(json_format=True)
return apply, output

return run_apply


@pytest.fixture
def basedir():
return BASEDIR
pytest_plugins = (
'tests.fixtures',
'tests.legacy_fixtures',
'tests.collectors',
)
15 changes: 8 additions & 7 deletions tests/fast/stages/s00_bootstrap/tftest.yaml
Expand Up @@ -2,10 +2,11 @@

module: fast/stages/00-bootstrap

simple:
tfvars:
- simple.tfvars
inventory:
- simple.yaml
- simple_projects.yaml
- simple_sas.yaml
tests:
simple:
tfvars:
- simple.tfvars
inventory:
- simple.yaml
- simple_projects.yaml
- simple_sas.yaml
11 changes: 9 additions & 2 deletions tests/fixtures.py
Expand Up @@ -11,6 +11,8 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Common fixtures."""

import collections
import itertools
import os
Expand Down Expand Up @@ -205,8 +207,7 @@ def plan_validator(module_path, inventory_paths, basedir, tf_var_files=None,

@pytest.fixture(name='plan_validator')
def plan_validator_fixture(request):
"""Return a function to builds a PlanSummary and compare it to YAML
inventory.
"""Return a function to build a PlanSummary and compare it to a YAML inventory.
In the returned function `basedir` becomes optional and it defaults
to the directory of the calling test'
Expand All @@ -222,3 +223,9 @@ def inner(module_path, inventory_paths, basedir=None, tf_var_files=None,
tf_var_files=tf_var_paths, **tf_vars)

return inner


# @pytest.fixture
# def repo_root():
# 'Return a pathlib.Path to the root of the repository'
# return Path(__file__).parents[1]

0 comments on commit e05fc12

Please sign in to comment.