Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
utils.py 5.31 KiB
import os
import subprocess
import time
from pathlib import Path
from random import choice
from string import ascii_lowercase

from git import Repo
from run_iocsh import IOC


class Wrapper:
    def __init__(
        self, root_path: Path, *, name=None, include_dbd=True, url=None, **kwargs
    ):
        test_env = os.environ.copy()

        assert "EPICS_BASE" in test_env
        assert "E3_REQUIRE_VERSION" in test_env
        assert "EPICS_HOST_ARCH" in test_env

        self.epics_base = Path(os.getenv("EPICS_BASE"))
        self.base_version = self.epics_base.name.split("base-")[-1]
        self.host_arch = os.getenv("EPICS_HOST_ARCH")
        self.require_version = os.getenv("E3_REQUIRE_VERSION")

        e3_require_config = (
            self.epics_base / "require" / self.require_version / "configure"
        )

        assert e3_require_config.is_dir()

        if name is None:
            name = "test_mod_" + "".join(choice(ascii_lowercase) for _ in range(16))
        self.name = name
        self.version = "0.0.0+0"

        self.path = root_path / f"e3-{self.name}"
        self.config_dir = self.path / "configure"
        self.config_module = self.config_dir / "CONFIG_MODULE"
        module_path = (
            self.name
            if "E3_MODULE_SRC_PATH" not in kwargs
            else kwargs["E3_MODULE_SRC_PATH"]
        )
        self.module_dir = self.path / module_path
        self.build_dir = self.module_dir / f"O.{self.base_version}_{self.host_arch}"

        self.makefile = self.path / "Makefile"
        self.module_makefile = self.path / f"{self.name}.Makefile"

        self.url = url
        if self.url:
            Repo.clone_from(self.url, self.path)
        else:
            self.module_dir.mkdir(parents=True)
            self.config_dir.mkdir()
            self.config_module.touch()

            makefile_contents = f"""
TOP:=$(CURDIR)

E3_MODULE_NAME:={name}
E3_MODULE_VERSION:={self.version}
E3_MODULE_SRC_PATH:={module_path}
E3_MODULE_MAKEFILE:={name}.Makefile

include $(TOP)/configure/CONFIG_MODULE
-include $(TOP)/configure/CONFIG_MODULE.local

REQUIRE_CONFIG:={e3_require_config}

include $(REQUIRE_CONFIG)/CONFIG
include $(REQUIRE_CONFIG)/RULES_SITEMODS
"""
            self.makefile.write_text(makefile_contents)

            module_makefile_contents = """
where_am_I := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
include $(E3_REQUIRE_TOOLS)/driver.makefile
"""
            self.module_makefile.write_text(module_makefile_contents)

            if include_dbd:
                self.add_file("test.dbd")

            Repo.init(self.path)

    def add_file(self, name):
        (self.module_dir / name).touch()

    def write_dot_local_data(self, config_file: str, config_vars: dict):
        """Write config data to the specific .local file."""

        with open(self.config_dir / f"{config_file}.local", "w") as f:
            for var, value in config_vars.items():
                f.write(f"{var} = {value}\n")

    def add_var_to_config_module(self, makefile_var: str, value: str, modifier="+"):
        with open(self.config_module, "a") as f:
            f.write(f"export {makefile_var} {modifier}= {value}\n")

    def add_var_to_module_makefile(self, makefile_var: str, value: str, modifier="+"):
        with open(self.module_makefile, "a") as f:
            f.write(f"{makefile_var} {modifier}= {value}\n")

    def get_env_var(self, var: str):
        """Fetch an environment variable from the module build environment."""

        _, out, _ = self.run_make("cellvars")
        for line in out.split("\n"):
            if line.startswith(var):
                return line.split("=")[1].strip()
        return ""

    def run_make(
        self,
        target: str,
        *args,
        module_version: str = "",
        cell_path: str = "",
        **kwargs,
    ):
        """Attempt to run `make <target> <args>` for the current wrapper."""

        # We should not install in the global environment during tests
        if target == "install":
            target = "cellinstall"

        if target == "uninstall":
            target = "celluninstall"

        env = os.environ.copy()
        env.update(kwargs)

        args = list(args)
        if module_version:
            args.append(f"E3_MODULE_VERSION={module_version}")
        if not ("TEST_DEBUG_ARCH" in env and not env["TEST_DEBUG_ARCH"]):
            args.append("EXCLUDE_ARCHS=debug")

        if cell_path:
            self.write_dot_local_data("CONFIG_CELL", {"E3_CELL_PATH": cell_path})
        make_cmd = ["make", "-C", self.path, target] + args
        p = subprocess.Popen(
            make_cmd,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            encoding="utf-8",
            env=env,
        )
        outs, errs = p.communicate()
        return p.returncode, outs, errs


def run_ioc_get_output(*args, **kwargs):
    """Run an IOC and try to load the test module."""
    ioc_args = []
    module = kwargs.get("module", None)
    version = kwargs.get("version", None)
    if module:
        ioc_args.append("-r")
        ioc_args.append(f"{module},{version}" if version else module)
    cell_path = kwargs.get("cell_path", None)
    if cell_path:
        ioc_args.extend(["-l", cell_path])
    ioc_args.extend(args)
    with IOC(*ioc_args, ioc_executable="iocsh") as ioc:
        time.sleep(1)
    return ioc.proc.returncode, ioc.outs, ioc.errs