Source code for snakemake.unit_tests

from itertools import groupby
from pathlib import Path
import shutil
import os
from snakemake.common import async_run

from snakemake.logging import logger
from snakemake import __version__
from snakemake.exceptions import WorkflowError


[docs] class RuleTest:
[docs] def __init__(self, job, basedir): self.name = job.rule.name self.output = job.output self.path = basedir / self.name
@property def target(self): return self.output or self.name @property def data_path(self): return self.path / "data" @property def expected_path(self): return self.path / "expected"
[docs] def generate(dag, path: Path, deploy=["conda", "singularity"], configfiles=None): """Generate unit tests from given dag at a given path.""" logger.info("Generating unit tests for each rule...") try: from jinja2 import Environment, PackageLoader except ImportError: raise WorkflowError( "Python package jinja2 must be installed to create reports." ) env = Environment( loader=PackageLoader("snakemake", "unit_tests/templates"), trim_blocks=True, lstrip_blocks=True, ) os.makedirs(path, exist_ok=True) with open(path / "common.py", "w") as common: print( env.get_template("common.py.jinja2").render(version=__version__), file=common, ) for rulename, jobs in groupby(dag.jobs, key=lambda job: job.rule.name): jobs = list(jobs) if jobs[0].rule.norun: logger.info( "Skipping rule {} because it does not execute anything.".format( rulename ) ) continue testpath = path / f"test_{rulename}.py" if testpath.exists(): logger.info( "Skipping rule {} as a unit test already exists for it: {}.".format( rulename, testpath ) ) continue written = False for job in jobs: if all(async_run(f.exists()) for f in job.input): logger.info(f"Generating unit test for rule {rulename}: {testpath}.") os.makedirs(path / rulename, exist_ok=True) def copy_files(files, content_type): for f in files: f = Path(f) parent = f.parent if parent.is_absolute(): root = str(f.parents[len(f.parents) - 1]) parent = str(parent)[len(root) :] target = path / rulename / content_type / parent if f.is_dir(): shutil.copytree(f, target / f.name) else: os.makedirs(target, exist_ok=True) shutil.copy(f, target) if not files: os.makedirs(path / rulename / content_type, exist_ok=True) # touch gitempty file if there are no input files open(path / rulename / content_type / ".gitempty", "w").close() copy_files(job.input, "data") copy_files(job.output, "expected") with open(testpath, "w") as test: print( env.get_template("ruletest.py.jinja2").render( ruletest=RuleTest(job, path), deploy=deploy, configfiles=configfiles, ), file=test, ) written = True break if not written: logger.warning( "No job with all input files present for rule {}. " "Consider re-executing the workflow with --notemp in order " "have all input files present before generating unit tests." )