Skip to content

Commit 7fc6a06

Browse files
authored
Merge branch 'master' into intel_vtune
2 parents aedb7f2 + 70d3dff commit 7fc6a06

File tree

8 files changed

+98
-88
lines changed

8 files changed

+98
-88
lines changed

reframe/core/exceptions.py

Lines changed: 61 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -8,34 +8,40 @@
88
import warnings
99
import sys
1010

11+
import reframe.utility as utility
1112

12-
class ReframeError(Exception):
13-
"""Base exception for soft errors.
1413

15-
Soft errors may be treated by simply printing the exception's message and
16-
trying to continue execution if possible.
17-
"""
14+
class ReframeBaseError(BaseException):
15+
"""Base exception for any ReFrame error."""
16+
17+
def __init__(self, *args):
18+
self._message = str(args[0]) if args else None
19+
20+
@property
21+
def message(self):
22+
return self._message
1823

1924
def __str__(self):
20-
ret = super().__str__()
25+
ret = self._message or ''
2126
if self.__cause__ is not None:
2227
ret += ': ' + str(self.__cause__)
2328

2429
return ret
2530

2631

27-
class ReframeFatalError(BaseException):
28-
"""A fatal framework error.
32+
class ReframeError(ReframeBaseError, Exception):
33+
"""Base exception for soft errors.
2934
30-
Execution must be aborted.
35+
Soft errors may be treated by simply printing the exception's message and
36+
trying to continue execution if possible.
3137
"""
3238

33-
def __str__(self):
34-
ret = super().__str__()
35-
if self.__cause__ is not None:
36-
ret += ': ' + str(self.__cause__)
3739

38-
return ret
40+
class ReframeFatalError(ReframeBaseError):
41+
"""A fatal framework error.
42+
43+
Execution must be aborted.
44+
"""
3945

4046

4147
class ReframeSyntaxError(ReframeError):
@@ -81,7 +87,11 @@ class EnvironError(ReframeError):
8187

8288

8389
class SanityError(ReframeError):
84-
"""Raised to denote an error in sanity or performance checking."""
90+
"""Raised to denote an error in sanity checking."""
91+
92+
93+
class PerformanceError(ReframeError):
94+
"""Raised to denote an error in performance checking."""
8595

8696

8797
class PipelineError(ReframeError):
@@ -102,37 +112,36 @@ class BuildError(ReframeError):
102112
"""Raised when a build fails."""
103113

104114
def __init__(self, stdout, stderr):
105-
self._stdout = stdout
106-
self._stderr = stderr
107-
108-
def __str__(self):
109-
return ("standard error can be found in `%s', "
110-
"standard output can be found in `%s'" % (self._stderr,
111-
self._stdout))
115+
super().__init__()
116+
self._message = (
117+
"standard error can be found in `%s', "
118+
"standard output can be found in `%s'" % (stdout, stderr)
119+
)
112120

113121

114122
class SpawnedProcessError(ReframeError):
115123
"""Raised when a spawned OS command has failed."""
116124

117125
def __init__(self, command, stdout, stderr, exitcode):
118-
super().__init__(command, stdout, stderr, exitcode)
119-
self._command = command
120-
self._stdout = stdout
121-
self._stderr = stderr
122-
self._exitcode = exitcode
126+
super().__init__()
123127

124-
def __str__(self):
125-
lines = ["command '{0}' failed with exit code {1}:".format(
126-
self._command, self._exitcode)]
128+
# Format message and put it in args
129+
lines = [
130+
"command '%s' failed with exit code %s:" % (command, exitcode)
131+
]
127132
lines.append('=== STDOUT ===')
128-
if self._stdout:
129-
lines.append(self._stdout)
133+
if stdout:
134+
lines.append(stdout)
130135

131136
lines.append('=== STDERR ===')
132-
if self._stderr:
133-
lines.append(self._stderr)
137+
if stderr:
138+
lines.append(stderr)
134139

135-
return '\n'.join(lines)
140+
self._message = '\n'.join(lines)
141+
self._command = command
142+
self._stdout = stdout
143+
self._stderr = stderr
144+
self._exitcode = exitcode
136145

137146
@property
138147
def command(self):
@@ -156,14 +165,8 @@ class SpawnedProcessTimeout(SpawnedProcessError):
156165

157166
def __init__(self, command, stdout, stderr, timeout):
158167
super().__init__(command, stdout, stderr, None)
159-
self._timeout = timeout
160-
161-
# Reset the args to match the real ones passed to this exception
162-
self.args = command, stdout, stderr, timeout
163168

164-
def __str__(self):
165-
lines = ["command '{0}' timed out after {1}s:".format(self._command,
166-
self._timeout)]
169+
lines = ["command '%s' timed out after %ss:" % (command, timeout)]
167170
lines.append('=== STDOUT ===')
168171
if self._stdout:
169172
lines.append(self._stdout)
@@ -172,7 +175,8 @@ def __str__(self):
172175
if self._stderr:
173176
lines.append(self._stderr)
174177

175-
return '\n'.join(lines)
178+
self._message = '\n'.join(lines)
179+
self._timeout = timeout
176180

177181
@property
178182
def timeout(self):
@@ -182,22 +186,18 @@ def timeout(self):
182186
class JobError(ReframeError):
183187
"""Job related errors."""
184188

185-
def __init__(self, *args, jobid=None):
186-
super().__init__(*args)
189+
def __init__(self, msg=None, jobid=None):
190+
message = '[jobid=%s]' % jobid
191+
if msg:
192+
message += ' ' + msg
193+
194+
super().__init__(message)
187195
self._jobid = jobid
188196

189197
@property
190198
def jobid(self):
191199
return self._jobid
192200

193-
def __str__(self):
194-
prefix = '(jobid=%s)' % self._jobid
195-
msg = super().__str__()
196-
if self.args:
197-
return prefix + ' ' + msg
198-
else:
199-
return prefix + msg
200-
201201

202202
class JobBlockedError(JobError):
203203
"""Raised by job schedulers when a job is blocked indefinitely."""
@@ -235,21 +235,18 @@ def format_user_frame(frame):
235235
if exc_type is None:
236236
return ''
237237

238-
if isinstance(exc_value, SanityError):
239-
return 'sanity error: %s' % exc_value
240-
241-
if isinstance(exc_value, ReframeFatalError):
242-
exc_str = ''.join(traceback.format_exception(exc_type, exc_value, tb))
243-
return 'fatal error: %s\n%s' % (exc_value, exc_str)
244-
245238
if isinstance(exc_value, AbortTaskError):
246239
return 'aborted due to %s' % type(exc_value.__cause__).__name__
247240

248-
if isinstance(exc_value, BuildError):
249-
return 'build failure: %s' % exc_value
250-
251241
if isinstance(exc_value, ReframeError):
252-
return 'caught framework exception: %s' % exc_value
242+
return '%s: %s' % (utility.decamelize(exc_type.__name__, ' '),
243+
exc_value)
244+
245+
if isinstance(exc_value, ReframeFatalError):
246+
exc_str = '%s: %s' % (utility.decamelize(exc_type.__name__, ' '),
247+
exc_value)
248+
tb_str = ''.join(traceback.format_exception(exc_type, exc_value, tb))
249+
return '%s\n%s' % (exc_str, tb_str)
253250

254251
if isinstance(exc_value, KeyboardInterrupt):
255252
return 'cancelled by user'

reframe/core/pipeline.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@
2323
from reframe.core.buildsystems import BuildSystem, BuildSystemField
2424
from reframe.core.deferrable import deferrable, _DeferredExpression, evaluate
2525
from reframe.core.environments import Environment, EnvironmentSnapshot
26-
from reframe.core.exceptions import BuildError, PipelineError, SanityError
26+
from reframe.core.exceptions import (BuildError, PipelineError, SanityError,
27+
PerformanceError)
2728
from reframe.core.launchers.registry import getlauncher
2829
from reframe.core.schedulers import Job
2930
from reframe.core.schedulers.registry import getscheduler
@@ -1075,7 +1076,7 @@ def sanity(self):
10751076
def performance(self):
10761077
try:
10771078
self.check_performance()
1078-
except SanityError:
1079+
except PerformanceError:
10791080
if self.strict_check:
10801081
raise
10811082

@@ -1090,7 +1091,7 @@ def check_sanity(self):
10901091
with os_ext.change_dir(self._stagedir):
10911092
success = evaluate(self.sanity_patterns)
10921093
if not success:
1093-
raise SanityError('sanity failure')
1094+
raise SanityError()
10941095

10951096
def check_performance(self):
10961097
"""The performance checking phase of the regression test pipeline.
@@ -1120,7 +1121,10 @@ def check_performance(self):
11201121

11211122
for val, reference in perf_values:
11221123
ref, low_thres, high_thres, *_ = reference
1123-
evaluate(assert_reference(val, ref, low_thres, high_thres))
1124+
try:
1125+
evaluate(assert_reference(val, ref, low_thres, high_thres))
1126+
except SanityError as e:
1127+
raise PerformanceError(e)
11241128

11251129
def _copy_job_files(self, job, dst):
11261130
if job is None:

reframe/core/schedulers/slurm.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -240,14 +240,17 @@ def _get_reservation_nodes(self, reservation):
240240
return {SlurmNode(descr) for descr in node_descriptions}
241241

242242
def _get_nodes_by_name(self, nodespec):
243-
try:
244-
completed = _run_strict('scontrol -a show -o node %s' % nodespec)
245-
except SpawnedProcessError as e:
246-
raise JobError('could not retrieve the node description '
247-
'of nodes: %s' % nodespec) from e
248-
243+
completed = os_ext.run_command('scontrol -a show -o node %s' %
244+
nodespec)
249245
node_descriptions = completed.stdout.splitlines()
250-
return {SlurmNode(descr) for descr in node_descriptions}
246+
nodes_avail = set()
247+
for descr in node_descriptions:
248+
try:
249+
nodes_avail.add(SlurmNode(descr))
250+
except JobError:
251+
pass
252+
253+
return nodes_avail
251254

252255
def _set_nodelist(self, nodespec):
253256
if self._nodelist is not None:

reframe/utility/__init__.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,10 +83,11 @@ def allx(iterable):
8383
return all(iterable) if iterable else False
8484

8585

86-
def decamelize(s):
86+
def decamelize(s, delim='_'):
8787
"""Decamelize the string ``s``.
8888
8989
For example, ``MyBaseClass`` will be converted to ``my_base_class``.
90+
The delimiter may be changed by setting the ``delim`` argument.
9091
"""
9192

9293
if not isinstance(s, str):
@@ -95,7 +96,7 @@ def decamelize(s):
9596
if not s:
9697
return ''
9798

98-
return re.sub(r'([a-z])([A-Z])', r'\1_\2', s).lower()
99+
return re.sub(r'([a-z])([A-Z])', r'\1%s\2' % delim, s).lower()
99100

100101

101102
def toalphanum(s):

reframe/utility/os_ext.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -176,13 +176,17 @@ def ignore(dir, contents):
176176
def rmtree(*args, max_retries=3, **kwargs):
177177
"""Persistent version of ``shutil.rmtree()``.
178178
179-
If ``shutil.rmtree()`` with ``ENOTEMPTY``, retry up to ``max_retries``
180-
times to delete the directory.
179+
If ``shutil.rmtree()`` fails with ``ENOTEMPTY`` or ``EBUSY``, retry up to
180+
``max_retries`times to delete the directory.
181181
182182
This version of ``rmtree()`` is mostly provided to work around a race
183183
condition between when ``sacct`` reports a job as completed and when the
184184
Slurm epilog runs. See https://github.com/eth-cscs/reframe/issues/291 for
185185
more information.
186+
Furthermore, it offers a work around for nfs file systems where a ``.nfs*``
187+
file may be present during the ``rmtree()`` call which throws a busy
188+
device/resource error. See https://github.com/eth-cscs/reframe/issues/712
189+
for more information.
186190
187191
``args`` and ``kwargs`` are passed through to ``shutil.rmtree()``.
188192
@@ -200,7 +204,7 @@ def rmtree(*args, max_retries=3, **kwargs):
200204
except OSError as e:
201205
if i == max_retries:
202206
raise
203-
elif e.errno == errno.ENOTEMPTY:
207+
elif e.errno in {errno.ENOTEMPTY, errno.EBUSY}:
204208
pass
205209
else:
206210
raise

unittests/resources/checks/frontend_checks.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
import reframe as rfm
66
import reframe.utility.sanity as sn
7-
from reframe.core.exceptions import ReframeError, SanityError
7+
from reframe.core.exceptions import ReframeError, PerformanceError
88

99

1010
class BaseFrontendCheck(rfm.RunOnlyRegressionTest):
@@ -91,7 +91,7 @@ def __init__(self):
9191
self.strict_check = False
9292

9393
def check_performance(self):
94-
raise SanityError('performance failure')
94+
raise PerformanceError('performance failure')
9595

9696

9797
class KeyboardInterruptCheck(BaseFrontendCheck):

unittests/test_exceptions.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ def test_job_error(self):
114114
exc_args = ('some error',)
115115
e = exc.JobError(*exc_args, jobid=1234)
116116
self.assertEqual(1234, e.jobid)
117-
self.assertRaisesRegex(exc.JobError, r'\(jobid=1234\) some error',
117+
self.assertRaisesRegex(exc.JobError, r'\[jobid=1234\] some error',
118118
raise_exc, e)
119119
self.assertEqual(exc_args, e.args)
120120

@@ -125,7 +125,7 @@ def test_reraise_job_error(self):
125125
except ValueError as e:
126126
raise exc.JobError('some error', jobid=1234) from e
127127
except exc.JobError as e:
128-
self.assertEqual('(jobid=1234) some error: random value error',
128+
self.assertEqual('[jobid=1234] some error: random value error',
129129
str(e))
130130

131131
def test_reraise_job_error_no_message(self):
@@ -135,5 +135,5 @@ def test_reraise_job_error_no_message(self):
135135
except ValueError as e:
136136
raise exc.JobError(jobid=1234) from e
137137
except exc.JobError as e:
138-
self.assertEqual('(jobid=1234): random value error',
138+
self.assertEqual('[jobid=1234]: random value error',
139139
str(e))

unittests/test_pipeline.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
import reframe.utility.sanity as sn
88
import unittests.fixtures as fixtures
99
from reframe.core.exceptions import (BuildError, PipelineError, ReframeError,
10-
ReframeSyntaxError, SanityError)
10+
ReframeSyntaxError, PerformanceError,
11+
SanityError)
1112
from reframe.core.pipeline import (CompileOnlyRegressionTest, RegressionTest,
1213
RunOnlyRegressionTest)
1314
from reframe.frontend.loader import RegressionCheckLoader
@@ -551,7 +552,7 @@ def test_performance_failure(self):
551552
self.output_file.write('result = success\n')
552553
self.output_file.close()
553554
self.test.check_sanity()
554-
self.assertRaises(SanityError, self.test.check_performance)
555+
self.assertRaises(PerformanceError, self.test.check_performance)
555556

556557
def test_unknown_tag(self):
557558
self.test.reference = {
@@ -634,7 +635,7 @@ def extract_perf(patt, tag):
634635
self.write_performance_output(performance1=1.0,
635636
performance2=1.8,
636637
performance3=3.3)
637-
with self.assertRaises(SanityError) as cm:
638+
with self.assertRaises(PerformanceError) as cm:
638639
self.test.check_performance()
639640

640641
logfile = os.path.join(self.test.stagedir, logfile)

0 commit comments

Comments
 (0)