##// END OF EJS Templates
remove last bits of pre-38 codepath....
Matthias Bussonnier -
Show More
@@ -1,372 +1,371 b''
1 1 """
2 2 Module to define and register Terminal IPython shortcuts with
3 3 :mod:`prompt_toolkit`
4 4 """
5 5
6 6 # Copyright (c) IPython Development Team.
7 7 # Distributed under the terms of the Modified BSD License.
8 8
9 9 import warnings
10 10 import signal
11 11 import sys
12 12 import re
13 13 from typing import Callable
14 14
15 15
16 16 from prompt_toolkit.application.current import get_app
17 17 from prompt_toolkit.enums import DEFAULT_BUFFER, SEARCH_BUFFER
18 18 from prompt_toolkit.filters import (has_focus, has_selection, Condition,
19 19 vi_insert_mode, emacs_insert_mode, has_completions, vi_mode)
20 20 from prompt_toolkit.key_binding.bindings.completion import display_completions_like_readline
21 21 from prompt_toolkit.key_binding import KeyBindings
22 22 from prompt_toolkit.key_binding.bindings import named_commands as nc
23 23 from prompt_toolkit.key_binding.vi_state import InputMode, ViState
24 24
25 25 from IPython.utils.decorators import undoc
26 26
27 27 @undoc
28 28 @Condition
29 29 def cursor_in_leading_ws():
30 30 before = get_app().current_buffer.document.current_line_before_cursor
31 31 return (not before) or before.isspace()
32 32
33 33
34 34 def create_ipython_shortcuts(shell):
35 35 """Set up the prompt_toolkit keyboard shortcuts for IPython"""
36 36
37 37 kb = KeyBindings()
38 38 insert_mode = vi_insert_mode | emacs_insert_mode
39 39
40 40 if getattr(shell, 'handle_return', None):
41 41 return_handler = shell.handle_return(shell)
42 42 else:
43 43 return_handler = newline_or_execute_outer(shell)
44 44
45 45 kb.add('enter', filter=(has_focus(DEFAULT_BUFFER)
46 46 & ~has_selection
47 47 & insert_mode
48 48 ))(return_handler)
49 49
50 50 def reformat_and_execute(event):
51 51 reformat_text_before_cursor(event.current_buffer, event.current_buffer.document, shell)
52 52 event.current_buffer.validate_and_handle()
53 53
54 54 kb.add('escape', 'enter', filter=(has_focus(DEFAULT_BUFFER)
55 55 & ~has_selection
56 56 & insert_mode
57 57 ))(reformat_and_execute)
58 58
59 59 kb.add('c-\\')(force_exit)
60 60
61 61 kb.add('c-p', filter=(vi_insert_mode & has_focus(DEFAULT_BUFFER))
62 62 )(previous_history_or_previous_completion)
63 63
64 64 kb.add('c-n', filter=(vi_insert_mode & has_focus(DEFAULT_BUFFER))
65 65 )(next_history_or_next_completion)
66 66
67 67 kb.add('c-g', filter=(has_focus(DEFAULT_BUFFER) & has_completions)
68 68 )(dismiss_completion)
69 69
70 70 kb.add('c-c', filter=has_focus(DEFAULT_BUFFER))(reset_buffer)
71 71
72 72 kb.add('c-c', filter=has_focus(SEARCH_BUFFER))(reset_search_buffer)
73 73
74 74 supports_suspend = Condition(lambda: hasattr(signal, 'SIGTSTP'))
75 75 kb.add('c-z', filter=supports_suspend)(suspend_to_bg)
76 76
77 77 # Ctrl+I == Tab
78 78 kb.add('tab', filter=(has_focus(DEFAULT_BUFFER)
79 79 & ~has_selection
80 80 & insert_mode
81 81 & cursor_in_leading_ws
82 82 ))(indent_buffer)
83 83 kb.add('c-o', filter=(has_focus(DEFAULT_BUFFER) & emacs_insert_mode)
84 84 )(newline_autoindent_outer(shell.input_transformer_manager))
85 85
86 86 kb.add('f2', filter=has_focus(DEFAULT_BUFFER))(open_input_in_editor)
87 87
88 88 if shell.display_completions == 'readlinelike':
89 89 kb.add('c-i', filter=(has_focus(DEFAULT_BUFFER)
90 90 & ~has_selection
91 91 & insert_mode
92 92 & ~cursor_in_leading_ws
93 93 ))(display_completions_like_readline)
94 94
95 95 if sys.platform == "win32":
96 96 kb.add("c-v", filter=(has_focus(DEFAULT_BUFFER) & ~vi_mode))(win_paste)
97 97
98 98 @Condition
99 99 def ebivim():
100 100 return shell.emacs_bindings_in_vi_insert_mode
101 101
102 102 focused_insert = has_focus(DEFAULT_BUFFER) & vi_insert_mode
103 103
104 104 # Needed for to accept autosuggestions in vi insert mode
105 105 @kb.add("c-e", filter=focused_insert & ebivim)
106 106 def _(event):
107 107 b = event.current_buffer
108 108 suggestion = b.suggestion
109 109 if suggestion:
110 110 b.insert_text(suggestion.text)
111 111 else:
112 112 nc.end_of_line(event)
113 113
114 114 @kb.add("c-f", filter=focused_insert & ebivim)
115 115 def _(event):
116 116 b = event.current_buffer
117 117 suggestion = b.suggestion
118 118 if suggestion:
119 119 b.insert_text(suggestion.text)
120 120 else:
121 121 nc.forward_char(event)
122 122
123 123 @kb.add("escape", "f", filter=focused_insert & ebivim)
124 124 def _(event):
125 125 b = event.current_buffer
126 126 suggestion = b.suggestion
127 127 if suggestion:
128 128 t = re.split(r"(\S+\s+)", suggestion.text)
129 129 b.insert_text(next((x for x in t if x), ""))
130 130 else:
131 131 nc.forward_word(event)
132 132
133 133 # Simple Control keybindings
134 134 key_cmd_dict = {
135 135 "c-a": nc.beginning_of_line,
136 136 "c-b": nc.backward_char,
137 137 "c-k": nc.kill_line,
138 138 "c-w": nc.backward_kill_word,
139 139 "c-y": nc.yank,
140 140 "c-_": nc.undo,
141 141 }
142 142
143 143 for key, cmd in key_cmd_dict.items():
144 144 kb.add(key, filter=focused_insert & ebivim)(cmd)
145 145
146 146 # Alt and Combo Control keybindings
147 147 keys_cmd_dict = {
148 148 # Control Combos
149 149 ("c-x", "c-e"): nc.edit_and_execute,
150 150 ("c-x", "e"): nc.edit_and_execute,
151 151 # Alt
152 152 ("escape", "b"): nc.backward_word,
153 153 ("escape", "c"): nc.capitalize_word,
154 154 ("escape", "d"): nc.kill_word,
155 155 ("escape", "h"): nc.backward_kill_word,
156 156 ("escape", "l"): nc.downcase_word,
157 157 ("escape", "u"): nc.uppercase_word,
158 158 ("escape", "y"): nc.yank_pop,
159 159 ("escape", "."): nc.yank_last_arg,
160 160 }
161 161
162 162 for keys, cmd in keys_cmd_dict.items():
163 163 kb.add(*keys, filter=focused_insert & ebivim)(cmd)
164 164
165 165 def get_input_mode(self):
166 if sys.version_info[0] == 3:
167 166 app = get_app()
168 167 app.ttimeoutlen = shell.ttimeoutlen
169 168 app.timeoutlen = shell.timeoutlen
170 169
171 170 return self._input_mode
172 171
173 172 def set_input_mode(self, mode):
174 173 shape = {InputMode.NAVIGATION: 2, InputMode.REPLACE: 4}.get(mode, 6)
175 174 cursor = "\x1b[{} q".format(shape)
176 175
177 176 if hasattr(sys.stdout, "_cli"):
178 177 write = sys.stdout._cli.output.write_raw
179 178 else:
180 179 write = sys.stdout.write
181 180
182 181 write(cursor)
183 182 sys.stdout.flush()
184 183
185 184 self._input_mode = mode
186 185
187 186 if shell.editing_mode == "vi" and shell.modal_cursor:
188 187 ViState._input_mode = InputMode.INSERT
189 188 ViState.input_mode = property(get_input_mode, set_input_mode)
190 189
191 190 return kb
192 191
193 192
194 193 def reformat_text_before_cursor(buffer, document, shell):
195 194 text = buffer.delete_before_cursor(len(document.text[:document.cursor_position]))
196 195 try:
197 196 formatted_text = shell.reformat_handler(text)
198 197 buffer.insert_text(formatted_text)
199 198 except Exception as e:
200 199 buffer.insert_text(text)
201 200
202 201
203 202 def newline_or_execute_outer(shell):
204 203
205 204 def newline_or_execute(event):
206 205 """When the user presses return, insert a newline or execute the code."""
207 206 b = event.current_buffer
208 207 d = b.document
209 208
210 209 if b.complete_state:
211 210 cc = b.complete_state.current_completion
212 211 if cc:
213 212 b.apply_completion(cc)
214 213 else:
215 214 b.cancel_completion()
216 215 return
217 216
218 217 # If there's only one line, treat it as if the cursor is at the end.
219 218 # See https://github.com/ipython/ipython/issues/10425
220 219 if d.line_count == 1:
221 220 check_text = d.text
222 221 else:
223 222 check_text = d.text[:d.cursor_position]
224 223 status, indent = shell.check_complete(check_text)
225 224
226 225 # if all we have after the cursor is whitespace: reformat current text
227 226 # before cursor
228 227 after_cursor = d.text[d.cursor_position:]
229 228 reformatted = False
230 229 if not after_cursor.strip():
231 230 reformat_text_before_cursor(b, d, shell)
232 231 reformatted = True
233 232 if not (d.on_last_line or
234 233 d.cursor_position_row >= d.line_count - d.empty_line_count_at_the_end()
235 234 ):
236 235 if shell.autoindent:
237 236 b.insert_text('\n' + indent)
238 237 else:
239 238 b.insert_text('\n')
240 239 return
241 240
242 241 if (status != 'incomplete') and b.accept_handler:
243 242 if not reformatted:
244 243 reformat_text_before_cursor(b, d, shell)
245 244 b.validate_and_handle()
246 245 else:
247 246 if shell.autoindent:
248 247 b.insert_text('\n' + indent)
249 248 else:
250 249 b.insert_text('\n')
251 250 return newline_or_execute
252 251
253 252
254 253 def previous_history_or_previous_completion(event):
255 254 """
256 255 Control-P in vi edit mode on readline is history next, unlike default prompt toolkit.
257 256
258 257 If completer is open this still select previous completion.
259 258 """
260 259 event.current_buffer.auto_up()
261 260
262 261
263 262 def next_history_or_next_completion(event):
264 263 """
265 264 Control-N in vi edit mode on readline is history previous, unlike default prompt toolkit.
266 265
267 266 If completer is open this still select next completion.
268 267 """
269 268 event.current_buffer.auto_down()
270 269
271 270
272 271 def dismiss_completion(event):
273 272 b = event.current_buffer
274 273 if b.complete_state:
275 274 b.cancel_completion()
276 275
277 276
278 277 def reset_buffer(event):
279 278 b = event.current_buffer
280 279 if b.complete_state:
281 280 b.cancel_completion()
282 281 else:
283 282 b.reset()
284 283
285 284
286 285 def reset_search_buffer(event):
287 286 if event.current_buffer.document.text:
288 287 event.current_buffer.reset()
289 288 else:
290 289 event.app.layout.focus(DEFAULT_BUFFER)
291 290
292 291 def suspend_to_bg(event):
293 292 event.app.suspend_to_background()
294 293
295 294 def force_exit(event):
296 295 """
297 296 Force exit (with a non-zero return value)
298 297 """
299 298 sys.exit("Quit")
300 299
301 300 def indent_buffer(event):
302 301 event.current_buffer.insert_text(' ' * 4)
303 302
304 303 @undoc
305 304 def newline_with_copy_margin(event):
306 305 """
307 306 DEPRECATED since IPython 6.0
308 307
309 308 See :any:`newline_autoindent_outer` for a replacement.
310 309
311 310 Preserve margin and cursor position when using
312 311 Control-O to insert a newline in EMACS mode
313 312 """
314 313 warnings.warn("`newline_with_copy_margin(event)` is deprecated since IPython 6.0. "
315 314 "see `newline_autoindent_outer(shell)(event)` for a replacement.",
316 315 DeprecationWarning, stacklevel=2)
317 316
318 317 b = event.current_buffer
319 318 cursor_start_pos = b.document.cursor_position_col
320 319 b.newline(copy_margin=True)
321 320 b.cursor_up(count=1)
322 321 cursor_end_pos = b.document.cursor_position_col
323 322 if cursor_start_pos != cursor_end_pos:
324 323 pos_diff = cursor_start_pos - cursor_end_pos
325 324 b.cursor_right(count=pos_diff)
326 325
327 326 def newline_autoindent_outer(inputsplitter) -> Callable[..., None]:
328 327 """
329 328 Return a function suitable for inserting a indented newline after the cursor.
330 329
331 330 Fancier version of deprecated ``newline_with_copy_margin`` which should
332 331 compute the correct indentation of the inserted line. That is to say, indent
333 332 by 4 extra space after a function definition, class definition, context
334 333 manager... And dedent by 4 space after ``pass``, ``return``, ``raise ...``.
335 334 """
336 335
337 336 def newline_autoindent(event):
338 337 """insert a newline after the cursor indented appropriately."""
339 338 b = event.current_buffer
340 339 d = b.document
341 340
342 341 if b.complete_state:
343 342 b.cancel_completion()
344 343 text = d.text[:d.cursor_position] + '\n'
345 344 _, indent = inputsplitter.check_complete(text)
346 345 b.insert_text('\n' + (' ' * (indent or 0)), move_cursor=False)
347 346
348 347 return newline_autoindent
349 348
350 349
351 350 def open_input_in_editor(event):
352 351 event.app.current_buffer.open_in_editor()
353 352
354 353
355 354 if sys.platform == 'win32':
356 355 from IPython.core.error import TryNext
357 356 from IPython.lib.clipboard import (ClipboardEmpty,
358 357 win32_clipboard_get,
359 358 tkinter_clipboard_get)
360 359
361 360 @undoc
362 361 def win_paste(event):
363 362 try:
364 363 text = win32_clipboard_get()
365 364 except TryNext:
366 365 try:
367 366 text = tkinter_clipboard_get()
368 367 except (TryNext, ClipboardEmpty):
369 368 return
370 369 except ClipboardEmpty:
371 370 return
372 371 event.current_buffer.insert_text(text.replace("\t", " " * 4))
@@ -1,212 +1,207 b''
1 1 # -*- coding: utf-8 -*-
2 2 """Decorators for labeling test objects.
3 3
4 4 Decorators that merely return a modified version of the original function
5 5 object are straightforward. Decorators that return a new function object need
6 6 to use nose.tools.make_decorator(original_function)(decorator) in returning the
7 7 decorator, in order to preserve metadata such as function name, setup and
8 8 teardown functions and so on - see nose.tools for more information.
9 9
10 10 This module provides a set of useful decorators meant to be ready to use in
11 11 your own tests. See the bottom of the file for the ready-made ones, and if you
12 12 find yourself writing a new one that may be of generic use, add it here.
13 13
14 14 Included decorators:
15 15
16 16
17 17 Lightweight testing that remains unittest-compatible.
18 18
19 19 - An @as_unittest decorator can be used to tag any normal parameter-less
20 20 function as a unittest TestCase. Then, both nose and normal unittest will
21 21 recognize it as such. This will make it easier to migrate away from Nose if
22 22 we ever need/want to while maintaining very lightweight tests.
23 23
24 24 NOTE: This file contains IPython-specific decorators. Using the machinery in
25 25 IPython.external.decorators, we import either numpy.testing.decorators if numpy is
26 26 available, OR use equivalent code in IPython.external._decorators, which
27 27 we've copied verbatim from numpy.
28 28
29 29 """
30 30
31 31 # Copyright (c) IPython Development Team.
32 32 # Distributed under the terms of the Modified BSD License.
33 33
34 34 import os
35 35 import shutil
36 36 import sys
37 37 import tempfile
38 38 import unittest
39 39 import warnings
40 40 from importlib import import_module
41 41
42 42 from decorator import decorator
43 43
44 44 # Expose the unittest-driven decorators
45 45 from .ipunittest import ipdoctest, ipdocstring
46 46
47 47 #-----------------------------------------------------------------------------
48 48 # Classes and functions
49 49 #-----------------------------------------------------------------------------
50 50
51 51 # Simple example of the basic idea
52 52 def as_unittest(func):
53 53 """Decorator to make a simple function into a normal test via unittest."""
54 54 class Tester(unittest.TestCase):
55 55 def test(self):
56 56 func()
57 57
58 58 Tester.__name__ = func.__name__
59 59
60 60 return Tester
61 61
62 62 # Utility functions
63 63
64 64
65 65 def skipif(skip_condition, msg=None):
66 66 """Make function raise SkipTest exception if skip_condition is true
67 67
68 68 Parameters
69 69 ----------
70 70
71 71 skip_condition : bool or callable
72 72 Flag to determine whether to skip test. If the condition is a
73 73 callable, it is used at runtime to dynamically make the decision. This
74 74 is useful for tests that may require costly imports, to delay the cost
75 75 until the test suite is actually executed.
76 76 msg : string
77 77 Message to give on raising a SkipTest exception.
78 78
79 79 Returns
80 80 -------
81 81 decorator : function
82 82 Decorator, which, when applied to a function, causes SkipTest
83 83 to be raised when the skip_condition was True, and the function
84 84 to be called normally otherwise.
85 85 """
86 86 if msg is None:
87 87 msg = "Test skipped due to test condition."
88 88
89 89 import pytest
90 90
91 91 assert isinstance(skip_condition, bool)
92 92 return pytest.mark.skipif(skip_condition, reason=msg)
93 93
94 94
95 95 # A version with the condition set to true, common case just to attach a message
96 96 # to a skip decorator
97 97 def skip(msg=None):
98 98 """Decorator factory - mark a test function for skipping from test suite.
99 99
100 100 Parameters
101 101 ----------
102 102 msg : string
103 103 Optional message to be added.
104 104
105 105 Returns
106 106 -------
107 107 decorator : function
108 108 Decorator, which, when applied to a function, causes SkipTest
109 109 to be raised, with the optional message added.
110 110 """
111 111 if msg and not isinstance(msg, str):
112 112 raise ValueError('invalid object passed to `@skip` decorator, did you '
113 113 'meant `@skip()` with brackets ?')
114 114 return skipif(True, msg)
115 115
116 116
117 117 def onlyif(condition, msg):
118 118 """The reverse from skipif, see skipif for details."""
119 119
120 120 return skipif(not condition, msg)
121 121
122 122 #-----------------------------------------------------------------------------
123 123 # Utility functions for decorators
124 124 def module_not_available(module):
125 125 """Can module be imported? Returns true if module does NOT import.
126 126
127 127 This is used to make a decorator to skip tests that require module to be
128 128 available, but delay the 'import numpy' to test execution time.
129 129 """
130 130 try:
131 131 mod = import_module(module)
132 132 mod_not_avail = False
133 133 except ImportError:
134 134 mod_not_avail = True
135 135
136 136 return mod_not_avail
137 137
138 138
139 139 #-----------------------------------------------------------------------------
140 140 # Decorators for public use
141 141
142 142 # Decorators to skip certain tests on specific platforms.
143 143 skip_win32 = skipif(sys.platform == 'win32',
144 144 "This test does not run under Windows")
145 145 skip_linux = skipif(sys.platform.startswith('linux'),
146 146 "This test does not run under Linux")
147 147 skip_osx = skipif(sys.platform == 'darwin',"This test does not run under OS X")
148 148
149 149
150 150 # Decorators to skip tests if not on specific platforms.
151 151 skip_if_not_win32 = skipif(sys.platform != 'win32',
152 152 "This test only runs under Windows")
153 153 skip_if_not_linux = skipif(not sys.platform.startswith('linux'),
154 154 "This test only runs under Linux")
155 155 skip_if_not_osx = skipif(sys.platform != 'darwin',
156 156 "This test only runs under OSX")
157 157
158 158
159 159 _x11_skip_cond = (sys.platform not in ('darwin', 'win32') and
160 160 os.environ.get('DISPLAY', '') == '')
161 161 _x11_skip_msg = "Skipped under *nix when X11/XOrg not available"
162 162
163 163 skip_if_no_x11 = skipif(_x11_skip_cond, _x11_skip_msg)
164 164
165
166 # Decorators to skip certain tests on specific platform/python combinations
167 skip_win32_py38 = skipif(sys.version_info > (3,8) and os.name == 'nt')
168
169
170 165 # Other skip decorators
171 166
172 167 # generic skip without module
173 168 skip_without = lambda mod: skipif(module_not_available(mod), "This test requires %s" % mod)
174 169
175 170 skipif_not_numpy = skip_without('numpy')
176 171
177 172 skipif_not_matplotlib = skip_without('matplotlib')
178 173
179 174 skipif_not_sympy = skip_without('sympy')
180 175
181 176 # A null 'decorator', useful to make more readable code that needs to pick
182 177 # between different decorators based on OS or other conditions
183 178 null_deco = lambda f: f
184 179
185 180 # Some tests only run where we can use unicode paths. Note that we can't just
186 181 # check os.path.supports_unicode_filenames, which is always False on Linux.
187 182 try:
188 183 f = tempfile.NamedTemporaryFile(prefix=u"tmp€")
189 184 except UnicodeEncodeError:
190 185 unicode_paths = False
191 186 else:
192 187 unicode_paths = True
193 188 f.close()
194 189
195 190 onlyif_unicode_paths = onlyif(unicode_paths, ("This test is only applicable "
196 191 "where we can use unicode in filenames."))
197 192
198 193
199 194 def onlyif_cmds_exist(*commands):
200 195 """
201 196 Decorator to skip test when at least one of `commands` is not found.
202 197 """
203 198 for cmd in commands:
204 199 reason = f"This test runs only if command '{cmd}' is installed"
205 200 if not shutil.which(cmd):
206 201 if os.environ.get("IPTEST_WORKING_DIR", None) is not None:
207 202 return skip(reason)
208 203 else:
209 204 import pytest
210 205
211 206 return pytest.mark.skip(reason=reason)
212 207 return null_deco
@@ -1,466 +1,463 b''
1 1 """Generic testing tools.
2 2
3 3 Authors
4 4 -------
5 5 - Fernando Perez <Fernando.Perez@berkeley.edu>
6 6 """
7 7
8 8
9 9 # Copyright (c) IPython Development Team.
10 10 # Distributed under the terms of the Modified BSD License.
11 11
12 12 import os
13 13 from pathlib import Path
14 14 import re
15 15 import sys
16 16 import tempfile
17 17 import unittest
18 18
19 19 from contextlib import contextmanager
20 20 from io import StringIO
21 21 from subprocess import Popen, PIPE
22 22 from unittest.mock import patch
23 23
24 24 from traitlets.config.loader import Config
25 25 from IPython.utils.process import get_output_error_code
26 26 from IPython.utils.text import list_strings
27 27 from IPython.utils.io import temp_pyfile, Tee
28 28 from IPython.utils import py3compat
29 29
30 30 from . import decorators as dec
31 31 from . import skipdoctest
32 32
33 33
34 34 # The docstring for full_path doctests differently on win32 (different path
35 35 # separator) so just skip the doctest there. The example remains informative.
36 36 doctest_deco = skipdoctest.skip_doctest if sys.platform == 'win32' else dec.null_deco
37 37
38 38 @doctest_deco
39 39 def full_path(startPath,files):
40 40 """Make full paths for all the listed files, based on startPath.
41 41
42 42 Only the base part of startPath is kept, since this routine is typically
43 43 used with a script's ``__file__`` variable as startPath. The base of startPath
44 44 is then prepended to all the listed files, forming the output list.
45 45
46 46 Parameters
47 47 ----------
48 48 startPath : string
49 49 Initial path to use as the base for the results. This path is split
50 50 using os.path.split() and only its first component is kept.
51 51
52 52 files : string or list
53 53 One or more files.
54 54
55 55 Examples
56 56 --------
57 57
58 58 >>> full_path('/foo/bar.py',['a.txt','b.txt'])
59 59 ['/foo/a.txt', '/foo/b.txt']
60 60
61 61 >>> full_path('/foo',['a.txt','b.txt'])
62 62 ['/a.txt', '/b.txt']
63 63
64 64 If a single file is given, the output is still a list::
65 65
66 66 >>> full_path('/foo','a.txt')
67 67 ['/a.txt']
68 68 """
69 69
70 70 files = list_strings(files)
71 71 base = os.path.split(startPath)[0]
72 72 return [ os.path.join(base,f) for f in files ]
73 73
74 74
75 75 def parse_test_output(txt):
76 76 """Parse the output of a test run and return errors, failures.
77 77
78 78 Parameters
79 79 ----------
80 80 txt : str
81 81 Text output of a test run, assumed to contain a line of one of the
82 82 following forms::
83 83
84 84 'FAILED (errors=1)'
85 85 'FAILED (failures=1)'
86 86 'FAILED (errors=1, failures=1)'
87 87
88 88 Returns
89 89 -------
90 90 nerr, nfail
91 91 number of errors and failures.
92 92 """
93 93
94 94 err_m = re.search(r'^FAILED \(errors=(\d+)\)', txt, re.MULTILINE)
95 95 if err_m:
96 96 nerr = int(err_m.group(1))
97 97 nfail = 0
98 98 return nerr, nfail
99 99
100 100 fail_m = re.search(r'^FAILED \(failures=(\d+)\)', txt, re.MULTILINE)
101 101 if fail_m:
102 102 nerr = 0
103 103 nfail = int(fail_m.group(1))
104 104 return nerr, nfail
105 105
106 106 both_m = re.search(r'^FAILED \(errors=(\d+), failures=(\d+)\)', txt,
107 107 re.MULTILINE)
108 108 if both_m:
109 109 nerr = int(both_m.group(1))
110 110 nfail = int(both_m.group(2))
111 111 return nerr, nfail
112 112
113 113 # If the input didn't match any of these forms, assume no error/failures
114 114 return 0, 0
115 115
116 116
117 117 # So nose doesn't think this is a test
118 118 parse_test_output.__test__ = False
119 119
120 120
121 121 def default_argv():
122 122 """Return a valid default argv for creating testing instances of ipython"""
123 123
124 124 return ['--quick', # so no config file is loaded
125 125 # Other defaults to minimize side effects on stdout
126 126 '--colors=NoColor', '--no-term-title','--no-banner',
127 127 '--autocall=0']
128 128
129 129
130 130 def default_config():
131 131 """Return a config object with good defaults for testing."""
132 132 config = Config()
133 133 config.TerminalInteractiveShell.colors = 'NoColor'
134 134 config.TerminalTerminalInteractiveShell.term_title = False,
135 135 config.TerminalInteractiveShell.autocall = 0
136 136 f = tempfile.NamedTemporaryFile(suffix=u'test_hist.sqlite', delete=False)
137 137 config.HistoryManager.hist_file = Path(f.name)
138 138 f.close()
139 139 config.HistoryManager.db_cache_size = 10000
140 140 return config
141 141
142 142
143 143 def get_ipython_cmd(as_string=False):
144 144 """
145 145 Return appropriate IPython command line name. By default, this will return
146 146 a list that can be used with subprocess.Popen, for example, but passing
147 147 `as_string=True` allows for returning the IPython command as a string.
148 148
149 149 Parameters
150 150 ----------
151 151 as_string: bool
152 152 Flag to allow to return the command as a string.
153 153 """
154 154 ipython_cmd = [sys.executable, "-m", "IPython"]
155 155
156 156 if as_string:
157 157 ipython_cmd = " ".join(ipython_cmd)
158 158
159 159 return ipython_cmd
160 160
161 161 def ipexec(fname, options=None, commands=()):
162 162 """Utility to call 'ipython filename'.
163 163
164 164 Starts IPython with a minimal and safe configuration to make startup as fast
165 165 as possible.
166 166
167 167 Note that this starts IPython in a subprocess!
168 168
169 169 Parameters
170 170 ----------
171 171 fname : str, Path
172 172 Name of file to be executed (should have .py or .ipy extension).
173 173
174 174 options : optional, list
175 175 Extra command-line flags to be passed to IPython.
176 176
177 177 commands : optional, list
178 178 Commands to send in on stdin
179 179
180 180 Returns
181 181 -------
182 182 ``(stdout, stderr)`` of ipython subprocess.
183 183 """
184 184 if options is None: options = []
185 185
186 186 cmdargs = default_argv() + options
187 187
188 188 test_dir = os.path.dirname(__file__)
189 189
190 190 ipython_cmd = get_ipython_cmd()
191 191 # Absolute path for filename
192 192 full_fname = os.path.join(test_dir, fname)
193 193 full_cmd = ipython_cmd + cmdargs + ['--', full_fname]
194 if sys.platform == "win32" and sys.version_info < (3, 8):
195 # subprocess.Popen does not support Path objects yet
196 full_cmd = list(map(str, full_cmd))
197 194 env = os.environ.copy()
198 195 # FIXME: ignore all warnings in ipexec while we have shims
199 196 # should we keep suppressing warnings here, even after removing shims?
200 197 env['PYTHONWARNINGS'] = 'ignore'
201 198 # env.pop('PYTHONWARNINGS', None) # Avoid extraneous warnings appearing on stderr
202 199 # Prevent coloring under PyCharm ("\x1b[0m" at the end of the stdout)
203 200 env.pop("PYCHARM_HOSTED", None)
204 201 for k, v in env.items():
205 202 # Debug a bizarre failure we've seen on Windows:
206 203 # TypeError: environment can only contain strings
207 204 if not isinstance(v, str):
208 205 print(k, v)
209 206 p = Popen(full_cmd, stdout=PIPE, stderr=PIPE, stdin=PIPE, env=env)
210 207 out, err = p.communicate(input=py3compat.encode('\n'.join(commands)) or None)
211 208 out, err = py3compat.decode(out), py3compat.decode(err)
212 209 # `import readline` causes 'ESC[?1034h' to be output sometimes,
213 210 # so strip that out before doing comparisons
214 211 if out:
215 212 out = re.sub(r'\x1b\[[^h]+h', '', out)
216 213 return out, err
217 214
218 215
219 216 def ipexec_validate(fname, expected_out, expected_err='',
220 217 options=None, commands=()):
221 218 """Utility to call 'ipython filename' and validate output/error.
222 219
223 220 This function raises an AssertionError if the validation fails.
224 221
225 222 Note that this starts IPython in a subprocess!
226 223
227 224 Parameters
228 225 ----------
229 226 fname : str, Path
230 227 Name of the file to be executed (should have .py or .ipy extension).
231 228
232 229 expected_out : str
233 230 Expected stdout of the process.
234 231
235 232 expected_err : optional, str
236 233 Expected stderr of the process.
237 234
238 235 options : optional, list
239 236 Extra command-line flags to be passed to IPython.
240 237
241 238 Returns
242 239 -------
243 240 None
244 241 """
245 242
246 243 out, err = ipexec(fname, options, commands)
247 244 #print 'OUT', out # dbg
248 245 #print 'ERR', err # dbg
249 246 # If there are any errors, we must check those before stdout, as they may be
250 247 # more informative than simply having an empty stdout.
251 248 if err:
252 249 if expected_err:
253 250 assert err.strip().splitlines() == expected_err.strip().splitlines()
254 251 else:
255 252 raise ValueError('Running file %r produced error: %r' %
256 253 (fname, err))
257 254 # If no errors or output on stderr was expected, match stdout
258 255 assert out.strip().splitlines() == expected_out.strip().splitlines()
259 256
260 257
261 258 class TempFileMixin(unittest.TestCase):
262 259 """Utility class to create temporary Python/IPython files.
263 260
264 261 Meant as a mixin class for test cases."""
265 262
266 263 def mktmp(self, src, ext='.py'):
267 264 """Make a valid python temp file."""
268 265 fname = temp_pyfile(src, ext)
269 266 if not hasattr(self, 'tmps'):
270 267 self.tmps=[]
271 268 self.tmps.append(fname)
272 269 self.fname = fname
273 270
274 271 def tearDown(self):
275 272 # If the tmpfile wasn't made because of skipped tests, like in
276 273 # win32, there's nothing to cleanup.
277 274 if hasattr(self, 'tmps'):
278 275 for fname in self.tmps:
279 276 # If the tmpfile wasn't made because of skipped tests, like in
280 277 # win32, there's nothing to cleanup.
281 278 try:
282 279 os.unlink(fname)
283 280 except:
284 281 # On Windows, even though we close the file, we still can't
285 282 # delete it. I have no clue why
286 283 pass
287 284
288 285 def __enter__(self):
289 286 return self
290 287
291 288 def __exit__(self, exc_type, exc_value, traceback):
292 289 self.tearDown()
293 290
294 291
295 292 pair_fail_msg = ("Testing {0}\n\n"
296 293 "In:\n"
297 294 " {1!r}\n"
298 295 "Expected:\n"
299 296 " {2!r}\n"
300 297 "Got:\n"
301 298 " {3!r}\n")
302 299 def check_pairs(func, pairs):
303 300 """Utility function for the common case of checking a function with a
304 301 sequence of input/output pairs.
305 302
306 303 Parameters
307 304 ----------
308 305 func : callable
309 306 The function to be tested. Should accept a single argument.
310 307 pairs : iterable
311 308 A list of (input, expected_output) tuples.
312 309
313 310 Returns
314 311 -------
315 312 None. Raises an AssertionError if any output does not match the expected
316 313 value.
317 314 """
318 315 name = getattr(func, "func_name", getattr(func, "__name__", "<unknown>"))
319 316 for inp, expected in pairs:
320 317 out = func(inp)
321 318 assert out == expected, pair_fail_msg.format(name, inp, expected, out)
322 319
323 320
324 321 MyStringIO = StringIO
325 322
326 323 _re_type = type(re.compile(r''))
327 324
328 325 notprinted_msg = """Did not find {0!r} in printed output (on {1}):
329 326 -------
330 327 {2!s}
331 328 -------
332 329 """
333 330
334 331 class AssertPrints(object):
335 332 """Context manager for testing that code prints certain text.
336 333
337 334 Examples
338 335 --------
339 336 >>> with AssertPrints("abc", suppress=False):
340 337 ... print("abcd")
341 338 ... print("def")
342 339 ...
343 340 abcd
344 341 def
345 342 """
346 343 def __init__(self, s, channel='stdout', suppress=True):
347 344 self.s = s
348 345 if isinstance(self.s, (str, _re_type)):
349 346 self.s = [self.s]
350 347 self.channel = channel
351 348 self.suppress = suppress
352 349
353 350 def __enter__(self):
354 351 self.orig_stream = getattr(sys, self.channel)
355 352 self.buffer = MyStringIO()
356 353 self.tee = Tee(self.buffer, channel=self.channel)
357 354 setattr(sys, self.channel, self.buffer if self.suppress else self.tee)
358 355
359 356 def __exit__(self, etype, value, traceback):
360 357 try:
361 358 if value is not None:
362 359 # If an error was raised, don't check anything else
363 360 return False
364 361 self.tee.flush()
365 362 setattr(sys, self.channel, self.orig_stream)
366 363 printed = self.buffer.getvalue()
367 364 for s in self.s:
368 365 if isinstance(s, _re_type):
369 366 assert s.search(printed), notprinted_msg.format(s.pattern, self.channel, printed)
370 367 else:
371 368 assert s in printed, notprinted_msg.format(s, self.channel, printed)
372 369 return False
373 370 finally:
374 371 self.tee.close()
375 372
376 373 printed_msg = """Found {0!r} in printed output (on {1}):
377 374 -------
378 375 {2!s}
379 376 -------
380 377 """
381 378
382 379 class AssertNotPrints(AssertPrints):
383 380 """Context manager for checking that certain output *isn't* produced.
384 381
385 382 Counterpart of AssertPrints"""
386 383 def __exit__(self, etype, value, traceback):
387 384 try:
388 385 if value is not None:
389 386 # If an error was raised, don't check anything else
390 387 self.tee.close()
391 388 return False
392 389 self.tee.flush()
393 390 setattr(sys, self.channel, self.orig_stream)
394 391 printed = self.buffer.getvalue()
395 392 for s in self.s:
396 393 if isinstance(s, _re_type):
397 394 assert not s.search(printed),printed_msg.format(
398 395 s.pattern, self.channel, printed)
399 396 else:
400 397 assert s not in printed, printed_msg.format(
401 398 s, self.channel, printed)
402 399 return False
403 400 finally:
404 401 self.tee.close()
405 402
406 403 @contextmanager
407 404 def mute_warn():
408 405 from IPython.utils import warn
409 406 save_warn = warn.warn
410 407 warn.warn = lambda *a, **kw: None
411 408 try:
412 409 yield
413 410 finally:
414 411 warn.warn = save_warn
415 412
416 413 @contextmanager
417 414 def make_tempfile(name):
418 415 """ Create an empty, named, temporary file for the duration of the context.
419 416 """
420 417 open(name, 'w').close()
421 418 try:
422 419 yield
423 420 finally:
424 421 os.unlink(name)
425 422
426 423 def fake_input(inputs):
427 424 """Temporarily replace the input() function to return the given values
428 425
429 426 Use as a context manager:
430 427
431 428 with fake_input(['result1', 'result2']):
432 429 ...
433 430
434 431 Values are returned in order. If input() is called again after the last value
435 432 was used, EOFError is raised.
436 433 """
437 434 it = iter(inputs)
438 435 def mock_input(prompt=''):
439 436 try:
440 437 return next(it)
441 438 except StopIteration as e:
442 439 raise EOFError('No more inputs given') from e
443 440
444 441 return patch('builtins.input', mock_input)
445 442
446 443 def help_output_test(subcommand=''):
447 444 """test that `ipython [subcommand] -h` works"""
448 445 cmd = get_ipython_cmd() + [subcommand, '-h']
449 446 out, err, rc = get_output_error_code(cmd)
450 447 assert rc == 0, err
451 448 assert "Traceback" not in err
452 449 assert "Options" in out
453 450 assert "--help-all" in out
454 451 return out, err
455 452
456 453
457 454 def help_all_output_test(subcommand=''):
458 455 """test that `ipython [subcommand] --help-all` works"""
459 456 cmd = get_ipython_cmd() + [subcommand, '--help-all']
460 457 out, err, rc = get_output_error_code(cmd)
461 458 assert rc == 0, err
462 459 assert "Traceback" not in err
463 460 assert "Options" in out
464 461 assert "Class" in out
465 462 return out, err
466 463
@@ -1,502 +1,504 b''
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 unittest.mock import patch
14 14 from os.path import join, abspath
15 15 from importlib import reload
16 16
17 17 import pytest
18 18
19 19 import IPython
20 20 from IPython import paths
21 21 from IPython.testing import decorators as dec
22 from IPython.testing.decorators import (skip_if_not_win32, skip_win32,
22 from IPython.testing.decorators import (
23 skip_if_not_win32,
24 skip_win32,
23 25 onlyif_unicode_paths,
24 skip_win32_py38,)
26 )
25 27 from IPython.testing.tools import make_tempfile
26 28 from IPython.utils import path
27 29 from IPython.utils.tempdir import TemporaryDirectory
28 30
29 31
30 32 # Platform-dependent imports
31 33 try:
32 34 import winreg as wreg
33 35 except ImportError:
34 36 #Fake _winreg module on non-windows platforms
35 37 import types
36 38 wr_name = "winreg"
37 39 sys.modules[wr_name] = types.ModuleType(wr_name)
38 40 try:
39 41 import winreg as wreg
40 42 except ImportError:
41 43 import _winreg as wreg
42 44 #Add entries that needs to be stubbed by the testing code
43 45 (wreg.OpenKey, wreg.QueryValueEx,) = (None, None)
44 46
45 47 #-----------------------------------------------------------------------------
46 48 # Globals
47 49 #-----------------------------------------------------------------------------
48 50 env = os.environ
49 51 TMP_TEST_DIR = tempfile.mkdtemp()
50 52 HOME_TEST_DIR = join(TMP_TEST_DIR, "home_test_dir")
51 53 #
52 54 # Setup/teardown functions/decorators
53 55 #
54 56
55 57 def setup_module():
56 58 """Setup testenvironment for the module:
57 59
58 60 - Adds dummy home dir tree
59 61 """
60 62 # Do not mask exceptions here. In particular, catching WindowsError is a
61 63 # problem because that exception is only defined on Windows...
62 64 os.makedirs(os.path.join(HOME_TEST_DIR, 'ipython'))
63 65
64 66
65 67 def teardown_module():
66 68 """Teardown testenvironment for the module:
67 69
68 70 - Remove dummy home dir tree
69 71 """
70 72 # Note: we remove the parent test dir, which is the root of all test
71 73 # subdirs we may have created. Use shutil instead of os.removedirs, so
72 74 # that non-empty directories are all recursively removed.
73 75 shutil.rmtree(TMP_TEST_DIR)
74 76
75 77
76 78 def setup_environment():
77 79 """Setup testenvironment for some functions that are tested
78 80 in this module. In particular this functions stores attributes
79 81 and other things that we need to stub in some test functions.
80 82 This needs to be done on a function level and not module level because
81 83 each testfunction needs a pristine environment.
82 84 """
83 85 global oldstuff, platformstuff
84 86 oldstuff = (env.copy(), os.name, sys.platform, path.get_home_dir, IPython.__file__, os.getcwd())
85 87
86 88 def teardown_environment():
87 89 """Restore things that were remembered by the setup_environment function
88 90 """
89 91 (oldenv, os.name, sys.platform, path.get_home_dir, IPython.__file__, old_wd) = oldstuff
90 92 os.chdir(old_wd)
91 93 reload(path)
92 94
93 95 for key in list(env):
94 96 if key not in oldenv:
95 97 del env[key]
96 98 env.update(oldenv)
97 99 if hasattr(sys, 'frozen'):
98 100 del sys.frozen
99 101
100 102
101 103 # Build decorator that uses the setup_environment/setup_environment
102 104 @pytest.fixture
103 105 def environment():
104 106 setup_environment()
105 107 yield
106 108 teardown_environment()
107 109
108 110
109 111 with_environment = pytest.mark.usefixtures("environment")
110 112
111 113
112 114 @skip_if_not_win32
113 115 @with_environment
114 116 def test_get_home_dir_1():
115 117 """Testcase for py2exe logic, un-compressed lib
116 118 """
117 119 unfrozen = path.get_home_dir()
118 120 sys.frozen = True
119 121
120 122 #fake filename for IPython.__init__
121 123 IPython.__file__ = abspath(join(HOME_TEST_DIR, "Lib/IPython/__init__.py"))
122 124
123 125 home_dir = path.get_home_dir()
124 126 assert home_dir == unfrozen
125 127
126 128
127 129 @skip_if_not_win32
128 130 @with_environment
129 131 def test_get_home_dir_2():
130 132 """Testcase for py2exe logic, compressed lib
131 133 """
132 134 unfrozen = path.get_home_dir()
133 135 sys.frozen = True
134 136 #fake filename for IPython.__init__
135 137 IPython.__file__ = abspath(join(HOME_TEST_DIR, "Library.zip/IPython/__init__.py")).lower()
136 138
137 139 home_dir = path.get_home_dir(True)
138 140 assert home_dir == unfrozen
139 141
140 142
141 @skip_win32_py38
143 @skip_win32
142 144 @with_environment
143 145 def test_get_home_dir_3():
144 146 """get_home_dir() uses $HOME if set"""
145 147 env["HOME"] = HOME_TEST_DIR
146 148 home_dir = path.get_home_dir(True)
147 149 # get_home_dir expands symlinks
148 150 assert home_dir == os.path.realpath(env["HOME"])
149 151
150 152
151 153 @with_environment
152 154 def test_get_home_dir_4():
153 155 """get_home_dir() still works if $HOME is not set"""
154 156
155 157 if 'HOME' in env: del env['HOME']
156 158 # this should still succeed, but we don't care what the answer is
157 159 home = path.get_home_dir(False)
158 160
159 @skip_win32_py38
161 @skip_win32
160 162 @with_environment
161 163 def test_get_home_dir_5():
162 164 """raise HomeDirError if $HOME is specified, but not a writable dir"""
163 165 env['HOME'] = abspath(HOME_TEST_DIR+'garbage')
164 166 # set os.name = posix, to prevent My Documents fallback on Windows
165 167 os.name = 'posix'
166 168 pytest.raises(path.HomeDirError, path.get_home_dir, True)
167 169
168 170 # Should we stub wreg fully so we can run the test on all platforms?
169 171 @skip_if_not_win32
170 172 @with_environment
171 173 def test_get_home_dir_8():
172 174 """Using registry hack for 'My Documents', os=='nt'
173 175
174 176 HOMESHARE, HOMEDRIVE, HOMEPATH, USERPROFILE and others are missing.
175 177 """
176 178 os.name = 'nt'
177 179 # Remove from stub environment all keys that may be set
178 180 for key in ['HOME', 'HOMESHARE', 'HOMEDRIVE', 'HOMEPATH', 'USERPROFILE']:
179 181 env.pop(key, None)
180 182
181 183 class key:
182 184 def __enter__(self):
183 185 pass
184 186 def Close(self):
185 187 pass
186 188 def __exit__(*args, **kwargs):
187 189 pass
188 190
189 191 with patch.object(wreg, 'OpenKey', return_value=key()), \
190 192 patch.object(wreg, 'QueryValueEx', return_value=[abspath(HOME_TEST_DIR)]):
191 193 home_dir = path.get_home_dir()
192 194 assert home_dir == abspath(HOME_TEST_DIR)
193 195
194 196 @with_environment
195 197 def test_get_xdg_dir_0():
196 198 """test_get_xdg_dir_0, check xdg_dir"""
197 199 reload(path)
198 200 path._writable_dir = lambda path: True
199 201 path.get_home_dir = lambda : 'somewhere'
200 202 os.name = "posix"
201 203 sys.platform = "linux2"
202 204 env.pop('IPYTHON_DIR', None)
203 205 env.pop('IPYTHONDIR', None)
204 206 env.pop('XDG_CONFIG_HOME', None)
205 207
206 208 assert path.get_xdg_dir() == os.path.join("somewhere", ".config")
207 209
208 210
209 211 @with_environment
210 212 def test_get_xdg_dir_1():
211 213 """test_get_xdg_dir_1, check nonexistent xdg_dir"""
212 214 reload(path)
213 215 path.get_home_dir = lambda : HOME_TEST_DIR
214 216 os.name = "posix"
215 217 sys.platform = "linux2"
216 218 env.pop('IPYTHON_DIR', None)
217 219 env.pop('IPYTHONDIR', None)
218 220 env.pop('XDG_CONFIG_HOME', None)
219 221 assert path.get_xdg_dir() is None
220 222
221 223 @with_environment
222 224 def test_get_xdg_dir_2():
223 225 """test_get_xdg_dir_2, check xdg_dir default to ~/.config"""
224 226 reload(path)
225 227 path.get_home_dir = lambda : HOME_TEST_DIR
226 228 os.name = "posix"
227 229 sys.platform = "linux2"
228 230 env.pop('IPYTHON_DIR', None)
229 231 env.pop('IPYTHONDIR', None)
230 232 env.pop('XDG_CONFIG_HOME', None)
231 233 cfgdir=os.path.join(path.get_home_dir(), '.config')
232 234 if not os.path.exists(cfgdir):
233 235 os.makedirs(cfgdir)
234 236
235 237 assert path.get_xdg_dir() == cfgdir
236 238
237 239 @with_environment
238 240 def test_get_xdg_dir_3():
239 241 """test_get_xdg_dir_3, check xdg_dir not used on OS X"""
240 242 reload(path)
241 243 path.get_home_dir = lambda : HOME_TEST_DIR
242 244 os.name = "posix"
243 245 sys.platform = "darwin"
244 246 env.pop('IPYTHON_DIR', None)
245 247 env.pop('IPYTHONDIR', None)
246 248 env.pop('XDG_CONFIG_HOME', None)
247 249 cfgdir=os.path.join(path.get_home_dir(), '.config')
248 250 os.makedirs(cfgdir, exist_ok=True)
249 251
250 252 assert path.get_xdg_dir() is None
251 253
252 254 def test_filefind():
253 255 """Various tests for filefind"""
254 256 f = tempfile.NamedTemporaryFile()
255 257 # print 'fname:',f.name
256 258 alt_dirs = paths.get_ipython_dir()
257 259 t = path.filefind(f.name, alt_dirs)
258 260 # print 'found:',t
259 261
260 262
261 263 @dec.skip_if_not_win32
262 264 def test_get_long_path_name_win32():
263 265 with TemporaryDirectory() as tmpdir:
264 266
265 267 # Make a long path. Expands the path of tmpdir prematurely as it may already have a long
266 268 # path component, so ensure we include the long form of it
267 269 long_path = os.path.join(path.get_long_path_name(tmpdir), 'this is my long path name')
268 270 os.makedirs(long_path)
269 271
270 272 # Test to see if the short path evaluates correctly.
271 273 short_path = os.path.join(tmpdir, 'THISIS~1')
272 274 evaluated_path = path.get_long_path_name(short_path)
273 275 assert evaluated_path.lower() == long_path.lower()
274 276
275 277
276 278 @dec.skip_win32
277 279 def test_get_long_path_name():
278 280 p = path.get_long_path_name("/usr/local")
279 281 assert p == "/usr/local"
280 282
281 283
282 284 class TestRaiseDeprecation(unittest.TestCase):
283 285
284 286 @dec.skip_win32 # can't create not-user-writable dir on win
285 287 @with_environment
286 288 def test_not_writable_ipdir(self):
287 289 tmpdir = tempfile.mkdtemp()
288 290 os.name = "posix"
289 291 env.pop('IPYTHON_DIR', None)
290 292 env.pop('IPYTHONDIR', None)
291 293 env.pop('XDG_CONFIG_HOME', None)
292 294 env['HOME'] = tmpdir
293 295 ipdir = os.path.join(tmpdir, '.ipython')
294 296 os.mkdir(ipdir, 0o555)
295 297 try:
296 298 open(os.path.join(ipdir, "_foo_"), 'w').close()
297 299 except IOError:
298 300 pass
299 301 else:
300 302 # I can still write to an unwritable dir,
301 303 # assume I'm root and skip the test
302 304 pytest.skip("I can't create directories that I can't write to")
303 305
304 306 with self.assertWarnsRegex(UserWarning, 'is not a writable location'):
305 307 ipdir = paths.get_ipython_dir()
306 308 env.pop('IPYTHON_DIR', None)
307 309
308 310 @with_environment
309 311 def test_get_py_filename():
310 312 os.chdir(TMP_TEST_DIR)
311 313 with make_tempfile("foo.py"):
312 314 assert path.get_py_filename("foo.py") == "foo.py"
313 315 assert path.get_py_filename("foo") == "foo.py"
314 316 with make_tempfile("foo"):
315 317 assert path.get_py_filename("foo") == "foo"
316 318 pytest.raises(IOError, path.get_py_filename, "foo.py")
317 319 pytest.raises(IOError, path.get_py_filename, "foo")
318 320 pytest.raises(IOError, path.get_py_filename, "foo.py")
319 321 true_fn = "foo with spaces.py"
320 322 with make_tempfile(true_fn):
321 323 assert path.get_py_filename("foo with spaces") == true_fn
322 324 assert path.get_py_filename("foo with spaces.py") == true_fn
323 325 pytest.raises(IOError, path.get_py_filename, '"foo with spaces.py"')
324 326 pytest.raises(IOError, path.get_py_filename, "'foo with spaces.py'")
325 327
326 328 @onlyif_unicode_paths
327 329 def test_unicode_in_filename():
328 330 """When a file doesn't exist, the exception raised should be safe to call
329 331 str() on - i.e. in Python 2 it must only have ASCII characters.
330 332
331 333 https://github.com/ipython/ipython/issues/875
332 334 """
333 335 try:
334 336 # these calls should not throw unicode encode exceptions
335 337 path.get_py_filename('fooéè.py')
336 338 except IOError as ex:
337 339 str(ex)
338 340
339 341
340 342 class TestShellGlob(unittest.TestCase):
341 343
342 344 @classmethod
343 345 def setUpClass(cls):
344 346 cls.filenames_start_with_a = ['a0', 'a1', 'a2']
345 347 cls.filenames_end_with_b = ['0b', '1b', '2b']
346 348 cls.filenames = cls.filenames_start_with_a + cls.filenames_end_with_b
347 349 cls.tempdir = TemporaryDirectory()
348 350 td = cls.tempdir.name
349 351
350 352 with cls.in_tempdir():
351 353 # Create empty files
352 354 for fname in cls.filenames:
353 355 open(os.path.join(td, fname), 'w').close()
354 356
355 357 @classmethod
356 358 def tearDownClass(cls):
357 359 cls.tempdir.cleanup()
358 360
359 361 @classmethod
360 362 @contextmanager
361 363 def in_tempdir(cls):
362 364 save = os.getcwd()
363 365 try:
364 366 os.chdir(cls.tempdir.name)
365 367 yield
366 368 finally:
367 369 os.chdir(save)
368 370
369 371 def check_match(self, patterns, matches):
370 372 with self.in_tempdir():
371 373 # glob returns unordered list. that's why sorted is required.
372 374 assert sorted(path.shellglob(patterns)) == sorted(matches)
373 375
374 376 def common_cases(self):
375 377 return [
376 378 (['*'], self.filenames),
377 379 (['a*'], self.filenames_start_with_a),
378 380 (['*c'], ['*c']),
379 381 (['*', 'a*', '*b', '*c'], self.filenames
380 382 + self.filenames_start_with_a
381 383 + self.filenames_end_with_b
382 384 + ['*c']),
383 385 (['a[012]'], self.filenames_start_with_a),
384 386 ]
385 387
386 388 @skip_win32
387 389 def test_match_posix(self):
388 390 for (patterns, matches) in self.common_cases() + [
389 391 ([r'\*'], ['*']),
390 392 ([r'a\*', 'a*'], ['a*'] + self.filenames_start_with_a),
391 393 ([r'a\[012]'], ['a[012]']),
392 394 ]:
393 395 self.check_match(patterns, matches)
394 396
395 397 @skip_if_not_win32
396 398 def test_match_windows(self):
397 399 for (patterns, matches) in self.common_cases() + [
398 400 # In windows, backslash is interpreted as path
399 401 # separator. Therefore, you can't escape glob
400 402 # using it.
401 403 ([r'a\*', 'a*'], [r'a\*'] + self.filenames_start_with_a),
402 404 ([r'a\[012]'], [r'a\[012]']),
403 405 ]:
404 406 self.check_match(patterns, matches)
405 407
406 408
407 409 # TODO : pytest.mark.parametrise once nose is gone.
408 410 def test_unescape_glob():
409 411 assert path.unescape_glob(r"\*\[\!\]\?") == "*[!]?"
410 412 assert path.unescape_glob(r"\\*") == r"\*"
411 413 assert path.unescape_glob(r"\\\*") == r"\*"
412 414 assert path.unescape_glob(r"\\a") == r"\a"
413 415 assert path.unescape_glob(r"\a") == r"\a"
414 416
415 417
416 418 @onlyif_unicode_paths
417 419 def test_ensure_dir_exists():
418 420 with TemporaryDirectory() as td:
419 421 d = os.path.join(td, 'βˆ‚ir')
420 422 path.ensure_dir_exists(d) # create it
421 423 assert os.path.isdir(d)
422 424 path.ensure_dir_exists(d) # no-op
423 425 f = os.path.join(td, 'Ζ’ile')
424 426 open(f, 'w').close() # touch
425 427 with pytest.raises(IOError):
426 428 path.ensure_dir_exists(f)
427 429
428 430 class TestLinkOrCopy(unittest.TestCase):
429 431 def setUp(self):
430 432 self.tempdir = TemporaryDirectory()
431 433 self.src = self.dst("src")
432 434 with open(self.src, "w") as f:
433 435 f.write("Hello, world!")
434 436
435 437 def tearDown(self):
436 438 self.tempdir.cleanup()
437 439
438 440 def dst(self, *args):
439 441 return os.path.join(self.tempdir.name, *args)
440 442
441 443 def assert_inode_not_equal(self, a, b):
442 444 assert (
443 445 os.stat(a).st_ino != os.stat(b).st_ino
444 446 ), "%r and %r do reference the same indoes" % (a, b)
445 447
446 448 def assert_inode_equal(self, a, b):
447 449 assert (
448 450 os.stat(a).st_ino == os.stat(b).st_ino
449 451 ), "%r and %r do not reference the same indoes" % (a, b)
450 452
451 453 def assert_content_equal(self, a, b):
452 454 with open(a) as a_f:
453 455 with open(b) as b_f:
454 456 assert a_f.read() == b_f.read()
455 457
456 458 @skip_win32
457 459 def test_link_successful(self):
458 460 dst = self.dst("target")
459 461 path.link_or_copy(self.src, dst)
460 462 self.assert_inode_equal(self.src, dst)
461 463
462 464 @skip_win32
463 465 def test_link_into_dir(self):
464 466 dst = self.dst("some_dir")
465 467 os.mkdir(dst)
466 468 path.link_or_copy(self.src, dst)
467 469 expected_dst = self.dst("some_dir", os.path.basename(self.src))
468 470 self.assert_inode_equal(self.src, expected_dst)
469 471
470 472 @skip_win32
471 473 def test_target_exists(self):
472 474 dst = self.dst("target")
473 475 open(dst, "w").close()
474 476 path.link_or_copy(self.src, dst)
475 477 self.assert_inode_equal(self.src, dst)
476 478
477 479 @skip_win32
478 480 def test_no_link(self):
479 481 real_link = os.link
480 482 try:
481 483 del os.link
482 484 dst = self.dst("target")
483 485 path.link_or_copy(self.src, dst)
484 486 self.assert_content_equal(self.src, dst)
485 487 self.assert_inode_not_equal(self.src, dst)
486 488 finally:
487 489 os.link = real_link
488 490
489 491 @skip_if_not_win32
490 492 def test_windows(self):
491 493 dst = self.dst("target")
492 494 path.link_or_copy(self.src, dst)
493 495 self.assert_content_equal(self.src, dst)
494 496
495 497 def test_link_twice(self):
496 498 # Linking the same file twice shouldn't leave duplicates around.
497 499 # See https://github.com/ipython/ipython/issues/6450
498 500 dst = self.dst('target')
499 501 path.link_or_copy(self.src, dst)
500 502 path.link_or_copy(self.src, dst)
501 503 self.assert_inode_equal(self.src, dst)
502 504 assert sorted(os.listdir(self.tempdir.name)) == ["src", "target"]
General Comments 0
You need to be logged in to leave comments. Login now