##// END OF EJS Templates
add skip_win32_py38 decorator, skip another test
Nicholas Bollweg -
Show More
@@ -1,378 +1,383 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Decorators for labeling test objects.
2 """Decorators for labeling test objects.
3
3
4 Decorators that merely return a modified version of the original function
4 Decorators that merely return a modified version of the original function
5 object are straightforward. Decorators that return a new function object need
5 object are straightforward. Decorators that return a new function object need
6 to use nose.tools.make_decorator(original_function)(decorator) in returning the
6 to use nose.tools.make_decorator(original_function)(decorator) in returning the
7 decorator, in order to preserve metadata such as function name, setup and
7 decorator, in order to preserve metadata such as function name, setup and
8 teardown functions and so on - see nose.tools for more information.
8 teardown functions and so on - see nose.tools for more information.
9
9
10 This module provides a set of useful decorators meant to be ready to use in
10 This module provides a set of useful decorators meant to be ready to use in
11 your own tests. See the bottom of the file for the ready-made ones, and if you
11 your own tests. See the bottom of the file for the ready-made ones, and if you
12 find yourself writing a new one that may be of generic use, add it here.
12 find yourself writing a new one that may be of generic use, add it here.
13
13
14 Included decorators:
14 Included decorators:
15
15
16
16
17 Lightweight testing that remains unittest-compatible.
17 Lightweight testing that remains unittest-compatible.
18
18
19 - An @as_unittest decorator can be used to tag any normal parameter-less
19 - An @as_unittest decorator can be used to tag any normal parameter-less
20 function as a unittest TestCase. Then, both nose and normal unittest will
20 function as a unittest TestCase. Then, both nose and normal unittest will
21 recognize it as such. This will make it easier to migrate away from Nose if
21 recognize it as such. This will make it easier to migrate away from Nose if
22 we ever need/want to while maintaining very lightweight tests.
22 we ever need/want to while maintaining very lightweight tests.
23
23
24 NOTE: This file contains IPython-specific decorators. Using the machinery in
24 NOTE: This file contains IPython-specific decorators. Using the machinery in
25 IPython.external.decorators, we import either numpy.testing.decorators if numpy is
25 IPython.external.decorators, we import either numpy.testing.decorators if numpy is
26 available, OR use equivalent code in IPython.external._decorators, which
26 available, OR use equivalent code in IPython.external._decorators, which
27 we've copied verbatim from numpy.
27 we've copied verbatim from numpy.
28
28
29 """
29 """
30
30
31 # Copyright (c) IPython Development Team.
31 # Copyright (c) IPython Development Team.
32 # Distributed under the terms of the Modified BSD License.
32 # Distributed under the terms of the Modified BSD License.
33
33
34 import os
34 import os
35 import shutil
35 import shutil
36 import sys
36 import sys
37 import tempfile
37 import tempfile
38 import unittest
38 import unittest
39 import warnings
39 import warnings
40 from importlib import import_module
40 from importlib import import_module
41
41
42 from decorator import decorator
42 from decorator import decorator
43
43
44 # Expose the unittest-driven decorators
44 # Expose the unittest-driven decorators
45 from .ipunittest import ipdoctest, ipdocstring
45 from .ipunittest import ipdoctest, ipdocstring
46
46
47 # Grab the numpy-specific decorators which we keep in a file that we
47 # Grab the numpy-specific decorators which we keep in a file that we
48 # occasionally update from upstream: decorators.py is a copy of
48 # occasionally update from upstream: decorators.py is a copy of
49 # numpy.testing.decorators, we expose all of it here.
49 # numpy.testing.decorators, we expose all of it here.
50 from IPython.external.decorators import knownfailureif
50 from IPython.external.decorators import knownfailureif
51
51
52 #-----------------------------------------------------------------------------
52 #-----------------------------------------------------------------------------
53 # Classes and functions
53 # Classes and functions
54 #-----------------------------------------------------------------------------
54 #-----------------------------------------------------------------------------
55
55
56 # Simple example of the basic idea
56 # Simple example of the basic idea
57 def as_unittest(func):
57 def as_unittest(func):
58 """Decorator to make a simple function into a normal test via unittest."""
58 """Decorator to make a simple function into a normal test via unittest."""
59 class Tester(unittest.TestCase):
59 class Tester(unittest.TestCase):
60 def test(self):
60 def test(self):
61 func()
61 func()
62
62
63 Tester.__name__ = func.__name__
63 Tester.__name__ = func.__name__
64
64
65 return Tester
65 return Tester
66
66
67 # Utility functions
67 # Utility functions
68
68
69 def apply_wrapper(wrapper, func):
69 def apply_wrapper(wrapper, func):
70 """Apply a wrapper to a function for decoration.
70 """Apply a wrapper to a function for decoration.
71
71
72 This mixes Michele Simionato's decorator tool with nose's make_decorator,
72 This mixes Michele Simionato's decorator tool with nose's make_decorator,
73 to apply a wrapper in a decorator so that all nose attributes, as well as
73 to apply a wrapper in a decorator so that all nose attributes, as well as
74 function signature and other properties, survive the decoration cleanly.
74 function signature and other properties, survive the decoration cleanly.
75 This will ensure that wrapped functions can still be well introspected via
75 This will ensure that wrapped functions can still be well introspected via
76 IPython, for example.
76 IPython, for example.
77 """
77 """
78 warnings.warn("The function `apply_wrapper` is deprecated since IPython 4.0",
78 warnings.warn("The function `apply_wrapper` is deprecated since IPython 4.0",
79 DeprecationWarning, stacklevel=2)
79 DeprecationWarning, stacklevel=2)
80 import nose.tools
80 import nose.tools
81
81
82 return decorator(wrapper,nose.tools.make_decorator(func)(wrapper))
82 return decorator(wrapper,nose.tools.make_decorator(func)(wrapper))
83
83
84
84
85 def make_label_dec(label, ds=None):
85 def make_label_dec(label, ds=None):
86 """Factory function to create a decorator that applies one or more labels.
86 """Factory function to create a decorator that applies one or more labels.
87
87
88 Parameters
88 Parameters
89 ----------
89 ----------
90 label : string or sequence
90 label : string or sequence
91 One or more labels that will be applied by the decorator to the functions
91 One or more labels that will be applied by the decorator to the functions
92 it decorates. Labels are attributes of the decorated function with their
92 it decorates. Labels are attributes of the decorated function with their
93 value set to True.
93 value set to True.
94
94
95 ds : string
95 ds : string
96 An optional docstring for the resulting decorator. If not given, a
96 An optional docstring for the resulting decorator. If not given, a
97 default docstring is auto-generated.
97 default docstring is auto-generated.
98
98
99 Returns
99 Returns
100 -------
100 -------
101 A decorator.
101 A decorator.
102
102
103 Examples
103 Examples
104 --------
104 --------
105
105
106 A simple labeling decorator:
106 A simple labeling decorator:
107
107
108 >>> slow = make_label_dec('slow')
108 >>> slow = make_label_dec('slow')
109 >>> slow.__doc__
109 >>> slow.__doc__
110 "Labels a test as 'slow'."
110 "Labels a test as 'slow'."
111
111
112 And one that uses multiple labels and a custom docstring:
112 And one that uses multiple labels and a custom docstring:
113
113
114 >>> rare = make_label_dec(['slow','hard'],
114 >>> rare = make_label_dec(['slow','hard'],
115 ... "Mix labels 'slow' and 'hard' for rare tests.")
115 ... "Mix labels 'slow' and 'hard' for rare tests.")
116 >>> rare.__doc__
116 >>> rare.__doc__
117 "Mix labels 'slow' and 'hard' for rare tests."
117 "Mix labels 'slow' and 'hard' for rare tests."
118
118
119 Now, let's test using this one:
119 Now, let's test using this one:
120 >>> @rare
120 >>> @rare
121 ... def f(): pass
121 ... def f(): pass
122 ...
122 ...
123 >>>
123 >>>
124 >>> f.slow
124 >>> f.slow
125 True
125 True
126 >>> f.hard
126 >>> f.hard
127 True
127 True
128 """
128 """
129
129
130 warnings.warn("The function `make_label_dec` is deprecated since IPython 4.0",
130 warnings.warn("The function `make_label_dec` is deprecated since IPython 4.0",
131 DeprecationWarning, stacklevel=2)
131 DeprecationWarning, stacklevel=2)
132 if isinstance(label, str):
132 if isinstance(label, str):
133 labels = [label]
133 labels = [label]
134 else:
134 else:
135 labels = label
135 labels = label
136
136
137 # Validate that the given label(s) are OK for use in setattr() by doing a
137 # Validate that the given label(s) are OK for use in setattr() by doing a
138 # dry run on a dummy function.
138 # dry run on a dummy function.
139 tmp = lambda : None
139 tmp = lambda : None
140 for label in labels:
140 for label in labels:
141 setattr(tmp,label,True)
141 setattr(tmp,label,True)
142
142
143 # This is the actual decorator we'll return
143 # This is the actual decorator we'll return
144 def decor(f):
144 def decor(f):
145 for label in labels:
145 for label in labels:
146 setattr(f,label,True)
146 setattr(f,label,True)
147 return f
147 return f
148
148
149 # Apply the user's docstring, or autogenerate a basic one
149 # Apply the user's docstring, or autogenerate a basic one
150 if ds is None:
150 if ds is None:
151 ds = "Labels a test as %r." % label
151 ds = "Labels a test as %r." % label
152 decor.__doc__ = ds
152 decor.__doc__ = ds
153
153
154 return decor
154 return decor
155
155
156
156
157 # Inspired by numpy's skipif, but uses the full apply_wrapper utility to
157 # Inspired by numpy's skipif, but uses the full apply_wrapper utility to
158 # preserve function metadata better and allows the skip condition to be a
158 # preserve function metadata better and allows the skip condition to be a
159 # callable.
159 # callable.
160 def skipif(skip_condition, msg=None):
160 def skipif(skip_condition, msg=None):
161 ''' Make function raise SkipTest exception if skip_condition is true
161 ''' Make function raise SkipTest exception if skip_condition is true
162
162
163 Parameters
163 Parameters
164 ----------
164 ----------
165
165
166 skip_condition : bool or callable
166 skip_condition : bool or callable
167 Flag to determine whether to skip test. If the condition is a
167 Flag to determine whether to skip test. If the condition is a
168 callable, it is used at runtime to dynamically make the decision. This
168 callable, it is used at runtime to dynamically make the decision. This
169 is useful for tests that may require costly imports, to delay the cost
169 is useful for tests that may require costly imports, to delay the cost
170 until the test suite is actually executed.
170 until the test suite is actually executed.
171 msg : string
171 msg : string
172 Message to give on raising a SkipTest exception.
172 Message to give on raising a SkipTest exception.
173
173
174 Returns
174 Returns
175 -------
175 -------
176 decorator : function
176 decorator : function
177 Decorator, which, when applied to a function, causes SkipTest
177 Decorator, which, when applied to a function, causes SkipTest
178 to be raised when the skip_condition was True, and the function
178 to be raised when the skip_condition was True, and the function
179 to be called normally otherwise.
179 to be called normally otherwise.
180
180
181 Notes
181 Notes
182 -----
182 -----
183 You will see from the code that we had to further decorate the
183 You will see from the code that we had to further decorate the
184 decorator with the nose.tools.make_decorator function in order to
184 decorator with the nose.tools.make_decorator function in order to
185 transmit function name, and various other metadata.
185 transmit function name, and various other metadata.
186 '''
186 '''
187
187
188 def skip_decorator(f):
188 def skip_decorator(f):
189 # Local import to avoid a hard nose dependency and only incur the
189 # Local import to avoid a hard nose dependency and only incur the
190 # import time overhead at actual test-time.
190 # import time overhead at actual test-time.
191 import nose
191 import nose
192
192
193 # Allow for both boolean or callable skip conditions.
193 # Allow for both boolean or callable skip conditions.
194 if callable(skip_condition):
194 if callable(skip_condition):
195 skip_val = skip_condition
195 skip_val = skip_condition
196 else:
196 else:
197 skip_val = lambda : skip_condition
197 skip_val = lambda : skip_condition
198
198
199 def get_msg(func,msg=None):
199 def get_msg(func,msg=None):
200 """Skip message with information about function being skipped."""
200 """Skip message with information about function being skipped."""
201 if msg is None: out = 'Test skipped due to test condition.'
201 if msg is None: out = 'Test skipped due to test condition.'
202 else: out = msg
202 else: out = msg
203 return "Skipping test: %s. %s" % (func.__name__,out)
203 return "Skipping test: %s. %s" % (func.__name__,out)
204
204
205 # We need to define *two* skippers because Python doesn't allow both
205 # We need to define *two* skippers because Python doesn't allow both
206 # return with value and yield inside the same function.
206 # return with value and yield inside the same function.
207 def skipper_func(*args, **kwargs):
207 def skipper_func(*args, **kwargs):
208 """Skipper for normal test functions."""
208 """Skipper for normal test functions."""
209 if skip_val():
209 if skip_val():
210 raise nose.SkipTest(get_msg(f,msg))
210 raise nose.SkipTest(get_msg(f,msg))
211 else:
211 else:
212 return f(*args, **kwargs)
212 return f(*args, **kwargs)
213
213
214 def skipper_gen(*args, **kwargs):
214 def skipper_gen(*args, **kwargs):
215 """Skipper for test generators."""
215 """Skipper for test generators."""
216 if skip_val():
216 if skip_val():
217 raise nose.SkipTest(get_msg(f,msg))
217 raise nose.SkipTest(get_msg(f,msg))
218 else:
218 else:
219 for x in f(*args, **kwargs):
219 for x in f(*args, **kwargs):
220 yield x
220 yield x
221
221
222 # Choose the right skipper to use when building the actual generator.
222 # Choose the right skipper to use when building the actual generator.
223 if nose.util.isgenerator(f):
223 if nose.util.isgenerator(f):
224 skipper = skipper_gen
224 skipper = skipper_gen
225 else:
225 else:
226 skipper = skipper_func
226 skipper = skipper_func
227
227
228 return nose.tools.make_decorator(f)(skipper)
228 return nose.tools.make_decorator(f)(skipper)
229
229
230 return skip_decorator
230 return skip_decorator
231
231
232 # A version with the condition set to true, common case just to attach a message
232 # A version with the condition set to true, common case just to attach a message
233 # to a skip decorator
233 # to a skip decorator
234 def skip(msg=None):
234 def skip(msg=None):
235 """Decorator factory - mark a test function for skipping from test suite.
235 """Decorator factory - mark a test function for skipping from test suite.
236
236
237 Parameters
237 Parameters
238 ----------
238 ----------
239 msg : string
239 msg : string
240 Optional message to be added.
240 Optional message to be added.
241
241
242 Returns
242 Returns
243 -------
243 -------
244 decorator : function
244 decorator : function
245 Decorator, which, when applied to a function, causes SkipTest
245 Decorator, which, when applied to a function, causes SkipTest
246 to be raised, with the optional message added.
246 to be raised, with the optional message added.
247 """
247 """
248 if msg and not isinstance(msg, str):
248 if msg and not isinstance(msg, str):
249 raise ValueError('invalid object passed to `@skip` decorator, did you '
249 raise ValueError('invalid object passed to `@skip` decorator, did you '
250 'meant `@skip()` with brackets ?')
250 'meant `@skip()` with brackets ?')
251 return skipif(True, msg)
251 return skipif(True, msg)
252
252
253
253
254 def onlyif(condition, msg):
254 def onlyif(condition, msg):
255 """The reverse from skipif, see skipif for details."""
255 """The reverse from skipif, see skipif for details."""
256
256
257 if callable(condition):
257 if callable(condition):
258 skip_condition = lambda : not condition()
258 skip_condition = lambda : not condition()
259 else:
259 else:
260 skip_condition = lambda : not condition
260 skip_condition = lambda : not condition
261
261
262 return skipif(skip_condition, msg)
262 return skipif(skip_condition, msg)
263
263
264 #-----------------------------------------------------------------------------
264 #-----------------------------------------------------------------------------
265 # Utility functions for decorators
265 # Utility functions for decorators
266 def module_not_available(module):
266 def module_not_available(module):
267 """Can module be imported? Returns true if module does NOT import.
267 """Can module be imported? Returns true if module does NOT import.
268
268
269 This is used to make a decorator to skip tests that require module to be
269 This is used to make a decorator to skip tests that require module to be
270 available, but delay the 'import numpy' to test execution time.
270 available, but delay the 'import numpy' to test execution time.
271 """
271 """
272 try:
272 try:
273 mod = import_module(module)
273 mod = import_module(module)
274 mod_not_avail = False
274 mod_not_avail = False
275 except ImportError:
275 except ImportError:
276 mod_not_avail = True
276 mod_not_avail = True
277
277
278 return mod_not_avail
278 return mod_not_avail
279
279
280
280
281 def decorated_dummy(dec, name):
281 def decorated_dummy(dec, name):
282 """Return a dummy function decorated with dec, with the given name.
282 """Return a dummy function decorated with dec, with the given name.
283
283
284 Examples
284 Examples
285 --------
285 --------
286 import IPython.testing.decorators as dec
286 import IPython.testing.decorators as dec
287 setup = dec.decorated_dummy(dec.skip_if_no_x11, __name__)
287 setup = dec.decorated_dummy(dec.skip_if_no_x11, __name__)
288 """
288 """
289 warnings.warn("The function `decorated_dummy` is deprecated since IPython 4.0",
289 warnings.warn("The function `decorated_dummy` is deprecated since IPython 4.0",
290 DeprecationWarning, stacklevel=2)
290 DeprecationWarning, stacklevel=2)
291 dummy = lambda: None
291 dummy = lambda: None
292 dummy.__name__ = name
292 dummy.__name__ = name
293 return dec(dummy)
293 return dec(dummy)
294
294
295 #-----------------------------------------------------------------------------
295 #-----------------------------------------------------------------------------
296 # Decorators for public use
296 # Decorators for public use
297
297
298 # Decorators to skip certain tests on specific platforms.
298 # Decorators to skip certain tests on specific platforms.
299 skip_win32 = skipif(sys.platform == 'win32',
299 skip_win32 = skipif(sys.platform == 'win32',
300 "This test does not run under Windows")
300 "This test does not run under Windows")
301 skip_linux = skipif(sys.platform.startswith('linux'),
301 skip_linux = skipif(sys.platform.startswith('linux'),
302 "This test does not run under Linux")
302 "This test does not run under Linux")
303 skip_osx = skipif(sys.platform == 'darwin',"This test does not run under OS X")
303 skip_osx = skipif(sys.platform == 'darwin',"This test does not run under OS X")
304
304
305
305
306 # Decorators to skip tests if not on specific platforms.
306 # Decorators to skip tests if not on specific platforms.
307 skip_if_not_win32 = skipif(sys.platform != 'win32',
307 skip_if_not_win32 = skipif(sys.platform != 'win32',
308 "This test only runs under Windows")
308 "This test only runs under Windows")
309 skip_if_not_linux = skipif(not sys.platform.startswith('linux'),
309 skip_if_not_linux = skipif(not sys.platform.startswith('linux'),
310 "This test only runs under Linux")
310 "This test only runs under Linux")
311 skip_if_not_osx = skipif(sys.platform != 'darwin',
311 skip_if_not_osx = skipif(sys.platform != 'darwin',
312 "This test only runs under OSX")
312 "This test only runs under OSX")
313
313
314
314
315 _x11_skip_cond = (sys.platform not in ('darwin', 'win32') and
315 _x11_skip_cond = (sys.platform not in ('darwin', 'win32') and
316 os.environ.get('DISPLAY', '') == '')
316 os.environ.get('DISPLAY', '') == '')
317 _x11_skip_msg = "Skipped under *nix when X11/XOrg not available"
317 _x11_skip_msg = "Skipped under *nix when X11/XOrg not available"
318
318
319 skip_if_no_x11 = skipif(_x11_skip_cond, _x11_skip_msg)
319 skip_if_no_x11 = skipif(_x11_skip_cond, _x11_skip_msg)
320
320
321
322 # Decorators to skip certain tests on specific platform/python combinations
323 skip_win32_py38 = skipif(sys.version_info > (3,8) and os.name == 'nt')
324
325
321 # not a decorator itself, returns a dummy function to be used as setup
326 # not a decorator itself, returns a dummy function to be used as setup
322 def skip_file_no_x11(name):
327 def skip_file_no_x11(name):
323 warnings.warn("The function `skip_file_no_x11` is deprecated since IPython 4.0",
328 warnings.warn("The function `skip_file_no_x11` is deprecated since IPython 4.0",
324 DeprecationWarning, stacklevel=2)
329 DeprecationWarning, stacklevel=2)
325 return decorated_dummy(skip_if_no_x11, name) if _x11_skip_cond else None
330 return decorated_dummy(skip_if_no_x11, name) if _x11_skip_cond else None
326
331
327 # Other skip decorators
332 # Other skip decorators
328
333
329 # generic skip without module
334 # generic skip without module
330 skip_without = lambda mod: skipif(module_not_available(mod), "This test requires %s" % mod)
335 skip_without = lambda mod: skipif(module_not_available(mod), "This test requires %s" % mod)
331
336
332 skipif_not_numpy = skip_without('numpy')
337 skipif_not_numpy = skip_without('numpy')
333
338
334 skipif_not_matplotlib = skip_without('matplotlib')
339 skipif_not_matplotlib = skip_without('matplotlib')
335
340
336 skipif_not_sympy = skip_without('sympy')
341 skipif_not_sympy = skip_without('sympy')
337
342
338 skip_known_failure = knownfailureif(True,'This test is known to fail')
343 skip_known_failure = knownfailureif(True,'This test is known to fail')
339
344
340 # A null 'decorator', useful to make more readable code that needs to pick
345 # A null 'decorator', useful to make more readable code that needs to pick
341 # between different decorators based on OS or other conditions
346 # between different decorators based on OS or other conditions
342 null_deco = lambda f: f
347 null_deco = lambda f: f
343
348
344 # Some tests only run where we can use unicode paths. Note that we can't just
349 # Some tests only run where we can use unicode paths. Note that we can't just
345 # check os.path.supports_unicode_filenames, which is always False on Linux.
350 # check os.path.supports_unicode_filenames, which is always False on Linux.
346 try:
351 try:
347 f = tempfile.NamedTemporaryFile(prefix=u"tmp€")
352 f = tempfile.NamedTemporaryFile(prefix=u"tmp€")
348 except UnicodeEncodeError:
353 except UnicodeEncodeError:
349 unicode_paths = False
354 unicode_paths = False
350 else:
355 else:
351 unicode_paths = True
356 unicode_paths = True
352 f.close()
357 f.close()
353
358
354 onlyif_unicode_paths = onlyif(unicode_paths, ("This test is only applicable "
359 onlyif_unicode_paths = onlyif(unicode_paths, ("This test is only applicable "
355 "where we can use unicode in filenames."))
360 "where we can use unicode in filenames."))
356
361
357
362
358 def onlyif_cmds_exist(*commands):
363 def onlyif_cmds_exist(*commands):
359 """
364 """
360 Decorator to skip test when at least one of `commands` is not found.
365 Decorator to skip test when at least one of `commands` is not found.
361 """
366 """
362 for cmd in commands:
367 for cmd in commands:
363 if not shutil.which(cmd):
368 if not shutil.which(cmd):
364 return skip("This test runs only if command '{0}' "
369 return skip("This test runs only if command '{0}' "
365 "is installed".format(cmd))
370 "is installed".format(cmd))
366 return null_deco
371 return null_deco
367
372
368 def onlyif_any_cmd_exists(*commands):
373 def onlyif_any_cmd_exists(*commands):
369 """
374 """
370 Decorator to skip test unless at least one of `commands` is found.
375 Decorator to skip test unless at least one of `commands` is found.
371 """
376 """
372 warnings.warn("The function `onlyif_any_cmd_exists` is deprecated since IPython 4.0",
377 warnings.warn("The function `onlyif_any_cmd_exists` is deprecated since IPython 4.0",
373 DeprecationWarning, stacklevel=2)
378 DeprecationWarning, stacklevel=2)
374 for cmd in commands:
379 for cmd in commands:
375 if shutil.which(cmd):
380 if shutil.which(cmd):
376 return null_deco
381 return null_deco
377 return skip("This test runs only if one of the commands {0} "
382 return skip("This test runs only if one of the commands {0} "
378 "is installed".format(commands))
383 "is installed".format(commands))
@@ -1,485 +1,488 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """Tests for IPython.utils.path.py"""
2 """Tests for IPython.utils.path.py"""
3
3
4 # Copyright (c) IPython Development Team.
4 # Copyright (c) IPython Development Team.
5 # Distributed under the terms of the Modified BSD License.
5 # Distributed under the terms of the Modified BSD License.
6
6
7 import os
7 import os
8 import shutil
8 import shutil
9 import sys
9 import sys
10 import tempfile
10 import tempfile
11 import unittest
11 import unittest
12 from contextlib import contextmanager
12 from contextlib import contextmanager
13 from unittest.mock import patch
13 from unittest.mock import patch
14 from os.path import join, abspath
14 from os.path import join, abspath
15 from imp import reload
15 from imp import reload
16
16
17 from nose import SkipTest, with_setup
17 from nose import SkipTest, with_setup
18 import nose.tools as nt
18 import nose.tools as nt
19
19
20 import IPython
20 import IPython
21 from IPython import paths
21 from IPython import paths
22 from IPython.testing import decorators as dec
22 from IPython.testing import decorators as dec
23 from IPython.testing.decorators import (skip_if_not_win32, skip_win32,
23 from IPython.testing.decorators import (skip_if_not_win32, skip_win32,
24 onlyif_unicode_paths,)
24 onlyif_unicode_paths, skipif,
25 skip_win32_py38,)
25 from IPython.testing.tools import make_tempfile, AssertPrints
26 from IPython.testing.tools import make_tempfile, AssertPrints
26 from IPython.utils import path
27 from IPython.utils import path
27 from IPython.utils.tempdir import TemporaryDirectory
28 from IPython.utils.tempdir import TemporaryDirectory
28
29
30
29 # Platform-dependent imports
31 # Platform-dependent imports
30 try:
32 try:
31 import winreg as wreg
33 import winreg as wreg
32 except ImportError:
34 except ImportError:
33 #Fake _winreg module on non-windows platforms
35 #Fake _winreg module on non-windows platforms
34 import types
36 import types
35 wr_name = "winreg"
37 wr_name = "winreg"
36 sys.modules[wr_name] = types.ModuleType(wr_name)
38 sys.modules[wr_name] = types.ModuleType(wr_name)
37 try:
39 try:
38 import winreg as wreg
40 import winreg as wreg
39 except ImportError:
41 except ImportError:
40 import _winreg as wreg
42 import _winreg as wreg
41 #Add entries that needs to be stubbed by the testing code
43 #Add entries that needs to be stubbed by the testing code
42 (wreg.OpenKey, wreg.QueryValueEx,) = (None, None)
44 (wreg.OpenKey, wreg.QueryValueEx,) = (None, None)
43
45
44 #-----------------------------------------------------------------------------
46 #-----------------------------------------------------------------------------
45 # Globals
47 # Globals
46 #-----------------------------------------------------------------------------
48 #-----------------------------------------------------------------------------
47 env = os.environ
49 env = os.environ
48 TMP_TEST_DIR = tempfile.mkdtemp()
50 TMP_TEST_DIR = tempfile.mkdtemp()
49 HOME_TEST_DIR = join(TMP_TEST_DIR, "home_test_dir")
51 HOME_TEST_DIR = join(TMP_TEST_DIR, "home_test_dir")
50 #
52 #
51 # Setup/teardown functions/decorators
53 # Setup/teardown functions/decorators
52 #
54 #
53
55
54 def setup_module():
56 def setup_module():
55 """Setup testenvironment for the module:
57 """Setup testenvironment for the module:
56
58
57 - Adds dummy home dir tree
59 - Adds dummy home dir tree
58 """
60 """
59 # Do not mask exceptions here. In particular, catching WindowsError is a
61 # Do not mask exceptions here. In particular, catching WindowsError is a
60 # problem because that exception is only defined on Windows...
62 # problem because that exception is only defined on Windows...
61 os.makedirs(os.path.join(HOME_TEST_DIR, 'ipython'))
63 os.makedirs(os.path.join(HOME_TEST_DIR, 'ipython'))
62
64
63
65
64 def teardown_module():
66 def teardown_module():
65 """Teardown testenvironment for the module:
67 """Teardown testenvironment for the module:
66
68
67 - Remove dummy home dir tree
69 - Remove dummy home dir tree
68 """
70 """
69 # Note: we remove the parent test dir, which is the root of all test
71 # Note: we remove the parent test dir, which is the root of all test
70 # subdirs we may have created. Use shutil instead of os.removedirs, so
72 # subdirs we may have created. Use shutil instead of os.removedirs, so
71 # that non-empty directories are all recursively removed.
73 # that non-empty directories are all recursively removed.
72 shutil.rmtree(TMP_TEST_DIR)
74 shutil.rmtree(TMP_TEST_DIR)
73
75
74
76
75 def setup_environment():
77 def setup_environment():
76 """Setup testenvironment for some functions that are tested
78 """Setup testenvironment for some functions that are tested
77 in this module. In particular this functions stores attributes
79 in this module. In particular this functions stores attributes
78 and other things that we need to stub in some test functions.
80 and other things that we need to stub in some test functions.
79 This needs to be done on a function level and not module level because
81 This needs to be done on a function level and not module level because
80 each testfunction needs a pristine environment.
82 each testfunction needs a pristine environment.
81 """
83 """
82 global oldstuff, platformstuff
84 global oldstuff, platformstuff
83 oldstuff = (env.copy(), os.name, sys.platform, path.get_home_dir, IPython.__file__, os.getcwd())
85 oldstuff = (env.copy(), os.name, sys.platform, path.get_home_dir, IPython.__file__, os.getcwd())
84
86
85 def teardown_environment():
87 def teardown_environment():
86 """Restore things that were remembered by the setup_environment function
88 """Restore things that were remembered by the setup_environment function
87 """
89 """
88 (oldenv, os.name, sys.platform, path.get_home_dir, IPython.__file__, old_wd) = oldstuff
90 (oldenv, os.name, sys.platform, path.get_home_dir, IPython.__file__, old_wd) = oldstuff
89 os.chdir(old_wd)
91 os.chdir(old_wd)
90 reload(path)
92 reload(path)
91
93
92 for key in list(env):
94 for key in list(env):
93 if key not in oldenv:
95 if key not in oldenv:
94 del env[key]
96 del env[key]
95 env.update(oldenv)
97 env.update(oldenv)
96 if hasattr(sys, 'frozen'):
98 if hasattr(sys, 'frozen'):
97 del sys.frozen
99 del sys.frozen
98
100
99 # Build decorator that uses the setup_environment/setup_environment
101 # Build decorator that uses the setup_environment/setup_environment
100 with_environment = with_setup(setup_environment, teardown_environment)
102 with_environment = with_setup(setup_environment, teardown_environment)
101
103
102 @skip_if_not_win32
104 @skip_if_not_win32
103 @with_environment
105 @with_environment
104 def test_get_home_dir_1():
106 def test_get_home_dir_1():
105 """Testcase for py2exe logic, un-compressed lib
107 """Testcase for py2exe logic, un-compressed lib
106 """
108 """
107 unfrozen = path.get_home_dir()
109 unfrozen = path.get_home_dir()
108 sys.frozen = True
110 sys.frozen = True
109
111
110 #fake filename for IPython.__init__
112 #fake filename for IPython.__init__
111 IPython.__file__ = abspath(join(HOME_TEST_DIR, "Lib/IPython/__init__.py"))
113 IPython.__file__ = abspath(join(HOME_TEST_DIR, "Lib/IPython/__init__.py"))
112
114
113 home_dir = path.get_home_dir()
115 home_dir = path.get_home_dir()
114 nt.assert_equal(home_dir, unfrozen)
116 nt.assert_equal(home_dir, unfrozen)
115
117
116
118
117 @skip_if_not_win32
119 @skip_if_not_win32
118 @with_environment
120 @with_environment
119 def test_get_home_dir_2():
121 def test_get_home_dir_2():
120 """Testcase for py2exe logic, compressed lib
122 """Testcase for py2exe logic, compressed lib
121 """
123 """
122 unfrozen = path.get_home_dir()
124 unfrozen = path.get_home_dir()
123 sys.frozen = True
125 sys.frozen = True
124 #fake filename for IPython.__init__
126 #fake filename for IPython.__init__
125 IPython.__file__ = abspath(join(HOME_TEST_DIR, "Library.zip/IPython/__init__.py")).lower()
127 IPython.__file__ = abspath(join(HOME_TEST_DIR, "Library.zip/IPython/__init__.py")).lower()
126
128
127 home_dir = path.get_home_dir(True)
129 home_dir = path.get_home_dir(True)
128 nt.assert_equal(home_dir, unfrozen)
130 nt.assert_equal(home_dir, unfrozen)
129
131
130
132
131 @skipif(sys.version_info > (3,8) and os.name == 'nt')
133 @skip_win32_py38
132 @with_environment
134 @with_environment
133 def test_get_home_dir_3():
135 def test_get_home_dir_3():
134 """get_home_dir() uses $HOME if set"""
136 """get_home_dir() uses $HOME if set"""
135 env["HOME"] = HOME_TEST_DIR
137 env["HOME"] = HOME_TEST_DIR
136 home_dir = path.get_home_dir(True)
138 home_dir = path.get_home_dir(True)
137 # get_home_dir expands symlinks
139 # get_home_dir expands symlinks
138 nt.assert_equal(home_dir, os.path.realpath(env["HOME"]))
140 nt.assert_equal(home_dir, os.path.realpath(env["HOME"]))
139
141
140
142
141 @with_environment
143 @with_environment
142 def test_get_home_dir_4():
144 def test_get_home_dir_4():
143 """get_home_dir() still works if $HOME is not set"""
145 """get_home_dir() still works if $HOME is not set"""
144
146
145 if 'HOME' in env: del env['HOME']
147 if 'HOME' in env: del env['HOME']
146 # this should still succeed, but we don't care what the answer is
148 # this should still succeed, but we don't care what the answer is
147 home = path.get_home_dir(False)
149 home = path.get_home_dir(False)
148
150
151 @skip_win32_py38
149 @with_environment
152 @with_environment
150 def test_get_home_dir_5():
153 def test_get_home_dir_5():
151 """raise HomeDirError if $HOME is specified, but not a writable dir"""
154 """raise HomeDirError if $HOME is specified, but not a writable dir"""
152 env['HOME'] = abspath(HOME_TEST_DIR+'garbage')
155 env['HOME'] = abspath(HOME_TEST_DIR+'garbage')
153 # set os.name = posix, to prevent My Documents fallback on Windows
156 # set os.name = posix, to prevent My Documents fallback on Windows
154 os.name = 'posix'
157 os.name = 'posix'
155 nt.assert_raises(path.HomeDirError, path.get_home_dir, True)
158 nt.assert_raises(path.HomeDirError, path.get_home_dir, True)
156
159
157 # Should we stub wreg fully so we can run the test on all platforms?
160 # Should we stub wreg fully so we can run the test on all platforms?
158 @skip_if_not_win32
161 @skip_if_not_win32
159 @with_environment
162 @with_environment
160 def test_get_home_dir_8():
163 def test_get_home_dir_8():
161 """Using registry hack for 'My Documents', os=='nt'
164 """Using registry hack for 'My Documents', os=='nt'
162
165
163 HOMESHARE, HOMEDRIVE, HOMEPATH, USERPROFILE and others are missing.
166 HOMESHARE, HOMEDRIVE, HOMEPATH, USERPROFILE and others are missing.
164 """
167 """
165 os.name = 'nt'
168 os.name = 'nt'
166 # Remove from stub environment all keys that may be set
169 # Remove from stub environment all keys that may be set
167 for key in ['HOME', 'HOMESHARE', 'HOMEDRIVE', 'HOMEPATH', 'USERPROFILE']:
170 for key in ['HOME', 'HOMESHARE', 'HOMEDRIVE', 'HOMEPATH', 'USERPROFILE']:
168 env.pop(key, None)
171 env.pop(key, None)
169
172
170 class key:
173 class key:
171 def Close(self):
174 def Close(self):
172 pass
175 pass
173
176
174 with patch.object(wreg, 'OpenKey', return_value=key()), \
177 with patch.object(wreg, 'OpenKey', return_value=key()), \
175 patch.object(wreg, 'QueryValueEx', return_value=[abspath(HOME_TEST_DIR)]):
178 patch.object(wreg, 'QueryValueEx', return_value=[abspath(HOME_TEST_DIR)]):
176 home_dir = path.get_home_dir()
179 home_dir = path.get_home_dir()
177 nt.assert_equal(home_dir, abspath(HOME_TEST_DIR))
180 nt.assert_equal(home_dir, abspath(HOME_TEST_DIR))
178
181
179 @with_environment
182 @with_environment
180 def test_get_xdg_dir_0():
183 def test_get_xdg_dir_0():
181 """test_get_xdg_dir_0, check xdg_dir"""
184 """test_get_xdg_dir_0, check xdg_dir"""
182 reload(path)
185 reload(path)
183 path._writable_dir = lambda path: True
186 path._writable_dir = lambda path: True
184 path.get_home_dir = lambda : 'somewhere'
187 path.get_home_dir = lambda : 'somewhere'
185 os.name = "posix"
188 os.name = "posix"
186 sys.platform = "linux2"
189 sys.platform = "linux2"
187 env.pop('IPYTHON_DIR', None)
190 env.pop('IPYTHON_DIR', None)
188 env.pop('IPYTHONDIR', None)
191 env.pop('IPYTHONDIR', None)
189 env.pop('XDG_CONFIG_HOME', None)
192 env.pop('XDG_CONFIG_HOME', None)
190
193
191 nt.assert_equal(path.get_xdg_dir(), os.path.join('somewhere', '.config'))
194 nt.assert_equal(path.get_xdg_dir(), os.path.join('somewhere', '.config'))
192
195
193
196
194 @with_environment
197 @with_environment
195 def test_get_xdg_dir_1():
198 def test_get_xdg_dir_1():
196 """test_get_xdg_dir_1, check nonexistent xdg_dir"""
199 """test_get_xdg_dir_1, check nonexistent xdg_dir"""
197 reload(path)
200 reload(path)
198 path.get_home_dir = lambda : HOME_TEST_DIR
201 path.get_home_dir = lambda : HOME_TEST_DIR
199 os.name = "posix"
202 os.name = "posix"
200 sys.platform = "linux2"
203 sys.platform = "linux2"
201 env.pop('IPYTHON_DIR', None)
204 env.pop('IPYTHON_DIR', None)
202 env.pop('IPYTHONDIR', None)
205 env.pop('IPYTHONDIR', None)
203 env.pop('XDG_CONFIG_HOME', None)
206 env.pop('XDG_CONFIG_HOME', None)
204 nt.assert_equal(path.get_xdg_dir(), None)
207 nt.assert_equal(path.get_xdg_dir(), None)
205
208
206 @with_environment
209 @with_environment
207 def test_get_xdg_dir_2():
210 def test_get_xdg_dir_2():
208 """test_get_xdg_dir_2, check xdg_dir default to ~/.config"""
211 """test_get_xdg_dir_2, check xdg_dir default to ~/.config"""
209 reload(path)
212 reload(path)
210 path.get_home_dir = lambda : HOME_TEST_DIR
213 path.get_home_dir = lambda : HOME_TEST_DIR
211 os.name = "posix"
214 os.name = "posix"
212 sys.platform = "linux2"
215 sys.platform = "linux2"
213 env.pop('IPYTHON_DIR', None)
216 env.pop('IPYTHON_DIR', None)
214 env.pop('IPYTHONDIR', None)
217 env.pop('IPYTHONDIR', None)
215 env.pop('XDG_CONFIG_HOME', None)
218 env.pop('XDG_CONFIG_HOME', None)
216 cfgdir=os.path.join(path.get_home_dir(), '.config')
219 cfgdir=os.path.join(path.get_home_dir(), '.config')
217 if not os.path.exists(cfgdir):
220 if not os.path.exists(cfgdir):
218 os.makedirs(cfgdir)
221 os.makedirs(cfgdir)
219
222
220 nt.assert_equal(path.get_xdg_dir(), cfgdir)
223 nt.assert_equal(path.get_xdg_dir(), cfgdir)
221
224
222 @with_environment
225 @with_environment
223 def test_get_xdg_dir_3():
226 def test_get_xdg_dir_3():
224 """test_get_xdg_dir_3, check xdg_dir not used on OS X"""
227 """test_get_xdg_dir_3, check xdg_dir not used on OS X"""
225 reload(path)
228 reload(path)
226 path.get_home_dir = lambda : HOME_TEST_DIR
229 path.get_home_dir = lambda : HOME_TEST_DIR
227 os.name = "posix"
230 os.name = "posix"
228 sys.platform = "darwin"
231 sys.platform = "darwin"
229 env.pop('IPYTHON_DIR', None)
232 env.pop('IPYTHON_DIR', None)
230 env.pop('IPYTHONDIR', None)
233 env.pop('IPYTHONDIR', None)
231 env.pop('XDG_CONFIG_HOME', None)
234 env.pop('XDG_CONFIG_HOME', None)
232 cfgdir=os.path.join(path.get_home_dir(), '.config')
235 cfgdir=os.path.join(path.get_home_dir(), '.config')
233 if not os.path.exists(cfgdir):
236 if not os.path.exists(cfgdir):
234 os.makedirs(cfgdir)
237 os.makedirs(cfgdir)
235
238
236 nt.assert_equal(path.get_xdg_dir(), None)
239 nt.assert_equal(path.get_xdg_dir(), None)
237
240
238 def test_filefind():
241 def test_filefind():
239 """Various tests for filefind"""
242 """Various tests for filefind"""
240 f = tempfile.NamedTemporaryFile()
243 f = tempfile.NamedTemporaryFile()
241 # print 'fname:',f.name
244 # print 'fname:',f.name
242 alt_dirs = paths.get_ipython_dir()
245 alt_dirs = paths.get_ipython_dir()
243 t = path.filefind(f.name, alt_dirs)
246 t = path.filefind(f.name, alt_dirs)
244 # print 'found:',t
247 # print 'found:',t
245
248
246
249
247 @dec.skip_if_not_win32
250 @dec.skip_if_not_win32
248 def test_get_long_path_name_win32():
251 def test_get_long_path_name_win32():
249 with TemporaryDirectory() as tmpdir:
252 with TemporaryDirectory() as tmpdir:
250
253
251 # Make a long path. Expands the path of tmpdir prematurely as it may already have a long
254 # Make a long path. Expands the path of tmpdir prematurely as it may already have a long
252 # path component, so ensure we include the long form of it
255 # path component, so ensure we include the long form of it
253 long_path = os.path.join(path.get_long_path_name(tmpdir), 'this is my long path name')
256 long_path = os.path.join(path.get_long_path_name(tmpdir), 'this is my long path name')
254 os.makedirs(long_path)
257 os.makedirs(long_path)
255
258
256 # Test to see if the short path evaluates correctly.
259 # Test to see if the short path evaluates correctly.
257 short_path = os.path.join(tmpdir, 'THISIS~1')
260 short_path = os.path.join(tmpdir, 'THISIS~1')
258 evaluated_path = path.get_long_path_name(short_path)
261 evaluated_path = path.get_long_path_name(short_path)
259 nt.assert_equal(evaluated_path.lower(), long_path.lower())
262 nt.assert_equal(evaluated_path.lower(), long_path.lower())
260
263
261
264
262 @dec.skip_win32
265 @dec.skip_win32
263 def test_get_long_path_name():
266 def test_get_long_path_name():
264 p = path.get_long_path_name('/usr/local')
267 p = path.get_long_path_name('/usr/local')
265 nt.assert_equal(p,'/usr/local')
268 nt.assert_equal(p,'/usr/local')
266
269
267
270
268 class TestRaiseDeprecation(unittest.TestCase):
271 class TestRaiseDeprecation(unittest.TestCase):
269
272
270 @dec.skip_win32 # can't create not-user-writable dir on win
273 @dec.skip_win32 # can't create not-user-writable dir on win
271 @with_environment
274 @with_environment
272 def test_not_writable_ipdir(self):
275 def test_not_writable_ipdir(self):
273 tmpdir = tempfile.mkdtemp()
276 tmpdir = tempfile.mkdtemp()
274 os.name = "posix"
277 os.name = "posix"
275 env.pop('IPYTHON_DIR', None)
278 env.pop('IPYTHON_DIR', None)
276 env.pop('IPYTHONDIR', None)
279 env.pop('IPYTHONDIR', None)
277 env.pop('XDG_CONFIG_HOME', None)
280 env.pop('XDG_CONFIG_HOME', None)
278 env['HOME'] = tmpdir
281 env['HOME'] = tmpdir
279 ipdir = os.path.join(tmpdir, '.ipython')
282 ipdir = os.path.join(tmpdir, '.ipython')
280 os.mkdir(ipdir, 0o555)
283 os.mkdir(ipdir, 0o555)
281 try:
284 try:
282 open(os.path.join(ipdir, "_foo_"), 'w').close()
285 open(os.path.join(ipdir, "_foo_"), 'w').close()
283 except IOError:
286 except IOError:
284 pass
287 pass
285 else:
288 else:
286 # I can still write to an unwritable dir,
289 # I can still write to an unwritable dir,
287 # assume I'm root and skip the test
290 # assume I'm root and skip the test
288 raise SkipTest("I can't create directories that I can't write to")
291 raise SkipTest("I can't create directories that I can't write to")
289 with self.assertWarnsRegex(UserWarning, 'is not a writable location'):
292 with self.assertWarnsRegex(UserWarning, 'is not a writable location'):
290 ipdir = paths.get_ipython_dir()
293 ipdir = paths.get_ipython_dir()
291 env.pop('IPYTHON_DIR', None)
294 env.pop('IPYTHON_DIR', None)
292
295
293 @with_environment
296 @with_environment
294 def test_get_py_filename():
297 def test_get_py_filename():
295 os.chdir(TMP_TEST_DIR)
298 os.chdir(TMP_TEST_DIR)
296 with make_tempfile('foo.py'):
299 with make_tempfile('foo.py'):
297 nt.assert_equal(path.get_py_filename('foo.py'), 'foo.py')
300 nt.assert_equal(path.get_py_filename('foo.py'), 'foo.py')
298 nt.assert_equal(path.get_py_filename('foo'), 'foo.py')
301 nt.assert_equal(path.get_py_filename('foo'), 'foo.py')
299 with make_tempfile('foo'):
302 with make_tempfile('foo'):
300 nt.assert_equal(path.get_py_filename('foo'), 'foo')
303 nt.assert_equal(path.get_py_filename('foo'), 'foo')
301 nt.assert_raises(IOError, path.get_py_filename, 'foo.py')
304 nt.assert_raises(IOError, path.get_py_filename, 'foo.py')
302 nt.assert_raises(IOError, path.get_py_filename, 'foo')
305 nt.assert_raises(IOError, path.get_py_filename, 'foo')
303 nt.assert_raises(IOError, path.get_py_filename, 'foo.py')
306 nt.assert_raises(IOError, path.get_py_filename, 'foo.py')
304 true_fn = 'foo with spaces.py'
307 true_fn = 'foo with spaces.py'
305 with make_tempfile(true_fn):
308 with make_tempfile(true_fn):
306 nt.assert_equal(path.get_py_filename('foo with spaces'), true_fn)
309 nt.assert_equal(path.get_py_filename('foo with spaces'), true_fn)
307 nt.assert_equal(path.get_py_filename('foo with spaces.py'), true_fn)
310 nt.assert_equal(path.get_py_filename('foo with spaces.py'), true_fn)
308 nt.assert_raises(IOError, path.get_py_filename, '"foo with spaces.py"')
311 nt.assert_raises(IOError, path.get_py_filename, '"foo with spaces.py"')
309 nt.assert_raises(IOError, path.get_py_filename, "'foo with spaces.py'")
312 nt.assert_raises(IOError, path.get_py_filename, "'foo with spaces.py'")
310
313
311 @onlyif_unicode_paths
314 @onlyif_unicode_paths
312 def test_unicode_in_filename():
315 def test_unicode_in_filename():
313 """When a file doesn't exist, the exception raised should be safe to call
316 """When a file doesn't exist, the exception raised should be safe to call
314 str() on - i.e. in Python 2 it must only have ASCII characters.
317 str() on - i.e. in Python 2 it must only have ASCII characters.
315
318
316 https://github.com/ipython/ipython/issues/875
319 https://github.com/ipython/ipython/issues/875
317 """
320 """
318 try:
321 try:
319 # these calls should not throw unicode encode exceptions
322 # these calls should not throw unicode encode exceptions
320 path.get_py_filename('fooéè.py')
323 path.get_py_filename('fooéè.py')
321 except IOError as ex:
324 except IOError as ex:
322 str(ex)
325 str(ex)
323
326
324
327
325 class TestShellGlob(unittest.TestCase):
328 class TestShellGlob(unittest.TestCase):
326
329
327 @classmethod
330 @classmethod
328 def setUpClass(cls):
331 def setUpClass(cls):
329 cls.filenames_start_with_a = ['a0', 'a1', 'a2']
332 cls.filenames_start_with_a = ['a0', 'a1', 'a2']
330 cls.filenames_end_with_b = ['0b', '1b', '2b']
333 cls.filenames_end_with_b = ['0b', '1b', '2b']
331 cls.filenames = cls.filenames_start_with_a + cls.filenames_end_with_b
334 cls.filenames = cls.filenames_start_with_a + cls.filenames_end_with_b
332 cls.tempdir = TemporaryDirectory()
335 cls.tempdir = TemporaryDirectory()
333 td = cls.tempdir.name
336 td = cls.tempdir.name
334
337
335 with cls.in_tempdir():
338 with cls.in_tempdir():
336 # Create empty files
339 # Create empty files
337 for fname in cls.filenames:
340 for fname in cls.filenames:
338 open(os.path.join(td, fname), 'w').close()
341 open(os.path.join(td, fname), 'w').close()
339
342
340 @classmethod
343 @classmethod
341 def tearDownClass(cls):
344 def tearDownClass(cls):
342 cls.tempdir.cleanup()
345 cls.tempdir.cleanup()
343
346
344 @classmethod
347 @classmethod
345 @contextmanager
348 @contextmanager
346 def in_tempdir(cls):
349 def in_tempdir(cls):
347 save = os.getcwd()
350 save = os.getcwd()
348 try:
351 try:
349 os.chdir(cls.tempdir.name)
352 os.chdir(cls.tempdir.name)
350 yield
353 yield
351 finally:
354 finally:
352 os.chdir(save)
355 os.chdir(save)
353
356
354 def check_match(self, patterns, matches):
357 def check_match(self, patterns, matches):
355 with self.in_tempdir():
358 with self.in_tempdir():
356 # glob returns unordered list. that's why sorted is required.
359 # glob returns unordered list. that's why sorted is required.
357 nt.assert_equal(sorted(path.shellglob(patterns)),
360 nt.assert_equal(sorted(path.shellglob(patterns)),
358 sorted(matches))
361 sorted(matches))
359
362
360 def common_cases(self):
363 def common_cases(self):
361 return [
364 return [
362 (['*'], self.filenames),
365 (['*'], self.filenames),
363 (['a*'], self.filenames_start_with_a),
366 (['a*'], self.filenames_start_with_a),
364 (['*c'], ['*c']),
367 (['*c'], ['*c']),
365 (['*', 'a*', '*b', '*c'], self.filenames
368 (['*', 'a*', '*b', '*c'], self.filenames
366 + self.filenames_start_with_a
369 + self.filenames_start_with_a
367 + self.filenames_end_with_b
370 + self.filenames_end_with_b
368 + ['*c']),
371 + ['*c']),
369 (['a[012]'], self.filenames_start_with_a),
372 (['a[012]'], self.filenames_start_with_a),
370 ]
373 ]
371
374
372 @skip_win32
375 @skip_win32
373 def test_match_posix(self):
376 def test_match_posix(self):
374 for (patterns, matches) in self.common_cases() + [
377 for (patterns, matches) in self.common_cases() + [
375 ([r'\*'], ['*']),
378 ([r'\*'], ['*']),
376 ([r'a\*', 'a*'], ['a*'] + self.filenames_start_with_a),
379 ([r'a\*', 'a*'], ['a*'] + self.filenames_start_with_a),
377 ([r'a\[012]'], ['a[012]']),
380 ([r'a\[012]'], ['a[012]']),
378 ]:
381 ]:
379 yield (self.check_match, patterns, matches)
382 yield (self.check_match, patterns, matches)
380
383
381 @skip_if_not_win32
384 @skip_if_not_win32
382 def test_match_windows(self):
385 def test_match_windows(self):
383 for (patterns, matches) in self.common_cases() + [
386 for (patterns, matches) in self.common_cases() + [
384 # In windows, backslash is interpreted as path
387 # In windows, backslash is interpreted as path
385 # separator. Therefore, you can't escape glob
388 # separator. Therefore, you can't escape glob
386 # using it.
389 # using it.
387 ([r'a\*', 'a*'], [r'a\*'] + self.filenames_start_with_a),
390 ([r'a\*', 'a*'], [r'a\*'] + self.filenames_start_with_a),
388 ([r'a\[012]'], [r'a\[012]']),
391 ([r'a\[012]'], [r'a\[012]']),
389 ]:
392 ]:
390 yield (self.check_match, patterns, matches)
393 yield (self.check_match, patterns, matches)
391
394
392
395
393 def test_unescape_glob():
396 def test_unescape_glob():
394 nt.assert_equal(path.unescape_glob(r'\*\[\!\]\?'), '*[!]?')
397 nt.assert_equal(path.unescape_glob(r'\*\[\!\]\?'), '*[!]?')
395 nt.assert_equal(path.unescape_glob(r'\\*'), r'\*')
398 nt.assert_equal(path.unescape_glob(r'\\*'), r'\*')
396 nt.assert_equal(path.unescape_glob(r'\\\*'), r'\*')
399 nt.assert_equal(path.unescape_glob(r'\\\*'), r'\*')
397 nt.assert_equal(path.unescape_glob(r'\\a'), r'\a')
400 nt.assert_equal(path.unescape_glob(r'\\a'), r'\a')
398 nt.assert_equal(path.unescape_glob(r'\a'), r'\a')
401 nt.assert_equal(path.unescape_glob(r'\a'), r'\a')
399
402
400
403
401 @onlyif_unicode_paths
404 @onlyif_unicode_paths
402 def test_ensure_dir_exists():
405 def test_ensure_dir_exists():
403 with TemporaryDirectory() as td:
406 with TemporaryDirectory() as td:
404 d = os.path.join(td, 'βˆ‚ir')
407 d = os.path.join(td, 'βˆ‚ir')
405 path.ensure_dir_exists(d) # create it
408 path.ensure_dir_exists(d) # create it
406 assert os.path.isdir(d)
409 assert os.path.isdir(d)
407 path.ensure_dir_exists(d) # no-op
410 path.ensure_dir_exists(d) # no-op
408 f = os.path.join(td, 'Ζ’ile')
411 f = os.path.join(td, 'Ζ’ile')
409 open(f, 'w').close() # touch
412 open(f, 'w').close() # touch
410 with nt.assert_raises(IOError):
413 with nt.assert_raises(IOError):
411 path.ensure_dir_exists(f)
414 path.ensure_dir_exists(f)
412
415
413 class TestLinkOrCopy(unittest.TestCase):
416 class TestLinkOrCopy(unittest.TestCase):
414 def setUp(self):
417 def setUp(self):
415 self.tempdir = TemporaryDirectory()
418 self.tempdir = TemporaryDirectory()
416 self.src = self.dst("src")
419 self.src = self.dst("src")
417 with open(self.src, "w") as f:
420 with open(self.src, "w") as f:
418 f.write("Hello, world!")
421 f.write("Hello, world!")
419
422
420 def tearDown(self):
423 def tearDown(self):
421 self.tempdir.cleanup()
424 self.tempdir.cleanup()
422
425
423 def dst(self, *args):
426 def dst(self, *args):
424 return os.path.join(self.tempdir.name, *args)
427 return os.path.join(self.tempdir.name, *args)
425
428
426 def assert_inode_not_equal(self, a, b):
429 def assert_inode_not_equal(self, a, b):
427 nt.assert_not_equal(os.stat(a).st_ino, os.stat(b).st_ino,
430 nt.assert_not_equal(os.stat(a).st_ino, os.stat(b).st_ino,
428 "%r and %r do reference the same indoes" %(a, b))
431 "%r and %r do reference the same indoes" %(a, b))
429
432
430 def assert_inode_equal(self, a, b):
433 def assert_inode_equal(self, a, b):
431 nt.assert_equal(os.stat(a).st_ino, os.stat(b).st_ino,
434 nt.assert_equal(os.stat(a).st_ino, os.stat(b).st_ino,
432 "%r and %r do not reference the same indoes" %(a, b))
435 "%r and %r do not reference the same indoes" %(a, b))
433
436
434 def assert_content_equal(self, a, b):
437 def assert_content_equal(self, a, b):
435 with open(a) as a_f:
438 with open(a) as a_f:
436 with open(b) as b_f:
439 with open(b) as b_f:
437 nt.assert_equal(a_f.read(), b_f.read())
440 nt.assert_equal(a_f.read(), b_f.read())
438
441
439 @skip_win32
442 @skip_win32
440 def test_link_successful(self):
443 def test_link_successful(self):
441 dst = self.dst("target")
444 dst = self.dst("target")
442 path.link_or_copy(self.src, dst)
445 path.link_or_copy(self.src, dst)
443 self.assert_inode_equal(self.src, dst)
446 self.assert_inode_equal(self.src, dst)
444
447
445 @skip_win32
448 @skip_win32
446 def test_link_into_dir(self):
449 def test_link_into_dir(self):
447 dst = self.dst("some_dir")
450 dst = self.dst("some_dir")
448 os.mkdir(dst)
451 os.mkdir(dst)
449 path.link_or_copy(self.src, dst)
452 path.link_or_copy(self.src, dst)
450 expected_dst = self.dst("some_dir", os.path.basename(self.src))
453 expected_dst = self.dst("some_dir", os.path.basename(self.src))
451 self.assert_inode_equal(self.src, expected_dst)
454 self.assert_inode_equal(self.src, expected_dst)
452
455
453 @skip_win32
456 @skip_win32
454 def test_target_exists(self):
457 def test_target_exists(self):
455 dst = self.dst("target")
458 dst = self.dst("target")
456 open(dst, "w").close()
459 open(dst, "w").close()
457 path.link_or_copy(self.src, dst)
460 path.link_or_copy(self.src, dst)
458 self.assert_inode_equal(self.src, dst)
461 self.assert_inode_equal(self.src, dst)
459
462
460 @skip_win32
463 @skip_win32
461 def test_no_link(self):
464 def test_no_link(self):
462 real_link = os.link
465 real_link = os.link
463 try:
466 try:
464 del os.link
467 del os.link
465 dst = self.dst("target")
468 dst = self.dst("target")
466 path.link_or_copy(self.src, dst)
469 path.link_or_copy(self.src, dst)
467 self.assert_content_equal(self.src, dst)
470 self.assert_content_equal(self.src, dst)
468 self.assert_inode_not_equal(self.src, dst)
471 self.assert_inode_not_equal(self.src, dst)
469 finally:
472 finally:
470 os.link = real_link
473 os.link = real_link
471
474
472 @skip_if_not_win32
475 @skip_if_not_win32
473 def test_windows(self):
476 def test_windows(self):
474 dst = self.dst("target")
477 dst = self.dst("target")
475 path.link_or_copy(self.src, dst)
478 path.link_or_copy(self.src, dst)
476 self.assert_content_equal(self.src, dst)
479 self.assert_content_equal(self.src, dst)
477
480
478 def test_link_twice(self):
481 def test_link_twice(self):
479 # Linking the same file twice shouldn't leave duplicates around.
482 # Linking the same file twice shouldn't leave duplicates around.
480 # See https://github.com/ipython/ipython/issues/6450
483 # See https://github.com/ipython/ipython/issues/6450
481 dst = self.dst('target')
484 dst = self.dst('target')
482 path.link_or_copy(self.src, dst)
485 path.link_or_copy(self.src, dst)
483 path.link_or_copy(self.src, dst)
486 path.link_or_copy(self.src, dst)
484 self.assert_inode_equal(self.src, dst)
487 self.assert_inode_equal(self.src, dst)
485 nt.assert_equal(sorted(os.listdir(self.tempdir.name)), ['src', 'target'])
488 nt.assert_equal(sorted(os.listdir(self.tempdir.name)), ['src', 'target'])
General Comments 0
You need to be logged in to leave comments. Login now