Skip to content

Commit 747eddd

Browse files
authored
Merge pull request #1 from boegel/read-only-copy2
implement test for `copy_file` to copy read-only file with extended attributes
2 parents 150d7c3 + 23b5b78 commit 747eddd

2 files changed

Lines changed: 48 additions & 4 deletions

File tree

easybuild/tools/filetools.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2443,11 +2443,11 @@ def copy_file(path, target_path, force_in_dry_run=False):
24432443
# see https://bugs.python.org/issue24538
24442444
shutil.copy2(path, target_path)
24452445
_log.info("%s copied to %s", path, target_path)
2446+
# catch the more general OSError instead of PermissionError,
2447+
# since Python 2.7 doesn't support PermissionError
24462448
except OSError as err:
2447-
# catch the more general OSError instead of PermissionError, since python2 doesn't support
2448-
# PermissionError
2449-
if not os.stat(path).st_mode & stat.S_IWUSR:
2450-
# failure not due to read-only file
2449+
# if file is writable (not read-only), then we give up since it's not a simple permission error
2450+
if os.path.exists(target_path) and os.stat(target_path).st_mode & stat.S_IWUSR:
24512451
raise EasyBuildError("Failed to copy file %s to %s: %s", path, target_path, err)
24522452

24532453
pyver = LooseVersion(platform.python_version())

test/framework/filetools.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
@author: Maxime Boissonneault (Compute Canada, Universite Laval)
3333
"""
3434
import datetime
35+
import filecmp
3536
import glob
3637
import logging
3738
import os
@@ -51,6 +52,8 @@
5152
from easybuild.tools.config import IGNORE, ERROR, build_option, update_build_option
5253
from easybuild.tools.multidiff import multidiff
5354
from easybuild.tools.py2vs3 import StringIO, std_urllib
55+
from easybuild.tools.run import run_cmd
56+
from easybuild.tools.systemtools import LINUX, get_os_type
5457

5558

5659
class FileToolsTest(EnhancedTestCase):
@@ -1912,6 +1915,47 @@ def test_copy_file(self):
19121915
# However, if we add 'force_in_dry_run=True' it should throw an exception
19131916
self.assertErrorRegex(EasyBuildError, "Could not copy *", ft.copy_file, src, target, force_in_dry_run=True)
19141917

1918+
def test_copy_file_xattr(self):
1919+
"""Test copying a file with extended attributes using copy_file."""
1920+
# test copying a read-only files with extended attributes set
1921+
# first, create a special file with extended attributes
1922+
special_file = os.path.join(self.test_prefix, 'special.txt')
1923+
ft.write_file(special_file, 'special')
1924+
# make read-only, and set extended attributes
1925+
attr = ft.which('attr')
1926+
xattr = ft.which('xattr')
1927+
# try to attr (Linux) or xattr (macOS) to set extended attributes foo=bar
1928+
cmd = None
1929+
if attr:
1930+
cmd = "attr -s foo -V bar %s" % special_file
1931+
elif xattr:
1932+
cmd = "xattr -w foo bar %s" % special_file
1933+
1934+
if cmd:
1935+
(_, ec) = run_cmd(cmd, simple=False, log_all=False, log_ok=False)
1936+
1937+
# need to make file read-only after setting extended attribute
1938+
ft.adjust_permissions(special_file, stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH, add=False)
1939+
1940+
# only proceed if setting extended attribute worked
1941+
if ec == 0:
1942+
target = os.path.join(self.test_prefix, 'copy.txt')
1943+
ft.copy_file(special_file, target)
1944+
self.assertTrue(os.path.exists(target))
1945+
self.assertTrue(filecmp.cmp(special_file, target, shallow=False))
1946+
1947+
# only verify wheter extended attributes were also copied on Linux,
1948+
# since shutil.copy2 doesn't copy them on macOS;
1949+
# see warning at https://docs.python.org/3/library/shutil.html
1950+
if get_os_type() == LINUX:
1951+
if attr:
1952+
cmd = "attr -g foo %s" % target
1953+
else:
1954+
cmd = "xattr -l %s" % target
1955+
(out, ec) = run_cmd(cmd, simple=False, log_all=False, log_ok=False)
1956+
self.assertEqual(ec, 0)
1957+
self.assertTrue(out.endswith('\nbar\n'))
1958+
19151959
def test_copy_files(self):
19161960
"""Test copy_files function."""
19171961
test_ecs = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'easyconfigs', 'test_ecs')

0 commit comments

Comments
 (0)