##// END OF EJS Templates
Fix a couple typos
David Wolever -
Show More
@@ -1,562 +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 19 import errno
20 20 import shutil
21 21 import random
22 22 import tempfile
23 23 import warnings
24 24 from hashlib import md5
25 25 import glob
26 26
27 27 import IPython
28 28 from IPython.testing.skipdoctest import skip_doctest
29 29 from IPython.utils.process import system
30 30 from IPython.utils.importstring import import_item
31 31 from IPython.utils import py3compat
32 32 #-----------------------------------------------------------------------------
33 33 # Code
34 34 #-----------------------------------------------------------------------------
35 35
36 36 fs_encoding = sys.getfilesystemencoding()
37 37
38 38 def _get_long_path_name(path):
39 39 """Dummy no-op."""
40 40 return path
41 41
42 42 def _writable_dir(path):
43 43 """Whether `path` is a directory, to which the user has write access."""
44 44 return os.path.isdir(path) and os.access(path, os.W_OK)
45 45
46 46 if sys.platform == 'win32':
47 47 @skip_doctest
48 48 def _get_long_path_name(path):
49 49 """Get a long path name (expand ~) on Windows using ctypes.
50 50
51 51 Examples
52 52 --------
53 53
54 54 >>> get_long_path_name('c:\\docume~1')
55 55 u'c:\\\\Documents and Settings'
56 56
57 57 """
58 58 try:
59 59 import ctypes
60 60 except ImportError:
61 61 raise ImportError('you need to have ctypes installed for this to work')
62 62 _GetLongPathName = ctypes.windll.kernel32.GetLongPathNameW
63 63 _GetLongPathName.argtypes = [ctypes.c_wchar_p, ctypes.c_wchar_p,
64 64 ctypes.c_uint ]
65 65
66 66 buf = ctypes.create_unicode_buffer(260)
67 67 rv = _GetLongPathName(path, buf, 260)
68 68 if rv == 0 or rv > 260:
69 69 return path
70 70 else:
71 71 return buf.value
72 72
73 73
74 74 def get_long_path_name(path):
75 75 """Expand a path into its long form.
76 76
77 77 On Windows this expands any ~ in the paths. On other platforms, it is
78 78 a null operation.
79 79 """
80 80 return _get_long_path_name(path)
81 81
82 82
83 83 def unquote_filename(name, win32=(sys.platform=='win32')):
84 84 """ On Windows, remove leading and trailing quotes from filenames.
85 85 """
86 86 if win32:
87 87 if name.startswith(("'", '"')) and name.endswith(("'", '"')):
88 88 name = name[1:-1]
89 89 return name
90 90
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, basestring):
159 159 path_dirs = (path_dirs,)
160 160
161 161 for path in path_dirs:
162 162 if path == '.': path = os.getcwdu()
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 import _winreg as wreg
203 203 key = wreg.OpenKey(
204 204 wreg.HKEY_CURRENT_USER,
205 205 "Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders"
206 206 )
207 207 homedir = wreg.QueryValueEx(key,'Personal')[0]
208 208 key.Close()
209 209 except:
210 210 pass
211 211
212 212 if (not require_writable) or _writable_dir(homedir):
213 213 return py3compat.cast_unicode(homedir, fs_encoding)
214 214 else:
215 215 raise HomeDirError('%s is not a writable dir, '
216 216 'set $HOME environment variable to override' % homedir)
217 217
218 218 def get_xdg_dir():
219 219 """Return the XDG_CONFIG_HOME, if it is defined and exists, else None.
220 220
221 221 This is only for non-OS X posix (Linux,Unix,etc.) systems.
222 222 """
223 223
224 224 env = os.environ
225 225
226 226 if os.name == 'posix' and sys.platform != 'darwin':
227 227 # Linux, Unix, AIX, etc.
228 228 # use ~/.config if empty OR not set
229 229 xdg = env.get("XDG_CONFIG_HOME", None) or os.path.join(get_home_dir(), '.config')
230 230 if xdg and _writable_dir(xdg):
231 231 return py3compat.cast_unicode(xdg, fs_encoding)
232 232
233 233 return None
234 234
235 235
236 236 def get_xdg_cache_dir():
237 237 """Return the XDG_CACHE_HOME, if it is defined and exists, else None.
238 238
239 239 This is only for non-OS X posix (Linux,Unix,etc.) systems.
240 240 """
241 241
242 242 env = os.environ
243 243
244 244 if os.name == 'posix' and sys.platform != 'darwin':
245 245 # Linux, Unix, AIX, etc.
246 246 # use ~/.cache if empty OR not set
247 247 xdg = env.get("XDG_CACHE_HOME", None) or os.path.join(get_home_dir(), '.cache')
248 248 if xdg and _writable_dir(xdg):
249 249 return py3compat.cast_unicode(xdg, fs_encoding)
250 250
251 251 return None
252 252
253 253
254 254 def get_ipython_dir():
255 255 """Get the IPython directory for this platform and user.
256 256
257 257 This uses the logic in `get_home_dir` to find the home directory
258 258 and then adds .ipython to the end of the path.
259 259 """
260 260
261 261 env = os.environ
262 262 pjoin = os.path.join
263 263
264 264
265 265 ipdir_def = '.ipython'
266 266 xdg_def = 'ipython'
267 267
268 268 home_dir = get_home_dir()
269 269 xdg_dir = get_xdg_dir()
270 270
271 271 # import pdb; pdb.set_trace() # dbg
272 272 if 'IPYTHON_DIR' in env:
273 273 warnings.warn('The environment variable IPYTHON_DIR is deprecated. '
274 274 'Please use IPYTHONDIR instead.')
275 275 ipdir = env.get('IPYTHONDIR', env.get('IPYTHON_DIR', None))
276 276 if ipdir is None:
277 277 # not set explicitly, use XDG_CONFIG_HOME or HOME
278 278 home_ipdir = pjoin(home_dir, ipdir_def)
279 279 if xdg_dir:
280 280 # use XDG, as long as the user isn't already
281 281 # using $HOME/.ipython and *not* XDG/ipython
282 282
283 283 xdg_ipdir = pjoin(xdg_dir, xdg_def)
284 284
285 285 if _writable_dir(xdg_ipdir) or not _writable_dir(home_ipdir):
286 286 ipdir = xdg_ipdir
287 287
288 288 if ipdir is None:
289 289 # not using XDG
290 290 ipdir = home_ipdir
291 291
292 292 ipdir = os.path.normpath(os.path.expanduser(ipdir))
293 293
294 294 if os.path.exists(ipdir) and not _writable_dir(ipdir):
295 295 # ipdir exists, but is not writable
296 296 warnings.warn("IPython dir '%s' is not a writable location,"
297 297 " using a temp directory."%ipdir)
298 298 ipdir = tempfile.mkdtemp()
299 299 elif not os.path.exists(ipdir):
300 300 parent = os.path.dirname(ipdir)
301 301 if not _writable_dir(parent):
302 302 # ipdir does not exist and parent isn't writable
303 303 warnings.warn("IPython parent '%s' is not a writable location,"
304 304 " using a temp directory."%parent)
305 305 ipdir = tempfile.mkdtemp()
306 306
307 307 return py3compat.cast_unicode(ipdir, fs_encoding)
308 308
309 309
310 310 def get_ipython_cache_dir():
311 311 """Get the cache directory it is created if it does not exist."""
312 312 xdgdir = get_xdg_cache_dir()
313 313 if xdgdir is None:
314 314 return get_ipython_dir()
315 315 ipdir = os.path.join(xdgdir, "ipython")
316 316 if not os.path.exists(ipdir) and _writable_dir(xdgdir):
317 317 os.makedirs(ipdir)
318 318 elif not _writable_dir(xdgdir):
319 319 return get_ipython_dir()
320 320
321 321 return py3compat.cast_unicode(ipdir, fs_encoding)
322 322
323 323
324 324 def get_ipython_package_dir():
325 325 """Get the base directory where IPython itself is installed."""
326 326 ipdir = os.path.dirname(IPython.__file__)
327 327 return py3compat.cast_unicode(ipdir, fs_encoding)
328 328
329 329
330 330 def get_ipython_module_path(module_str):
331 331 """Find the path to an IPython module in this version of IPython.
332 332
333 333 This will always find the version of the module that is in this importable
334 334 IPython package. This will always return the path to the ``.py``
335 335 version of the module.
336 336 """
337 337 if module_str == 'IPython':
338 338 return os.path.join(get_ipython_package_dir(), '__init__.py')
339 339 mod = import_item(module_str)
340 340 the_path = mod.__file__.replace('.pyc', '.py')
341 341 the_path = the_path.replace('.pyo', '.py')
342 342 return py3compat.cast_unicode(the_path, fs_encoding)
343 343
344 344 def locate_profile(profile='default'):
345 345 """Find the path to the folder associated with a given profile.
346 346
347 347 I.e. find $IPYTHONDIR/profile_whatever.
348 348 """
349 349 from IPython.core.profiledir import ProfileDir, ProfileDirError
350 350 try:
351 351 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile)
352 352 except ProfileDirError:
353 353 # IOError makes more sense when people are expecting a path
354 354 raise IOError("Couldn't find profile %r" % profile)
355 355 return pd.location
356 356
357 357 def expand_path(s):
358 358 """Expand $VARS and ~names in a string, like a shell
359 359
360 360 :Examples:
361 361
362 362 In [2]: os.environ['FOO']='test'
363 363
364 364 In [3]: expand_path('variable FOO is $FOO')
365 365 Out[3]: 'variable FOO is test'
366 366 """
367 367 # This is a pretty subtle hack. When expand user is given a UNC path
368 368 # on Windows (\\server\share$\%username%), os.path.expandvars, removes
369 369 # the $ to get (\\server\share\%username%). I think it considered $
370 370 # alone an empty var. But, we need the $ to remains there (it indicates
371 371 # a hidden share).
372 372 if os.name=='nt':
373 373 s = s.replace('$\\', 'IPYTHON_TEMP')
374 374 s = os.path.expandvars(os.path.expanduser(s))
375 375 if os.name=='nt':
376 376 s = s.replace('IPYTHON_TEMP', '$\\')
377 377 return s
378 378
379 379
380 380 def unescape_glob(string):
381 381 """Unescape glob pattern in `string`."""
382 382 def unescape(s):
383 383 for pattern in '*[]!?':
384 384 s = s.replace(r'\{0}'.format(pattern), pattern)
385 385 return s
386 386 return '\\'.join(map(unescape, string.split('\\\\')))
387 387
388 388
389 389 def shellglob(args):
390 390 """
391 391 Do glob expansion for each element in `args` and return a flattened list.
392 392
393 393 Unmatched glob pattern will remain as-is in the returned list.
394 394
395 395 """
396 396 expanded = []
397 397 # Do not unescape backslash in Windows as it is interpreted as
398 398 # path separator:
399 399 unescape = unescape_glob if sys.platform != 'win32' else lambda x: x
400 400 for a in args:
401 401 expanded.extend(glob.glob(a) or [unescape(a)])
402 402 return expanded
403 403
404 404
405 405 def target_outdated(target,deps):
406 406 """Determine whether a target is out of date.
407 407
408 408 target_outdated(target,deps) -> 1/0
409 409
410 410 deps: list of filenames which MUST exist.
411 411 target: single filename which may or may not exist.
412 412
413 413 If target doesn't exist or is older than any file listed in deps, return
414 414 true, otherwise return false.
415 415 """
416 416 try:
417 417 target_time = os.path.getmtime(target)
418 418 except os.error:
419 419 return 1
420 420 for dep in deps:
421 421 dep_time = os.path.getmtime(dep)
422 422 if dep_time > target_time:
423 423 #print "For target",target,"Dep failed:",dep # dbg
424 424 #print "times (dep,tar):",dep_time,target_time # dbg
425 425 return 1
426 426 return 0
427 427
428 428
429 429 def target_update(target,deps,cmd):
430 430 """Update a target with a given command given a list of dependencies.
431 431
432 432 target_update(target,deps,cmd) -> runs cmd if target is outdated.
433 433
434 434 This is just a wrapper around target_outdated() which calls the given
435 435 command if target is outdated."""
436 436
437 437 if target_outdated(target,deps):
438 438 system(cmd)
439 439
440 440 def filehash(path):
441 441 """Make an MD5 hash of a file, ignoring any differences in line
442 442 ending characters."""
443 443 with open(path, "rU") as f:
444 444 return md5(py3compat.str_to_bytes(f.read())).hexdigest()
445 445
446 446 # If the config is unmodified from the default, we'll just delete it.
447 447 # These are consistent for 0.10.x, thankfully. We're not going to worry about
448 448 # older versions.
449 449 old_config_md5 = {'ipy_user_conf.py': 'fc108bedff4b9a00f91fa0a5999140d3',
450 450 'ipythonrc': '12a68954f3403eea2eec09dc8fe5a9b5'}
451 451
452 452 def check_for_old_config(ipython_dir=None):
453 453 """Check for old config files, and present a warning if they exist.
454 454
455 455 A link to the docs of the new config is included in the message.
456 456
457 457 This should mitigate confusion with the transition to the new
458 458 config system in 0.11.
459 459 """
460 460 if ipython_dir is None:
461 461 ipython_dir = get_ipython_dir()
462 462
463 463 old_configs = ['ipy_user_conf.py', 'ipythonrc', 'ipython_config.py']
464 464 warned = False
465 465 for cfg in old_configs:
466 466 f = os.path.join(ipython_dir, cfg)
467 467 if os.path.exists(f):
468 468 if filehash(f) == old_config_md5.get(cfg, ''):
469 469 os.unlink(f)
470 470 else:
471 471 warnings.warn("Found old IPython config file %r (modified by user)"%f)
472 472 warned = True
473 473
474 474 if warned:
475 475 warnings.warn("""
476 476 The IPython configuration system has changed as of 0.11, and these files will
477 477 be ignored. See http://ipython.github.com/ipython-doc/dev/config for details
478 478 of the new config system.
479 479 To start configuring IPython, do `ipython profile create`, and edit
480 480 `ipython_config.py` in <ipython_dir>/profile_default.
481 481 If you need to leave the old config files in place for an older version of
482 482 IPython and want to suppress this warning message, set
483 483 `c.InteractiveShellApp.ignore_old_config=True` in the new config.""")
484 484
485 485 def get_security_file(filename, profile='default'):
486 486 """Return the absolute path of a security file given by filename and profile
487 487
488 488 This allows users and developers to find security files without
489 489 knowledge of the IPython directory structure. The search path
490 490 will be ['.', profile.security_dir]
491 491
492 492 Parameters
493 493 ----------
494 494
495 495 filename : str
496 496 The file to be found. If it is passed as an absolute path, it will
497 497 simply be returned.
498 498 profile : str [default: 'default']
499 499 The name of the profile to search. Leaving this unspecified
500 500 The file to be found. If it is passed as an absolute path, fname will
501 501 simply be returned.
502 502
503 503 Returns
504 504 -------
505 505 Raises :exc:`IOError` if file not found or returns absolute path to file.
506 506 """
507 507 # import here, because profiledir also imports from utils.path
508 508 from IPython.core.profiledir import ProfileDir
509 509 try:
510 510 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile)
511 511 except Exception:
512 512 # will raise ProfileDirError if no such profile
513 513 raise IOError("Profile %r not found")
514 514 return filefind(filename, ['.', pd.security_dir])
515 515
516 516
517 517 def link(src, dst):
518 """Attempts to hardlink 'src' to 'dst', errno on failure (or 0 on success).
518 """Hard links ``src`` to ``dst``, returning 0 or errno.
519 519
520 520 Note that the special errno ``1998`` will be returned if ``os.link`` isn't
521 521 supported by the operating system.
522 522 """
523 523
524 524 if not hasattr(os, "link"):
525 525 return 1998
526 526 link_errno = 0
527 527 try:
528 528 os.link(src, dst)
529 529 except OSError as e:
530 530 link_errno = e.errno
531 531 return link_errno
532 532
533 533
534 534 def link_or_copy(src, dst):
535 535 """Attempts to hardlink ``src`` to ``dst``, copying if the link fails.
536 536
537 537 Attempts to maintain the semantics of ``shutil.copy``.
538 538
539 539 Because ``os.link`` does not overwrite files, a unique temporary file
540 540 will be used if the target already exists, then that file will be moved
541 541 into place.
542 542 """
543 543
544 544 if os.path.isdir(dst):
545 545 dst = os.path.join(dst, os.path.basename(src))
546 546
547 547 link_errno = link(src, dst)
548 548 if link_errno == errno.EEXIST:
549 549 new_dst = dst + "-temp-%04X" %(random.randint(1, 16**4), )
550 550 try:
551 551 link_or_copy(src, new_dst)
552 552 except:
553 553 try:
554 554 os.remove(new_dst)
555 555 except OSError:
556 556 pass
557 557 raise
558 558 os.rename(new_dst, dst)
559 559 elif link_errno != 0:
560 560 # Either link isn't supported, or the filesystem doesn't support
561 561 # linking, or 'src' and 'dst' are on different filesystems.
562 562 shutil.copy(src, dst)
@@ -1,626 +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 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 562
563 563
564 564 class TestLinkOrCopy(object):
565 565 def setUp(self):
566 566 self.tempdir = TemporaryDirectory()
567 567 self.src = self.dst("src")
568 568 with open(self.src, "w") as f:
569 569 f.write("Hello, world!")
570 570
571 571 def tearDown(self):
572 572 self.tempdir.cleanup()
573 573
574 574 def dst(self, *args):
575 575 return os.path.join(self.tempdir.name, *args)
576 576
577 577 def assert_inode_not_equal(self, a, b):
578 578 nt.assert_not_equals(os.stat(a).st_ino, os.stat(b).st_ino,
579 579 "%r and %r do reference the same indoes" %(a, b))
580 580
581 581 def assert_inode_equal(self, a, b):
582 582 nt.assert_equals(os.stat(a).st_ino, os.stat(b).st_ino,
583 583 "%r and %r do not reference the same indoes" %(a, b))
584 584
585 def assert_content_eqal(self, a, b):
585 def assert_content_equal(self, a, b):
586 586 with nested(open(a), open(b)) as (a_f, b_f):
587 587 nt.assert_equals(a_f.read(), b_f.read())
588 588
589 589 @skip_win32
590 590 def test_link_successful(self):
591 591 dst = self.dst("target")
592 592 path.link_or_copy(self.src, dst)
593 593 self.assert_inode_equal(self.src, dst)
594 594
595 595 @skip_win32
596 596 def test_link_into_dir(self):
597 597 dst = self.dst("some_dir")
598 598 os.mkdir(dst)
599 599 path.link_or_copy(self.src, dst)
600 600 expected_dst = self.dst("some_dir", os.path.basename(self.src))
601 601 self.assert_inode_equal(self.src, expected_dst)
602 602
603 603 @skip_win32
604 604 def test_target_exists(self):
605 605 dst = self.dst("target")
606 606 open(dst, "w").close()
607 607 path.link_or_copy(self.src, dst)
608 608 self.assert_inode_equal(self.src, dst)
609 609
610 610 @skip_win32
611 611 def test_no_link(self):
612 612 real_link = os.link
613 613 try:
614 614 del os.link
615 615 dst = self.dst("target")
616 616 path.link_or_copy(self.src, dst)
617 self.assert_content_eqal(self.src, dst)
617 self.assert_content_equal(self.src, dst)
618 618 self.assert_inode_not_equal(self.src, dst)
619 619 finally:
620 620 os.link = real_link
621 621
622 622 @skip_if_not_win32
623 623 def test_windows(self):
624 624 dst = self.dst("target")
625 625 path.link_or_copy(self.src, dst)
626 self.assert_content_eqal(self.src, dst)
626 self.assert_content_equal(self.src, dst)
General Comments 0
You need to be logged in to leave comments. Login now