##// END OF EJS Templates
Add link_or_copy to IPython.utils.path
David Wolever -
Show More
@@ -1,512 +1,562 b''
1 1 # encoding: utf-8
2 2 """
3 3 Utilities for path handling.
4 4 """
5 5
6 6 #-----------------------------------------------------------------------------
7 7 # Copyright (C) 2008-2011 The IPython Development Team
8 8 #
9 9 # Distributed under the terms of the BSD License. The full license is in
10 10 # the file COPYING, distributed as part of this software.
11 11 #-----------------------------------------------------------------------------
12 12
13 13 #-----------------------------------------------------------------------------
14 14 # Imports
15 15 #-----------------------------------------------------------------------------
16 16
17 17 import os
18 18 import sys
19 import errno
20 import shutil
21 import random
19 22 import tempfile
20 23 import warnings
21 24 from hashlib import md5
22 25 import glob
23 26
24 27 import IPython
25 28 from IPython.testing.skipdoctest import skip_doctest
26 29 from IPython.utils.process import system
27 30 from IPython.utils.importstring import import_item
28 31 from IPython.utils import py3compat
29 32 #-----------------------------------------------------------------------------
30 33 # Code
31 34 #-----------------------------------------------------------------------------
32 35
33 36 fs_encoding = sys.getfilesystemencoding()
34 37
35 38 def _get_long_path_name(path):
36 39 """Dummy no-op."""
37 40 return path
38 41
39 42 def _writable_dir(path):
40 43 """Whether `path` is a directory, to which the user has write access."""
41 44 return os.path.isdir(path) and os.access(path, os.W_OK)
42 45
43 46 if sys.platform == 'win32':
44 47 @skip_doctest
45 48 def _get_long_path_name(path):
46 49 """Get a long path name (expand ~) on Windows using ctypes.
47 50
48 51 Examples
49 52 --------
50 53
51 54 >>> get_long_path_name('c:\\docume~1')
52 55 u'c:\\\\Documents and Settings'
53 56
54 57 """
55 58 try:
56 59 import ctypes
57 60 except ImportError:
58 61 raise ImportError('you need to have ctypes installed for this to work')
59 62 _GetLongPathName = ctypes.windll.kernel32.GetLongPathNameW
60 63 _GetLongPathName.argtypes = [ctypes.c_wchar_p, ctypes.c_wchar_p,
61 64 ctypes.c_uint ]
62 65
63 66 buf = ctypes.create_unicode_buffer(260)
64 67 rv = _GetLongPathName(path, buf, 260)
65 68 if rv == 0 or rv > 260:
66 69 return path
67 70 else:
68 71 return buf.value
69 72
70 73
71 74 def get_long_path_name(path):
72 75 """Expand a path into its long form.
73 76
74 77 On Windows this expands any ~ in the paths. On other platforms, it is
75 78 a null operation.
76 79 """
77 80 return _get_long_path_name(path)
78 81
79 82
80 83 def unquote_filename(name, win32=(sys.platform=='win32')):
81 84 """ On Windows, remove leading and trailing quotes from filenames.
82 85 """
83 86 if win32:
84 87 if name.startswith(("'", '"')) and name.endswith(("'", '"')):
85 88 name = name[1:-1]
86 89 return name
87 90
88 91
89 92 def get_py_filename(name, force_win32=None):
90 93 """Return a valid python filename in the current directory.
91 94
92 95 If the given name is not a file, it adds '.py' and searches again.
93 96 Raises IOError with an informative message if the file isn't found.
94 97
95 98 On Windows, apply Windows semantics to the filename. In particular, remove
96 99 any quoting that has been applied to it. This option can be forced for
97 100 testing purposes.
98 101 """
99 102
100 103 name = os.path.expanduser(name)
101 104 if force_win32 is None:
102 105 win32 = (sys.platform == 'win32')
103 106 else:
104 107 win32 = force_win32
105 108 name = unquote_filename(name, win32=win32)
106 109 if not os.path.isfile(name) and not name.endswith('.py'):
107 110 name += '.py'
108 111 if os.path.isfile(name):
109 112 return name
110 113 else:
111 114 raise IOError('File `%r` not found.' % name)
112 115
113 116
114 117 def filefind(filename, path_dirs=None):
115 118 """Find a file by looking through a sequence of paths.
116 119
117 120 This iterates through a sequence of paths looking for a file and returns
118 121 the full, absolute path of the first occurence of the file. If no set of
119 122 path dirs is given, the filename is tested as is, after running through
120 123 :func:`expandvars` and :func:`expanduser`. Thus a simple call::
121 124
122 125 filefind('myfile.txt')
123 126
124 127 will find the file in the current working dir, but::
125 128
126 129 filefind('~/myfile.txt')
127 130
128 131 Will find the file in the users home directory. This function does not
129 132 automatically try any paths, such as the cwd or the user's home directory.
130 133
131 134 Parameters
132 135 ----------
133 136 filename : str
134 137 The filename to look for.
135 138 path_dirs : str, None or sequence of str
136 139 The sequence of paths to look for the file in. If None, the filename
137 140 need to be absolute or be in the cwd. If a string, the string is
138 141 put into a sequence and the searched. If a sequence, walk through
139 142 each element and join with ``filename``, calling :func:`expandvars`
140 143 and :func:`expanduser` before testing for existence.
141 144
142 145 Returns
143 146 -------
144 147 Raises :exc:`IOError` or returns absolute path to file.
145 148 """
146 149
147 150 # If paths are quoted, abspath gets confused, strip them...
148 151 filename = filename.strip('"').strip("'")
149 152 # If the input is an absolute path, just check it exists
150 153 if os.path.isabs(filename) and os.path.isfile(filename):
151 154 return filename
152 155
153 156 if path_dirs is None:
154 157 path_dirs = ("",)
155 158 elif isinstance(path_dirs, basestring):
156 159 path_dirs = (path_dirs,)
157 160
158 161 for path in path_dirs:
159 162 if path == '.': path = os.getcwdu()
160 163 testname = expand_path(os.path.join(path, filename))
161 164 if os.path.isfile(testname):
162 165 return os.path.abspath(testname)
163 166
164 167 raise IOError("File %r does not exist in any of the search paths: %r" %
165 168 (filename, path_dirs) )
166 169
167 170
168 171 class HomeDirError(Exception):
169 172 pass
170 173
171 174
172 175 def get_home_dir(require_writable=False):
173 176 """Return the 'home' directory, as a unicode string.
174 177
175 178 Uses os.path.expanduser('~'), and checks for writability.
176 179
177 180 See stdlib docs for how this is determined.
178 181 $HOME is first priority on *ALL* platforms.
179 182
180 183 Parameters
181 184 ----------
182 185
183 186 require_writable : bool [default: False]
184 187 if True:
185 188 guarantees the return value is a writable directory, otherwise
186 189 raises HomeDirError
187 190 if False:
188 191 The path is resolved, but it is not guaranteed to exist or be writable.
189 192 """
190 193
191 194 homedir = os.path.expanduser('~')
192 195 # Next line will make things work even when /home/ is a symlink to
193 196 # /usr/home as it is on FreeBSD, for example
194 197 homedir = os.path.realpath(homedir)
195 198
196 199 if not _writable_dir(homedir) and os.name == 'nt':
197 200 # expanduser failed, use the registry to get the 'My Documents' folder.
198 201 try:
199 202 import _winreg as wreg
200 203 key = wreg.OpenKey(
201 204 wreg.HKEY_CURRENT_USER,
202 205 "Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders"
203 206 )
204 207 homedir = wreg.QueryValueEx(key,'Personal')[0]
205 208 key.Close()
206 209 except:
207 210 pass
208 211
209 212 if (not require_writable) or _writable_dir(homedir):
210 213 return py3compat.cast_unicode(homedir, fs_encoding)
211 214 else:
212 215 raise HomeDirError('%s is not a writable dir, '
213 216 'set $HOME environment variable to override' % homedir)
214 217
215 218 def get_xdg_dir():
216 219 """Return the XDG_CONFIG_HOME, if it is defined and exists, else None.
217 220
218 221 This is only for non-OS X posix (Linux,Unix,etc.) systems.
219 222 """
220 223
221 224 env = os.environ
222 225
223 226 if os.name == 'posix' and sys.platform != 'darwin':
224 227 # Linux, Unix, AIX, etc.
225 228 # use ~/.config if empty OR not set
226 229 xdg = env.get("XDG_CONFIG_HOME", None) or os.path.join(get_home_dir(), '.config')
227 230 if xdg and _writable_dir(xdg):
228 231 return py3compat.cast_unicode(xdg, fs_encoding)
229 232
230 233 return None
231 234
232 235
233 236 def get_xdg_cache_dir():
234 237 """Return the XDG_CACHE_HOME, if it is defined and exists, else None.
235 238
236 239 This is only for non-OS X posix (Linux,Unix,etc.) systems.
237 240 """
238 241
239 242 env = os.environ
240 243
241 244 if os.name == 'posix' and sys.platform != 'darwin':
242 245 # Linux, Unix, AIX, etc.
243 246 # use ~/.cache if empty OR not set
244 247 xdg = env.get("XDG_CACHE_HOME", None) or os.path.join(get_home_dir(), '.cache')
245 248 if xdg and _writable_dir(xdg):
246 249 return py3compat.cast_unicode(xdg, fs_encoding)
247 250
248 251 return None
249 252
250 253
251 254 def get_ipython_dir():
252 255 """Get the IPython directory for this platform and user.
253 256
254 257 This uses the logic in `get_home_dir` to find the home directory
255 258 and then adds .ipython to the end of the path.
256 259 """
257 260
258 261 env = os.environ
259 262 pjoin = os.path.join
260 263
261 264
262 265 ipdir_def = '.ipython'
263 266 xdg_def = 'ipython'
264 267
265 268 home_dir = get_home_dir()
266 269 xdg_dir = get_xdg_dir()
267 270
268 271 # import pdb; pdb.set_trace() # dbg
269 272 if 'IPYTHON_DIR' in env:
270 273 warnings.warn('The environment variable IPYTHON_DIR is deprecated. '
271 274 'Please use IPYTHONDIR instead.')
272 275 ipdir = env.get('IPYTHONDIR', env.get('IPYTHON_DIR', None))
273 276 if ipdir is None:
274 277 # not set explicitly, use XDG_CONFIG_HOME or HOME
275 278 home_ipdir = pjoin(home_dir, ipdir_def)
276 279 if xdg_dir:
277 280 # use XDG, as long as the user isn't already
278 281 # using $HOME/.ipython and *not* XDG/ipython
279 282
280 283 xdg_ipdir = pjoin(xdg_dir, xdg_def)
281 284
282 285 if _writable_dir(xdg_ipdir) or not _writable_dir(home_ipdir):
283 286 ipdir = xdg_ipdir
284 287
285 288 if ipdir is None:
286 289 # not using XDG
287 290 ipdir = home_ipdir
288 291
289 292 ipdir = os.path.normpath(os.path.expanduser(ipdir))
290 293
291 294 if os.path.exists(ipdir) and not _writable_dir(ipdir):
292 295 # ipdir exists, but is not writable
293 296 warnings.warn("IPython dir '%s' is not a writable location,"
294 297 " using a temp directory."%ipdir)
295 298 ipdir = tempfile.mkdtemp()
296 299 elif not os.path.exists(ipdir):
297 300 parent = os.path.dirname(ipdir)
298 301 if not _writable_dir(parent):
299 302 # ipdir does not exist and parent isn't writable
300 303 warnings.warn("IPython parent '%s' is not a writable location,"
301 304 " using a temp directory."%parent)
302 305 ipdir = tempfile.mkdtemp()
303 306
304 307 return py3compat.cast_unicode(ipdir, fs_encoding)
305 308
306 309
307 310 def get_ipython_cache_dir():
308 311 """Get the cache directory it is created if it does not exist."""
309 312 xdgdir = get_xdg_cache_dir()
310 313 if xdgdir is None:
311 314 return get_ipython_dir()
312 315 ipdir = os.path.join(xdgdir, "ipython")
313 316 if not os.path.exists(ipdir) and _writable_dir(xdgdir):
314 317 os.makedirs(ipdir)
315 318 elif not _writable_dir(xdgdir):
316 319 return get_ipython_dir()
317 320
318 321 return py3compat.cast_unicode(ipdir, fs_encoding)
319 322
320 323
321 324 def get_ipython_package_dir():
322 325 """Get the base directory where IPython itself is installed."""
323 326 ipdir = os.path.dirname(IPython.__file__)
324 327 return py3compat.cast_unicode(ipdir, fs_encoding)
325 328
326 329
327 330 def get_ipython_module_path(module_str):
328 331 """Find the path to an IPython module in this version of IPython.
329 332
330 333 This will always find the version of the module that is in this importable
331 334 IPython package. This will always return the path to the ``.py``
332 335 version of the module.
333 336 """
334 337 if module_str == 'IPython':
335 338 return os.path.join(get_ipython_package_dir(), '__init__.py')
336 339 mod = import_item(module_str)
337 340 the_path = mod.__file__.replace('.pyc', '.py')
338 341 the_path = the_path.replace('.pyo', '.py')
339 342 return py3compat.cast_unicode(the_path, fs_encoding)
340 343
341 344 def locate_profile(profile='default'):
342 345 """Find the path to the folder associated with a given profile.
343 346
344 347 I.e. find $IPYTHONDIR/profile_whatever.
345 348 """
346 349 from IPython.core.profiledir import ProfileDir, ProfileDirError
347 350 try:
348 351 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile)
349 352 except ProfileDirError:
350 353 # IOError makes more sense when people are expecting a path
351 354 raise IOError("Couldn't find profile %r" % profile)
352 355 return pd.location
353 356
354 357 def expand_path(s):
355 358 """Expand $VARS and ~names in a string, like a shell
356 359
357 360 :Examples:
358 361
359 362 In [2]: os.environ['FOO']='test'
360 363
361 364 In [3]: expand_path('variable FOO is $FOO')
362 365 Out[3]: 'variable FOO is test'
363 366 """
364 367 # This is a pretty subtle hack. When expand user is given a UNC path
365 368 # on Windows (\\server\share$\%username%), os.path.expandvars, removes
366 369 # the $ to get (\\server\share\%username%). I think it considered $
367 370 # alone an empty var. But, we need the $ to remains there (it indicates
368 371 # a hidden share).
369 372 if os.name=='nt':
370 373 s = s.replace('$\\', 'IPYTHON_TEMP')
371 374 s = os.path.expandvars(os.path.expanduser(s))
372 375 if os.name=='nt':
373 376 s = s.replace('IPYTHON_TEMP', '$\\')
374 377 return s
375 378
376 379
377 380 def unescape_glob(string):
378 381 """Unescape glob pattern in `string`."""
379 382 def unescape(s):
380 383 for pattern in '*[]!?':
381 384 s = s.replace(r'\{0}'.format(pattern), pattern)
382 385 return s
383 386 return '\\'.join(map(unescape, string.split('\\\\')))
384 387
385 388
386 389 def shellglob(args):
387 390 """
388 391 Do glob expansion for each element in `args` and return a flattened list.
389 392
390 393 Unmatched glob pattern will remain as-is in the returned list.
391 394
392 395 """
393 396 expanded = []
394 397 # Do not unescape backslash in Windows as it is interpreted as
395 398 # path separator:
396 399 unescape = unescape_glob if sys.platform != 'win32' else lambda x: x
397 400 for a in args:
398 401 expanded.extend(glob.glob(a) or [unescape(a)])
399 402 return expanded
400 403
401 404
402 405 def target_outdated(target,deps):
403 406 """Determine whether a target is out of date.
404 407
405 408 target_outdated(target,deps) -> 1/0
406 409
407 410 deps: list of filenames which MUST exist.
408 411 target: single filename which may or may not exist.
409 412
410 413 If target doesn't exist or is older than any file listed in deps, return
411 414 true, otherwise return false.
412 415 """
413 416 try:
414 417 target_time = os.path.getmtime(target)
415 418 except os.error:
416 419 return 1
417 420 for dep in deps:
418 421 dep_time = os.path.getmtime(dep)
419 422 if dep_time > target_time:
420 423 #print "For target",target,"Dep failed:",dep # dbg
421 424 #print "times (dep,tar):",dep_time,target_time # dbg
422 425 return 1
423 426 return 0
424 427
425 428
426 429 def target_update(target,deps,cmd):
427 430 """Update a target with a given command given a list of dependencies.
428 431
429 432 target_update(target,deps,cmd) -> runs cmd if target is outdated.
430 433
431 434 This is just a wrapper around target_outdated() which calls the given
432 435 command if target is outdated."""
433 436
434 437 if target_outdated(target,deps):
435 438 system(cmd)
436 439
437 440 def filehash(path):
438 441 """Make an MD5 hash of a file, ignoring any differences in line
439 442 ending characters."""
440 443 with open(path, "rU") as f:
441 444 return md5(py3compat.str_to_bytes(f.read())).hexdigest()
442 445
443 446 # If the config is unmodified from the default, we'll just delete it.
444 447 # These are consistent for 0.10.x, thankfully. We're not going to worry about
445 448 # older versions.
446 449 old_config_md5 = {'ipy_user_conf.py': 'fc108bedff4b9a00f91fa0a5999140d3',
447 450 'ipythonrc': '12a68954f3403eea2eec09dc8fe5a9b5'}
448 451
449 452 def check_for_old_config(ipython_dir=None):
450 453 """Check for old config files, and present a warning if they exist.
451 454
452 455 A link to the docs of the new config is included in the message.
453 456
454 457 This should mitigate confusion with the transition to the new
455 458 config system in 0.11.
456 459 """
457 460 if ipython_dir is None:
458 461 ipython_dir = get_ipython_dir()
459 462
460 463 old_configs = ['ipy_user_conf.py', 'ipythonrc', 'ipython_config.py']
461 464 warned = False
462 465 for cfg in old_configs:
463 466 f = os.path.join(ipython_dir, cfg)
464 467 if os.path.exists(f):
465 468 if filehash(f) == old_config_md5.get(cfg, ''):
466 469 os.unlink(f)
467 470 else:
468 471 warnings.warn("Found old IPython config file %r (modified by user)"%f)
469 472 warned = True
470 473
471 474 if warned:
472 475 warnings.warn("""
473 476 The IPython configuration system has changed as of 0.11, and these files will
474 477 be ignored. See http://ipython.github.com/ipython-doc/dev/config for details
475 478 of the new config system.
476 479 To start configuring IPython, do `ipython profile create`, and edit
477 480 `ipython_config.py` in <ipython_dir>/profile_default.
478 481 If you need to leave the old config files in place for an older version of
479 482 IPython and want to suppress this warning message, set
480 483 `c.InteractiveShellApp.ignore_old_config=True` in the new config.""")
481 484
482 485 def get_security_file(filename, profile='default'):
483 486 """Return the absolute path of a security file given by filename and profile
484 487
485 488 This allows users and developers to find security files without
486 489 knowledge of the IPython directory structure. The search path
487 490 will be ['.', profile.security_dir]
488 491
489 492 Parameters
490 493 ----------
491 494
492 495 filename : str
493 496 The file to be found. If it is passed as an absolute path, it will
494 497 simply be returned.
495 498 profile : str [default: 'default']
496 499 The name of the profile to search. Leaving this unspecified
497 500 The file to be found. If it is passed as an absolute path, fname will
498 501 simply be returned.
499 502
500 503 Returns
501 504 -------
502 505 Raises :exc:`IOError` if file not found or returns absolute path to file.
503 506 """
504 507 # import here, because profiledir also imports from utils.path
505 508 from IPython.core.profiledir import ProfileDir
506 509 try:
507 510 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile)
508 511 except Exception:
509 512 # will raise ProfileDirError if no such profile
510 513 raise IOError("Profile %r not found")
511 514 return filefind(filename, ['.', pd.security_dir])
512 515
516
517 def link(src, dst):
518 """Attempts to hardlink 'src' to 'dst', errno on failure (or 0 on success).
519
520 Note that the special errno ``1998`` will be returned if ``os.link`` isn't
521 supported by the operating system.
522 """
523
524 if not hasattr(os, "link"):
525 return 1998
526 link_errno = 0
527 try:
528 os.link(src, dst)
529 except OSError as e:
530 link_errno = e.errno
531 return link_errno
532
533
534 def link_or_copy(src, dst):
535 """Attempts to hardlink ``src`` to ``dst``, copying if the link fails.
536
537 Attempts to maintain the semantics of ``shutil.copy``.
538
539 Because ``os.link`` does not overwrite files, a unique temporary file
540 will be used if the target already exists, then that file will be moved
541 into place.
542 """
543
544 if os.path.isdir(dst):
545 dst = os.path.join(dst, os.path.basename(src))
546
547 link_errno = link(src, dst)
548 if link_errno == errno.EEXIST:
549 new_dst = dst + "-temp-%04X" %(random.randint(1, 16**4), )
550 try:
551 link_or_copy(src, new_dst)
552 except:
553 try:
554 os.remove(new_dst)
555 except OSError:
556 pass
557 raise
558 os.rename(new_dst, dst)
559 elif link_errno != 0:
560 # Either link isn't supported, or the filesystem doesn't support
561 # linking, or 'src' and 'dst' are on different filesystems.
562 shutil.copy(src, dst)
@@ -1,561 +1,626 b''
1 1 # encoding: utf-8
2 2 """Tests for IPython.utils.path.py"""
3 3
4 4 #-----------------------------------------------------------------------------
5 5 # Copyright (C) 2008-2011 The IPython Development Team
6 6 #
7 7 # Distributed under the terms of the BSD License. The full license is in
8 8 # the file COPYING, distributed as part of this software.
9 9 #-----------------------------------------------------------------------------
10 10
11 11 #-----------------------------------------------------------------------------
12 12 # Imports
13 13 #-----------------------------------------------------------------------------
14 14
15 15 from __future__ import with_statement
16 16
17 17 import os
18 18 import shutil
19 19 import sys
20 20 import tempfile
21 from contextlib import contextmanager
21 from contextlib import contextmanager, nested
22 22
23 23 from os.path import join, abspath, split
24 24
25 25 import nose.tools as nt
26 26
27 27 from nose import with_setup
28 28
29 29 import IPython
30 30 from IPython.testing import decorators as dec
31 31 from IPython.testing.decorators import skip_if_not_win32, skip_win32
32 32 from IPython.testing.tools import make_tempfile, AssertPrints
33 33 from IPython.utils import path
34 34 from IPython.utils import py3compat
35 35 from IPython.utils.tempdir import TemporaryDirectory
36 36
37 37 # Platform-dependent imports
38 38 try:
39 39 import _winreg as wreg
40 40 except ImportError:
41 41 #Fake _winreg module on none windows platforms
42 42 import types
43 43 wr_name = "winreg" if py3compat.PY3 else "_winreg"
44 44 sys.modules[wr_name] = types.ModuleType(wr_name)
45 45 import _winreg as wreg
46 46 #Add entries that needs to be stubbed by the testing code
47 47 (wreg.OpenKey, wreg.QueryValueEx,) = (None, None)
48 48
49 49 try:
50 50 reload
51 51 except NameError: # Python 3
52 52 from imp import reload
53 53
54 54 #-----------------------------------------------------------------------------
55 55 # Globals
56 56 #-----------------------------------------------------------------------------
57 57 env = os.environ
58 58 TEST_FILE_PATH = split(abspath(__file__))[0]
59 59 TMP_TEST_DIR = tempfile.mkdtemp()
60 60 HOME_TEST_DIR = join(TMP_TEST_DIR, "home_test_dir")
61 61 XDG_TEST_DIR = join(HOME_TEST_DIR, "xdg_test_dir")
62 62 XDG_CACHE_DIR = join(HOME_TEST_DIR, "xdg_cache_dir")
63 63 IP_TEST_DIR = join(HOME_TEST_DIR,'.ipython')
64 64 #
65 65 # Setup/teardown functions/decorators
66 66 #
67 67
68 68 def setup():
69 69 """Setup testenvironment for the module:
70 70
71 71 - Adds dummy home dir tree
72 72 """
73 73 # Do not mask exceptions here. In particular, catching WindowsError is a
74 74 # problem because that exception is only defined on Windows...
75 75 os.makedirs(IP_TEST_DIR)
76 76 os.makedirs(os.path.join(XDG_TEST_DIR, 'ipython'))
77 77 os.makedirs(os.path.join(XDG_CACHE_DIR, 'ipython'))
78 78
79 79
80 80 def teardown():
81 81 """Teardown testenvironment for the module:
82 82
83 83 - Remove dummy home dir tree
84 84 """
85 85 # Note: we remove the parent test dir, which is the root of all test
86 86 # subdirs we may have created. Use shutil instead of os.removedirs, so
87 87 # that non-empty directories are all recursively removed.
88 88 shutil.rmtree(TMP_TEST_DIR)
89 89
90 90
91 91 def setup_environment():
92 92 """Setup testenvironment for some functions that are tested
93 93 in this module. In particular this functions stores attributes
94 94 and other things that we need to stub in some test functions.
95 95 This needs to be done on a function level and not module level because
96 96 each testfunction needs a pristine environment.
97 97 """
98 98 global oldstuff, platformstuff
99 99 oldstuff = (env.copy(), os.name, sys.platform, path.get_home_dir, IPython.__file__, os.getcwd())
100 100
101 101 if os.name == 'nt':
102 102 platformstuff = (wreg.OpenKey, wreg.QueryValueEx,)
103 103
104 104
105 105 def teardown_environment():
106 106 """Restore things that were remembered by the setup_environment function
107 107 """
108 108 (oldenv, os.name, sys.platform, path.get_home_dir, IPython.__file__, old_wd) = oldstuff
109 109 os.chdir(old_wd)
110 110 reload(path)
111 111
112 112 for key in env.keys():
113 113 if key not in oldenv:
114 114 del env[key]
115 115 env.update(oldenv)
116 116 if hasattr(sys, 'frozen'):
117 117 del sys.frozen
118 118 if os.name == 'nt':
119 119 (wreg.OpenKey, wreg.QueryValueEx,) = platformstuff
120 120
121 121 # Build decorator that uses the setup_environment/setup_environment
122 122 with_environment = with_setup(setup_environment, teardown_environment)
123 123
124 124 @skip_if_not_win32
125 125 @with_environment
126 126 def test_get_home_dir_1():
127 127 """Testcase for py2exe logic, un-compressed lib
128 128 """
129 129 unfrozen = path.get_home_dir()
130 130 sys.frozen = True
131 131
132 132 #fake filename for IPython.__init__
133 133 IPython.__file__ = abspath(join(HOME_TEST_DIR, "Lib/IPython/__init__.py"))
134 134
135 135 home_dir = path.get_home_dir()
136 136 nt.assert_equal(home_dir, unfrozen)
137 137
138 138
139 139 @skip_if_not_win32
140 140 @with_environment
141 141 def test_get_home_dir_2():
142 142 """Testcase for py2exe logic, compressed lib
143 143 """
144 144 unfrozen = path.get_home_dir()
145 145 sys.frozen = True
146 146 #fake filename for IPython.__init__
147 147 IPython.__file__ = abspath(join(HOME_TEST_DIR, "Library.zip/IPython/__init__.py")).lower()
148 148
149 149 home_dir = path.get_home_dir(True)
150 150 nt.assert_equal(home_dir, unfrozen)
151 151
152 152
153 153 @with_environment
154 154 def test_get_home_dir_3():
155 155 """get_home_dir() uses $HOME if set"""
156 156 env["HOME"] = HOME_TEST_DIR
157 157 home_dir = path.get_home_dir(True)
158 158 # get_home_dir expands symlinks
159 159 nt.assert_equal(home_dir, os.path.realpath(env["HOME"]))
160 160
161 161
162 162 @with_environment
163 163 def test_get_home_dir_4():
164 164 """get_home_dir() still works if $HOME is not set"""
165 165
166 166 if 'HOME' in env: del env['HOME']
167 167 # this should still succeed, but we don't care what the answer is
168 168 home = path.get_home_dir(False)
169 169
170 170 @with_environment
171 171 def test_get_home_dir_5():
172 172 """raise HomeDirError if $HOME is specified, but not a writable dir"""
173 173 env['HOME'] = abspath(HOME_TEST_DIR+'garbage')
174 174 # set os.name = posix, to prevent My Documents fallback on Windows
175 175 os.name = 'posix'
176 176 nt.assert_raises(path.HomeDirError, path.get_home_dir, True)
177 177
178 178
179 179 # Should we stub wreg fully so we can run the test on all platforms?
180 180 @skip_if_not_win32
181 181 @with_environment
182 182 def test_get_home_dir_8():
183 183 """Using registry hack for 'My Documents', os=='nt'
184 184
185 185 HOMESHARE, HOMEDRIVE, HOMEPATH, USERPROFILE and others are missing.
186 186 """
187 187 os.name = 'nt'
188 188 # Remove from stub environment all keys that may be set
189 189 for key in ['HOME', 'HOMESHARE', 'HOMEDRIVE', 'HOMEPATH', 'USERPROFILE']:
190 190 env.pop(key, None)
191 191
192 192 #Stub windows registry functions
193 193 def OpenKey(x, y):
194 194 class key:
195 195 def Close(self):
196 196 pass
197 197 return key()
198 198 def QueryValueEx(x, y):
199 199 return [abspath(HOME_TEST_DIR)]
200 200
201 201 wreg.OpenKey = OpenKey
202 202 wreg.QueryValueEx = QueryValueEx
203 203
204 204 home_dir = path.get_home_dir()
205 205 nt.assert_equal(home_dir, abspath(HOME_TEST_DIR))
206 206
207 207
208 208 @with_environment
209 209 def test_get_ipython_dir_1():
210 210 """test_get_ipython_dir_1, Testcase to see if we can call get_ipython_dir without Exceptions."""
211 211 env_ipdir = os.path.join("someplace", ".ipython")
212 212 path._writable_dir = lambda path: True
213 213 env['IPYTHONDIR'] = env_ipdir
214 214 ipdir = path.get_ipython_dir()
215 215 nt.assert_equal(ipdir, env_ipdir)
216 216
217 217
218 218 @with_environment
219 219 def test_get_ipython_dir_2():
220 220 """test_get_ipython_dir_2, Testcase to see if we can call get_ipython_dir without Exceptions."""
221 221 path.get_home_dir = lambda : "someplace"
222 222 path.get_xdg_dir = lambda : None
223 223 path._writable_dir = lambda path: True
224 224 os.name = "posix"
225 225 env.pop('IPYTHON_DIR', None)
226 226 env.pop('IPYTHONDIR', None)
227 227 env.pop('XDG_CONFIG_HOME', None)
228 228 ipdir = path.get_ipython_dir()
229 229 nt.assert_equal(ipdir, os.path.join("someplace", ".ipython"))
230 230
231 231 @with_environment
232 232 def test_get_ipython_dir_3():
233 233 """test_get_ipython_dir_3, use XDG if defined, and .ipython doesn't exist."""
234 234 path.get_home_dir = lambda : "someplace"
235 235 path._writable_dir = lambda path: True
236 236 os.name = "posix"
237 237 env.pop('IPYTHON_DIR', None)
238 238 env.pop('IPYTHONDIR', None)
239 239 env['XDG_CONFIG_HOME'] = XDG_TEST_DIR
240 240 ipdir = path.get_ipython_dir()
241 241 if sys.platform == "darwin":
242 242 expected = os.path.join("someplace", ".ipython")
243 243 else:
244 244 expected = os.path.join(XDG_TEST_DIR, "ipython")
245 245 nt.assert_equal(ipdir, expected)
246 246
247 247 @with_environment
248 248 def test_get_ipython_dir_4():
249 249 """test_get_ipython_dir_4, use XDG if both exist."""
250 250 path.get_home_dir = lambda : HOME_TEST_DIR
251 251 os.name = "posix"
252 252 env.pop('IPYTHON_DIR', None)
253 253 env.pop('IPYTHONDIR', None)
254 254 env['XDG_CONFIG_HOME'] = XDG_TEST_DIR
255 255 ipdir = path.get_ipython_dir()
256 256 if sys.platform == "darwin":
257 257 expected = os.path.join(HOME_TEST_DIR, ".ipython")
258 258 else:
259 259 expected = os.path.join(XDG_TEST_DIR, "ipython")
260 260 nt.assert_equal(ipdir, expected)
261 261
262 262 @with_environment
263 263 def test_get_ipython_dir_5():
264 264 """test_get_ipython_dir_5, use .ipython if exists and XDG defined, but doesn't exist."""
265 265 path.get_home_dir = lambda : HOME_TEST_DIR
266 266 os.name = "posix"
267 267 env.pop('IPYTHON_DIR', None)
268 268 env.pop('IPYTHONDIR', None)
269 269 env['XDG_CONFIG_HOME'] = XDG_TEST_DIR
270 270 os.rmdir(os.path.join(XDG_TEST_DIR, 'ipython'))
271 271 ipdir = path.get_ipython_dir()
272 272 nt.assert_equal(ipdir, IP_TEST_DIR)
273 273
274 274 @with_environment
275 275 def test_get_ipython_dir_6():
276 276 """test_get_ipython_dir_6, use XDG if defined and neither exist."""
277 277 xdg = os.path.join(HOME_TEST_DIR, 'somexdg')
278 278 os.mkdir(xdg)
279 279 shutil.rmtree(os.path.join(HOME_TEST_DIR, '.ipython'))
280 280 path.get_home_dir = lambda : HOME_TEST_DIR
281 281 path.get_xdg_dir = lambda : xdg
282 282 os.name = "posix"
283 283 env.pop('IPYTHON_DIR', None)
284 284 env.pop('IPYTHONDIR', None)
285 285 env.pop('XDG_CONFIG_HOME', None)
286 286 xdg_ipdir = os.path.join(xdg, "ipython")
287 287 ipdir = path.get_ipython_dir()
288 288 nt.assert_equal(ipdir, xdg_ipdir)
289 289
290 290 @with_environment
291 291 def test_get_ipython_dir_7():
292 292 """test_get_ipython_dir_7, test home directory expansion on IPYTHONDIR"""
293 293 path._writable_dir = lambda path: True
294 294 home_dir = os.path.normpath(os.path.expanduser('~'))
295 295 env['IPYTHONDIR'] = os.path.join('~', 'somewhere')
296 296 ipdir = path.get_ipython_dir()
297 297 nt.assert_equal(ipdir, os.path.join(home_dir, 'somewhere'))
298 298
299 299 @skip_win32
300 300 @with_environment
301 301 def test_get_ipython_dir_8():
302 302 """test_get_ipython_dir_8, test / home directory"""
303 303 old = path._writable_dir, path.get_xdg_dir
304 304 try:
305 305 path._writable_dir = lambda path: bool(path)
306 306 path.get_xdg_dir = lambda: None
307 307 env.pop('IPYTHON_DIR', None)
308 308 env.pop('IPYTHONDIR', None)
309 309 env['HOME'] = '/'
310 310 nt.assert_equal(path.get_ipython_dir(), '/.ipython')
311 311 finally:
312 312 path._writable_dir, path.get_xdg_dir = old
313 313
314 314 @with_environment
315 315 def test_get_xdg_dir_0():
316 316 """test_get_xdg_dir_0, check xdg_dir"""
317 317 reload(path)
318 318 path._writable_dir = lambda path: True
319 319 path.get_home_dir = lambda : 'somewhere'
320 320 os.name = "posix"
321 321 sys.platform = "linux2"
322 322 env.pop('IPYTHON_DIR', None)
323 323 env.pop('IPYTHONDIR', None)
324 324 env.pop('XDG_CONFIG_HOME', None)
325 325
326 326 nt.assert_equal(path.get_xdg_dir(), os.path.join('somewhere', '.config'))
327 327
328 328
329 329 @with_environment
330 330 def test_get_xdg_dir_1():
331 331 """test_get_xdg_dir_1, check nonexistant xdg_dir"""
332 332 reload(path)
333 333 path.get_home_dir = lambda : HOME_TEST_DIR
334 334 os.name = "posix"
335 335 sys.platform = "linux2"
336 336 env.pop('IPYTHON_DIR', None)
337 337 env.pop('IPYTHONDIR', None)
338 338 env.pop('XDG_CONFIG_HOME', None)
339 339 nt.assert_equal(path.get_xdg_dir(), None)
340 340
341 341 @with_environment
342 342 def test_get_xdg_dir_2():
343 343 """test_get_xdg_dir_2, check xdg_dir default to ~/.config"""
344 344 reload(path)
345 345 path.get_home_dir = lambda : HOME_TEST_DIR
346 346 os.name = "posix"
347 347 sys.platform = "linux2"
348 348 env.pop('IPYTHON_DIR', None)
349 349 env.pop('IPYTHONDIR', None)
350 350 env.pop('XDG_CONFIG_HOME', None)
351 351 cfgdir=os.path.join(path.get_home_dir(), '.config')
352 352 if not os.path.exists(cfgdir):
353 353 os.makedirs(cfgdir)
354 354
355 355 nt.assert_equal(path.get_xdg_dir(), cfgdir)
356 356
357 357 @with_environment
358 358 def test_get_xdg_dir_3():
359 359 """test_get_xdg_dir_3, check xdg_dir not used on OS X"""
360 360 reload(path)
361 361 path.get_home_dir = lambda : HOME_TEST_DIR
362 362 os.name = "posix"
363 363 sys.platform = "darwin"
364 364 env.pop('IPYTHON_DIR', None)
365 365 env.pop('IPYTHONDIR', None)
366 366 env.pop('XDG_CONFIG_HOME', None)
367 367 cfgdir=os.path.join(path.get_home_dir(), '.config')
368 368 if not os.path.exists(cfgdir):
369 369 os.makedirs(cfgdir)
370 370
371 371 nt.assert_equal(path.get_xdg_dir(), None)
372 372
373 373 def test_filefind():
374 374 """Various tests for filefind"""
375 375 f = tempfile.NamedTemporaryFile()
376 376 # print 'fname:',f.name
377 377 alt_dirs = path.get_ipython_dir()
378 378 t = path.filefind(f.name, alt_dirs)
379 379 # print 'found:',t
380 380
381 381 @with_environment
382 382 def test_get_ipython_cache_dir():
383 383 os.environ["HOME"] = HOME_TEST_DIR
384 384 if os.name == 'posix' and sys.platform != 'darwin':
385 385 # test default
386 386 os.makedirs(os.path.join(HOME_TEST_DIR, ".cache"))
387 387 os.environ.pop("XDG_CACHE_HOME", None)
388 388 ipdir = path.get_ipython_cache_dir()
389 389 nt.assert_equal(os.path.join(HOME_TEST_DIR, ".cache", "ipython"),
390 390 ipdir)
391 391 nt.assert_true(os.path.isdir(ipdir))
392 392
393 393 # test env override
394 394 os.environ["XDG_CACHE_HOME"] = XDG_CACHE_DIR
395 395 ipdir = path.get_ipython_cache_dir()
396 396 nt.assert_true(os.path.isdir(ipdir))
397 397 nt.assert_equal(ipdir, os.path.join(XDG_CACHE_DIR, "ipython"))
398 398 else:
399 399 nt.assert_equal(path.get_ipython_cache_dir(),
400 400 path.get_ipython_dir())
401 401
402 402 def test_get_ipython_package_dir():
403 403 ipdir = path.get_ipython_package_dir()
404 404 nt.assert_true(os.path.isdir(ipdir))
405 405
406 406
407 407 def test_get_ipython_module_path():
408 408 ipapp_path = path.get_ipython_module_path('IPython.terminal.ipapp')
409 409 nt.assert_true(os.path.isfile(ipapp_path))
410 410
411 411
412 412 @dec.skip_if_not_win32
413 413 def test_get_long_path_name_win32():
414 414 p = path.get_long_path_name('c:\\docume~1')
415 415 nt.assert_equal(p,u'c:\\Documents and Settings')
416 416
417 417
418 418 @dec.skip_win32
419 419 def test_get_long_path_name():
420 420 p = path.get_long_path_name('/usr/local')
421 421 nt.assert_equal(p,'/usr/local')
422 422
423 423 @dec.skip_win32 # can't create not-user-writable dir on win
424 424 @with_environment
425 425 def test_not_writable_ipdir():
426 426 tmpdir = tempfile.mkdtemp()
427 427 os.name = "posix"
428 428 env.pop('IPYTHON_DIR', None)
429 429 env.pop('IPYTHONDIR', None)
430 430 env.pop('XDG_CONFIG_HOME', None)
431 431 env['HOME'] = tmpdir
432 432 ipdir = os.path.join(tmpdir, '.ipython')
433 433 os.mkdir(ipdir)
434 434 os.chmod(ipdir, 600)
435 435 with AssertPrints('is not a writable location', channel='stderr'):
436 436 ipdir = path.get_ipython_dir()
437 437 env.pop('IPYTHON_DIR', None)
438 438
439 439 def test_unquote_filename():
440 440 for win32 in (True, False):
441 441 nt.assert_equal(path.unquote_filename('foo.py', win32=win32), 'foo.py')
442 442 nt.assert_equal(path.unquote_filename('foo bar.py', win32=win32), 'foo bar.py')
443 443 nt.assert_equal(path.unquote_filename('"foo.py"', win32=True), 'foo.py')
444 444 nt.assert_equal(path.unquote_filename('"foo bar.py"', win32=True), 'foo bar.py')
445 445 nt.assert_equal(path.unquote_filename("'foo.py'", win32=True), 'foo.py')
446 446 nt.assert_equal(path.unquote_filename("'foo bar.py'", win32=True), 'foo bar.py')
447 447 nt.assert_equal(path.unquote_filename('"foo.py"', win32=False), '"foo.py"')
448 448 nt.assert_equal(path.unquote_filename('"foo bar.py"', win32=False), '"foo bar.py"')
449 449 nt.assert_equal(path.unquote_filename("'foo.py'", win32=False), "'foo.py'")
450 450 nt.assert_equal(path.unquote_filename("'foo bar.py'", win32=False), "'foo bar.py'")
451 451
452 452 @with_environment
453 453 def test_get_py_filename():
454 454 os.chdir(TMP_TEST_DIR)
455 455 for win32 in (True, False):
456 456 with make_tempfile('foo.py'):
457 457 nt.assert_equal(path.get_py_filename('foo.py', force_win32=win32), 'foo.py')
458 458 nt.assert_equal(path.get_py_filename('foo', force_win32=win32), 'foo.py')
459 459 with make_tempfile('foo'):
460 460 nt.assert_equal(path.get_py_filename('foo', force_win32=win32), 'foo')
461 461 nt.assert_raises(IOError, path.get_py_filename, 'foo.py', force_win32=win32)
462 462 nt.assert_raises(IOError, path.get_py_filename, 'foo', force_win32=win32)
463 463 nt.assert_raises(IOError, path.get_py_filename, 'foo.py', force_win32=win32)
464 464 true_fn = 'foo with spaces.py'
465 465 with make_tempfile(true_fn):
466 466 nt.assert_equal(path.get_py_filename('foo with spaces', force_win32=win32), true_fn)
467 467 nt.assert_equal(path.get_py_filename('foo with spaces.py', force_win32=win32), true_fn)
468 468 if win32:
469 469 nt.assert_equal(path.get_py_filename('"foo with spaces.py"', force_win32=True), true_fn)
470 470 nt.assert_equal(path.get_py_filename("'foo with spaces.py'", force_win32=True), true_fn)
471 471 else:
472 472 nt.assert_raises(IOError, path.get_py_filename, '"foo with spaces.py"', force_win32=False)
473 473 nt.assert_raises(IOError, path.get_py_filename, "'foo with spaces.py'", force_win32=False)
474 474
475 475 def test_unicode_in_filename():
476 476 """When a file doesn't exist, the exception raised should be safe to call
477 477 str() on - i.e. in Python 2 it must only have ASCII characters.
478 478
479 479 https://github.com/ipython/ipython/issues/875
480 480 """
481 481 try:
482 482 # these calls should not throw unicode encode exceptions
483 483 path.get_py_filename(u'fooéè.py', force_win32=False)
484 484 except IOError as ex:
485 485 str(ex)
486 486
487 487
488 488 class TestShellGlob(object):
489 489
490 490 @classmethod
491 491 def setUpClass(cls):
492 492 cls.filenames_start_with_a = map('a{0}'.format, range(3))
493 493 cls.filenames_end_with_b = map('{0}b'.format, range(3))
494 494 cls.filenames = cls.filenames_start_with_a + cls.filenames_end_with_b
495 495 cls.tempdir = TemporaryDirectory()
496 496 td = cls.tempdir.name
497 497
498 498 with cls.in_tempdir():
499 499 # Create empty files
500 500 for fname in cls.filenames:
501 501 open(os.path.join(td, fname), 'w').close()
502 502
503 503 @classmethod
504 504 def tearDownClass(cls):
505 505 cls.tempdir.cleanup()
506 506
507 507 @classmethod
508 508 @contextmanager
509 509 def in_tempdir(cls):
510 510 save = os.getcwdu()
511 511 try:
512 512 os.chdir(cls.tempdir.name)
513 513 yield
514 514 finally:
515 515 os.chdir(save)
516 516
517 517 def check_match(self, patterns, matches):
518 518 with self.in_tempdir():
519 519 # glob returns unordered list. that's why sorted is required.
520 520 nt.assert_equals(sorted(path.shellglob(patterns)),
521 521 sorted(matches))
522 522
523 523 def common_cases(self):
524 524 return [
525 525 (['*'], self.filenames),
526 526 (['a*'], self.filenames_start_with_a),
527 527 (['*c'], ['*c']),
528 528 (['*', 'a*', '*b', '*c'], self.filenames
529 529 + self.filenames_start_with_a
530 530 + self.filenames_end_with_b
531 531 + ['*c']),
532 532 (['a[012]'], self.filenames_start_with_a),
533 533 ]
534 534
535 535 @skip_win32
536 536 def test_match_posix(self):
537 537 for (patterns, matches) in self.common_cases() + [
538 538 ([r'\*'], ['*']),
539 539 ([r'a\*', 'a*'], ['a*'] + self.filenames_start_with_a),
540 540 ([r'a\[012]'], ['a[012]']),
541 541 ]:
542 542 yield (self.check_match, patterns, matches)
543 543
544 544 @skip_if_not_win32
545 545 def test_match_windows(self):
546 546 for (patterns, matches) in self.common_cases() + [
547 547 # In windows, backslash is interpreted as path
548 548 # separator. Therefore, you can't escape glob
549 549 # using it.
550 550 ([r'a\*', 'a*'], [r'a\*'] + self.filenames_start_with_a),
551 551 ([r'a\[012]'], [r'a\[012]']),
552 552 ]:
553 553 yield (self.check_match, patterns, matches)
554 554
555 555
556 556 def test_unescape_glob():
557 557 nt.assert_equals(path.unescape_glob(r'\*\[\!\]\?'), '*[!]?')
558 558 nt.assert_equals(path.unescape_glob(r'\\*'), r'\*')
559 559 nt.assert_equals(path.unescape_glob(r'\\\*'), r'\*')
560 560 nt.assert_equals(path.unescape_glob(r'\\a'), r'\a')
561 561 nt.assert_equals(path.unescape_glob(r'\a'), r'\a')
562
563
564 class TestLinkOrCopy(object):
565 def setUp(self):
566 self.tempdir = TemporaryDirectory()
567 self.src = self.dst("src")
568 with open(self.src, "w") as f:
569 f.write("Hello, world!")
570
571 def tearDown(self):
572 self.tempdir.cleanup()
573
574 def dst(self, *args):
575 return os.path.join(self.tempdir.name, *args)
576
577 def assert_inode_not_equal(self, a, b):
578 nt.assert_not_equals(os.stat(a).st_ino, os.stat(b).st_ino,
579 "%r and %r do reference the same indoes" %(a, b))
580
581 def assert_inode_equal(self, a, b):
582 nt.assert_equals(os.stat(a).st_ino, os.stat(b).st_ino,
583 "%r and %r do not reference the same indoes" %(a, b))
584
585 def assert_content_eqal(self, a, b):
586 with nested(open(a), open(b)) as (a_f, b_f):
587 nt.assert_equals(a_f.read(), b_f.read())
588
589 @skip_win32
590 def test_link_successful(self):
591 dst = self.dst("target")
592 path.link_or_copy(self.src, dst)
593 self.assert_inode_equal(self.src, dst)
594
595 @skip_win32
596 def test_link_into_dir(self):
597 dst = self.dst("some_dir")
598 os.mkdir(dst)
599 path.link_or_copy(self.src, dst)
600 expected_dst = self.dst("some_dir", os.path.basename(self.src))
601 self.assert_inode_equal(self.src, expected_dst)
602
603 @skip_win32
604 def test_target_exists(self):
605 dst = self.dst("target")
606 open(dst, "w").close()
607 path.link_or_copy(self.src, dst)
608 self.assert_inode_equal(self.src, dst)
609
610 @skip_win32
611 def test_no_link(self):
612 real_link = os.link
613 try:
614 del os.link
615 dst = self.dst("target")
616 path.link_or_copy(self.src, dst)
617 self.assert_content_eqal(self.src, dst)
618 self.assert_inode_not_equal(self.src, dst)
619 finally:
620 os.link = real_link
621
622 @skip_if_not_win32
623 def test_windows(self):
624 dst = self.dst("target")
625 path.link_or_copy(self.src, dst)
626 self.assert_content_eqal(self.src, dst)
General Comments 0
You need to be logged in to leave comments. Login now