Repository: easybuilders/easybuild-framework

Issue:
Module alias/wrapper detection is broken/incomplete
Some modules have wrapper modules or aliases. E.g. `Java/11` is not a real modules but implemented via a `.modulerc.lua` as `module_version("Java/11.0.6-ppc64le", "11")`

Similar thing exists for aliases: `module_alias(...)`

When EasyBuild checks for an existing module it runs `module show <modname>` and checks the output with a regexp `^\s*\S*/<modname>.*(\.lua)?:\s*$`

This isn't exactly reliable. E.g. one might have a module `Java/site-default-11` or so. Additionally not all possible modulerc locations are considered and aliases are completely unsupported. See https://lmod.readthedocs.io/en/latest/093_modulerc.html

The obvious solution would be to start parsing the modulerc files correctly.

But: Of course it isn't as this is what the modules tool can do way better.

Hence the better solution would be to check for `_mlstatus = False` and if that is found the module isn't available. Getting the filename isn't required for the correct return value of `<ModulesTool>.exist` so this can be greatly simplified.

Edit: This doesn't seem to work reliably for some reason. Sometimes lmod seems to return only `_mlstatus = True` even though the module isn't found. But only in EB, can't reproduce in shell. Seems to be that `LMOD_QUIET=1` also suppresses the error code

In case we need the filename sometime later we should split the stdout/stderr and parse only stderr for a filename (LMod, not sure about EnvironmentModules)


Discussion:

Resolving patch:
diff --git a/easybuild/tools/modules.py b/easybuild/tools/modules.py
--- a/easybuild/tools/modules.py
+++ b/easybuild/tools/modules.py
@@ -487,7 +487,11 @@ def module_wrapper_exists(self, mod_name, modulerc_fn='.modulerc', mod_wrapper_r
         """
         Determine whether a module wrapper with specified name exists.
         Only .modulerc file in Tcl syntax is considered here.
+        DEPRECATED. Use exists()
         """
+        self.log.deprecated('module_wrapper_exists is unreliable and should no longer be used. ' +
+                            'Use exists instead to check for an existing module or alias.', '5.0')
+
         if mod_wrapper_regex_template is None:
             mod_wrapper_regex_template = "^[ ]*module-version (?P<wrapped_mod>[^ ]*) %s$"
 
@@ -528,24 +532,40 @@ def module_wrapper_exists(self, mod_name, modulerc_fn='.modulerc', mod_wrapper_r
 
         return wrapped_mod
 
-    def exist(self, mod_names, mod_exists_regex_template=r'^\s*\S*/%s.*:\s*$', skip_avail=False, maybe_partial=True):
+    def exist(self, mod_names, mod_exists_regex_template=None, skip_avail=False, maybe_partial=True):
         """
         Check if modules with specified names exists.
 
         :param mod_names: list of module names
-        :param mod_exists_regex_template: template regular expression to search 'module show' output with
+        :param mod_exists_regex_template: DEPRECATED and unused
         :param skip_avail: skip checking through 'module avail', only check via 'module show'
         :param maybe_partial: indicates if the module name may be a partial module name
         """
+        if mod_exists_regex_template is not None:
+            self.log.deprecated('mod_exists_regex_template is no longer used', '5.0')
+
         def mod_exists_via_show(mod_name):
             """
             Helper function to check whether specified module name exists through 'module show'.
 
             :param mod_name: module name
             """
-            mod_exists_regex = mod_exists_regex_template % re.escape(mod_name)
-            txt = self.show(mod_name)
-            return bool(re.search(mod_exists_regex, txt, re.M))
+            stderr = self.show(mod_name)
+            res = False
+            # Parse the output:
+            # - Skip whitespace
+            # - Any error -> Module does not exist
+            # - Check first non-whitespace line for something that looks like an absolute path terminated by a colon
+            mod_exists_regex = r'\s*/.+:\s*'
+            for line in stderr.split('\n'):
+                if OUTPUT_MATCHES['whitespace'].search(line):
+                    continue
+                if OUTPUT_MATCHES['error'].search(line):
+                    break
+                if re.match(mod_exists_regex, line):
+                    res = True
+                break
+            return res
 
         if skip_avail:
             avail_mod_names = []
@@ -570,15 +590,6 @@ def mod_exists_via_show(mod_name):
                 self.log.debug("checking whether hidden module %s exists via 'show'..." % mod_name)
                 mod_exists = mod_exists_via_show(mod_name)
 
-            # if no module file was found, check whether specified module name can be a 'wrapper' module...
-            if not mod_exists:
-                self.log.debug("Module %s not found via module avail/show, checking whether it is a wrapper", mod_name)
-                wrapped_mod = self.module_wrapper_exists(mod_name)
-                if wrapped_mod is not None:
-                    # module wrapper only really exists if the wrapped module file is also available
-                    mod_exists = wrapped_mod in avail_mod_names or mod_exists_via_show(wrapped_mod)
-                    self.log.debug("Result for existence check of wrapped module %s: %s", wrapped_mod, mod_exists)
-
             self.log.debug("Result for existence check of %s module: %s", mod_name, mod_exists)
 
             mods_exist.append(mod_exists)
@@ -643,7 +654,7 @@ def show(self, mod_name):
             ans = MODULE_SHOW_CACHE[key]
             self.log.debug("Found cached result for 'module show %s' with key '%s': %s", mod_name, key, ans)
         else:
-            ans = self.run_module('show', mod_name, check_output=False, return_output=True)
+            ans = self.run_module('show', mod_name, check_output=False, return_stderr=True)
             MODULE_SHOW_CACHE[key] = ans
             self.log.debug("Cached result for 'module show %s' with key '%s': %s", mod_name, key, ans)
 
@@ -759,13 +770,15 @@ def run_module(self, *args, **kwargs):
         # also catch and check exit code
         exit_code = proc.returncode
         if kwargs.get('check_exit_code', True) and exit_code != 0:
-            raise EasyBuildError("Module command 'module %s' failed with exit code %s; stderr: %s; stdout: %s",
-                                 ' '.join(cmd_list[2:]), exit_code, stderr, stdout)
+            raise EasyBuildError("Module command '%s' failed with exit code %s; stderr: %s; stdout: %s",
+                                 ' '.join(cmd_list), exit_code, stderr, stdout)
 
         if kwargs.get('check_output', True):
             self.check_module_output(full_cmd, stdout, stderr)
 
-        if kwargs.get('return_output', False):
+        if kwargs.get('return_stderr', False):
+            return stderr
+        elif kwargs.get('return_output', False):
             return stdout + stderr
         else:
             # the module command was run with an outdated selected environment variables (see LD_ENV_VAR_KEYS list)
@@ -1394,7 +1407,7 @@ def prepend_module_path(self, path, set_mod_paths=True, priority=None):
     def module_wrapper_exists(self, mod_name):
         """
         Determine whether a module wrapper with specified name exists.
-        First check for wrapper defined in .modulerc.lua, fall back to also checking .modulerc (Tcl syntax).
+        DEPRECATED. Use exists()
         """
         res = None
 
@@ -1410,19 +1423,6 @@ def module_wrapper_exists(self, mod_name):
 
         return res
 
-    def exist(self, mod_names, skip_avail=False, maybe_partial=True):
-        """
-        Check if modules with specified names exists.
-
-        :param mod_names: list of module names
-        :param skip_avail: skip checking through 'module avail', only check via 'module show'
-        """
-        # module file may be either in Tcl syntax (no file extension) or Lua sytax (.lua extension);
-        # the current configuration for matters little, since the module may have been installed with a different cfg;
-        # Lmod may pick up both Tcl and Lua module files, regardless of the EasyBuild configuration
-        return super(Lmod, self).exist(mod_names, mod_exists_regex_template=r'^\s*\S*/%s.*(\.lua)?:\s*$',
-                                       skip_avail=skip_avail, maybe_partial=maybe_partial)
-
     def get_setenv_value_from_modulefile(self, mod_name, var_name):
         """
         Get value for specific 'setenv' statement from module file for the specified module.


Test patch:
diff --git a/test/framework/modules.py b/test/framework/modules.py
--- a/test/framework/modules.py
+++ b/test/framework/modules.py
@@ -44,6 +44,7 @@
 from easybuild.framework.easyblock import EasyBlock
 from easybuild.framework.easyconfig.easyconfig import EasyConfig
 from easybuild.tools.build_log import EasyBuildError
+from easybuild.tools.environment import modify_env
 from easybuild.tools.filetools import adjust_permissions, copy_file, copy_dir, mkdir
 from easybuild.tools.filetools import read_file, remove_dir, remove_file, symlink, write_file
 from easybuild.tools.modules import EnvironmentModules, EnvironmentModulesC, EnvironmentModulesTcl, Lmod, NoModulesTool
@@ -92,6 +93,104 @@ def test_long_module_path(self):
 
         shutil.rmtree(tmpdir)
 
+    def test_run_module(self):
+        """Test for ModulesTool.run_module method."""
+
+        testdir = os.path.dirname(os.path.abspath(__file__))
+
+        for key in ['EBROOTGCC', 'EBROOTOPENMPI', 'EBROOTOPENBLAS']:
+            if key in os.environ:
+                del os.environ[key]
+
+        # arguments can be passed in two ways: multiple arguments, or just 1 list argument
+        self.modtool.run_module('load', 'GCC/6.4.0-2.28')
+        self.assertEqual(os.environ['EBROOTGCC'], '/prefix/software/GCC/6.4.0-2.28')
+
+        # restore original environment
+        modify_env(os.environ, self.orig_environ, verbose=False)
+        self.reset_modulepath([os.path.join(testdir, 'modules')])
+
+        self.assertFalse('EBROOTGCC' in os.environ)
+        self.modtool.run_module(['load', 'GCC/6.4.0-2.28'])
+        self.assertEqual(os.environ['EBROOTGCC'], '/prefix/software/GCC/6.4.0-2.28')
+
+        # skip tests that rely on exit codes when using EnvironmentModulesTcl modules tool,
+        # because it doesn't use proper exit codes
+        if not isinstance(self.modtool, EnvironmentModulesTcl):
+
+            # by default, exit code is checked and an error is raised if we run something that fails
+            error_pattern = "Module command '.*thisdoesnotmakesense' failed with exit code [1-9]"
+            self.assertErrorRegex(EasyBuildError, error_pattern, self.modtool.run_module, 'thisdoesnotmakesense')
+
+            # we need to use a different error pattern here with EnvironmentModulesC,
+            # because a load of a non-existing module doesnt' trigger a non-zero exit code...
+            # it will still fail though, just differently
+            if isinstance(self.modtool, EnvironmentModulesC):
+                error_pattern = "Unable to locate a modulefile for 'nosuchmodule/1.2.3'"
+            else:
+                error_pattern = "Module command '.*load nosuchmodule/1.2.3' failed with exit code [1-9]"
+            self.assertErrorRegex(EasyBuildError, error_pattern, self.modtool.run_module, 'load', 'nosuchmodule/1.2.3')
+
+        # we can choose to blatently ignore the exit code,
+        # and also disable the output check that serves as a fallback;
+        # we also enable return_output here, because trying to apply the environment changes produced
+        # by a faulty command is bound to cause trouble...
+        kwargs = {
+            'check_exit_code': False,
+            'check_output': False,
+            'return_output': True,
+        }
+        self.modtool.run_module('thisdoesnotmakesense', **kwargs)
+        self.modtool.run_module('load', 'nosuchmodule/1.2.3', **kwargs)
+
+        # by default, the output (stdout+stderr) produced by the command is processed;
+        # result is a list of useful info (module names in case of list/avail)
+        res = self.modtool.run_module('list')
+        self.assertEqual(res, [{'mod_name': 'GCC/6.4.0-2.28', 'default': None}])
+
+        res = self.modtool.run_module('avail', 'GCC/4.6.3')
+        self.assertTrue(isinstance(res, list))
+        self.assertEqual(sorted([x['mod_name'] for x in res]), ['GCC/4.6.3'])
+
+        # loading a module produces no output, so we get an empty list
+        res = self.modtool.run_module('load', 'OpenMPI/2.1.2-GCC-6.4.0-2.28')
+        self.assertEqual(res, [])
+        self.assertEqual(os.environ['EBROOTOPENMPI'], '/prefix/software/OpenMPI/2.1.2-GCC-6.4.0-2.28')
+
+        # we can opt into getting back the raw output (stdout + stderr);
+        # in that cases, the output includes Python statements to change the environment;
+        # the changes that would be made by the module command are *not* applied to the environment
+        out = self.modtool.run_module('load', 'OpenBLAS/0.2.20-GCC-6.4.0-2.28', return_output=True)
+        patterns = [
+            r"^os.environ\[.EBROOTOPENBLAS.\]\s*=\s*./prefix/software/OpenBLAS/0.2.20-GCC-6.4.0-2.28.",
+            r"^os.environ\[.LOADEDMODULES.\]\s*=.*OpenBLAS/0.2.20-GCC-6.4.0-2.28",
+        ]
+        for pattern in patterns:
+            regex = re.compile(pattern, re.M)
+            self.assertTrue(regex.search(out), "Pattern '%s' should be found in: %s" % (regex.pattern, out))
+
+        # OpenBLAS module did *not* get loaded
+        self.assertFalse('EBROOTOPENBLAS' in os.environ)
+        res = self.modtool.list()
+        expected = ['GCC/6.4.0-2.28', 'OpenMPI/2.1.2-GCC-6.4.0-2.28', 'hwloc/1.11.8-GCC-6.4.0-2.28']
+        self.assertEqual(sorted([x['mod_name'] for x in res]), expected)
+
+        # we can also only obtain the stderr output (which contains the user-facing output),
+        # and just drop the stdout output (which contains the statements to change the environment)
+        out = self.modtool.run_module('show', 'OpenBLAS/0.2.20-GCC-6.4.0-2.28', return_stderr=True)
+        patterns = [
+            r"test/framework/modules/OpenBLAS/0.2.20-GCC-6.4.0-2.28:\s*$",
+            r"setenv\W+EBROOTOPENBLAS.+/prefix/software/OpenBLAS/0.2.20-GCC-6.4.0-2.28",
+            r"prepend[_-]path\W+LD_LIBRARY_PATH.+/prefix/software/OpenBLAS/0.2.20-GCC-6.4.0-2.28/lib",
+        ]
+        for pattern in patterns:
+            regex = re.compile(pattern, re.M)
+            self.assertTrue(regex.search(out), "Pattern '%s' should be found in: %s" % (regex.pattern, out))
+
+        # show method only returns user-facing output (obtained via stderr), not changes to the environment
+        regex = re.compile(r'^os\.environ\[', re.M)
+        self.assertFalse(regex.search(out), "Pattern '%s' should not be found in: %s" % (regex.pattern, out))
+
     def test_avail(self):
         """Test if getting a (restricted) list of available modules works."""
         self.init_testmods()
@@ -168,6 +267,7 @@ def test_exist(self):
 
         java_mod_dir = os.path.join(self.test_prefix, 'Java')
         write_file(os.path.join(java_mod_dir, '1.8.0_181'), '#%Module')
+        write_file(os.path.join(self.test_prefix, 'toy', '42.1337'), '#%Module')
 
         if self.modtool.__class__ == EnvironmentModulesC:
             modulerc_tcl_txt = '\n'.join([
@@ -175,11 +275,18 @@ def test_exist(self):
                 'if {"Java/1.8" eq [module-info version Java/1.8]} {',
                 '    module-version Java/1.8.0_181 1.8',
                 '}',
+                'if {"Java/site_default" eq [module-info version Java/site_default]} {',
+                '    module-version Java/1.8.0_181 site_default',
+                '}',
             ])
         else:
             modulerc_tcl_txt = '\n'.join([
                 '#%Module',
                 'module-version Java/1.8.0_181 1.8',
+                'module-version Java/1.8.0_181 site_default',
+                'module-alias Java/Alias toy/42.1337',
+                # 'module-alias Java/NonExist non_existant/1',  # (only) LMod has this in module avail, disable for now
+                'module-alias JavaAlias Java/1.8.0_181',  # LMod 7+ only
             ])
 
         write_file(os.path.join(java_mod_dir, '.modulerc'), modulerc_tcl_txt)
@@ -188,8 +295,20 @@ def test_exist(self):
         self.assertTrue('Java/1.8.0_181' in avail_mods)
         if isinstance(self.modtool, Lmod) and StrictVersion(self.modtool.version) >= StrictVersion('7.0'):
             self.assertTrue('Java/1.8' in avail_mods)
+            self.assertTrue('Java/site_default' in avail_mods)
+            self.assertTrue('JavaAlias' in avail_mods)
+            self.assertEqual(self.modtool.exist(['JavaAlias']), [True])
+
         self.assertEqual(self.modtool.exist(['Java/1.8', 'Java/1.8.0_181']), [True, True])
-        self.assertEqual(self.modtool.module_wrapper_exists('Java/1.8'), 'Java/1.8.0_181')
+
+        # module-version with different version suffix than the base module
+        self.assertEqual(self.modtool.exist(['Java/site_default']), [True])
+        # Check for aliases:
+        # - completely different nameTrue, True,
+        # - alias to non existant module
+        # Skipped for EnvironmentModulesC as module-alias not working correctly there
+        if self.modtool.__class__ != EnvironmentModulesC:
+            self.assertEqual(self.modtool.exist(['Java/Alias', 'Java/NonExist']), [True, False])
 
         reset_module_caches()
 
@@ -199,8 +318,12 @@ def test_exist(self):
 
         self.assertTrue('Core/Java/1.8.0_181' in self.modtool.available())
         self.assertEqual(self.modtool.exist(['Core/Java/1.8.0_181']), [True])
-        self.assertEqual(self.modtool.exist(['Core/Java/1.8']), [True])
-        self.assertEqual(self.modtool.module_wrapper_exists('Core/Java/1.8'), 'Core/Java/1.8.0_181')
+        # module-version only works for EnvironmentModules(C) as LMod and EnvironmentModulesTcl would need updating
+        # to full path, see https://github.com/TACC/Lmod/issues/446
+        if isinstance(self.modtool, Lmod) or self.modtool.__class__ == EnvironmentModulesTcl:
+            self.assertEqual(self.modtool.exist(['Core/Java/1.8', 'Core/Java/site_default']), [False, False])
+        else:
+            self.assertEqual(self.modtool.exist(['Core/Java/1.8', 'Core/Java/site_default']), [True, True])
 
         # also check with .modulerc.lua for Lmod 7.8 or newer
         if isinstance(self.modtool, Lmod) and StrictVersion(self.modtool.version) >= StrictVersion('7.8'):
@@ -208,13 +331,22 @@ def test_exist(self):
             reset_module_caches()
 
             remove_file(os.path.join(java_mod_dir, '.modulerc'))
-            write_file(os.path.join(java_mod_dir, '.modulerc.lua'), 'module_version("Java/1.8.0_181", "1.8")')
+            write_file(os.path.join(java_mod_dir, '.modulerc.lua'),
+                       '\n'.join([
+                            'module_version("Java/1.8.0_181", "1.8")',
+                            'module_version("Java/1.8.0_181", "site_default")',
+                            'module_alias("JavaAlias", "Java/1.8")',
+                        ]))
 
             avail_mods = self.modtool.available()
             self.assertTrue('Java/1.8.0_181' in avail_mods)
             self.assertTrue('Java/1.8' in avail_mods)
             self.assertEqual(self.modtool.exist(['Java/1.8', 'Java/1.8.0_181']), [True, True])
-            self.assertEqual(self.modtool.module_wrapper_exists('Java/1.8'), 'Java/1.8.0_181')
+
+            # check for an alias with a different version suffix than the base module
+            self.assertEqual(self.modtool.exist(['Java/site_default']), [True])
+            # And completely different name
+            self.assertEqual(self.modtool.exist(['JavaAlias']), [True])
 
             reset_module_caches()
 
@@ -222,8 +354,24 @@ def test_exist(self):
             shutil.move(java_mod_dir, os.path.join(self.test_prefix, 'Core', 'Java'))
             self.assertTrue('Core/Java/1.8.0_181' in self.modtool.available())
             self.assertEqual(self.modtool.exist(['Core/Java/1.8.0_181']), [True])
-            self.assertEqual(self.modtool.exist(['Core/Java/1.8']), [True])
-            self.assertEqual(self.modtool.module_wrapper_exists('Core/Java/1.8'), 'Core/Java/1.8.0_181')
+            self.assertEqual(self.modtool.exist(['Core/Java/1.8']), [False])
+            self.assertEqual(self.modtool.exist(['Core/Java/site_default']), [False])
+
+        # Test alias in home directory .modulerc
+        if isinstance(self.modtool, Lmod) and StrictVersion(self.modtool.version) >= StrictVersion('7.0'):
+            # Required or temporary HOME would be in MODULEPATH already
+            self.init_testmods()
+            # Sanity check: Module aliases don't exist yet
+            self.assertEqual(self.modtool.exist(['OpenMPI/99', 'OpenMPIAlias']), [False, False])
+            # Use a temporary dir, not the users HOME
+            os.environ['HOME'] = tempfile.mkdtemp()
+            reset_module_caches()
+            write_file(os.path.join(os.environ['HOME'], '.modulerc'), '\n'.join([
+                '#%Module',
+                'module-version OpenMPI/2.1.2-GCC-6.4.0-2.28 99',
+                'module-alias OpenMPIAlias OpenMPI/2.1.2-GCC-6.4.0-2.28',
+            ]))
+            self.assertEqual(self.modtool.exist(['OpenMPI/99', 'OpenMPIAlias']), [True, True])
 
     def test_load(self):
         """ test if we load one module it is in the loaded_modules """
@@ -284,6 +432,25 @@ def test_load(self):
         self.assertEqual(os.environ.get('EBROOTGCC'), None)
         self.assertFalse(loaded_modules[-1] == 'GCC/6.4.0-2.28')
 
+    def test_show(self):
+        """Test for ModulesTool.show method."""
+
+        out = self.modtool.show('GCC/7.3.0-2.30')
+
+        patterns = [
+            # full path to module is included in output of 'show'
+            r"test/framework/modules/GCC/7.3.0-2.30:\s*$",
+            r"setenv\W+EBROOTGCC.+prefix/software/GCC/7.3.0-2.30",
+            r"^prepend[_-]path\W+PATH.+/prefix/software/GCC/7.3.0-2.30/bin",
+        ]
+        for pattern in patterns:
+            regex = re.compile(pattern, re.M)
+            self.assertTrue(regex.search(out), "Pattern '%s' should be found in: %s" % (regex.pattern, out))
+
+        # show method only returns user-facing output (obtained via stderr), not changes to the environment
+        regex = re.compile(r'^os\.environ\[', re.M)
+        self.assertFalse(regex.search(out), "Pattern '%s' should not be found in: %s" % (regex.pattern, out))
+
     def test_curr_module_paths(self):
         """Test for curr_module_paths function."""
 
@@ -544,24 +711,6 @@ def test_modulefile_path(self):
             res = modtool.modulefile_path('bzip2/.1.0.6', strip_ext=True)
             self.assertTrue(res.endswith('test/framework/modules/bzip2/.1.0.6'))
 
-        # hack into 'module show GCC/6.4.0-2.28' cache and inject alternate output that modulecmd.tcl sometimes produces
-        # make sure we only extract the module file path, nothing else...
-        # cfr. https://github.com/easybuilders/easybuild/issues/368
-        modulepath = os.environ['MODULEPATH'].split(':')
-        mod_show_cache_key = modtool.mk_module_cache_key('GCC/6.4.0-2.28')
-        mod.MODULE_SHOW_CACHE[mod_show_cache_key] = '\n'.join([
-            "import os",
-            "os.environ['MODULEPATH_modshare'] = '%s'" % ':'.join(m + ':1' for m in modulepath),
-            "os.environ['MODULEPATH'] = '%s'" % ':'.join(modulepath),
-            "------------------------------------------------------------------------------",
-            "%s:" % gcc_mod_file,
-            "------------------------------------------------------------------------------",
-            # remainder of output doesn't really matter in this context
-            "setenv		EBROOTGCC /prefix/GCC/6.4.0-2.28"
-        ])
-        res = modtool.modulefile_path('GCC/6.4.0-2.28')
-        self.assertTrue(os.path.samefile(res, os.path.join(test_dir, 'modules', 'GCC', '6.4.0-2.28')))
-
         reset_module_caches()
 
     def test_path_to_top_of_module_tree(self):
@@ -891,7 +1040,7 @@ def test_modules_tool_stateless(self):
             # exact error message depends on Lmod version
             load_err_msg = '|'.join([
                 r'These[\s\sn]*module\(s\)[\s\sn]*exist[\s\sn]*but[\s\sn]*cannot[\s\sn]*be',
-                'The[\s\sn]*following[\s\sn]*module\(s\)[\s\sn]*are[\s\sn]*unknown',
+                r'The[\s\sn]*following[\s\sn]*module\(s\)[\s\sn]*are[\s\sn]*unknown',
             ])
         else:
             load_err_msg = "Unable to locate a modulefile"
@@ -1060,7 +1209,7 @@ def test_load_in_hierarchy(self):
     def test_exit_code_check(self):
         """Verify that EasyBuild checks exit code of executed module commands"""
         if isinstance(self.modtool, Lmod):
-            error_pattern = "Module command 'module load nosuchmoduleavailableanywhere' failed with exit code"
+            error_pattern = "Module command '.*load nosuchmoduleavailableanywhere' failed with exit code"
         else:
             # Tcl implementations exit with 0 even when a non-existing module is loaded...
             error_pattern = "Unable to locate a modulefile for 'nosuchmoduleavailableanywhere'"
@@ -1101,7 +1250,7 @@ def check_loaded_modules():
             r"^\* GCC/6.4.0-2.28",
             r"^\* hwloc/1.11.8-GCC-6.4.0-2.28",
             r"^\* OpenMPI/2.1.2-GCC-6.4.0-2.28",
-            "This is not recommended since it may affect the installation procedure\(s\) performed by EasyBuild.",
+            r"This is not recommended since it may affect the installation procedure\(s\) performed by EasyBuild.",
             "To make EasyBuild allow particular loaded modules, use the --allow-loaded-modules configuration option.",
             "To specify action to take when loaded modules are detected, use "
             "--detect-loaded-modules={error,ignore,purge,unload,warn}",
@@ -1119,7 +1268,7 @@ def check_loaded_modules():
 
         # error mentioning 1 non-allowed module (OpenMPI), both GCC and hwloc loaded modules are allowed
         error_pattern = r"Found one or more non-allowed loaded .* module.*\n"
-        error_pattern += "\* OpenMPI/2.1.2-GCC-6.4.0-2.28\n\nThis is not"
+        error_pattern += r"\* OpenMPI/2.1.2-GCC-6.4.0-2.28\n\nThis is not"
         self.assertErrorRegex(EasyBuildError, error_pattern, self.modtool.check_loaded_modules)
 
         # check for warning message when purge is being run on loaded modules
diff --git a/test/framework/toy_build.py b/test/framework/toy_build.py
--- a/test/framework/toy_build.py
+++ b/test/framework/toy_build.py
@@ -1362,7 +1362,7 @@ def test_external_dependencies(self):
         write_file(toy_ec, ectxt + extraectxt)
 
         if isinstance(self.modtool, Lmod):
-            err_msg = r"Module command \\'module load nosuchbuilddep/0.0.0\\' failed"
+            err_msg = r"Module command \\'.*load nosuchbuilddep/0.0.0\\' failed"
         else:
             err_msg = r"Unable to locate a modulefile for 'nosuchbuilddep/0.0.0'"
 
@@ -1374,7 +1374,7 @@ def test_external_dependencies(self):
         write_file(toy_ec, ectxt + extraectxt)
 
         if isinstance(self.modtool, Lmod):
-            err_msg = r"Module command \\'module load nosuchmodule/1.2.3\\' failed"
+            err_msg = r"Module command \\'.*load nosuchmodule/1.2.3\\' failed"
         else:
             err_msg = r"Unable to locate a modulefile for 'nosuchmodule/1.2.3'"
 
diff --git a/test/framework/utilities.py b/test/framework/utilities.py
--- a/test/framework/utilities.py
+++ b/test/framework/utilities.py
@@ -125,9 +125,8 @@ def setUp(self):
         os.environ['EASYBUILD_ROBOT_PATHS'] = os.path.join(testdir, 'easyconfigs', 'test_ecs')
 
         # make sure no deprecated behaviour is being triggered (unless intended by the test)
-        # trip *all* log.deprecated statements by setting deprecation version ridiculously high
         self.orig_current_version = eb_build_log.CURRENT_VERSION
-        os.environ['EASYBUILD_DEPRECATED'] = '10000000'
+        self.disallow_deprecated_behaviour()
 
         init_config()
 
@@ -181,6 +180,11 @@ def setUp(self):
         self.reset_modulepath([os.path.join(testdir, 'modules')])
         reset_module_caches()
 
+    def disallow_deprecated_behaviour(self):
+        """trip *all* log.deprecated statements by setting deprecation version ridiculously high"""
+        os.environ['EASYBUILD_DEPRECATED'] = '10000000'
+        eb_build_log.CURRENT_VERSION = os.environ['EASYBUILD_DEPRECATED']
+
     def allow_deprecated_behaviour(self):
         """Restore EasyBuild version to what it was originally, to allow triggering deprecated behaviour."""
         if 'EASYBUILD_DEPRECATED' in os.environ: