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