mirror of
https://github.com/isc-projects/bind9.git
synced 2026-05-26 11:22:52 -04:00
[9.18] new: dev: Support jinja2 templates in pytest runner
Configuration files in system tests which require some variables (e.g. port numbers) filled in during test setup, can now use jinja2 templates when `jinja2` python package is available. Any `*.j2` file found within the system test directory will be automatically rendered with the environment variables into a file without the `.j2` extension by the pytest runner. E.g. `ns1/named.conf.j2` will become `ns1/named.conf` during test setup. To avoid automatic rendering, use `.j2.manual` extension and render the files manually at test time. New `templates` pytest fixture has been added. Its `render()` function can be used to render a template with custom test variables. This can be useful to fill in different config options during the test. With advanced jinja2 template syntax, it can also be used to include/omit entire sections of the config file rather than using `named1.conf.in`, `named2.conf.in` etc. Closes #4938 Backport of MR !9587 Merge branch 'backport-4938-use-jinja2-templates-in-system-tests-9.18' into 'bind-9.18' See merge request isc-projects/bind9!9700
This commit is contained in:
commit
fa2ff6b690
4 changed files with 118 additions and 0 deletions
|
|
@ -319,6 +319,16 @@ prereq.sh Run at the beginning to determine whether the test can be run at
|
|||
if not present, the test is assumed to have all its prerequisites
|
||||
met.
|
||||
|
||||
*.j2 These jinja2 templates can be used for configuration files or any
|
||||
other files which require certain variables filled in, e.g. ports from the
|
||||
environment variables. During test setup, the pytest runner will automatically
|
||||
fill those in and strip the filename extension .j2, e.g. `ns1/named.conf.j2`
|
||||
becomes `ns1/named.conf`. When using advanced templating to conditionally
|
||||
include/omit entire sections or when filling in custom variables used for the
|
||||
test, ensure the templates always include the defaults. If you don't need the
|
||||
file to be auto-templated during test setup, use `.j2.manual` instead and then
|
||||
no defaults are needed.
|
||||
|
||||
setup.sh Run after prereq.sh, this sets up the preconditions for the tests.
|
||||
Although optional, virtually all tests will require such a file to
|
||||
set up the ports they should use for the test.
|
||||
|
|
|
|||
|
|
@ -453,6 +453,11 @@ def system_test_dir(request, env, system_test_name):
|
|||
unlink(symlink_dst)
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def templates(system_test_dir: Path):
|
||||
return isctest.template.TemplateEngine(system_test_dir)
|
||||
|
||||
|
||||
def _run_script(
|
||||
env,
|
||||
system_test_dir: Path,
|
||||
|
|
@ -531,6 +536,7 @@ def system_test(
|
|||
request,
|
||||
env: Dict[str, str],
|
||||
system_test_dir,
|
||||
templates,
|
||||
shell,
|
||||
perl,
|
||||
):
|
||||
|
|
@ -572,6 +578,7 @@ def system_test(
|
|||
pytest.skip("Prerequisites missing.")
|
||||
|
||||
def setup_test():
|
||||
templates.render_auto()
|
||||
try:
|
||||
shell(f"{system_test_dir}/setup.sh")
|
||||
except FileNotFoundError:
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ from . import query
|
|||
from . import name
|
||||
from . import rndc
|
||||
from . import run
|
||||
from . import template
|
||||
from . import log
|
||||
from . import hypothesis
|
||||
|
||||
|
|
|
|||
100
bin/tests/system/isctest/template.py
Normal file
100
bin/tests/system/isctest/template.py
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
#!/usr/bin/python3
|
||||
|
||||
# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
|
||||
#
|
||||
# SPDX-License-Identifier: MPL-2.0
|
||||
#
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
#
|
||||
# See the COPYRIGHT file distributed with this work for additional
|
||||
# information regarding copyright ownership.
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, Optional, Union
|
||||
|
||||
import pytest
|
||||
|
||||
from .log import debug
|
||||
|
||||
|
||||
class TemplateEngine:
|
||||
"""
|
||||
Engine for rendering jinja2 templates in system test directories.
|
||||
"""
|
||||
|
||||
def __init__(self, directory: Union[str, Path], env_vars=None):
|
||||
"""
|
||||
Initialize the template engine for `directory`, optionally overriding
|
||||
the `env_vars` that will be used when rendering the templates (defaults
|
||||
to the environment variables set by the pytest runner).
|
||||
"""
|
||||
self.directory = Path(directory)
|
||||
self._j2env = None
|
||||
if env_vars is None:
|
||||
self.env_vars = dict(os.environ)
|
||||
else:
|
||||
self.env_vars = dict(env_vars)
|
||||
|
||||
@property
|
||||
def j2env(self):
|
||||
"""
|
||||
Jinja2 engine that is initialized when first requested. In case the
|
||||
jinja2 package in unavailable, the current test will be skipped.
|
||||
"""
|
||||
if self._j2env is None:
|
||||
try:
|
||||
import jinja2 # pylint: disable=import-outside-toplevel
|
||||
except ImportError:
|
||||
pytest.skip("jinja2 not found")
|
||||
|
||||
loader = jinja2.FileSystemLoader(str(self.directory))
|
||||
return jinja2.Environment(
|
||||
loader=loader,
|
||||
undefined=jinja2.StrictUndefined,
|
||||
variable_start_string="@",
|
||||
variable_end_string="@",
|
||||
)
|
||||
return self._j2env
|
||||
|
||||
def render(
|
||||
self,
|
||||
output: str,
|
||||
data: Optional[Dict[str, Any]] = None,
|
||||
template: Optional[str] = None,
|
||||
) -> None:
|
||||
"""
|
||||
Render `output` file from jinja `template` and fill in the `data`. The
|
||||
`template` defaults to *.j2.manual or *.j2 file. The environment
|
||||
variables which the engine was initialized with are also filled in. In
|
||||
case of a variable name clash, `data` has precedence.
|
||||
"""
|
||||
if template is None:
|
||||
template = f"{output}.j2.manual"
|
||||
if not Path(template).is_file():
|
||||
template = f"{output}.j2"
|
||||
if not Path(template).is_file():
|
||||
raise RuntimeError('No jinja2 template found for "{output}"')
|
||||
|
||||
if data is None:
|
||||
data = self.env_vars
|
||||
else:
|
||||
data = {**self.env_vars, **data}
|
||||
|
||||
debug("rendering template `%s` to file `%s`", template, output)
|
||||
stream = self.j2env.get_template(template).stream(data)
|
||||
stream.dump(output, encoding="utf-8")
|
||||
|
||||
def render_auto(self):
|
||||
"""
|
||||
Render all *.j2 templates with default values and write the output to
|
||||
files without the .j2 extensions.
|
||||
"""
|
||||
templates = [
|
||||
str(filepath.relative_to(self.directory))
|
||||
for filepath in self.directory.rglob("*.j2")
|
||||
]
|
||||
for template in templates:
|
||||
self.render(template[:-3])
|
||||
Loading…
Reference in a new issue