Format: text/x-python
⬇ Download File (15.6 KB)
   1  # encoding: utf-8
   2  """Tests for IPython.utils.path.py"""
   3  
   4  # Copyright (c) IPython Development Team.
   5  # Distributed under the terms of the Modified BSD License.
   6  
   7  import os
   8  import shutil
   9  import sys
  10  import tempfile
  11  import unittest
  12  from contextlib import contextmanager
  13  from importlib import reload
  14  from os.path import abspath, join
  15  from unittest.mock import patch
  16  
  17  import pytest
  18  from tempfile import TemporaryDirectory
  19  
  20  import IPython
  21  from IPython import paths
  22  from IPython.testing import decorators as dec
  23  from IPython.testing.decorators import (
  24      onlyif_unicode_paths,
  25      skip_if_not_win32,
  26      skip_win32,
  27  )
  28  from IPython.testing.tools import make_tempfile
  29  from IPython.utils import path
  30  
  31  # Platform-dependent imports
  32  try:
  33      import winreg as wreg
  34  except ImportError:
  35      #Fake _winreg module on non-windows platforms
  36      import types
  37      wr_name = "winreg"
  38      sys.modules[wr_name] = types.ModuleType(wr_name)
  39      try:
  40          import winreg as wreg
  41      except ImportError:
  42          import _winreg as wreg
  43  
  44          #Add entries that needs to be stubbed by the testing code
  45          (wreg.OpenKey, wreg.QueryValueEx,) = (None, None)
  46  
  47  #-----------------------------------------------------------------------------
  48  # Globals
  49  #-----------------------------------------------------------------------------
  50  env = os.environ
  51  TMP_TEST_DIR = tempfile.mkdtemp()
  52  HOME_TEST_DIR = join(TMP_TEST_DIR, "home_test_dir")
  53  #
  54  # Setup/teardown functions/decorators
  55  #
  56  
  57  def setup_module():
  58      """Setup testenvironment for the module:
  59  
  60      - Adds dummy home dir tree
  61      """
  62      # Do not mask exceptions here.  In particular, catching WindowsError is a
  63      # problem because that exception is only defined on Windows...
  64      os.makedirs(os.path.join(HOME_TEST_DIR, 'ipython'))
  65  
  66  
  67  def teardown_module():
  68      """Teardown testenvironment for the module:
  69  
  70      - Remove dummy home dir tree
  71      """
  72      # Note: we remove the parent test dir, which is the root of all test
  73      # subdirs we may have created.  Use shutil instead of os.removedirs, so
  74      # that non-empty directories are all recursively removed.
  75      shutil.rmtree(TMP_TEST_DIR)
  76  
  77  
  78  # Build decorator that uses the setup_environment/setup_environment
  79  @pytest.fixture
  80  def environment():
  81      global oldstuff, platformstuff
  82      oldstuff = (
  83          env.copy(),
  84          os.name,
  85          sys.platform,
  86          path.get_home_dir,
  87          IPython.__file__,
  88          os.getcwd(),
  89      )
  90  
  91      yield
  92  
  93      (
  94          oldenv,
  95          os.name,
  96          sys.platform,
  97          path.get_home_dir,
  98          IPython.__file__,
  99          old_wd,
 100      ) = oldstuff
 101      os.chdir(old_wd)
 102      reload(path)
 103  
 104      for key in list(env):
 105          if key not in oldenv:
 106              del env[key]
 107      env.update(oldenv)
 108      assert not hasattr(sys, "frozen")
 109  
 110  
 111  with_environment = pytest.mark.usefixtures("environment")
 112  
 113  
 114  @skip_if_not_win32
 115  @with_environment
 116  def test_get_home_dir_1(monkeypatch):
 117      """Testcase for py2exe logic, un-compressed lib
 118      """
 119      unfrozen = path.get_home_dir()
 120      monkeypatch.setattr(sys, "frozen", True, raising=False)
 121  
 122      #fake filename for IPython.__init__
 123      IPython.__file__ = abspath(join(HOME_TEST_DIR, "Lib/IPython/__init__.py"))
 124  
 125      home_dir = path.get_home_dir()
 126      assert home_dir == unfrozen
 127  
 128  
 129  @skip_if_not_win32
 130  @with_environment
 131  def test_get_home_dir_2(monkeypatch):
 132      """Testcase for py2exe logic, compressed lib
 133      """
 134      unfrozen = path.get_home_dir()
 135      monkeypatch.setattr(sys, "frozen", True, raising=False)
 136      # fake filename for IPython.__init__
 137      IPython.__file__ = abspath(
 138          join(HOME_TEST_DIR, "Library.zip/IPython/__init__.py")
 139      ).lower()
 140  
 141      home_dir = path.get_home_dir(True)
 142      assert home_dir == unfrozen
 143  
 144  
 145  @skip_win32
 146  @with_environment
 147  def test_get_home_dir_3():
 148      """get_home_dir() uses $HOME if set"""
 149      env["HOME"] = HOME_TEST_DIR
 150      home_dir = path.get_home_dir(True)
 151      # get_home_dir expands symlinks
 152      assert home_dir == os.path.realpath(env["HOME"])
 153  
 154  
 155  @with_environment
 156  def test_get_home_dir_4():
 157      """get_home_dir() still works if $HOME is not set"""
 158  
 159      if 'HOME' in env: del env['HOME']
 160      # this should still succeed, but we don't care what the answer is
 161      home = path.get_home_dir(False)
 162  
 163  @skip_win32
 164  @with_environment
 165  def test_get_home_dir_5(monkeypatch):
 166      """raise HomeDirError if $HOME is specified, but not a writable dir"""
 167      env['HOME'] = abspath(HOME_TEST_DIR+'garbage')
 168      # set os.name = posix, to prevent My Documents fallback on Windows
 169      monkeypatch.setattr(os, "name", "posix")
 170      pytest.raises(path.HomeDirError, path.get_home_dir, True)
 171  
 172  # Should we stub wreg fully so we can run the test on all platforms?
 173  @skip_if_not_win32
 174  @with_environment
 175  def test_get_home_dir_8(monkeypatch):
 176      """Using registry hack for 'My Documents', os=='nt'
 177  
 178      HOMESHARE, HOMEDRIVE, HOMEPATH, USERPROFILE and others are missing.
 179      """
 180      monkeypatch.setattr(os, "name", "nt")
 181      # Remove from stub environment all keys that may be set
 182      for key in ['HOME', 'HOMESHARE', 'HOMEDRIVE', 'HOMEPATH', 'USERPROFILE']:
 183          env.pop(key, None)
 184  
 185      class key:
 186          def __enter__(self):
 187              pass
 188          def Close(self):
 189              pass
 190          def __exit__(*args, **kwargs):
 191              pass
 192  
 193      with patch.object(wreg, 'OpenKey', return_value=key()), \
 194           patch.object(wreg, 'QueryValueEx', return_value=[abspath(HOME_TEST_DIR)]):
 195          home_dir = path.get_home_dir()
 196      assert home_dir == abspath(HOME_TEST_DIR)
 197  
 198  @with_environment
 199  def test_get_xdg_dir_0(monkeypatch):
 200      """test_get_xdg_dir_0, check xdg_dir"""
 201      monkeypatch.setattr(path, "_writable_dir", lambda path: True)
 202      monkeypatch.setattr(path, "get_home_dir", lambda: "somewhere")
 203      monkeypatch.setattr(os, "name", "posix")
 204      monkeypatch.setattr(sys, "platform", "linux2")
 205      env.pop('IPYTHON_DIR', None)
 206      env.pop('IPYTHONDIR', None)
 207      env.pop('XDG_CONFIG_HOME', None)
 208  
 209      assert path.get_xdg_dir() == os.path.join("somewhere", ".config")
 210  
 211  
 212  @with_environment
 213  def test_get_xdg_dir_1(monkeypatch):
 214      """test_get_xdg_dir_1, check nonexistent xdg_dir"""
 215      monkeypatch.setattr(path, "get_home_dir", lambda: HOME_TEST_DIR)
 216      monkeypatch.setattr(os, "name", "posix")
 217      monkeypatch.setattr(sys, "platform", "linux2")
 218      env.pop("IPYTHON_DIR", None)
 219      env.pop("IPYTHONDIR", None)
 220      env.pop("XDG_CONFIG_HOME", None)
 221      assert path.get_xdg_dir() is None
 222  
 223  @with_environment
 224  def test_get_xdg_dir_2(monkeypatch):
 225      """test_get_xdg_dir_2, check xdg_dir default to ~/.config"""
 226      monkeypatch.setattr(path, "get_home_dir", lambda: HOME_TEST_DIR)
 227      monkeypatch.setattr(os, "name", "posix")
 228      monkeypatch.setattr(sys, "platform", "linux2")
 229      env.pop("IPYTHON_DIR", None)
 230      env.pop("IPYTHONDIR", None)
 231      env.pop("XDG_CONFIG_HOME", None)
 232      cfgdir = os.path.join(path.get_home_dir(), ".config")
 233      if not os.path.exists(cfgdir):
 234          os.makedirs(cfgdir)
 235  
 236      assert path.get_xdg_dir() == cfgdir
 237  
 238  @with_environment
 239  def test_get_xdg_dir_3(monkeypatch):
 240      """test_get_xdg_dir_3, check xdg_dir not used on non-posix systems"""
 241      monkeypatch.setattr(path, "get_home_dir", lambda: HOME_TEST_DIR)
 242      monkeypatch.setattr(os, "name", "nt")
 243      monkeypatch.setattr(sys, "platform", "win32")
 244      env.pop("IPYTHON_DIR", None)
 245      env.pop("IPYTHONDIR", None)
 246      env.pop("XDG_CONFIG_HOME", None)
 247      cfgdir = os.path.join(path.get_home_dir(), ".config")
 248      os.makedirs(cfgdir, exist_ok=True)
 249  
 250      assert path.get_xdg_dir() is None
 251  
 252  def test_filefind():
 253      """Various tests for filefind"""
 254      f = tempfile.NamedTemporaryFile()
 255      # print('fname:',f.name)
 256      alt_dirs = paths.get_ipython_dir()
 257      t = path.filefind(f.name, alt_dirs)
 258      # print('found:',t)
 259  
 260  
 261  @dec.skip_if_not_win32
 262  def test_get_long_path_name_win32():
 263      with TemporaryDirectory() as tmpdir:
 264  
 265          # Make a long path. Expands the path of tmpdir prematurely as it may already have a long
 266          # path component, so ensure we include the long form of it
 267          long_path = os.path.join(path.get_long_path_name(tmpdir), 'this is my long path name')
 268          os.makedirs(long_path)
 269  
 270          # Test to see if the short path evaluates correctly.
 271          short_path = os.path.join(tmpdir, 'THISIS~1')
 272          evaluated_path = path.get_long_path_name(short_path)
 273          assert evaluated_path.lower() == long_path.lower()
 274  
 275  
 276  @dec.skip_win32
 277  def test_get_long_path_name():
 278      p = path.get_long_path_name("/usr/local")
 279      assert p == "/usr/local"
 280  
 281  
 282  @dec.skip_win32  # can't create not-user-writable dir on win
 283  @with_environment
 284  def test_not_writable_ipdir():
 285      tmpdir = tempfile.mkdtemp()
 286      os.name = "posix"
 287      env.pop("IPYTHON_DIR", None)
 288      env.pop("IPYTHONDIR", None)
 289      env.pop("XDG_CONFIG_HOME", None)
 290      env["HOME"] = tmpdir
 291      ipdir = os.path.join(tmpdir, ".ipython")
 292      os.mkdir(ipdir, 0o555)
 293      try:
 294          open(os.path.join(ipdir, "_foo_"), "w", encoding="utf-8").close()
 295      except IOError:
 296          pass
 297      else:
 298          # I can still write to an unwritable dir,
 299          # assume I'm root and skip the test
 300          pytest.skip("I can't create directories that I can't write to")
 301  
 302      with pytest.warns(UserWarning, match="is not a writable location"):
 303          ipdir = paths.get_ipython_dir()
 304      env.pop("IPYTHON_DIR", None)
 305  
 306  
 307  @with_environment
 308  def test_get_py_filename():
 309      os.chdir(TMP_TEST_DIR)
 310      with make_tempfile("foo.py"):
 311          assert path.get_py_filename("foo.py") == "foo.py"
 312          assert path.get_py_filename("foo") == "foo.py"
 313      with make_tempfile("foo"):
 314          assert path.get_py_filename("foo") == "foo"
 315          pytest.raises(IOError, path.get_py_filename, "foo.py")
 316      pytest.raises(IOError, path.get_py_filename, "foo")
 317      pytest.raises(IOError, path.get_py_filename, "foo.py")
 318      true_fn = "foo with spaces.py"
 319      with make_tempfile(true_fn):
 320          assert path.get_py_filename("foo with spaces") == true_fn
 321          assert path.get_py_filename("foo with spaces.py") == true_fn
 322          pytest.raises(IOError, path.get_py_filename, '"foo with spaces.py"')
 323          pytest.raises(IOError, path.get_py_filename, "'foo with spaces.py'")
 324  
 325  @onlyif_unicode_paths
 326  def test_unicode_in_filename():
 327      """When a file doesn't exist, the exception raised should be safe to call
 328      str() on - i.e. in Python 2 it must only have ASCII characters.
 329  
 330      https://github.com/ipython/ipython/issues/875
 331      """
 332      try:
 333          # these calls should not throw unicode encode exceptions
 334          path.get_py_filename('fooéè.py')
 335      except IOError as ex:
 336          str(ex)
 337  
 338  
 339  class TestShellGlob(unittest.TestCase):
 340  
 341      @classmethod
 342      def setUpClass(cls):
 343          cls.filenames_start_with_a = ['a0', 'a1', 'a2']
 344          cls.filenames_end_with_b = ['0b', '1b', '2b']
 345          cls.filenames = cls.filenames_start_with_a + cls.filenames_end_with_b
 346          cls.tempdir = TemporaryDirectory()
 347          td = cls.tempdir.name
 348  
 349          with cls.in_tempdir():
 350              # Create empty files
 351              for fname in cls.filenames:
 352                  open(os.path.join(td, fname), "w", encoding="utf-8").close()
 353  
 354      @classmethod
 355      def tearDownClass(cls):
 356          cls.tempdir.cleanup()
 357  
 358      @classmethod
 359      @contextmanager
 360      def in_tempdir(cls):
 361          save = os.getcwd()
 362          try:
 363              os.chdir(cls.tempdir.name)
 364              yield
 365          finally:
 366              os.chdir(save)
 367  
 368      def check_match(self, patterns, matches):
 369          with self.in_tempdir():
 370              # glob returns unordered list. that's why sorted is required.
 371              assert sorted(path.shellglob(patterns)) == sorted(matches)
 372  
 373      def common_cases(self):
 374          return [
 375              (['*'], self.filenames),
 376              (['a*'], self.filenames_start_with_a),
 377              (['*c'], ['*c']),
 378              (['*', 'a*', '*b', '*c'], self.filenames
 379                                        + self.filenames_start_with_a
 380                                        + self.filenames_end_with_b
 381                                        + ['*c']),
 382              (['a[012]'], self.filenames_start_with_a),
 383          ]
 384  
 385      @skip_win32
 386      def test_match_posix(self):
 387          for (patterns, matches) in self.common_cases() + [
 388                  ([r'\*'], ['*']),
 389                  ([r'a\*', 'a*'], ['a*'] + self.filenames_start_with_a),
 390                  ([r'a\[012]'], ['a[012]']),
 391                  ]:
 392              self.check_match(patterns, matches)
 393  
 394      @skip_if_not_win32
 395      def test_match_windows(self):
 396          for (patterns, matches) in self.common_cases() + [
 397                  # In windows, backslash is interpreted as path
 398                  # separator.  Therefore, you can't escape glob
 399                  # using it.
 400                  ([r'a\*', 'a*'], [r'a\*'] + self.filenames_start_with_a),
 401                  ([r'a\[012]'], [r'a\[012]']),
 402                  ]:
 403              self.check_match(patterns, matches)
 404  
 405  
 406  @pytest.mark.parametrize(
 407      "globstr, unescaped_globstr",
 408      [
 409          (r"\*\[\!\]\?", "*[!]?"),
 410          (r"\\*", r"\*"),
 411          (r"\\\*", r"\*"),
 412          (r"\\a", r"\a"),
 413          (r"\a", r"\a"),
 414      ],
 415  )
 416  def test_unescape_glob(globstr, unescaped_globstr):
 417      assert path.unescape_glob(globstr) == unescaped_globstr
 418  
 419  
 420  @onlyif_unicode_paths
 421  def test_ensure_dir_exists():
 422      with TemporaryDirectory() as td:
 423          d = os.path.join(td, '∂ir')
 424          path.ensure_dir_exists(d) # create it
 425          assert os.path.isdir(d)
 426          path.ensure_dir_exists(d)  # no-op
 427          f = os.path.join(td, "ƒile")
 428          open(f, "w", encoding="utf-8").close()  # touch
 429          with pytest.raises(IOError):
 430              path.ensure_dir_exists(f)
 431  
 432  class TestLinkOrCopy(unittest.TestCase):
 433      def setUp(self):
 434          self.tempdir = TemporaryDirectory()
 435          self.src = self.dst("src")
 436          with open(self.src, "w", encoding="utf-8") as f:
 437              f.write("Hello, world!")
 438  
 439      def tearDown(self):
 440          self.tempdir.cleanup()
 441  
 442      def dst(self, *args):
 443          return os.path.join(self.tempdir.name, *args)
 444  
 445      def assert_inode_not_equal(self, a, b):
 446          assert (
 447              os.stat(a).st_ino != os.stat(b).st_ino
 448          ), "%r and %r do reference the same indoes" % (a, b)
 449  
 450      def assert_inode_equal(self, a, b):
 451          assert (
 452              os.stat(a).st_ino == os.stat(b).st_ino
 453          ), "%r and %r do not reference the same indoes" % (a, b)
 454  
 455      def assert_content_equal(self, a, b):
 456          with open(a, "rb") as a_f:
 457              with open(b, "rb") as b_f:
 458                  assert a_f.read() == b_f.read()
 459  
 460      @skip_win32
 461      def test_link_successful(self):
 462          dst = self.dst("target")
 463          path.link_or_copy(self.src, dst)
 464          self.assert_inode_equal(self.src, dst)
 465  
 466      @skip_win32
 467      def test_link_into_dir(self):
 468          dst = self.dst("some_dir")
 469          os.mkdir(dst)
 470          path.link_or_copy(self.src, dst)
 471          expected_dst = self.dst("some_dir", os.path.basename(self.src))
 472          self.assert_inode_equal(self.src, expected_dst)
 473  
 474      @skip_win32
 475      def test_target_exists(self):
 476          dst = self.dst("target")
 477          open(dst, "w", encoding="utf-8").close()
 478          path.link_or_copy(self.src, dst)
 479          self.assert_inode_equal(self.src, dst)
 480  
 481      @skip_win32
 482      def test_no_link(self):
 483          real_link = os.link
 484          try:
 485              del os.link
 486              dst = self.dst("target")
 487              path.link_or_copy(self.src, dst)
 488              self.assert_content_equal(self.src, dst)
 489              self.assert_inode_not_equal(self.src, dst)
 490          finally:
 491              os.link = real_link
 492  
 493      @skip_if_not_win32
 494      def test_windows(self):
 495          dst = self.dst("target")
 496          path.link_or_copy(self.src, dst)
 497          self.assert_content_equal(self.src, dst)
 498  
 499      def test_link_twice(self):
 500          # Linking the same file twice shouldn't leave duplicates around.
 501          # See https://github.com/ipython/ipython/issues/6450
 502          dst = self.dst('target')
 503          path.link_or_copy(self.src, dst)
 504          path.link_or_copy(self.src, dst)
 505          self.assert_inode_equal(self.src, dst)
 506          assert sorted(os.listdir(self.tempdir.name)) == ["src", "target"]
 507