diff options
author | John W. Parent <45471568+johnwparent@users.noreply.github.com> | 2024-04-16 14:02:02 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-04-16 11:02:02 -0700 |
commit | de3b324983d83267943c7747b063fd6ffb574939 (patch) | |
tree | 8fcaf07fce320a1505dca97159f989ccba061c05 /lib | |
parent | 747cd374df925703fc5355a1ddefddb25ad470f2 (diff) | |
download | spack-de3b324983d83267943c7747b063fd6ffb574939.tar.gz spack-de3b324983d83267943c7747b063fd6ffb574939.tar.bz2 spack-de3b324983d83267943c7747b063fd6ffb574939.tar.xz spack-de3b324983d83267943c7747b063fd6ffb574939.zip |
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`
Diffstat (limited to 'lib')
-rw-r--r-- | lib/spack/llnl/util/filesystem.py | 6 | ||||
-rw-r--r-- | lib/spack/spack/test/llnl/util/filesystem.py | 38 |
2 files changed, 41 insertions, 3 deletions
diff --git a/lib/spack/llnl/util/filesystem.py b/lib/spack/llnl/util/filesystem.py index 722f51f3db..f9cee1e118 100644 --- a/lib/spack/llnl/util/filesystem.py +++ b/lib/spack/llnl/util/filesystem.py @@ -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 diff --git a/lib/spack/spack/test/llnl/util/filesystem.py b/lib/spack/spack/test/llnl/util/filesystem.py index a38faaef27..f04c2455cc 100644 --- a/lib/spack/spack/test/llnl/util/filesystem.py +++ b/lib/spack/spack/test/llnl/util/filesystem.py @@ -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 test_rename_dest_exists(tmpdir): 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")) |