Skip to content

Commit ba24be2

Browse files
authored
Merge pull request #9 from eth-cscs/deploy/reframe-2.5
Reframe 2.5 public release
2 parents d1ae0c4 + 67c6a8a commit ba24be2

File tree

21 files changed

+837
-152
lines changed

21 files changed

+837
-152
lines changed

ci-scripts/ci-runner.bash

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,14 @@ checked_exec()
6060

6161
run_user_checks()
6262
{
63-
cmd="python reframe.py --prefix . --notimestamp -r -t production $@"
63+
cmd="./bin/reframe --exec-policy=async -r -t production $@"
64+
echo "Running user checks with \`$cmd'"
65+
checked_exec $cmd
66+
}
67+
68+
run_serial_user_checks()
69+
{
70+
cmd="./bin/reframe --exec-policy=serial -r -t production-serial $@"
6471
echo "Running user checks with \`$cmd'"
6572
checked_exec $cmd
6673
}
@@ -194,6 +201,7 @@ if [ ${#userchecks[@]} -ne 0 ]; then
194201
#
195202
for i in ${!invocations[@]}; do
196203
run_user_checks ${userchecks_path} ${invocations[i]}
204+
run_serial_user_checks ${userchecks_path} ${invocations[i]}
197205
done
198206
fi
199207

reframe/core/fields.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,20 @@ def __set__(self, obj, value):
2121
obj.__dict__[self.name] = value
2222

2323

24+
class ForwardField(object):
25+
"""Simple field that forwards set/get to a target object."""
26+
def __init__(self, obj, attr):
27+
self.target = obj
28+
self.attr = attr
29+
30+
def __get__(self, obj, objtype):
31+
return self.target.__dict__[self.attr]
32+
33+
34+
def __set__(self, obj, value):
35+
self.target.__dict__[self.attr] = value
36+
37+
2438
class TypedField(Field):
2539
"""Stores a field of predefined type"""
2640
def __init__(self, fieldname, fieldtype, allow_none = False):
@@ -354,6 +368,11 @@ def __init__(self, mapping={}, scope_sep=':', global_scope='*'):
354368
self.global_scope = global_scope
355369

356370

371+
def __str__(self):
372+
# just print the internal dictionary
373+
return str(self.scopes)
374+
375+
357376
def _check_scope_type(self, key, value):
358377
if not isinstance(key, str):
359378
raise TypeError('scope keys in a scoped dict must be strings')

reframe/core/launchers.py

Lines changed: 66 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,86 @@
1+
from math import ceil
2+
3+
14
class JobLauncher:
2-
def __init__(self, job, options):
3-
self.job = job
5+
def __init__(self, job, options=[]):
6+
self.job = job
47
self.options = options
58

6-
def emit_run_command(self, cmd, builder, **builder_opts):
9+
@property
10+
def executable(self):
711
raise NotImplementedError('Attempt to call an abstract method')
812

13+
@property
14+
def fixed_options(self):
15+
return []
916

10-
class LocalLauncher(JobLauncher):
11-
def __init__(self, job, options = []):
12-
super().__init__(job, options)
13-
14-
def emit_run_command(self, cmd, builder, **builder_opts):
15-
# Just emit the command
16-
return builder.verbatim(cmd, **builder_opts)
17+
def emit_run_command(self, target_executable, builder, **builder_opts):
18+
options = ' '.join(self.fixed_options + self.options)
19+
return builder.verbatim('%s %s %s' % \
20+
(self.executable, options, target_executable),
21+
**builder_opts)
1722

1823

1924
class NativeSlurmLauncher(JobLauncher):
20-
def __init__(self, job, options = []):
21-
super().__init__(job, options)
22-
self.launcher = 'srun %s' % (' '.join(self.options))
23-
24-
25-
def emit_run_command(self, cmd, builder, **builder_opts):
26-
return builder.verbatim('%s %s' % (self.launcher, cmd), **builder_opts)
25+
@property
26+
def executable(self):
27+
return 'srun'
2728

2829

2930
class AlpsLauncher(JobLauncher):
30-
def __init__(self, job, options = []):
31-
super().__init__(job, options)
32-
self.launcher = 'aprun -B %s' % (' '.join(self.options))
31+
@property
32+
def executable(self):
33+
return 'aprun'
3334

34-
def emit_run_command(self, cmd, builder, **builder_opts):
35-
return builder.verbatim('%s %s' % (self.launcher, cmd), **builder_opts)
35+
@property
36+
def fixed_options(self):
37+
return [ '-B' ]
3638

3739

3840
class LauncherWrapper(JobLauncher):
39-
"""
40-
Wraps a launcher object so that you can modify the launcher's invocation
41-
"""
42-
def __init__(self, launcher, wrapper_cmd, wrapper_options = []):
43-
self.launcher = launcher
44-
self.wrapper = wrapper_cmd
41+
"""Wrap a launcher object so that its invocation may be modified."""
42+
def __init__(self, target_launcher, wrapper_command, wrapper_options=[]):
43+
super().__init__(target_launcher.job, target_launcher.options)
44+
self.target_launcher = target_launcher
45+
self.wrapper_command = wrapper_command
4546
self.wrapper_options = wrapper_options
4647

48+
@property
49+
def executable(self):
50+
return self.wrapper_command
51+
52+
@property
53+
def fixed_options(self):
54+
return self.wrapper_options + [ self.target_launcher.executable ] + \
55+
self.target_launcher.fixed_options
4756

57+
58+
class LocalLauncher(JobLauncher):
4859
def emit_run_command(self, cmd, builder, **builder_opts):
49-
# Suppress the output of the wrapped launcher in the builder
50-
launcher_cmd = self.launcher.emit_run_command(cmd, builder,
51-
suppress=True)
52-
return builder.verbatim(
53-
'%s %s %s' % (self.wrapper, ' '.join(self.wrapper_options),
54-
launcher_cmd), **builder_opts)
60+
# Just emit the command
61+
return builder.verbatim(cmd, **builder_opts)
62+
63+
64+
class VisitLauncher(JobLauncher):
65+
def __init__(self, job, options=[]):
66+
super().__init__(job, options)
67+
if self.job:
68+
# The self.job.launcher must be stored at the moment of the
69+
# VisitLauncher construction, because the user will afterwards set
70+
# the newly created VisitLauncher as new self.job.launcher!
71+
self.target_launcher = self.job.launcher
72+
73+
@property
74+
def executable(self):
75+
return 'visit'
76+
77+
@property
78+
def fixed_options(self):
79+
options = []
80+
if self.target_launcher and \
81+
not isinstance(self.target_launcher, LocalLauncher):
82+
num_nodes = ceil(self.job.num_tasks/self.job.num_tasks_per_node)
83+
options.append('-np %s' % self.job.num_tasks)
84+
options.append('-nn %s' % num_nodes)
85+
options.append('-l %s' % self.target_launcher.executable)
86+
return options

reframe/core/pipeline.py

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ class RegressionTest(object):
5656
use_multithreading = BooleanField('use_multithreading', allow_none=True)
5757
local = BooleanField('local')
5858
prefix = StringField('prefix')
59-
sourcesdir = StringField('sourcesdir')
59+
sourcesdir = StringField('sourcesdir', allow_none=True)
6060
stagedir = StringField('stagedir', allow_none=True)
6161
stdout = StringField('stdout', allow_none=True)
6262
stderr = StringField('stderr', allow_none=True)
@@ -171,6 +171,13 @@ def is_local(self):
171171
return self.local or self.current_partition.scheduler == 'local'
172172

173173

174+
def _sanitize_basename(self, name):
175+
"""Create a basename safe to be used as path component
176+
177+
Replace all path separator characters in `name` with underscores."""
178+
return name.replace(os.sep, '_')
179+
180+
174181
def _setup_environ(self, environ):
175182
"""Setup the current environment and load it."""
176183

@@ -196,10 +203,17 @@ def _setup_environ(self, environ):
196203
def _setup_paths(self):
197204
"""Setup the check's dynamic paths."""
198205
self.logger.debug('setting up paths')
206+
199207
self.stagedir = self._resources.stagedir(
200-
self.current_partition.name, self.name, self.current_environ.name)
208+
self._sanitize_basename(self.current_partition.name),
209+
self.name,
210+
self._sanitize_basename(self.current_environ.name)
211+
)
201212
self.outputdir = self._resources.outputdir(
202-
self.current_partition.name, self.name, self.current_environ.name)
213+
self._sanitize_basename(self.current_partition.name),
214+
self.name,
215+
self._sanitize_basename(self.current_environ.name)
216+
)
203217
self.stdout = os.path.join(self.stagedir, '%s.out' % self.name)
204218
self.stderr = os.path.join(self.stagedir, '%s.err' % self.name)
205219

@@ -230,10 +244,13 @@ def _setup_job(self, **job_opts):
230244
raise ReframeFatalError('Oops: unsupported launcher: %s' %
231245
self.current_partition.scheduler)
232246

233-
job_name = '%s_%s_%s_%s' % (self.name,
234-
self.current_system.name,
235-
self.current_partition.name,
236-
self.current_environ.name)
247+
job_name = '%s_%s_%s_%s' % (
248+
self.name,
249+
self._sanitize_basename(self.current_system.name),
250+
self._sanitize_basename(self.current_partition.name),
251+
self._sanitize_basename(self.current_environ.name)
252+
)
253+
237254
if self.is_local():
238255
self.job = LocalJob(
239256
job_name=job_name,
@@ -342,6 +359,9 @@ def compile(self, **compile_opts):
342359
if not self.current_environ:
343360
raise ReframeError('no programming environment set')
344361

362+
if not self.sourcesdir:
363+
raise ReframeError('sourcesdir is not set')
364+
345365
# if self.sourcepath refers to a directory, stage it first
346366
target_sourcepath = os.path.join(self.sourcesdir, self.sourcepath)
347367
if os.path.isdir(target_sourcepath):
@@ -420,8 +440,8 @@ def check_performance(self):
420440
return self._match_patterns(self.perf_patterns, self.reference)
421441

422442

423-
def cleanup(self, remove_files=False, unload_env=True):
424-
# Copy stdout/stderr and job script
443+
def _copy_to_outputdir(self):
444+
"""Copy checks interesting files to the output directory."""
425445
self.logger.debug('copying interesting files to output directory')
426446
shutil.copy(self.stdout, self.outputdir)
427447
shutil.copy(self.stderr, self.outputdir)
@@ -434,6 +454,15 @@ def cleanup(self, remove_files=False, unload_env=True):
434454
f = os.path.join(self.stagedir, f)
435455
shutil.copy(f, self.outputdir)
436456

457+
458+
def cleanup(self, remove_files=False, unload_env=True):
459+
aliased = os.path.samefile(self.stagedir, self.outputdir)
460+
if aliased:
461+
self.logger.debug('skipping copy to output dir '
462+
'since they alias each other')
463+
else:
464+
self._copy_to_outputdir()
465+
437466
if remove_files:
438467
self.logger.debug('removing stage directory')
439468
shutil.rmtree(self.stagedir)
@@ -550,7 +579,11 @@ def compile(self, **compile_opts):
550579

551580

552581
def run(self):
553-
self._copy_to_stagedir(os.path.join(self.sourcesdir, self.sourcepath))
582+
# The sourcesdir can be set to None by the user; then we don't copy.
583+
if self.sourcesdir:
584+
self._copy_to_stagedir(os.path.join(self.sourcesdir,
585+
self.sourcepath))
586+
554587
super().run()
555588

556589

0 commit comments

Comments
 (0)