Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 5 additions & 38 deletions easybuild/base/testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,12 @@
import difflib
import os
import pprint
import re
import sys
from contextlib import contextmanager
from io import StringIO

from unittest import TestCase as OrigTestCase
from easybuild.base import fancylogger


def nicediff(txta, txtb, offset=5):
Expand All @@ -66,7 +66,7 @@ def nicediff(txta, txtb, offset=5):


class TestCase(OrigTestCase):
"""Enhanced test case, provides extra functionality (e.g. an assertErrorRegex method)."""
"""Enhanced test case, provides extra functionality."""

longMessage = True # print both standard messgae and custom message

Expand Down Expand Up @@ -140,42 +140,9 @@ def setUp(self):
self.orig_sys_stdout = sys.stdout
self.orig_sys_stderr = sys.stderr

def convert_exception_to_str(self, err):
"""Convert an Exception instance to a string."""
msg = err
if hasattr(err, 'msg'):
msg = err.msg
elif hasattr(err, 'message'):
msg = err.message
if not msg:
# rely on str(msg) in case err.message is empty
msg = err
elif hasattr(err, 'args'): # KeyError in Python 2.4 only provides message via 'args' attribute
msg = err.args[0]
else:
msg = err
try:
res = str(msg)
except UnicodeEncodeError:
res = msg.encode('utf8', 'replace')

return res

def assertErrorRegex(self, error, regex, call, *args, **kwargs):
"""
Convenience method to match regex with the expected error message.
Example: self.assertErrorRegex(OSError, "No such file or directory", os.remove, '/no/such/file')
"""
try:
call(*args, **kwargs)
str_kwargs = ['='.join([k, str(v)]) for (k, v) in kwargs.items()]
str_args = ', '.join(list(map(str, args)) + str_kwargs)
self.fail("Expected errors with %s(%s) call should occur" % (call.__name__, str_args))
except error as err:
msg = self.convert_exception_to_str(err)
if isinstance(regex, str):
regex = re.compile(regex)
self.assertTrue(regex.search(msg), "Pattern '%s' is found in '%s'" % (regex.pattern, msg))
def assertErrorRegex(*args, **kwargs):
fancylogger.getLogger().deprecated('assertErrorRegex is deprecated, use assertRaisesRegex instead', '6.0')
return OrigTestCase.assertRaisesRegex(*args, **kwargs)

def mock_stdout(self, enable):
"""Enable/disable mocking stdout."""
Expand Down
2 changes: 1 addition & 1 deletion easybuild/tools/build_log.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ def __init__(self, msg, *args, exit_code=EasyBuildExit.ERROR, **kwargs):

def __str__(self):
"""Return string representation of this EasyBuildError instance."""
return repr(self.msg)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This includes the changes from #5009 ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed. Otherwise tests fail due to escaping as we had special handling for that, see convert_exception_to_str

return self.msg


def raise_easybuilderror(msg, *args):
Expand Down
30 changes: 15 additions & 15 deletions test/framework/build_log.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,19 +66,19 @@ def test_easybuilderror(self):

# if no logger is available, and no logger is specified, use default 'root' fancylogger
logToFile(tmplog, enable=True)
self.assertErrorRegex(EasyBuildError, 'BOOM', raise_easybuilderror, 'BOOM')
self.assertRaisesRegex(EasyBuildError, 'BOOM', raise_easybuilderror, 'BOOM')
logToFile(tmplog, enable=False)

log_re = re.compile(r"^fancyroot ::.* BOOM \(at .*:[0-9]+ in [a-z_]+\)$", re.M)
logtxt = read_file(tmplog, 'r')
self.assertTrue(log_re.match(logtxt), "%s matches %s" % (log_re.pattern, logtxt))

# test formatting of message
self.assertErrorRegex(EasyBuildError, 'BOOMBAF', raise_easybuilderror, 'BOOM%s', 'BAF')
self.assertRaisesRegex(EasyBuildError, 'BOOMBAF', raise_easybuilderror, 'BOOM%s', 'BAF')

# a '%s' in a value used to template the error message should not print a traceback!
self.mock_stderr(True)
self.assertErrorRegex(EasyBuildError, 'err: msg: %s', raise_easybuilderror, "err: %s", "msg: %s")
self.assertRaisesRegex(EasyBuildError, 'err: msg: %s', raise_easybuilderror, "err: %s", "msg: %s")
stderr = self.get_stderr()
self.mock_stderr(False)
# stderr should be *empty* (there should definitely not be a traceback)
Expand Down Expand Up @@ -148,10 +148,10 @@ def test_easybuildlog(self):
logtxt_regex = re.compile(r'^%s' % expected_logtxt, re.M)
self.assertTrue(logtxt_regex.search(logtxt), "Pattern '%s' found in %s" % (logtxt_regex.pattern, logtxt))

self.assertErrorRegex(EasyBuildError, r"DEPRECATED \(since .*: kaput", log.deprecated, "kaput", older_ver)
self.assertErrorRegex(EasyBuildError, r"DEPRECATED \(since .*: 2>1", log.deprecated, "2>1", '2.0', '1.0')
self.assertErrorRegex(EasyBuildError, r"DEPRECATED \(since .*: 2>1", log.deprecated, "2>1", '2.0',
max_ver='1.0')
self.assertRaisesRegex(EasyBuildError, r"DEPRECATED \(since .*: kaput", log.deprecated, "kaput", older_ver)
self.assertRaisesRegex(EasyBuildError, r"DEPRECATED \(since .*: 2>1", log.deprecated, "2>1", '2.0', '1.0')
self.assertRaisesRegex(EasyBuildError, r"DEPRECATED \(since .*: 2>1", log.deprecated, "2>1", '2.0',
max_ver='1.0')

# wipe log so we can reuse it
write_file(tmplog, '')
Expand Down Expand Up @@ -257,7 +257,7 @@ def run_check(args, silent=False, expected_stderr='', **kwargs):
run_check(['You have been %s.', 'warned'], silent=True)
run_check(['You %s %s %s.', 'have', 'been', 'warned'], silent=True)

self.assertErrorRegex(EasyBuildError, "Unknown named arguments", print_warning, 'foo', unknown_arg='bar')
self.assertRaisesRegex(EasyBuildError, "Unknown named arguments", print_warning, 'foo', unknown_arg='bar')

# test passing of logger to print_warning
tmp_logfile = os.path.join(self.test_prefix, 'test.log')
Expand All @@ -273,7 +273,7 @@ def run_check(args, silent=False, expected_stderr=''):
"""Helper function to check stdout/stderr produced via print_error."""
self.mock_stderr(True)
self.mock_stdout(True)
self.assertErrorRegex(SystemExit, '1', print_error, *args, silent=silent)
self.assertRaisesRegex(SystemExit, '1', print_error, *args, silent=silent)
stderr = self.get_stderr()
stdout = self.get_stdout()
self.mock_stdout(False)
Expand All @@ -288,7 +288,7 @@ def run_check(args, silent=False, expected_stderr=''):
run_check(['You have %s.', 'failed'], silent=True)
run_check(['%s %s %s.', 'You', 'have', 'failed'], silent=True)

self.assertErrorRegex(EasyBuildError, "Unknown named arguments", print_error, 'foo', unknown_arg='bar')
self.assertRaisesRegex(EasyBuildError, "Unknown named arguments", print_error, 'foo', unknown_arg='bar')

def test_print_msg(self):
"""Test print_msg"""
Expand Down Expand Up @@ -321,7 +321,7 @@ def run_check(msg, args, expected_stdout='', expected_stderr='', **kwargs):
run_check("testing, 1, 2, 3", [], silent=True, stderr=True)
run_check("testing, %s, %s, 3", ['1', '2'], silent=True, stderr=True)

self.assertErrorRegex(EasyBuildError, "Unknown named arguments", print_msg, 'foo', unknown_arg='bar')
self.assertRaisesRegex(EasyBuildError, "Unknown named arguments", print_msg, 'foo', unknown_arg='bar')

def test_time_str_since(self):
"""Test time_str_since"""
Expand Down Expand Up @@ -356,7 +356,7 @@ def run_check(msg, args, expected_stdout='', **kwargs):
run_check("test 123", [], silent=True)
run_check("test %s", ['123'], silent=True)

self.assertErrorRegex(EasyBuildError, "Unknown named arguments", dry_run_msg, 'foo', unknown_arg='bar')
self.assertRaisesRegex(EasyBuildError, "Unknown named arguments", dry_run_msg, 'foo', unknown_arg='bar')

def test_dry_run_warning(self):
"""Test dry_run_warningmsg"""
Expand All @@ -377,7 +377,7 @@ def run_check(msg, args, expected_stdout='', **kwargs):
run_check("test 123", [], silent=True)
run_check("test %s", ['123'], silent=True)

self.assertErrorRegex(EasyBuildError, "Unknown named arguments", dry_run_warning, 'foo', unknown_arg='bar')
self.assertRaisesRegex(EasyBuildError, "Unknown named arguments", dry_run_warning, 'foo', unknown_arg='bar')

def test_init_logging(self):
"""Test init_logging function."""
Expand Down Expand Up @@ -434,8 +434,8 @@ def test_init_logging(self):
stop_logging(logfile, logtostdout=True)

def test_raise_nosupport(self):
self.assertErrorRegex(EasyBuildError, 'NO LONGER SUPPORTED since v42: foobar;',
raise_nosupport, 'foobar', 42)
self.assertRaisesRegex(EasyBuildError, 'NO LONGER SUPPORTED since v42: foobar;',
raise_nosupport, 'foobar', 42)


def suite(loader=None):
Expand Down
16 changes: 8 additions & 8 deletions test/framework/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ def test_generaloption_config(self):
installpath_software = tempfile.mkdtemp(prefix='installpath-software')
os.environ['EASYBUILD_SUBDIR_SOFTWARE'] = installpath_software
error_regex = r"Found problems validating the options.*'subdir_software' must specify a \*relative\* path"
self.assertErrorRegex(EasyBuildError, error_regex, init_config)
self.assertRaisesRegex(EasyBuildError, error_regex, init_config)

del os.environ['EASYBUILD_PREFIX']
del os.environ['EASYBUILD_SUBDIR_SOFTWARE']
Expand All @@ -192,7 +192,7 @@ def test_error_env_var_typo(self):
error = r"Found 2 environment variable\(s\) that are prefixed with %s " % CONFIG_ENV_VAR_PREFIX
error += r"but do not match valid option\(s\): "
error += r','.join(['EASYBUILD_FOO', 'EASYBUILD_THERESNOSUCHCONFIGURATIONOPTION'])
self.assertErrorRegex(EasyBuildError, error, init_config)
self.assertRaisesRegex(EasyBuildError, error, init_config)

del os.environ['EASYBUILD_THERESNOSUCHCONFIGURATIONOPTION']
del os.environ['EASYBUILD_FOO']
Expand All @@ -205,7 +205,7 @@ def test_install_path(self):
self.assertEqual(install_path(typ='mod'), os.path.join(self.test_installpath, 'modules'))
self.assertEqual(install_path('modules'), os.path.join(self.test_installpath, 'modules'))

self.assertErrorRegex(EasyBuildError, "Unknown type specified", install_path, typ='foo')
self.assertRaisesRegex(EasyBuildError, "Unknown type specified", install_path, typ='foo')

args = [
'--subdir-software', 'SOFT',
Expand Down Expand Up @@ -365,16 +365,16 @@ def test_build_options(self):
self.assertTrue(bo['force'])

# updating is impossible (methods are not even available)
self.assertErrorRegex(Exception, '.*(item assignment|no attribute).*', lambda x: bo.update(x), {'debug': True})
self.assertErrorRegex(AttributeError, '.*no attribute.*', lambda x: bo.__setitem__(*x), ('debug', True))
self.assertRaisesRegex(Exception, '.*(item assignment|no attribute).*', lambda x: bo.update(x), {'debug': True})
self.assertRaisesRegex(AttributeError, '.*no attribute.*', lambda x: bo.__setitem__(*x), ('debug', True))

# only valid keys can be set
BuildOptions.__class__._instances.clear()
msg = r"Encountered unknown keys .* \(known keys: .*"
self.assertErrorRegex(KeyError, msg, BuildOptions, {'thisisclearlynotavalidbuildoption': 'FAIL'})
self.assertRaisesRegex(KeyError, msg, BuildOptions, {'thisisclearlynotavalidbuildoption': 'FAIL'})

# test init_build_options and build_option functions
self.assertErrorRegex(KeyError, msg, init_build_options, {'thisisclearlynotavalidbuildoption': 'FAIL'})
self.assertRaisesRegex(KeyError, msg, init_build_options, {'thisisclearlynotavalidbuildoption': 'FAIL'})
bo = init_build_options({
'robot_path': '/some/robot/path',
'stop': 'configure',
Expand Down Expand Up @@ -656,7 +656,7 @@ def test_log_file_format(self):
# test handling of incorrect setting for --logfile-format
init_config(args=['--logfile-format=easybuild,log.txt,thisiswrong'])
error_pattern = "Incorrect log file format specification, should be 2-tuple"
self.assertErrorRegex(EasyBuildError, error_pattern, log_file_format)
self.assertRaisesRegex(EasyBuildError, error_pattern, log_file_format)

def test_log_path(self):
"""Test for log_path()."""
Expand Down
36 changes: 18 additions & 18 deletions test/framework/containers.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,16 +110,16 @@ def test_end2end_singularity_recipe_config(self):

args.extend(['--container-config', 'osversion=7.6.1810'])
error_pattern = r"Keyword 'bootstrap' is required in container base config"
self.assertErrorRegex(EasyBuildError, error_pattern, self.run_main, args, raise_error=True)
self.assertRaisesRegex(EasyBuildError, error_pattern, self.run_main, args, raise_error=True)

args.extend(['--container-config', 'bootstrap=foobar'])
error_pattern = r"Unknown value specified for 'bootstrap' keyword: foobar \(known: arch, busybox, debootstrap, "
self.assertErrorRegex(EasyBuildError, error_pattern, self.run_main, args, raise_error=True)
self.assertRaisesRegex(EasyBuildError, error_pattern, self.run_main, args, raise_error=True)

# default mirror URL for yum bootstrap agent uses ${OSVERSION}, so 'osversion' must be specified too
args.extend(['--container-config', 'bootstrap=yum'])
error_pattern = "Keyword 'osversion' is required in container base config when '%{OSVERSION}' is used"
self.assertErrorRegex(EasyBuildError, error_pattern, self.run_main, args, raise_error=True)
self.assertRaisesRegex(EasyBuildError, error_pattern, self.run_main, args, raise_error=True)

args[-1] = 'bootstrap=yum,osversion=7.6.1810'
stdout, stderr = self.run_main(args, raise_error=True)
Expand Down Expand Up @@ -197,7 +197,7 @@ def test_end2end_singularity_recipe_config(self):
error_pattern = "Keyword 'from' is required in container base config when using bootstrap agent"
for (bootstrap, from_spec) in test_cases:
args[-1] = 'bootstrap=%s' % bootstrap
self.assertErrorRegex(EasyBuildError, error_pattern, self.run_main, args, raise_error=True)
self.assertRaisesRegex(EasyBuildError, error_pattern, self.run_main, args, raise_error=True)

args[-1] += ',from=%s' % from_spec
remove_file(test_container_recipe)
Expand Down Expand Up @@ -281,7 +281,7 @@ def test_end2end_singularity_image(self):

if which('singularity') is None:
error_pattern = "singularity with version 2.4 or higher not found on your system."
self.assertErrorRegex(EasyBuildError, error_pattern, self.eb_main, args, raise_error=True)
self.assertRaisesRegex(EasyBuildError, error_pattern, self.eb_main, args, raise_error=True)

# install mocked versions of 'sudo' and 'singularity' commands
singularity = os.path.join(self.test_prefix, 'bin', 'singularity')
Expand Down Expand Up @@ -338,7 +338,7 @@ def test_end2end_singularity_image(self):

error_pattern = "Container image already exists at %s, not overwriting it without --force" % cont_img
self.mock_stdout(True)
self.assertErrorRegex(EasyBuildError, error_pattern, self.run_main, args, raise_error=True)
self.assertRaisesRegex(EasyBuildError, error_pattern, self.run_main, args, raise_error=True)
self.mock_stdout(False)

args.append('--force')
Expand Down Expand Up @@ -380,11 +380,11 @@ def test_end2end_dockerfile(self):
]

error_pattern = "Unsupported container config 'not-supported'"
self.assertErrorRegex(EasyBuildError,
error_pattern,
self.run_main,
base_args + ['--container-config=not-supported'],
raise_error=True)
self.assertRaisesRegex(EasyBuildError,
error_pattern,
self.run_main,
base_args + ['--container-config=not-supported'],
raise_error=True)

for cont_base in ['ubuntu:20.04', 'centos:7']:
stdout, stderr = self.run_main(base_args + ['--container-config=%s' % cont_base])
Expand All @@ -397,11 +397,11 @@ def test_end2end_dockerfile(self):

error_pattern = "Container recipe at %s/containers/Dockerfile.toy-0.0 already exists, " \
"not overwriting it without --force" % self.test_prefix
self.assertErrorRegex(EasyBuildError,
error_pattern,
self.run_main,
base_args + ['--container-config=centos:7'],
raise_error=True)
self.assertRaisesRegex(EasyBuildError,
error_pattern,
self.run_main,
base_args + ['--container-config=centos:7'],
raise_error=True)

remove_file(os.path.join(self.test_prefix, 'containers', 'Dockerfile.toy-0.0'))

Expand Down Expand Up @@ -441,7 +441,7 @@ def test_end2end_docker_image(self):

if not which('docker'):
error_pattern = "docker not found on your system."
self.assertErrorRegex(EasyBuildError, error_pattern, self.run_main, args, raise_error=True)
self.assertRaisesRegex(EasyBuildError, error_pattern, self.run_main, args, raise_error=True)

# install mocked versions of 'sudo' and 'docker' commands
docker = os.path.join(self.test_prefix, 'bin', 'docker')
Expand Down Expand Up @@ -488,7 +488,7 @@ def test_container_config_template_recipe(self):
'toy-0.0.eb',
]
error_pattern = "--container-config must be specified!"
self.assertErrorRegex(EasyBuildError, error_pattern, self.run_main, args)
self.assertRaisesRegex(EasyBuildError, error_pattern, self.run_main, args)

args.extend(['--container-config', 'bootstrap=localimage,from=foobar'])
stdout, stderr = self.run_main(args)
Expand Down
8 changes: 4 additions & 4 deletions test/framework/docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -1288,8 +1288,8 @@ def test_mk_table(self):
res = mk_rst_table(titles, table)
self.assertEqual(res, expected_rst)

self.assertErrorRegex(ValueError, "Number of titles/columns should be equal", mk_md_table, titles, [])
self.assertErrorRegex(ValueError, "Number of titles/columns should be equal", mk_rst_table, titles, [])
self.assertRaisesRegex(ValueError, "Number of titles/columns should be equal", mk_md_table, titles, [])
self.assertRaisesRegex(ValueError, "Number of titles/columns should be equal", mk_rst_table, titles, [])

def test_title_and_table(self):
"""
Expand Down Expand Up @@ -1329,8 +1329,8 @@ def test_title_and_table(self):
self.assertEqual(res, expected_rst)

error_pattern = "Number of titles/columns should be equal"
self.assertErrorRegex(ValueError, error_pattern, md_title_and_table, '', titles, [])
self.assertErrorRegex(ValueError, error_pattern, rst_title_and_table, '', titles, [('val 11', 'val 12')])
self.assertRaisesRegex(ValueError, error_pattern, md_title_and_table, '', titles, [])
self.assertRaisesRegex(ValueError, error_pattern, rst_title_and_table, '', titles, [('val 11', 'val 12')])

def test_help(self):
"""
Expand Down
Loading