Skip to content

Commit 2651466

Browse files
Merge branch 'master' into namd_perf_update
2 parents 52bf0ed + 0e010db commit 2651466

File tree

4 files changed

+57
-8
lines changed

4 files changed

+57
-8
lines changed

reframe/core/exceptions.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,12 @@ class PipelineError(ReframeError):
112112
'''
113113

114114

115+
class ReframeForceExitError(ReframeError):
116+
'''Raised when ReFrame execution must be forcefully ended,
117+
e.g., after a SIGTERM was received.
118+
'''
119+
120+
115121
class StatisticsError(ReframeError):
116122
'''Raised to denote an error in dealing with statistics.'''
117123

reframe/frontend/cli.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,11 @@
1919
import reframe.frontend.check_filters as filters
2020
import reframe.frontend.dependency as dependency
2121
import reframe.utility.os_ext as os_ext
22-
from reframe.core.exceptions import (EnvironError, ConfigError, ReframeError,
23-
ReframeFatalError, format_exception,
24-
SystemAutodetectionError)
22+
from reframe.core.exceptions import (
23+
ConfigError, EnvironError, ReframeError, ReframeFatalError,
24+
ReframeForceExitError, SystemAutodetectionError
25+
)
26+
from reframe.core.exceptions import format_exception
2527
from reframe.frontend.executors import Runner, generate_testcases
2628
from reframe.frontend.executors.policies import (SerialExecutionPolicy,
2729
AsynchronousExecutionPolicy)
@@ -637,7 +639,7 @@ def main():
637639

638640
sys.exit(0)
639641

640-
except KeyboardInterrupt:
642+
except (KeyboardInterrupt, ReframeForceExitError):
641643
sys.exit(1)
642644
except ReframeError as e:
643645
printer.error(str(e))

reframe/frontend/executors/__init__.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import abc
77
import copy
8+
import signal
89
import sys
910
import weakref
1011

@@ -14,11 +15,11 @@
1415
import reframe.core.runtime as runtime
1516
import reframe.frontend.dependency as dependency
1617
from reframe.core.exceptions import (AbortTaskError, JobNotStartedError,
17-
ReframeFatalError, TaskExit)
18+
ReframeForceExitError, TaskExit)
1819
from reframe.frontend.printer import PrettyPrinter
1920
from reframe.frontend.statistics import TestStats
2021

21-
ABORT_REASONS = (KeyboardInterrupt, ReframeFatalError, AssertionError)
22+
ABORT_REASONS = (KeyboardInterrupt, ReframeForceExitError, AssertionError)
2223

2324

2425
class TestCase:
@@ -256,6 +257,10 @@ def on_task_success(self, task):
256257
'''Called when a regression test has succeeded.'''
257258

258259

260+
def _handle_sigterm(signum, frame):
261+
raise ReframeForceExitError('received TERM signal')
262+
263+
259264
class Runner:
260265
'''Responsible for executing a set of regression tests based on an
261266
execution policy.'''
@@ -267,6 +272,7 @@ def __init__(self, policy, printer=None, max_retries=0):
267272
self._stats = TestStats()
268273
self._policy.stats = self._stats
269274
self._policy.printer = self._printer
275+
signal.signal(signal.SIGTERM, _handle_sigterm)
270276

271277
def __repr__(self):
272278
return debug.repr(self)
@@ -376,7 +382,6 @@ def __init__(self):
376382

377383
# Task event listeners
378384
self.task_listeners = []
379-
380385
self.stats = None
381386

382387
def __repr__(self):

unittests/test_policies.py

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
import collections
77
import itertools
88
import os
9+
import multiprocessing
910
import pytest
11+
import time
1012
import tempfile
1113
import unittest
1214

@@ -19,7 +21,8 @@
1921
import reframe.utility.os_ext as os_ext
2022
from reframe.core.environments import Environment
2123
from reframe.core.exceptions import (
22-
DependencyError, JobNotStartedError, TaskDependencyError
24+
DependencyError, JobNotStartedError,
25+
ReframeForceExitError, TaskDependencyError
2326
)
2427
from reframe.frontend.loader import RegressionCheckLoader
2528
import unittests.fixtures as fixtures
@@ -257,6 +260,39 @@ def test_dependencies(self):
257260
if t.ref_count == 0:
258261
assert os.path.exists(os.path.join(check.outputdir, 'out.txt'))
259262

263+
def test_sigterm(self):
264+
# Wrapper of self.runall which is used from a child process and
265+
# passes any exception to the parent process
266+
def _runall(checks, conn):
267+
exc = None
268+
try:
269+
self.runall(checks)
270+
except BaseException as e:
271+
exc = e
272+
273+
conn.send((exc, len(self.runner.stats.failures())))
274+
conn.close()
275+
276+
rd_endpoint, wr_endpoint = multiprocessing.Pipe(duplex=False)
277+
p = multiprocessing.Process(target=_runall,
278+
args=([SleepCheck(3)], wr_endpoint))
279+
p.start()
280+
281+
# The unused write endpoint has to be closed from the parent to
282+
# ensure that the `recv()` method of `rd_endpoint` returns
283+
wr_endpoint.close()
284+
285+
# Allow some time so that the SleepCheck is submitted
286+
time.sleep(1)
287+
p.terminate()
288+
p.join()
289+
exc, num_failures = rd_endpoint.recv()
290+
assert 1 == num_failures
291+
with pytest.raises(ReframeForceExitError,
292+
match='received TERM signal'):
293+
if exc:
294+
raise exc
295+
260296
def test_dependencies_with_retries(self):
261297
self.runner._max_retries = 2
262298
self.test_dependencies()

0 commit comments

Comments
 (0)