mirror of
https://github.com/python/cpython.git
synced 2024-11-23 09:54:58 +08:00
gh-127001: Fix PATHEXT issues in shutil.which() on Windows (GH-127035)
* Name without a PATHEXT extension is only searched if the mode does not include X_OK. * Support multi-component PATHEXT extensions (e.g. ".foo.bar"). * Support files without extensions in PATHEXT contains dot-only extension (".", "..", etc). * Support PATHEXT extensions that end with a dot (e.g. ".foo.").
This commit is contained in:
parent
615abb99a4
commit
8899e85de1
@ -491,12 +491,6 @@ Directory and files operations
|
|||||||
or ends with an extension that is in ``PATHEXT``; and filenames that
|
or ends with an extension that is in ``PATHEXT``; and filenames that
|
||||||
have no extension can now be found.
|
have no extension can now be found.
|
||||||
|
|
||||||
.. versionchanged:: 3.12.1
|
|
||||||
On Windows, if *mode* includes ``os.X_OK``, executables with an
|
|
||||||
extension in ``PATHEXT`` will be preferred over executables without a
|
|
||||||
matching extension.
|
|
||||||
This brings behavior closer to that of Python 3.11.
|
|
||||||
|
|
||||||
.. exception:: Error
|
.. exception:: Error
|
||||||
|
|
||||||
This exception collects exceptions that are raised during a multi-file
|
This exception collects exceptions that are raised during a multi-file
|
||||||
|
@ -1550,21 +1550,21 @@ def which(cmd, mode=os.F_OK | os.X_OK, path=None):
|
|||||||
if sys.platform == "win32":
|
if sys.platform == "win32":
|
||||||
# PATHEXT is necessary to check on Windows.
|
# PATHEXT is necessary to check on Windows.
|
||||||
pathext_source = os.getenv("PATHEXT") or _WIN_DEFAULT_PATHEXT
|
pathext_source = os.getenv("PATHEXT") or _WIN_DEFAULT_PATHEXT
|
||||||
pathext = [ext for ext in pathext_source.split(os.pathsep) if ext]
|
pathext = pathext_source.split(os.pathsep)
|
||||||
|
pathext = [ext.rstrip('.') for ext in pathext if ext]
|
||||||
|
|
||||||
if use_bytes:
|
if use_bytes:
|
||||||
pathext = [os.fsencode(ext) for ext in pathext]
|
pathext = [os.fsencode(ext) for ext in pathext]
|
||||||
|
|
||||||
files = ([cmd] + [cmd + ext for ext in pathext])
|
files = [cmd + ext for ext in pathext]
|
||||||
|
|
||||||
# gh-109590. If we are looking for an executable, we need to look
|
# If X_OK in mode, simulate the cmd.exe behavior: look at direct
|
||||||
# for a PATHEXT match. The first cmd is the direct match
|
# match if and only if the extension is in PATHEXT.
|
||||||
# (e.g. python.exe instead of python)
|
# If X_OK not in mode, simulate the first result of where.exe:
|
||||||
# Check that direct match first if and only if the extension is in PATHEXT
|
# always look at direct match before a PATHEXT match.
|
||||||
# Otherwise check it last
|
normcmd = cmd.upper()
|
||||||
suffix = os.path.splitext(files[0])[1].upper()
|
if not (mode & os.X_OK) or any(normcmd.endswith(ext.upper()) for ext in pathext):
|
||||||
if mode & os.X_OK and not any(suffix == ext.upper() for ext in pathext):
|
files.insert(0, cmd)
|
||||||
files.append(files.pop(0))
|
|
||||||
else:
|
else:
|
||||||
# On other platforms you don't have things like PATHEXT to tell you
|
# On other platforms you don't have things like PATHEXT to tell you
|
||||||
# what file suffixes are executable, so just pass on cmd as-is.
|
# what file suffixes are executable, so just pass on cmd as-is.
|
||||||
@ -1573,7 +1573,7 @@ def which(cmd, mode=os.F_OK | os.X_OK, path=None):
|
|||||||
seen = set()
|
seen = set()
|
||||||
for dir in path:
|
for dir in path:
|
||||||
normdir = os.path.normcase(dir)
|
normdir = os.path.normcase(dir)
|
||||||
if not normdir in seen:
|
if normdir not in seen:
|
||||||
seen.add(normdir)
|
seen.add(normdir)
|
||||||
for thefile in files:
|
for thefile in files:
|
||||||
name = os.path.join(dir, thefile)
|
name = os.path.join(dir, thefile)
|
||||||
|
@ -70,18 +70,17 @@ def mock_rename(func):
|
|||||||
os.rename = builtin_rename
|
os.rename = builtin_rename
|
||||||
return wrap
|
return wrap
|
||||||
|
|
||||||
def write_file(path, content, binary=False):
|
def create_file(path, content=b''):
|
||||||
"""Write *content* to a file located at *path*.
|
"""Write *content* to a file located at *path*.
|
||||||
|
|
||||||
If *path* is a tuple instead of a string, os.path.join will be used to
|
If *path* is a tuple instead of a string, os.path.join will be used to
|
||||||
make a path. If *binary* is true, the file will be opened in binary
|
make a path.
|
||||||
mode.
|
|
||||||
"""
|
"""
|
||||||
if isinstance(path, tuple):
|
if isinstance(path, tuple):
|
||||||
path = os.path.join(*path)
|
path = os.path.join(*path)
|
||||||
mode = 'wb' if binary else 'w'
|
if isinstance(content, str):
|
||||||
encoding = None if binary else "utf-8"
|
content = content.encode()
|
||||||
with open(path, mode, encoding=encoding) as fp:
|
with open(path, 'xb') as fp:
|
||||||
fp.write(content)
|
fp.write(content)
|
||||||
|
|
||||||
def write_test_file(path, size):
|
def write_test_file(path, size):
|
||||||
@ -190,7 +189,7 @@ class TestRmTree(BaseTest, unittest.TestCase):
|
|||||||
tmp = self.mkdtemp()
|
tmp = self.mkdtemp()
|
||||||
victim = os.path.join(tmp, 'killme')
|
victim = os.path.join(tmp, 'killme')
|
||||||
os.mkdir(victim)
|
os.mkdir(victim)
|
||||||
write_file(os.path.join(victim, 'somefile'), 'foo')
|
create_file(os.path.join(victim, 'somefile'), 'foo')
|
||||||
victim = os.fsencode(victim)
|
victim = os.fsencode(victim)
|
||||||
self.assertIsInstance(victim, bytes)
|
self.assertIsInstance(victim, bytes)
|
||||||
shutil.rmtree(victim)
|
shutil.rmtree(victim)
|
||||||
@ -242,7 +241,7 @@ class TestRmTree(BaseTest, unittest.TestCase):
|
|||||||
for d in dir1, dir2, dir3:
|
for d in dir1, dir2, dir3:
|
||||||
os.mkdir(d)
|
os.mkdir(d)
|
||||||
file1 = os.path.join(tmp, 'file1')
|
file1 = os.path.join(tmp, 'file1')
|
||||||
write_file(file1, 'foo')
|
create_file(file1, 'foo')
|
||||||
link1 = os.path.join(dir1, 'link1')
|
link1 = os.path.join(dir1, 'link1')
|
||||||
os.symlink(dir2, link1)
|
os.symlink(dir2, link1)
|
||||||
link2 = os.path.join(dir1, 'link2')
|
link2 = os.path.join(dir1, 'link2')
|
||||||
@ -304,7 +303,7 @@ class TestRmTree(BaseTest, unittest.TestCase):
|
|||||||
for d in dir1, dir2, dir3:
|
for d in dir1, dir2, dir3:
|
||||||
os.mkdir(d)
|
os.mkdir(d)
|
||||||
file1 = os.path.join(tmp, 'file1')
|
file1 = os.path.join(tmp, 'file1')
|
||||||
write_file(file1, 'foo')
|
create_file(file1, 'foo')
|
||||||
link1 = os.path.join(dir1, 'link1')
|
link1 = os.path.join(dir1, 'link1')
|
||||||
_winapi.CreateJunction(dir2, link1)
|
_winapi.CreateJunction(dir2, link1)
|
||||||
link2 = os.path.join(dir1, 'link2')
|
link2 = os.path.join(dir1, 'link2')
|
||||||
@ -327,7 +326,7 @@ class TestRmTree(BaseTest, unittest.TestCase):
|
|||||||
# existing file
|
# existing file
|
||||||
tmpdir = self.mkdtemp()
|
tmpdir = self.mkdtemp()
|
||||||
filename = os.path.join(tmpdir, "tstfile")
|
filename = os.path.join(tmpdir, "tstfile")
|
||||||
write_file(filename, "")
|
create_file(filename)
|
||||||
with self.assertRaises(NotADirectoryError) as cm:
|
with self.assertRaises(NotADirectoryError) as cm:
|
||||||
shutil.rmtree(filename)
|
shutil.rmtree(filename)
|
||||||
self.assertEqual(cm.exception.filename, filename)
|
self.assertEqual(cm.exception.filename, filename)
|
||||||
@ -347,7 +346,7 @@ class TestRmTree(BaseTest, unittest.TestCase):
|
|||||||
def test_rmtree_errors_onerror(self):
|
def test_rmtree_errors_onerror(self):
|
||||||
tmpdir = self.mkdtemp()
|
tmpdir = self.mkdtemp()
|
||||||
filename = os.path.join(tmpdir, "tstfile")
|
filename = os.path.join(tmpdir, "tstfile")
|
||||||
write_file(filename, "")
|
create_file(filename)
|
||||||
errors = []
|
errors = []
|
||||||
def onerror(*args):
|
def onerror(*args):
|
||||||
errors.append(args)
|
errors.append(args)
|
||||||
@ -365,7 +364,7 @@ class TestRmTree(BaseTest, unittest.TestCase):
|
|||||||
def test_rmtree_errors_onexc(self):
|
def test_rmtree_errors_onexc(self):
|
||||||
tmpdir = self.mkdtemp()
|
tmpdir = self.mkdtemp()
|
||||||
filename = os.path.join(tmpdir, "tstfile")
|
filename = os.path.join(tmpdir, "tstfile")
|
||||||
write_file(filename, "")
|
create_file(filename)
|
||||||
errors = []
|
errors = []
|
||||||
def onexc(*args):
|
def onexc(*args):
|
||||||
errors.append(args)
|
errors.append(args)
|
||||||
@ -547,7 +546,7 @@ class TestRmTree(BaseTest, unittest.TestCase):
|
|||||||
os.lstat = raiser
|
os.lstat = raiser
|
||||||
|
|
||||||
os.mkdir(TESTFN)
|
os.mkdir(TESTFN)
|
||||||
write_file((TESTFN, 'foo'), 'foo')
|
create_file((TESTFN, 'foo'), 'foo')
|
||||||
shutil.rmtree(TESTFN)
|
shutil.rmtree(TESTFN)
|
||||||
finally:
|
finally:
|
||||||
os.lstat = orig_lstat
|
os.lstat = orig_lstat
|
||||||
@ -618,7 +617,7 @@ class TestRmTree(BaseTest, unittest.TestCase):
|
|||||||
self.addCleanup(os.close, dir_fd)
|
self.addCleanup(os.close, dir_fd)
|
||||||
os.mkdir(fullname)
|
os.mkdir(fullname)
|
||||||
os.mkdir(os.path.join(fullname, 'subdir'))
|
os.mkdir(os.path.join(fullname, 'subdir'))
|
||||||
write_file(os.path.join(fullname, 'subdir', 'somefile'), 'foo')
|
create_file(os.path.join(fullname, 'subdir', 'somefile'), 'foo')
|
||||||
self.assertTrue(os.path.exists(fullname))
|
self.assertTrue(os.path.exists(fullname))
|
||||||
shutil.rmtree(victim, dir_fd=dir_fd)
|
shutil.rmtree(victim, dir_fd=dir_fd)
|
||||||
self.assertFalse(os.path.exists(fullname))
|
self.assertFalse(os.path.exists(fullname))
|
||||||
@ -658,7 +657,7 @@ class TestRmTree(BaseTest, unittest.TestCase):
|
|||||||
src = os.path.join(TESTFN, 'cheese')
|
src = os.path.join(TESTFN, 'cheese')
|
||||||
dst = os.path.join(TESTFN, 'shop')
|
dst = os.path.join(TESTFN, 'shop')
|
||||||
os.mkdir(src)
|
os.mkdir(src)
|
||||||
open(os.path.join(src, 'spam'), 'wb').close()
|
create_file(os.path.join(src, 'spam'))
|
||||||
_winapi.CreateJunction(src, dst)
|
_winapi.CreateJunction(src, dst)
|
||||||
self.assertRaises(OSError, shutil.rmtree, dst)
|
self.assertRaises(OSError, shutil.rmtree, dst)
|
||||||
shutil.rmtree(dst, ignore_errors=True)
|
shutil.rmtree(dst, ignore_errors=True)
|
||||||
@ -718,7 +717,7 @@ class TestRmTree(BaseTest, unittest.TestCase):
|
|||||||
for path in dirs:
|
for path in dirs:
|
||||||
os.mkdir(path)
|
os.mkdir(path)
|
||||||
for path in files:
|
for path in files:
|
||||||
write_file(path, '')
|
create_file(path)
|
||||||
|
|
||||||
old_modes = [os.stat(path).st_mode for path in paths]
|
old_modes = [os.stat(path).st_mode for path in paths]
|
||||||
|
|
||||||
@ -757,9 +756,9 @@ class TestCopyTree(BaseTest, unittest.TestCase):
|
|||||||
dst_dir = os.path.join(self.mkdtemp(), 'destination')
|
dst_dir = os.path.join(self.mkdtemp(), 'destination')
|
||||||
self.addCleanup(shutil.rmtree, src_dir)
|
self.addCleanup(shutil.rmtree, src_dir)
|
||||||
self.addCleanup(shutil.rmtree, os.path.dirname(dst_dir))
|
self.addCleanup(shutil.rmtree, os.path.dirname(dst_dir))
|
||||||
write_file((src_dir, 'test.txt'), '123')
|
create_file((src_dir, 'test.txt'), '123')
|
||||||
os.mkdir(os.path.join(src_dir, 'test_dir'))
|
os.mkdir(os.path.join(src_dir, 'test_dir'))
|
||||||
write_file((src_dir, 'test_dir', 'test.txt'), '456')
|
create_file((src_dir, 'test_dir', 'test.txt'), '456')
|
||||||
|
|
||||||
shutil.copytree(src_dir, dst_dir)
|
shutil.copytree(src_dir, dst_dir)
|
||||||
self.assertTrue(os.path.isfile(os.path.join(dst_dir, 'test.txt')))
|
self.assertTrue(os.path.isfile(os.path.join(dst_dir, 'test.txt')))
|
||||||
@ -777,11 +776,11 @@ class TestCopyTree(BaseTest, unittest.TestCase):
|
|||||||
self.addCleanup(shutil.rmtree, src_dir)
|
self.addCleanup(shutil.rmtree, src_dir)
|
||||||
self.addCleanup(shutil.rmtree, dst_dir)
|
self.addCleanup(shutil.rmtree, dst_dir)
|
||||||
|
|
||||||
write_file((src_dir, 'nonexisting.txt'), '123')
|
create_file((src_dir, 'nonexisting.txt'), '123')
|
||||||
os.mkdir(os.path.join(src_dir, 'existing_dir'))
|
os.mkdir(os.path.join(src_dir, 'existing_dir'))
|
||||||
os.mkdir(os.path.join(dst_dir, 'existing_dir'))
|
os.mkdir(os.path.join(dst_dir, 'existing_dir'))
|
||||||
write_file((dst_dir, 'existing_dir', 'existing.txt'), 'will be replaced')
|
create_file((dst_dir, 'existing_dir', 'existing.txt'), 'will be replaced')
|
||||||
write_file((src_dir, 'existing_dir', 'existing.txt'), 'has been replaced')
|
create_file((src_dir, 'existing_dir', 'existing.txt'), 'has been replaced')
|
||||||
|
|
||||||
shutil.copytree(src_dir, dst_dir, dirs_exist_ok=True)
|
shutil.copytree(src_dir, dst_dir, dirs_exist_ok=True)
|
||||||
self.assertTrue(os.path.isfile(os.path.join(dst_dir, 'nonexisting.txt')))
|
self.assertTrue(os.path.isfile(os.path.join(dst_dir, 'nonexisting.txt')))
|
||||||
@ -804,7 +803,7 @@ class TestCopyTree(BaseTest, unittest.TestCase):
|
|||||||
sub_dir = os.path.join(src_dir, 'sub')
|
sub_dir = os.path.join(src_dir, 'sub')
|
||||||
os.mkdir(src_dir)
|
os.mkdir(src_dir)
|
||||||
os.mkdir(sub_dir)
|
os.mkdir(sub_dir)
|
||||||
write_file((src_dir, 'file.txt'), 'foo')
|
create_file((src_dir, 'file.txt'), 'foo')
|
||||||
src_link = os.path.join(sub_dir, 'link')
|
src_link = os.path.join(sub_dir, 'link')
|
||||||
dst_link = os.path.join(dst_dir, 'sub/link')
|
dst_link = os.path.join(dst_dir, 'sub/link')
|
||||||
os.symlink(os.path.join(src_dir, 'file.txt'),
|
os.symlink(os.path.join(src_dir, 'file.txt'),
|
||||||
@ -835,16 +834,16 @@ class TestCopyTree(BaseTest, unittest.TestCase):
|
|||||||
src_dir = self.mkdtemp()
|
src_dir = self.mkdtemp()
|
||||||
try:
|
try:
|
||||||
dst_dir = join(self.mkdtemp(), 'destination')
|
dst_dir = join(self.mkdtemp(), 'destination')
|
||||||
write_file((src_dir, 'test.txt'), '123')
|
create_file((src_dir, 'test.txt'), '123')
|
||||||
write_file((src_dir, 'test.tmp'), '123')
|
create_file((src_dir, 'test.tmp'), '123')
|
||||||
os.mkdir(join(src_dir, 'test_dir'))
|
os.mkdir(join(src_dir, 'test_dir'))
|
||||||
write_file((src_dir, 'test_dir', 'test.txt'), '456')
|
create_file((src_dir, 'test_dir', 'test.txt'), '456')
|
||||||
os.mkdir(join(src_dir, 'test_dir2'))
|
os.mkdir(join(src_dir, 'test_dir2'))
|
||||||
write_file((src_dir, 'test_dir2', 'test.txt'), '456')
|
create_file((src_dir, 'test_dir2', 'test.txt'), '456')
|
||||||
os.mkdir(join(src_dir, 'test_dir2', 'subdir'))
|
os.mkdir(join(src_dir, 'test_dir2', 'subdir'))
|
||||||
os.mkdir(join(src_dir, 'test_dir2', 'subdir2'))
|
os.mkdir(join(src_dir, 'test_dir2', 'subdir2'))
|
||||||
write_file((src_dir, 'test_dir2', 'subdir', 'test.txt'), '456')
|
create_file((src_dir, 'test_dir2', 'subdir', 'test.txt'), '456')
|
||||||
write_file((src_dir, 'test_dir2', 'subdir2', 'test.py'), '456')
|
create_file((src_dir, 'test_dir2', 'subdir2', 'test.py'), '456')
|
||||||
|
|
||||||
# testing glob-like patterns
|
# testing glob-like patterns
|
||||||
try:
|
try:
|
||||||
@ -903,7 +902,7 @@ class TestCopyTree(BaseTest, unittest.TestCase):
|
|||||||
os.mkdir(join(src_dir))
|
os.mkdir(join(src_dir))
|
||||||
os.mkdir(join(src_dir, 'test_dir'))
|
os.mkdir(join(src_dir, 'test_dir'))
|
||||||
os.mkdir(os.path.join(src_dir, 'test_dir', 'subdir'))
|
os.mkdir(os.path.join(src_dir, 'test_dir', 'subdir'))
|
||||||
write_file((src_dir, 'test_dir', 'subdir', 'test.txt'), '456')
|
create_file((src_dir, 'test_dir', 'subdir', 'test.txt'), '456')
|
||||||
|
|
||||||
invocations = []
|
invocations = []
|
||||||
|
|
||||||
@ -943,9 +942,9 @@ class TestCopyTree(BaseTest, unittest.TestCase):
|
|||||||
self.addCleanup(shutil.rmtree, tmp_dir)
|
self.addCleanup(shutil.rmtree, tmp_dir)
|
||||||
|
|
||||||
os.chmod(src_dir, 0o777)
|
os.chmod(src_dir, 0o777)
|
||||||
write_file((src_dir, 'permissive.txt'), '123')
|
create_file((src_dir, 'permissive.txt'), '123')
|
||||||
os.chmod(os.path.join(src_dir, 'permissive.txt'), 0o777)
|
os.chmod(os.path.join(src_dir, 'permissive.txt'), 0o777)
|
||||||
write_file((src_dir, 'restrictive.txt'), '456')
|
create_file((src_dir, 'restrictive.txt'), '456')
|
||||||
os.chmod(os.path.join(src_dir, 'restrictive.txt'), 0o600)
|
os.chmod(os.path.join(src_dir, 'restrictive.txt'), 0o600)
|
||||||
restrictive_subdir = tempfile.mkdtemp(dir=src_dir)
|
restrictive_subdir = tempfile.mkdtemp(dir=src_dir)
|
||||||
self.addCleanup(os_helper.rmtree, restrictive_subdir)
|
self.addCleanup(os_helper.rmtree, restrictive_subdir)
|
||||||
@ -988,8 +987,7 @@ class TestCopyTree(BaseTest, unittest.TestCase):
|
|||||||
flag = []
|
flag = []
|
||||||
src = self.mkdtemp()
|
src = self.mkdtemp()
|
||||||
dst = tempfile.mktemp(dir=self.mkdtemp())
|
dst = tempfile.mktemp(dir=self.mkdtemp())
|
||||||
with open(os.path.join(src, 'foo'), 'w', encoding='utf-8') as f:
|
create_file(os.path.join(src, 'foo'))
|
||||||
f.close()
|
|
||||||
shutil.copytree(src, dst, copy_function=custom_cpfun)
|
shutil.copytree(src, dst, copy_function=custom_cpfun)
|
||||||
self.assertEqual(len(flag), 1)
|
self.assertEqual(len(flag), 1)
|
||||||
|
|
||||||
@ -1024,9 +1022,9 @@ class TestCopyTree(BaseTest, unittest.TestCase):
|
|||||||
def test_copytree_special_func(self):
|
def test_copytree_special_func(self):
|
||||||
src_dir = self.mkdtemp()
|
src_dir = self.mkdtemp()
|
||||||
dst_dir = os.path.join(self.mkdtemp(), 'destination')
|
dst_dir = os.path.join(self.mkdtemp(), 'destination')
|
||||||
write_file((src_dir, 'test.txt'), '123')
|
create_file((src_dir, 'test.txt'), '123')
|
||||||
os.mkdir(os.path.join(src_dir, 'test_dir'))
|
os.mkdir(os.path.join(src_dir, 'test_dir'))
|
||||||
write_file((src_dir, 'test_dir', 'test.txt'), '456')
|
create_file((src_dir, 'test_dir', 'test.txt'), '456')
|
||||||
|
|
||||||
copied = []
|
copied = []
|
||||||
def _copy(src, dst):
|
def _copy(src, dst):
|
||||||
@ -1039,7 +1037,7 @@ class TestCopyTree(BaseTest, unittest.TestCase):
|
|||||||
def test_copytree_dangling_symlinks(self):
|
def test_copytree_dangling_symlinks(self):
|
||||||
src_dir = self.mkdtemp()
|
src_dir = self.mkdtemp()
|
||||||
valid_file = os.path.join(src_dir, 'test.txt')
|
valid_file = os.path.join(src_dir, 'test.txt')
|
||||||
write_file(valid_file, 'abc')
|
create_file(valid_file, 'abc')
|
||||||
dir_a = os.path.join(src_dir, 'dir_a')
|
dir_a = os.path.join(src_dir, 'dir_a')
|
||||||
os.mkdir(dir_a)
|
os.mkdir(dir_a)
|
||||||
for d in src_dir, dir_a:
|
for d in src_dir, dir_a:
|
||||||
@ -1067,8 +1065,7 @@ class TestCopyTree(BaseTest, unittest.TestCase):
|
|||||||
src_dir = self.mkdtemp()
|
src_dir = self.mkdtemp()
|
||||||
dst_dir = os.path.join(self.mkdtemp(), 'destination')
|
dst_dir = os.path.join(self.mkdtemp(), 'destination')
|
||||||
os.mkdir(os.path.join(src_dir, 'real_dir'))
|
os.mkdir(os.path.join(src_dir, 'real_dir'))
|
||||||
with open(os.path.join(src_dir, 'real_dir', 'test.txt'), 'wb'):
|
create_file(os.path.join(src_dir, 'real_dir', 'test.txt'))
|
||||||
pass
|
|
||||||
os.symlink(os.path.join(src_dir, 'real_dir'),
|
os.symlink(os.path.join(src_dir, 'real_dir'),
|
||||||
os.path.join(src_dir, 'link_to_dir'),
|
os.path.join(src_dir, 'link_to_dir'),
|
||||||
target_is_directory=True)
|
target_is_directory=True)
|
||||||
@ -1088,7 +1085,7 @@ class TestCopyTree(BaseTest, unittest.TestCase):
|
|||||||
dst_dir = src_dir + "dest"
|
dst_dir = src_dir + "dest"
|
||||||
self.addCleanup(shutil.rmtree, dst_dir, True)
|
self.addCleanup(shutil.rmtree, dst_dir, True)
|
||||||
src = os.path.join(src_dir, 'foo')
|
src = os.path.join(src_dir, 'foo')
|
||||||
write_file(src, 'foo')
|
create_file(src, 'foo')
|
||||||
rv = shutil.copytree(src_dir, dst_dir)
|
rv = shutil.copytree(src_dir, dst_dir)
|
||||||
self.assertEqual(['foo'], os.listdir(rv))
|
self.assertEqual(['foo'], os.listdir(rv))
|
||||||
|
|
||||||
@ -1100,7 +1097,7 @@ class TestCopyTree(BaseTest, unittest.TestCase):
|
|||||||
dst_dir = os.path.join(src_dir, "somevendor", "1.0")
|
dst_dir = os.path.join(src_dir, "somevendor", "1.0")
|
||||||
os.makedirs(src_dir)
|
os.makedirs(src_dir)
|
||||||
src = os.path.join(src_dir, 'pol')
|
src = os.path.join(src_dir, 'pol')
|
||||||
write_file(src, 'pol')
|
create_file(src, 'pol')
|
||||||
rv = shutil.copytree(src_dir, dst_dir)
|
rv = shutil.copytree(src_dir, dst_dir)
|
||||||
self.assertEqual(['pol'], os.listdir(rv))
|
self.assertEqual(['pol'], os.listdir(rv))
|
||||||
|
|
||||||
@ -1115,8 +1112,8 @@ class TestCopy(BaseTest, unittest.TestCase):
|
|||||||
dst = os.path.join(tmp_dir, 'bar')
|
dst = os.path.join(tmp_dir, 'bar')
|
||||||
src_link = os.path.join(tmp_dir, 'baz')
|
src_link = os.path.join(tmp_dir, 'baz')
|
||||||
dst_link = os.path.join(tmp_dir, 'quux')
|
dst_link = os.path.join(tmp_dir, 'quux')
|
||||||
write_file(src, 'foo')
|
create_file(src, 'foo')
|
||||||
write_file(dst, 'foo')
|
create_file(dst, 'foo')
|
||||||
os.symlink(src, src_link)
|
os.symlink(src, src_link)
|
||||||
os.symlink(dst, dst_link)
|
os.symlink(dst, dst_link)
|
||||||
os.chmod(src, stat.S_IRWXU|stat.S_IRWXG)
|
os.chmod(src, stat.S_IRWXU|stat.S_IRWXG)
|
||||||
@ -1147,8 +1144,8 @@ class TestCopy(BaseTest, unittest.TestCase):
|
|||||||
dst = os.path.join(tmp_dir, 'bar')
|
dst = os.path.join(tmp_dir, 'bar')
|
||||||
src_link = os.path.join(tmp_dir, 'baz')
|
src_link = os.path.join(tmp_dir, 'baz')
|
||||||
dst_link = os.path.join(tmp_dir, 'quux')
|
dst_link = os.path.join(tmp_dir, 'quux')
|
||||||
write_file(src, 'foo')
|
create_file(src, 'foo')
|
||||||
write_file(dst, 'foo')
|
create_file(dst, 'foo')
|
||||||
os.symlink(src, src_link)
|
os.symlink(src, src_link)
|
||||||
os.symlink(dst, dst_link)
|
os.symlink(dst, dst_link)
|
||||||
os.chmod(src, stat.S_IRWXU|stat.S_IRWXG)
|
os.chmod(src, stat.S_IRWXU|stat.S_IRWXG)
|
||||||
@ -1178,8 +1175,8 @@ class TestCopy(BaseTest, unittest.TestCase):
|
|||||||
dst = os.path.join(tmp_dir, 'bar')
|
dst = os.path.join(tmp_dir, 'bar')
|
||||||
src_link = os.path.join(tmp_dir, 'baz')
|
src_link = os.path.join(tmp_dir, 'baz')
|
||||||
dst_link = os.path.join(tmp_dir, 'quux')
|
dst_link = os.path.join(tmp_dir, 'quux')
|
||||||
write_file(src, 'foo')
|
create_file(src, 'foo')
|
||||||
write_file(dst, 'foo')
|
create_file(dst, 'foo')
|
||||||
os.symlink(src, src_link)
|
os.symlink(src, src_link)
|
||||||
os.symlink(dst, dst_link)
|
os.symlink(dst, dst_link)
|
||||||
shutil.copymode(src_link, dst_link, follow_symlinks=False) # silent fail
|
shutil.copymode(src_link, dst_link, follow_symlinks=False) # silent fail
|
||||||
@ -1193,11 +1190,11 @@ class TestCopy(BaseTest, unittest.TestCase):
|
|||||||
dst = os.path.join(tmp_dir, 'bar')
|
dst = os.path.join(tmp_dir, 'bar')
|
||||||
src_link = os.path.join(tmp_dir, 'baz')
|
src_link = os.path.join(tmp_dir, 'baz')
|
||||||
dst_link = os.path.join(tmp_dir, 'qux')
|
dst_link = os.path.join(tmp_dir, 'qux')
|
||||||
write_file(src, 'foo')
|
create_file(src, 'foo')
|
||||||
src_stat = os.stat(src)
|
src_stat = os.stat(src)
|
||||||
os.utime(src, (src_stat.st_atime,
|
os.utime(src, (src_stat.st_atime,
|
||||||
src_stat.st_mtime - 42.0)) # ensure different mtimes
|
src_stat.st_mtime - 42.0)) # ensure different mtimes
|
||||||
write_file(dst, 'bar')
|
create_file(dst, 'bar')
|
||||||
self.assertNotEqual(os.stat(src).st_mtime, os.stat(dst).st_mtime)
|
self.assertNotEqual(os.stat(src).st_mtime, os.stat(dst).st_mtime)
|
||||||
os.symlink(src, src_link)
|
os.symlink(src, src_link)
|
||||||
os.symlink(dst, dst_link)
|
os.symlink(dst, dst_link)
|
||||||
@ -1235,8 +1232,8 @@ class TestCopy(BaseTest, unittest.TestCase):
|
|||||||
tmpdir = self.mkdtemp()
|
tmpdir = self.mkdtemp()
|
||||||
file1 = os.path.join(tmpdir, 'file1')
|
file1 = os.path.join(tmpdir, 'file1')
|
||||||
file2 = os.path.join(tmpdir, 'file2')
|
file2 = os.path.join(tmpdir, 'file2')
|
||||||
write_file(file1, 'xxx')
|
create_file(file1, 'xxx')
|
||||||
write_file(file2, 'xxx')
|
create_file(file2, 'xxx')
|
||||||
|
|
||||||
def make_chflags_raiser(err):
|
def make_chflags_raiser(err):
|
||||||
ex = OSError()
|
ex = OSError()
|
||||||
@ -1262,9 +1259,9 @@ class TestCopy(BaseTest, unittest.TestCase):
|
|||||||
def test_copyxattr(self):
|
def test_copyxattr(self):
|
||||||
tmp_dir = self.mkdtemp()
|
tmp_dir = self.mkdtemp()
|
||||||
src = os.path.join(tmp_dir, 'foo')
|
src = os.path.join(tmp_dir, 'foo')
|
||||||
write_file(src, 'foo')
|
create_file(src, 'foo')
|
||||||
dst = os.path.join(tmp_dir, 'bar')
|
dst = os.path.join(tmp_dir, 'bar')
|
||||||
write_file(dst, 'bar')
|
create_file(dst, 'bar')
|
||||||
|
|
||||||
# no xattr == no problem
|
# no xattr == no problem
|
||||||
shutil._copyxattr(src, dst)
|
shutil._copyxattr(src, dst)
|
||||||
@ -1278,7 +1275,7 @@ class TestCopy(BaseTest, unittest.TestCase):
|
|||||||
os.getxattr(dst, 'user.foo'))
|
os.getxattr(dst, 'user.foo'))
|
||||||
# check errors don't affect other attrs
|
# check errors don't affect other attrs
|
||||||
os.remove(dst)
|
os.remove(dst)
|
||||||
write_file(dst, 'bar')
|
create_file(dst, 'bar')
|
||||||
os_error = OSError(errno.EPERM, 'EPERM')
|
os_error = OSError(errno.EPERM, 'EPERM')
|
||||||
|
|
||||||
def _raise_on_user_foo(fname, attr, val, **kwargs):
|
def _raise_on_user_foo(fname, attr, val, **kwargs):
|
||||||
@ -1308,15 +1305,15 @@ class TestCopy(BaseTest, unittest.TestCase):
|
|||||||
# test that shutil.copystat copies xattrs
|
# test that shutil.copystat copies xattrs
|
||||||
src = os.path.join(tmp_dir, 'the_original')
|
src = os.path.join(tmp_dir, 'the_original')
|
||||||
srcro = os.path.join(tmp_dir, 'the_original_ro')
|
srcro = os.path.join(tmp_dir, 'the_original_ro')
|
||||||
write_file(src, src)
|
create_file(src, src)
|
||||||
write_file(srcro, srcro)
|
create_file(srcro, srcro)
|
||||||
os.setxattr(src, 'user.the_value', b'fiddly')
|
os.setxattr(src, 'user.the_value', b'fiddly')
|
||||||
os.setxattr(srcro, 'user.the_value', b'fiddly')
|
os.setxattr(srcro, 'user.the_value', b'fiddly')
|
||||||
os.chmod(srcro, 0o444)
|
os.chmod(srcro, 0o444)
|
||||||
dst = os.path.join(tmp_dir, 'the_copy')
|
dst = os.path.join(tmp_dir, 'the_copy')
|
||||||
dstro = os.path.join(tmp_dir, 'the_copy_ro')
|
dstro = os.path.join(tmp_dir, 'the_copy_ro')
|
||||||
write_file(dst, dst)
|
create_file(dst, dst)
|
||||||
write_file(dstro, dstro)
|
create_file(dstro, dstro)
|
||||||
shutil.copystat(src, dst)
|
shutil.copystat(src, dst)
|
||||||
shutil.copystat(srcro, dstro)
|
shutil.copystat(srcro, dstro)
|
||||||
self.assertEqual(os.getxattr(dst, 'user.the_value'), b'fiddly')
|
self.assertEqual(os.getxattr(dst, 'user.the_value'), b'fiddly')
|
||||||
@ -1332,13 +1329,13 @@ class TestCopy(BaseTest, unittest.TestCase):
|
|||||||
tmp_dir = self.mkdtemp()
|
tmp_dir = self.mkdtemp()
|
||||||
src = os.path.join(tmp_dir, 'foo')
|
src = os.path.join(tmp_dir, 'foo')
|
||||||
src_link = os.path.join(tmp_dir, 'baz')
|
src_link = os.path.join(tmp_dir, 'baz')
|
||||||
write_file(src, 'foo')
|
create_file(src, 'foo')
|
||||||
os.symlink(src, src_link)
|
os.symlink(src, src_link)
|
||||||
os.setxattr(src, 'trusted.foo', b'42')
|
os.setxattr(src, 'trusted.foo', b'42')
|
||||||
os.setxattr(src_link, 'trusted.foo', b'43', follow_symlinks=False)
|
os.setxattr(src_link, 'trusted.foo', b'43', follow_symlinks=False)
|
||||||
dst = os.path.join(tmp_dir, 'bar')
|
dst = os.path.join(tmp_dir, 'bar')
|
||||||
dst_link = os.path.join(tmp_dir, 'qux')
|
dst_link = os.path.join(tmp_dir, 'qux')
|
||||||
write_file(dst, 'bar')
|
create_file(dst, 'bar')
|
||||||
os.symlink(dst, dst_link)
|
os.symlink(dst, dst_link)
|
||||||
shutil._copyxattr(src_link, dst_link, follow_symlinks=False)
|
shutil._copyxattr(src_link, dst_link, follow_symlinks=False)
|
||||||
self.assertEqual(os.getxattr(dst_link, 'trusted.foo', follow_symlinks=False), b'43')
|
self.assertEqual(os.getxattr(dst_link, 'trusted.foo', follow_symlinks=False), b'43')
|
||||||
@ -1351,7 +1348,7 @@ class TestCopy(BaseTest, unittest.TestCase):
|
|||||||
def _copy_file(self, method):
|
def _copy_file(self, method):
|
||||||
fname = 'test.txt'
|
fname = 'test.txt'
|
||||||
tmpdir = self.mkdtemp()
|
tmpdir = self.mkdtemp()
|
||||||
write_file((tmpdir, fname), 'xxx')
|
create_file((tmpdir, fname), 'xxx')
|
||||||
file1 = os.path.join(tmpdir, fname)
|
file1 = os.path.join(tmpdir, fname)
|
||||||
tmpdir2 = self.mkdtemp()
|
tmpdir2 = self.mkdtemp()
|
||||||
method(file1, tmpdir2)
|
method(file1, tmpdir2)
|
||||||
@ -1370,7 +1367,7 @@ class TestCopy(BaseTest, unittest.TestCase):
|
|||||||
src = os.path.join(tmp_dir, 'foo')
|
src = os.path.join(tmp_dir, 'foo')
|
||||||
dst = os.path.join(tmp_dir, 'bar')
|
dst = os.path.join(tmp_dir, 'bar')
|
||||||
src_link = os.path.join(tmp_dir, 'baz')
|
src_link = os.path.join(tmp_dir, 'baz')
|
||||||
write_file(src, 'foo')
|
create_file(src, 'foo')
|
||||||
os.symlink(src, src_link)
|
os.symlink(src, src_link)
|
||||||
if hasattr(os, 'lchmod'):
|
if hasattr(os, 'lchmod'):
|
||||||
os.lchmod(src_link, stat.S_IRWXU | stat.S_IRWXO)
|
os.lchmod(src_link, stat.S_IRWXU | stat.S_IRWXO)
|
||||||
@ -1412,7 +1409,7 @@ class TestCopy(BaseTest, unittest.TestCase):
|
|||||||
src = os.path.join(tmp_dir, 'foo')
|
src = os.path.join(tmp_dir, 'foo')
|
||||||
dst = os.path.join(tmp_dir, 'bar')
|
dst = os.path.join(tmp_dir, 'bar')
|
||||||
src_link = os.path.join(tmp_dir, 'baz')
|
src_link = os.path.join(tmp_dir, 'baz')
|
||||||
write_file(src, 'foo')
|
create_file(src, 'foo')
|
||||||
os.symlink(src, src_link)
|
os.symlink(src, src_link)
|
||||||
if hasattr(os, 'lchmod'):
|
if hasattr(os, 'lchmod'):
|
||||||
os.lchmod(src_link, stat.S_IRWXU | stat.S_IRWXO)
|
os.lchmod(src_link, stat.S_IRWXU | stat.S_IRWXO)
|
||||||
@ -1446,7 +1443,7 @@ class TestCopy(BaseTest, unittest.TestCase):
|
|||||||
tmp_dir = self.mkdtemp()
|
tmp_dir = self.mkdtemp()
|
||||||
src = os.path.join(tmp_dir, 'foo')
|
src = os.path.join(tmp_dir, 'foo')
|
||||||
dst = os.path.join(tmp_dir, 'bar')
|
dst = os.path.join(tmp_dir, 'bar')
|
||||||
write_file(src, 'foo')
|
create_file(src, 'foo')
|
||||||
os.setxattr(src, 'user.foo', b'42')
|
os.setxattr(src, 'user.foo', b'42')
|
||||||
shutil.copy2(src, dst)
|
shutil.copy2(src, dst)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
@ -1460,7 +1457,7 @@ class TestCopy(BaseTest, unittest.TestCase):
|
|||||||
src_dir = self.mkdtemp()
|
src_dir = self.mkdtemp()
|
||||||
dst_dir = self.mkdtemp()
|
dst_dir = self.mkdtemp()
|
||||||
src = os.path.join(src_dir, 'foo')
|
src = os.path.join(src_dir, 'foo')
|
||||||
write_file(src, 'foo')
|
create_file(src, 'foo')
|
||||||
rv = fn(src, dst_dir)
|
rv = fn(src, dst_dir)
|
||||||
self.assertEqual(rv, os.path.join(dst_dir, 'foo'))
|
self.assertEqual(rv, os.path.join(dst_dir, 'foo'))
|
||||||
rv = fn(src, os.path.join(dst_dir, 'bar'))
|
rv = fn(src, os.path.join(dst_dir, 'bar'))
|
||||||
@ -1477,7 +1474,7 @@ class TestCopy(BaseTest, unittest.TestCase):
|
|||||||
src_file = os.path.join(src_dir, 'foo')
|
src_file = os.path.join(src_dir, 'foo')
|
||||||
dir2 = self.mkdtemp()
|
dir2 = self.mkdtemp()
|
||||||
dst = os.path.join(src_dir, 'does_not_exist/')
|
dst = os.path.join(src_dir, 'does_not_exist/')
|
||||||
write_file(src_file, 'foo')
|
create_file(src_file, 'foo')
|
||||||
if sys.platform == "win32":
|
if sys.platform == "win32":
|
||||||
err = PermissionError
|
err = PermissionError
|
||||||
else:
|
else:
|
||||||
@ -1497,7 +1494,7 @@ class TestCopy(BaseTest, unittest.TestCase):
|
|||||||
dst = os.path.join(tmp_dir, 'dst')
|
dst = os.path.join(tmp_dir, 'dst')
|
||||||
dst_link = os.path.join(tmp_dir, 'dst_link')
|
dst_link = os.path.join(tmp_dir, 'dst_link')
|
||||||
link = os.path.join(tmp_dir, 'link')
|
link = os.path.join(tmp_dir, 'link')
|
||||||
write_file(src, 'foo')
|
create_file(src, 'foo')
|
||||||
os.symlink(src, link)
|
os.symlink(src, link)
|
||||||
# don't follow
|
# don't follow
|
||||||
shutil.copyfile(link, dst_link, follow_symlinks=False)
|
shutil.copyfile(link, dst_link, follow_symlinks=False)
|
||||||
@ -1514,8 +1511,7 @@ class TestCopy(BaseTest, unittest.TestCase):
|
|||||||
src = os.path.join(TESTFN, 'cheese')
|
src = os.path.join(TESTFN, 'cheese')
|
||||||
dst = os.path.join(TESTFN, 'shop')
|
dst = os.path.join(TESTFN, 'shop')
|
||||||
try:
|
try:
|
||||||
with open(src, 'w', encoding='utf-8') as f:
|
create_file(src, 'cheddar')
|
||||||
f.write('cheddar')
|
|
||||||
try:
|
try:
|
||||||
os.link(src, dst)
|
os.link(src, dst)
|
||||||
except PermissionError as e:
|
except PermissionError as e:
|
||||||
@ -1534,8 +1530,7 @@ class TestCopy(BaseTest, unittest.TestCase):
|
|||||||
src = os.path.join(TESTFN, 'cheese')
|
src = os.path.join(TESTFN, 'cheese')
|
||||||
dst = os.path.join(TESTFN, 'shop')
|
dst = os.path.join(TESTFN, 'shop')
|
||||||
try:
|
try:
|
||||||
with open(src, 'w', encoding='utf-8') as f:
|
create_file(src, 'cheddar')
|
||||||
f.write('cheddar')
|
|
||||||
# Using `src` here would mean we end up with a symlink pointing
|
# Using `src` here would mean we end up with a symlink pointing
|
||||||
# to TESTFN/TESTFN/cheese, while it should point at
|
# to TESTFN/TESTFN/cheese, while it should point at
|
||||||
# TESTFN/cheese.
|
# TESTFN/cheese.
|
||||||
@ -1570,7 +1565,7 @@ class TestCopy(BaseTest, unittest.TestCase):
|
|||||||
dst_dir = self.mkdtemp()
|
dst_dir = self.mkdtemp()
|
||||||
dst_file = os.path.join(dst_dir, 'bar')
|
dst_file = os.path.join(dst_dir, 'bar')
|
||||||
src_file = os.path.join(src_dir, 'foo')
|
src_file = os.path.join(src_dir, 'foo')
|
||||||
write_file(src_file, 'foo')
|
create_file(src_file, 'foo')
|
||||||
rv = shutil.copyfile(src_file, dst_file)
|
rv = shutil.copyfile(src_file, dst_file)
|
||||||
self.assertTrue(os.path.exists(rv))
|
self.assertTrue(os.path.exists(rv))
|
||||||
self.assertEqual(read_file(src_file), read_file(dst_file))
|
self.assertEqual(read_file(src_file), read_file(dst_file))
|
||||||
@ -1580,7 +1575,7 @@ class TestCopy(BaseTest, unittest.TestCase):
|
|||||||
# are the same.
|
# are the same.
|
||||||
src_dir = self.mkdtemp()
|
src_dir = self.mkdtemp()
|
||||||
src_file = os.path.join(src_dir, 'foo')
|
src_file = os.path.join(src_dir, 'foo')
|
||||||
write_file(src_file, 'foo')
|
create_file(src_file, 'foo')
|
||||||
self.assertRaises(SameFileError, shutil.copyfile, src_file, src_file)
|
self.assertRaises(SameFileError, shutil.copyfile, src_file, src_file)
|
||||||
# But Error should work too, to stay backward compatible.
|
# But Error should work too, to stay backward compatible.
|
||||||
self.assertRaises(Error, shutil.copyfile, src_file, src_file)
|
self.assertRaises(Error, shutil.copyfile, src_file, src_file)
|
||||||
@ -1597,7 +1592,7 @@ class TestCopy(BaseTest, unittest.TestCase):
|
|||||||
src_dir = self.mkdtemp()
|
src_dir = self.mkdtemp()
|
||||||
src_file = os.path.join(src_dir, 'foo')
|
src_file = os.path.join(src_dir, 'foo')
|
||||||
dst = os.path.join(src_dir, 'does_not_exist/')
|
dst = os.path.join(src_dir, 'does_not_exist/')
|
||||||
write_file(src_file, 'foo')
|
create_file(src_file, 'foo')
|
||||||
self.assertRaises(FileNotFoundError, shutil.copyfile, src_file, dst)
|
self.assertRaises(FileNotFoundError, shutil.copyfile, src_file, dst)
|
||||||
|
|
||||||
def test_copyfile_copy_dir(self):
|
def test_copyfile_copy_dir(self):
|
||||||
@ -1608,7 +1603,7 @@ class TestCopy(BaseTest, unittest.TestCase):
|
|||||||
src_file = os.path.join(src_dir, 'foo')
|
src_file = os.path.join(src_dir, 'foo')
|
||||||
dir2 = self.mkdtemp()
|
dir2 = self.mkdtemp()
|
||||||
dst = os.path.join(src_dir, 'does_not_exist/')
|
dst = os.path.join(src_dir, 'does_not_exist/')
|
||||||
write_file(src_file, 'foo')
|
create_file(src_file, 'foo')
|
||||||
if sys.platform == "win32":
|
if sys.platform == "win32":
|
||||||
err = PermissionError
|
err = PermissionError
|
||||||
else:
|
else:
|
||||||
@ -1634,13 +1629,13 @@ class TestArchives(BaseTest, unittest.TestCase):
|
|||||||
root_dir = self.mkdtemp()
|
root_dir = self.mkdtemp()
|
||||||
dist = os.path.join(root_dir, base_dir)
|
dist = os.path.join(root_dir, base_dir)
|
||||||
os.makedirs(dist, exist_ok=True)
|
os.makedirs(dist, exist_ok=True)
|
||||||
write_file((dist, 'file1'), 'xxx')
|
create_file((dist, 'file1'), 'xxx')
|
||||||
write_file((dist, 'file2'), 'xxx')
|
create_file((dist, 'file2'), 'xxx')
|
||||||
os.mkdir(os.path.join(dist, 'sub'))
|
os.mkdir(os.path.join(dist, 'sub'))
|
||||||
write_file((dist, 'sub', 'file3'), 'xxx')
|
create_file((dist, 'sub', 'file3'), 'xxx')
|
||||||
os.mkdir(os.path.join(dist, 'sub2'))
|
os.mkdir(os.path.join(dist, 'sub2'))
|
||||||
if base_dir:
|
if base_dir:
|
||||||
write_file((root_dir, 'outer'), 'xxx')
|
create_file((root_dir, 'outer'), 'xxx')
|
||||||
return root_dir, base_dir
|
return root_dir, base_dir
|
||||||
|
|
||||||
@support.requires_zlib()
|
@support.requires_zlib()
|
||||||
@ -2221,7 +2216,7 @@ class TestMisc(BaseTest, unittest.TestCase):
|
|||||||
dirname = self.mkdtemp()
|
dirname = self.mkdtemp()
|
||||||
filename = tempfile.mktemp(dir=dirname)
|
filename = tempfile.mktemp(dir=dirname)
|
||||||
linkname = os.path.join(dirname, "chown_link")
|
linkname = os.path.join(dirname, "chown_link")
|
||||||
write_file(filename, 'testing chown function')
|
create_file(filename, 'testing chown function')
|
||||||
os.symlink(filename, linkname)
|
os.symlink(filename, linkname)
|
||||||
|
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
@ -2314,37 +2309,41 @@ class TestMisc(BaseTest, unittest.TestCase):
|
|||||||
class TestWhich(BaseTest, unittest.TestCase):
|
class TestWhich(BaseTest, unittest.TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.temp_dir = self.mkdtemp(prefix="Tmp")
|
temp_dir = self.mkdtemp(prefix="Tmp")
|
||||||
|
base_dir = os.path.join(temp_dir, TESTFN + '-basedir')
|
||||||
|
os.mkdir(base_dir)
|
||||||
|
self.dir = os.path.join(base_dir, TESTFN + '-dir')
|
||||||
|
os.mkdir(self.dir)
|
||||||
|
self.other_dir = os.path.join(base_dir, TESTFN + '-dir2')
|
||||||
|
os.mkdir(self.other_dir)
|
||||||
# Give the temp_file an ".exe" suffix for all.
|
# Give the temp_file an ".exe" suffix for all.
|
||||||
# It's needed on Windows and not harmful on other platforms.
|
# It's needed on Windows and not harmful on other platforms.
|
||||||
self.temp_file = tempfile.NamedTemporaryFile(dir=self.temp_dir,
|
self.file = TESTFN + '.Exe'
|
||||||
prefix="Tmp",
|
self.filepath = os.path.join(self.dir, self.file)
|
||||||
suffix=".Exe")
|
self.create_file(self.filepath)
|
||||||
os.chmod(self.temp_file.name, stat.S_IXUSR)
|
|
||||||
self.addCleanup(self.temp_file.close)
|
|
||||||
self.dir, self.file = os.path.split(self.temp_file.name)
|
|
||||||
self.env_path = self.dir
|
self.env_path = self.dir
|
||||||
self.curdir = os.curdir
|
self.curdir = os.curdir
|
||||||
self.ext = ".EXE"
|
self.ext = ".EXE"
|
||||||
|
|
||||||
def to_text_type(self, s):
|
to_text_type = staticmethod(os.fsdecode)
|
||||||
'''
|
|
||||||
In this class we're testing with str, so convert s to a str
|
def create_file(self, path):
|
||||||
'''
|
create_file(path)
|
||||||
if isinstance(s, bytes):
|
os.chmod(path, 0o755)
|
||||||
return s.decode()
|
|
||||||
return s
|
def assertNormEqual(self, actual, expected):
|
||||||
|
self.assertEqual(os.path.normcase(actual), os.path.normcase(expected))
|
||||||
|
|
||||||
def test_basic(self):
|
def test_basic(self):
|
||||||
# Given an EXE in a directory, it should be returned.
|
# Given an EXE in a directory, it should be returned.
|
||||||
rv = shutil.which(self.file, path=self.dir)
|
rv = shutil.which(self.file, path=self.dir)
|
||||||
self.assertEqual(rv, self.temp_file.name)
|
self.assertEqual(rv, self.filepath)
|
||||||
|
|
||||||
def test_absolute_cmd(self):
|
def test_absolute_cmd(self):
|
||||||
# When given the fully qualified path to an executable that exists,
|
# When given the fully qualified path to an executable that exists,
|
||||||
# it should be returned.
|
# it should be returned.
|
||||||
rv = shutil.which(self.temp_file.name, path=self.temp_dir)
|
rv = shutil.which(self.filepath, path=self.other_dir)
|
||||||
self.assertEqual(rv, self.temp_file.name)
|
self.assertEqual(rv, self.filepath)
|
||||||
|
|
||||||
def test_relative_cmd(self):
|
def test_relative_cmd(self):
|
||||||
# When given the relative path with a directory part to an executable
|
# When given the relative path with a directory part to an executable
|
||||||
@ -2352,7 +2351,7 @@ class TestWhich(BaseTest, unittest.TestCase):
|
|||||||
base_dir, tail_dir = os.path.split(self.dir)
|
base_dir, tail_dir = os.path.split(self.dir)
|
||||||
relpath = os.path.join(tail_dir, self.file)
|
relpath = os.path.join(tail_dir, self.file)
|
||||||
with os_helper.change_cwd(path=base_dir):
|
with os_helper.change_cwd(path=base_dir):
|
||||||
rv = shutil.which(relpath, path=self.temp_dir)
|
rv = shutil.which(relpath, path=self.other_dir)
|
||||||
self.assertEqual(rv, relpath)
|
self.assertEqual(rv, relpath)
|
||||||
# But it shouldn't be searched in PATH directories (issue #16957).
|
# But it shouldn't be searched in PATH directories (issue #16957).
|
||||||
with os_helper.change_cwd(path=self.dir):
|
with os_helper.change_cwd(path=self.dir):
|
||||||
@ -2363,9 +2362,8 @@ class TestWhich(BaseTest, unittest.TestCase):
|
|||||||
"test is for non win32")
|
"test is for non win32")
|
||||||
def test_cwd_non_win32(self):
|
def test_cwd_non_win32(self):
|
||||||
# Issue #16957
|
# Issue #16957
|
||||||
base_dir = os.path.dirname(self.dir)
|
|
||||||
with os_helper.change_cwd(path=self.dir):
|
with os_helper.change_cwd(path=self.dir):
|
||||||
rv = shutil.which(self.file, path=base_dir)
|
rv = shutil.which(self.file, path=self.other_dir)
|
||||||
# non-win32: shouldn't match in the current directory.
|
# non-win32: shouldn't match in the current directory.
|
||||||
self.assertIsNone(rv)
|
self.assertIsNone(rv)
|
||||||
|
|
||||||
@ -2375,57 +2373,32 @@ class TestWhich(BaseTest, unittest.TestCase):
|
|||||||
base_dir = os.path.dirname(self.dir)
|
base_dir = os.path.dirname(self.dir)
|
||||||
with os_helper.change_cwd(path=self.dir):
|
with os_helper.change_cwd(path=self.dir):
|
||||||
with unittest.mock.patch('shutil._win_path_needs_curdir', return_value=True):
|
with unittest.mock.patch('shutil._win_path_needs_curdir', return_value=True):
|
||||||
rv = shutil.which(self.file, path=base_dir)
|
rv = shutil.which(self.file, path=self.other_dir)
|
||||||
# Current directory implicitly on PATH
|
# Current directory implicitly on PATH
|
||||||
self.assertEqual(rv, os.path.join(self.curdir, self.file))
|
self.assertEqual(rv, os.path.join(self.curdir, self.file))
|
||||||
with unittest.mock.patch('shutil._win_path_needs_curdir', return_value=False):
|
with unittest.mock.patch('shutil._win_path_needs_curdir', return_value=False):
|
||||||
rv = shutil.which(self.file, path=base_dir)
|
rv = shutil.which(self.file, path=self.other_dir)
|
||||||
# Current directory not on PATH
|
# Current directory not on PATH
|
||||||
self.assertIsNone(rv)
|
self.assertIsNone(rv)
|
||||||
|
|
||||||
@unittest.skipUnless(sys.platform == "win32",
|
@unittest.skipUnless(sys.platform == "win32",
|
||||||
"test is for win32")
|
"test is for win32")
|
||||||
def test_cwd_win32_added_before_all_other_path(self):
|
def test_cwd_win32_added_before_all_other_path(self):
|
||||||
base_dir = pathlib.Path(os.fsdecode(self.dir))
|
other_file_path = os.path.join(self.other_dir, self.file)
|
||||||
|
self.create_file(other_file_path)
|
||||||
elsewhere_in_path_dir = base_dir / 'dir1'
|
with unittest.mock.patch('shutil._win_path_needs_curdir', return_value=True):
|
||||||
elsewhere_in_path_dir.mkdir()
|
with os_helper.change_cwd(path=self.dir):
|
||||||
match_elsewhere_in_path = elsewhere_in_path_dir / 'hello.exe'
|
rv = shutil.which(self.file, path=self.other_dir)
|
||||||
match_elsewhere_in_path.touch()
|
self.assertEqual(rv, os.path.join(self.curdir, self.file))
|
||||||
|
with os_helper.change_cwd(path=self.other_dir):
|
||||||
exe_in_cwd = base_dir / 'hello.exe'
|
rv = shutil.which(self.file, path=self.dir)
|
||||||
exe_in_cwd.touch()
|
self.assertEqual(rv, os.path.join(self.curdir, self.file))
|
||||||
|
|
||||||
with os_helper.change_cwd(path=base_dir):
|
|
||||||
with unittest.mock.patch('shutil._win_path_needs_curdir', return_value=True):
|
|
||||||
rv = shutil.which('hello.exe', path=elsewhere_in_path_dir)
|
|
||||||
|
|
||||||
self.assertEqual(os.path.abspath(rv), os.path.abspath(exe_in_cwd))
|
|
||||||
|
|
||||||
@unittest.skipUnless(sys.platform == "win32",
|
|
||||||
"test is for win32")
|
|
||||||
def test_pathext_match_before_path_full_match(self):
|
|
||||||
base_dir = pathlib.Path(os.fsdecode(self.dir))
|
|
||||||
dir1 = base_dir / 'dir1'
|
|
||||||
dir2 = base_dir / 'dir2'
|
|
||||||
dir1.mkdir()
|
|
||||||
dir2.mkdir()
|
|
||||||
|
|
||||||
pathext_match = dir1 / 'hello.com.exe'
|
|
||||||
path_match = dir2 / 'hello.com'
|
|
||||||
pathext_match.touch()
|
|
||||||
path_match.touch()
|
|
||||||
|
|
||||||
test_path = os.pathsep.join([str(dir1), str(dir2)])
|
|
||||||
assert os.path.basename(shutil.which(
|
|
||||||
'hello.com', path=test_path, mode = os.F_OK
|
|
||||||
)).lower() == 'hello.com.exe'
|
|
||||||
|
|
||||||
@os_helper.skip_if_dac_override
|
@os_helper.skip_if_dac_override
|
||||||
def test_non_matching_mode(self):
|
def test_non_matching_mode(self):
|
||||||
# Set the file read-only and ask for writeable files.
|
# Set the file read-only and ask for writeable files.
|
||||||
os.chmod(self.temp_file.name, stat.S_IREAD)
|
os.chmod(self.filepath, stat.S_IREAD)
|
||||||
if os.access(self.temp_file.name, os.W_OK):
|
if os.access(self.filepath, os.W_OK):
|
||||||
self.skipTest("can't set the file read-only")
|
self.skipTest("can't set the file read-only")
|
||||||
rv = shutil.which(self.file, path=self.dir, mode=os.W_OK)
|
rv = shutil.which(self.file, path=self.dir, mode=os.W_OK)
|
||||||
self.assertIsNone(rv)
|
self.assertIsNone(rv)
|
||||||
@ -2447,13 +2420,13 @@ class TestWhich(BaseTest, unittest.TestCase):
|
|||||||
# Ask for the file without the ".exe" extension, then ensure that
|
# Ask for the file without the ".exe" extension, then ensure that
|
||||||
# it gets found properly with the extension.
|
# it gets found properly with the extension.
|
||||||
rv = shutil.which(self.file[:-4], path=self.dir)
|
rv = shutil.which(self.file[:-4], path=self.dir)
|
||||||
self.assertEqual(rv, self.temp_file.name[:-4] + self.ext)
|
self.assertEqual(rv, self.filepath[:-4] + self.ext)
|
||||||
|
|
||||||
def test_environ_path(self):
|
def test_environ_path(self):
|
||||||
with os_helper.EnvironmentVarGuard() as env:
|
with os_helper.EnvironmentVarGuard() as env:
|
||||||
env['PATH'] = self.env_path
|
env['PATH'] = self.env_path
|
||||||
rv = shutil.which(self.file)
|
rv = shutil.which(self.file)
|
||||||
self.assertEqual(rv, self.temp_file.name)
|
self.assertEqual(rv, self.filepath)
|
||||||
|
|
||||||
def test_environ_path_empty(self):
|
def test_environ_path_empty(self):
|
||||||
# PATH='': no match
|
# PATH='': no match
|
||||||
@ -2467,12 +2440,9 @@ class TestWhich(BaseTest, unittest.TestCase):
|
|||||||
self.assertIsNone(rv)
|
self.assertIsNone(rv)
|
||||||
|
|
||||||
def test_environ_path_cwd(self):
|
def test_environ_path_cwd(self):
|
||||||
expected_cwd = os.path.basename(self.temp_file.name)
|
expected_cwd = self.file
|
||||||
if sys.platform == "win32":
|
if sys.platform == "win32":
|
||||||
curdir = os.curdir
|
expected_cwd = os.path.join(self.curdir, expected_cwd)
|
||||||
if isinstance(expected_cwd, bytes):
|
|
||||||
curdir = os.fsencode(curdir)
|
|
||||||
expected_cwd = os.path.join(curdir, expected_cwd)
|
|
||||||
|
|
||||||
# PATH=':': explicitly looks in the current directory
|
# PATH=':': explicitly looks in the current directory
|
||||||
with os_helper.EnvironmentVarGuard() as env:
|
with os_helper.EnvironmentVarGuard() as env:
|
||||||
@ -2497,14 +2467,14 @@ class TestWhich(BaseTest, unittest.TestCase):
|
|||||||
create=True), \
|
create=True), \
|
||||||
support.swap_attr(os, 'defpath', self.dir):
|
support.swap_attr(os, 'defpath', self.dir):
|
||||||
rv = shutil.which(self.file)
|
rv = shutil.which(self.file)
|
||||||
self.assertEqual(rv, self.temp_file.name)
|
self.assertEqual(rv, self.filepath)
|
||||||
|
|
||||||
# with confstr
|
# with confstr
|
||||||
with unittest.mock.patch('os.confstr', return_value=self.dir, \
|
with unittest.mock.patch('os.confstr', return_value=self.dir, \
|
||||||
create=True), \
|
create=True), \
|
||||||
support.swap_attr(os, 'defpath', ''):
|
support.swap_attr(os, 'defpath', ''):
|
||||||
rv = shutil.which(self.file)
|
rv = shutil.which(self.file)
|
||||||
self.assertEqual(rv, self.temp_file.name)
|
self.assertEqual(rv, self.filepath)
|
||||||
|
|
||||||
def test_empty_path(self):
|
def test_empty_path(self):
|
||||||
base_dir = os.path.dirname(self.dir)
|
base_dir = os.path.dirname(self.dir)
|
||||||
@ -2522,50 +2492,88 @@ class TestWhich(BaseTest, unittest.TestCase):
|
|||||||
|
|
||||||
@unittest.skipUnless(sys.platform == "win32", 'test specific to Windows')
|
@unittest.skipUnless(sys.platform == "win32", 'test specific to Windows')
|
||||||
def test_pathext(self):
|
def test_pathext(self):
|
||||||
ext = self.to_text_type(".xyz")
|
ext = '.xyz'
|
||||||
temp_filexyz = tempfile.NamedTemporaryFile(dir=self.temp_dir,
|
cmd = self.to_text_type(TESTFN2)
|
||||||
prefix=self.to_text_type("Tmp2"), suffix=ext)
|
cmdext = cmd + self.to_text_type(ext)
|
||||||
os.chmod(temp_filexyz.name, stat.S_IXUSR)
|
filepath = os.path.join(self.dir, cmdext)
|
||||||
self.addCleanup(temp_filexyz.close)
|
self.create_file(filepath)
|
||||||
|
|
||||||
# strip path and extension
|
|
||||||
program = os.path.basename(temp_filexyz.name)
|
|
||||||
program = os.path.splitext(program)[0]
|
|
||||||
|
|
||||||
with os_helper.EnvironmentVarGuard() as env:
|
with os_helper.EnvironmentVarGuard() as env:
|
||||||
env['PATHEXT'] = ext if isinstance(ext, str) else ext.decode()
|
env['PATHEXT'] = ext
|
||||||
rv = shutil.which(program, path=self.temp_dir)
|
self.assertEqual(shutil.which(cmd, path=self.dir), filepath)
|
||||||
self.assertEqual(rv, temp_filexyz.name)
|
self.assertEqual(shutil.which(cmdext, path=self.dir), filepath)
|
||||||
|
|
||||||
# Issue 40592: See https://bugs.python.org/issue40592
|
# Issue 40592: See https://bugs.python.org/issue40592
|
||||||
@unittest.skipUnless(sys.platform == "win32", 'test specific to Windows')
|
@unittest.skipUnless(sys.platform == "win32", 'test specific to Windows')
|
||||||
def test_pathext_with_empty_str(self):
|
def test_pathext_with_empty_str(self):
|
||||||
ext = self.to_text_type(".xyz")
|
ext = '.xyz'
|
||||||
temp_filexyz = tempfile.NamedTemporaryFile(dir=self.temp_dir,
|
cmd = self.to_text_type(TESTFN2)
|
||||||
prefix=self.to_text_type("Tmp2"), suffix=ext)
|
cmdext = cmd + self.to_text_type(ext)
|
||||||
self.addCleanup(temp_filexyz.close)
|
filepath = os.path.join(self.dir, cmdext)
|
||||||
|
self.create_file(filepath)
|
||||||
# strip path and extension
|
|
||||||
program = os.path.basename(temp_filexyz.name)
|
|
||||||
program = os.path.splitext(program)[0]
|
|
||||||
|
|
||||||
with os_helper.EnvironmentVarGuard() as env:
|
with os_helper.EnvironmentVarGuard() as env:
|
||||||
env['PATHEXT'] = f"{ext if isinstance(ext, str) else ext.decode()};" # note the ;
|
env['PATHEXT'] = ext + ';' # note the ;
|
||||||
rv = shutil.which(program, path=self.temp_dir)
|
self.assertEqual(shutil.which(cmd, path=self.dir), filepath)
|
||||||
self.assertEqual(rv, temp_filexyz.name)
|
self.assertEqual(shutil.which(cmdext, path=self.dir), filepath)
|
||||||
|
|
||||||
|
@unittest.skipUnless(sys.platform == "win32", 'test specific to Windows')
|
||||||
|
def test_pathext_with_multidot_extension(self):
|
||||||
|
ext = '.foo.bar'
|
||||||
|
cmd = self.to_text_type(TESTFN2)
|
||||||
|
cmdext = cmd + self.to_text_type(ext)
|
||||||
|
filepath = os.path.join(self.dir, cmdext)
|
||||||
|
self.create_file(filepath)
|
||||||
|
with os_helper.EnvironmentVarGuard() as env:
|
||||||
|
env['PATHEXT'] = ext
|
||||||
|
self.assertEqual(shutil.which(cmd, path=self.dir), filepath)
|
||||||
|
self.assertEqual(shutil.which(cmdext, path=self.dir), filepath)
|
||||||
|
|
||||||
|
@unittest.skipUnless(sys.platform == "win32", 'test specific to Windows')
|
||||||
|
def test_pathext_with_null_extension(self):
|
||||||
|
cmd = self.to_text_type(TESTFN2)
|
||||||
|
cmddot = cmd + self.to_text_type('.')
|
||||||
|
filepath = os.path.join(self.dir, cmd)
|
||||||
|
self.create_file(filepath)
|
||||||
|
with os_helper.EnvironmentVarGuard() as env:
|
||||||
|
env['PATHEXT'] = '.xyz'
|
||||||
|
self.assertIsNone(shutil.which(cmd, path=self.dir))
|
||||||
|
self.assertIsNone(shutil.which(cmddot, path=self.dir))
|
||||||
|
env['PATHEXT'] = '.xyz;.' # note the .
|
||||||
|
self.assertEqual(shutil.which(cmd, path=self.dir), filepath)
|
||||||
|
self.assertEqual(shutil.which(cmddot, path=self.dir),
|
||||||
|
filepath + self.to_text_type('.'))
|
||||||
|
env['PATHEXT'] = '.xyz;..' # multiple dots
|
||||||
|
self.assertEqual(shutil.which(cmd, path=self.dir), filepath)
|
||||||
|
self.assertEqual(shutil.which(cmddot, path=self.dir),
|
||||||
|
filepath + self.to_text_type('.'))
|
||||||
|
|
||||||
|
@unittest.skipUnless(sys.platform == "win32", 'test specific to Windows')
|
||||||
|
def test_pathext_extension_ends_with_dot(self):
|
||||||
|
ext = '.xyz'
|
||||||
|
cmd = self.to_text_type(TESTFN2)
|
||||||
|
cmdext = cmd + self.to_text_type(ext)
|
||||||
|
dot = self.to_text_type('.')
|
||||||
|
filepath = os.path.join(self.dir, cmdext)
|
||||||
|
self.create_file(filepath)
|
||||||
|
with os_helper.EnvironmentVarGuard() as env:
|
||||||
|
env['PATHEXT'] = ext + '.'
|
||||||
|
self.assertEqual(shutil.which(cmd, path=self.dir), filepath) # cmd.exe hangs here
|
||||||
|
self.assertEqual(shutil.which(cmdext, path=self.dir), filepath)
|
||||||
|
self.assertIsNone(shutil.which(cmd + dot, path=self.dir))
|
||||||
|
self.assertIsNone(shutil.which(cmdext + dot, path=self.dir))
|
||||||
|
|
||||||
# See GH-75586
|
# See GH-75586
|
||||||
@unittest.skipUnless(sys.platform == "win32", 'test specific to Windows')
|
@unittest.skipUnless(sys.platform == "win32", 'test specific to Windows')
|
||||||
def test_pathext_applied_on_files_in_path(self):
|
def test_pathext_applied_on_files_in_path(self):
|
||||||
|
ext = '.xyz'
|
||||||
|
cmd = self.to_text_type(TESTFN2)
|
||||||
|
cmdext = cmd + self.to_text_type(ext)
|
||||||
|
filepath = os.path.join(self.dir, cmdext)
|
||||||
|
self.create_file(filepath)
|
||||||
with os_helper.EnvironmentVarGuard() as env:
|
with os_helper.EnvironmentVarGuard() as env:
|
||||||
env["PATH"] = self.temp_dir if isinstance(self.temp_dir, str) else self.temp_dir.decode()
|
env["PATH"] = os.fsdecode(self.dir)
|
||||||
env["PATHEXT"] = ".test"
|
env["PATHEXT"] = ext
|
||||||
|
self.assertEqual(shutil.which(cmd), filepath)
|
||||||
test_path = os.path.join(self.temp_dir, self.to_text_type("test_program.test"))
|
self.assertEqual(shutil.which(cmdext), filepath)
|
||||||
open(test_path, 'w').close()
|
|
||||||
os.chmod(test_path, 0o755)
|
|
||||||
|
|
||||||
self.assertEqual(shutil.which(self.to_text_type("test_program")), test_path)
|
|
||||||
|
|
||||||
# See GH-75586
|
# See GH-75586
|
||||||
@unittest.skipUnless(sys.platform == "win32", 'test specific to Windows')
|
@unittest.skipUnless(sys.platform == "win32", 'test specific to Windows')
|
||||||
@ -2581,49 +2589,107 @@ class TestWhich(BaseTest, unittest.TestCase):
|
|||||||
self.assertFalse(shutil._win_path_needs_curdir('dontcare', os.X_OK))
|
self.assertFalse(shutil._win_path_needs_curdir('dontcare', os.X_OK))
|
||||||
need_curdir_mock.assert_called_once_with('dontcare')
|
need_curdir_mock.assert_called_once_with('dontcare')
|
||||||
|
|
||||||
# See GH-109590
|
|
||||||
@unittest.skipUnless(sys.platform == "win32", 'test specific to Windows')
|
@unittest.skipUnless(sys.platform == "win32", 'test specific to Windows')
|
||||||
def test_pathext_preferred_for_execute(self):
|
def test_same_dir_with_pathext_extension(self):
|
||||||
with os_helper.EnvironmentVarGuard() as env:
|
cmd = self.file # with .exe extension
|
||||||
env["PATH"] = self.temp_dir if isinstance(self.temp_dir, str) else self.temp_dir.decode()
|
# full match
|
||||||
env["PATHEXT"] = ".test"
|
self.assertNormEqual(shutil.which(cmd, path=self.dir), self.filepath)
|
||||||
|
self.assertNormEqual(shutil.which(cmd, path=self.dir, mode=os.F_OK),
|
||||||
|
self.filepath)
|
||||||
|
|
||||||
exe = os.path.join(self.temp_dir, self.to_text_type("test.exe"))
|
cmd2 = cmd + self.to_text_type('.com') # with .exe.com extension
|
||||||
open(exe, 'w').close()
|
other_file_path = os.path.join(self.dir, cmd2)
|
||||||
os.chmod(exe, 0o755)
|
self.create_file(other_file_path)
|
||||||
|
|
||||||
# default behavior allows a direct match if nothing in PATHEXT matches
|
# full match
|
||||||
self.assertEqual(shutil.which(self.to_text_type("test.exe")), exe)
|
self.assertNormEqual(shutil.which(cmd, path=self.dir), self.filepath)
|
||||||
|
self.assertNormEqual(shutil.which(cmd, path=self.dir, mode=os.F_OK),
|
||||||
|
self.filepath)
|
||||||
|
self.assertNormEqual(shutil.which(cmd2, path=self.dir), other_file_path)
|
||||||
|
self.assertNormEqual(shutil.which(cmd2, path=self.dir, mode=os.F_OK),
|
||||||
|
other_file_path)
|
||||||
|
|
||||||
dot_test = os.path.join(self.temp_dir, self.to_text_type("test.exe.test"))
|
|
||||||
open(dot_test, 'w').close()
|
|
||||||
os.chmod(dot_test, 0o755)
|
|
||||||
|
|
||||||
# now we have a PATHEXT match, so it take precedence
|
|
||||||
self.assertEqual(shutil.which(self.to_text_type("test.exe")), dot_test)
|
|
||||||
|
|
||||||
# but if we don't use os.X_OK we don't change the order based off PATHEXT
|
|
||||||
# and therefore get the direct match.
|
|
||||||
self.assertEqual(shutil.which(self.to_text_type("test.exe"), mode=os.F_OK), exe)
|
|
||||||
|
|
||||||
# See GH-109590
|
|
||||||
@unittest.skipUnless(sys.platform == "win32", 'test specific to Windows')
|
@unittest.skipUnless(sys.platform == "win32", 'test specific to Windows')
|
||||||
def test_pathext_given_extension_preferred(self):
|
def test_same_dir_without_pathext_extension(self):
|
||||||
with os_helper.EnvironmentVarGuard() as env:
|
cmd = self.file[:-4] # without .exe extension
|
||||||
env["PATH"] = self.temp_dir if isinstance(self.temp_dir, str) else self.temp_dir.decode()
|
# pathext match
|
||||||
env["PATHEXT"] = ".exe2;.exe"
|
self.assertNormEqual(shutil.which(cmd, path=self.dir), self.filepath)
|
||||||
|
self.assertNormEqual(shutil.which(cmd, path=self.dir, mode=os.F_OK),
|
||||||
|
self.filepath)
|
||||||
|
|
||||||
exe = os.path.join(self.temp_dir, self.to_text_type("test.exe"))
|
# without extension
|
||||||
open(exe, 'w').close()
|
other_file_path = os.path.join(self.dir, cmd)
|
||||||
os.chmod(exe, 0o755)
|
self.create_file(other_file_path)
|
||||||
|
|
||||||
exe2 = os.path.join(self.temp_dir, self.to_text_type("test.exe2"))
|
# pathext match if mode contains X_OK
|
||||||
open(exe2, 'w').close()
|
self.assertNormEqual(shutil.which(cmd, path=self.dir), self.filepath)
|
||||||
os.chmod(exe2, 0o755)
|
# full match
|
||||||
|
self.assertNormEqual(shutil.which(cmd, path=self.dir, mode=os.F_OK),
|
||||||
|
other_file_path)
|
||||||
|
self.assertNormEqual(shutil.which(self.file, path=self.dir), self.filepath)
|
||||||
|
self.assertNormEqual(shutil.which(self.file, path=self.dir, mode=os.F_OK),
|
||||||
|
self.filepath)
|
||||||
|
|
||||||
# even though .exe2 is preferred in PATHEXT, we matched directly to test.exe
|
@unittest.skipUnless(sys.platform == "win32", 'test specific to Windows')
|
||||||
self.assertEqual(shutil.which(self.to_text_type("test.exe")), exe)
|
def test_dir_order_with_pathext_extension(self):
|
||||||
self.assertEqual(shutil.which(self.to_text_type("test")), exe2)
|
cmd = self.file # with .exe extension
|
||||||
|
search_path = os.pathsep.join([os.fsdecode(self.other_dir),
|
||||||
|
os.fsdecode(self.dir)])
|
||||||
|
# full match in the second directory
|
||||||
|
self.assertNormEqual(shutil.which(cmd, path=search_path), self.filepath)
|
||||||
|
self.assertNormEqual(shutil.which(cmd, path=search_path, mode=os.F_OK),
|
||||||
|
self.filepath)
|
||||||
|
|
||||||
|
cmd2 = cmd + self.to_text_type('.com') # with .exe.com extension
|
||||||
|
other_file_path = os.path.join(self.other_dir, cmd2)
|
||||||
|
self.create_file(other_file_path)
|
||||||
|
|
||||||
|
# pathext match in the first directory
|
||||||
|
self.assertNormEqual(shutil.which(cmd, path=search_path), other_file_path)
|
||||||
|
self.assertNormEqual(shutil.which(cmd, path=search_path, mode=os.F_OK),
|
||||||
|
other_file_path)
|
||||||
|
# full match in the first directory
|
||||||
|
self.assertNormEqual(shutil.which(cmd2, path=search_path), other_file_path)
|
||||||
|
self.assertNormEqual(shutil.which(cmd2, path=search_path, mode=os.F_OK),
|
||||||
|
other_file_path)
|
||||||
|
|
||||||
|
# full match in the first directory
|
||||||
|
search_path = os.pathsep.join([os.fsdecode(self.dir),
|
||||||
|
os.fsdecode(self.other_dir)])
|
||||||
|
self.assertEqual(shutil.which(cmd, path=search_path), self.filepath)
|
||||||
|
self.assertEqual(shutil.which(cmd, path=search_path, mode=os.F_OK),
|
||||||
|
self.filepath)
|
||||||
|
|
||||||
|
@unittest.skipUnless(sys.platform == "win32", 'test specific to Windows')
|
||||||
|
def test_dir_order_without_pathext_extension(self):
|
||||||
|
cmd = self.file[:-4] # without .exe extension
|
||||||
|
search_path = os.pathsep.join([os.fsdecode(self.other_dir),
|
||||||
|
os.fsdecode(self.dir)])
|
||||||
|
# pathext match in the second directory
|
||||||
|
self.assertNormEqual(shutil.which(cmd, path=search_path), self.filepath)
|
||||||
|
self.assertNormEqual(shutil.which(cmd, path=search_path, mode=os.F_OK),
|
||||||
|
self.filepath)
|
||||||
|
|
||||||
|
# without extension
|
||||||
|
other_file_path = os.path.join(self.other_dir, cmd)
|
||||||
|
self.create_file(other_file_path)
|
||||||
|
|
||||||
|
# pathext match in the second directory
|
||||||
|
self.assertNormEqual(shutil.which(cmd, path=search_path), self.filepath)
|
||||||
|
# full match in the first directory
|
||||||
|
self.assertNormEqual(shutil.which(cmd, path=search_path, mode=os.F_OK),
|
||||||
|
other_file_path)
|
||||||
|
# full match in the second directory
|
||||||
|
self.assertNormEqual(shutil.which(self.file, path=search_path), self.filepath)
|
||||||
|
self.assertNormEqual(shutil.which(self.file, path=search_path, mode=os.F_OK),
|
||||||
|
self.filepath)
|
||||||
|
|
||||||
|
# pathext match in the first directory
|
||||||
|
search_path = os.pathsep.join([os.fsdecode(self.dir),
|
||||||
|
os.fsdecode(self.other_dir)])
|
||||||
|
self.assertNormEqual(shutil.which(cmd, path=search_path), self.filepath)
|
||||||
|
self.assertNormEqual(shutil.which(cmd, path=search_path, mode=os.F_OK),
|
||||||
|
self.filepath)
|
||||||
|
|
||||||
|
|
||||||
class TestWhichBytes(TestWhich):
|
class TestWhichBytes(TestWhich):
|
||||||
@ -2631,18 +2697,12 @@ class TestWhichBytes(TestWhich):
|
|||||||
TestWhich.setUp(self)
|
TestWhich.setUp(self)
|
||||||
self.dir = os.fsencode(self.dir)
|
self.dir = os.fsencode(self.dir)
|
||||||
self.file = os.fsencode(self.file)
|
self.file = os.fsencode(self.file)
|
||||||
self.temp_file.name = os.fsencode(self.temp_file.name)
|
self.filepath = os.fsencode(self.filepath)
|
||||||
self.temp_dir = os.fsencode(self.temp_dir)
|
self.other_dir = os.fsencode(self.other_dir)
|
||||||
self.curdir = os.fsencode(self.curdir)
|
self.curdir = os.fsencode(self.curdir)
|
||||||
self.ext = os.fsencode(self.ext)
|
self.ext = os.fsencode(self.ext)
|
||||||
|
|
||||||
def to_text_type(self, s):
|
to_text_type = staticmethod(os.fsencode)
|
||||||
'''
|
|
||||||
In this class we're testing with bytes, so convert s to a bytes
|
|
||||||
'''
|
|
||||||
if isinstance(s, str):
|
|
||||||
return s.encode()
|
|
||||||
return s
|
|
||||||
|
|
||||||
|
|
||||||
class TestMove(BaseTest, unittest.TestCase):
|
class TestMove(BaseTest, unittest.TestCase):
|
||||||
@ -2653,8 +2713,7 @@ class TestMove(BaseTest, unittest.TestCase):
|
|||||||
self.dst_dir = self.mkdtemp()
|
self.dst_dir = self.mkdtemp()
|
||||||
self.src_file = os.path.join(self.src_dir, filename)
|
self.src_file = os.path.join(self.src_dir, filename)
|
||||||
self.dst_file = os.path.join(self.dst_dir, filename)
|
self.dst_file = os.path.join(self.dst_dir, filename)
|
||||||
with open(self.src_file, "wb") as f:
|
create_file(self.src_file, b"spam")
|
||||||
f.write(b"spam")
|
|
||||||
|
|
||||||
def _check_move_file(self, src, dst, real_dst):
|
def _check_move_file(self, src, dst, real_dst):
|
||||||
with open(src, "rb") as f:
|
with open(src, "rb") as f:
|
||||||
@ -2732,8 +2791,7 @@ class TestMove(BaseTest, unittest.TestCase):
|
|||||||
|
|
||||||
def test_existing_file_inside_dest_dir(self):
|
def test_existing_file_inside_dest_dir(self):
|
||||||
# A file with the same name inside the destination dir already exists.
|
# A file with the same name inside the destination dir already exists.
|
||||||
with open(self.dst_file, "wb"):
|
create_file(self.dst_file)
|
||||||
pass
|
|
||||||
self.assertRaises(shutil.Error, shutil.move, self.src_file, self.dst_dir)
|
self.assertRaises(shutil.Error, shutil.move, self.src_file, self.dst_dir)
|
||||||
|
|
||||||
def test_dont_move_dir_in_itself(self):
|
def test_dont_move_dir_in_itself(self):
|
||||||
@ -3148,8 +3206,7 @@ class _ZeroCopyFileTest(object):
|
|||||||
dstname = TESTFN + 'dst'
|
dstname = TESTFN + 'dst'
|
||||||
self.addCleanup(lambda: os_helper.unlink(srcname))
|
self.addCleanup(lambda: os_helper.unlink(srcname))
|
||||||
self.addCleanup(lambda: os_helper.unlink(dstname))
|
self.addCleanup(lambda: os_helper.unlink(dstname))
|
||||||
with open(srcname, "wb"):
|
create_file(srcname)
|
||||||
pass
|
|
||||||
|
|
||||||
with open(srcname, "rb") as src:
|
with open(srcname, "rb") as src:
|
||||||
with open(dstname, "wb") as dst:
|
with open(dstname, "wb") as dst:
|
||||||
@ -3272,7 +3329,7 @@ class TestZeroCopySendfile(_ZeroCopyFileTest, unittest.TestCase):
|
|||||||
self.assertEqual(blocksize, os.path.getsize(TESTFN))
|
self.assertEqual(blocksize, os.path.getsize(TESTFN))
|
||||||
# ...unless we're dealing with a small file.
|
# ...unless we're dealing with a small file.
|
||||||
os_helper.unlink(TESTFN2)
|
os_helper.unlink(TESTFN2)
|
||||||
write_file(TESTFN2, b"hello", binary=True)
|
create_file(TESTFN2, b"hello")
|
||||||
self.addCleanup(os_helper.unlink, TESTFN2 + '3')
|
self.addCleanup(os_helper.unlink, TESTFN2 + '3')
|
||||||
self.assertRaises(ZeroDivisionError,
|
self.assertRaises(ZeroDivisionError,
|
||||||
shutil.copyfile, TESTFN2, TESTFN2 + '3')
|
shutil.copyfile, TESTFN2, TESTFN2 + '3')
|
||||||
|
@ -0,0 +1,4 @@
|
|||||||
|
Fix :mod:`shutil.which` on Windows. Now it looks at direct match if and only
|
||||||
|
if the command ends with a PATHEXT extension or X_OK is not in mode. Support
|
||||||
|
extensionless files if "." is in PATHEXT. Support PATHEXT extensions that end
|
||||||
|
with a dot.
|
Loading…
Reference in New Issue
Block a user