Skip to content
Snippets Groups Projects
test_build.py 14.1 KiB
Newer Older
import os
import re
from pathlib import Path
import pytest

from .utils import run_ioc_get_output
Simon Rose's avatar
Simon Rose committed

Simon Rose's avatar
Simon Rose committed
MODULE_VERSION = "0.0.0+0"
Simon Rose's avatar
Simon Rose committed
MODULE_VERSION_NO_BUILD = "0.0.0"
RE_MISSING_FILE = "No rule to make target [`']{filename}'"

Simon Rose's avatar
Simon Rose committed

def create_patch_file(path, desc):
    path.parent.mkdir(parents=True, exist_ok=True)
    patch_file_contents = """
Simon Rose's avatar
Simon Rose committed
diff --git database.db database.db
index 1806ff6..8701832 100644
--- database.db
+++ database.db
@@ -1,3 +1,3 @@
 record(ai, "TEST") {{
Simon Rose's avatar
Simon Rose committed
-
Simon Rose's avatar
Simon Rose committed
+    field(DESC, "{desc}")
 }}
"""
Simon Rose's avatar
Simon Rose committed
    with open(path, "w") as f:
        f.write(patch_file_contents.format(desc=desc))
def test_patch(wrapper):
    db_path = wrapper.module_dir / "database.db"
    db_file_contents = """record(ai, "TEST") {

}
"""
Simon Rose's avatar
Simon Rose committed
    with open(db_path, "w") as f:
        f.write(db_file_contents)
Simon Rose's avatar
Simon Rose committed
    patch_dir = wrapper.path / "patch"
    create_patch_file(patch_dir / "apply.p0.patch", "OK")
    create_patch_file(patch_dir / MODULE_VERSION / "do-not-apply.p0.patch", "Bad")
    rc, _, _ = wrapper.run_make("patch", module_version=MODULE_VERSION)
Simon Rose's avatar
Simon Rose committed
    assert rc == 0
Simon Rose's avatar
Simon Rose committed
    with open(db_path, "r") as f:
Simon Rose's avatar
Simon Rose committed
        db_contents = f.read()
    assert 'field(DESC, "OK")' in db_contents
Simon Rose's avatar
Simon Rose committed
    assert "Bad" not in db_contents
    rc, _, _ = wrapper.run_make("build", module_version=MODULE_VERSION)
Simon Rose's avatar
Simon Rose committed
    assert rc == 0
    assert any((wrapper.module_dir).glob("O.*"))
    rc, _, _ = wrapper.run_make("cellinstall", module_version=MODULE_VERSION)
    assert any((wrapper.path / "cellMods").glob("**/*.db"))
def test_local_module(wrapper):
    rc, outs, _ = wrapper.run_make(
        "init",
        module_version=MODULE_VERSION,
    )
    assert rc == 0
    assert "You are in the local source mode" in outs


def test_missing_dbd_file(wrapper):
    wrapper.add_var_to_makefile("DBDS", "nonexistent.dbd")
    rc, _, errs = wrapper.run_make("build")
Simon Rose's avatar
Simon Rose committed

    assert re.search(
        RE_MISSING_FILE.format(filename=re.escape("../nonexistent.dbd")),
    )


def test_missing_source_file(wrapper):
    wrapper.add_var_to_makefile("SOURCES", "nonexistent.c")
    rc, _, errs = wrapper.run_make("build")

    assert rc == 2
    assert re.search(
        RE_MISSING_FILE.format(filename=re.escape("nonexistent.o")),


def test_missing_requirement(wrapper):
Simon Rose's avatar
Simon Rose committed
    wrapper.add_var_to_config_module("FOO_DEP_VERSION", "bar")
    rc, _, errs = wrapper.run_make("build")
Simon Rose's avatar
Simon Rose committed
    assert rc == 0
    assert 'Invalid dependency "FOO_DEP_VERSION"; pruning' in errs


def test_header_install_location(wrapper):
    subdir = wrapper.module_dir / "db" / "subdir"
    subdir.mkdir(parents=True)

    extensions = ["h", "hpp", "hxx", "hh"]
    for ext in extensions:
        wrapper.add_var_to_makefile("HEADERS", f"db/subdir/header.{ext}")
    wrapper.add_var_to_makefile("KEEP_HEADER_SUBDIRS", "db")
    for ext in extensions:
        (subdir / f"header.{ext}").touch()
    rc, *_ = wrapper.run_make("cellinstall")
    assert rc == 0

    cell_path = wrapper.get_env_var("E3_MODULES_INSTALL_LOCATION")

    for ext in extensions:
        assert (Path(cell_path) / "include" / "subdir" / f"header.{ext}").is_file()
        assert not (Path(cell_path) / "include" / f"header.{ext}").is_file()


def test_updated_dependencies(wrappers):
    wrapper_dep = wrappers.get()
    wrapper_main = wrappers.get()

    cell_path = wrapper_main.path / "cellMods"

    old_version = "0.0.0+0"

Simon Rose's avatar
Simon Rose committed
    wrapper_main.add_var_to_config_module(
        f"{wrapper_dep.name}_DEP_VERSION", old_version, modifier=""
    rc, *_ = wrapper_dep.run_make(
        "cellinstall",
        module_version=old_version,
        cell_path=cell_path,
    )
    assert rc == 0

    rc, *_ = wrapper_main.run_make("cellinstall", module_version=old_version)
    assert rc == 0

    new_version = "1.0.0+0"

    rc, *_ = wrapper_dep.run_make(
        "cellinstall",
        module_version=new_version,
        cell_path=cell_path,
    )
    assert rc == 0

Simon Rose's avatar
Simon Rose committed
    wrapper_main.add_var_to_config_module(
        f"{wrapper_dep.name}_DEP_VERSION", new_version, modifier=""
    rc, *_ = wrapper_main.run_make("cellinstall", module_version=new_version)
    assert rc == 0

    rc, outs, _ = run_ioc_get_output(
        wrapper_main.name, new_version, wrapper_main.path / "cellMods"
    )
    assert rc == 0
    assert f"Loaded {wrapper_dep.name} version {new_version}" in outs
def test_match_versions(wrappers):
    """Test match version scenario.

    This test checks if inconsistent versions are correctly verified during
    build time. This tests if the dependecies B->A->C and B->C with A and B
    both requesting the same version of C will be correctly built.
    """
    wrapper_dep = wrappers.get()
    wrapper_a = wrappers.get()
    wrapper_b = wrappers.get()

    cell_path = wrapper_b.path / "cellmods"

    dep_version = "1.0.0+0"

    a_version = "0.0.0+0"
    b_version = "0.0.0+0"

    # Wrapper a dependes on dep,1.0.0+0
    wrapper_a.add_var_to_config_module(
        f"{wrapper_dep.name}_DEP_VERSION", dep_version, modifier=""
    )

    # Wrapper b depends on dep,1.0.0+0
    wrapper_b.add_var_to_config_module(
        f"{wrapper_dep.name}_DEP_VERSION", dep_version, modifier=""
    )

    # Wrapper b also depends on a
    wrapper_b.add_var_to_config_module(
        f"{wrapper_a.name}_DEP_VERSION", a_version, modifier=""
    )

    rc, *_ = wrapper_dep.run_make(
        "cellinstall", module_version=dep_version, cell_path=cell_path
    )
    assert rc == 0

    rc, *_ = wrapper_a.run_make(
        "cellinstall", module_version=a_version, cell_path=cell_path
    )
    assert rc == 0

    # As wrappers a and b both depends on dep,1.0.0+0 this build should finish
    # corretly.
    rc, *_ = wrapper_b.run_make(
        "cellinstall", module_version=b_version, cell_path=cell_path
    )
    assert rc == 0


def test_unmatching_versions(wrappers):
    """Test unmatching version scenarion.

    This test checks if inconsistent versions are correctly verified during
    build time. This checks for the scenarion where B->A->C and B->C however
    A depends on a version of C different than B.
    """
    wrapper_dep = wrappers.get()
    wrapper_a = wrappers.get()
    wrapper_b = wrappers.get()

    cell_path = wrapper_b.path / "cellmods"

    dep_version_1 = "1.0.0+0"
    dep_version_2 = "2.0.0+0"

    a_version = "0.0.0+0"
    b_version = "0.0.0+0"

    # Wrapper a dependes on dep v1
    wrapper_a.add_var_to_config_module(
        f"{wrapper_dep.name}_DEP_VERSION", dep_version_1, modifier=""
    )

    # Wrapper b depends on dep v2
    wrapper_b.add_var_to_config_module(
        f"{wrapper_dep.name}_DEP_VERSION", dep_version_2, modifier=""
    )

    # Wrapper b also depends on wrapper_a
    wrapper_b.add_var_to_config_module(
        f"{wrapper_a.name}_DEP_VERSION", a_version, modifier=""
    )

    rc, *_ = wrapper_dep.run_make(
        "cellinstall", module_version=dep_version_1, cell_path=cell_path
    )
    assert rc == 0

    rc, *_ = wrapper_a.run_make(
        "cellinstall", module_version=a_version, cell_path=cell_path
    )
    assert rc == 0

    # Now a second installation of wrapper_dep but with version 2
    rc, *_ = wrapper_dep.run_make(
        "cellinstall", module_version=dep_version_2, cell_path=cell_path
    )
    assert rc == 0

    # This next installation should fail because B depends on A
    # that depends on DEP. However A needs DEP 1.0.0+0 and B
    # needs DEP 2.0.0+0
    rc, *_ = wrapper_b.run_make(
        "cellinstall", module_version=b_version, cell_path=cell_path
    )
    assert rc != 0


def test_indirect_unmatching_versions(wrappers):
    """Test indirect unmatching version scenarion.

    This test checks if inconsistend versions are correctly verified during
    build time. This checks for the scenarion where B->A->C and B->D->C
    however A depends on a version of C different than D.
    """

    wrapper_c = wrappers.get()
    wrapper_a = wrappers.get()
    wrapper_b = wrappers.get()
    wrapper_d = wrappers.get()

    cell_path = wrapper_b.path / "cellmods"

    c_version_a = "1.0.0+0"
    c_version_d = "2.0.0+0"

    a_version = "0.0.0+0"
    d_version = "0.0.0+0"
    b_version = "0.0.0+0"

    # Wrapper a dependes on c
    wrapper_a.add_var_to_config_module(
        f"{wrapper_c.name}_DEP_VERSION", c_version_a, modifier=""
    )

    # Wrapper d dependes on c
    wrapper_d.add_var_to_config_module(
        f"{wrapper_c.name}_DEP_VERSION", c_version_d, modifier=""
    )

    # Wrapper b depends on d
    wrapper_b.add_var_to_config_module(
        f"{wrapper_d.name}_DEP_VERSION", d_version, modifier=""
    )

    # Wrapper b also depends on a
    wrapper_b.add_var_to_config_module(
        f"{wrapper_a.name}_DEP_VERSION", a_version, modifier=""
    )

    rc, *_ = wrapper_c.run_make(
        "cellinstall", module_version=c_version_a, cell_path=cell_path
    )
    assert rc == 0

    rc, *_ = wrapper_a.run_make(
        "cellinstall", module_version=a_version, cell_path=cell_path
    )
    assert rc == 0

    # Now a second installation of wrapper_dep but with version 2
    rc, *_ = wrapper_c.run_make(
        "cellinstall", module_version=c_version_d, cell_path=cell_path
    )
    assert rc == 0

    rc, *_ = wrapper_d.run_make(
        "cellinstall", module_version=d_version, cell_path=cell_path
    )
    assert rc == 0

    # This next installation should fail because A depends on C
    # with a different version that D depends on C.
    rc, *_ = wrapper_b.run_make(
        "cellinstall", module_version=b_version, cell_path=cell_path
    )
    assert rc != 0


def test_automated_dependency(wrappers):
    wrapper_a = wrappers.get()
    wrapper_b = wrappers.get()

    cell_path = wrapper_a.path / "cellMods"

    module_version = "0.0.0+0"

    wrapper_a.add_var_to_config_module(f"{wrapper_b.name}_DEP_VERSION", module_version)

    rc, *_ = wrapper_b.run_make(
        "cellinstall", module_version=module_version, cell_path=cell_path
    )
    assert rc == 0
    rc, *_ = wrapper_a.run_make(
        "cellinstall", module_version=module_version, cell_path=cell_path
    )
    assert rc == 0

    for dep_file in (cell_path / wrapper_a.name).rglob("*.dep"):
        with open(dep_file, "r") as f:
            contents = f.readlines()

        assert len(contents) == 2
Simon Rose's avatar
Simon Rose committed
        assert contents[0].strip() == "# Generated file. Do not edit."
        assert f"{wrapper_b.name} {module_version}" == contents[1]


Simon Rose's avatar
Simon Rose committed
def test_architecture_dependent_dependency(wrappers):
    wrapper_a = wrappers.get()
    wrapper_b = wrappers.get()
    wrapper_c = wrappers.get()

    cell_path = wrapper_a.path / "cellMods"

    module_version = "0.0.0+0"

    wrapper_a.add_var_to_config_module(
        f"{wrapper_b.name}_DEP_VERSION_linux", module_version
    )
    wrapper_a.add_var_to_config_module(
        f"{wrapper_c.name}_DEP_VERSION_not_an_arch", module_version
    )

    rc, *_ = wrapper_c.run_make(
        "cellinstall", module_version=module_version, cell_path=cell_path
    )
    assert rc == 0
    rc, *_ = wrapper_b.run_make(
        "cellinstall", module_version=module_version, cell_path=cell_path
    )
    assert rc == 0
    rc, *_ = wrapper_a.run_make(
        "cellinstall", module_version=module_version, cell_path=cell_path
    )
    assert rc == 0

    rc, outs, _ = run_ioc_get_output(
        wrapper_a.name, module_version, wrapper_a.path / "cellMods"
    )
    assert rc == 0
    assert f"Loaded {wrapper_b.name} version {module_version}" in outs
    assert f"Loaded {wrapper_c.name} version {module_version}" not in outs


def test_recursive_header_include(wrappers):
Simon Rose's avatar
Simon Rose committed
    wrapper_a = wrappers.get()
    wrapper_b = wrappers.get()
    wrapper_c = wrappers.get()

    cell_path = wrapper_a.path / "cellMods"

    module_version = "0.0.0+0"

    wrapper_b.add_var_to_config_module(f"{wrapper_c.name}_DEP_VERSION", module_version)
    wrapper_a.add_var_to_config_module(f"{wrapper_b.name}_DEP_VERSION", module_version)
Simon Rose's avatar
Simon Rose committed

    wrapper_c.add_var_to_makefile("HEADERS", f"{wrapper_c.name}.h")
    (wrapper_c.module_dir / f"{wrapper_c.name}.h").touch()

    wrapper_a.add_var_to_makefile("SOURCES", f"{wrapper_a.name}.c")
    with open(wrapper_a.module_dir / f"{wrapper_a.name}.c", "w") as f:
        f.write(f'#include "{wrapper_c.name}.h"')

    rc, *_ = wrapper_c.run_make(
        "cellinstall", module_version=module_version, cell_path=cell_path
    )
    assert rc == 0
    rc, *_ = wrapper_b.run_make(
        "cellinstall", module_version=module_version, cell_path=cell_path
    )
Simon Rose's avatar
Simon Rose committed
    assert rc == 0
    rc, *_ = wrapper_a.run_make(
        "cellinstall", module_version=module_version, cell_path=cell_path
    )
Simon Rose's avatar
Simon Rose committed
    assert rc == 0

    rc, outs, _ = run_ioc_get_output(
        wrapper_a.name, module_version, wrapper_a.path / "cellMods"
    )
Simon Rose's avatar
Simon Rose committed
    assert rc == 0
    assert f"Loaded {wrapper_c.name} version {module_version}" in outs


def test_updated_template_files(wrapper):
    wrapper.add_var_to_makefile("SUBS", "x.substitutions")

    substitution_file = wrapper.module_dir / "x.substitutions"
    substitution_file.write_text("file x.template {pattern {x} {y}}")

    template_file = wrapper.module_dir / "x.template"
Simon Rose's avatar
Simon Rose committed
    template_file.write_text("record(ai, initial) {}")

    base_version = wrapper.get_env_var("EPICS_VERSION_NUMBER")
    db_file = wrapper.module_dir / f"O.{base_version}_Common" / "x.db"

    rc, *_ = wrapper.run_make("db_internal")
    assert rc == 0
Simon Rose's avatar
Simon Rose committed
    assert db_file.read_text() == "record(ai, initial) {}"
Simon Rose's avatar
Simon Rose committed
    template_file.write_text("record(ai, updated) {}")

    rc, *_ = wrapper.run_make("db_internal")
    assert rc == 0
Simon Rose's avatar
Simon Rose committed
    assert db_file.read_text() == "record(ai, updated) {}"


@pytest.mark.parametrize(
    "installed_archs, param, expected",
    [
        ("foo bar baz foo-bar", "ARCH_FILTER=foo", ["foo"]),
        ("foo", "EXCLUDE_ARCHS=foo", []),
        ("foo-bar foo-baz baz baz-qux", "EXCLUDE_ARCHS=foo", ["baz", "baz-qux"]),
    ],
)
def test_arch_filter(wrapper, installed_archs, param, expected):
    arch_regex = re.compile(r"Pass 3: T_A =\s*([^\s]+)")

    wrapper.add_var_to_makefile(
        "CROSS_COMPILER_TARGET_ARCHS", installed_archs, modifier=""
    )

    rc, o, _ = wrapper.run_make("debug", param)

    assert rc == 0

    host_arch = os.getenv("EPICS_HOST_ARCH")
    build_archs = [arch for arch in arch_regex.findall(o) if arch != host_arch]

    assert build_archs == expected