| |
| """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) |
|
|
|
|
| |
| @pytest.fixture |
| def environment(): |
| global oldstuff, platformstuff |
| oldstuff = ( |
| env.copy(), |
| os.name, |
| sys.platform, |
| path.get_home_dir, |
| IPython.__file__, |
| os.getcwd(), |
| ) |
|
|
| yield |
|
|
| ( |
| 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) |
| assert not hasattr(sys, "frozen") |
|
|
|
|
| with_environment = pytest.mark.usefixtures("environment") |
|
|
|
|
| @skip_if_not_win32 |
| @with_environment |
| def test_get_home_dir_1(monkeypatch): |
| """Testcase for py2exe logic, un-compressed lib |
| """ |
| unfrozen = path.get_home_dir() |
| monkeypatch.setattr(sys, "frozen", True, raising=False) |
|
|
| |
| 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(monkeypatch): |
| """Testcase for py2exe logic, compressed lib |
| """ |
| unfrozen = path.get_home_dir() |
| monkeypatch.setattr(sys, "frozen", True, raising=False) |
| |
| 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(monkeypatch): |
| """raise HomeDirError if $HOME is specified, but not a writable dir""" |
| env['HOME'] = abspath(HOME_TEST_DIR+'garbage') |
| |
| monkeypatch.setattr(os, "name", "posix") |
| pytest.raises(path.HomeDirError, path.get_home_dir, True) |
|
|
| |
| @skip_if_not_win32 |
| @with_environment |
| def test_get_home_dir_8(monkeypatch): |
| """Using registry hack for 'My Documents', os=='nt' |
| |
| HOMESHARE, HOMEDRIVE, HOMEPATH, USERPROFILE and others are missing. |
| """ |
| monkeypatch.setattr(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(monkeypatch): |
| """test_get_xdg_dir_0, check xdg_dir""" |
| monkeypatch.setattr(path, "_writable_dir", lambda path: True) |
| monkeypatch.setattr(path, "get_home_dir", lambda: "somewhere") |
| monkeypatch.setattr(os, "name", "posix") |
| monkeypatch.setattr(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(monkeypatch): |
| """test_get_xdg_dir_1, check nonexistent xdg_dir""" |
| monkeypatch.setattr(path, "get_home_dir", lambda: HOME_TEST_DIR) |
| monkeypatch.setattr(os, "name", "posix") |
| monkeypatch.setattr(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(monkeypatch): |
| """test_get_xdg_dir_2, check xdg_dir default to ~/.config""" |
| monkeypatch.setattr(path, "get_home_dir", lambda: HOME_TEST_DIR) |
| monkeypatch.setattr(os, "name", "posix") |
| monkeypatch.setattr(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(monkeypatch): |
| """test_get_xdg_dir_3, check xdg_dir not used on non-posix systems""" |
| monkeypatch.setattr(path, "get_home_dir", lambda: HOME_TEST_DIR) |
| monkeypatch.setattr(os, "name", "nt") |
| monkeypatch.setattr(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" |
|
|
|
|
| @dec.skip_win32 |
| @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") |
| 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 pytest.warns(UserWarning, match="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"] |
|
|