Skip to content

Commit 825f842

Browse files
authored
Merge pull request #5015 from Flamefire/restore-test-env
Introduce `saved_env` context manager for restoring environment in tests
2 parents dfd3553 + 5476d7f commit 825f842

File tree

8 files changed

+46
-71
lines changed

8 files changed

+46
-71
lines changed

easybuild/base/testing.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,16 @@ def mocked_stdout_stderr(self, mock_stdout=True, mock_stderr=True):
221221
if mock_stderr:
222222
self.mock_stderr(False)
223223

224+
@contextmanager
225+
def saved_env(self):
226+
"""Context manager to reset environment to state when it was entered"""
227+
orig_env = os.environ.copy()
228+
try:
229+
yield
230+
finally:
231+
os.environ.clear()
232+
os.environ.update(orig_env)
233+
224234
def tearDown(self):
225235
"""Cleanup after running a test."""
226236
self.mock_stdout(False)

easybuild/tools/modules.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1095,6 +1095,8 @@ def load(self, modules, mod_paths=None, purge=False, init_env=None, allow_reload
10951095
:param init_env: original environment to restore after running 'module purge'
10961096
:param allow_reload: allow reloading an already loaded module
10971097
"""
1098+
if not any((modules, mod_paths, purge)):
1099+
return # Avoid costly module paths if nothing to do
10981100
if mod_paths is None:
10991101
mod_paths = []
11001102

test/framework/easyblock.py

Lines changed: 13 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030
@author: Maxime Boissonneault (Compute Canada)
3131
@author: Jan Andre Reuter (Juelich Supercomputing Centre)
3232
"""
33-
import copy
3433
import fileinput
3534
import os
3635
import re
@@ -53,7 +52,6 @@
5352
from easybuild.tools import LooseVersion, config
5453
from easybuild.tools.build_log import EasyBuildError
5554
from easybuild.tools.config import get_module_syntax, update_build_option
56-
from easybuild.tools.environment import modify_env
5755
from easybuild.tools.filetools import change_dir, copy_dir, copy_file, mkdir, read_file, remove_dir, remove_file
5856
from easybuild.tools.filetools import symlink, verify_checksum, write_file
5957
from easybuild.tools.module_generator import module_generator
@@ -1290,14 +1288,14 @@ def test_post_processing_step(self):
12901288
eb.silent = True
12911289
depr_msg = r"EasyBlock.post_install_step\(\) is deprecated, use EasyBlock.post_processing_step\(\) instead"
12921290
expected_error = r"DEPRECATED \(since v6.0\).*" + depr_msg
1293-
with self.mocked_stdout_stderr():
1291+
with self.mocked_stdout_stderr(), self.saved_env():
12941292
self.assertErrorRegex(EasyBuildError, expected_error, eb.run_all_steps, True)
12951293

12961294
change_dir(cwd)
12971295
toy_ec = EasyConfig(toy_ec_fn)
12981296
eb = EB_toy(toy_ec)
12991297
eb.silent = True
1300-
with self.mocked_stdout_stderr() as (_, stderr):
1298+
with self.mocked_stdout_stderr() as (_, stderr), self.saved_env():
13011299
eb.run_all_steps(True)
13021300
# no deprecation warning
13031301
stderr = stderr.getvalue()
@@ -1309,14 +1307,13 @@ def test_post_processing_step(self):
13091307
# check again with toy easyblock that still uses post_install_step,
13101308
# to verify that the expected file is being created when deprecated functionality is allow
13111309
remove_file(libtoy_post_a)
1312-
modify_env(os.environ, self.orig_environ, verbose=False)
13131310
change_dir(cwd)
13141311

13151312
self.allow_deprecated_behaviour()
13161313
toy_ec = EasyConfig(toy_ec_fn)
13171314
eb = EB_toy_deprecated(toy_ec)
13181315
eb.silent = True
1319-
with self.mocked_stdout_stderr() as (stdout, stderr):
1316+
with self.mocked_stdout_stderr() as (stdout, stderr), self.saved_env():
13201317
eb.run_all_steps(True)
13211318

13221319
regex = re.compile(depr_msg, re.M)
@@ -2606,7 +2603,7 @@ def test_extensions_sanity_check(self):
26062603
error_pattern = r"Sanity check failed: extensions sanity check failed for 1 extensions: toy\n"
26072604
error_pattern += r"failing sanity check for 'toy' extension: "
26082605
error_pattern += r'command "thisshouldfail" failed; output:\n.* thisshouldfail: command not found'
2609-
with self.mocked_stdout_stderr():
2606+
with self.mocked_stdout_stderr(), self.saved_env():
26102607
self.assertErrorRegex(EasyBuildError, error_pattern, eb.run_all_steps, True)
26112608

26122609
# purposely put sanity check command in place that breaks the build,
@@ -2617,7 +2614,7 @@ def test_extensions_sanity_check(self):
26172614
toy_ec['exts_defaultclass'] = 'DummyExtension'
26182615
eb = EB_toy(toy_ec)
26192616
eb.silent = True
2620-
with self.mocked_stdout_stderr():
2617+
with self.mocked_stdout_stderr(), self.saved_env():
26212618
eb.run_all_steps(True)
26222619

26232620
def test_parallel(self):
@@ -2976,23 +2973,19 @@ def test_extension_patch_step(self):
29762973

29772974
cwd = os.getcwd()
29782975
self.assertExists(cwd)
2979-
# Take environment with test-specific variable set up
2980-
orig_environ = copy.deepcopy(os.environ)
29812976

29822977
def run_extension_step():
2983-
try:
2984-
change_dir(cwd)
2985-
eb = EasyBlock(ec)
2986-
# Cleanup build directory
2987-
if os.path.exists(eb.builddir):
2988-
remove_dir(eb.builddir)
2978+
change_dir(cwd)
2979+
eb = EasyBlock(ec)
2980+
# Cleanup build directory
2981+
if os.path.exists(eb.builddir):
2982+
remove_dir(eb.builddir)
2983+
# restore original environment to continue testing with a clean slate
2984+
with self.saved_env():
29892985
eb.make_builddir()
29902986
eb.update_config_template_run_step()
29912987
eb.extensions_step(fetch=True, install=True)
2992-
return os.path.join(eb.builddir)
2993-
finally:
2994-
# restore original environment to continue testing with a clean slate
2995-
modify_env(os.environ, orig_environ, verbose=False)
2988+
return os.path.join(eb.builddir)
29962989

29972990
ec['exts_defaultclass'] = 'DummyExtension'
29982991
ec['exts_list'] = [('toy', '0.0', {'easyblock': 'DummyExtension'})]

test/framework/modules.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@
4444
from easybuild.framework.easyconfig.easyconfig import EasyConfig
4545
from easybuild.tools import LooseVersion
4646
from easybuild.tools.build_log import EasyBuildError
47-
from easybuild.tools.environment import modify_env
4847
from easybuild.tools.filetools import adjust_permissions, copy_file, copy_dir, mkdir
4948
from easybuild.tools.filetools import read_file, remove_dir, remove_file, symlink, write_file
5049
from easybuild.tools.modules import EnvironmentModules, EnvironmentModulesC, EnvironmentModulesTcl, Lmod, NoModulesTool
@@ -103,11 +102,10 @@ def test_run_module(self):
103102
os.environ.pop(key, None)
104103

105104
# arguments can be passed in two ways: multiple arguments, or just 1 list argument
106-
self.modtool.run_module('load', 'GCC/6.4.0-2.28')
107-
self.assertEqual(os.environ['EBROOTGCC'], '/prefix/software/GCC/6.4.0-2.28')
105+
with self.saved_env():
106+
self.modtool.run_module('load', 'GCC/6.4.0-2.28')
107+
self.assertEqual(os.environ['EBROOTGCC'], '/prefix/software/GCC/6.4.0-2.28')
108108

109-
# restore original environment
110-
modify_env(os.environ, self.orig_environ, verbose=False)
111109
self.reset_modulepath([os.path.join(testdir, 'modules')])
112110

113111
self.assertNotIn('EBROOTGCC', os.environ)

test/framework/options.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@
5353
from easybuild.tools.build_log import EasyBuildError, EasyBuildLog
5454
from easybuild.tools.config import DEFAULT_MODULECLASSES, BuildOptions, ConfigurationVariables
5555
from easybuild.tools.config import build_option, find_last_log, get_build_log_path, get_module_syntax, module_classes
56-
from easybuild.tools.environment import modify_env
5756
from easybuild.tools.filetools import adjust_permissions, change_dir, copy_dir, copy_file, download_file
5857
from easybuild.tools.filetools import is_patch_file, mkdir, move_file, parse_http_header_fields_urlpat
5958
from easybuild.tools.filetools import read_file, remove_dir, remove_file, which, write_file
@@ -4324,7 +4323,6 @@ def check_tmpdir(tmpdir):
43244323
# cleanup
43254324
os.close(fd)
43264325
shutil.rmtree(mytmpdir)
4327-
modify_env(os.environ, self.orig_environ)
43284326
tempfile.tempdir = None
43294327

43304328
orig_tmpdir = tempfile.gettempdir()
@@ -4335,7 +4333,8 @@ def check_tmpdir(tmpdir):
43354333
os.path.join(orig_tmpdir, '[ab @cd]%/#*'),
43364334
]
43374335
for tmpdir in cand_tmpdirs:
4338-
check_tmpdir(tmpdir)
4336+
with self.saved_env():
4337+
check_tmpdir(tmpdir)
43394338

43404339
def test_minimal_toolchains(self):
43414340
"""End-to-end test for --minimal-toolchains."""

test/framework/systemtools.py

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
@author: Kenneth hoste (Ghent University)
2929
@author: Ward Poelmans (Ghent University)
3030
"""
31-
import copy
3231
import ctypes
3332
import logging
3433
import os
@@ -41,7 +40,7 @@
4140

4241
import easybuild.tools.systemtools as st
4342
from easybuild.tools.build_log import EasyBuildError
44-
from easybuild.tools.environment import modify_env, setvar
43+
from easybuild.tools.environment import setvar
4544
from easybuild.tools.filetools import adjust_permissions, mkdir, read_file, symlink, which, write_file
4645
from easybuild.tools.run import RunShellCmdResult, run_shell_cmd
4746
from easybuild.tools.systemtools import CPU_ARCHITECTURES, AARCH32, AARCH64, POWER, X86_64
@@ -740,9 +739,9 @@ def test_cpu_architecture(self):
740739
'x86_64': X86_64,
741740
'some_fancy_arch': UNKNOWN,
742741
}
743-
for name in machine_names:
742+
for name, arch in machine_names.items():
744743
MACHINE_NAME = name
745-
self.assertEqual(get_cpu_architecture(), machine_names[name])
744+
self.assertEqual(get_cpu_architecture(), arch)
746745

747746
def test_cpu_arch_name_native(self):
748747
"""Test getting CPU arch name."""
@@ -1323,9 +1322,6 @@ def test_find_library_path(self):
13231322

13241323
def test_get_cuda_object_dump_raw(self):
13251324
"""Test get_cuda_object_dump_raw function"""
1326-
# This test modifies environment, make sure we can revert the changes:
1327-
start_env = copy.deepcopy(os.environ)
1328-
13291325
# Mock the shell command for certain known commands
13301326
st.run_shell_cmd = mocked_run_shell_cmd
13311327

@@ -1373,14 +1369,8 @@ def test_get_cuda_object_dump_raw(self):
13731369
# Test case 7: call on CUDA static lib, which only contains device code
13741370
self.assertEqual(get_cuda_object_dump_raw('mock_cuda_staticlib'), CUOBJDUMP_DEVICE_CODE_ONLY)
13751371

1376-
# Restore original environment
1377-
modify_env(os.environ, start_env, verbose=False)
1378-
13791372
def test_get_cuda_architectures(self):
13801373
"""Test get_cuda_architectures function"""
1381-
# This test modifies environment, make sure we can revert the changes:
1382-
start_env = copy.deepcopy(os.environ)
1383-
13841374
# Mock the shell command for certain known commands
13851375
st.run_shell_cmd = mocked_run_shell_cmd
13861376

@@ -1450,9 +1440,6 @@ def test_get_cuda_architectures(self):
14501440
self.assertTrue(warning_regex_elf.search(logtxt), fail_msg)
14511441
self.assertIsNone(res_elf)
14521442

1453-
# Restore original environment
1454-
modify_env(os.environ, start_env, verbose=False)
1455-
14561443
def test_get_linked_libs_raw(self):
14571444
"""
14581445
Test get_linked_libs_raw function.

test/framework/toy_build.py

Lines changed: 11 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
@author: Kenneth Hoste (Ghent University)
3030
@author: Damian Alvarez (Forschungszentrum Juelich GmbH)
3131
"""
32-
import copy
3332
import glob
3433
import grp
3534
import os
@@ -54,7 +53,7 @@
5453
from easybuild.main import main_with_hooks
5554
from easybuild.tools.build_log import EasyBuildError
5655
from easybuild.tools.config import get_module_syntax, get_repositorypath
57-
from easybuild.tools.environment import modify_env, setvar
56+
from easybuild.tools.environment import setvar
5857
from easybuild.tools.filetools import adjust_permissions, change_dir, copy_file, mkdir, move_file
5958
from easybuild.tools.filetools import read_file, remove_dir, remove_file, which, write_file
6059
from easybuild.tools.module_generator import ModuleGeneratorTcl
@@ -1759,18 +1758,14 @@ def test_external_dependencies(self):
17591758
installed_test_modules = os.path.join(self.test_installpath, 'modules', 'all')
17601759
self.reset_modulepath([modulepath, installed_test_modules])
17611760

1762-
start_env = copy.deepcopy(os.environ)
1763-
17641761
with self.mocked_stdout_stderr():
17651762
self._test_toy_build(ec_file=toy_ec, versionsuffix='-external-deps', verbose=True, raise_error=True)
17661763

1767-
self.modtool.load(['toy/0.0-external-deps'])
1768-
# note build dependency is not loaded
1769-
mods = ['intel/2018a', 'GCC/6.4.0-2.28', 'foobar/1.2.3', 'toy/0.0-external-deps']
1770-
self.assertEqual([x['mod_name'] for x in self.modtool.list()], mods)
1771-
1772-
# restore original environment (to undo 'module load' done above)
1773-
modify_env(os.environ, start_env, verbose=False)
1764+
with self.saved_env():
1765+
self.modtool.load(['toy/0.0-external-deps'])
1766+
# note build dependency is not loaded
1767+
mods = ['intel/2018a', 'GCC/6.4.0-2.28', 'foobar/1.2.3', 'toy/0.0-external-deps']
1768+
self.assertEqual([x['mod_name'] for x in self.modtool.list()], mods)
17741769

17751770
# check behaviour when a non-existing external (build) dependency is included
17761771
extraectxt = "\nbuilddependencies = [('nosuchbuilddep/0.0.0', EXTERNAL_MODULE)]"
@@ -3147,10 +3142,6 @@ def test_toy_filter_rpath_sanity_libs(self):
31473142

31483143
def test_toy_cuda_sanity_check(self):
31493144
"""Test the CUDA sanity check"""
3150-
# We need to mock a cuobjdump executable and prepend in on the PATH
3151-
# First, make sure we can restore environment at the end of this test
3152-
start_env = copy.deepcopy(os.environ)
3153-
31543145
# Define the toy_ec file we want to use
31553146
topdir = os.path.dirname(os.path.abspath(__file__))
31563147
toy_ec = os.path.join(topdir, 'easyconfigs', 'test_ecs', 't', 'toy', 'toy-0.0.eb')
@@ -3540,9 +3531,6 @@ def assert_cuda_report(missing_cc, additional_cc, missing_ptx, log, stdout=None,
35403531
self.assertTrue(expected_result.search(outtxt), msg)
35413532
assert_cuda_report(missing_cc=0, additional_cc=0, missing_ptx=0, log=outtxt, stdout=stdout, num_checked=0)
35423533

3543-
# Restore original environment
3544-
modify_env(os.environ, start_env, verbose=False)
3545-
35463534
def test_toy_modaltsoftname(self):
35473535
"""Build two dependent toys as in test_toy_toy but using modaltsoftname"""
35483536
topdir = os.path.dirname(os.path.abspath(__file__))
@@ -3953,7 +3941,8 @@ def check_toy_load(depends_on=False):
39533941
# just undo
39543942
self.modtool.unload(['toy/0.0', 'GCC/4.6.3'])
39553943

3956-
check_toy_load()
3944+
with self.saved_env():
3945+
check_toy_load()
39573946

39583947
# this behaviour can be disabled via "multi_dep_load_defaults = False"
39593948
write_file(test_ec, test_ec_txt + "\nmulti_deps_load_default = False")
@@ -3965,8 +3954,9 @@ def check_toy_load(depends_on=False):
39653954

39663955
self.assertNotIn(expected, toy_mod_txt)
39673956

3968-
self.modtool.load(['toy/0.0'])
3969-
loaded_mod_names = [x['mod_name'] for x in self.modtool.list()]
3957+
with self.saved_env():
3958+
self.modtool.load(['toy/0.0'])
3959+
loaded_mod_names = [x['mod_name'] for x in self.modtool.list()]
39703960
self.assertIn('toy/0.0', loaded_mod_names)
39713961
self.assertNotIn('GCC/4.6.3', loaded_mod_names)
39723962
self.assertNotIn('GCC/7.3.0-2.30', loaded_mod_names)
@@ -3989,10 +3979,6 @@ def check_toy_load(depends_on=False):
39893979
error_msg_whatis = "Pattern '%s' should be found in: %s" % (expected_whatis_no_default, toy_mod_txt)
39903980
self.assertIn(expected_whatis_no_default, toy_mod_txt, error_msg_whatis)
39913981

3992-
# restore original environment to continue testing with a clean slate
3993-
modify_env(os.environ, self.orig_environ, verbose=False)
3994-
self.modtool.use(test_mod_path)
3995-
39963982
# disable showing of progress bars (again), doesn't make sense when running tests
39973983
os.environ['EASYBUILD_DISABLE_SHOW_PROGRESS_BAR'] = '1'
39983984

test/framework/utilities.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -501,9 +501,9 @@ def init_config(args=None, build_options=None, with_include=True, clear_caches=T
501501
'valid_module_classes': module_classes(),
502502
'valid_stops': [x[0] for x in EasyBlock.get_steps()],
503503
}
504-
for key in default_build_options:
504+
for key, def_option in default_build_options.items():
505505
if key not in build_options:
506-
build_options[key] = default_build_options[key]
506+
build_options[key] = def_option
507507

508508
config.init_build_options(build_options=build_options)
509509

0 commit comments

Comments
 (0)