Merge pull request #157 from terraform-google-modules/example-tests

Make examples in READMEs runnable and testable
This commit is contained in:
Julio Castillo
2020-11-07 11:38:19 +01:00
committed by GitHub
42 changed files with 713 additions and 272 deletions

View File

@@ -55,6 +55,26 @@ def plan_runner():
return run_plan
@pytest.fixture(scope='session')
def example_plan_runner():
"Returns a function to run Terraform plan on an example."
def run_plan(fixture_path, is_module=True, targets=None, **tf_vars):
"Runs Terraform plan and returns parsed output"
tf = tftest.TerraformTest(fixture_path, BASEDIR,
os.environ.get('TERRAFORM', 'terraform'))
tf.setup()
plan = tf.plan(output=True, tf_vars=tf_vars, targets=targets)
modules = plan.modules
resources = []
for name, module in modules.items():
for _, resource in module.resources.items():
resources.append(resource)
return plan, modules, resources
return run_plan
@pytest.fixture(scope='session')
def apply_runner():
"Returns a function to run Terraform apply on a fixture."

View File

@@ -0,0 +1,46 @@
# Copyright 2020 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# 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.
from pathlib import Path
import marko
MODULES_PATH = Path(__file__).parents[3] / 'modules/'
def pytest_generate_tests(metafunc):
if 'example' in metafunc.fixturenames:
modules = [
x for x in MODULES_PATH.iterdir()
if x.is_dir()
]
modules.sort()
examples = []
ids = []
for module in modules:
readme = module / 'README.md'
if not readme.exists(): continue
doc = marko.parse(readme.read_text())
index = 0
for child in doc.children:
if isinstance(child, marko.block.FencedCode) and child.lang == 'hcl':
index += 1
code = child.children[0].children
if 'tftest:skip' in code:
continue
examples.append(code)
ids.append(f'{module.stem}:example{index}')
metafunc.parametrize('example', examples, ids=ids)

View File

@@ -0,0 +1,38 @@
# Copyright 2020 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# 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.
import tftest
import re
import tempfile
from pathlib import Path
import marko
MODULES_PATH = Path(__file__, '../../../../modules/').resolve()
VARIABLES_PATH = Path(__file__, '../variables.tf').resolve()
EXPECTED_RESOURCES_RE = re.compile(r'# tftest:modules=(\d+):resources=(\d+)')
def test_example(example_plan_runner, tmp_path, example):
(tmp_path / 'modules').symlink_to(MODULES_PATH)
(tmp_path / 'variables.tf').symlink_to(VARIABLES_PATH)
(tmp_path / 'main.tf').write_text(example)
match = EXPECTED_RESOURCES_RE.search(example)
expected_modules = int(match.group(1)) if match is not None else 1
expected_resources = int(match.group(2)) if match is not None else 1
plan, modules, resources = example_plan_runner(str(tmp_path))
assert expected_modules == len(modules)
assert expected_resources == len(resources)

View File

@@ -0,0 +1,69 @@
# Copyright 2020 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# 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 variables used for examples
variable "organization_id" {
default = "organization/organization"
}
variable "project_id" {
default = "projects/project-id"
}
variable "billing_account_id" {
default = "billing_account_id"
}
variable "bucket" {
default = "bucket"
}
variable "region" {
default = "region"
}
variable "zone" {
default = "zone"
}
variable "vpc" {
default = {
name = "vpc_name"
self_link = "vpc_self_link"
}
}
variable "subnet" {
default = {
name = "subnet_name"
region = "subnet_region"
cidr = "subnet_cidr"
self_link = "subnet_self_link"
}
}
variable "kms_key" {
default = {
self_link = "kms_key_self_link"
}
}
variable "service_account" {
default = {
id = "service_account_id"
email = "service_account_email"
iam_email = "service_account_iam_email"
}
}

View File

@@ -1,3 +1,4 @@
pytest>=4.6.0
PyYAML>=5.3
tftest>=1.5.2
marko>=0.9.1