diff --git a/test/unit/tools/test_tool_deps.py b/test/unit/tools/test_tool_deps.py
index c8f3c5366e9..11f9413158e 100644
--- a/test/unit/tools/test_tool_deps.py
+++ b/test/unit/tools/test_tool_deps.py
@@ -3,7 +3,6 @@ import tempfile
from contextlib import contextmanager
from os import (
chmod,
- environ,
makedirs,
stat,
symlink,
@@ -23,6 +22,8 @@ from galaxy.tool_util.deps.resolvers.lmod import LmodDependency, LmodDependencyR
from galaxy.tool_util.deps.resolvers.modules import ModuleDependency, ModuleDependencyResolver
from galaxy.util.bunch import Bunch
+from .util import modify_environ
+
# If true, test created DependencyManager objects by serializing out to json and re-constituting.
ROUND_TRIP_TEST_DEPENDENCY_MANAGER_SERIALIZATION = True
@@ -582,7 +583,7 @@ def test_config_modulepath():
def test_config_MODULEPATH():
# Test reads and splits MODULEPATH if modulepath is not specified.
- with __environ({"MODULEPATH": "/opt/modules/modulefiles:/usr/local/modules/modulefiles"}):
+ with modify_environ({"MODULEPATH": "/opt/modules/modulefiles:/usr/local/modules/modulefiles"}):
with __parse_resolvers('''
@@ -593,7 +594,7 @@ def test_config_MODULEPATH():
def test_config_MODULESHOME():
# Test fallbacks to read MODULESHOME if modulepath is not specified and
# neither is MODULEPATH.
- with __environ({"MODULESHOME": "/opt/modules"}, keys_to_remove=["MODULEPATH"]):
+ with modify_environ({"MODULESHOME": "/opt/modules"}, keys_to_remove=["MODULEPATH"]):
with __parse_resolvers('''
@@ -718,72 +719,6 @@ def _first_conda_resolver_options(dm):
return [r for r in dm.to_dict()["resolvers"] if r["resolver_type"] == "conda"][0]
-@contextmanager
-def __environ(values, keys_to_remove=[]):
- """
- Modify the environment for a test, adding/updating values in dict `values` and
- removing any environment variables mentioned in list `remove`.
- """
- new_keys = set(values.keys()) - set(environ.keys())
- old_environ = environ.copy()
- try:
- environ.update(values)
- for key in keys_to_remove:
- try:
- del environ[key]
- new_keys.remove(key) # Because key no longer exists
- except KeyError:
- pass
- yield
- finally:
- for key in new_keys:
- del environ[key]
- environ.update(old_environ)
-
-
-def test_environ_contextmanager_env_restored():
- """ os.environ must be preserved across calls to __environ """
- key, val = 'foo_test_678363', '42'
- os.environ[key] = val
- with __environ({}, []):
- pass
- assert os.environ[key] == val
- del os.environ[key]
-
-
-def test_environ_contextmanager_env_updated_and_restored():
- """
- os.environ must be preserved across calls to __environ
- when updating existing or adding new key:value pairs
- """
- key, val = 'foo_test_678363', '42'
- os.environ[key] = val
- new_key, new_val1 = 'foo_test_new', '53' # add new key + value
- new_val2 = '43' # update old key's value
- to_update = {new_key: new_val1, key: new_val2}
- with __environ(values=to_update):
- pass
- assert os.environ[key] == val # value should not change
- assert new_key not in os.environ # key should not be added
- del os.environ[key]
-
-
-def test_environ_contextmanager_env_removed_and_restored():
- """
- os.environ must be preserved across calls to __environ
- when removing key:value pairs
- """
- key, val = 'foo_test_678363', '42'
- os.environ[key] = val
- new_key, new_val = 'foo_test_new', '53' # add new key + value
- to_update = {new_key: new_val}
- with __environ(values=to_update, keys_to_remove=[key, new_key]):
- pass
- assert os.environ[key] == val # key should be present w/value
- assert new_key not in os.environ # key should not be added
- del os.environ[key]
-
-
@contextmanager
def __parse_resolvers(file_content, extension=".xml"):
with __dependency_manager(file_content, extension=extension) as dm:
diff --git a/test/unit/tools/test_util.py b/test/unit/tools/test_util.py
new file mode 100644
index 00000000000..7962469ce58
--- /dev/null
+++ b/test/unit/tools/test_util.py
@@ -0,0 +1,85 @@
+from os import environ
+import pytest
+
+from .util import modify_environ
+
+
+@pytest.fixture
+def load_keyval(request):
+ """
+ Create key/value pair and load it into os.environ. Delete on teardown.
+ """
+ keys = [] # preserve keys for teardown
+
+ def _load_keyval(key='a unique key', val='a value'):
+ # If this is called twice with default values within the same test function,
+ # it will raise a KeyError. This is intentional: os.environ cannot have duplicate keys.
+ keys.append(key)
+ environ[key] = val
+ return key, val
+
+ def _teardown():
+ for k in keys:
+ del environ[k]
+
+ request.addfinalizer(_teardown)
+ return _load_keyval
+
+
+def test_modify_environ__restore(load_keyval):
+ key, val = load_keyval()
+ with modify_environ({}):
+ assert environ[key] == val # key/val unchanged
+ assert environ[key] == val # key/val unchanged
+
+
+def test_modify_environ__add_and_restore(load_keyval):
+ key1, val1 = load_keyval()
+ key2, val2 = 'key to add', 'value to add'
+ to_update = {key2: val2}
+
+ assert key2 not in environ # ensure key to add does not exist
+ with modify_environ(to_update):
+ assert environ[key1] == val1 # key/val unchanged
+ assert environ[key2] == val2 # new key/val added
+ assert environ[key1] == val1 # key/val unchanged
+ assert key2 not in environ # new key removed
+
+
+def test_modify_environ__update_and_restore(load_keyval):
+ key1, val1 = load_keyval()
+ key2, val2 = load_keyval('key to update', 'value to update')
+ val2_updated = 'updated'
+ to_update = {key2: val2_updated}
+
+ with modify_environ(to_update):
+ assert environ[key1] == val1 # key/val unchanged
+ assert environ[key2] == val2_updated # value updated
+ assert environ[key1] == val1 # key/val unchanged
+ assert environ[key2] == val2 # value restored
+
+
+def test_modify_environ__remove_and_restore(load_keyval):
+ key1, val1 = load_keyval()
+ key2, val2 = load_keyval('key to remove', 'value to remove')
+ to_update = {}
+ to_remove = [key2]
+
+ with modify_environ(to_update, to_remove):
+ assert environ[key1] == val1 # key/val unchanged
+ assert key2 not in environ # key removed
+ assert environ[key1] == val1 # key/val unchanged
+ assert environ[key2] == val2 # key/value restored
+
+
+def test_modify_environ__remove_nonexistant_key(load_keyval):
+ # Test that removing wrong key does not raise an error
+ key1, val1 = load_keyval()
+ key_nonexistant = 'no such key'
+ to_update = {}
+ to_remove = [key_nonexistant]
+
+ assert key_nonexistant not in environ # ensure key to remove does not exist
+ with modify_environ(to_update, to_remove):
+ assert environ[key1] == val1 # key/val unchanged
+ assert environ[key1] == val1 # key/val unchanged
diff --git a/test/unit/tools/util.py b/test/unit/tools/util.py
new file mode 100644
index 00000000000..2bc0340711c
--- /dev/null
+++ b/test/unit/tools/util.py
@@ -0,0 +1,22 @@
+from contextlib import contextmanager
+from os import environ
+
+
+@contextmanager
+def modify_environ(values, keys_to_remove=None):
+ """
+ Modify the environment for a test, adding/updating values in dict `values` and
+ removing any environment variables mentioned in list `keys_to_remove`.
+ """
+ old_environ = environ.copy()
+ try:
+ if values:
+ environ.update(values)
+ if keys_to_remove:
+ for key in keys_to_remove:
+ if key in environ:
+ del environ[key]
+ yield
+ finally:
+ environ.clear()
+ environ.update(old_environ)