Skip to content

Commit e1fd987

Browse files
committed
Introduce context manager for restoring environment in tests
Many tests modify the environment in some steps and need to restore it afterwards. To avoid missing this the contextmanager does that automatically.
1 parent 29b7575 commit e1fd987

File tree

7 files changed

+45
-71
lines changed

7 files changed

+45
-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)

test/framework/easyblock.py

Lines changed: 14 additions & 21 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)
@@ -2605,7 +2602,7 @@ def test_extensions_sanity_check(self):
26052602
error_pattern = r"Sanity check failed: extensions sanity check failed for 1 extensions: toy\n"
26062603
error_pattern += r"failing sanity check for 'toy' extension: "
26072604
error_pattern += r'command "thisshouldfail" failed; output:\n.* thisshouldfail: command not found'
2608-
with self.mocked_stdout_stderr():
2605+
with self.mocked_stdout_stderr(), self.saved_env():
26092606
self.assertErrorRegex(EasyBuildError, error_pattern, eb.run_all_steps, True)
26102607

26112608
# purposely put sanity check command in place that breaks the build,
@@ -2615,7 +2612,7 @@ def test_extensions_sanity_check(self):
26152612
toy_ec.update('sanity_check_commands', [("%(installdir)s/bin/toy && rm %(installdir)s/bin/toy", '')])
26162613
eb = EB_toy(toy_ec)
26172614
eb.silent = True
2618-
with self.mocked_stdout_stderr():
2615+
with self.mocked_stdout_stderr(), self.saved_env():
26192616
eb.run_all_steps(True)
26202617

26212618
# Verify custom paths and commands of extensions are checked
@@ -2643,7 +2640,7 @@ def test_extensions_sanity_check(self):
26432640
eb = EB_toy(EasyConfig(self.eb_file))
26442641
eb.silent = True
26452642
write_file(os.path.join(eb.installdir, 'any_file'), '')
2646-
with self.mocked_stdout_stderr():
2643+
with self.mocked_stdout_stderr(), self.saved_env():
26472644
eb.sanity_check_step()
26482645
logtxt = read_file(eb.logfile)
26492646
self.assertRegex(logtxt, 'Running .*command.*echo "toy output"')
@@ -3021,23 +3018,19 @@ def test_extension_patch_step(self):
30213018

30223019
cwd = os.getcwd()
30233020
self.assertExists(cwd)
3024-
# Take environment with test-specific variable set up
3025-
orig_environ = copy.deepcopy(os.environ)
30263021

30273022
def run_extension_step():
3028-
try:
3029-
change_dir(cwd)
3030-
eb = EasyBlock(ec)
3031-
# Cleanup build directory
3032-
if os.path.exists(eb.builddir):
3033-
remove_dir(eb.builddir)
3023+
change_dir(cwd)
3024+
eb = EasyBlock(ec)
3025+
# Cleanup build directory
3026+
if os.path.exists(eb.builddir):
3027+
remove_dir(eb.builddir)
3028+
# restore original environment to continue testing with a clean slate
3029+
with self.saved_env():
30343030
eb.make_builddir()
30353031
eb.update_config_template_run_step()
30363032
eb.extensions_step(fetch=True, install=True)
3037-
return os.path.join(eb.builddir)
3038-
finally:
3039-
# restore original environment to continue testing with a clean slate
3040-
modify_env(os.environ, orig_environ, verbose=False)
3033+
return os.path.join(eb.builddir)
30413034

30423035
ec['exts_defaultclass'] = 'DummyExtension'
30433036
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
@@ -4322,7 +4321,6 @@ def check_tmpdir(tmpdir):
43224321
# cleanup
43234322
os.close(fd)
43244323
shutil.rmtree(mytmpdir)
4325-
modify_env(os.environ, self.orig_environ)
43264324
tempfile.tempdir = None
43274325

43284326
orig_tmpdir = tempfile.gettempdir()
@@ -4333,7 +4331,8 @@ def check_tmpdir(tmpdir):
43334331
os.path.join(orig_tmpdir, '[ab @cd]%/#*'),
43344332
]
43354333
for tmpdir in cand_tmpdirs:
4336-
check_tmpdir(tmpdir)
4334+
with self.saved_env():
4335+
check_tmpdir(tmpdir)
43374336

43384337
def test_minimal_toolchains(self):
43394338
"""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 & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@
5454
from easybuild.main import main_with_hooks
5555
from easybuild.tools.build_log import EasyBuildError
5656
from easybuild.tools.config import get_module_syntax, get_repositorypath
57-
from easybuild.tools.environment import modify_env, setvar
57+
from easybuild.tools.environment import setvar
5858
from easybuild.tools.filetools import adjust_permissions, change_dir, copy_file, mkdir, move_file
5959
from easybuild.tools.filetools import read_file, remove_dir, remove_file, which, write_file
6060
from easybuild.tools.module_generator import ModuleGeneratorTcl
@@ -1751,18 +1751,14 @@ def test_external_dependencies(self):
17511751
installed_test_modules = os.path.join(self.test_installpath, 'modules', 'all')
17521752
self.reset_modulepath([modulepath, installed_test_modules])
17531753

1754-
start_env = copy.deepcopy(os.environ)
1755-
17561754
with self.mocked_stdout_stderr():
17571755
self._test_toy_build(ec_file=toy_ec, versionsuffix='-external-deps', verbose=True, raise_error=True)
17581756

1759-
self.modtool.load(['toy/0.0-external-deps'])
1760-
# note build dependency is not loaded
1761-
mods = ['intel/2018a', 'GCC/6.4.0-2.28', 'foobar/1.2.3', 'toy/0.0-external-deps']
1762-
self.assertEqual([x['mod_name'] for x in self.modtool.list()], mods)
1763-
1764-
# restore original environment (to undo 'module load' done above)
1765-
modify_env(os.environ, start_env, verbose=False)
1757+
with self.saved_env():
1758+
self.modtool.load(['toy/0.0-external-deps'])
1759+
# note build dependency is not loaded
1760+
mods = ['intel/2018a', 'GCC/6.4.0-2.28', 'foobar/1.2.3', 'toy/0.0-external-deps']
1761+
self.assertEqual([x['mod_name'] for x in self.modtool.list()], mods)
17661762

17671763
# check behaviour when a non-existing external (build) dependency is included
17681764
extraectxt = "\nbuilddependencies = [('nosuchbuilddep/0.0.0', EXTERNAL_MODULE)]"
@@ -3136,10 +3132,6 @@ def test_toy_filter_rpath_sanity_libs(self):
31363132

31373133
def test_toy_cuda_sanity_check(self):
31383134
"""Test the CUDA sanity check"""
3139-
# We need to mock a cuobjdump executable and prepend in on the PATH
3140-
# First, make sure we can restore environment at the end of this test
3141-
start_env = copy.deepcopy(os.environ)
3142-
31433135
# Define the toy_ec file we want to use
31443136
topdir = os.path.dirname(os.path.abspath(__file__))
31453137
toy_ec = os.path.join(topdir, 'easyconfigs', 'test_ecs', 't', 'toy', 'toy-0.0.eb')
@@ -3529,9 +3521,6 @@ def assert_cuda_report(missing_cc, additional_cc, missing_ptx, log, stdout=None,
35293521
self.assertTrue(expected_result.search(outtxt), msg)
35303522
assert_cuda_report(missing_cc=0, additional_cc=0, missing_ptx=0, log=outtxt, stdout=stdout, num_checked=0)
35313523

3532-
# Restore original environment
3533-
modify_env(os.environ, start_env, verbose=False)
3534-
35353524
def test_toy_modaltsoftname(self):
35363525
"""Build two dependent toys as in test_toy_toy but using modaltsoftname"""
35373526
topdir = os.path.dirname(os.path.abspath(__file__))
@@ -3940,7 +3929,8 @@ def check_toy_load(depends_on=False):
39403929
# just undo
39413930
self.modtool.unload(['toy/0.0', 'GCC/4.6.3'])
39423931

3943-
check_toy_load()
3932+
with self.saved_env():
3933+
check_toy_load()
39443934

39453935
# this behaviour can be disabled via "multi_dep_load_defaults = False"
39463936
write_file(test_ec, test_ec_txt + "\nmulti_deps_load_default = False")
@@ -3952,8 +3942,9 @@ def check_toy_load(depends_on=False):
39523942

39533943
self.assertNotIn(expected, toy_mod_txt)
39543944

3955-
self.modtool.load(['toy/0.0'])
3956-
loaded_mod_names = [x['mod_name'] for x in self.modtool.list()]
3945+
with self.saved_env():
3946+
self.modtool.load(['toy/0.0'])
3947+
loaded_mod_names = [x['mod_name'] for x in self.modtool.list()]
39573948
self.assertIn('toy/0.0', loaded_mod_names)
39583949
self.assertNotIn('GCC/4.6.3', loaded_mod_names)
39593950
self.assertNotIn('GCC/7.3.0-2.30', loaded_mod_names)
@@ -3976,10 +3967,6 @@ def check_toy_load(depends_on=False):
39763967
error_msg_whatis = "Pattern '%s' should be found in: %s" % (expected_whatis_no_default, toy_mod_txt)
39773968
self.assertIn(expected_whatis_no_default, toy_mod_txt, error_msg_whatis)
39783969

3979-
# restore original environment to continue testing with a clean slate
3980-
modify_env(os.environ, self.orig_environ, verbose=False)
3981-
self.modtool.use(test_mod_path)
3982-
39833970
# disable showing of progress bars (again), doesn't make sense when running tests
39843971
os.environ['EASYBUILD_DISABLE_SHOW_PROGRESS_BAR'] = '1'
39853972

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)