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