Skip to content
Snippets Groups Projects
test_versions.py 4.21 KiB
Newer Older
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",
    [
Simon Rose's avatar
Simon Rose committed
        # 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"]),
Simon Rose's avatar
Simon Rose committed
        # 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)
Simon Rose's avatar
Simon Rose committed
        assert rc == 0
        assert match
    else:
        match = re.search(RE_MODULE_NOT_LOADED.format(required=re.escape(requested)), o)
        assert match
        assert rc != 0