From a9c917de2736af59e3d88d5c5a055cf6ff1f1922 Mon Sep 17 00:00:00 2001 From: Simon Rose <simon.rose@ess.eu> Date: Mon, 26 Sep 2022 13:09:31 +0200 Subject: [PATCH] Add tests for e3-sequencer compilation This commit also modifies how driver.makefile processes .st/.stt files. According to the SNL documentation, .stt files should not be pre-processed. However, driver.makefile has historically been performing the exact same actions to both types of files, counter to what the documentation said. --- require-ess/tools/driver.makefile | 44 ++------ tests/test_sequencer.py | 160 ++++++++++++++++++++++++++++++ tests/utils.py | 17 ++-- 3 files changed, 181 insertions(+), 40 deletions(-) create mode 100644 tests/test_sequencer.py diff --git a/require-ess/tools/driver.makefile b/require-ess/tools/driver.makefile index 66d3d087..c4ddc74c 100644 --- a/require-ess/tools/driver.makefile +++ b/require-ess/tools/driver.makefile @@ -514,7 +514,7 @@ DBDFILES += $(patsubst %.stt,%_snl.dbd,$(notdir $(filter %.stt,${SRCS}))) DBDFILES += $(patsubst %.gt,%.dbd,$(notdir $(filter %.gt,${SRCS}))) # snc location -SNCALL=$(shell ls -dv $(E3_SITEMODS_PATH)/sequencer/$(sequencer_VERSION)/bin/$(EPICS_HOST_ARCH) 2> /dev/null)) +SNCALL=$(shell ls -dv $(E3_SITEMODS_PATH)/sequencer/$(sequencer_VERSION)/bin/$(EPICS_HOST_ARCH) 2> /dev/null) SNC=$(lastword $(SNCALL))/snc ifneq (,$(strip $(VLIBS))) @@ -698,59 +698,35 @@ ${INSTALL_BINS}: $(addprefix ../,$(filter-out /%,${BINS})) $(filter /%,${BINS}) # Create SNL code from st/stt file. # Important to have %.o: %.st and %.o: %.stt rule before %.o: %.c rule! -# Preprocess in any case because docu and implemented EPICS rules mismatch here. CPPSNCFLAGS1 = $(filter -D%, ${OP_SYS_CFLAGS}) CPPSNCFLAGS1 += $(filter-out ${OP_SYS_INCLUDE_CPPFLAGS} ,${CPPFLAGS}) ${CPPSNCFLAGS} CPPSNCFLAGS1 += -I $(dir $(SNC))../../include SNCFLAGS += -r +%.i: %.st + @echo ">> Preprocessing $(<F)" + $(CPP) ${CPPSNCFLAGS1} $< > $(*F).i -# 1) ESS uses 7.0.3.1 as the minimal EPICS BASE, so we don't need to check 3.13, -# 2) We also need -c option in $(COMPILE.c) in order to compile generated source file properly -# 3) SNC (2.1.21) should use -o, because without them, snc returns $(*F).i.c instead of $(*F).c -# With the EPICS standard building rule, -o and mv are used. -# -# Tuesday, November 28 15:59:37 CET 2017, Jeong Han Lee - - -%$(OBJ) %_snl.dbd: %.st +%.c: %.i @echo "" @echo ">> SNC building process .... " @echo ">> SNC : $(SNC)" @echo ">> SNC_VERSION : $(sequencer_VERSION)" - @echo ">> Preprocessing $(<F)" - $(RM) $(*F).i - $(CPP) ${CPPSNCFLAGS1} $< > $(*F).i - @echo ">> Converting $(*F).i to $(*F).c" - $(RM) $@ @echo ">> SNC is defined as $(SNC)" - $(SNC) $(TARGET_SNCFLAGS) $(SNCFLAGS) $(*F).i -o $(*F).c.tmp - @mv $(*F).c.tmp $(*F).c - @echo ">> Compiling $(*F).c" - $(RM) $@ - $(COMPILE.c) -c ${SNC_CFLAGS} $(*F).c + $(SNC) $(TARGET_SNCFLAGS) $(SNCFLAGS) $(*F).i -o $(*F).c + +%_snl.dbd: %.c @echo ">> Building $(*F)_snl.dbd" awk -F [\(\)] '/epicsExportRegistrar/ { print "registrar (" $$2 ")"}' $(*F).c > $(*F)_snl.dbd -%$(OBJ) %_snl.dbd: %.stt +%.c: %.stt @echo "" @echo ">> SNC building process .... " @echo ">> SNC : $(SNC)" @echo ">> SNC_VERSION : $(sequencer_VERSION)" - @echo ">> Preprocessing $(<F)" - $(RM) $(*F).i - $(CPP) ${CPPSNCFLAGS1} $< > $(*F).i - @echo ">> Converting $(*F).i to $(*F).c" - $(RM) $@ @echo ">> SNC is defined as $(SNC)" - $(SNC) $(TARGET_SNCFLAGS) $(SNCFLAGS) $(*F).i -o $(*F).c.tmp - @mv $(*F).c.tmp $(*F).c - @echo ">> Compiling $(*F).c" - $(RM) $@ - $(COMPILE.c) -c ${SNC_CFLAGS} $(*F).c - @echo "Building $(*F)_snl.dbd" - awk -F [\(\)] '/epicsExportRegistrar/ { print "registrar(" $$2 ")"}' $(*F).c > $(*F)_snl.dbd + $(SNC) $(TARGET_SNCFLAGS) $(SNCFLAGS) $< -o $(*F).c # Create GPIB code from *.gt file. diff --git a/tests/test_sequencer.py b/tests/test_sequencer.py new file mode 100644 index 00000000..327d0925 --- /dev/null +++ b/tests/test_sequencer.py @@ -0,0 +1,160 @@ +import os +import pathlib +import subprocess + +import pytest +from git import Repo + +from .utils import Wrapper + +GITLAB_URL = "https://gitlab.esss.lu.se" + +TEST_SEQ_SRC = """ +program test + +ss ss1 {{ + state init {{ + when(delay(1)) {{ + printf({}); + }} + state init + }} +}} +""" + + +class SequencerBuild: + sequencer_url = f"{GITLAB_URL}/e3/wrappers/core/e3-sequencer.git" + + def __init__(self, path: pathlib.Path): + self.epics_base = pathlib.Path(os.getenv("EPICS_BASE")) + self.base_version = self.epics_base.name.split("base-")[-1] + assert self.base_version + + self.require_version = os.getenv("E3_REQUIRE_VERSION") + assert self.require_version + + self.version = "sequencer_test" + + self.host_arch = os.getenv("EPICS_HOST_ARCH") + assert self.host_arch + + self.path = path / "e3-sequencer" + + Repo.clone_from(self.sequencer_url, self.path) + + self.config_dir = self.path / "configure" + self.config_dir.mkdir(exist_ok=True) + + (self.config_dir / "RELEASE.local").write_text( + f"""EPICS_BASE := {self.epics_base} +E3_REQUIRE_VERSION := {self.require_version}""" + ) + (self.config_dir / "CONFIG_MODULE.local").write_text( + f"E3_MODULE_VERSION := {self.version}" + ) + + self.cell_path = self.path / "cellMods" + + make_arg = ["make", "-C", self.path] + results = subprocess.run( + make_arg + ["init"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + encoding="utf-8", + ) + assert results.returncode == 0 + results = subprocess.run(make_arg + ["patch"]) + assert results.returncode == 0 + results = subprocess.run(make_arg + ["build"]) + assert results.returncode == 0 + results = subprocess.run( + make_arg + ["cellinstall", f"E3_CELL_PATH={self.cell_path}"] + ) + assert results.returncode == 0 + + self.snc_path = ( + self.cell_path + / f"base-{self.base_version}" + / f"require-{self.require_version}" + / "sequencer" + / self.version + / "bin" + / self.host_arch + / "snc" + ) + assert self.snc_path.is_file() + + +@pytest.fixture(scope="module") +def sequencer(tmp_path_factory): + path = tmp_path_factory.mktemp("sequencer_build") + yield SequencerBuild(path) + + +class SequencerWrapper(Wrapper): + def __init__(self, sequencer: SequencerBuild, path: pathlib.Path): + super().__init__(path) + self.sequencer = sequencer + + self.add_var_to_config_module("SEQUENCER_DEP_VERSION", self.sequencer.version) + + def run_make(self, *args): + return super().run_make( + *args, + f"E3_CELL_PATH={self.sequencer.cell_path}", + f"SNC={self.sequencer.snc_path}", + ) + + +@pytest.fixture +def sequence_wrapper(sequencer, tmp_path): + yield SequencerWrapper(sequencer, tmp_path) + + +@pytest.mark.parametrize("extension", ["st", "stt"]) +def test_compile_snl_file(sequence_wrapper: SequencerWrapper, extension): + snl_filename = "test_filename" + seq_source = sequence_wrapper.module_dir / f"{snl_filename}.{extension}" + seq_source.write_text(TEST_SEQ_SRC.format('""')) + + sequence_wrapper.add_var_to_module_makefile( + "SOURCES", f"{snl_filename}.{extension}" + ) + + rc, *_ = sequence_wrapper.run_make("cellbuild") + assert rc == 0 + + assert (sequence_wrapper.build_dir / f"{snl_filename}.o").is_file() + assert (sequence_wrapper.build_dir / f"{snl_filename}_snl.dbd").is_file() + + +def test_preprocess_st_file(sequence_wrapper: SequencerWrapper): + snl_filename = "test_file.st" + seq_src = sequence_wrapper.module_dir / snl_filename + seq_src.write_text( + '#define MESSAGE "waiting\\n"\n' + TEST_SEQ_SRC.format("MESSAGE") + ) + + sequence_wrapper.add_var_to_module_makefile("SOURCES", snl_filename) + + rc, *_ = sequence_wrapper.run_make("cellbuild") + assert rc == 0 + + +def test_do_not_preprocess_stt_file(sequence_wrapper: SequencerWrapper): + snl_filename = "test_file" + seq_src = sequence_wrapper.module_dir / f"{snl_filename}.stt" + seq_src.write_text( + '#define MESSAGE "waiting\\n"\n' + TEST_SEQ_SRC.format("MESSAGE") + ) + + sequence_wrapper.add_var_to_module_makefile("SOURCES", f"{snl_filename}.stt") + + rc, _, errs = sequence_wrapper.run_make("cellbuild") + assert rc == 2 + assert ( + f"No rule to make target `{snl_filename}.c', needed by `{snl_filename}_snl.dbd'" + in errs + ) + assert not (sequence_wrapper.build_dir / f"{snl_filename}.i").is_file() diff --git a/tests/utils.py b/tests/utils.py index 3a9958da..e8c39439 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -15,12 +15,15 @@ class Wrapper: 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 = ( - Path(os.environ.get("EPICS_BASE")) - / "require" - / os.environ.get("E3_REQUIRE_VERSION") - / "configure" + self.epics_base / "require" / self.require_version / "configure" ) assert e3_require_config.is_dir() @@ -38,6 +41,8 @@ class Wrapper: self.module_dir = self.path / module_path self.module_dir.mkdir(parents=True) + self.build_dir = self.module_dir / f"O.{self.base_version}_{self.host_arch}" + self.config_dir = self.path / "configure" self.config_dir.mkdir() @@ -61,7 +66,7 @@ REQUIRE_CONFIG:={e3_require_config} include $(REQUIRE_CONFIG)/CONFIG include $(REQUIRE_CONFIG)/RULES_SITEMODS """ - (self.makefile).write_text(makefile_contents) + self.makefile.write_text(makefile_contents) self.module_makefile = self.path / f"{name}.Makefile" module_makefile_contents = """ @@ -70,7 +75,7 @@ include $(E3_REQUIRE_TOOLS)/driver.makefile EXCLUDE_ARCHS+=debug """ - (self.module_makefile).write_text(module_makefile_contents) + self.module_makefile.write_text(module_makefile_contents) if include_dbd: self.add_file("test.dbd") -- GitLab