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.
--- /dev/null
+#!/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])