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