cpython/Lib/test/symlink_support.py

101 lines
3.2 KiB
Python

import os
import unittest
import platform
from test.test_support import TESTFN
def can_symlink():
# cache the result in can_symlink.prev_val
prev_val = getattr(can_symlink, 'prev_val', None)
if prev_val is not None:
return prev_val
symlink_path = TESTFN + "can_symlink"
try:
symlink(TESTFN, symlink_path)
can = True
except (OSError, NotImplementedError, AttributeError):
can = False
else:
os.remove(symlink_path)
can_symlink.prev_val = can
return can
def skip_unless_symlink(test):
"""Skip decorator for tests that require functional symlink"""
ok = can_symlink()
msg = "Requires functional symlink implementation"
return test if ok else unittest.skip(msg)(test)
def _symlink_win32(target, link, target_is_directory=False):
"""
Ctypes symlink implementation since Python doesn't support
symlinks in windows yet. Borrowed from jaraco.windows project.
"""
import ctypes.wintypes
CreateSymbolicLink = ctypes.windll.kernel32.CreateSymbolicLinkW
CreateSymbolicLink.argtypes = (
ctypes.wintypes.LPWSTR,
ctypes.wintypes.LPWSTR,
ctypes.wintypes.DWORD,
)
CreateSymbolicLink.restype = ctypes.wintypes.BOOLEAN
def format_system_message(errno):
"""
Call FormatMessage with a system error number to retrieve
the descriptive error message.
"""
# first some flags used by FormatMessageW
ALLOCATE_BUFFER = 0x100
ARGUMENT_ARRAY = 0x2000
FROM_HMODULE = 0x800
FROM_STRING = 0x400
FROM_SYSTEM = 0x1000
IGNORE_INSERTS = 0x200
# Let FormatMessageW allocate the buffer (we'll free it below)
# Also, let it know we want a system error message.
flags = ALLOCATE_BUFFER | FROM_SYSTEM
source = None
message_id = errno
language_id = 0
result_buffer = ctypes.wintypes.LPWSTR()
buffer_size = 0
arguments = None
bytes = ctypes.windll.kernel32.FormatMessageW(
flags,
source,
message_id,
language_id,
ctypes.byref(result_buffer),
buffer_size,
arguments,
)
# note the following will cause an infinite loop if GetLastError
# repeatedly returns an error that cannot be formatted, although
# this should not happen.
handle_nonzero_success(bytes)
message = result_buffer.value
ctypes.windll.kernel32.LocalFree(result_buffer)
return message
def handle_nonzero_success(result):
if result == 0:
value = ctypes.windll.kernel32.GetLastError()
strerror = format_system_message(value)
raise WindowsError(value, strerror)
target_is_directory = target_is_directory or os.path.isdir(target)
handle_nonzero_success(CreateSymbolicLink(link, target, target_is_directory))
symlink = os.symlink if hasattr(os, 'symlink') else (
_symlink_win32 if platform.system() == 'Windows' else None
)
def remove_symlink(name):
# On Windows, to remove a directory symlink, one must use rmdir
try:
os.rmdir(name)
except OSError:
os.remove(name)