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