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