diff --git a/tests/test_versions/test_versions.py b/tests/test_versions/test_versions.py new file mode 100644 index 0000000000000000000000000000000000000000..e7de0b1424ed6d84f9afb8da50b039cff1dea565 --- /dev/null +++ b/tests/test_versions/test_versions.py @@ -0,0 +1,120 @@ +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", + [ + # 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"]), + ], +) +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 match + else: + match = re.search(RE_MODULE_NOT_LOADED.format(required=re.escape(requested)), o) + assert match + assert rc != 0