Building JobSpec objects#
A job generator ultimately returns runnable work items to canary. In this plugin, those
work items are JobSpec objects.
What the YAML plugin sets#
For each test entry, the plugin populates a spec with:
file_rootandfile_path: where the test came from;family: the logical test name (the key undertests:);keywords: copied from YAML (if present);attributes: storesdescriptionunderattributes["description"]; andcommand: the concrete command line to execute.
Parameter expansion#
When a YAML entry provides a parameters mapping, the plugin computes the Cartesian product of
parameter values and emits one spec per combination:
def lock(self, on_options: list[str] | None = None) -> list[canary.JobSpec]:
"""Take the cartesian product of parameters and from each combination create a test case."""
with open(self.file, "r") as fh:
fd = yaml.safe_load(fh)
fd = yaml_schema.validate(fd)
specs: list[canary.ResolvedSpec] = []
for name, details in fd["tests"].items():
kwds: dict[str, Any] = dict(
file_root=Path(self.root),
file_path=Path(self.path),
family=name,
keywords=details.get("keywords", []),
attributes={"description": details.get("description")},
)
script = details["script"]
sh = canary.filesystem.which("sh", required=True)
if parameters := details.get("parameters"):
keys = list(parameters.keys())
for values in product(*parameters.values()):
p = kwds["parameters"] = dict(zip(keys, values))
shell_cmds: list[str] = [Template(_).safe_substitute(**p) for _ in script]
kwds["command"] = [sh, "-c", "set -e\n" + "\n".join(shell_cmds)]
spec = canary.JobSpec(**kwds)
specs.append(spec)
else:
kwds["command"] = [sh, "-c", "set -e\n" + "\n".join(script)]
spec = canary.JobSpec(**kwds)
specs.append(spec)
return specs
For each combination, the plugin sets spec.parameters and expands the script lines using the
parameter values.
Command construction (sh -c)#
The plugin runs the YAML script by constructing a shell command:
it finds a shell with
canary.filesystem.which()(required);it passes the script using
sh -c; andit prefixes the script with
set -eso failures propagate via exit code.
This construction happens in lock() when setting kwds["command"]:
def lock(self, on_options: list[str] | None = None) -> list[canary.JobSpec]:
"""Take the cartesian product of parameters and from each combination create a test case."""
with open(self.file, "r") as fh:
fd = yaml.safe_load(fh)
fd = yaml_schema.validate(fd)
specs: list[canary.ResolvedSpec] = []
for name, details in fd["tests"].items():
kwds: dict[str, Any] = dict(
file_root=Path(self.root),
file_path=Path(self.path),
family=name,
keywords=details.get("keywords", []),
attributes={"description": details.get("description")},
)
script = details["script"]
sh = canary.filesystem.which("sh", required=True)
if parameters := details.get("parameters"):
keys = list(parameters.keys())
for values in product(*parameters.values()):
p = kwds["parameters"] = dict(zip(keys, values))
shell_cmds: list[str] = [Template(_).safe_substitute(**p) for _ in script]
kwds["command"] = [sh, "-c", "set -e\n" + "\n".join(shell_cmds)]
spec = canary.JobSpec(**kwds)
specs.append(spec)
else:
kwds["command"] = [sh, "-c", "set -e\n" + "\n".join(script)]
spec = canary.JobSpec(**kwds)
specs.append(spec)
return specs
Template substitution#
When parameters are present, each script line is expanded using string.Template and
safe_substitute. Placeholders in the YAML should use the ${name} form:
script:
- echo "a=${a}"
Note
safe_substitute leaves unknown placeholders unchanged, which can be helpful during
incremental development of a format. If you prefer strict behavior, use substitute instead.