Skip to content

Commit cbed99a

Browse files
committed
Add option to ignore failing test step
Users can now use --ignore-test-failure instead of --skip-test-step which will run the tests but does not abort on error and just prints it instead. Using both options doesn't make sense and is hence disallowed and a warning is show to make users move to ignoring failures instead of skipping the test completely.
1 parent 22b9603 commit cbed99a

File tree

6 files changed

+74
-1
lines changed

6 files changed

+74
-1
lines changed

easybuild/base/testing.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import pprint
3636
import re
3737
import sys
38+
from contextlib import contextmanager
3839

3940
try:
4041
from cStringIO import StringIO # Python 2
@@ -185,6 +186,26 @@ def get_stderr(self):
185186
"""Return output captured from stderr until now."""
186187
return sys.stderr.getvalue()
187188

189+
@contextmanager
190+
def mocked_stdout_stderr(self, mock_stdout=True, mock_stderr=True):
191+
"""Context manager to mock stdout and stderr"""
192+
if mock_stdout:
193+
self.mock_stdout(True)
194+
if mock_stderr:
195+
self.mock_stderr(True)
196+
try:
197+
if mock_stdout and mock_stderr:
198+
yield sys.stdout, sys.stderr
199+
elif mock_stdout:
200+
yield sys.stdout
201+
else:
202+
yield sys.stderr
203+
finally:
204+
if mock_stdout:
205+
self.mock_stdout(False)
206+
if mock_stderr:
207+
self.mock_stderr(False)
208+
188209
def tearDown(self):
189210
"""Cleanup after running a test."""
190211
self.mock_stdout(False)

easybuild/framework/easyblock.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,8 @@ def __init__(self, ec):
258258
if group_name is not None:
259259
self.group = use_group(group_name)
260260

261+
self.ignore_test_failure = build_option('ignore_test_failure')
262+
261263
# generate build/install directories
262264
self.gen_builddir()
263265
self.gen_installdir()
@@ -1814,6 +1816,16 @@ def remove_module_file(self):
18141816
self.log.info("Removing existing module file %s", self.mod_filepath)
18151817
remove_file(self.mod_filepath)
18161818

1819+
def report_test_failure(self, msgOrError):
1820+
"""Report a failing test either via an exception or warning depending on ignore-test-failure
1821+
1822+
msgOrError - Failure description. Either a string or an EasyBuildError"""
1823+
if self.ignore_test_failure:
1824+
print_warning("Test failure ignored: " + str(msgOrError), log=self.log)
1825+
else:
1826+
exception = msgOrError if isinstance(msgOrError, EasyBuildError) else EasyBuildError(msgOrError)
1827+
raise exception
1828+
18171829
#
18181830
# STEP FUNCTIONS
18191831
#
@@ -2229,6 +2241,13 @@ def test_step(self):
22292241

22302242
return out
22312243

2244+
def _test_step(self):
2245+
"""Run the test_step and handles failures"""
2246+
try:
2247+
self.test_step()
2248+
except EasyBuildError as e:
2249+
self.report_test_failure(e)
2250+
22322251
def stage_install_step(self):
22332252
"""
22342253
Install in a stage directory before actual installation.
@@ -3439,7 +3458,7 @@ def install_step_spec(initial):
34393458
prepare_step_spec = (PREPARE_STEP, 'preparing', [lambda x: x.prepare_step], False)
34403459
configure_step_spec = (CONFIGURE_STEP, 'configuring', [lambda x: x.configure_step], True)
34413460
build_step_spec = (BUILD_STEP, 'building', [lambda x: x.build_step], True)
3442-
test_step_spec = (TEST_STEP, 'testing', [lambda x: x.test_step], True)
3461+
test_step_spec = (TEST_STEP, 'testing', [lambda x: x._test_step], True)
34433462
extensions_step_spec = (EXTENSIONS_STEP, 'taking care of extensions', [lambda x: x.extensions_step], False)
34443463

34453464
# part 1: pre-iteration + first iteration

easybuild/main.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
from easybuild.framework.easyconfig.tools import det_easyconfig_paths, dump_env_script, get_paths_for
5656
from easybuild.framework.easyconfig.tools import parse_easyconfigs, review_pr, run_contrib_checks, skip_available
5757
from easybuild.framework.easyconfig.tweak import obtain_ec_for, tweak
58+
from easybuild.tools.build_log import print_warning
5859
from easybuild.tools.config import find_last_log, get_repository, get_repositorypath, build_option
5960
from easybuild.tools.containers.common import containerize
6061
from easybuild.tools.docs import list_software
@@ -304,6 +305,14 @@ def main(args=None, logfile=None, do_build=None, testing=False, modtool=None):
304305
init_session_state.update({'module_list': modlist})
305306
_log.debug("Initial session state: %s" % init_session_state)
306307

308+
if options.skip_test_step:
309+
if options.ignore_test_failure:
310+
raise EasyBuildError("Found both ignore-test-failure and skip-test-step being set. "
311+
"Please use only one of them.")
312+
else:
313+
print_warning("Will not run the test step as requested via skip-test-step. "
314+
"Consider using ignore-test-failure instead and verify the results afterwards")
315+
307316
# determine easybuild-easyconfigs package install path
308317
easyconfigs_pkg_paths = get_paths_for(subdir=EASYCONFIGS_PKG_SUBDIR)
309318
if not easyconfigs_pkg_paths:

easybuild/tools/config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,7 @@ def mk_full_default_path(name, prefix=DEFAULT_PREFIX):
248248
'ignore_checksums',
249249
'ignore_index',
250250
'ignore_locks',
251+
'ignore_test_failure',
251252
'install_latest_eb_release',
252253
'logtostdout',
253254
'minimal_toolchains',

easybuild/tools/options.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,7 @@ def override_options(self):
418418
"\"client[A-z0-9]*.example.com': ['Authorization: Basic token']\".",
419419
None, 'append', None, {'metavar': '[URLPAT::][HEADER:]FILE|FIELD'}),
420420
'ignore-checksums': ("Ignore failing checksum verification", None, 'store_true', False),
421+
'ignore-test-failure': ("Ignore a failing test step", None, 'store_true', False),
421422
'ignore-osdeps': ("Ignore any listed OS dependencies", None, 'store_true', False),
422423
'install-latest-eb-release': ("Install latest known version of easybuild", None, 'store_true', False),
423424
'lib-lib64-symlink': ("Automatically create symlinks for lib/ pointing to lib64/ if the former is missing",

test/framework/options.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,28 @@ def test_skip_test_step(self):
394394
found = re.search(test_run_msg, outtxt)
395395
self.assertFalse(found, "Test execution command is NOT present, outtxt: %s" % outtxt)
396396

397+
def test_ignore_test_failure(self):
398+
"""Test ignore failing tests (--ignore-test-failure)."""
399+
400+
topdir = os.path.abspath(os.path.dirname(__file__))
401+
toy_ec = os.path.join(topdir, 'easyconfigs', 'test_ecs', 't', 'toy', 'toy-0.0-test.eb')
402+
403+
args = [toy_ec, '--ignore-test-failure', '--force']
404+
405+
with self.mocked_stdout_stderr() as (_, stderr):
406+
outtxt = self.eb_main(args, do_build=True)
407+
408+
msg = 'Test failure ignored'
409+
self.assertTrue(re.search(msg, outtxt),
410+
"Ignored test failure message in log not found, outtxt: %s" % outtxt)
411+
self.assertTrue(re.search(msg, stderr.getvalue()),
412+
"Ignored test failure message in stderr not found, stderr: %s" % stderr.getvalue())
413+
414+
# Passing skip and ignore options is disallowed
415+
args.append('--skip-test-step')
416+
error_pattern = 'Found both ignore-test-failure and skip-test-step being set'
417+
self.assertErrorRegex(EasyBuildError, error_pattern, self.eb_main, args, do_build=True, raise_error=True)
418+
397419
def test_job(self):
398420
"""Test submitting build as a job."""
399421

0 commit comments

Comments
 (0)