test_path.py
514 lines
| 17.3 KiB
| text/x-python
|
PythonLexer
|
r1617 | # encoding: utf-8 | ||
|
r2498 | """Tests for IPython.utils.path.py""" | ||
|
r1617 | |||
|
r16489 | # Copyright (c) IPython Development Team. | ||
# Distributed under the terms of the Modified BSD License. | ||||
|
r4688 | |||
|
r13407 | import errno | ||
|
r1843 | import os | ||
import shutil | ||||
import sys | ||||
import tempfile | ||||
|
r13407 | import warnings | ||
|
r11653 | from contextlib import contextmanager | ||
|
r1751 | |||
|
r18050 | try: # Python 3.3+ | ||
from unittest.mock import patch | ||||
except ImportError: | ||||
from mock import patch | ||||
|
r1801 | from os.path import join, abspath, split | ||
|
r1843 | |||
|
r17996 | from nose import SkipTest | ||
|
r1802 | import nose.tools as nt | ||
|
r1617 | |||
|
r1843 | from nose import with_setup | ||
|
r1617 | |||
|
r1843 | import IPython | ||
|
r21253 | from IPython import paths | ||
|
r2436 | from IPython.testing import decorators as dec | ||
|
r12166 | from IPython.testing.decorators import (skip_if_not_win32, skip_win32, | ||
onlyif_unicode_paths,) | ||||
|
r4901 | from IPython.testing.tools import make_tempfile, AssertPrints | ||
|
r11127 | from IPython.utils import path | ||
|
r4764 | from IPython.utils import py3compat | ||
|
r8014 | from IPython.utils.tempdir import TemporaryDirectory | ||
|
r1843 | |||
# Platform-dependent imports | ||||
|
r1750 | try: | ||
|
r13354 | import winreg as wreg # Py 3 | ||
|
r1750 | except ImportError: | ||
|
r13354 | try: | ||
import _winreg as wreg # Py 2 | ||||
except ImportError: | ||||
#Fake _winreg module on none windows platforms | ||||
import types | ||||
wr_name = "winreg" if py3compat.PY3 else "_winreg" | ||||
sys.modules[wr_name] = types.ModuleType(wr_name) | ||||
try: | ||||
import winreg as wreg | ||||
except ImportError: | ||||
import _winreg as wreg | ||||
#Add entries that needs to be stubbed by the testing code | ||||
(wreg.OpenKey, wreg.QueryValueEx,) = (None, None) | ||||
|
r4872 | |||
|
r4764 | try: | ||
reload | ||||
except NameError: # Python 3 | ||||
from imp import reload | ||||
|
r1750 | |||
|
r1843 | #----------------------------------------------------------------------------- | ||
# Globals | ||||
#----------------------------------------------------------------------------- | ||||
env = os.environ | ||||
TMP_TEST_DIR = tempfile.mkdtemp() | ||||
HOME_TEST_DIR = join(TMP_TEST_DIR, "home_test_dir") | ||||
|
r1804 | # | ||
# Setup/teardown functions/decorators | ||||
|
r1803 | # | ||
def setup(): | ||||
|
r1805 | """Setup testenvironment for the module: | ||
|
r4872 | |||
|
r1805 | - Adds dummy home dir tree | ||
""" | ||||
|
r1843 | # Do not mask exceptions here. In particular, catching WindowsError is a | ||
# problem because that exception is only defined on Windows... | ||||
|
r21041 | os.makedirs(os.path.join(HOME_TEST_DIR, 'ipython')) | ||
|
r1803 | |||
|
r2505 | |||
|
r1803 | def teardown(): | ||
|
r1805 | """Teardown testenvironment for the module: | ||
|
r4872 | |||
|
r1805 | - Remove dummy home dir tree | ||
""" | ||||
|
r1843 | # Note: we remove the parent test dir, which is the root of all test | ||
# subdirs we may have created. Use shutil instead of os.removedirs, so | ||||
# that non-empty directories are all recursively removed. | ||||
shutil.rmtree(TMP_TEST_DIR) | ||||
|
r1803 | |||
|
r2505 | |||
|
r1750 | def setup_environment(): | ||
|
r4872 | """Setup testenvironment for some functions that are tested | ||
|
r1805 | in this module. In particular this functions stores attributes | ||
and other things that we need to stub in some test functions. | ||||
|
r4872 | This needs to be done on a function level and not module level because | ||
|
r1805 | each testfunction needs a pristine environment. | ||
""" | ||||
|
r1750 | global oldstuff, platformstuff | ||
|
r7086 | oldstuff = (env.copy(), os.name, sys.platform, path.get_home_dir, IPython.__file__, os.getcwd()) | ||
|
r1750 | |||
def teardown_environment(): | ||||
|
r11500 | """Restore things that were remembered by the setup_environment function | ||
|
r1805 | """ | ||
|
r7086 | (oldenv, os.name, sys.platform, path.get_home_dir, IPython.__file__, old_wd) = oldstuff | ||
|
r4688 | os.chdir(old_wd) | ||
|
r4475 | reload(path) | ||
|
r4872 | |||
|
r13373 | for key in list(env): | ||
|
r1750 | if key not in oldenv: | ||
del env[key] | ||||
env.update(oldenv) | ||||
if hasattr(sys, 'frozen'): | ||||
del sys.frozen | ||||
|
r1805 | # Build decorator that uses the setup_environment/setup_environment | ||
|
r2436 | with_environment = with_setup(setup_environment, teardown_environment) | ||
|
r1750 | |||
|
r1820 | @skip_if_not_win32 | ||
|
r2436 | @with_environment | ||
|
r1745 | def test_get_home_dir_1(): | ||
|
r1746 | """Testcase for py2exe logic, un-compressed lib | ||
""" | ||||
|
r11502 | unfrozen = path.get_home_dir() | ||
|
r1796 | sys.frozen = True | ||
|
r4872 | |||
|
r1746 | #fake filename for IPython.__init__ | ||
|
r1855 | IPython.__file__ = abspath(join(HOME_TEST_DIR, "Lib/IPython/__init__.py")) | ||
|
r4872 | |||
|
r2513 | home_dir = path.get_home_dir() | ||
|
r11502 | nt.assert_equal(home_dir, unfrozen) | ||
|
r2505 | |||
|
r1812 | @skip_if_not_win32 | ||
|
r2436 | @with_environment | ||
|
r1800 | def test_get_home_dir_2(): | ||
|
r1746 | """Testcase for py2exe logic, compressed lib | ||
""" | ||||
|
r11502 | unfrozen = path.get_home_dir() | ||
|
r1801 | sys.frozen = True | ||
|
r1746 | #fake filename for IPython.__init__ | ||
|
r1855 | IPython.__file__ = abspath(join(HOME_TEST_DIR, "Library.zip/IPython/__init__.py")).lower() | ||
|
r4872 | |||
|
r5384 | home_dir = path.get_home_dir(True) | ||
|
r11502 | nt.assert_equal(home_dir, unfrozen) | ||
|
r1746 | |||
|
r2505 | |||
|
r2436 | @with_environment | ||
|
r1800 | def test_get_home_dir_3(): | ||
|
r5383 | """get_home_dir() uses $HOME if set""" | ||
|
r1843 | env["HOME"] = HOME_TEST_DIR | ||
|
r5384 | home_dir = path.get_home_dir(True) | ||
|
r6123 | # get_home_dir expands symlinks | ||
nt.assert_equal(home_dir, os.path.realpath(env["HOME"])) | ||||
|
r1745 | |||
|
r2505 | |||
|
r2436 | @with_environment | ||
|
r1800 | def test_get_home_dir_4(): | ||
|
r5383 | """get_home_dir() still works if $HOME is not set""" | ||
|
r4872 | |||
|
r1812 | if 'HOME' in env: del env['HOME'] | ||
|
r7669 | # this should still succeed, but we don't care what the answer is | ||
home = path.get_home_dir(False) | ||||
|
r2505 | |||
|
r2436 | @with_environment | ||
|
r1800 | def test_get_home_dir_5(): | ||
|
r5383 | """raise HomeDirError if $HOME is specified, but not a writable dir""" | ||
env['HOME'] = abspath(HOME_TEST_DIR+'garbage') | ||||
|
r5385 | # set os.name = posix, to prevent My Documents fallback on Windows | ||
os.name = 'posix' | ||||
|
r5384 | nt.assert_raises(path.HomeDirError, path.get_home_dir, True) | ||
|
r3373 | |||
|
r2513 | # Should we stub wreg fully so we can run the test on all platforms? | ||
@skip_if_not_win32 | ||||
@with_environment | ||||
def test_get_home_dir_8(): | ||||
"""Using registry hack for 'My Documents', os=='nt' | ||||
|
r4872 | |||
|
r2513 | HOMESHARE, HOMEDRIVE, HOMEPATH, USERPROFILE and others are missing. | ||
|
r1746 | """ | ||
|
r1796 | os.name = 'nt' | ||
|
r2447 | # Remove from stub environment all keys that may be set | ||
for key in ['HOME', 'HOMESHARE', 'HOMEDRIVE', 'HOMEPATH', 'USERPROFILE']: | ||||
env.pop(key, None) | ||||
|
r1746 | |||
|
r18050 | class key: | ||
def Close(self): | ||||
pass | ||||
|
r1746 | |||
|
r18050 | with patch.object(wreg, 'OpenKey', return_value=key()), \ | ||
patch.object(wreg, 'QueryValueEx', return_value=[abspath(HOME_TEST_DIR)]): | ||||
home_dir = path.get_home_dir() | ||||
|
r1843 | nt.assert_equal(home_dir, abspath(HOME_TEST_DIR)) | ||
|
r1745 | |||
|
r3896 | @with_environment | ||
|
r7086 | def test_get_xdg_dir_0(): | ||
"""test_get_xdg_dir_0, check xdg_dir""" | ||||
|
r3347 | reload(path) | ||
|
r4475 | path._writable_dir = lambda path: True | ||
|
r3347 | path.get_home_dir = lambda : 'somewhere' | ||
os.name = "posix" | ||||
|
r7086 | sys.platform = "linux2" | ||
|
r3347 | env.pop('IPYTHON_DIR', None) | ||
env.pop('IPYTHONDIR', None) | ||||
env.pop('XDG_CONFIG_HOME', None) | ||||
|
r4872 | |||
|
r3347 | nt.assert_equal(path.get_xdg_dir(), os.path.join('somewhere', '.config')) | ||
@with_environment | ||||
def test_get_xdg_dir_1(): | ||||
"""test_get_xdg_dir_1, check nonexistant xdg_dir""" | ||||
reload(path) | ||||
path.get_home_dir = lambda : HOME_TEST_DIR | ||||
os.name = "posix" | ||||
|
r7086 | sys.platform = "linux2" | ||
|
r3347 | env.pop('IPYTHON_DIR', None) | ||
env.pop('IPYTHONDIR', None) | ||||
env.pop('XDG_CONFIG_HOME', None) | ||||
nt.assert_equal(path.get_xdg_dir(), None) | ||||
@with_environment | ||||
def test_get_xdg_dir_2(): | ||||
"""test_get_xdg_dir_2, check xdg_dir default to ~/.config""" | ||||
reload(path) | ||||
path.get_home_dir = lambda : HOME_TEST_DIR | ||||
os.name = "posix" | ||||
|
r7086 | sys.platform = "linux2" | ||
|
r3347 | env.pop('IPYTHON_DIR', None) | ||
env.pop('IPYTHONDIR', None) | ||||
env.pop('XDG_CONFIG_HOME', None) | ||||
cfgdir=os.path.join(path.get_home_dir(), '.config') | ||||
|
r7086 | if not os.path.exists(cfgdir): | ||
os.makedirs(cfgdir) | ||||
|
r4872 | |||
|
r3347 | nt.assert_equal(path.get_xdg_dir(), cfgdir) | ||
|
r1969 | |||
|
r7086 | @with_environment | ||
def test_get_xdg_dir_3(): | ||||
"""test_get_xdg_dir_3, check xdg_dir not used on OS X""" | ||||
reload(path) | ||||
path.get_home_dir = lambda : HOME_TEST_DIR | ||||
os.name = "posix" | ||||
sys.platform = "darwin" | ||||
env.pop('IPYTHON_DIR', None) | ||||
env.pop('IPYTHONDIR', None) | ||||
env.pop('XDG_CONFIG_HOME', None) | ||||
cfgdir=os.path.join(path.get_home_dir(), '.config') | ||||
if not os.path.exists(cfgdir): | ||||
os.makedirs(cfgdir) | ||||
nt.assert_equal(path.get_xdg_dir(), None) | ||||
|
r1969 | def test_filefind(): | ||
"""Various tests for filefind""" | ||||
f = tempfile.NamedTemporaryFile() | ||||
|
r2505 | # print 'fname:',f.name | ||
|
r21253 | alt_dirs = paths.get_ipython_dir() | ||
|
r2505 | t = path.filefind(f.name, alt_dirs) | ||
# print 'found:',t | ||||
|
r2357 | |||
|
r2505 | |||
|
r2498 | @dec.skip_if_not_win32 | ||
def test_get_long_path_name_win32(): | ||||
|
r12083 | with TemporaryDirectory() as tmpdir: | ||
|
r12085 | |||
|
r13753 | # Make a long path. Expands the path of tmpdir prematurely as it may already have a long | ||
# path component, so ensure we include the long form of it | ||||
|
r13741 | long_path = os.path.join(path.get_long_path_name(tmpdir), u'this is my long path name') | ||
|
r12085 | os.makedirs(long_path) | ||
|
r12083 | |||
|
r12085 | # Test to see if the short path evaluates correctly. | ||
short_path = os.path.join(tmpdir, u'THISIS~1') | ||||
evaluated_path = path.get_long_path_name(short_path) | ||||
nt.assert_equal(evaluated_path.lower(), long_path.lower()) | ||||
|
r2436 | |||
|
r2505 | |||
|
r2498 | @dec.skip_win32 | ||
def test_get_long_path_name(): | ||||
|
r2505 | p = path.get_long_path_name('/usr/local') | ||
|
r7875 | nt.assert_equal(p,'/usr/local') | ||
|
r2436 | |||
|
r4483 | @dec.skip_win32 # can't create not-user-writable dir on win | ||
|
r4475 | @with_environment | ||
def test_not_writable_ipdir(): | ||||
tmpdir = tempfile.mkdtemp() | ||||
os.name = "posix" | ||||
env.pop('IPYTHON_DIR', None) | ||||
env.pop('IPYTHONDIR', None) | ||||
env.pop('XDG_CONFIG_HOME', None) | ||||
env['HOME'] = tmpdir | ||||
ipdir = os.path.join(tmpdir, '.ipython') | ||||
|
r19240 | os.mkdir(ipdir, 0o555) | ||
|
r17996 | try: | ||
|
r19240 | open(os.path.join(ipdir, "_foo_"), 'w').close() | ||
except IOError: | ||||
|
r17996 | pass | ||
else: | ||||
|
r19240 | # I can still write to an unwritable dir, | ||
|
r17996 | # assume I'm root and skip the test | ||
|
r19285 | raise SkipTest("I can't create directories that I can't write to") | ||
|
r4902 | with AssertPrints('is not a writable location', channel='stderr'): | ||
|
r21253 | ipdir = paths.get_ipython_dir() | ||
|
r4475 | env.pop('IPYTHON_DIR', None) | ||
|
r4688 | |||
|
r4696 | def test_unquote_filename(): | ||
for win32 in (True, False): | ||||
|
r7875 | nt.assert_equal(path.unquote_filename('foo.py', win32=win32), 'foo.py') | ||
nt.assert_equal(path.unquote_filename('foo bar.py', win32=win32), 'foo bar.py') | ||||
nt.assert_equal(path.unquote_filename('"foo.py"', win32=True), 'foo.py') | ||||
nt.assert_equal(path.unquote_filename('"foo bar.py"', win32=True), 'foo bar.py') | ||||
nt.assert_equal(path.unquote_filename("'foo.py'", win32=True), 'foo.py') | ||||
nt.assert_equal(path.unquote_filename("'foo bar.py'", win32=True), 'foo bar.py') | ||||
nt.assert_equal(path.unquote_filename('"foo.py"', win32=False), '"foo.py"') | ||||
nt.assert_equal(path.unquote_filename('"foo bar.py"', win32=False), '"foo bar.py"') | ||||
nt.assert_equal(path.unquote_filename("'foo.py'", win32=False), "'foo.py'") | ||||
nt.assert_equal(path.unquote_filename("'foo bar.py'", win32=False), "'foo bar.py'") | ||||
|
r4696 | |||
|
r4688 | @with_environment | ||
def test_get_py_filename(): | ||||
os.chdir(TMP_TEST_DIR) | ||||
for win32 in (True, False): | ||||
with make_tempfile('foo.py'): | ||||
|
r7875 | nt.assert_equal(path.get_py_filename('foo.py', force_win32=win32), 'foo.py') | ||
nt.assert_equal(path.get_py_filename('foo', force_win32=win32), 'foo.py') | ||||
|
r4688 | with make_tempfile('foo'): | ||
|
r7875 | nt.assert_equal(path.get_py_filename('foo', force_win32=win32), 'foo') | ||
|
r4696 | nt.assert_raises(IOError, path.get_py_filename, 'foo.py', force_win32=win32) | ||
nt.assert_raises(IOError, path.get_py_filename, 'foo', force_win32=win32) | ||||
nt.assert_raises(IOError, path.get_py_filename, 'foo.py', force_win32=win32) | ||||
|
r4688 | true_fn = 'foo with spaces.py' | ||
with make_tempfile(true_fn): | ||||
|
r7875 | nt.assert_equal(path.get_py_filename('foo with spaces', force_win32=win32), true_fn) | ||
nt.assert_equal(path.get_py_filename('foo with spaces.py', force_win32=win32), true_fn) | ||||
|
r4688 | if win32: | ||
|
r7875 | nt.assert_equal(path.get_py_filename('"foo with spaces.py"', force_win32=True), true_fn) | ||
nt.assert_equal(path.get_py_filename("'foo with spaces.py'", force_win32=True), true_fn) | ||||
|
r4688 | else: | ||
|
r4696 | nt.assert_raises(IOError, path.get_py_filename, '"foo with spaces.py"', force_win32=False) | ||
nt.assert_raises(IOError, path.get_py_filename, "'foo with spaces.py'", force_win32=False) | ||||
|
r12166 | |||
@onlyif_unicode_paths | ||||
r6152 | def test_unicode_in_filename(): | |||
|
r6164 | """When a file doesn't exist, the exception raised should be safe to call | ||
str() on - i.e. in Python 2 it must only have ASCII characters. | ||||
https://github.com/ipython/ipython/issues/875 | ||||
""" | ||||
r6152 | try: | |||
# these calls should not throw unicode encode exceptions | ||||
path.get_py_filename(u'fooéè.py', force_win32=False) | ||||
except IOError as ex: | ||||
str(ex) | ||||
|
r8014 | |||
|
r8638 | class TestShellGlob(object): | ||
|
r8014 | |||
|
r8638 | @classmethod | ||
def setUpClass(cls): | ||||
|
r13373 | cls.filenames_start_with_a = ['a0', 'a1', 'a2'] | ||
cls.filenames_end_with_b = ['0b', '1b', '2b'] | ||||
|
r8638 | cls.filenames = cls.filenames_start_with_a + cls.filenames_end_with_b | ||
cls.tempdir = TemporaryDirectory() | ||||
td = cls.tempdir.name | ||||
|
r8014 | |||
|
r8638 | with cls.in_tempdir(): | ||
|
r8014 | # Create empty files | ||
|
r8638 | for fname in cls.filenames: | ||
|
r8014 | open(os.path.join(td, fname), 'w').close() | ||
|
r8638 | @classmethod | ||
def tearDownClass(cls): | ||||
cls.tempdir.cleanup() | ||||
@classmethod | ||||
@contextmanager | ||||
def in_tempdir(cls): | ||||
|
r13447 | save = py3compat.getcwd() | ||
|
r8640 | try: | ||
os.chdir(cls.tempdir.name) | ||||
yield | ||||
finally: | ||||
os.chdir(save) | ||||
|
r8638 | |||
def check_match(self, patterns, matches): | ||||
with self.in_tempdir(): | ||||
# glob returns unordered list. that's why sorted is required. | ||||
nt.assert_equals(sorted(path.shellglob(patterns)), | ||||
sorted(matches)) | ||||
def common_cases(self): | ||||
return [ | ||||
(['*'], self.filenames), | ||||
(['a*'], self.filenames_start_with_a), | ||||
(['*c'], ['*c']), | ||||
(['*', 'a*', '*b', '*c'], self.filenames | ||||
+ self.filenames_start_with_a | ||||
+ self.filenames_end_with_b | ||||
+ ['*c']), | ||||
(['a[012]'], self.filenames_start_with_a), | ||||
] | ||||
@skip_win32 | ||||
def test_match_posix(self): | ||||
for (patterns, matches) in self.common_cases() + [ | ||||
([r'\*'], ['*']), | ||||
([r'a\*', 'a*'], ['a*'] + self.filenames_start_with_a), | ||||
([r'a\[012]'], ['a[012]']), | ||||
]: | ||||
yield (self.check_match, patterns, matches) | ||||
@skip_if_not_win32 | ||||
def test_match_windows(self): | ||||
for (patterns, matches) in self.common_cases() + [ | ||||
# In windows, backslash is interpreted as path | ||||
# separator. Therefore, you can't escape glob | ||||
# using it. | ||||
([r'a\*', 'a*'], [r'a\*'] + self.filenames_start_with_a), | ||||
([r'a\[012]'], [r'a\[012]']), | ||||
]: | ||||
yield (self.check_match, patterns, matches) | ||||
|
r8119 | |||
def test_unescape_glob(): | ||||
nt.assert_equals(path.unescape_glob(r'\*\[\!\]\?'), '*[!]?') | ||||
|
r8122 | nt.assert_equals(path.unescape_glob(r'\\*'), r'\*') | ||
nt.assert_equals(path.unescape_glob(r'\\\*'), r'\*') | ||||
nt.assert_equals(path.unescape_glob(r'\\a'), r'\a') | ||||
nt.assert_equals(path.unescape_glob(r'\a'), r'\a') | ||||
|
r11647 | |||
|
r16489 | def test_ensure_dir_exists(): | ||
with TemporaryDirectory() as td: | ||||
d = os.path.join(td, u'∂ir') | ||||
path.ensure_dir_exists(d) # create it | ||||
assert os.path.isdir(d) | ||||
path.ensure_dir_exists(d) # no-op | ||||
f = os.path.join(td, u'Æ’ile') | ||||
open(f, 'w').close() # touch | ||||
with nt.assert_raises(IOError): | ||||
path.ensure_dir_exists(f) | ||||
|
r11647 | class TestLinkOrCopy(object): | ||
def setUp(self): | ||||
self.tempdir = TemporaryDirectory() | ||||
self.src = self.dst("src") | ||||
with open(self.src, "w") as f: | ||||
f.write("Hello, world!") | ||||
def tearDown(self): | ||||
self.tempdir.cleanup() | ||||
def dst(self, *args): | ||||
return os.path.join(self.tempdir.name, *args) | ||||
def assert_inode_not_equal(self, a, b): | ||||
nt.assert_not_equals(os.stat(a).st_ino, os.stat(b).st_ino, | ||||
"%r and %r do reference the same indoes" %(a, b)) | ||||
def assert_inode_equal(self, a, b): | ||||
nt.assert_equals(os.stat(a).st_ino, os.stat(b).st_ino, | ||||
"%r and %r do not reference the same indoes" %(a, b)) | ||||
|
r11650 | def assert_content_equal(self, a, b): | ||
|
r11653 | with open(a) as a_f: | ||
with open(b) as b_f: | ||||
nt.assert_equals(a_f.read(), b_f.read()) | ||||
|
r11647 | |||
@skip_win32 | ||||
def test_link_successful(self): | ||||
dst = self.dst("target") | ||||
path.link_or_copy(self.src, dst) | ||||
self.assert_inode_equal(self.src, dst) | ||||
@skip_win32 | ||||
def test_link_into_dir(self): | ||||
dst = self.dst("some_dir") | ||||
os.mkdir(dst) | ||||
path.link_or_copy(self.src, dst) | ||||
expected_dst = self.dst("some_dir", os.path.basename(self.src)) | ||||
self.assert_inode_equal(self.src, expected_dst) | ||||
@skip_win32 | ||||
def test_target_exists(self): | ||||
dst = self.dst("target") | ||||
open(dst, "w").close() | ||||
path.link_or_copy(self.src, dst) | ||||
self.assert_inode_equal(self.src, dst) | ||||
@skip_win32 | ||||
def test_no_link(self): | ||||
real_link = os.link | ||||
try: | ||||
del os.link | ||||
dst = self.dst("target") | ||||
path.link_or_copy(self.src, dst) | ||||
|
r11650 | self.assert_content_equal(self.src, dst) | ||
|
r11647 | self.assert_inode_not_equal(self.src, dst) | ||
finally: | ||||
os.link = real_link | ||||
@skip_if_not_win32 | ||||
def test_windows(self): | ||||
dst = self.dst("target") | ||||
path.link_or_copy(self.src, dst) | ||||
|
r11650 | self.assert_content_equal(self.src, dst) | ||
|
r20038 | |||
def test_link_twice(self): | ||||
# Linking the same file twice shouldn't leave duplicates around. | ||||
# See https://github.com/ipython/ipython/issues/6450 | ||||
dst = self.dst('target') | ||||
path.link_or_copy(self.src, dst) | ||||
path.link_or_copy(self.src, dst) | ||||
self.assert_inode_equal(self.src, dst) | ||||
nt.assert_equal(sorted(os.listdir(self.tempdir.name)), ['src', 'target']) | ||||