import os import re import subprocess import time from pathlib import Path import pytest from run_iocsh import IOC TEST_DATA = Path(__file__).absolute().parent / "data" EPICS_BASE = os.environ.get("EPICS_BASE") E3_REQUIRE_VERSION = os.environ.get("E3_REQUIRE_VERSION") IOCSH_PATH = Path(EPICS_BASE) / "require" / E3_REQUIRE_VERSION / "bin" / "iocsh.bash" TEST_MODULE_NAME = "testversions" RE_MODULE_LOADED = f"Loaded {TEST_MODULE_NAME} version {{version}}" RE_MODULE_NOT_LOADED = f"Module {TEST_MODULE_NAME} (not available|version {{required}} not available|version {{required}} not available \\(but other versions are available\\))" CONFIG = f""" TOP:=$(CURDIR) # To configure require EPICS_BASE:=$(__EPICS_BASE_LOCATION) E3_REQUIRE_VERSION:=$(__REQUIRE_VERSION) E3_REQUIRE_LOCATION := $(EPICS_BASE)/require/$(E3_REQUIRE_VERSION) REQUIRE_CONFIG := $(E3_REQUIRE_LOCATION)/configure # To configure the modules EPICS_MODULE_NAME:={TEST_MODULE_NAME} E3_MODULE_VERSION:=$(__DEBUG_VERSION) E3_MODULE_NAME:=$(EPICS_MODULE_NAME) E3_MODULE_SRC_PATH:=$(EPICS_MODULE_NAME) E3_MODULE_MAKEFILE:=$(EPICS_MODULE_NAME).Makefile include $(REQUIRE_CONFIG)/CONFIG include $(REQUIRE_CONFIG)/RULES_SITEMODS .PHONY: db db: """ MODULE_MAKEFILE = """ where_am_I := $(dir $(abspath $(lastword $(MAKEFILE_LIST)))) include $(E3_REQUIRE_TOOLS)/driver.makefile DBDS += ./test.dbd """ def run_make(path, *args, **kwargs): test_env = os.environ.copy() for kw in kwargs: test_env[kw] = kwargs[kw] make_cmd = ["make", "-C", path] + list(args) p = subprocess.Popen( make_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=test_env ) outs, errs = p.communicate() return p.returncode, outs, errs def run_ioc_get_output(version, cell_path): with IOC("-r", f"{TEST_MODULE_NAME},{version}", "-l", cell_path) as ioc: time.sleep(1) return ioc.proc.returncode, ioc.outs, ioc.errs @pytest.fixture def wrapper(tmpdir): wrapper_dir = Path(tmpdir / "wrapper") test_dir = wrapper_dir / TEST_MODULE_NAME test_dir.mkdir(parents=True) with open(wrapper_dir / "Makefile", "w") as f: f.write(CONFIG) with open(wrapper_dir / f"{TEST_MODULE_NAME}.Makefile", "w") as f: f.write(MODULE_MAKEFILE) with open(wrapper_dir / TEST_MODULE_NAME / "test.dbd", "w") as f: f.write("") yield wrapper_dir @pytest.mark.parametrize( "requested, expected, installed", [ # If nothing is installed, nothing should be loaded ("test", "", []), # Test versions can be loaded ("test", "test", ["test", "0.0.1"]), # Numeric versions should be prioritized over test versions ("", "0.0.1+0", ["test", "0.0.1"]), ("", "0.0.1+0", ["0.0.1", "test"]), # Highest build number should be loaded if version is unspecified ("", "0.0.1+7", ["0.0.1", "0.0.1+7", "0.0.1+3"]), # Only load the given build number if it is specified ("0.0.1+0", "", ["0.0.1+1"]), # If no build number is specified, load the highest build number ("0.0.1", "0.0.1+4", ["0.1.0", "0.0.1+4", "0.0.1"]), # Build number 0 means load that version exactly ("0.0.1+0", "0.0.1+0", ["0.0.1+0"]), ("0.0.1+0", "0.0.1+0", ["0.0.1", "0.0.1+1", "1.0.0"]), # 1-test counts as a test version, as does 1.0 ("", "0.0.1+0", ["0.0.1", "1-test"]), ("", "0.0.1+0", ["0.0.1", "1.0"]), # Numeric version should be prioritised over "higher" test version ("", "0.1.0+0", ["0.1.0", "1.0.0-rc1"]), ], ) def test_version(wrapper, requested, expected, installed): for version in installed: returncode, _, _ = run_make( wrapper, "clean", "cellinstall", __EPICS_BASE_LOCATION=EPICS_BASE, __REQUIRE_VERSION=E3_REQUIRE_VERSION, __DEBUG_VERSION=version, ) assert returncode == 0 rc, o, e = run_ioc_get_output(requested, wrapper / "cellMods") if expected: match = re.search(RE_MODULE_LOADED.format(version=re.escape(expected)), o) assert rc == 0 assert match else: match = re.search(RE_MODULE_NOT_LOADED.format(required=re.escape(requested)), o) assert match assert rc != 0