Skip to content

Commit 7d45217

Browse files
author
Vasileios Karakasis
committed
Add option to disable cleaning of the test stage directory
To better support this, the `os_ext.copytree()` was rewritten to comply with the `dirs_exist_ok` argument added in Python 3.8
1 parent d4533be commit 7d45217

File tree

4 files changed

+72
-41
lines changed

4 files changed

+72
-41
lines changed

reframe/core/pipeline.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1069,9 +1069,11 @@ def _copy_to_stagedir(self, path):
10691069
(path, self._stagedir))
10701070
self.logger.debug('symlinking files: %s' % self.readonly_files)
10711071
try:
1072-
os_ext.copytree_virtual(path, self._stagedir, self.readonly_files)
1072+
os_ext.copytree_virtual(
1073+
path, self._stagedir, self.readonly_files, dirs_exist_ok=True
1074+
)
10731075
except (OSError, ValueError, TypeError) as e:
1074-
raise PipelineError('virtual copying of files failed') from e
1076+
raise PipelineError('copying of files failed') from e
10751077

10761078
def _clone_to_stagedir(self, url):
10771079
self.logger.debug('cloning URL %s to stage directory (%s)' %

reframe/core/runtime.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,9 +145,8 @@ def make_stagedir(self, *dirs):
145145
*self._format_dirs(*dirs), wipeout=wipeout)
146146

147147
def make_outputdir(self, *dirs):
148-
wipeout = self.get_option('general/0/clean_stagedir')
149148
return self._makedir(self.output_prefix,
150-
*self._format_dirs(*dirs), wipeout=wipeout)
149+
*self._format_dirs(*dirs), wipeout=True)
151150

152151
@property
153152
def modules_system(self):

reframe/utility/os_ext.py

Lines changed: 46 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -95,25 +95,44 @@ def osgroup():
9595

9696

9797
def copytree(src, dst, symlinks=False, ignore=None, copy_function=shutil.copy2,
98-
ignore_dangling_symlinks=False):
99-
'''Same as shutil.copytree() but valid also if 'dst' exists.
100-
101-
In this case it will first remove it and then call the standard
102-
shutil.copytree().'''
98+
ignore_dangling_symlinks=False, dirs_exist_ok=False):
99+
'''Compatibility version of :py:func:`shutil.copytree()` for Python <= 3.8
100+
'''
103101
if src == os.path.commonpath([src, dst]):
104102
raise ValueError("cannot copy recursively the parent directory "
105103
"`%s' into one of its descendants `%s'" % (src, dst))
106104

107-
if os.path.exists(dst):
108-
shutil.rmtree(dst)
105+
if sys.version_info[2] >= 8:
106+
return shutil.copytree(src, dst, symlinks, ignore, copy_function,
107+
ignore_dangling_symlinks, dirs_exist_ok)
108+
109+
if not dirs_exist_ok:
110+
return shutil.copytree(src, dst, symlinks, ignore, copy_function,
111+
ignore_dangling_symlinks)
112+
113+
# dirs_exist_ok=True and Python < 3.8
114+
if not os.path.exists(dst):
115+
return shutil.copytree(src, dst, symlinks, ignore, copy_function,
116+
ignore_dangling_symlinks)
117+
118+
# dst exists; manually descend into the subdirectories
119+
_, subdirs, files = list(os.walk(src))[0]
120+
ignore_paths = ignore(src, os.listdir(src)) if ignore else {}
121+
for f in files:
122+
if f not in ignore_paths:
123+
copy_function(os.path.join(src, f), os.path.join(dst, f))
109124

110-
shutil.copytree(src, dst, symlinks, ignore, copy_function,
111-
ignore_dangling_symlinks)
125+
for d in subdirs:
126+
if d not in ignore_paths:
127+
copytree(os.path.join(src, d), os.path.join(dst, d), symlinks,
128+
ignore, copy_function, ignore_dangling_symlinks)
129+
130+
return dst
112131

113132

114133
def copytree_virtual(src, dst, file_links=[],
115134
symlinks=False, copy_function=shutil.copy2,
116-
ignore_dangling_symlinks=False):
135+
ignore_dangling_symlinks=False, dirs_exist_ok=False):
117136
'''Copy `dst` to `src`, but create symlinks for the files in `file_links`.
118137
119138
If `file_links` is empty, this is equivalent to `copytree()`. The rest of
@@ -134,35 +153,42 @@ def copytree_virtual(src, dst, file_links=[],
134153
link_targets = set()
135154
for f in file_links:
136155
if os.path.isabs(f):
137-
raise ValueError("copytree_virtual() failed: `%s': "
138-
"absolute paths not allowed in file_links" % f)
156+
raise ValueError(f'copytree_virtual() failed: {f!r}: '
157+
f'absolute paths not allowed in file_links')
139158

140159
target = os.path.join(src, f)
141160
if not os.path.exists(target):
142-
raise ValueError("copytree_virtual() failed: `%s' "
143-
"does not exist" % target)
161+
raise ValueError(f'copytree_virtual() failed: {target!r} '
162+
f'does not exist')
144163

145164
if os.path.commonpath([src, target]) != src:
146-
raise ValueError("copytree_virtual() failed: "
147-
"`%s' not under `%s'" % (target, src))
165+
raise ValueError(f'copytree_virtual() failed: '
166+
f'{target!r} not under {src!r}')
148167

149168
link_targets.add(os.path.abspath(target))
150169

170+
if '.' in file_links or '..' in file_links:
171+
raise ValueError(f"'.' or '..' are not allowed in file_links")
172+
151173
if not file_links:
152174
ignore = None
153175
else:
154176
def ignore(dir, contents):
155-
return [c for c in contents
156-
if os.path.join(dir, c) in link_targets]
177+
return {c for c in contents
178+
if os.path.join(dir, c) in link_targets}
157179

158180
# Copy to dst ignoring the file_links
159181
copytree(src, dst, symlinks, ignore,
160-
copy_function, ignore_dangling_symlinks)
182+
copy_function, ignore_dangling_symlinks, dirs_exist_ok)
161183

162184
# Now create the symlinks
163185
for f in link_targets:
164186
link_name = f.replace(src, dst)
165-
os.symlink(f, link_name)
187+
try:
188+
os.symlink(f, link_name)
189+
except FileExistsError:
190+
if not dirs_exist_ok:
191+
raise
166192

167193

168194
def rmtree(*args, max_retries=3, **kwargs):

unittests/test_utility.py

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -61,15 +61,7 @@ def test_command_async(self):
6161
def test_copytree(self):
6262
dir_src = tempfile.mkdtemp()
6363
dir_dst = tempfile.mkdtemp()
64-
65-
with pytest.raises(OSError):
66-
shutil.copytree(dir_src, dir_dst)
67-
68-
try:
69-
os_ext.copytree(dir_src, dir_dst)
70-
except Exception as e:
71-
pytest.fail('custom copytree failed: %s' % e)
72-
64+
os_ext.copytree(dir_src, dir_dst, dirs_exist_ok=True)
7365
shutil.rmtree(dir_src)
7466
shutil.rmtree(dir_dst)
7567

@@ -301,38 +293,50 @@ def verify_target_directory(self, file_links=[]):
301293
assert target_name == os.readlink(link_name)
302294

303295
def test_virtual_copy_nolinks(self):
304-
os_ext.copytree_virtual(self.prefix, self.target)
296+
os_ext.copytree_virtual(self.prefix, self.target, dirs_exist_ok=True)
305297
self.verify_target_directory()
306298

307299
def test_virtual_copy_valid_links(self):
308300
file_links = ['bar/', 'foo/bar.txt', 'foo.txt']
309-
os_ext.copytree_virtual(self.prefix, self.target, file_links)
301+
os_ext.copytree_virtual(self.prefix, self.target,
302+
file_links, dirs_exist_ok=True)
310303
self.verify_target_directory(file_links)
311304

312305
def test_virtual_copy_inexistent_links(self):
313306
file_links = ['foobar/', 'foo/bar.txt', 'foo.txt']
314307
with pytest.raises(ValueError):
315-
os_ext.copytree_virtual(self.prefix, self.target, file_links)
308+
os_ext.copytree_virtual(self.prefix, self.target,
309+
file_links, dirs_exist_ok=True)
316310

317311
def test_virtual_copy_absolute_paths(self):
318312
file_links = [os.path.join(self.prefix, 'bar'),
319313
'foo/bar.txt', 'foo.txt']
320314
with pytest.raises(ValueError):
321-
os_ext.copytree_virtual(self.prefix, self.target, file_links)
315+
os_ext.copytree_virtual(self.prefix, self.target,
316+
file_links, dirs_exist_ok=True)
322317

323318
def test_virtual_copy_irrelevenant_paths(self):
324319
file_links = ['/bin', 'foo/bar.txt', 'foo.txt']
325320
with pytest.raises(ValueError):
326-
os_ext.copytree_virtual(self.prefix, self.target, file_links)
321+
os_ext.copytree_virtual(self.prefix, self.target,
322+
file_links, dirs_exist_ok=True)
327323

328324
file_links = [os.path.dirname(self.prefix), 'foo/bar.txt', 'foo.txt']
329325
with pytest.raises(ValueError):
330-
os_ext.copytree_virtual(self.prefix, self.target, file_links)
326+
os_ext.copytree_virtual(self.prefix, self.target,
327+
file_links, dirs_exist_ok=True)
331328

332329
def test_virtual_copy_linkself(self):
333330
file_links = ['.']
334-
with pytest.raises(OSError):
335-
os_ext.copytree_virtual(self.prefix, self.target, file_links)
331+
with pytest.raises(ValueError):
332+
os_ext.copytree_virtual(self.prefix, self.target,
333+
file_links, dirs_exist_ok=True)
334+
335+
def test_virtual_copy_linkparent(self):
336+
file_links = ['..']
337+
with pytest.raises(ValueError):
338+
os_ext.copytree_virtual(self.prefix, self.target,
339+
file_links, dirs_exist_ok=True)
336340

337341
def tearDown(self):
338342
shutil.rmtree(self.prefix)

0 commit comments

Comments
 (0)