test_path.py
509 lines
| 15.7 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 | |||
|
r1843 | import os | ||
import shutil | ||||
import sys | ||||
import tempfile | ||||
|
r23706 | import unittest | ||
|
r11653 | from contextlib import contextmanager | ||
|
r27076 | from importlib import reload | ||
|
r27509 | from os.path import abspath, join | ||
from unittest.mock import patch | ||||
|
r1843 | |||
|
r26923 | import pytest | ||
|
r27509 | from tempfile import TemporaryDirectory | ||
|
r1617 | |||
|
r1843 | import IPython | ||
|
r21253 | from IPython import paths | ||
|
r2436 | from IPython.testing import decorators as dec | ||
|
r27154 | from IPython.testing.decorators import ( | ||
|
r27509 | onlyif_unicode_paths, | ||
|
r27154 | skip_if_not_win32, | ||
skip_win32, | ||||
) | ||||
|
r25335 | from IPython.testing.tools import make_tempfile | ||
|
r11127 | from IPython.utils import path | ||
|
r25224 | |||
|
r1843 | # Platform-dependent imports | ||
|
r1750 | try: | ||
|
r25223 | import winreg as wreg | ||
|
r1750 | except ImportError: | ||
|
r23092 | #Fake _winreg module on non-windows platforms | ||
import types | ||||
wr_name = "winreg" | ||||
sys.modules[wr_name] = types.ModuleType(wr_name) | ||||
|
r13354 | try: | ||
|
r23092 | import winreg as wreg | ||
|
r13354 | except ImportError: | ||
|
r23092 | import _winreg as wreg | ||
|
r27509 | |||
|
r13354 | #Add entries that needs to be stubbed by the testing code | ||
(wreg.OpenKey, wreg.QueryValueEx,) = (None, None) | ||||
|
r4872 | |||
|
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 | # | ||
|
r25082 | def setup_module(): | ||
|
r1805 | """Setup testenvironment for the module: | ||
|
r4872 | |||
|
r26419 | - Adds dummy home dir tree | ||
|
r1805 | """ | ||
|
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 | |||
|
r25082 | def teardown_module(): | ||
|
r1805 | """Teardown testenvironment for the module: | ||
|
r4872 | |||
|
r26419 | - Remove dummy home dir tree | ||
|
r1805 | """ | ||
|
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 | ||||
|
r27042 | |||
|
r1805 | # Build decorator that uses the setup_environment/setup_environment | ||
|
r27042 | @pytest.fixture | ||
def environment(): | ||||
setup_environment() | ||||
yield | ||||
teardown_environment() | ||||
with_environment = pytest.mark.usefixtures("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() | ||
|
r26719 | assert 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) | ||
|
r26719 | assert home_dir == unfrozen | ||
|
r1746 | |||
|
r2505 | |||
|
r27154 | @skip_win32 | ||
|
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 | ||
|
r26719 | assert 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 | |||
|
r27154 | @skip_win32 | ||
|
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' | ||||
|
r26923 | pytest.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: | ||
|
r25293 | def __enter__(self): | ||
pass | ||||
|
r18050 | def Close(self): | ||
pass | ||||
|
r25293 | def __exit__(*args, **kwargs): | ||
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() | ||||
|
r26719 | assert 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 | |||
|
r26719 | assert path.get_xdg_dir() == os.path.join("somewhere", ".config") | ||
|
r3347 | |||
@with_environment | ||||
def test_get_xdg_dir_1(): | ||||
|
r24084 | """test_get_xdg_dir_1, check nonexistent xdg_dir""" | ||
|
r3347 | 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) | ||||
|
r26719 | assert path.get_xdg_dir() is None | ||
|
r3347 | |||
@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 | |||
|
r26719 | assert path.get_xdg_dir() == cfgdir | ||
|
r1969 | |||
|
r7086 | @with_environment | ||
def test_get_xdg_dir_3(): | ||||
|
r26956 | """test_get_xdg_dir_3, check xdg_dir not used on non-posix systems""" | ||
|
r7086 | reload(path) | ||
path.get_home_dir = lambda : HOME_TEST_DIR | ||||
|
r26956 | os.name = "nt" | ||
sys.platform = "win32" | ||||
|
r7086 | env.pop('IPYTHON_DIR', None) | ||
env.pop('IPYTHONDIR', None) | ||||
env.pop('XDG_CONFIG_HOME', None) | ||||
cfgdir=os.path.join(path.get_home_dir(), '.config') | ||||
|
r26923 | os.makedirs(cfgdir, exist_ok=True) | ||
|
r7086 | |||
|
r26719 | assert path.get_xdg_dir() is None | ||
|
r7086 | |||
|
r1969 | def test_filefind(): | ||
"""Various tests for filefind""" | ||||
f = tempfile.NamedTemporaryFile() | ||||
|
r28756 | # print('fname:',f.name) | ||
|
r21253 | alt_dirs = paths.get_ipython_dir() | ||
|
r2505 | t = path.filefind(f.name, alt_dirs) | ||
|
r28756 | # print('found:',t) | ||
|
r2357 | |||
|
r2505 | |||
|
r2498 | @dec.skip_if_not_win32 | ||
def test_get_long_path_name_win32(): | ||||
|
r12083 | with TemporaryDirectory() as tmpdir: | ||
|
r12085 | |||
|
r25223 | # Make a long path. Expands the path of tmpdir prematurely as it may already have a long | ||
|
r13753 | # path component, so ensure we include the long form of it | ||
|
r23213 | long_path = os.path.join(path.get_long_path_name(tmpdir), 'this is my long path name') | ||
|
r12085 | os.makedirs(long_path) | ||
|
r12083 | |||
|
r12085 | # Test to see if the short path evaluates correctly. | ||
|
r23213 | short_path = os.path.join(tmpdir, 'THISIS~1') | ||
|
r12085 | evaluated_path = path.get_long_path_name(short_path) | ||
|
r26719 | assert evaluated_path.lower() == long_path.lower() | ||
|
r2436 | |||
|
r2505 | |||
|
r2498 | @dec.skip_win32 | ||
def test_get_long_path_name(): | ||||
|
r26719 | p = path.get_long_path_name("/usr/local") | ||
assert p == "/usr/local" | ||||
|
r2436 | |||
|
r25109 | |||
class TestRaiseDeprecation(unittest.TestCase): | ||||
@dec.skip_win32 # can't create not-user-writable dir on win | ||||
@with_environment | ||||
def test_not_writable_ipdir(self): | ||||
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') | ||||
os.mkdir(ipdir, 0o555) | ||||
try: | ||||
|
r27495 | open(os.path.join(ipdir, "_foo_"), "w", encoding="utf-8").close() | ||
|
r25109 | except IOError: | ||
pass | ||||
else: | ||||
# I can still write to an unwritable dir, | ||||
# assume I'm root and skip the test | ||||
|
r27042 | pytest.skip("I can't create directories that I can't write to") | ||
|
r25109 | with self.assertWarnsRegex(UserWarning, 'is not a writable location'): | ||
ipdir = paths.get_ipython_dir() | ||||
env.pop('IPYTHON_DIR', None) | ||||
|
r4688 | |||
@with_environment | ||||
def test_get_py_filename(): | ||||
os.chdir(TMP_TEST_DIR) | ||||
|
r26719 | with make_tempfile("foo.py"): | ||
assert path.get_py_filename("foo.py") == "foo.py" | ||||
assert path.get_py_filename("foo") == "foo.py" | ||||
with make_tempfile("foo"): | ||||
assert path.get_py_filename("foo") == "foo" | ||||
|
r26923 | pytest.raises(IOError, path.get_py_filename, "foo.py") | ||
pytest.raises(IOError, path.get_py_filename, "foo") | ||||
pytest.raises(IOError, path.get_py_filename, "foo.py") | ||||
|
r26719 | true_fn = "foo with spaces.py" | ||
|
r22457 | with make_tempfile(true_fn): | ||
|
r26719 | assert path.get_py_filename("foo with spaces") == true_fn | ||
assert path.get_py_filename("foo with spaces.py") == true_fn | ||||
|
r26923 | pytest.raises(IOError, path.get_py_filename, '"foo with spaces.py"') | ||
pytest.raises(IOError, path.get_py_filename, "'foo with spaces.py'") | ||||
|
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. | ||||
|
r25223 | |||
|
r6164 | https://github.com/ipython/ipython/issues/875 | ||
""" | ||||
r6152 | try: | |||
# these calls should not throw unicode encode exceptions | ||||
|
r25109 | path.get_py_filename('fooéè.py') | ||
r6152 | except IOError as ex: | |||
str(ex) | ||||
|
r8014 | |||
|
r23706 | class TestShellGlob(unittest.TestCase): | ||
|
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: | ||
|
r27495 | open(os.path.join(td, fname), "w", encoding="utf-8").close() | ||
|
r8014 | |||
|
r8638 | @classmethod | ||
def tearDownClass(cls): | ||||
cls.tempdir.cleanup() | ||||
@classmethod | ||||
@contextmanager | ||||
def in_tempdir(cls): | ||||
|
r23045 | save = os.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. | ||||
|
r26719 | assert sorted(path.shellglob(patterns)) == sorted(matches) | ||
|
r8638 | |||
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]']), | ||||
]: | ||||
|
r27084 | self.check_match(patterns, matches) | ||
|
r8638 | |||
@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]']), | ||||
]: | ||||
|
r27084 | self.check_match(patterns, matches) | ||
|
r8119 | |||
|
r27423 | @pytest.mark.parametrize( | ||
|
r27444 | "globstr, unescaped_globstr", | ||
|
r27423 | [ | ||
(r"\*\[\!\]\?", "*[!]?"), | ||||
(r"\\*", r"\*"), | ||||
(r"\\\*", r"\*"), | ||||
(r"\\a", r"\a"), | ||||
|
r27444 | (r"\a", r"\a"), | ||
], | ||||
|
r27423 | ) | ||
|
r27422 | def test_unescape_glob(globstr, unescaped_globstr): | ||
assert path.unescape_glob(globstr) == unescaped_globstr | ||||
|
r11647 | |||
|
r23892 | @onlyif_unicode_paths | ||
|
r16489 | def test_ensure_dir_exists(): | ||
with TemporaryDirectory() as td: | ||||
|
r23213 | d = os.path.join(td, '∂ir') | ||
|
r16489 | path.ensure_dir_exists(d) # create it | ||
|
r21733 | assert os.path.isdir(d) | ||
|
r27495 | path.ensure_dir_exists(d) # no-op | ||
f = os.path.join(td, "Æ’ile") | ||||
open(f, "w", encoding="utf-8").close() # touch | ||||
|
r26923 | with pytest.raises(IOError): | ||
|
r16489 | path.ensure_dir_exists(f) | ||
|
r25109 | class TestLinkOrCopy(unittest.TestCase): | ||
|
r11647 | def setUp(self): | ||
self.tempdir = TemporaryDirectory() | ||||
self.src = self.dst("src") | ||||
|
r27495 | with open(self.src, "w", encoding="utf-8") as f: | ||
|
r11647 | 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): | ||||
|
r26719 | assert ( | ||
os.stat(a).st_ino != os.stat(b).st_ino | ||||
), "%r and %r do reference the same indoes" % (a, b) | ||||
|
r11647 | |||
def assert_inode_equal(self, a, b): | ||||
|
r26719 | assert ( | ||
os.stat(a).st_ino == os.stat(b).st_ino | ||||
), "%r and %r do not reference the same indoes" % (a, b) | ||||
|
r11647 | |||
|
r11650 | def assert_content_equal(self, a, b): | ||
|
r27495 | with open(a, "rb") as a_f: | ||
with open(b, "rb") as b_f: | ||||
|
r26719 | assert 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") | ||||
|
r27495 | open(dst, "w", encoding="utf-8").close() | ||
|
r11647 | 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) | ||||
|
r26719 | assert sorted(os.listdir(self.tempdir.name)) == ["src", "target"] | ||