From afeded38cf913aacd8d625df2b13c73b488df8c9 Mon Sep 17 00:00:00 2001
From: Simon Rose <simon.rose@ess.eu>
Date: Mon, 19 Sep 2022 16:22:32 +0200
Subject: [PATCH] Ensure that EPICS_DRIVER_PATH is set first

Previously, it was the case that running
```
$ iocsh -r module -l cellMods
$ iocsh -l cellMods -r module
```
would both work correctly, but if you looked at the IOC log it was not
clear why that was the case; in the former case it _appeared_ like we
were finding the module before setting `EPICS_DRIVER_PATH`. This only
worked because this variable is exported from the shell.

This change forces the `-l` flag to be processed _before_ all of the
other flags in order to ensure that paths are determined first.
---
 require-ess/tools/iocsh                |  2 ++
 require-ess/tools/iocsh_functions.bash | 48 ++++++++++++++++----------
 tests/test_build.py                    | 19 ++++++----
 tests/test_iocsh.py                    | 48 ++++++++++++++++++++++++++
 tests/test_versions.py                 |  2 +-
 tests/utils.py                         | 16 ++++++---
 6 files changed, 106 insertions(+), 29 deletions(-)
 create mode 100644 tests/test_iocsh.py

diff --git a/require-ess/tools/iocsh b/require-ess/tools/iocsh
index a23ad25a..ac58d761 100644
--- a/require-ess/tools/iocsh
+++ b/require-ess/tools/iocsh
@@ -124,6 +124,8 @@ trap "softIoc_end ${IOC_STARTUP}" EXIT HUP INT TERM
 
   loadRequire
 
+  setPaths "$@"
+
   loadFiles "$@"
 
   printf "# // Set the IOC Prompt String\n"
diff --git a/require-ess/tools/iocsh_functions.bash b/require-ess/tools/iocsh_functions.bash
index d1443114..89300b49 100644
--- a/require-ess/tools/iocsh_functions.bash
+++ b/require-ess/tools/iocsh_functions.bash
@@ -248,21 +248,37 @@ function check_mandatory_env_settings() {
   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
 
-    file=$1
+    arg=$1
 
-    case $file in
+    case $arg in
       -rt | -RT | -realtime | --realtime)
         REALTIME="RT"
         __LOADER__="chrt --fifo 1 "
         ;;
       @*)
-        loadFiles "$(cat "${file#@}")"
+        loadFiles "$(cat "${arg#@}")"
         ;;
       *=*)
-        echo -n "$file" | awk -F '=' '{printf "epicsEnvSet %s '\''%s'\''\n" $1 $2}'
+        echo -n "$arg" | awk -F '=' '{printf "epicsEnvSet %s '\''%s'\''\n" $1 $2}'
         ;;
       -c)
         shift
@@ -294,11 +310,8 @@ function loadFiles() {
         shift
         echo "require $1"
         ;;
-      -l)
+      -l) # This is taken care of in setPaths()
         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"
         ;;
       -dg)
         if [[ -n "${2%--dgarg=*}" ]]; then
@@ -333,7 +346,7 @@ function loadFiles() {
         help
         ;;
       *.so)
-        echo "dlload \"$file\""
+        echo "dlload \"$arg\""
         ;;
       *)
         subst=""
@@ -349,26 +362,26 @@ function loadFiles() {
           esac
         done
         subst=${subst#,}
-        case $file in
+        case $arg in
           *.db | *.template)
-            echo "dbLoadRecords '$file','$subst'"
+            echo "dbLoadRecords '$arg','$subst'"
             ;;
           *.subs | *.subst)
-            echo "dbLoadTemplate '$file','$subst'"
+            echo "dbLoadTemplate '$arg','$subst'"
             ;;
           *.dbd)
             # some dbd files must be loaded before main to take effect
-            echo "dbLoadDatabase '$file','$DBD','$subst'"
+            echo "dbLoadDatabase '$arg','$DBD','$subst'"
             ;;
           *)
-            set_e3_cmd_top "$file"
-            echo "iocshLoad '$file','$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" "$file"; then
+            if grep -q "^\s*iocInit\b" "$arg"; then
               init=NO
             fi
             ;;
@@ -379,8 +392,7 @@ function loadFiles() {
     shift
   done
 
-} \
-  ;
+}
 
 function set_e3_cmd_top() {
   local file=$1
diff --git a/tests/test_build.py b/tests/test_build.py
index 6ce80558..901746b5 100644
--- a/tests/test_build.py
+++ b/tests/test_build.py
@@ -176,7 +176,9 @@ def test_updated_dependencies(wrappers):
     assert rc == 0
 
     rc, outs, _ = run_ioc_get_output(
-        wrapper_main.name, new_version, wrapper_main.path / "cellMods"
+        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
@@ -421,7 +423,9 @@ def test_architecture_dependent_dependency(wrappers):
     assert rc == 0
 
     rc, outs, _ = run_ioc_get_output(
-        wrapper_a.name, module_version, wrapper_a.path / "cellMods"
+        module=wrapper_a.name,
+        version=module_version,
+        cell_path=wrapper_a.path / "cellMods",
     )
     assert rc == 0
     assert f"Loaded {wrapper_b.name} version {module_version}" in outs
@@ -461,7 +465,9 @@ def test_recursive_header_include(wrappers):
     assert rc == 0
 
     rc, outs, _ = run_ioc_get_output(
-        wrapper_a.name, module_version, wrapper_a.path / "cellMods"
+        module=wrapper_a.name,
+        version=module_version,
+        cell_path=wrapper_a.path / "cellMods",
     )
     assert rc == 0
     assert f"Loaded {wrapper_c.name} version {module_version}" in outs
@@ -591,13 +597,14 @@ def test_build_fails_if_nth_architecture_fails(wrapper: Wrapper, archs, failing_
 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.run_make("install", module_version=module_version)
     assert rc == 0
 
-    rc, _, errs = wrapper.run_make("install", module_version = module_version)
+    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)),
+            module=re.escape(wrapper.name), version=re.escape(module_version)
+        ),
         errs,
     )
diff --git a/tests/test_iocsh.py b/tests/test_iocsh.py
new file mode 100644
index 00000000..97dc0c07
--- /dev/null
+++ b/tests/test_iocsh.py
@@ -0,0 +1,48 @@
+import re
+
+from .conftest import Wrapper
+from .utils import run_ioc_get_output
+
+
+def test_multiple_cell_paths(wrapper: Wrapper):
+    cell_path_a = wrapper.path / "cellModsA"
+    wrapper.run_make("cellinstall", cell_path=cell_path_a)
+
+    cell_path_b = wrapper.path / "cellModsB"
+    wrapper.run_make("cellinstall", cell_path=cell_path_b)
+
+    rc, outs, _ = run_ioc_get_output(
+        "-l", cell_path_a, "-l", cell_path_b, module=wrapper.name
+    )
+    assert rc == 0
+
+    regex = r"Module {name} version {version} found in {path}"
+    assert not re.search(
+        regex.format(
+            name=re.escape(wrapper.name),
+            version=re.escape(wrapper.version),
+            path=re.escape(str(cell_path_a)),
+        ),
+        outs,
+    )
+
+    assert re.search(
+        regex.format(
+            name=re.escape(wrapper.name),
+            version=re.escape(wrapper.version),
+            path=re.escape(str(cell_path_b)),
+        ),
+        outs,
+    )
+
+
+def test_cellpath_does_not_depend_on_order(wrapper: Wrapper):
+    cell_path = wrapper.path / "cellMods"
+
+    wrapper.run_make("cellinstall", cell_path=cell_path)
+
+    rc, *_ = run_ioc_get_output("-l", cell_path, "-r", wrapper.name)
+    assert rc == 0
+
+    rc, *_ = run_ioc_get_output("-r", wrapper.name, "-l", cell_path)
+    assert rc == 0
diff --git a/tests/test_versions.py b/tests/test_versions.py
index dcec1f4e..8d6ff943 100644
--- a/tests/test_versions.py
+++ b/tests/test_versions.py
@@ -45,7 +45,7 @@ def test_version(wrapper: Wrapper, requested, expected, installed):
         assert returncode == 0
 
     rc, stdout, _ = run_ioc_get_output(
-        wrapper.name, requested, wrapper.path / "cellMods"
+        module=wrapper.name, version=requested, cell_path=wrapper.path / "cellMods"
     )
 
     if expected:
diff --git a/tests/utils.py b/tests/utils.py
index 2ef7a92d..9b202a31 100644
--- a/tests/utils.py
+++ b/tests/utils.py
@@ -3,12 +3,20 @@ import time
 from run_iocsh import IOC
 
 
-def run_ioc_get_output(module, version, cell_path):
+def run_ioc_get_output(*args, **kwargs):
     """
     Run an IOC and try to load the test module
     """
-    with IOC(
-        "-r", f"{module},{version}", "-l", cell_path, ioc_executable="iocsh"
-    ) as ioc:
+    ioc_args = []
+    module = kwargs.get("module", None)
+    version = kwargs.get("version", None)
+    if module:
+        ioc_args.append("-r")
+        ioc_args.append(f"{module},{version}" if version else module)
+    cell_path = kwargs.get("cell_path", None)
+    if cell_path:
+        ioc_args.extend(["-l", cell_path])
+    ioc_args.extend(args)
+    with IOC(*ioc_args, ioc_executable="iocsh") as ioc:
         time.sleep(1)
     return ioc.proc.returncode, ioc.outs, ioc.errs
-- 
GitLab