Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • e3/wrappers/e3-require
  • waynelewis/e3-require
  • krisztianloki/e3-require
  • timokorhonen/e3-require
  • juntongliu/e3-require
  • roryclarke/e3-require
  • alfiorizzo/e3-require
  • lucasmagalhaes/e3-require
  • lucas-module-testgroup/e3-require
  • grzegorzkowalski/e3-require
  • anderslindh1/e3-require
11 results
Show changes
Showing
with 1503 additions and 530 deletions
#!/usr/bin/env bash
# shellcheck disable=SC2034
# -*- mode: sh -*-
#
# Copyright (c) 2004 - 2017 Paul Scherrer Institute
# Copyright (c) 2017 - 2021 European Spallation Source ERIC
#
# The program is free software: you can redistribute
# it and/or modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation, either version 2 of the
# License, or any newer version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along with
# this program. If not, see https://www.gnu.org/licenses/gpl-2.0.txt
#
#
# PSI original iocsh author : Dirk Zimoch
# ESS author : Jeong Han Lee
# ESS maintainer : Simon Rose
# email : simon.rose@ess.eu
# date : 2021-12-10
#
REALTIME=
__LOADER__=
# Usage :
# e3_version="$(read_file_get_string "${file_name}" "E3_VERSION:=")";
function read_file_get_string {
local FILENAME=$1
local PREFIX=$2
sed -n "s/^$PREFIX\(.*\)/\1/p" "$FILENAME"
}
# Base Code is defined with 8 digits numbers
# Two digits are enough to cover all others (I think)
# 7.0.6.1 : 07000601
#
# First Two : 00 (EPICS VERSION)
# Second Two : 00 (EPICS_REVISION)
# Third Two : 00 (EPICS_MODIFICATION)
# Fourth Two : 00 (EPICS_PATCH_LEVEL)
function basecode_generator() { #@ Generator BASECODE
#@ USAGE: BASECODE=$(basecode_generator)
local epics_ver_maj epics_ver_mid epics_ver_min epics_ver_patch
epics_ver_maj="$(read_file_get_string "${EPICS_BASE}/configure/CONFIG_BASE_VERSION" "EPICS_VERSION = ")"
epics_ver_mid="$(read_file_get_string "${EPICS_BASE}/configure/CONFIG_BASE_VERSION" "EPICS_REVISION = ")"
epics_ver_min="$(read_file_get_string "${EPICS_BASE}/configure/CONFIG_BASE_VERSION" "EPICS_MODIFICATION = ")"
epics_ver_patch="$(read_file_get_string "${EPICS_BASE}/configure/CONFIG_BASE_VERSION" "EPICS_PATCH_LEVEL = ")"
local base_code=""
if [[ ${#epics_ver_maj} -lt 2 ]]; then
epics_ver_maj="00${epics_ver_maj}"
epics_ver_maj="${epics_ver_maj: -2}"
fi
if [[ ${#epics_ver_mid} -lt 2 ]]; then
epics_ver_mid="00${epics_ver_mid}"
epics_ver_mid="${epics_ver_mid: -2}"
fi
if [[ ${#epics_ver_min} -lt 2 ]]; then
epics_ver_min="00${epics_ver_min}"
epics_ver_min="${epics_ver_min: -2}"
fi
if [[ ${#epics_ver_patch} -lt 2 ]]; then
epics_ver_patch="00${epics_ver_patch}"
epics_ver_patch="${epics_ver_patch: -2}"
fi
base_code=${epics_ver_maj}${epics_ver_mid}${epics_ver_min}${epics_ver_patch}
echo "$base_code"
}
function version() {
printf "%s : %s%s\n" "European Spallation Source ERIC" "$SC_SCRIPTNAME" ${SC_VERSION:+" ($SC_VERSION)"} >&2
exit
}
function print_iocsh_header() {
printf "███████╗██████╗ ██╗ ██████╗ ██████╗ ███████╗██╗ ██╗███████╗██╗ ██╗ \n"
printf "██╔════╝╚════██╗ ██║██╔═══██╗██╔════╝ ██╔════╝██║ ██║██╔════╝██║ ██║ \n"
printf "█████╗ █████╔╝ ██║██║ ██║██║ ███████╗███████║█████╗ ██║ ██║ \n"
printf "██╔══╝ ╚═══██╗ ██║██║ ██║██║ ╚════██║██╔══██║██╔══╝ ██║ ██║ \n"
printf "███████╗██████╔╝ ██║╚██████╔╝╚██████╗ ███████║██║ ██║███████╗███████╗███████╗\n"
printf "╚══════╝╚═════╝ ╚═╝ ╚═════╝ ╚═════╝ ╚══════╝╚═╝ ╚═╝╚══════╝╚══════╝╚══════╝\n"
printf "\n"
}
function print_header_line() {
printf "############################################################################\n"
printf "## %s\n" "${1}"
printf "############################################################################\n"
}
function print_vars() {
print_header_line "$1"
shift
for var in "$@"; do
printf "# %s = \"%s\"\n" "${var}" "${!var}"
done
printf "#\n"
}
function printIocEnv() {
printf "# Start at \"%s\"\n" "$(date +%Y-W%V-%b%d-%H%M-%S-%Z)"
printf "# %s : %s%s\n" "European Spallation Source ERIC" "$SC_SCRIPTNAME" ${SC_VERSION:+" ($SC_VERSION)"}
printf "#\n"
print_vars "Shell and environment variables" PWD USER LOGNAME PATH
print_vars "EPICS variables" EPICS_BASE EPICS_HOST_ARCH EPICS_DRIVER_PATH EPICS_CA_AUTO_ADDR_LIST EPICS_CA_ADDR_LIST EPICS_PVA_AUTO_ADDR_LIST EPICS_PVA_ADDR_LIST
print_vars "e3-specific variables" E3_REQUIRE_VERSION E3_REQUIRE_LOCATION E3_REQUIRE_BIN E3_REQUIRE_DB E3_REQUIRE_DBD E3_REQUIRE_INC E3_REQUIRE_LIB
}
# Ctrl+c : OK
# exit : OK
# kill softioc process : OK
# kill main process : Enter twice in terminal,
# close softIoc, but STARTUP file is remained.
#
function softIoc_end() {
local startup_file=$1
rm -f "${startup_file}"
# only clean terminal when stdout is opened on a terminal
# avoid "stty: standard input: Inappropriate ioctl for device" otherwise
[[ -t 1 ]] && stty sane
exit
}
function die() { #@ Print error message and exit with error code
#@ USAGE: die [errno [message]]
error=${1:-1}
## exits with 1 if error number not given
shift
[ -n "$*" ] &&
printf "%s%s: %s\n" "$SC_SCRIPTNAME" ${SC_VERSION:+" ($SC_VERSION)"} "$*" >&2
exit "$error"
}
function iocsh_ps1() {
local iocsh_ps1=""
local pid="$1"
# Keep only short hostname (without domain)
local host=${HOSTNAME%%.*}
iocsh_ps1+=${host:0:15}
iocsh_ps1+="-"
iocsh_ps1+=$pid
iocsh_ps1+=" > "
echo "${iocsh_ps1}"
}
# Please look at the limitation in require.c in registerModule()
# /*
# Require DB has the following four PVs:
# - $(REQUIRE_IOC):$(MODULE)_VER
# - $(REQUIRE_IOC):MOD_VER
# - $(REQUIRE_IOC):VERSIONS
# - $(REQUIRE_IOC):MODULES
# We reserved 30 chars for :$(MODULE)_VER, so MODULE has the maximum 24 chars.
# And we've reserved for 30 chars for $(REQUIRE_IOC).
# So, the whole PV and record name in moduleversion.template has 59 + 1.
# */
function require_ioc() {
# e3-ioc-hash-hostname-pid fails when host has icslab-ser03 and IOCUSER-VIRTUALBOX
# so better to keep simple in case when hostname is long.
# And it has the limitation of PV length
# #define PVNAME_STRINGSZ 61 in EPICS_BASE/include/dbDefs.h
local require_ioc=""
# Test if IOCNAME is defined
if [ -z "${IOCNAME}" ]; then
local pid="$1"
# Keep only short hostname (without domain)
local hostname=${HOSTNAME%%.*}
# Record name should not have . character, because it is used inside record
require_ioc="REQMOD" # char 6 ( 6)
require_ioc+=":" # char 1 ( 7)
require_ioc+=${hostname:0:15} # char 15 (22)
require_ioc+="-" # char 1 (23)
require_ioc+=${pid} # char 7 (30), max pid in 64 bit 4194304 (7),
else
require_ioc=${IOCNAME:0:30}
fi
echo "${require_ioc}"
}
function loadRequire() {
local libPrefix=lib
local libPostfix=.so
local libName=${libPrefix}${E3_REQUIRE_NAME}${libPostfix}
local require_lib=${E3_REQUIRE_LIB}/${EPICS_HOST_ARCH}/${libName}
local require_dbd=${E3_REQUIRE_DBD}/${E3_REQUIRE_NAME}.dbd
printf "# // Load %s module, version %s\n" "${E3_REQUIRE_NAME}" "${E3_REQUIRE_VERSION}"
printf "#\n"
printf "dlload %s\n" "${require_lib}"
printf "dbLoadDatabase %s\n" "${require_dbd}"
printf "%s_registerRecordDeviceDriver\n\n" "${E3_REQUIRE_NAME%-*}"
printf "# \n"
}
function check_mandatory_env_settings() {
declare -a var_list=()
var_list+=(EPICS_HOST_ARCH)
var_list+=(EPICS_BASE)
var_list+=(E3_REQUIRE_NAME)
var_list+=(E3_REQUIRE_BIN)
var_list+=(E3_REQUIRE_LIB)
var_list+=(E3_REQUIRE_DB)
var_list+=(E3_REQUIRE_DBD)
var_list+=(E3_REQUIRE_VERSION)
for var in "${var_list[@]}"; do
if [[ -z "${!var}" ]]; then
die 1 " $var is not defined!. Please source ${E3_REQUIRE_BIN}/setE3Env.bash "
fi
done
if [[ -z "$IOCNAME" ]]; then
echo "Warning: environment variable IOCNAME is not set." >&2
else
echo "IOCNAME is set to $IOCNAME"
fi
}
function setPaths() {
while [ "$#" -gt 0 ]; do
arg="$1"
case $arg in
-l)
shift
add_path="$1/$(basename "${EPICS_BASE}")/require-${E3_REQUIRE_VERSION}"
printf "epicsEnvSet EPICS_DRIVER_PATH %s:${EPICS_DRIVER_PATH}\n" "$add_path"
EPICS_DRIVER_PATH="$add_path:$EPICS_DRIVER_PATH"
;;
esac
shift
done
}
function loadFiles() {
while [ "$#" -gt 0 ]; do
arg=$1
case $arg in
-rt | -RT | -realtime | --realtime)
REALTIME="RT"
__LOADER__="chrt --fifo 1 "
;;
@*)
loadFiles "$(cat "${arg#@}")"
;;
*=*)
echo -n "$arg" | awk -F '=' '{printf "epicsEnvSet %s '\''%s'\''\n" $1 $2}'
;;
-c)
shift
case $1 in
seq*)
if [ "$init" != NO ]; then
echo "iocInit"
init=NO
fi
;;
iocInit)
init=NO
;;
esac
echo "$1"
;;
-s)
shift
if [ "$init" != NO ]; then
echo "iocInit"
init=NO
fi
echo "seq $1"
;;
-i | -noinit | --noinit)
init=NO
;;
-r)
shift
echo "require $1"
;;
-l) # This is taken care of in setPaths()
shift
;;
-dg)
if [[ -n "${2%--dgarg=*}" ]]; then
__LOADER__="gdb --eval-command run --args "
else
shift
if [[ -z "${1#*=}" ]]; then
__LOADER__="gdb "
else
__LOADER__="gdb ${1#*=} "
fi
fi
;;
-dv)
if [[ -n "${2%--dvarg=*}" ]]; then
__LOADER__="valgrind --leak-check=full "
else
shift
if [[ -z "${1#*=}" ]]; then
__LOADER__="valgrind "
else
__LOADER__="valgrind ${1#*=} "
fi
fi
;;
-n)
__LOADER__="nice --10 "
shift
;;
-*)
printf "Unknown option %s\n\n" "$1" >&2
help
;;
*.so)
echo "dlload \"$arg\""
;;
*)
subst=""
while [ "$#" -gt 1 ]; do
case $2 in
*=*)
subst="$subst,$2"
shift
;;
*)
break
;;
esac
done
subst=${subst#,}
case $arg in
*.db | *.template)
echo "dbLoadRecords '$arg','$subst'"
;;
*.subs | *.subst)
echo "dbLoadTemplate '$arg','$subst'"
;;
*.dbd)
# some dbd files must be loaded before main to take effect
echo "dbLoadDatabase '$arg','$DBD','$subst'"
;;
*)
set_e3_cmd_top "$arg"
echo "iocshLoad '$arg','$subst'"
# Search for any instance of iocInit at the start of the line.
# If found, do not add the iocInit to the startup script. Any
# other occurrence of iocInit (e.g. in comments) is not matched
# and the script will add an active iocInit.
if grep -q "^\s*iocInit\b" "$arg"; then
init=NO
fi
;;
esac
;;
esac
shift
done
}
function set_e3_cmd_top() {
local file=$1
local file_path=""
local file_top=""
local file_name=""
if [ -f "$file" ]; then
file_path="$(readlink -e "$file")"
file_top="${file_path%/*}"
file_name=${file##*/}
printf "# Set E3_CMD_TOP for the absolute path where %s exists\n" "$file_name"
printf "epicsEnvSet E3_CMD_TOP \"%s\"\n" "$file_top"
printf "#\n"
fi
}
function help() {
{
printf "\n"
printf "USAGE: iocsh [startup files]\n"
printf "\n"
printf "Start the ESS iocsh and load startup scripts.\n\n"
printf "Options:\n\n"
printf " -?, -h, --help Show this page and exit.\n"
printf " -v, --version Show version and exit.\n"
printf " -rt Execute in realtime mode.\n"
printf " (Also -RT, -realtime, --realtime)\n"
printf " -c 'cmd args' Ioc shell command.\n"
printf " -s 'prog m=v' Sequencer program (and arguments), run with 'seq'.\n"
printf " This forces an 'iocInit' before running the program.\n"
printf " -i Do not add iocInit. This option does not override\n"
printf " a valid iocInit in the startup script.\n"
printf " (Also -noinit, --noinit)\n"
printf " -r module[,ver] Module (optionally with version) loaded via 'require'.\n"
printf " -l 'cell path' Run Ioc with a cell path.\n"
printf " -dg [--dgarg='gdb-options'] Run with debugger gdb with user selected options or default option.\n"
printf " -dv [--dvarg='valgrind-options'] Run with valgrind with user selected options or default option.\n"
printf " -n Run with 'nice --10' (requires sudo).\n"
printf " @file More arguments are read from file.\n\n"
printf "Supported filetypes:\n\n"
printf " *.db, *.dbt, *.template loaded via 'dbLoadRecords'\n"
printf " *.subs, *.subst loaded via 'dbLoadTemplate'\n"
printf " *.dbd loaded via 'dbLoadDatabase'\n"
printf " *.so loaded via 'dlload'\n"
printf "\n"
printf "All other files are executed as startup scripts by the EPICS shell.\n"
printf "After a file you can specify substitutions like m1=v1 m2=v1 for that file.\n\n"
printf "Examples:\n"
printf " iocsh st.cmd\n"
printf " iocsh my_database.template P=XY M=3\n"
printf " iocsh -r my_module,version -c 'initModule()'\n"
printf " iocsh -c 'var requireDebug 1' st.cmd\n"
printf " iocsh -i st.cmd\n"
printf " iocsh -dv --dvarg='--vgdb=full'\n"
printf " iocsh -dv st.cmd\n\n"
} >&2
exit
}
for arg in "$@"; do
case $arg in
-h | "-?" | -help | --help)
help
;;
-v | -ver | --ver | -version | --version)
version
;;
*) ;;
esac
done
"""Utilities for iocsh."""
import atexit
import os
import logging
import socket
import sys
from tempfile import NamedTemporaryFile
from pathlib import Path
from sys import platform
DEFAULT_ERRLOG_BUFFER_SIZE = 2048
@atexit.register
def graceful_shutdown() -> None:
print("\nExiting e3 IOC shell")
os.system("/bin/bash -c '[[ -t 1 ]] && stty sane'")
class TemporaryStartupScript:
"""Class to manage IOC shell commands.
Holds on to commands in a buffer which it writes to file.
"""
def __init__(self, iocname: str = None) -> None:
self.file = NamedTemporaryFile(delete=False)
self.command_buffer = []
self._saved = False
self.set_variable("REQUIRE_IOC", iocname or generate_prefix())
self.set_variable("IOCSH_TOP", Path.cwd())
self.set_variable(
"IOCSH_PS1", f"{iocname} > " if iocname else generate_prompt()
)
# The message size maximum must be slightly smaller than the buffer size
# (to account for the null terminator)
self.add_command(
f"errlogInit2 {DEFAULT_ERRLOG_BUFFER_SIZE} {DEFAULT_ERRLOG_BUFFER_SIZE-1}"
)
if platform.startswith("linux"):
shared_lib_suffix = "so"
elif platform == "darwin":
shared_lib_suffix = "dylib"
else:
raise NotImplementedError(f"Unsupported platform: {platform}")
# load require
self.add_command(
f"dlload {str(Path(os.environ['E3_REQUIRE_LIB']) / os.environ['EPICS_HOST_ARCH'] / f'librequire.{shared_lib_suffix}')}"
)
self.add_command(
f"dbLoadDatabase {str(Path(os.environ['E3_REQUIRE_DBD']) / 'require.dbd')}"
)
self.add_command("require_registerRecordDeviceDriver")
@property
def name(self) -> str:
return self.file.name
def __enter__(self) -> None:
return self
def __exit__(self, *args) -> None:
self.file.close()
def _get_content(self) -> str:
"""Return list of commands as multi-line string."""
return "\n".join(self.command_buffer) + "\n" # ensure newline at EOF
def save(self) -> None:
"""Store command 'buffer' to file."""
if self._saved:
raise RuntimeError("File has already been saved")
self.file.write(self._get_content().encode())
self.file.flush()
self._saved = True
def set_variable(self, var: str, val: str) -> None:
self.add_command(
f'epicsEnvSet {var} "{val}"'
) # add quotation marks to avoid symbols being interpreted
def load_snippet(self, name: str) -> None:
self.add_command(f"iocshLoad {name}")
def load_module(self, name: str) -> None:
self.add_command(f"require {name}")
def load_database(self, name: str) -> None:
self.add_command(f"dbLoadRecords {name}")
def add_command(self, command: str) -> None:
self.command_buffer.append(command)
def verify_environment_and_return_require_version() -> str:
"""Verify select known EPICS and e3 variables and return current require version."""
def check_mandatory_env_vars() -> None:
mandatory_vars = (
"EPICS_HOST_ARCH",
"EPICS_BASE",
"E3_REQUIRE_VERSION",
"E3_REQUIRE_NAME",
"E3_REQUIRE_BIN",
"E3_REQUIRE_LIB",
"E3_REQUIRE_DB",
"E3_REQUIRE_DBD",
)
for var in mandatory_vars:
_ = os.environ[var]
try:
check_mandatory_env_vars()
except KeyError as e:
logging.debug(f"Environment variable {e} is not set")
sys.exit("Please source an environment before you try to use the IOC shell")
# compare path of this script to sourced environment's executables
if not str(Path(__file__).resolve().parent) == os.environ["E3_REQUIRE_BIN"]:
logging.debug(
f"Sourced environment is '{os.environ['E3_REQUIRE_BIN']}' and this script is from '{Path(__file__).resolve().parent}'"
)
sys.exit(
"You have sourced a different environment than what this IOC shell is from"
)
return os.environ["E3_REQUIRE_VERSION"]
def extract_require_version() -> str:
"""Return loaded environment's version of require."""
try:
return os.environ["E3_REQUIRE_VERSION"]
except KeyError:
sys.exit("Please source an environment before you try to use the IOC shell")
def generate_prompt() -> str:
"""Return IOC shell prompt."""
fqdn = socket.gethostname()
hostname, *_ = fqdn.partition(".")
prompt = f"{hostname}-{os.getpid()}"
return f"{prompt} > "
def generate_prefix() -> str:
"""Return fallback PV prefix."""
try:
user = os.getlogin()
except OSError: # for wonky cases
user = "UNKNOWN"
return f"TEST:{user}-{os.getpid()}"
def generate_banner() -> str:
"""Return ascii art banner."""
ascii_art = """
,----. ,--. ,-----. ,-----. ,--. ,--.,--.
,---. '.-. | | |' .-. '' .--./ ,---. | ,---. ,---. | || |
| .-. : .' < | || | | || | ( .-' | .-. || .-. :| || |
\ --./'-' | | |' '-' '' '--'\ .-' `)| | | |\ --.| || |
`----'`----' `--' `-----' `-----' `----' `--' `--' `----'`--'`--'
"""
return ascii_art
......@@ -8,8 +8,8 @@
#
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"
# shellcheck source=require-ess/tools/setE3Env.bash
source "$DIR"/setE3Env.bash
# shellcheck source=require-ess/tools/activate
source "$DIR"/activate
IFS='/' read -ra base <<<"$EPICS_BASE"
PS1="(EPICS-${base[-1]})$PS1"
#!/usr/bin/env bash
# Small script to fetch current build nuber for a given module. If you pass a numeric version
# but no build number, it fetches the latest build number installed from the target e3 env.
# but no revision number, it fetches the latest revision number installed from the target e3 env.
#
# Arguments:
# $1: require module path e.g. /epics/base-$BASE_VERSION/require/$REQUIRE_VERSION/siteMods
......
......@@ -27,9 +27,7 @@ where_am_I := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
# It is easy to maintain RULES_E3 if we use the "repository" makefile
# instead of the installed makefile.
#
include $(where_am_I)/tools/driver.makefile
BUILDCLASSES += Linux
include $(where_am_I)/require-ess/tools/driver.makefile
APP := .
APPSRC := $(APP)/src
......@@ -37,22 +35,11 @@ APPDB := $(APP)/Db
SOURCES += $(APPSRC)/require.c
SOURCES += $(APPSRC)/version.c
SOURCES += $(APPSRC)/afterInit.c
SOURCES += $(APPSRC)/common.c
SOURCES += $(APPSRC)/module.c
DBDS += $(APPSRC)/require.dbd
SOURCES += $(APPSRC)/runScript.c
DBDS += $(APPSRC)/runScript.dbd
SOURCES += $(APPSRC)/expr.c
SOURCES += $(APPSRC)/dbLoadTemplate.y
DBDS += $(APPSRC)/dbLoadTemplate.dbd
# ESS doesn't have any T2_ppc604 and vxWorks target
# Friday, May 11 22:05:07 CEST 2018, jhlee
#
# SOURCES_T2 += strdup.c
# SOURCES_vxWorks += asprintf.c
#
# HEADERS += strdup.h
# HEADERS += asprintf.h
DBDS += $(APPSRC)/afterInit.dbd
HEADERS += $(APPSRC)/require.h
......@@ -62,7 +49,7 @@ HEADERS += $(APPSRC)/require.h
#
USR_INCLUDES_Linux=-idirafter $(EPICS_BASE)/include
USR_CFLAGS += -std=c99
USR_CFLAGS += -std=gnu99
# ESS require doesn't use T_A, because Linux should handle linux as "1"
# instead of its name. ESS require can handle them within the EPICS
......@@ -70,35 +57,4 @@ USR_CFLAGS += -std=c99
#
# USR_CFLAGS += -DT_A='"${T_A}"'
# ESS doesn't support WIN32
# This should really go into some global WIN32 config file
#
# USR_CFLAGS_WIN32 += /D_WIN32_WINNT=0x501
TEMPLATES += $(APPDB)/moduleversion.template
# TEMPLATES += moduleversion.db
vpath dbLoadTemplate_lex.l ../$(APPSRC)
dbLoadTemplate.c: dbLoadTemplate_lex.c ../$(APPSRC)/dbLoadTemplate.h
# moduleversion should convert to db instead of template
# So, ESS uses it internally independent upon any IOC
# variables
#
EPICS_BASE_HOST_BIN = $(EPICS_BASE)/bin/$(EPICS_HOST_ARCH)
MSI = $(EPICS_BASE_HOST_BIN)/msi
USR_DBFLAGS += -I . -I ..
USR_DBFLAGS += -I$(EPICS_BASE)/db
TMPS = $(wildcard $(APPDB)/*.template)
.PHONY: db
db: $(TMPS)
.PHONY: $(TMPS)
$(TMPS):
@printf "Inflating database ... %44s >>> %40s \n" "$@" "$(basename $(@)).db"
@rm -f $(basename $(@)).db.d $(basename $(@)).db
@$(MSI) -D $(USR_DBFLAGS) -o $(basename $(@)).db $@ > $(basename $(@)).db.d
@$(MSI) $(USR_DBFLAGS) -o $(basename $(@)).db $@
extend-select = ["D212"]
extend-ignore = ["D105", "D107", "D202", "D418", "E501"]
import os
import re
import pytest
@pytest.mark.parametrize(
"installed_archs, expected",
[("foo", []), ("foo-bar foo-baz baz baz-qux", ["baz", "baz-qux"])],
)
def test_arch_filter(wrapper, installed_archs, expected):
arch_regex = re.compile(r"Pass 2: T_A =\s*([^\s]+)")
wrapper.write_var_to_makefile(
wrapper.module_makefile,
"CROSS_COMPILER_TARGET_ARCHS",
installed_archs,
)
_, outs, _ = wrapper.run_make("debug", EXCLUDE_ARCHS="foo")
host_arch = os.getenv("EPICS_HOST_ARCH")
build_archs = [arch for arch in arch_regex.findall(outs) if arch != host_arch]
assert build_archs == expected
def test_load_custom_config_files(wrapper):
custom_config_file = wrapper.config_dir / "CONFIG_FOO"
custom_config_file.write_text("$(error foobarbaz)")
wrapper.add_file(custom_config_file)
wrapper.write_var_to_makefile(
wrapper.module_makefile, "CONFIGS", str(custom_config_file), modifier="+"
)
rc, _, errs = wrapper.run_make("build")
assert rc == 2
assert "foobarbaz" in errs
def test_append_missing_revision_number(wrapper):
version = "1.2.3"
wrapper.write_var_to_makefile(
wrapper.config_dir / "CONFIG_MODULE.local", "E3_MODULE_VERSION", version
)
rc, outs, _ = wrapper.run_make("vars")
assert rc == 0
assert f"E3_MODULE_VERSION = {version}+0" in outs.splitlines()
def test_warning_msg_if_building_from_sitemods(wrapper):
rc, _, errs = wrapper.run_make(
"build_path_check", E3_MODULES_PATH=wrapper.wrapper_dir / ".."
)
assert rc == 0
assert (
"It is ill-advised to build a module from the module install location." in errs
)
def test_automated_subs_tmps_db_expansion(wrapper):
wrapper.write_var_to_makefile(
wrapper.module_makefile, "TMPS", "templates/a.template", modifier="+"
)
wrapper.write_var_to_makefile(
wrapper.module_makefile, "SUBS", "b.substitutions", modifier="+"
)
wrapper.write_var_to_makefile(
wrapper.module_makefile, "USR_DBFLAGS", "-I templates", modifier="+"
)
template_dir = wrapper.module_dir / "templates"
template_dir.mkdir()
template_file = template_dir / "a.template"
template_file_contents = "record (ai, $(P)) {}"
template_file.write_text(template_file_contents)
substitution_file = wrapper.module_dir / "b.substitutions"
substitution_file.write_text(
"""file "a.template"
{
pattern { P }
{ "$(PREF)" }
}
"""
)
common_dir = wrapper.module_dir / f"O.{wrapper.base_version}_Common"
rc, *_ = wrapper.run_make("db_internal")
assert rc == 0
expanded_template_file = common_dir / "a.db"
assert expanded_template_file.read_text() == template_file_contents
expanded_substitution_file = common_dir / "b.db"
assert expanded_substitution_file.read_text() == template_file_contents.replace(
"$(P)", "$(PREF)"
)
def test_automated_subs_tmps_db_expansion_priority(wrapper):
wrapper.write_var_to_makefile(
wrapper.module_makefile, "TMPS", "a.template", modifier="+"
)
wrapper.write_var_to_makefile(
wrapper.module_makefile, "SUBS", "a.substitutions", modifier="+"
)
wrapper.write_var_to_makefile(
wrapper.module_makefile, "USR_DBFLAGS", "-I.", modifier="+"
)
template_file = wrapper.module_dir / "a.template"
template_file_contents = "record (ai, $(FOO)) {}"
template_file.write_text(template_file_contents)
substitution_file = wrapper.module_dir / "a.substitutions"
substitution_file.write_text(
"""file "a.template"
{
pattern { FOO }
{ "$(BAR)" }
}
"""
)
common_dir = wrapper.module_dir / f"O.{wrapper.base_version}_Common"
rc, *_ = wrapper.run_make("db_internal")
assert rc == 0
expanded_template_file = common_dir / "a.db"
assert "BAR" in expanded_template_file.read_text()
assert "FOO" not in expanded_template_file.read_text()
import os
import re
from pathlib import Path
import sys
import pytest
from .utils import Wrapper, run_ioc_get_output
from ..utils import run_ioc_get_output
RE_MISSING_FILE = "No rule to make target [`']{filename}'"
RE_MISSING_VERSION = "Module '{module}' version '{version}' does not exist."
RE_MODULE_VERSION_EXISTS = "Error .*{module}/{version}.* already exists"
def test_match_versions(wrapper_factory):
# This tests if the dependencies
# B->A->C
# B->C
# with A and B both requesting same version of C
def create_patch_file(path, desc):
path.parent.mkdir(parents=True, exist_ok=True)
patch_file_contents = """
diff --git database.db database.db
index 1806ff6..8701832 100644
--- database.db
+++ database.db
@@ -1,3 +1,3 @@
record(ai, "TEST") {{
-
+ field(DESC, "{desc}")
}}
"""
with open(path, "w") as f:
f.write(patch_file_contents.format(desc=desc))
wrapper_dep = wrapper_factory.create()
wrapper_a = wrapper_factory.create()
wrapper_b = wrapper_factory.create()
def test_patch(wrapper: Wrapper):
db_path = wrapper.module_dir / "database.db"
db_file_contents = """record(ai, "TEST") {
}
"""
with open(db_path, "w") as f:
f.write(db_file_contents)
patch_dir = wrapper.path / "patch"
create_patch_file(patch_dir / "apply.p0.patch", "OK")
create_patch_file(patch_dir / wrapper.version / "do-not-apply.p0.patch", "Bad")
rc, _, _ = wrapper.run_make("patch")
assert rc == 0
with open(db_path, "r") as f:
db_contents = f.read()
assert 'field(DESC, "OK")' in db_contents
assert "Bad" not in db_contents
rc, _, _ = wrapper.run_make("build")
assert rc == 0
assert any((wrapper.module_dir).glob("O.*"))
rc, _, _ = wrapper.run_make("cellinstall")
assert rc == 0
assert any((wrapper.path / "cellMods").glob("**/*.db"))
def test_local_module(wrapper: Wrapper):
rc, outs, _ = wrapper.run_make("init")
assert rc == 0
assert "You are in the local source mode" in outs
def test_missing_dbd_file(wrapper: Wrapper):
wrapper.add_var_to_module_makefile("DBDS", "nonexistent.dbd")
rc, _, errs = wrapper.run_make("build")
assert rc == 2
assert re.search(
RE_MISSING_FILE.format(filename=re.escape("../nonexistent.dbd")),
errs,
)
def test_missing_source_file(wrapper: Wrapper):
wrapper.add_var_to_module_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")),
errs,
)
def test_missing_requirement(wrapper: Wrapper):
wrapper.add_var_to_config_module("FOO_DEP_VERSION", "bar")
rc, _, errs = wrapper.run_make("build")
assert rc == 0
assert 'Invalid dependency "FOO_DEP_VERSION"; pruning' in errs
def test_missing_dependent_version(wrappers):
wrapper_dep = wrappers.get()
wrapper_main = wrappers.get()
cell_path = wrapper_main.path / "cellMods"
rc, *_ = wrapper_dep.run_make("cellinstall", cell_path=cell_path)
assert rc == 0
missing_version = "not_a_real_version"
wrapper_main.add_var_to_config_module(
f"{wrapper_dep.name}_DEP_VERSION", missing_version
)
rc, _, errs = wrapper_main.run_make("cellbuild")
assert rc == 2
assert re.search(
RE_MISSING_VERSION.format(module=wrapper_dep.name, version=missing_version),
errs,
)
def test_header_install_location(wrapper: Wrapper):
subdir = wrapper.module_dir / "db" / "subdir"
subdir.mkdir(parents=True)
extensions = ["h", "hpp", "hxx", "hh"]
for ext in extensions:
wrapper.add_var_to_module_makefile("HEADERS", f"db/subdir/header.{ext}")
wrapper.add_var_to_module_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"
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
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(
module=wrapper_main.name,
version=new_version,
cell_path=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"
cell_path = wrapper_b.wrapper_dir / "cellmods"
dep_version = "1.0.0+0"
......@@ -202,18 +23,27 @@ def test_match_versions(wrappers):
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_a.write_var_to_makefile(
wrapper_a.config_module,
f"{wrapper_dep.name}_DEP_VERSION",
dep_version,
export=True,
)
# 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.write_var_to_makefile(
wrapper_b.config_module,
f"{wrapper_dep.name}_DEP_VERSION",
dep_version,
export=True,
)
# Wrapper b also depends on a
wrapper_b.add_var_to_config_module(
f"{wrapper_a.name}_DEP_VERSION", a_version, modifier=""
wrapper_b.write_var_to_makefile(
wrapper_b.config_module,
f"{wrapper_a.name}_DEP_VERSION",
a_version,
export=True,
)
rc, *_ = wrapper_dep.run_make(
......@@ -234,18 +64,17 @@ def test_match_versions(wrappers):
assert rc == 0
def test_unmatching_versions(wrappers):
"""Test unmatching version scenarion.
def test_unmatching_versions(wrapper_factory):
# This tests if the dependencies
# B->A->C
# B->C
# but A depends on a version of C different than B
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()
wrapper_dep = wrapper_factory.create()
wrapper_a = wrapper_factory.create()
wrapper_b = wrapper_factory.create()
cell_path = wrapper_b.path / "cellmods"
cell_path = wrapper_b.wrapper_dir / "cellmods"
dep_version_1 = "1.0.0+0"
dep_version_2 = "2.0.0+0"
......@@ -254,18 +83,24 @@ def test_unmatching_versions(wrappers):
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_a.write_var_to_makefile(
wrapper_a.config_module,
f"{wrapper_dep.name}_DEP_VERSION",
dep_version_1,
export=True,
)
# 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.write_var_to_makefile(
wrapper_b.config_module,
f"{wrapper_dep.name}_DEP_VERSION",
dep_version_2,
export=True,
)
# Wrapper b also depends on wrapper_a
wrapper_b.add_var_to_config_module(
f"{wrapper_a.name}_DEP_VERSION", a_version, modifier=""
wrapper_b.write_var_to_makefile(
wrapper_b.config_module, f"{wrapper_a.name}_DEP_VERSION", a_version, export=True
)
rc, *_ = wrapper_dep.run_make(
......@@ -293,20 +128,18 @@ def test_unmatching_versions(wrappers):
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.
"""
def test_indirect_unmatching_versions(wrapper_factory):
# This tests if the dependencies
# B->A->C
# B->D->C
# but 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()
wrapper_c = wrapper_factory.create()
wrapper_a = wrapper_factory.create()
wrapper_b = wrapper_factory.create()
wrapper_d = wrapper_factory.create()
cell_path = wrapper_b.path / "cellmods"
cell_path = wrapper_b.wrapper_dir / "cellmods"
c_version_a = "1.0.0+0"
c_version_d = "2.0.0+0"
......@@ -316,23 +149,29 @@ def test_indirect_unmatching_versions(wrappers):
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_a.write_var_to_makefile(
wrapper_a.config_module,
f"{wrapper_c.name}_DEP_VERSION",
c_version_a,
export=True,
)
# Wrapper d dependes on c
wrapper_d.add_var_to_config_module(
f"{wrapper_c.name}_DEP_VERSION", c_version_d, modifier=""
wrapper_d.write_var_to_makefile(
wrapper_d.config_module,
f"{wrapper_c.name}_DEP_VERSION",
c_version_d,
export=True,
)
# Wrapper b depends on d
wrapper_b.add_var_to_config_module(
f"{wrapper_d.name}_DEP_VERSION", d_version, modifier=""
wrapper_b.write_var_to_makefile(
wrapper_b.config_module, f"{wrapper_d.name}_DEP_VERSION", d_version, export=True
)
# Wrapper b also depends on a
wrapper_b.add_var_to_config_module(
f"{wrapper_a.name}_DEP_VERSION", a_version, modifier=""
wrapper_b.write_var_to_makefile(
wrapper_b.config_module, f"{wrapper_a.name}_DEP_VERSION", a_version, export=True
)
rc, *_ = wrapper_c.run_make(
......@@ -364,50 +203,50 @@ def test_indirect_unmatching_versions(wrappers):
assert rc != 0
def test_automated_dependency(wrappers):
wrapper_a = wrappers.get()
wrapper_b = wrappers.get()
def test_recursive_header_include(wrapper_factory):
wrapper_a = wrapper_factory.create()
wrapper_b = wrapper_factory.create()
wrapper_c = wrapper_factory.create()
wrapper_d = wrapper_factory.create()
cell_path = wrapper_a.path / "cellMods"
cell_path = wrapper_a.wrapper_dir / "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
wrapper_c.write_var_to_makefile(
wrapper_c.config_module,
f"{wrapper_d.name}_DEP_VERSION",
module_version,
export=True,
)
assert rc == 0
rc, *_ = wrapper_a.run_make(
"cellinstall", module_version=module_version, cell_path=cell_path
wrapper_b.write_var_to_makefile(
wrapper_b.config_module,
f"{wrapper_c.name}_DEP_VERSION",
module_version,
export=True,
)
wrapper_a.write_var_to_makefile(
wrapper_a.config_module,
f"{wrapper_b.name}_DEP_VERSION",
module_version,
export=True,
)
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
assert contents[0].strip() == "# Generated file. Do not edit."
assert f"{wrapper_b.name} {module_version}" == contents[1]
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_d.write_var_to_makefile(
wrapper_d.module_makefile, "HEADERS", f"{wrapper_d.name}.h", modifier="+"
)
wrapper_a.add_var_to_config_module(
f"{wrapper_c.name}_DEP_VERSION_not_an_arch", module_version
(wrapper_d.module_dir / f"{wrapper_d.name}.h").touch()
wrapper_a.write_var_to_makefile(
wrapper_a.module_makefile, "SOURCES", f"{wrapper_a.name}.c", modifier="+"
)
wrapper_a_source = wrapper_a.module_dir / f"{wrapper_a.name}.c"
wrapper_a_source.write_text(f'#include "{wrapper_d.name}.h"')
rc, *_ = wrapper_d.run_make(
"cellinstall", module_version=module_version, cell_path=cell_path
)
assert rc == 0
rc, *_ = wrapper_c.run_make(
"cellinstall", module_version=module_version, cell_path=cell_path
)
......@@ -424,186 +263,193 @@ def test_architecture_dependent_dependency(wrappers):
rc, outs, _ = run_ioc_get_output(
module=wrapper_a.name,
version=module_version,
cell_path=wrapper_a.path / "cellMods",
cell_path=wrapper_a.wrapper_dir / "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):
wrapper_a = wrappers.get()
wrapper_b = wrappers.get()
wrapper_c = wrappers.get()
assert f"Loaded {wrapper_c.name} version {module_version}" in outs
cell_path = wrapper_a.path / "cellMods"
module_version = "0.0.0+0"
def test_updated_dependencies(wrapper_factory):
wrapper_dep = wrapper_factory.create()
wrapper_main = wrapper_factory.create()
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)
cell_path = wrapper_main.wrapper_dir / "cellMods"
wrapper_c.add_var_to_module_makefile("HEADERS", f"{wrapper_c.name}.h")
(wrapper_c.module_dir / f"{wrapper_c.name}.h").touch()
old_version = "0.0.0+0"
wrapper_a.add_var_to_module_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"')
wrapper_main.write_var_to_makefile(
wrapper_main.config_module,
f"{wrapper_dep.name}_DEP_VERSION",
old_version,
export=True,
)
rc, *_ = wrapper_c.run_make(
"cellinstall", module_version=module_version, cell_path=cell_path
rc, *_ = wrapper_dep.run_make(
"cellinstall",
module_version=old_version,
cell_path=cell_path,
)
assert rc == 0
rc, *_ = wrapper_b.run_make(
"cellinstall", module_version=module_version, cell_path=cell_path
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
rc, *_ = wrapper_a.run_make(
"cellinstall", module_version=module_version, cell_path=cell_path
wrapper_main.write_var_to_makefile(
wrapper_main.config_module,
f"{wrapper_dep.name}_DEP_VERSION",
new_version,
export=True,
)
rc, *_ = wrapper_main.run_make("cellinstall", module_version=new_version)
assert rc == 0
rc, outs, _ = run_ioc_get_output(
module=wrapper_a.name,
version=module_version,
cell_path=wrapper_a.path / "cellMods",
module=wrapper_main.name,
version=new_version,
cell_path=wrapper_main.wrapper_dir / "cellMods",
)
assert rc == 0
assert f"Loaded {wrapper_c.name} version {module_version}" in outs
assert f"Loaded {wrapper_dep.name} version {new_version}" in outs
def test_updated_template_files(wrapper: Wrapper):
wrapper.add_var_to_module_makefile("SUBS", "x.substitutions")
def test_that_setting_DEP_var_sets_dependency(wrapper_factory):
wrapper_a = wrapper_factory.create()
wrapper_b = wrapper_factory.create()
substitution_file = wrapper.module_dir / "x.substitutions"
substitution_file.write_text("file x.template {pattern {x} {y}}")
cell_path = wrapper_a.wrapper_dir / "cellMods"
template_file = wrapper.module_dir / "x.template"
template_file.write_text("record(ai, initial) {}")
module_version = "0.0.0+0"
base_version = wrapper.get_env_var("EPICS_VERSION_NUMBER")
db_file = wrapper.module_dir / f"O.{base_version}_Common" / "x.db"
wrapper_a.write_var_to_makefile(
wrapper_a.config_module,
f"{wrapper_b.name}_DEP_VERSION",
module_version,
export=True,
)
rc, *_ = wrapper.run_make("db_internal")
rc, *_ = wrapper_b.run_make(
"cellinstall", module_version=module_version, cell_path=cell_path
)
assert rc == 0
assert db_file.read_text() == "record(ai, initial) {}"
template_file.write_text("record(ai, updated) {}")
rc, *_ = wrapper.run_make("db_internal")
rc, *_ = wrapper_a.run_make(
"cellinstall", module_version=module_version, cell_path=cell_path
)
assert rc == 0
assert db_file.read_text() == "record(ai, updated) {}"
def test_expand_db_files(wrapper: Wrapper):
"""Test that the automated template/substitution file expansion works."""
for dep_file in (cell_path / wrapper_a.name).rglob("*.dep"):
contents = dep_file.read_text()
wrapper.add_var_to_module_makefile("TMPS", "templates/a.template")
wrapper.add_var_to_module_makefile("SUBS", "b.substitutions")
wrapper.add_var_to_module_makefile("USR_DBFLAGS", "-I templates")
assert len(contents) == 2
assert contents[0].strip() == "# Generated file. Do not edit."
assert f"{wrapper_b.name} {module_version}" == contents[1]
template_dir = wrapper.module_dir / "templates"
template_dir.mkdir()
template_file = template_dir / "a.template"
template_file_contents = "record (ai, $(P)) {}"
template_file.write_text(template_file_contents)
substitution_file = wrapper.module_dir / "b.substitutions"
substitution_file.write_text(
"""file "a.template"
{
pattern { P }
{ "$(PREF)" }
}
"""
def test_make_clean_should_run_correctly_with_missing_dependencies(wrapper):
wrapper.write_var_to_makefile(
wrapper.config_module, "FOO_DEP_VERSION", "bar", export=True
)
base_version = wrapper.get_env_var("EPICS_VERSION_NUMBER")
common_dir = wrapper.module_dir / f"O.{base_version}_Common"
rc, *_ = wrapper.run_make("db_internal")
rc, *_ = wrapper.run_make("clean")
assert rc == 0
expanded_template_file = common_dir / "a.db"
assert expanded_template_file.read_text() == template_file_contents
expanded_substitution_file = common_dir / "b.db"
assert expanded_substitution_file.read_text() == template_file_contents.replace(
"$(P)", "$(PREF)"
)
def test_recursive_db_dependency(wrapper_factory):
wrapper_main = wrapper_factory.create()
wrapper_dep = wrapper_factory.create()
@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: Wrapper, installed_archs, param, expected):
arch_regex = re.compile(r"Pass 2: T_A =\s*([^\s]+)")
cell_path = wrapper_main.wrapper_dir / "cellMods"
common_dir = wrapper_main.module_dir / f"O.{wrapper_main.base_version}_Common"
wrapper.add_var_to_module_makefile(
"CROSS_COMPILER_TARGET_ARCHS", installed_archs, modifier=""
wrapper_main.write_var_to_makefile(
wrapper_main.module_makefile,
f"{wrapper_dep.name}_DEP_VERSION",
wrapper_dep.version,
export=True,
)
substitution_file = wrapper_main.module_dir / "a.substitutions"
substitution_file.write_text(
"""file "b.template"
{
pattern { PATTERN }
{ SUBST }
}"""
)
wrapper_main.write_var_to_makefile(
wrapper_main.module_makefile, "SUBS", "a.substitutions", modifier="+"
)
wrapper_main.write_var_to_makefile(
wrapper_main.module_makefile,
"USR_DBFLAGS",
f"-I $(EPICS_MODULES)/{wrapper_dep.name}/$({wrapper_dep.name}_VERSION)/db",
modifier="+",
)
rc, o, _ = wrapper.run_make("debug", param)
template_file = wrapper_dep.module_dir / "b.template"
template_file.write_text("record(ai, $(PATTERN)) {}")
wrapper_dep.write_var_to_makefile(
wrapper_dep.module_makefile, "TEMPLATES", "b.template", modifier="+"
)
rc, *_ = wrapper_dep.run_make("install", cell_path=cell_path)
assert rc == 0
host_arch = os.getenv("EPICS_HOST_ARCH")
build_archs = [arch for arch in arch_regex.findall(o) if arch != host_arch]
rc, *_ = wrapper_main.run_make("cellbuild", cell_path=cell_path)
assert rc == 0
assert build_archs == expected
assert "SUBST" in (common_dir / "a.db").read_text()
@pytest.mark.parametrize("archs,failing_arch", [("foo bar", "foo"), ("foo bar", "bar")])
def test_build_fails_if_nth_architecture_fails(wrapper: Wrapper, archs, failing_arch):
# LIBOBJS is determined in part based on configuration data coming from
# $(CONFIG)/os/CONFIG.Common.$(T_A); since our architectures do not actually
# exist, we need to manually define these /before/ driver.makefile is included.
makefile_content = wrapper.module_makefile.read_text()
with open(wrapper.module_makefile, "w") as f:
f.write(
f"""ifeq ($(T_A),{failing_arch})
LIBOBJS = nonexistent_{failing_arch}.o
endif
"""
)
f.write(makefile_content)
@pytest.mark.skipif(sys.platform != "linux", reason="only works for linux")
def test_architecture_dependent_dependency(wrapper_factory):
wrapper_a = wrapper_factory.create()
wrapper_b = wrapper_factory.create()
wrapper_c = wrapper_factory.create()
wrapper.add_var_to_config_module("OS_CLASS", "Linux")
cell_path = wrapper_a.wrapper_dir / "cellMods"
# Skip the host architecture, we are not testing it.
host_arch = os.getenv("EPICS_HOST_ARCH")
wrapper.add_var_to_module_makefile("EXCLUDE_ARCHS", host_arch)
module_version = "0.0.0+0"
wrapper.add_var_to_module_makefile(
"CROSS_COMPILER_TARGET_ARCHS", archs, modifier=""
wrapper_a.write_var_to_makefile(
wrapper_a.config_module,
f"{wrapper_b.name}_DEP_VERSION_linux",
module_version,
export=True,
)
wrapper.add_var_to_module_makefile("SOURCES", "-none-")
rc, _, errs = wrapper.run_make("build")
assert rc == 2
assert re.search(
RE_MISSING_FILE.format(filename=re.escape(f"nonexistent_{failing_arch}.o")),
errs,
wrapper_a.write_var_to_makefile(
wrapper_a.config_module,
f"{wrapper_c.name}_DEP_VERSION_not_an_arch",
module_version,
export=True,
)
@pytest.mark.parametrize("module_version", ["0.0.0+0", "test"])
def test_double_install_fails(wrapper: Wrapper, module_version):
RE_ERR_MOD_VER_EXISTS = ".*{module}/{version}.* already exists"
rc, *_ = wrapper.run_make("install", module_version=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, _, errs = wrapper.run_make("install", module_version=module_version)
assert rc == 2
assert re.search(
RE_ERR_MOD_VER_EXISTS.format(
module=re.escape(wrapper.name), version=re.escape(module_version)
),
errs,
rc, outs, _ = run_ioc_get_output(
module=wrapper_a.name,
version=module_version,
cell_path=wrapper_a.wrapper_dir / "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
from .utils import Wrapper
def test_loc(wrappers):
wrapper = wrappers.get(E3_MODULE_SRC_PATH="test-loc")
def test_error_if_using_local_mode_with_loc_suffix(wrapper_factory):
wrapper = wrapper_factory.create(E3_MODULE_SRC_PATH="test-loc")
rc, _, errs = wrapper.run_make("build")
assert rc == 2
assert 'Local source mode "-loc" has been deprecated' in errs
def test_sitelibs(wrapper: Wrapper):
def test_error_if_loading_from_outside_of_sitemods(wrapper):
makefile_contents = wrapper.makefile.read_text()
wrapper.makefile.write_text(
......@@ -21,31 +18,3 @@ def test_sitelibs(wrapper: Wrapper):
rc, _, errs = wrapper.run_make("build")
assert rc == 2
assert "RULES_E3 should only be loaded from RULES_SITEMODS" in errs
def test_incorrect_module_name(wrappers):
module_name = "ADCore"
wrapper = wrappers.get(name=module_name)
rc, _, errs = wrapper.run_make("build")
assert rc == 2
assert f"E3_MODULE_NAME '{module_name}' is not valid" in errs
def test_add_missing_build_number(wrapper: Wrapper):
version = "1.2.3"
wrapper.write_dot_local_data("CONFIG_MODULE", {"E3_MODULE_VERSION": version})
rc, outs, _ = wrapper.run_make("vars")
assert rc == 0
assert f"E3_MODULE_VERSION = {version}+0" in outs.splitlines()
def test_warning_if_building_from_sitemods(wrapper: Wrapper):
rc, _, errs = wrapper.run_make(
"build_path_check", E3_MODULES_PATH=wrapper.path / ".."
)
assert rc == 0
assert (
"It is ill-advised to build a module from the module install location." in errs
)
import os
import sys
import re
import pytest
from ..utils import RE_MISSING_FILE, RE_MISSING_VERSION
def test_error_if_missing_source_file(wrapper):
wrapper.write_var_to_makefile(
wrapper.module_makefile, "SOURCES", "nonexistent.c", modifier="+"
)
rc, _, errs = wrapper.run_make("build")
assert rc == 2
assert re.search(
RE_MISSING_FILE.format(filename=re.escape("nonexistent.o")),
errs,
)
def test_error_if_missing_dbd_file(wrapper):
wrapper.write_var_to_makefile(
wrapper.module_makefile, "DBDS", "nonexistent.dbd", modifier="+"
)
rc, _, errs = wrapper.run_make("build")
assert rc == 2
assert re.search(
RE_MISSING_FILE.format(filename=re.escape("../nonexistent.dbd")),
errs,
)
@pytest.mark.skipif(sys.platform == "darwin", reason="this fails on the regex search")
def test_error_if_missing_record_dbd_file(wrapper):
wrapper.add_file("fooRecord.c")
wrapper.write_var_to_makefile(
wrapper.module_makefile, "SOURCES", "fooRecord.c", modifier="+"
)
rc, _, errs = wrapper.run_make("build")
assert rc != 0
assert re.search(
RE_MISSING_FILE.format(
filename=re.escape(f"../O.{wrapper.base_version}_Common/fooRecord.dbd")
),
errs,
)
def test_error_if_missing_requirement(wrapper):
missing_module = "FOO"
wrapper.write_var_to_makefile(
wrapper.config_module,
f"{missing_module}_DEP_VERSION",
"bar",
export=True,
)
rc, _, errs = wrapper.run_make("build")
assert rc != 0
assert (
f'Dependent module "{missing_module.lower()}" (from "{missing_module}_DEP_VERSION") not installed'
in errs
)
def test_error_if_missing_pinned_requirement(wrapper_factory):
wrapper_dep = wrapper_factory.create()
wrapper_main = wrapper_factory.create()
cell_path = wrapper_main.wrapper_dir / "cellMods"
_ = wrapper_dep.run_make("cellinstall", cell_path=cell_path)
missing_version = "not_a_real_version"
wrapper_main.write_var_to_makefile(
wrapper_main.config_module,
f"{wrapper_dep.name}_DEP_VERSION",
missing_version,
export=True,
)
rc, _, errs = wrapper_main.run_make("cellbuild")
assert rc == 2
assert re.search(
RE_MISSING_VERSION.format(module=wrapper_dep.name, version=missing_version),
errs,
)
@pytest.mark.skipif(
sys.platform == "darwin", reason="this fails at update for reasons beyond me"
)
def test_updated_template_files(wrapper):
wrapper.write_var_to_makefile(
wrapper.module_makefile, "SUBS", "x.substitutions", modifier="+"
)
wrapper.write_var_to_makefile(
wrapper.module_makefile, "USR_DBFLAGS", "-I.", modifier="+"
)
substitution_file = wrapper.module_dir / "x.substitutions"
substitution_file.write_text("file x.template {pattern {x} {y}}")
template_file = wrapper.module_dir / "x.template"
template_file.write_text("record(ai, initial) {}")
db_file = wrapper.module_dir / f"O.{wrapper.base_version}_Common" / "x.db"
rc, *_ = wrapper.run_make("db_internal")
assert rc == 0
assert db_file.read_text() == "record(ai, initial) {}"
template_file.write_text("record(ai, updated) {}")
rc, *_ = wrapper.run_make("db_internal")
assert rc == 0
assert db_file.read_text() == "record(ai, updated) {}"
@pytest.mark.skipif(sys.platform != "linux", reason="only works for linux")
@pytest.mark.parametrize("archs,failing_arch", [("foo bar", "foo"), ("foo bar", "bar")])
def test_build_fails_if_nth_architecture_fails(wrapper, archs, failing_arch):
# LIBOBJS is determined in part based on configuration data coming from
# $(CONFIG)/os/CONFIG.Common.$(T_A); since our architectures do not actually
# exist, we need to manually define these /before/ driver.makefile is included.
makefile_content = wrapper.module_makefile.read_text()
new_content = (
f"""\
ifeq ($(T_A),{failing_arch})
LIBOBJS = nonexistent_{failing_arch}.o
endif
"""
+ makefile_content
)
wrapper.module_makefile.write_text(new_content)
wrapper.write_var_to_makefile(
wrapper.config_module, "OS_CLASS", "Linux", export=True
)
# Skip the host architecture, we are not testing it.
host_arch = os.environ["EPICS_HOST_ARCH"]
wrapper.write_var_to_makefile(
wrapper.module_makefile, "EXCLUDE_ARCHS", host_arch, modifier="+"
)
wrapper.write_var_to_makefile(
wrapper.module_makefile, "CROSS_COMPILER_TARGET_ARCHS", archs
)
rc, _, errs = wrapper.run_make("build")
assert rc == 2
assert re.search(
RE_MISSING_FILE.format(filename=re.escape(f"nonexistent_{failing_arch}.o")),
errs,
)
import pytest
@pytest.mark.parametrize(
"targets",
[
["cellbuild", "build"],
["cellinstall", "install"],
["vars", "cellbuild"],
["debug", "cellinstall"],
["cellbuild", "uninstall"],
],
ids=["build", "install", "vars", "debug", "uninstall"],
)
def test_error_if_celltarget_with_non_celltarget(wrapper, targets):
rc, _, errs = wrapper.run_make(targets[0], targets[1])
assert rc != 0
assert (
"cell targets cannot be called with non cell targets on the same command line"
in errs
)
import sys
def test_make_clean_removes_library_files(wrapper):
_ = wrapper.run_make("build clean")
for file in wrapper.module_dir.rglob("*.*"):
assert file.suffix != ".so" if sys.platform == "linux" else ".dylib"
def test_make_clean_removes_object_files(wrapper):
_ = wrapper.run_make("build clean")
for file in wrapper.module_dir.rglob("*.*"):
assert file.suffix != ".o"
import shutil
import subprocess
import pytest
def set_up_git_submodule(main, sub) -> None:
# remove local dir with dbd-file and add fake repo as submodule
shutil.rmtree(main.module_dir)
subprocess.run(
["git", "init"],
cwd=main.wrapper_dir,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
# we allow file:// as protocol in order to not get a fatal error like
# fatal: transport 'file' not allowed
# see https://lore.kernel.org/git/000001d91c8b$6a26cd60$3e746820$@nexbridge.com/T/
subprocess.run(
[
"git",
"-c",
"protocol.file.allow=always",
"submodule",
"add",
f"file://{sub.module_dir.as_posix()}",
main.name,
],
cwd=main.wrapper_dir,
)
main.write_var_to_makefile(
main.config_module,
"EPICS_MODULE_TAG",
"fake-tag",
)
def test_error_if_epics_module_tag_set_but_no_submodule(wrapper):
wrapper.write_var_to_makefile(
wrapper.config_module,
"EPICS_MODULE_TAG",
"whatever",
)
rc, _, stderr = wrapper.run_make("init")
assert rc != 0
assert stderr
@pytest.mark.skipif(
subprocess.run(
["git", "config", "get", "protocol.file.allow"], stdout=subprocess.PIPE
)
.stdout.decode()
.strip()
!= "always",
reason="file protocol must be allowed",
)
def test_init_inits_without_error_if_defined(wrapper, module):
set_up_git_submodule(wrapper, module)
rc, *_ = wrapper.run_make("init")
assert rc == 0
def test_init_fetches_submodule_files(wrapper, module):
set_up_git_submodule(wrapper, module)
_ = wrapper.run_make("init")
assert len(list((wrapper.wrapper_dir / wrapper.name).iterdir())) > 0
def test_init_checks_out_version_in_epics_module_tag(wrapper, module):
set_up_git_submodule(wrapper, module)
_ = wrapper.run_make("init")
res = subprocess.run(
["git", "describe", "--tags"], cwd=wrapper.module_dir, stdout=subprocess.PIPE
)
assert res.stdout.decode().strip() == "fake-tag"
def test_info_msg_if_local_mode(wrapper):
rc, outs, _ = wrapper.run_make("init")
assert rc == 0
assert "You are in the local source mode" in outs
from pathlib import Path
import re
import sys
import pytest
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.write_var_to_makefile(
wrapper.module_makefile, "HEADERS", f"db/subdir/header.{ext}", modifier="+"
)
wrapper.write_var_to_makefile(
wrapper.module_makefile, "KEEP_HEADER_SUBDIRS", "db", modifier="+"
)
for ext in extensions:
(subdir / f"header.{ext}").touch()
rc, *_ = wrapper.run_make("cellinstall")
assert rc == 0
cell_path = wrapper.package_dir
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()
@pytest.mark.parametrize("module_version", ["0.0.0+0", "test"])
def test_warning_msg_if_double_install(wrapper, module_version):
RE_ERR_MOD_VER_EXISTS = (
"Skipping installation of.*{module}/{version}.*it is already installed"
)
rc, *_ = wrapper.run_make("install", module_version=module_version)
rc, _, errs = wrapper.run_make("install", module_version=module_version)
assert rc == 0
assert re.search(
RE_ERR_MOD_VER_EXISTS.format(
module=re.escape(wrapper.name), version=re.escape(module_version)
),
errs,
)
@pytest.mark.skipif(
sys.platform != "linux", reason="only have cross-compilers for linux"
)
def test_install_missing_archs(wrapper):
RE_SKIP_ARCH = "Skipping installation of.*{module}/{version}/lib/{arch} because it is already installed"
cross_arch = "foo"
# Some explaining is needed here. In order to "add an architecture" we need
# to define a lot of configuration that comes with EPICS base. This is usually
# done in driver.makefile when we load ${EPICS_BASE}/CONFIG... in the middle
# of driver.makefile. This is quite difficult to mock, since there is not a
# good way to insert a new config file either at that time or place.
# The following does, however, work: we add a new arch (foo) and we simply
# tell driver.makefile that it should "act like" the host arch.
wrapper.module_makefile.write_text(
f"""
where_am_I := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
_tmpmkfile := $(lastword $(MAKEFILE_LIST))
ifeq ($(T_A),{cross_arch})
CONFIG:=$(EPICS_BASE)/configure
include $(CONFIG)/os/CONFIG.Common.{wrapper.host_arch}
endif
MAKEFILE_LIST := $(MAKEFILE_LIST) $(_tmpmkfile)
include $(E3_REQUIRE_TOOLS)/driver.makefile
CROSS_COMPILER_TARGET_ARCHS += {cross_arch}
"""
)
rc, *_ = wrapper.run_make("install", EXCLUDE_ARCHS=cross_arch)
assert rc == 0
assert (wrapper.package_dir / "lib" / wrapper.host_arch).is_dir()
assert not (wrapper.package_dir / "lib" / cross_arch).exists()
rc, _, errs = wrapper.run_make("install")
assert rc == 0
assert re.search(
RE_SKIP_ARCH.format(
module=re.escape(wrapper.name),
version=re.escape(wrapper.version),
arch=re.escape(wrapper.host_arch),
),
errs,
)
assert not re.search(
RE_SKIP_ARCH.format(
module=re.escape(wrapper.name),
version=re.escape(wrapper.version),
arch=re.escape(cross_arch),
),
errs,
)
assert (wrapper.package_dir / "lib" / cross_arch).is_dir()
def test_metadata_installed_on_success(wrapper):
rc, *_ = wrapper.run_make("install")
assert rc == 0
assert (wrapper.package_dir / f"{wrapper.name}_meta.yaml").exists()
def test_metadata_not_installed_if_arch_fails(wrapper):
cross_arch = "foo"
wrapper.write_var_to_makefile(
wrapper.module_makefile, f"VLIBS_{cross_arch}", "not_a_library", modifier="+"
)
rc, *_ = wrapper.run_make(
"install", CROSS_COMPILER_TARGET_ARCHS=cross_arch, OS_CLASS="Linux"
)
assert rc == 2
assert not (wrapper.package_dir / f"{wrapper.name}_meta.yaml").exists()
def test_licenses_are_installed(wrapper):
wrapper.add_file("LICENSE")
wrapper.add_directory("foo")
wrapper.add_file("foo/LICENSE")
rc, out, _ = wrapper.run_make("install")
assert rc == 0
assert re.search("Installing license file .././LICENSE", out)
assert re.search("Installing license file .././foo/LICENSE", out)
file_path = wrapper.package_dir / "doc/LICENSE"
assert file_path.exists()
file_path = wrapper.package_dir / "doc/foo/LICENSE"
assert file_path.exists()
from pathlib import Path
import pytest
def create_patch_file(path: Path, filename: str, desc: str):
path.parent.mkdir(parents=True, exist_ok=True)
patch_file_contents = """
diff --git {filename} {filename}
index 1806ff6..8701832 100644
--- {filename}
+++ {filename}
@@ -1,3 +1,3 @@
record(ai, "TEST") {{
-
+ field(DESC, "{desc}")
}}
"""
path.write_text(patch_file_contents.format(filename=filename, desc=desc))
def test_patch_target_applies_patch_in_patch_dir(wrapper):
db_path = wrapper.module_dir / "database.db"
db_file_contents = """record(ai, "TEST") {
}
"""
db_path.write_text(db_file_contents)
patch_dir = wrapper.wrapper_dir / "patch"
create_patch_file(patch_dir / "apply.p0.patch", "database.db", "OK")
rc, *_ = wrapper.run_make("patch")
assert rc == 0
assert 'field(DESC, "OK")' in db_path.read_text()
rc, *_ = wrapper.run_make("build")
assert rc == 0
assert any((wrapper.module_dir).glob("O.*"))
rc, *_ = wrapper.run_make("cellinstall")
assert rc == 0
assert any((wrapper.wrapper_dir / "cellMods").glob("**/*.db"))
def test_patch_target_does_not_apply_patch_outside_patch_dir(wrapper):
db_path = wrapper.module_dir / "database.db"
db_file_contents = """record(ai, "TEST") {
}
"""
db_path.write_text(db_file_contents)
patch_dir = wrapper.wrapper_dir / "patch"
create_patch_file(
patch_dir / wrapper.version / "do-not-apply.p0.patch", "database.db", "NOK"
)
rc, *_ = wrapper.run_make("patch")
assert rc == 0
assert "NOK" not in db_path.read_text()
@pytest.mark.parametrize(
"good_patch_first", [True, False], ids=["good-patch-first", "bad-patch-first"]
)
def test_error_if_patch_failure(wrapper, good_patch_first):
db_path = wrapper.module_dir / "database.db"
db_file_contents = """record(ai, "TEST") {
}
"""
db_path.write_text(db_file_contents)
patch_dir = wrapper.wrapper_dir / "patch"
good_patch_order, bad_patch_order = (1, 2) if good_patch_first else (2, 1)
create_patch_file(
patch_dir / f"{good_patch_order}_good.p0.patch", "database.db", "OK"
)
create_patch_file(
patch_dir / f"{bad_patch_order}_bad.p0.patch", "not_a_file", "this_should_fail"
)
rc, _, errs = wrapper.run_make("patch")
assert rc == 2
assert "not_a_file: No such file or directory" in errs
import pytest
@pytest.mark.parametrize(
"name",
[
"lowercase",
"under_score",
"numb3r",
],
)
def test_no_error_if_valid_characters_in_module_name(wrapper_factory, name):
wrapper = wrapper_factory.create(name=name)
rc, *_ = wrapper.run_make("build")
assert rc == 0
@pytest.mark.parametrize(
"name",
[
"Capitalised",
"ALLCAPS",
"hyp-hen",
"symbol?",
],
)
def test_error_if_invalid_characters_in_module_name(wrapper_factory, name):
wrapper = wrapper_factory.create(name=name)
rc, _, errs = wrapper.run_make("build")
assert rc == 2
assert f"E3_MODULE_NAME '{name}' is not valid" in errs
import os
import subprocess
from pathlib import Path
from random import choice
from string import ascii_lowercase
from typing import Tuple
import pytest
from .utils import Wrapper
@pytest.fixture
def wrappers(tmpdir):
@pytest.fixture()
def wrapper_factory(tmp_path):
class WrapperFactory:
def get(self, **kwargs):
return Wrapper(Path(tmpdir), **kwargs)
def create(self, **kwargs):
return Wrapper(tmp_path, **kwargs)
return WrapperFactory()
@pytest.fixture()
def wrapper(wrapper_factory):
return wrapper_factory.create()
@pytest.fixture()
def module(tmp_path):
return Module(tmp_path)
class Module:
def __init__(self, tmp_test_dir: Path):
self.name = "module-" + "".join(choice(ascii_lowercase) for _ in range(16))
self.module_dir = tmp_test_dir / self.name
self.module_dir.mkdir()
# set up fake repo
fake_file = self.module_dir / "random-file-to-commit"
fake_file.write_text("whatever")
for cmd in [
"git init",
"git add .",
"git commit -m message -q",
"git tag fake-tag",
]:
subprocess.run(
cmd.split(),
cwd=self.module_dir,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
class Wrapper:
def __init__(
self,
tmp_test_dir: Path,
*,
name: str = None,
**kwargs,
):
epics_base = Path(os.environ["EPICS_BASE"])
# environment
self.base_version = epics_base.name.split("base-")[-1]
self.host_arch = os.environ["EPICS_HOST_ARCH"]
self.require_version = os.environ["E3_REQUIRE_VERSION"]
# attributes
self.name = name or "test_mod_" + "".join(
choice(ascii_lowercase) for _ in range(16)
)
self.version = "0.0.0+0"
e3_require_config = epics_base / "require" / self.require_version / "configure"
module_path = kwargs.get("E3_MODULE_SRC_PATH", self.name)
# source
self.wrapper_dir = tmp_test_dir / f"e3-{self.name}"
self.config_dir = self.wrapper_dir / "configure"
self.module_dir = self.wrapper_dir / module_path
self.config_module = self.config_dir / "CONFIG_MODULE"
self.makefile = self.wrapper_dir / "Makefile"
self.module_makefile = self.wrapper_dir / f"{self.name}.Makefile"
# build
self.build_dir = self.module_dir / f"O.{self.base_version}_{self.host_arch}"
# install
self.install_dir = (
self.wrapper_dir
/ "cellMods"
/ f"base-{self.base_version}"
/ f"require-{self.require_version}"
)
self.package_dir = self.install_dir / self.name / self.version
# set up files
self.module_dir.mkdir(parents=True)
self.config_dir.mkdir()
self.config_module.touch()
self._add_root_makefile(module_path, e3_require_config)
self._add_module_makefile()
self.add_file("test.dbd")
def _add_root_makefile(self, module_path, e3_require_config) -> None:
makefile_contents = f"""
TOP:=$(CURDIR)
E3_MODULE_NAME:={self.name}
E3_MODULE_VERSION:={self.version}
E3_MODULE_SRC_PATH:={module_path}
E3_MODULE_MAKEFILE:={self.name}.Makefile
include $(TOP)/configure/CONFIG_MODULE
-include $(TOP)/configure/CONFIG_MODULE.local
REQUIRE_CONFIG:={e3_require_config}
include $(REQUIRE_CONFIG)/CONFIG
include $(REQUIRE_CONFIG)/RULES_SITEMODS
"""
self.makefile.write_text(makefile_contents)
def _add_module_makefile(self) -> None:
module_makefile_contents = """
where_am_I := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
include $(E3_REQUIRE_TOOLS)/driver.makefile
EXCLUDE_ARCHS+=debug
"""
self.module_makefile.write_text(module_makefile_contents)
def add_file(self, name):
(self.module_dir / name).touch()
def add_directory(self, name):
(self.module_dir / name).mkdir()
def write_var_to_makefile(
self,
file: Path,
makefile_var: str,
value: str,
modifier: str = None,
mode: str = "a",
export: bool = False,
):
with open(file, mode) as f:
f.write(
f"{'export' if export else ''} {makefile_var} {modifier if modifier else ''}= {value}\n"
)
def run_make(
self,
target: str,
*args,
module_version: str = None,
cell_path: str = None,
**kwargs,
) -> Tuple[int, bytes, bytes]:
"""Attempt to run `make <target> <args>` for the current wrapper."""
# We should not install in the global environment during tests
# so we use the cell commands
if target in ["install", "uninstall"]:
target = "cell" + target
yield WrapperFactory()
args = list(args)
if module_version:
args.append(f"E3_MODULE_VERSION={module_version}")
# These are added as arguments to make to ensure that they are prioritised
for k, v in kwargs.items():
args.append(f"{k}={v}")
@pytest.fixture
def wrapper(wrappers):
yield wrappers.get()
if cell_path:
# this is often/usually a Path object, so let's cast it for sanity of mind
cell_path = str(cell_path)
self.write_var_to_makefile(
self.config_dir / "CONFIG_CELL.local", "E3_CELL_PATH", cell_path
)
make_cmd = ["make", "-C", self.wrapper_dir, target, *args]
p = subprocess.Popen(
make_cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
env=os.environ.copy(),
)
outs, errs = p.communicate()
return p.returncode, outs, errs