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