Windows filesystem utilities (bugfix): improve SFN usage (#43645)

Reduce incidence of spurious errors by:
* Ensuring we're passing the buffer by reference
* Get the correct short string size from Windows API instead of computing ourselves
* Ensure sufficient space for null terminator character

Add test for `windows_sfn`
This commit is contained in:
John W. Parent 2024-04-16 14:02:02 -04:00 committed by GitHub
parent 747cd374df
commit de3b324983
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 41 additions and 3 deletions

View file

@ -1234,10 +1234,12 @@ def windows_sfn(path: os.PathLike):
import ctypes
k32 = ctypes.WinDLL("kernel32", use_last_error=True)
# Method with null values returns size of short path name
sz = k32.GetShortPathNameW(path, None, 0)
# stub Windows types TCHAR[LENGTH]
TCHAR_arr = ctypes.c_wchar * len(path)
TCHAR_arr = ctypes.c_wchar * sz
ret_str = TCHAR_arr()
k32.GetShortPathNameW(path, ret_str, len(path))
k32.GetShortPathNameW(path, ctypes.byref(ret_str), sz)
return ret_str.value

View file

@ -912,7 +912,6 @@ def test_find_first_file(tmpdir, bfs_depth):
def test_rename_dest_exists(tmpdir):
@contextmanager
def setup_test_files():
a = tmpdir.join("a", "file1")
@ -999,3 +998,40 @@ def setup_test_dirs():
assert os.path.exists(link2)
assert os.path.realpath(link2) == a
shutil.rmtree(tmpdir.join("f"))
@pytest.mark.skipif(sys.platform != "win32", reason="No-op on non Windows")
def test_windows_sfn(tmpdir):
# first check some standard Windows locations
# we know require sfn names
# this is basically a smoke test
# ensure spaces are replaced + path abbreviated
assert fs.windows_sfn("C:\\Program Files (x86)") == "C:\\PROGRA~2"
# ensure path without spaces is still properly shortened
assert fs.windows_sfn("C:\\ProgramData") == "C:\\PROGRA~3"
# test user created paths
# ensure longer path with spaces is properly abbreviated
a = tmpdir.join("d", "this is a test", "a", "still test")
# ensure longer path is properly abbreviated
b = tmpdir.join("d", "long_path_with_no_spaces", "more_long_path")
# ensure path not in need of abbreviation is properly roundtripped
c = tmpdir.join("d", "this", "is", "short")
# ensure paths that are the same in the first six letters
# are incremented post tilde
d = tmpdir.join("d", "longerpath1")
e = tmpdir.join("d", "longerpath2")
fs.mkdirp(a)
fs.mkdirp(b)
fs.mkdirp(c)
fs.mkdirp(d)
fs.mkdirp(e)
# check only for path of path we can control,
# pytest prefix may or may not be mangled by windows_sfn
# based on user/pytest config
assert "d\\THISIS~1\\a\\STILLT~1" in fs.windows_sfn(a)
assert "d\\LONG_P~1\\MORE_L~1" in fs.windows_sfn(b)
assert "d\\this\\is\\short" in fs.windows_sfn(c)
assert "d\\LONGER~1" in fs.windows_sfn(d)
assert "d\\LONGER~2" in fs.windows_sfn(e)
shutil.rmtree(tmpdir.join("d"))