Home | History | Annotate | Download | only in test
      1 import os
      2 import unittest
      3 import platform
      4 
      5 from test.test_support import TESTFN
      6 
      7 def can_symlink():
      8     # cache the result in can_symlink.prev_val
      9     prev_val = getattr(can_symlink, 'prev_val', None)
     10     if prev_val is not None:
     11         return prev_val
     12     symlink_path = TESTFN + "can_symlink"
     13     try:
     14         symlink(TESTFN, symlink_path)
     15         can = True
     16     except (OSError, NotImplementedError, AttributeError):
     17         can = False
     18     else:
     19         os.remove(symlink_path)
     20     can_symlink.prev_val = can
     21     return can
     22 
     23 def skip_unless_symlink(test):
     24     """Skip decorator for tests that require functional symlink"""
     25     ok = can_symlink()
     26     msg = "Requires functional symlink implementation"
     27     return test if ok else unittest.skip(msg)(test)
     28 
     29 def _symlink_win32(target, link, target_is_directory=False):
     30     """
     31     Ctypes symlink implementation since Python doesn't support
     32     symlinks in windows yet. Borrowed from jaraco.windows project.
     33     """
     34     import ctypes.wintypes
     35     CreateSymbolicLink = ctypes.windll.kernel32.CreateSymbolicLinkW
     36     CreateSymbolicLink.argtypes = (
     37         ctypes.wintypes.LPWSTR,
     38         ctypes.wintypes.LPWSTR,
     39         ctypes.wintypes.DWORD,
     40         )
     41     CreateSymbolicLink.restype = ctypes.wintypes.BOOLEAN
     42 
     43     def format_system_message(errno):
     44         """
     45         Call FormatMessage with a system error number to retrieve
     46         the descriptive error message.
     47         """
     48         # first some flags used by FormatMessageW
     49         ALLOCATE_BUFFER = 0x100
     50         ARGUMENT_ARRAY = 0x2000
     51         FROM_HMODULE = 0x800
     52         FROM_STRING = 0x400
     53         FROM_SYSTEM = 0x1000
     54         IGNORE_INSERTS = 0x200
     55 
     56         # Let FormatMessageW allocate the buffer (we'll free it below)
     57         # Also, let it know we want a system error message.
     58         flags = ALLOCATE_BUFFER | FROM_SYSTEM
     59         source = None
     60         message_id = errno
     61         language_id = 0
     62         result_buffer = ctypes.wintypes.LPWSTR()
     63         buffer_size = 0
     64         arguments = None
     65         bytes = ctypes.windll.kernel32.FormatMessageW(
     66             flags,
     67             source,
     68             message_id,
     69             language_id,
     70             ctypes.byref(result_buffer),
     71             buffer_size,
     72             arguments,
     73             )
     74         # note the following will cause an infinite loop if GetLastError
     75         #  repeatedly returns an error that cannot be formatted, although
     76         #  this should not happen.
     77         handle_nonzero_success(bytes)
     78         message = result_buffer.value
     79         ctypes.windll.kernel32.LocalFree(result_buffer)
     80         return message
     81 
     82     def handle_nonzero_success(result):
     83         if result == 0:
     84             value = ctypes.windll.kernel32.GetLastError()
     85             strerror = format_system_message(value)
     86             raise WindowsError(value, strerror)
     87 
     88     target_is_directory = target_is_directory or os.path.isdir(target)
     89     handle_nonzero_success(CreateSymbolicLink(link, target, target_is_directory))
     90 
     91 symlink = os.symlink if hasattr(os, 'symlink') else (
     92     _symlink_win32 if platform.system() == 'Windows' else None
     93 )
     94 
     95 def remove_symlink(name):
     96     # On Windows, to remove a directory symlink, one must use rmdir
     97     try:
     98         os.rmdir(name)
     99     except OSError:
    100         os.remove(name)
    101