|
|
|
"""Tests for IPython.utils.path.py""" |
|
|
|
|
|
|
|
|
|
import os |
|
import shutil |
|
import sys |
|
import tempfile |
|
import unittest |
|
from contextlib import contextmanager |
|
from importlib import reload |
|
from os.path import abspath, join |
|
from unittest.mock import patch |
|
|
|
import pytest |
|
from tempfile import TemporaryDirectory |
|
|
|
import IPython |
|
from IPython import paths |
|
from IPython.testing import decorators as dec |
|
from IPython.testing.decorators import ( |
|
onlyif_unicode_paths, |
|
skip_if_not_win32, |
|
skip_win32, |
|
) |
|
from IPython.testing.tools import make_tempfile |
|
from IPython.utils import path |
|
|
|
|
|
try: |
|
import winreg as wreg |
|
except ImportError: |
|
|
|
import types |
|
wr_name = "winreg" |
|
sys.modules[wr_name] = types.ModuleType(wr_name) |
|
try: |
|
import winreg as wreg |
|
except ImportError: |
|
import _winreg as wreg |
|
|
|
|
|
(wreg.OpenKey, wreg.QueryValueEx,) = (None, None) |
|
|
|
|
|
|
|
|
|
env = os.environ |
|
TMP_TEST_DIR = tempfile.mkdtemp() |
|
HOME_TEST_DIR = join(TMP_TEST_DIR, "home_test_dir") |
|
|
|
|
|
|
|
|
|
def setup_module(): |
|
"""Setup testenvironment for the module: |
|
|
|
- Adds dummy home dir tree |
|
""" |
|
|
|
|
|
os.makedirs(os.path.join(HOME_TEST_DIR, 'ipython')) |
|
|
|
|
|
def teardown_module(): |
|
"""Teardown testenvironment for the module: |
|
|
|
- Remove dummy home dir tree |
|
""" |
|
|
|
|
|
|
|
shutil.rmtree(TMP_TEST_DIR) |
|
|
|
|
|
def setup_environment(): |
|
"""Setup testenvironment for some functions that are tested |
|
in this module. In particular this functions stores attributes |
|
and other things that we need to stub in some test functions. |
|
This needs to be done on a function level and not module level because |
|
each testfunction needs a pristine environment. |
|
""" |
|
global oldstuff, platformstuff |
|
oldstuff = (env.copy(), os.name, sys.platform, path.get_home_dir, IPython.__file__, os.getcwd()) |
|
|
|
def teardown_environment(): |
|
"""Restore things that were remembered by the setup_environment function |
|
""" |
|
(oldenv, os.name, sys.platform, path.get_home_dir, IPython.__file__, old_wd) = oldstuff |
|
os.chdir(old_wd) |
|
reload(path) |
|
|
|
for key in list(env): |
|
if key not in oldenv: |
|
del env[key] |
|
env.update(oldenv) |
|
if hasattr(sys, 'frozen'): |
|
del sys.frozen |
|
|
|
|
|
|
|
@pytest.fixture |
|
def environment(): |
|
setup_environment() |
|
yield |
|
teardown_environment() |
|
|
|
|
|
with_environment = pytest.mark.usefixtures("environment") |
|
|
|
|
|
@skip_if_not_win32 |
|
@with_environment |
|
def test_get_home_dir_1(): |
|
"""Testcase for py2exe logic, un-compressed lib |
|
""" |
|
unfrozen = path.get_home_dir() |
|
sys.frozen = True |
|
|
|
|
|
IPython.__file__ = abspath(join(HOME_TEST_DIR, "Lib/IPython/__init__.py")) |
|
|
|
home_dir = path.get_home_dir() |
|
assert home_dir == unfrozen |
|
|
|
|
|
@skip_if_not_win32 |
|
@with_environment |
|
def test_get_home_dir_2(): |
|
"""Testcase for py2exe logic, compressed lib |
|
""" |
|
unfrozen = path.get_home_dir() |
|
sys.frozen = True |
|
|
|
IPython.__file__ = abspath(join(HOME_TEST_DIR, "Library.zip/IPython/__init__.py")).lower() |
|
|
|
home_dir = path.get_home_dir(True) |
|
assert home_dir == unfrozen |
|
|
|
|
|
@skip_win32 |
|
@with_environment |
|
def test_get_home_dir_3(): |
|
"""get_home_dir() uses $HOME if set""" |
|
env["HOME"] = HOME_TEST_DIR |
|
home_dir = path.get_home_dir(True) |
|
|
|
assert home_dir == os.path.realpath(env["HOME"]) |
|
|
|
|
|
@with_environment |
|
def test_get_home_dir_4(): |
|
"""get_home_dir() still works if $HOME is not set""" |
|
|
|
if 'HOME' in env: del env['HOME'] |
|
|
|
home = path.get_home_dir(False) |
|
|
|
@skip_win32 |
|
@with_environment |
|
def test_get_home_dir_5(): |
|
"""raise HomeDirError if $HOME is specified, but not a writable dir""" |
|
env['HOME'] = abspath(HOME_TEST_DIR+'garbage') |
|
|
|
os.name = 'posix' |
|
pytest.raises(path.HomeDirError, path.get_home_dir, True) |
|
|
|
|
|
@skip_if_not_win32 |
|
@with_environment |
|
def test_get_home_dir_8(): |
|
"""Using registry hack for 'My Documents', os=='nt' |
|
|
|
HOMESHARE, HOMEDRIVE, HOMEPATH, USERPROFILE and others are missing. |
|
""" |
|
os.name = 'nt' |
|
|
|
for key in ['HOME', 'HOMESHARE', 'HOMEDRIVE', 'HOMEPATH', 'USERPROFILE']: |
|
env.pop(key, None) |
|
|
|
class key: |
|
def __enter__(self): |
|
pass |
|
def Close(self): |
|
pass |
|
def __exit__(*args, **kwargs): |
|
pass |
|
|
|
with patch.object(wreg, 'OpenKey', return_value=key()), \ |
|
patch.object(wreg, 'QueryValueEx', return_value=[abspath(HOME_TEST_DIR)]): |
|
home_dir = path.get_home_dir() |
|
assert home_dir == abspath(HOME_TEST_DIR) |
|
|
|
@with_environment |
|
def test_get_xdg_dir_0(): |
|
"""test_get_xdg_dir_0, check xdg_dir""" |
|
reload(path) |
|
path._writable_dir = lambda path: True |
|
path.get_home_dir = lambda : 'somewhere' |
|
os.name = "posix" |
|
sys.platform = "linux2" |
|
env.pop('IPYTHON_DIR', None) |
|
env.pop('IPYTHONDIR', None) |
|
env.pop('XDG_CONFIG_HOME', None) |
|
|
|
assert path.get_xdg_dir() == os.path.join("somewhere", ".config") |
|
|
|
|
|
@with_environment |
|
def test_get_xdg_dir_1(): |
|
"""test_get_xdg_dir_1, check nonexistent xdg_dir""" |
|
reload(path) |
|
path.get_home_dir = lambda : HOME_TEST_DIR |
|
os.name = "posix" |
|
sys.platform = "linux2" |
|
env.pop('IPYTHON_DIR', None) |
|
env.pop('IPYTHONDIR', None) |
|
env.pop('XDG_CONFIG_HOME', None) |
|
assert path.get_xdg_dir() is 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" |
|
sys.platform = "linux2" |
|
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) |
|
|
|
assert path.get_xdg_dir() == cfgdir |
|
|
|
@with_environment |
|
def test_get_xdg_dir_3(): |
|
"""test_get_xdg_dir_3, check xdg_dir not used on non-posix systems""" |
|
reload(path) |
|
path.get_home_dir = lambda : HOME_TEST_DIR |
|
os.name = "nt" |
|
sys.platform = "win32" |
|
env.pop('IPYTHON_DIR', None) |
|
env.pop('IPYTHONDIR', None) |
|
env.pop('XDG_CONFIG_HOME', None) |
|
cfgdir=os.path.join(path.get_home_dir(), '.config') |
|
os.makedirs(cfgdir, exist_ok=True) |
|
|
|
assert path.get_xdg_dir() is None |
|
|
|
def test_filefind(): |
|
"""Various tests for filefind""" |
|
f = tempfile.NamedTemporaryFile() |
|
|
|
alt_dirs = paths.get_ipython_dir() |
|
t = path.filefind(f.name, alt_dirs) |
|
|
|
|
|
|
|
@dec.skip_if_not_win32 |
|
def test_get_long_path_name_win32(): |
|
with TemporaryDirectory() as tmpdir: |
|
|
|
|
|
|
|
long_path = os.path.join(path.get_long_path_name(tmpdir), 'this is my long path name') |
|
os.makedirs(long_path) |
|
|
|
|
|
short_path = os.path.join(tmpdir, 'THISIS~1') |
|
evaluated_path = path.get_long_path_name(short_path) |
|
assert evaluated_path.lower() == long_path.lower() |
|
|
|
|
|
@dec.skip_win32 |
|
def test_get_long_path_name(): |
|
p = path.get_long_path_name("/usr/local") |
|
assert p == "/usr/local" |
|
|
|
|
|
class TestRaiseDeprecation(unittest.TestCase): |
|
|
|
@dec.skip_win32 |
|
@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: |
|
open(os.path.join(ipdir, "_foo_"), "w", encoding="utf-8").close() |
|
except IOError: |
|
pass |
|
else: |
|
|
|
|
|
pytest.skip("I can't create directories that I can't write to") |
|
|
|
with self.assertWarnsRegex(UserWarning, 'is not a writable location'): |
|
ipdir = paths.get_ipython_dir() |
|
env.pop('IPYTHON_DIR', None) |
|
|
|
@with_environment |
|
def test_get_py_filename(): |
|
os.chdir(TMP_TEST_DIR) |
|
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" |
|
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") |
|
true_fn = "foo with spaces.py" |
|
with make_tempfile(true_fn): |
|
assert path.get_py_filename("foo with spaces") == true_fn |
|
assert path.get_py_filename("foo with spaces.py") == true_fn |
|
pytest.raises(IOError, path.get_py_filename, '"foo with spaces.py"') |
|
pytest.raises(IOError, path.get_py_filename, "'foo with spaces.py'") |
|
|
|
@onlyif_unicode_paths |
|
def test_unicode_in_filename(): |
|
"""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 |
|
""" |
|
try: |
|
|
|
path.get_py_filename('fooéè.py') |
|
except IOError as ex: |
|
str(ex) |
|
|
|
|
|
class TestShellGlob(unittest.TestCase): |
|
|
|
@classmethod |
|
def setUpClass(cls): |
|
cls.filenames_start_with_a = ['a0', 'a1', 'a2'] |
|
cls.filenames_end_with_b = ['0b', '1b', '2b'] |
|
cls.filenames = cls.filenames_start_with_a + cls.filenames_end_with_b |
|
cls.tempdir = TemporaryDirectory() |
|
td = cls.tempdir.name |
|
|
|
with cls.in_tempdir(): |
|
|
|
for fname in cls.filenames: |
|
open(os.path.join(td, fname), "w", encoding="utf-8").close() |
|
|
|
@classmethod |
|
def tearDownClass(cls): |
|
cls.tempdir.cleanup() |
|
|
|
@classmethod |
|
@contextmanager |
|
def in_tempdir(cls): |
|
save = os.getcwd() |
|
try: |
|
os.chdir(cls.tempdir.name) |
|
yield |
|
finally: |
|
os.chdir(save) |
|
|
|
def check_match(self, patterns, matches): |
|
with self.in_tempdir(): |
|
|
|
assert 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]']), |
|
]: |
|
self.check_match(patterns, matches) |
|
|
|
@skip_if_not_win32 |
|
def test_match_windows(self): |
|
for (patterns, matches) in self.common_cases() + [ |
|
|
|
|
|
|
|
([r'a\*', 'a*'], [r'a\*'] + self.filenames_start_with_a), |
|
([r'a\[012]'], [r'a\[012]']), |
|
]: |
|
self.check_match(patterns, matches) |
|
|
|
|
|
@pytest.mark.parametrize( |
|
"globstr, unescaped_globstr", |
|
[ |
|
(r"\*\[\!\]\?", "*[!]?"), |
|
(r"\\*", r"\*"), |
|
(r"\\\*", r"\*"), |
|
(r"\\a", r"\a"), |
|
(r"\a", r"\a"), |
|
], |
|
) |
|
def test_unescape_glob(globstr, unescaped_globstr): |
|
assert path.unescape_glob(globstr) == unescaped_globstr |
|
|
|
|
|
@onlyif_unicode_paths |
|
def test_ensure_dir_exists(): |
|
with TemporaryDirectory() as td: |
|
d = os.path.join(td, '∂ir') |
|
path.ensure_dir_exists(d) |
|
assert os.path.isdir(d) |
|
path.ensure_dir_exists(d) |
|
f = os.path.join(td, "ƒile") |
|
open(f, "w", encoding="utf-8").close() |
|
with pytest.raises(IOError): |
|
path.ensure_dir_exists(f) |
|
|
|
class TestLinkOrCopy(unittest.TestCase): |
|
def setUp(self): |
|
self.tempdir = TemporaryDirectory() |
|
self.src = self.dst("src") |
|
with open(self.src, "w", encoding="utf-8") 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): |
|
assert ( |
|
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): |
|
assert ( |
|
os.stat(a).st_ino == os.stat(b).st_ino |
|
), "%r and %r do not reference the same indoes" % (a, b) |
|
|
|
def assert_content_equal(self, a, b): |
|
with open(a, "rb") as a_f: |
|
with open(b, "rb") as b_f: |
|
assert a_f.read() == b_f.read() |
|
|
|
@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", encoding="utf-8").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) |
|
self.assert_content_equal(self.src, dst) |
|
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) |
|
self.assert_content_equal(self.src, dst) |
|
|
|
def test_link_twice(self): |
|
|
|
|
|
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) |
|
assert sorted(os.listdir(self.tempdir.name)) == ["src", "target"] |
|
|