##// END OF EJS Templates
skip %HOME% 3.8 test on windows, update docs for get_home_dir
Nicholas Bollweg -
Show More
@@ -1,438 +1,439 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 Utilities for path handling.
3 Utilities for path handling.
4 """
4 """
5
5
6 # Copyright (c) IPython Development Team.
6 # Copyright (c) IPython Development Team.
7 # Distributed under the terms of the Modified BSD License.
7 # Distributed under the terms of the Modified BSD License.
8
8
9 import os
9 import os
10 import sys
10 import sys
11 import errno
11 import errno
12 import shutil
12 import shutil
13 import random
13 import random
14 import glob
14 import glob
15 from warnings import warn
15 from warnings import warn
16
16
17 from IPython.utils.process import system
17 from IPython.utils.process import system
18 from IPython.utils import py3compat
18 from IPython.utils import py3compat
19 from IPython.utils.decorators import undoc
19 from IPython.utils.decorators import undoc
20
20
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22 # Code
22 # Code
23 #-----------------------------------------------------------------------------
23 #-----------------------------------------------------------------------------
24
24
25 fs_encoding = sys.getfilesystemencoding()
25 fs_encoding = sys.getfilesystemencoding()
26
26
27 def _writable_dir(path):
27 def _writable_dir(path):
28 """Whether `path` is a directory, to which the user has write access."""
28 """Whether `path` is a directory, to which the user has write access."""
29 return os.path.isdir(path) and os.access(path, os.W_OK)
29 return os.path.isdir(path) and os.access(path, os.W_OK)
30
30
31 if sys.platform == 'win32':
31 if sys.platform == 'win32':
32 def _get_long_path_name(path):
32 def _get_long_path_name(path):
33 """Get a long path name (expand ~) on Windows using ctypes.
33 """Get a long path name (expand ~) on Windows using ctypes.
34
34
35 Examples
35 Examples
36 --------
36 --------
37
37
38 >>> get_long_path_name('c:\\docume~1')
38 >>> get_long_path_name('c:\\docume~1')
39 'c:\\\\Documents and Settings'
39 'c:\\\\Documents and Settings'
40
40
41 """
41 """
42 try:
42 try:
43 import ctypes
43 import ctypes
44 except ImportError:
44 except ImportError:
45 raise ImportError('you need to have ctypes installed for this to work')
45 raise ImportError('you need to have ctypes installed for this to work')
46 _GetLongPathName = ctypes.windll.kernel32.GetLongPathNameW
46 _GetLongPathName = ctypes.windll.kernel32.GetLongPathNameW
47 _GetLongPathName.argtypes = [ctypes.c_wchar_p, ctypes.c_wchar_p,
47 _GetLongPathName.argtypes = [ctypes.c_wchar_p, ctypes.c_wchar_p,
48 ctypes.c_uint ]
48 ctypes.c_uint ]
49
49
50 buf = ctypes.create_unicode_buffer(260)
50 buf = ctypes.create_unicode_buffer(260)
51 rv = _GetLongPathName(path, buf, 260)
51 rv = _GetLongPathName(path, buf, 260)
52 if rv == 0 or rv > 260:
52 if rv == 0 or rv > 260:
53 return path
53 return path
54 else:
54 else:
55 return buf.value
55 return buf.value
56 else:
56 else:
57 def _get_long_path_name(path):
57 def _get_long_path_name(path):
58 """Dummy no-op."""
58 """Dummy no-op."""
59 return path
59 return path
60
60
61
61
62
62
63 def get_long_path_name(path):
63 def get_long_path_name(path):
64 """Expand a path into its long form.
64 """Expand a path into its long form.
65
65
66 On Windows this expands any ~ in the paths. On other platforms, it is
66 On Windows this expands any ~ in the paths. On other platforms, it is
67 a null operation.
67 a null operation.
68 """
68 """
69 return _get_long_path_name(path)
69 return _get_long_path_name(path)
70
70
71
71
72 def unquote_filename(name, win32=(sys.platform=='win32')):
72 def unquote_filename(name, win32=(sys.platform=='win32')):
73 """ On Windows, remove leading and trailing quotes from filenames.
73 """ On Windows, remove leading and trailing quotes from filenames.
74
74
75 This function has been deprecated and should not be used any more:
75 This function has been deprecated and should not be used any more:
76 unquoting is now taken care of by :func:`IPython.utils.process.arg_split`.
76 unquoting is now taken care of by :func:`IPython.utils.process.arg_split`.
77 """
77 """
78 warn("'unquote_filename' is deprecated since IPython 5.0 and should not "
78 warn("'unquote_filename' is deprecated since IPython 5.0 and should not "
79 "be used anymore", DeprecationWarning, stacklevel=2)
79 "be used anymore", DeprecationWarning, stacklevel=2)
80 if win32:
80 if win32:
81 if name.startswith(("'", '"')) and name.endswith(("'", '"')):
81 if name.startswith(("'", '"')) and name.endswith(("'", '"')):
82 name = name[1:-1]
82 name = name[1:-1]
83 return name
83 return name
84
84
85
85
86 def compress_user(path):
86 def compress_user(path):
87 """Reverse of :func:`os.path.expanduser`
87 """Reverse of :func:`os.path.expanduser`
88 """
88 """
89 home = os.path.expanduser('~')
89 home = os.path.expanduser('~')
90 if path.startswith(home):
90 if path.startswith(home):
91 path = "~" + path[len(home):]
91 path = "~" + path[len(home):]
92 return path
92 return path
93
93
94 def get_py_filename(name, force_win32=None):
94 def get_py_filename(name, force_win32=None):
95 """Return a valid python filename in the current directory.
95 """Return a valid python filename in the current directory.
96
96
97 If the given name is not a file, it adds '.py' and searches again.
97 If the given name is not a file, it adds '.py' and searches again.
98 Raises IOError with an informative message if the file isn't found.
98 Raises IOError with an informative message if the file isn't found.
99 """
99 """
100
100
101 name = os.path.expanduser(name)
101 name = os.path.expanduser(name)
102 if force_win32 is not None:
102 if force_win32 is not None:
103 warn("The 'force_win32' argument to 'get_py_filename' is deprecated "
103 warn("The 'force_win32' argument to 'get_py_filename' is deprecated "
104 "since IPython 5.0 and should not be used anymore",
104 "since IPython 5.0 and should not be used anymore",
105 DeprecationWarning, stacklevel=2)
105 DeprecationWarning, stacklevel=2)
106 if not os.path.isfile(name) and not name.endswith('.py'):
106 if not os.path.isfile(name) and not name.endswith('.py'):
107 name += '.py'
107 name += '.py'
108 if os.path.isfile(name):
108 if os.path.isfile(name):
109 return name
109 return name
110 else:
110 else:
111 raise IOError('File `%r` not found.' % name)
111 raise IOError('File `%r` not found.' % name)
112
112
113
113
114 def filefind(filename, path_dirs=None):
114 def filefind(filename, path_dirs=None):
115 """Find a file by looking through a sequence of paths.
115 """Find a file by looking through a sequence of paths.
116
116
117 This iterates through a sequence of paths looking for a file and returns
117 This iterates through a sequence of paths looking for a file and returns
118 the full, absolute path of the first occurrence of the file. If no set of
118 the full, absolute path of the first occurrence of the file. If no set of
119 path dirs is given, the filename is tested as is, after running through
119 path dirs is given, the filename is tested as is, after running through
120 :func:`expandvars` and :func:`expanduser`. Thus a simple call::
120 :func:`expandvars` and :func:`expanduser`. Thus a simple call::
121
121
122 filefind('myfile.txt')
122 filefind('myfile.txt')
123
123
124 will find the file in the current working dir, but::
124 will find the file in the current working dir, but::
125
125
126 filefind('~/myfile.txt')
126 filefind('~/myfile.txt')
127
127
128 Will find the file in the users home directory. This function does not
128 Will find the file in the users home directory. This function does not
129 automatically try any paths, such as the cwd or the user's home directory.
129 automatically try any paths, such as the cwd or the user's home directory.
130
130
131 Parameters
131 Parameters
132 ----------
132 ----------
133 filename : str
133 filename : str
134 The filename to look for.
134 The filename to look for.
135 path_dirs : str, None or sequence of str
135 path_dirs : str, None or sequence of str
136 The sequence of paths to look for the file in. If None, the filename
136 The sequence of paths to look for the file in. If None, the filename
137 need to be absolute or be in the cwd. If a string, the string is
137 need to be absolute or be in the cwd. If a string, the string is
138 put into a sequence and the searched. If a sequence, walk through
138 put into a sequence and the searched. If a sequence, walk through
139 each element and join with ``filename``, calling :func:`expandvars`
139 each element and join with ``filename``, calling :func:`expandvars`
140 and :func:`expanduser` before testing for existence.
140 and :func:`expanduser` before testing for existence.
141
141
142 Returns
142 Returns
143 -------
143 -------
144 Raises :exc:`IOError` or returns absolute path to file.
144 Raises :exc:`IOError` or returns absolute path to file.
145 """
145 """
146
146
147 # If paths are quoted, abspath gets confused, strip them...
147 # If paths are quoted, abspath gets confused, strip them...
148 filename = filename.strip('"').strip("'")
148 filename = filename.strip('"').strip("'")
149 # If the input is an absolute path, just check it exists
149 # If the input is an absolute path, just check it exists
150 if os.path.isabs(filename) and os.path.isfile(filename):
150 if os.path.isabs(filename) and os.path.isfile(filename):
151 return filename
151 return filename
152
152
153 if path_dirs is None:
153 if path_dirs is None:
154 path_dirs = ("",)
154 path_dirs = ("",)
155 elif isinstance(path_dirs, str):
155 elif isinstance(path_dirs, str):
156 path_dirs = (path_dirs,)
156 path_dirs = (path_dirs,)
157
157
158 for path in path_dirs:
158 for path in path_dirs:
159 if path == '.': path = os.getcwd()
159 if path == '.': path = os.getcwd()
160 testname = expand_path(os.path.join(path, filename))
160 testname = expand_path(os.path.join(path, filename))
161 if os.path.isfile(testname):
161 if os.path.isfile(testname):
162 return os.path.abspath(testname)
162 return os.path.abspath(testname)
163
163
164 raise IOError("File %r does not exist in any of the search paths: %r" %
164 raise IOError("File %r does not exist in any of the search paths: %r" %
165 (filename, path_dirs) )
165 (filename, path_dirs) )
166
166
167
167
168 class HomeDirError(Exception):
168 class HomeDirError(Exception):
169 pass
169 pass
170
170
171
171
172 def get_home_dir(require_writable=False):
172 def get_home_dir(require_writable=False):
173 """Return the 'home' directory, as a unicode string.
173 """Return the 'home' directory, as a unicode string.
174
174
175 Uses os.path.expanduser('~'), and checks for writability.
175 Uses os.path.expanduser('~'), and checks for writability.
176
176
177 See stdlib docs for how this is determined.
177 See stdlib docs for how this is determined.
178 $HOME is first priority on *ALL* platforms.
178 For Python <3.8, $HOME is first priority on *ALL* platforms.
179 For Python >=3.8 on Windows, %HOME% is no longer considered.
179
180
180 Parameters
181 Parameters
181 ----------
182 ----------
182
183
183 require_writable : bool [default: False]
184 require_writable : bool [default: False]
184 if True:
185 if True:
185 guarantees the return value is a writable directory, otherwise
186 guarantees the return value is a writable directory, otherwise
186 raises HomeDirError
187 raises HomeDirError
187 if False:
188 if False:
188 The path is resolved, but it is not guaranteed to exist or be writable.
189 The path is resolved, but it is not guaranteed to exist or be writable.
189 """
190 """
190
191
191 homedir = os.path.expanduser('~')
192 homedir = os.path.expanduser('~')
192 # Next line will make things work even when /home/ is a symlink to
193 # Next line will make things work even when /home/ is a symlink to
193 # /usr/home as it is on FreeBSD, for example
194 # /usr/home as it is on FreeBSD, for example
194 homedir = os.path.realpath(homedir)
195 homedir = os.path.realpath(homedir)
195
196
196 if not _writable_dir(homedir) and os.name == 'nt':
197 if not _writable_dir(homedir) and os.name == 'nt':
197 # expanduser failed, use the registry to get the 'My Documents' folder.
198 # expanduser failed, use the registry to get the 'My Documents' folder.
198 try:
199 try:
199 try:
200 try:
200 import winreg as wreg # Py 3
201 import winreg as wreg # Py 3
201 except ImportError:
202 except ImportError:
202 import _winreg as wreg # Py 2
203 import _winreg as wreg # Py 2
203 key = wreg.OpenKey(
204 key = wreg.OpenKey(
204 wreg.HKEY_CURRENT_USER,
205 wreg.HKEY_CURRENT_USER,
205 r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders"
206 r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders"
206 )
207 )
207 homedir = wreg.QueryValueEx(key,'Personal')[0]
208 homedir = wreg.QueryValueEx(key,'Personal')[0]
208 key.Close()
209 key.Close()
209 except:
210 except:
210 pass
211 pass
211
212
212 if (not require_writable) or _writable_dir(homedir):
213 if (not require_writable) or _writable_dir(homedir):
213 return py3compat.cast_unicode(homedir, fs_encoding)
214 return py3compat.cast_unicode(homedir, fs_encoding)
214 else:
215 else:
215 raise HomeDirError('%s is not a writable dir, '
216 raise HomeDirError('%s is not a writable dir, '
216 'set $HOME environment variable to override' % homedir)
217 'set $HOME environment variable to override' % homedir)
217
218
218 def get_xdg_dir():
219 def get_xdg_dir():
219 """Return the XDG_CONFIG_HOME, if it is defined and exists, else None.
220 """Return the XDG_CONFIG_HOME, if it is defined and exists, else None.
220
221
221 This is only for non-OS X posix (Linux,Unix,etc.) systems.
222 This is only for non-OS X posix (Linux,Unix,etc.) systems.
222 """
223 """
223
224
224 env = os.environ
225 env = os.environ
225
226
226 if os.name == 'posix' and sys.platform != 'darwin':
227 if os.name == 'posix' and sys.platform != 'darwin':
227 # Linux, Unix, AIX, etc.
228 # Linux, Unix, AIX, etc.
228 # use ~/.config if empty OR not set
229 # use ~/.config if empty OR not set
229 xdg = env.get("XDG_CONFIG_HOME", None) or os.path.join(get_home_dir(), '.config')
230 xdg = env.get("XDG_CONFIG_HOME", None) or os.path.join(get_home_dir(), '.config')
230 if xdg and _writable_dir(xdg):
231 if xdg and _writable_dir(xdg):
231 return py3compat.cast_unicode(xdg, fs_encoding)
232 return py3compat.cast_unicode(xdg, fs_encoding)
232
233
233 return None
234 return None
234
235
235
236
236 def get_xdg_cache_dir():
237 def get_xdg_cache_dir():
237 """Return the XDG_CACHE_HOME, if it is defined and exists, else None.
238 """Return the XDG_CACHE_HOME, if it is defined and exists, else None.
238
239
239 This is only for non-OS X posix (Linux,Unix,etc.) systems.
240 This is only for non-OS X posix (Linux,Unix,etc.) systems.
240 """
241 """
241
242
242 env = os.environ
243 env = os.environ
243
244
244 if os.name == 'posix' and sys.platform != 'darwin':
245 if os.name == 'posix' and sys.platform != 'darwin':
245 # Linux, Unix, AIX, etc.
246 # Linux, Unix, AIX, etc.
246 # use ~/.cache if empty OR not set
247 # use ~/.cache if empty OR not set
247 xdg = env.get("XDG_CACHE_HOME", None) or os.path.join(get_home_dir(), '.cache')
248 xdg = env.get("XDG_CACHE_HOME", None) or os.path.join(get_home_dir(), '.cache')
248 if xdg and _writable_dir(xdg):
249 if xdg and _writable_dir(xdg):
249 return py3compat.cast_unicode(xdg, fs_encoding)
250 return py3compat.cast_unicode(xdg, fs_encoding)
250
251
251 return None
252 return None
252
253
253
254
254 @undoc
255 @undoc
255 def get_ipython_dir():
256 def get_ipython_dir():
256 warn("get_ipython_dir has moved to the IPython.paths module since IPython 4.0.", stacklevel=2)
257 warn("get_ipython_dir has moved to the IPython.paths module since IPython 4.0.", stacklevel=2)
257 from IPython.paths import get_ipython_dir
258 from IPython.paths import get_ipython_dir
258 return get_ipython_dir()
259 return get_ipython_dir()
259
260
260 @undoc
261 @undoc
261 def get_ipython_cache_dir():
262 def get_ipython_cache_dir():
262 warn("get_ipython_cache_dir has moved to the IPython.paths module since IPython 4.0.", stacklevel=2)
263 warn("get_ipython_cache_dir has moved to the IPython.paths module since IPython 4.0.", stacklevel=2)
263 from IPython.paths import get_ipython_cache_dir
264 from IPython.paths import get_ipython_cache_dir
264 return get_ipython_cache_dir()
265 return get_ipython_cache_dir()
265
266
266 @undoc
267 @undoc
267 def get_ipython_package_dir():
268 def get_ipython_package_dir():
268 warn("get_ipython_package_dir has moved to the IPython.paths module since IPython 4.0.", stacklevel=2)
269 warn("get_ipython_package_dir has moved to the IPython.paths module since IPython 4.0.", stacklevel=2)
269 from IPython.paths import get_ipython_package_dir
270 from IPython.paths import get_ipython_package_dir
270 return get_ipython_package_dir()
271 return get_ipython_package_dir()
271
272
272 @undoc
273 @undoc
273 def get_ipython_module_path(module_str):
274 def get_ipython_module_path(module_str):
274 warn("get_ipython_module_path has moved to the IPython.paths module since IPython 4.0.", stacklevel=2)
275 warn("get_ipython_module_path has moved to the IPython.paths module since IPython 4.0.", stacklevel=2)
275 from IPython.paths import get_ipython_module_path
276 from IPython.paths import get_ipython_module_path
276 return get_ipython_module_path(module_str)
277 return get_ipython_module_path(module_str)
277
278
278 @undoc
279 @undoc
279 def locate_profile(profile='default'):
280 def locate_profile(profile='default'):
280 warn("locate_profile has moved to the IPython.paths module since IPython 4.0.", stacklevel=2)
281 warn("locate_profile has moved to the IPython.paths module since IPython 4.0.", stacklevel=2)
281 from IPython.paths import locate_profile
282 from IPython.paths import locate_profile
282 return locate_profile(profile=profile)
283 return locate_profile(profile=profile)
283
284
284 def expand_path(s):
285 def expand_path(s):
285 """Expand $VARS and ~names in a string, like a shell
286 """Expand $VARS and ~names in a string, like a shell
286
287
287 :Examples:
288 :Examples:
288
289
289 In [2]: os.environ['FOO']='test'
290 In [2]: os.environ['FOO']='test'
290
291
291 In [3]: expand_path('variable FOO is $FOO')
292 In [3]: expand_path('variable FOO is $FOO')
292 Out[3]: 'variable FOO is test'
293 Out[3]: 'variable FOO is test'
293 """
294 """
294 # This is a pretty subtle hack. When expand user is given a UNC path
295 # This is a pretty subtle hack. When expand user is given a UNC path
295 # on Windows (\\server\share$\%username%), os.path.expandvars, removes
296 # on Windows (\\server\share$\%username%), os.path.expandvars, removes
296 # the $ to get (\\server\share\%username%). I think it considered $
297 # the $ to get (\\server\share\%username%). I think it considered $
297 # alone an empty var. But, we need the $ to remains there (it indicates
298 # alone an empty var. But, we need the $ to remains there (it indicates
298 # a hidden share).
299 # a hidden share).
299 if os.name=='nt':
300 if os.name=='nt':
300 s = s.replace('$\\', 'IPYTHON_TEMP')
301 s = s.replace('$\\', 'IPYTHON_TEMP')
301 s = os.path.expandvars(os.path.expanduser(s))
302 s = os.path.expandvars(os.path.expanduser(s))
302 if os.name=='nt':
303 if os.name=='nt':
303 s = s.replace('IPYTHON_TEMP', '$\\')
304 s = s.replace('IPYTHON_TEMP', '$\\')
304 return s
305 return s
305
306
306
307
307 def unescape_glob(string):
308 def unescape_glob(string):
308 """Unescape glob pattern in `string`."""
309 """Unescape glob pattern in `string`."""
309 def unescape(s):
310 def unescape(s):
310 for pattern in '*[]!?':
311 for pattern in '*[]!?':
311 s = s.replace(r'\{0}'.format(pattern), pattern)
312 s = s.replace(r'\{0}'.format(pattern), pattern)
312 return s
313 return s
313 return '\\'.join(map(unescape, string.split('\\\\')))
314 return '\\'.join(map(unescape, string.split('\\\\')))
314
315
315
316
316 def shellglob(args):
317 def shellglob(args):
317 """
318 """
318 Do glob expansion for each element in `args` and return a flattened list.
319 Do glob expansion for each element in `args` and return a flattened list.
319
320
320 Unmatched glob pattern will remain as-is in the returned list.
321 Unmatched glob pattern will remain as-is in the returned list.
321
322
322 """
323 """
323 expanded = []
324 expanded = []
324 # Do not unescape backslash in Windows as it is interpreted as
325 # Do not unescape backslash in Windows as it is interpreted as
325 # path separator:
326 # path separator:
326 unescape = unescape_glob if sys.platform != 'win32' else lambda x: x
327 unescape = unescape_glob if sys.platform != 'win32' else lambda x: x
327 for a in args:
328 for a in args:
328 expanded.extend(glob.glob(a) or [unescape(a)])
329 expanded.extend(glob.glob(a) or [unescape(a)])
329 return expanded
330 return expanded
330
331
331
332
332 def target_outdated(target,deps):
333 def target_outdated(target,deps):
333 """Determine whether a target is out of date.
334 """Determine whether a target is out of date.
334
335
335 target_outdated(target,deps) -> 1/0
336 target_outdated(target,deps) -> 1/0
336
337
337 deps: list of filenames which MUST exist.
338 deps: list of filenames which MUST exist.
338 target: single filename which may or may not exist.
339 target: single filename which may or may not exist.
339
340
340 If target doesn't exist or is older than any file listed in deps, return
341 If target doesn't exist or is older than any file listed in deps, return
341 true, otherwise return false.
342 true, otherwise return false.
342 """
343 """
343 try:
344 try:
344 target_time = os.path.getmtime(target)
345 target_time = os.path.getmtime(target)
345 except os.error:
346 except os.error:
346 return 1
347 return 1
347 for dep in deps:
348 for dep in deps:
348 dep_time = os.path.getmtime(dep)
349 dep_time = os.path.getmtime(dep)
349 if dep_time > target_time:
350 if dep_time > target_time:
350 #print "For target",target,"Dep failed:",dep # dbg
351 #print "For target",target,"Dep failed:",dep # dbg
351 #print "times (dep,tar):",dep_time,target_time # dbg
352 #print "times (dep,tar):",dep_time,target_time # dbg
352 return 1
353 return 1
353 return 0
354 return 0
354
355
355
356
356 def target_update(target,deps,cmd):
357 def target_update(target,deps,cmd):
357 """Update a target with a given command given a list of dependencies.
358 """Update a target with a given command given a list of dependencies.
358
359
359 target_update(target,deps,cmd) -> runs cmd if target is outdated.
360 target_update(target,deps,cmd) -> runs cmd if target is outdated.
360
361
361 This is just a wrapper around target_outdated() which calls the given
362 This is just a wrapper around target_outdated() which calls the given
362 command if target is outdated."""
363 command if target is outdated."""
363
364
364 if target_outdated(target,deps):
365 if target_outdated(target,deps):
365 system(cmd)
366 system(cmd)
366
367
367
368
368 ENOLINK = 1998
369 ENOLINK = 1998
369
370
370 def link(src, dst):
371 def link(src, dst):
371 """Hard links ``src`` to ``dst``, returning 0 or errno.
372 """Hard links ``src`` to ``dst``, returning 0 or errno.
372
373
373 Note that the special errno ``ENOLINK`` will be returned if ``os.link`` isn't
374 Note that the special errno ``ENOLINK`` will be returned if ``os.link`` isn't
374 supported by the operating system.
375 supported by the operating system.
375 """
376 """
376
377
377 if not hasattr(os, "link"):
378 if not hasattr(os, "link"):
378 return ENOLINK
379 return ENOLINK
379 link_errno = 0
380 link_errno = 0
380 try:
381 try:
381 os.link(src, dst)
382 os.link(src, dst)
382 except OSError as e:
383 except OSError as e:
383 link_errno = e.errno
384 link_errno = e.errno
384 return link_errno
385 return link_errno
385
386
386
387
387 def link_or_copy(src, dst):
388 def link_or_copy(src, dst):
388 """Attempts to hardlink ``src`` to ``dst``, copying if the link fails.
389 """Attempts to hardlink ``src`` to ``dst``, copying if the link fails.
389
390
390 Attempts to maintain the semantics of ``shutil.copy``.
391 Attempts to maintain the semantics of ``shutil.copy``.
391
392
392 Because ``os.link`` does not overwrite files, a unique temporary file
393 Because ``os.link`` does not overwrite files, a unique temporary file
393 will be used if the target already exists, then that file will be moved
394 will be used if the target already exists, then that file will be moved
394 into place.
395 into place.
395 """
396 """
396
397
397 if os.path.isdir(dst):
398 if os.path.isdir(dst):
398 dst = os.path.join(dst, os.path.basename(src))
399 dst = os.path.join(dst, os.path.basename(src))
399
400
400 link_errno = link(src, dst)
401 link_errno = link(src, dst)
401 if link_errno == errno.EEXIST:
402 if link_errno == errno.EEXIST:
402 if os.stat(src).st_ino == os.stat(dst).st_ino:
403 if os.stat(src).st_ino == os.stat(dst).st_ino:
403 # dst is already a hard link to the correct file, so we don't need
404 # dst is already a hard link to the correct file, so we don't need
404 # to do anything else. If we try to link and rename the file
405 # to do anything else. If we try to link and rename the file
405 # anyway, we get duplicate files - see http://bugs.python.org/issue21876
406 # anyway, we get duplicate files - see http://bugs.python.org/issue21876
406 return
407 return
407
408
408 new_dst = dst + "-temp-%04X" %(random.randint(1, 16**4), )
409 new_dst = dst + "-temp-%04X" %(random.randint(1, 16**4), )
409 try:
410 try:
410 link_or_copy(src, new_dst)
411 link_or_copy(src, new_dst)
411 except:
412 except:
412 try:
413 try:
413 os.remove(new_dst)
414 os.remove(new_dst)
414 except OSError:
415 except OSError:
415 pass
416 pass
416 raise
417 raise
417 os.rename(new_dst, dst)
418 os.rename(new_dst, dst)
418 elif link_errno != 0:
419 elif link_errno != 0:
419 # Either link isn't supported, or the filesystem doesn't support
420 # Either link isn't supported, or the filesystem doesn't support
420 # linking, or 'src' and 'dst' are on different filesystems.
421 # linking, or 'src' and 'dst' are on different filesystems.
421 shutil.copy(src, dst)
422 shutil.copy(src, dst)
422
423
423 def ensure_dir_exists(path, mode=0o755):
424 def ensure_dir_exists(path, mode=0o755):
424 """ensure that a directory exists
425 """ensure that a directory exists
425
426
426 If it doesn't exist, try to create it and protect against a race condition
427 If it doesn't exist, try to create it and protect against a race condition
427 if another process is doing the same.
428 if another process is doing the same.
428
429
429 The default permissions are 755, which differ from os.makedirs default of 777.
430 The default permissions are 755, which differ from os.makedirs default of 777.
430 """
431 """
431 if not os.path.exists(path):
432 if not os.path.exists(path):
432 try:
433 try:
433 os.makedirs(path, mode=mode)
434 os.makedirs(path, mode=mode)
434 except OSError as e:
435 except OSError as e:
435 if e.errno != errno.EEXIST:
436 if e.errno != errno.EEXIST:
436 raise
437 raise
437 elif not os.path.isdir(path):
438 elif not os.path.isdir(path):
438 raise IOError("%r exists but is not a directory" % path)
439 raise IOError("%r exists but is not a directory" % path)
@@ -1,484 +1,485 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """Tests for IPython.utils.path.py"""
2 """Tests for IPython.utils.path.py"""
3
3
4 # Copyright (c) IPython Development Team.
4 # Copyright (c) IPython Development Team.
5 # Distributed under the terms of the Modified BSD License.
5 # Distributed under the terms of the Modified BSD License.
6
6
7 import os
7 import os
8 import shutil
8 import shutil
9 import sys
9 import sys
10 import tempfile
10 import tempfile
11 import unittest
11 import unittest
12 from contextlib import contextmanager
12 from contextlib import contextmanager
13 from unittest.mock import patch
13 from unittest.mock import patch
14 from os.path import join, abspath
14 from os.path import join, abspath
15 from imp import reload
15 from imp import reload
16
16
17 from nose import SkipTest, with_setup
17 from nose import SkipTest, with_setup
18 import nose.tools as nt
18 import nose.tools as nt
19
19
20 import IPython
20 import IPython
21 from IPython import paths
21 from IPython import paths
22 from IPython.testing import decorators as dec
22 from IPython.testing import decorators as dec
23 from IPython.testing.decorators import (skip_if_not_win32, skip_win32,
23 from IPython.testing.decorators import (skip_if_not_win32, skip_win32,
24 onlyif_unicode_paths,)
24 onlyif_unicode_paths,)
25 from IPython.testing.tools import make_tempfile, AssertPrints
25 from IPython.testing.tools import make_tempfile, AssertPrints
26 from IPython.utils import path
26 from IPython.utils import path
27 from IPython.utils.tempdir import TemporaryDirectory
27 from IPython.utils.tempdir import TemporaryDirectory
28
28
29 # Platform-dependent imports
29 # Platform-dependent imports
30 try:
30 try:
31 import winreg as wreg
31 import winreg as wreg
32 except ImportError:
32 except ImportError:
33 #Fake _winreg module on non-windows platforms
33 #Fake _winreg module on non-windows platforms
34 import types
34 import types
35 wr_name = "winreg"
35 wr_name = "winreg"
36 sys.modules[wr_name] = types.ModuleType(wr_name)
36 sys.modules[wr_name] = types.ModuleType(wr_name)
37 try:
37 try:
38 import winreg as wreg
38 import winreg as wreg
39 except ImportError:
39 except ImportError:
40 import _winreg as wreg
40 import _winreg as wreg
41 #Add entries that needs to be stubbed by the testing code
41 #Add entries that needs to be stubbed by the testing code
42 (wreg.OpenKey, wreg.QueryValueEx,) = (None, None)
42 (wreg.OpenKey, wreg.QueryValueEx,) = (None, None)
43
43
44 #-----------------------------------------------------------------------------
44 #-----------------------------------------------------------------------------
45 # Globals
45 # Globals
46 #-----------------------------------------------------------------------------
46 #-----------------------------------------------------------------------------
47 env = os.environ
47 env = os.environ
48 TMP_TEST_DIR = tempfile.mkdtemp()
48 TMP_TEST_DIR = tempfile.mkdtemp()
49 HOME_TEST_DIR = join(TMP_TEST_DIR, "home_test_dir")
49 HOME_TEST_DIR = join(TMP_TEST_DIR, "home_test_dir")
50 #
50 #
51 # Setup/teardown functions/decorators
51 # Setup/teardown functions/decorators
52 #
52 #
53
53
54 def setup_module():
54 def setup_module():
55 """Setup testenvironment for the module:
55 """Setup testenvironment for the module:
56
56
57 - Adds dummy home dir tree
57 - Adds dummy home dir tree
58 """
58 """
59 # Do not mask exceptions here. In particular, catching WindowsError is a
59 # Do not mask exceptions here. In particular, catching WindowsError is a
60 # problem because that exception is only defined on Windows...
60 # problem because that exception is only defined on Windows...
61 os.makedirs(os.path.join(HOME_TEST_DIR, 'ipython'))
61 os.makedirs(os.path.join(HOME_TEST_DIR, 'ipython'))
62
62
63
63
64 def teardown_module():
64 def teardown_module():
65 """Teardown testenvironment for the module:
65 """Teardown testenvironment for the module:
66
66
67 - Remove dummy home dir tree
67 - Remove dummy home dir tree
68 """
68 """
69 # Note: we remove the parent test dir, which is the root of all test
69 # Note: we remove the parent test dir, which is the root of all test
70 # subdirs we may have created. Use shutil instead of os.removedirs, so
70 # subdirs we may have created. Use shutil instead of os.removedirs, so
71 # that non-empty directories are all recursively removed.
71 # that non-empty directories are all recursively removed.
72 shutil.rmtree(TMP_TEST_DIR)
72 shutil.rmtree(TMP_TEST_DIR)
73
73
74
74
75 def setup_environment():
75 def setup_environment():
76 """Setup testenvironment for some functions that are tested
76 """Setup testenvironment for some functions that are tested
77 in this module. In particular this functions stores attributes
77 in this module. In particular this functions stores attributes
78 and other things that we need to stub in some test functions.
78 and other things that we need to stub in some test functions.
79 This needs to be done on a function level and not module level because
79 This needs to be done on a function level and not module level because
80 each testfunction needs a pristine environment.
80 each testfunction needs a pristine environment.
81 """
81 """
82 global oldstuff, platformstuff
82 global oldstuff, platformstuff
83 oldstuff = (env.copy(), os.name, sys.platform, path.get_home_dir, IPython.__file__, os.getcwd())
83 oldstuff = (env.copy(), os.name, sys.platform, path.get_home_dir, IPython.__file__, os.getcwd())
84
84
85 def teardown_environment():
85 def teardown_environment():
86 """Restore things that were remembered by the setup_environment function
86 """Restore things that were remembered by the setup_environment function
87 """
87 """
88 (oldenv, os.name, sys.platform, path.get_home_dir, IPython.__file__, old_wd) = oldstuff
88 (oldenv, os.name, sys.platform, path.get_home_dir, IPython.__file__, old_wd) = oldstuff
89 os.chdir(old_wd)
89 os.chdir(old_wd)
90 reload(path)
90 reload(path)
91
91
92 for key in list(env):
92 for key in list(env):
93 if key not in oldenv:
93 if key not in oldenv:
94 del env[key]
94 del env[key]
95 env.update(oldenv)
95 env.update(oldenv)
96 if hasattr(sys, 'frozen'):
96 if hasattr(sys, 'frozen'):
97 del sys.frozen
97 del sys.frozen
98
98
99 # Build decorator that uses the setup_environment/setup_environment
99 # Build decorator that uses the setup_environment/setup_environment
100 with_environment = with_setup(setup_environment, teardown_environment)
100 with_environment = with_setup(setup_environment, teardown_environment)
101
101
102 @skip_if_not_win32
102 @skip_if_not_win32
103 @with_environment
103 @with_environment
104 def test_get_home_dir_1():
104 def test_get_home_dir_1():
105 """Testcase for py2exe logic, un-compressed lib
105 """Testcase for py2exe logic, un-compressed lib
106 """
106 """
107 unfrozen = path.get_home_dir()
107 unfrozen = path.get_home_dir()
108 sys.frozen = True
108 sys.frozen = True
109
109
110 #fake filename for IPython.__init__
110 #fake filename for IPython.__init__
111 IPython.__file__ = abspath(join(HOME_TEST_DIR, "Lib/IPython/__init__.py"))
111 IPython.__file__ = abspath(join(HOME_TEST_DIR, "Lib/IPython/__init__.py"))
112
112
113 home_dir = path.get_home_dir()
113 home_dir = path.get_home_dir()
114 nt.assert_equal(home_dir, unfrozen)
114 nt.assert_equal(home_dir, unfrozen)
115
115
116
116
117 @skip_if_not_win32
117 @skip_if_not_win32
118 @with_environment
118 @with_environment
119 def test_get_home_dir_2():
119 def test_get_home_dir_2():
120 """Testcase for py2exe logic, compressed lib
120 """Testcase for py2exe logic, compressed lib
121 """
121 """
122 unfrozen = path.get_home_dir()
122 unfrozen = path.get_home_dir()
123 sys.frozen = True
123 sys.frozen = True
124 #fake filename for IPython.__init__
124 #fake filename for IPython.__init__
125 IPython.__file__ = abspath(join(HOME_TEST_DIR, "Library.zip/IPython/__init__.py")).lower()
125 IPython.__file__ = abspath(join(HOME_TEST_DIR, "Library.zip/IPython/__init__.py")).lower()
126
126
127 home_dir = path.get_home_dir(True)
127 home_dir = path.get_home_dir(True)
128 nt.assert_equal(home_dir, unfrozen)
128 nt.assert_equal(home_dir, unfrozen)
129
129
130
130
131 @skipif(sys.version_info > (3,8) and os.name == 'nt')
131 @with_environment
132 @with_environment
132 def test_get_home_dir_3():
133 def test_get_home_dir_3():
133 """get_home_dir() uses $HOME if set"""
134 """get_home_dir() uses $HOME if set"""
134 env["HOME"] = HOME_TEST_DIR
135 env["HOME"] = HOME_TEST_DIR
135 home_dir = path.get_home_dir(True)
136 home_dir = path.get_home_dir(True)
136 # get_home_dir expands symlinks
137 # get_home_dir expands symlinks
137 nt.assert_equal(home_dir, os.path.realpath(env["HOME"]))
138 nt.assert_equal(home_dir, os.path.realpath(env["HOME"]))
138
139
139
140
140 @with_environment
141 @with_environment
141 def test_get_home_dir_4():
142 def test_get_home_dir_4():
142 """get_home_dir() still works if $HOME is not set"""
143 """get_home_dir() still works if $HOME is not set"""
143
144
144 if 'HOME' in env: del env['HOME']
145 if 'HOME' in env: del env['HOME']
145 # this should still succeed, but we don't care what the answer is
146 # this should still succeed, but we don't care what the answer is
146 home = path.get_home_dir(False)
147 home = path.get_home_dir(False)
147
148
148 @with_environment
149 @with_environment
149 def test_get_home_dir_5():
150 def test_get_home_dir_5():
150 """raise HomeDirError if $HOME is specified, but not a writable dir"""
151 """raise HomeDirError if $HOME is specified, but not a writable dir"""
151 env['HOME'] = abspath(HOME_TEST_DIR+'garbage')
152 env['HOME'] = abspath(HOME_TEST_DIR+'garbage')
152 # set os.name = posix, to prevent My Documents fallback on Windows
153 # set os.name = posix, to prevent My Documents fallback on Windows
153 os.name = 'posix'
154 os.name = 'posix'
154 nt.assert_raises(path.HomeDirError, path.get_home_dir, True)
155 nt.assert_raises(path.HomeDirError, path.get_home_dir, True)
155
156
156 # Should we stub wreg fully so we can run the test on all platforms?
157 # Should we stub wreg fully so we can run the test on all platforms?
157 @skip_if_not_win32
158 @skip_if_not_win32
158 @with_environment
159 @with_environment
159 def test_get_home_dir_8():
160 def test_get_home_dir_8():
160 """Using registry hack for 'My Documents', os=='nt'
161 """Using registry hack for 'My Documents', os=='nt'
161
162
162 HOMESHARE, HOMEDRIVE, HOMEPATH, USERPROFILE and others are missing.
163 HOMESHARE, HOMEDRIVE, HOMEPATH, USERPROFILE and others are missing.
163 """
164 """
164 os.name = 'nt'
165 os.name = 'nt'
165 # Remove from stub environment all keys that may be set
166 # Remove from stub environment all keys that may be set
166 for key in ['HOME', 'HOMESHARE', 'HOMEDRIVE', 'HOMEPATH', 'USERPROFILE']:
167 for key in ['HOME', 'HOMESHARE', 'HOMEDRIVE', 'HOMEPATH', 'USERPROFILE']:
167 env.pop(key, None)
168 env.pop(key, None)
168
169
169 class key:
170 class key:
170 def Close(self):
171 def Close(self):
171 pass
172 pass
172
173
173 with patch.object(wreg, 'OpenKey', return_value=key()), \
174 with patch.object(wreg, 'OpenKey', return_value=key()), \
174 patch.object(wreg, 'QueryValueEx', return_value=[abspath(HOME_TEST_DIR)]):
175 patch.object(wreg, 'QueryValueEx', return_value=[abspath(HOME_TEST_DIR)]):
175 home_dir = path.get_home_dir()
176 home_dir = path.get_home_dir()
176 nt.assert_equal(home_dir, abspath(HOME_TEST_DIR))
177 nt.assert_equal(home_dir, abspath(HOME_TEST_DIR))
177
178
178 @with_environment
179 @with_environment
179 def test_get_xdg_dir_0():
180 def test_get_xdg_dir_0():
180 """test_get_xdg_dir_0, check xdg_dir"""
181 """test_get_xdg_dir_0, check xdg_dir"""
181 reload(path)
182 reload(path)
182 path._writable_dir = lambda path: True
183 path._writable_dir = lambda path: True
183 path.get_home_dir = lambda : 'somewhere'
184 path.get_home_dir = lambda : 'somewhere'
184 os.name = "posix"
185 os.name = "posix"
185 sys.platform = "linux2"
186 sys.platform = "linux2"
186 env.pop('IPYTHON_DIR', None)
187 env.pop('IPYTHON_DIR', None)
187 env.pop('IPYTHONDIR', None)
188 env.pop('IPYTHONDIR', None)
188 env.pop('XDG_CONFIG_HOME', None)
189 env.pop('XDG_CONFIG_HOME', None)
189
190
190 nt.assert_equal(path.get_xdg_dir(), os.path.join('somewhere', '.config'))
191 nt.assert_equal(path.get_xdg_dir(), os.path.join('somewhere', '.config'))
191
192
192
193
193 @with_environment
194 @with_environment
194 def test_get_xdg_dir_1():
195 def test_get_xdg_dir_1():
195 """test_get_xdg_dir_1, check nonexistent xdg_dir"""
196 """test_get_xdg_dir_1, check nonexistent xdg_dir"""
196 reload(path)
197 reload(path)
197 path.get_home_dir = lambda : HOME_TEST_DIR
198 path.get_home_dir = lambda : HOME_TEST_DIR
198 os.name = "posix"
199 os.name = "posix"
199 sys.platform = "linux2"
200 sys.platform = "linux2"
200 env.pop('IPYTHON_DIR', None)
201 env.pop('IPYTHON_DIR', None)
201 env.pop('IPYTHONDIR', None)
202 env.pop('IPYTHONDIR', None)
202 env.pop('XDG_CONFIG_HOME', None)
203 env.pop('XDG_CONFIG_HOME', None)
203 nt.assert_equal(path.get_xdg_dir(), None)
204 nt.assert_equal(path.get_xdg_dir(), None)
204
205
205 @with_environment
206 @with_environment
206 def test_get_xdg_dir_2():
207 def test_get_xdg_dir_2():
207 """test_get_xdg_dir_2, check xdg_dir default to ~/.config"""
208 """test_get_xdg_dir_2, check xdg_dir default to ~/.config"""
208 reload(path)
209 reload(path)
209 path.get_home_dir = lambda : HOME_TEST_DIR
210 path.get_home_dir = lambda : HOME_TEST_DIR
210 os.name = "posix"
211 os.name = "posix"
211 sys.platform = "linux2"
212 sys.platform = "linux2"
212 env.pop('IPYTHON_DIR', None)
213 env.pop('IPYTHON_DIR', None)
213 env.pop('IPYTHONDIR', None)
214 env.pop('IPYTHONDIR', None)
214 env.pop('XDG_CONFIG_HOME', None)
215 env.pop('XDG_CONFIG_HOME', None)
215 cfgdir=os.path.join(path.get_home_dir(), '.config')
216 cfgdir=os.path.join(path.get_home_dir(), '.config')
216 if not os.path.exists(cfgdir):
217 if not os.path.exists(cfgdir):
217 os.makedirs(cfgdir)
218 os.makedirs(cfgdir)
218
219
219 nt.assert_equal(path.get_xdg_dir(), cfgdir)
220 nt.assert_equal(path.get_xdg_dir(), cfgdir)
220
221
221 @with_environment
222 @with_environment
222 def test_get_xdg_dir_3():
223 def test_get_xdg_dir_3():
223 """test_get_xdg_dir_3, check xdg_dir not used on OS X"""
224 """test_get_xdg_dir_3, check xdg_dir not used on OS X"""
224 reload(path)
225 reload(path)
225 path.get_home_dir = lambda : HOME_TEST_DIR
226 path.get_home_dir = lambda : HOME_TEST_DIR
226 os.name = "posix"
227 os.name = "posix"
227 sys.platform = "darwin"
228 sys.platform = "darwin"
228 env.pop('IPYTHON_DIR', None)
229 env.pop('IPYTHON_DIR', None)
229 env.pop('IPYTHONDIR', None)
230 env.pop('IPYTHONDIR', None)
230 env.pop('XDG_CONFIG_HOME', None)
231 env.pop('XDG_CONFIG_HOME', None)
231 cfgdir=os.path.join(path.get_home_dir(), '.config')
232 cfgdir=os.path.join(path.get_home_dir(), '.config')
232 if not os.path.exists(cfgdir):
233 if not os.path.exists(cfgdir):
233 os.makedirs(cfgdir)
234 os.makedirs(cfgdir)
234
235
235 nt.assert_equal(path.get_xdg_dir(), None)
236 nt.assert_equal(path.get_xdg_dir(), None)
236
237
237 def test_filefind():
238 def test_filefind():
238 """Various tests for filefind"""
239 """Various tests for filefind"""
239 f = tempfile.NamedTemporaryFile()
240 f = tempfile.NamedTemporaryFile()
240 # print 'fname:',f.name
241 # print 'fname:',f.name
241 alt_dirs = paths.get_ipython_dir()
242 alt_dirs = paths.get_ipython_dir()
242 t = path.filefind(f.name, alt_dirs)
243 t = path.filefind(f.name, alt_dirs)
243 # print 'found:',t
244 # print 'found:',t
244
245
245
246
246 @dec.skip_if_not_win32
247 @dec.skip_if_not_win32
247 def test_get_long_path_name_win32():
248 def test_get_long_path_name_win32():
248 with TemporaryDirectory() as tmpdir:
249 with TemporaryDirectory() as tmpdir:
249
250
250 # Make a long path. Expands the path of tmpdir prematurely as it may already have a long
251 # Make a long path. Expands the path of tmpdir prematurely as it may already have a long
251 # path component, so ensure we include the long form of it
252 # path component, so ensure we include the long form of it
252 long_path = os.path.join(path.get_long_path_name(tmpdir), 'this is my long path name')
253 long_path = os.path.join(path.get_long_path_name(tmpdir), 'this is my long path name')
253 os.makedirs(long_path)
254 os.makedirs(long_path)
254
255
255 # Test to see if the short path evaluates correctly.
256 # Test to see if the short path evaluates correctly.
256 short_path = os.path.join(tmpdir, 'THISIS~1')
257 short_path = os.path.join(tmpdir, 'THISIS~1')
257 evaluated_path = path.get_long_path_name(short_path)
258 evaluated_path = path.get_long_path_name(short_path)
258 nt.assert_equal(evaluated_path.lower(), long_path.lower())
259 nt.assert_equal(evaluated_path.lower(), long_path.lower())
259
260
260
261
261 @dec.skip_win32
262 @dec.skip_win32
262 def test_get_long_path_name():
263 def test_get_long_path_name():
263 p = path.get_long_path_name('/usr/local')
264 p = path.get_long_path_name('/usr/local')
264 nt.assert_equal(p,'/usr/local')
265 nt.assert_equal(p,'/usr/local')
265
266
266
267
267 class TestRaiseDeprecation(unittest.TestCase):
268 class TestRaiseDeprecation(unittest.TestCase):
268
269
269 @dec.skip_win32 # can't create not-user-writable dir on win
270 @dec.skip_win32 # can't create not-user-writable dir on win
270 @with_environment
271 @with_environment
271 def test_not_writable_ipdir(self):
272 def test_not_writable_ipdir(self):
272 tmpdir = tempfile.mkdtemp()
273 tmpdir = tempfile.mkdtemp()
273 os.name = "posix"
274 os.name = "posix"
274 env.pop('IPYTHON_DIR', None)
275 env.pop('IPYTHON_DIR', None)
275 env.pop('IPYTHONDIR', None)
276 env.pop('IPYTHONDIR', None)
276 env.pop('XDG_CONFIG_HOME', None)
277 env.pop('XDG_CONFIG_HOME', None)
277 env['HOME'] = tmpdir
278 env['HOME'] = tmpdir
278 ipdir = os.path.join(tmpdir, '.ipython')
279 ipdir = os.path.join(tmpdir, '.ipython')
279 os.mkdir(ipdir, 0o555)
280 os.mkdir(ipdir, 0o555)
280 try:
281 try:
281 open(os.path.join(ipdir, "_foo_"), 'w').close()
282 open(os.path.join(ipdir, "_foo_"), 'w').close()
282 except IOError:
283 except IOError:
283 pass
284 pass
284 else:
285 else:
285 # I can still write to an unwritable dir,
286 # I can still write to an unwritable dir,
286 # assume I'm root and skip the test
287 # assume I'm root and skip the test
287 raise SkipTest("I can't create directories that I can't write to")
288 raise SkipTest("I can't create directories that I can't write to")
288 with self.assertWarnsRegex(UserWarning, 'is not a writable location'):
289 with self.assertWarnsRegex(UserWarning, 'is not a writable location'):
289 ipdir = paths.get_ipython_dir()
290 ipdir = paths.get_ipython_dir()
290 env.pop('IPYTHON_DIR', None)
291 env.pop('IPYTHON_DIR', None)
291
292
292 @with_environment
293 @with_environment
293 def test_get_py_filename():
294 def test_get_py_filename():
294 os.chdir(TMP_TEST_DIR)
295 os.chdir(TMP_TEST_DIR)
295 with make_tempfile('foo.py'):
296 with make_tempfile('foo.py'):
296 nt.assert_equal(path.get_py_filename('foo.py'), 'foo.py')
297 nt.assert_equal(path.get_py_filename('foo.py'), 'foo.py')
297 nt.assert_equal(path.get_py_filename('foo'), 'foo.py')
298 nt.assert_equal(path.get_py_filename('foo'), 'foo.py')
298 with make_tempfile('foo'):
299 with make_tempfile('foo'):
299 nt.assert_equal(path.get_py_filename('foo'), 'foo')
300 nt.assert_equal(path.get_py_filename('foo'), 'foo')
300 nt.assert_raises(IOError, path.get_py_filename, 'foo.py')
301 nt.assert_raises(IOError, path.get_py_filename, 'foo.py')
301 nt.assert_raises(IOError, path.get_py_filename, 'foo')
302 nt.assert_raises(IOError, path.get_py_filename, 'foo')
302 nt.assert_raises(IOError, path.get_py_filename, 'foo.py')
303 nt.assert_raises(IOError, path.get_py_filename, 'foo.py')
303 true_fn = 'foo with spaces.py'
304 true_fn = 'foo with spaces.py'
304 with make_tempfile(true_fn):
305 with make_tempfile(true_fn):
305 nt.assert_equal(path.get_py_filename('foo with spaces'), true_fn)
306 nt.assert_equal(path.get_py_filename('foo with spaces'), true_fn)
306 nt.assert_equal(path.get_py_filename('foo with spaces.py'), true_fn)
307 nt.assert_equal(path.get_py_filename('foo with spaces.py'), true_fn)
307 nt.assert_raises(IOError, path.get_py_filename, '"foo with spaces.py"')
308 nt.assert_raises(IOError, path.get_py_filename, '"foo with spaces.py"')
308 nt.assert_raises(IOError, path.get_py_filename, "'foo with spaces.py'")
309 nt.assert_raises(IOError, path.get_py_filename, "'foo with spaces.py'")
309
310
310 @onlyif_unicode_paths
311 @onlyif_unicode_paths
311 def test_unicode_in_filename():
312 def test_unicode_in_filename():
312 """When a file doesn't exist, the exception raised should be safe to call
313 """When a file doesn't exist, the exception raised should be safe to call
313 str() on - i.e. in Python 2 it must only have ASCII characters.
314 str() on - i.e. in Python 2 it must only have ASCII characters.
314
315
315 https://github.com/ipython/ipython/issues/875
316 https://github.com/ipython/ipython/issues/875
316 """
317 """
317 try:
318 try:
318 # these calls should not throw unicode encode exceptions
319 # these calls should not throw unicode encode exceptions
319 path.get_py_filename('fooéè.py')
320 path.get_py_filename('fooéè.py')
320 except IOError as ex:
321 except IOError as ex:
321 str(ex)
322 str(ex)
322
323
323
324
324 class TestShellGlob(unittest.TestCase):
325 class TestShellGlob(unittest.TestCase):
325
326
326 @classmethod
327 @classmethod
327 def setUpClass(cls):
328 def setUpClass(cls):
328 cls.filenames_start_with_a = ['a0', 'a1', 'a2']
329 cls.filenames_start_with_a = ['a0', 'a1', 'a2']
329 cls.filenames_end_with_b = ['0b', '1b', '2b']
330 cls.filenames_end_with_b = ['0b', '1b', '2b']
330 cls.filenames = cls.filenames_start_with_a + cls.filenames_end_with_b
331 cls.filenames = cls.filenames_start_with_a + cls.filenames_end_with_b
331 cls.tempdir = TemporaryDirectory()
332 cls.tempdir = TemporaryDirectory()
332 td = cls.tempdir.name
333 td = cls.tempdir.name
333
334
334 with cls.in_tempdir():
335 with cls.in_tempdir():
335 # Create empty files
336 # Create empty files
336 for fname in cls.filenames:
337 for fname in cls.filenames:
337 open(os.path.join(td, fname), 'w').close()
338 open(os.path.join(td, fname), 'w').close()
338
339
339 @classmethod
340 @classmethod
340 def tearDownClass(cls):
341 def tearDownClass(cls):
341 cls.tempdir.cleanup()
342 cls.tempdir.cleanup()
342
343
343 @classmethod
344 @classmethod
344 @contextmanager
345 @contextmanager
345 def in_tempdir(cls):
346 def in_tempdir(cls):
346 save = os.getcwd()
347 save = os.getcwd()
347 try:
348 try:
348 os.chdir(cls.tempdir.name)
349 os.chdir(cls.tempdir.name)
349 yield
350 yield
350 finally:
351 finally:
351 os.chdir(save)
352 os.chdir(save)
352
353
353 def check_match(self, patterns, matches):
354 def check_match(self, patterns, matches):
354 with self.in_tempdir():
355 with self.in_tempdir():
355 # glob returns unordered list. that's why sorted is required.
356 # glob returns unordered list. that's why sorted is required.
356 nt.assert_equal(sorted(path.shellglob(patterns)),
357 nt.assert_equal(sorted(path.shellglob(patterns)),
357 sorted(matches))
358 sorted(matches))
358
359
359 def common_cases(self):
360 def common_cases(self):
360 return [
361 return [
361 (['*'], self.filenames),
362 (['*'], self.filenames),
362 (['a*'], self.filenames_start_with_a),
363 (['a*'], self.filenames_start_with_a),
363 (['*c'], ['*c']),
364 (['*c'], ['*c']),
364 (['*', 'a*', '*b', '*c'], self.filenames
365 (['*', 'a*', '*b', '*c'], self.filenames
365 + self.filenames_start_with_a
366 + self.filenames_start_with_a
366 + self.filenames_end_with_b
367 + self.filenames_end_with_b
367 + ['*c']),
368 + ['*c']),
368 (['a[012]'], self.filenames_start_with_a),
369 (['a[012]'], self.filenames_start_with_a),
369 ]
370 ]
370
371
371 @skip_win32
372 @skip_win32
372 def test_match_posix(self):
373 def test_match_posix(self):
373 for (patterns, matches) in self.common_cases() + [
374 for (patterns, matches) in self.common_cases() + [
374 ([r'\*'], ['*']),
375 ([r'\*'], ['*']),
375 ([r'a\*', 'a*'], ['a*'] + self.filenames_start_with_a),
376 ([r'a\*', 'a*'], ['a*'] + self.filenames_start_with_a),
376 ([r'a\[012]'], ['a[012]']),
377 ([r'a\[012]'], ['a[012]']),
377 ]:
378 ]:
378 yield (self.check_match, patterns, matches)
379 yield (self.check_match, patterns, matches)
379
380
380 @skip_if_not_win32
381 @skip_if_not_win32
381 def test_match_windows(self):
382 def test_match_windows(self):
382 for (patterns, matches) in self.common_cases() + [
383 for (patterns, matches) in self.common_cases() + [
383 # In windows, backslash is interpreted as path
384 # In windows, backslash is interpreted as path
384 # separator. Therefore, you can't escape glob
385 # separator. Therefore, you can't escape glob
385 # using it.
386 # using it.
386 ([r'a\*', 'a*'], [r'a\*'] + self.filenames_start_with_a),
387 ([r'a\*', 'a*'], [r'a\*'] + self.filenames_start_with_a),
387 ([r'a\[012]'], [r'a\[012]']),
388 ([r'a\[012]'], [r'a\[012]']),
388 ]:
389 ]:
389 yield (self.check_match, patterns, matches)
390 yield (self.check_match, patterns, matches)
390
391
391
392
392 def test_unescape_glob():
393 def test_unescape_glob():
393 nt.assert_equal(path.unescape_glob(r'\*\[\!\]\?'), '*[!]?')
394 nt.assert_equal(path.unescape_glob(r'\*\[\!\]\?'), '*[!]?')
394 nt.assert_equal(path.unescape_glob(r'\\*'), r'\*')
395 nt.assert_equal(path.unescape_glob(r'\\*'), r'\*')
395 nt.assert_equal(path.unescape_glob(r'\\\*'), r'\*')
396 nt.assert_equal(path.unescape_glob(r'\\\*'), r'\*')
396 nt.assert_equal(path.unescape_glob(r'\\a'), r'\a')
397 nt.assert_equal(path.unescape_glob(r'\\a'), r'\a')
397 nt.assert_equal(path.unescape_glob(r'\a'), r'\a')
398 nt.assert_equal(path.unescape_glob(r'\a'), r'\a')
398
399
399
400
400 @onlyif_unicode_paths
401 @onlyif_unicode_paths
401 def test_ensure_dir_exists():
402 def test_ensure_dir_exists():
402 with TemporaryDirectory() as td:
403 with TemporaryDirectory() as td:
403 d = os.path.join(td, 'βˆ‚ir')
404 d = os.path.join(td, 'βˆ‚ir')
404 path.ensure_dir_exists(d) # create it
405 path.ensure_dir_exists(d) # create it
405 assert os.path.isdir(d)
406 assert os.path.isdir(d)
406 path.ensure_dir_exists(d) # no-op
407 path.ensure_dir_exists(d) # no-op
407 f = os.path.join(td, 'Ζ’ile')
408 f = os.path.join(td, 'Ζ’ile')
408 open(f, 'w').close() # touch
409 open(f, 'w').close() # touch
409 with nt.assert_raises(IOError):
410 with nt.assert_raises(IOError):
410 path.ensure_dir_exists(f)
411 path.ensure_dir_exists(f)
411
412
412 class TestLinkOrCopy(unittest.TestCase):
413 class TestLinkOrCopy(unittest.TestCase):
413 def setUp(self):
414 def setUp(self):
414 self.tempdir = TemporaryDirectory()
415 self.tempdir = TemporaryDirectory()
415 self.src = self.dst("src")
416 self.src = self.dst("src")
416 with open(self.src, "w") as f:
417 with open(self.src, "w") as f:
417 f.write("Hello, world!")
418 f.write("Hello, world!")
418
419
419 def tearDown(self):
420 def tearDown(self):
420 self.tempdir.cleanup()
421 self.tempdir.cleanup()
421
422
422 def dst(self, *args):
423 def dst(self, *args):
423 return os.path.join(self.tempdir.name, *args)
424 return os.path.join(self.tempdir.name, *args)
424
425
425 def assert_inode_not_equal(self, a, b):
426 def assert_inode_not_equal(self, a, b):
426 nt.assert_not_equal(os.stat(a).st_ino, os.stat(b).st_ino,
427 nt.assert_not_equal(os.stat(a).st_ino, os.stat(b).st_ino,
427 "%r and %r do reference the same indoes" %(a, b))
428 "%r and %r do reference the same indoes" %(a, b))
428
429
429 def assert_inode_equal(self, a, b):
430 def assert_inode_equal(self, a, b):
430 nt.assert_equal(os.stat(a).st_ino, os.stat(b).st_ino,
431 nt.assert_equal(os.stat(a).st_ino, os.stat(b).st_ino,
431 "%r and %r do not reference the same indoes" %(a, b))
432 "%r and %r do not reference the same indoes" %(a, b))
432
433
433 def assert_content_equal(self, a, b):
434 def assert_content_equal(self, a, b):
434 with open(a) as a_f:
435 with open(a) as a_f:
435 with open(b) as b_f:
436 with open(b) as b_f:
436 nt.assert_equal(a_f.read(), b_f.read())
437 nt.assert_equal(a_f.read(), b_f.read())
437
438
438 @skip_win32
439 @skip_win32
439 def test_link_successful(self):
440 def test_link_successful(self):
440 dst = self.dst("target")
441 dst = self.dst("target")
441 path.link_or_copy(self.src, dst)
442 path.link_or_copy(self.src, dst)
442 self.assert_inode_equal(self.src, dst)
443 self.assert_inode_equal(self.src, dst)
443
444
444 @skip_win32
445 @skip_win32
445 def test_link_into_dir(self):
446 def test_link_into_dir(self):
446 dst = self.dst("some_dir")
447 dst = self.dst("some_dir")
447 os.mkdir(dst)
448 os.mkdir(dst)
448 path.link_or_copy(self.src, dst)
449 path.link_or_copy(self.src, dst)
449 expected_dst = self.dst("some_dir", os.path.basename(self.src))
450 expected_dst = self.dst("some_dir", os.path.basename(self.src))
450 self.assert_inode_equal(self.src, expected_dst)
451 self.assert_inode_equal(self.src, expected_dst)
451
452
452 @skip_win32
453 @skip_win32
453 def test_target_exists(self):
454 def test_target_exists(self):
454 dst = self.dst("target")
455 dst = self.dst("target")
455 open(dst, "w").close()
456 open(dst, "w").close()
456 path.link_or_copy(self.src, dst)
457 path.link_or_copy(self.src, dst)
457 self.assert_inode_equal(self.src, dst)
458 self.assert_inode_equal(self.src, dst)
458
459
459 @skip_win32
460 @skip_win32
460 def test_no_link(self):
461 def test_no_link(self):
461 real_link = os.link
462 real_link = os.link
462 try:
463 try:
463 del os.link
464 del os.link
464 dst = self.dst("target")
465 dst = self.dst("target")
465 path.link_or_copy(self.src, dst)
466 path.link_or_copy(self.src, dst)
466 self.assert_content_equal(self.src, dst)
467 self.assert_content_equal(self.src, dst)
467 self.assert_inode_not_equal(self.src, dst)
468 self.assert_inode_not_equal(self.src, dst)
468 finally:
469 finally:
469 os.link = real_link
470 os.link = real_link
470
471
471 @skip_if_not_win32
472 @skip_if_not_win32
472 def test_windows(self):
473 def test_windows(self):
473 dst = self.dst("target")
474 dst = self.dst("target")
474 path.link_or_copy(self.src, dst)
475 path.link_or_copy(self.src, dst)
475 self.assert_content_equal(self.src, dst)
476 self.assert_content_equal(self.src, dst)
476
477
477 def test_link_twice(self):
478 def test_link_twice(self):
478 # Linking the same file twice shouldn't leave duplicates around.
479 # Linking the same file twice shouldn't leave duplicates around.
479 # See https://github.com/ipython/ipython/issues/6450
480 # See https://github.com/ipython/ipython/issues/6450
480 dst = self.dst('target')
481 dst = self.dst('target')
481 path.link_or_copy(self.src, dst)
482 path.link_or_copy(self.src, dst)
482 path.link_or_copy(self.src, dst)
483 path.link_or_copy(self.src, dst)
483 self.assert_inode_equal(self.src, dst)
484 self.assert_inode_equal(self.src, dst)
484 nt.assert_equal(sorted(os.listdir(self.tempdir.name)), ['src', 'target'])
485 nt.assert_equal(sorted(os.listdir(self.tempdir.name)), ['src', 'target'])
General Comments 0
You need to be logged in to leave comments. Login now