test_path.py 16 KB

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