diff --git a/packages/tool_util/tests/__init__.py b/packages/tool_util/tests/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/tool_util/tests/util.py b/packages/tool_util/tests/util.py new file mode 120000 index 00000000000..9469bc59223 --- /dev/null +++ b/packages/tool_util/tests/util.py @@ -0,0 +1 @@ +../../../test/unit/tools/util.py \ No newline at end of file diff --git a/test/unit/tools/test_tool_deps.py b/test/unit/tools/test_tool_deps.py index c28818bd526..1f15d880a0c 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, @@ -22,6 +21,7 @@ from galaxy.tool_util.deps.resolvers.galaxy_packages import GalaxyPackageDepende from galaxy.tool_util.deps.resolvers.lmod import LmodDependency, LmodDependencyResolver 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 +582,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 +593,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"}, remove="MODULEPATH"): + with modify_environ({"MODULESHOME": "/opt/modules"}, keys_to_remove=["MODULEPATH"]): with __parse_resolvers(''' @@ -718,28 +718,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, 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(environ.keys()) - set(values.keys()) - old_environ = environ.copy() - try: - environ.update(values) - for to_remove in remove: - try: - del environ[remove] - except KeyError: - pass - yield - finally: - environ.update(old_environ) - for key in new_keys: - del 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..e2778fbf74e --- /dev/null +++ b/test/unit/tools/test_util.py @@ -0,0 +1,86 @@ +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)