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