##// END OF EJS Templates
Also catch SyntaxErrors from InputTransformers in run_cell()...
Volker Braun -
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -0,0 +1,58 b''
1 # coding: utf-8
2 """Tests for the IPython terminal"""
3
4 import os
5 import tempfile
6 import shutil
7
8 import nose.tools as nt
9
10 from IPython.testing.tools import make_tempfile, ipexec
11
12
13 TEST_SYNTAX_ERROR_CMDS = """
14 from IPython.core.inputtransformer import InputTransformer
15
16 %cpaste
17 class SyntaxErrorTransformer(InputTransformer):
18
19 def push(self, line):
20 pos = line.find('syntaxerror')
21 if pos >= 0:
22 e = SyntaxError('input contains "syntaxerror"')
23 e.text = line
24 e.offset = pos + 1
25 raise e
26 return line
27
28 def reset(self):
29 pass
30 --
31
32 ip = get_ipython()
33 transformer = SyntaxErrorTransformer()
34 ip.input_splitter.python_line_transforms.append(transformer)
35 ip.input_transformer_manager.python_line_transforms.append(transformer)
36
37 # now the actual commands
38 1234
39 2345 # syntaxerror <- triggered here
40 3456
41 """
42
43 def test_syntax_error():
44 """Check that the IPython terminal does not abort if a SyntaxError is raised in an InputTransformer"""
45 try:
46 tmp = tempfile.mkdtemp()
47 filename = os.path.join(tmp, 'test_syntax_error.py')
48 with open(filename, 'w') as f:
49 f.write(TEST_SYNTAX_ERROR_CMDS)
50 out, err = ipexec(filename, pipe=True)
51 nt.assert_equal(err, '')
52 nt.assert_in('1234', out)
53 nt.assert_in(' 2345 # syntaxerror <- triggered here', out)
54 nt.assert_in(' ^', out)
55 nt.assert_in('SyntaxError: input contains "syntaxerror"', out)
56 nt.assert_in('3456', out)
57 finally:
58 shutil.rmtree(tmp)
@@ -1,535 +1,538 b''
1 1 import abc
2 2 import functools
3 3 import re
4 4
5 5 from IPython.core.splitinput import LineInfo
6 6 from IPython.utils import tokenize2
7 7 from IPython.utils.openpy import cookie_comment_re
8 8 from IPython.utils.py3compat import with_metaclass, PY3
9 9 from IPython.utils.tokenize2 import generate_tokens, untokenize, TokenError
10 10
11 11 if PY3:
12 12 from io import StringIO
13 13 else:
14 14 from StringIO import StringIO
15 15
16 16 #-----------------------------------------------------------------------------
17 17 # Globals
18 18 #-----------------------------------------------------------------------------
19 19
20 20 # The escape sequences that define the syntax transformations IPython will
21 21 # apply to user input. These can NOT be just changed here: many regular
22 22 # expressions and other parts of the code may use their hardcoded values, and
23 23 # for all intents and purposes they constitute the 'IPython syntax', so they
24 24 # should be considered fixed.
25 25
26 26 ESC_SHELL = '!' # Send line to underlying system shell
27 27 ESC_SH_CAP = '!!' # Send line to system shell and capture output
28 28 ESC_HELP = '?' # Find information about object
29 29 ESC_HELP2 = '??' # Find extra-detailed information about object
30 30 ESC_MAGIC = '%' # Call magic function
31 31 ESC_MAGIC2 = '%%' # Call cell-magic function
32 32 ESC_QUOTE = ',' # Split args on whitespace, quote each as string and call
33 33 ESC_QUOTE2 = ';' # Quote all args as a single string, call
34 34 ESC_PAREN = '/' # Call first argument with rest of line as arguments
35 35
36 36 ESC_SEQUENCES = [ESC_SHELL, ESC_SH_CAP, ESC_HELP ,\
37 37 ESC_HELP2, ESC_MAGIC, ESC_MAGIC2,\
38 38 ESC_QUOTE, ESC_QUOTE2, ESC_PAREN ]
39 39
40 40
41 41 class InputTransformer(with_metaclass(abc.ABCMeta, object)):
42 42 """Abstract base class for line-based input transformers."""
43 43
44 44 @abc.abstractmethod
45 45 def push(self, line):
46 46 """Send a line of input to the transformer, returning the transformed
47 47 input or None if the transformer is waiting for more input.
48 48
49 49 Must be overridden by subclasses.
50
51 Implementations may raise ``SyntaxError`` if the input is invalid. No
52 other exceptions may be raised.
50 53 """
51 54 pass
52 55
53 56 @abc.abstractmethod
54 57 def reset(self):
55 58 """Return, transformed any lines that the transformer has accumulated,
56 59 and reset its internal state.
57 60
58 61 Must be overridden by subclasses.
59 62 """
60 63 pass
61 64
62 65 @classmethod
63 66 def wrap(cls, func):
64 67 """Can be used by subclasses as a decorator, to return a factory that
65 68 will allow instantiation with the decorated object.
66 69 """
67 70 @functools.wraps(func)
68 71 def transformer_factory(**kwargs):
69 72 return cls(func, **kwargs)
70 73
71 74 return transformer_factory
72 75
73 76 class StatelessInputTransformer(InputTransformer):
74 77 """Wrapper for a stateless input transformer implemented as a function."""
75 78 def __init__(self, func):
76 79 self.func = func
77 80
78 81 def __repr__(self):
79 82 return "StatelessInputTransformer(func={0!r})".format(self.func)
80 83
81 84 def push(self, line):
82 85 """Send a line of input to the transformer, returning the
83 86 transformed input."""
84 87 return self.func(line)
85 88
86 89 def reset(self):
87 90 """No-op - exists for compatibility."""
88 91 pass
89 92
90 93 class CoroutineInputTransformer(InputTransformer):
91 94 """Wrapper for an input transformer implemented as a coroutine."""
92 95 def __init__(self, coro, **kwargs):
93 96 # Prime it
94 97 self.coro = coro(**kwargs)
95 98 next(self.coro)
96 99
97 100 def __repr__(self):
98 101 return "CoroutineInputTransformer(coro={0!r})".format(self.coro)
99 102
100 103 def push(self, line):
101 104 """Send a line of input to the transformer, returning the
102 105 transformed input or None if the transformer is waiting for more
103 106 input.
104 107 """
105 108 return self.coro.send(line)
106 109
107 110 def reset(self):
108 111 """Return, transformed any lines that the transformer has
109 112 accumulated, and reset its internal state.
110 113 """
111 114 return self.coro.send(None)
112 115
113 116 class TokenInputTransformer(InputTransformer):
114 117 """Wrapper for a token-based input transformer.
115 118
116 119 func should accept a list of tokens (5-tuples, see tokenize docs), and
117 120 return an iterable which can be passed to tokenize.untokenize().
118 121 """
119 122 def __init__(self, func):
120 123 self.func = func
121 124 self.current_line = ""
122 125 self.line_used = False
123 126 self.reset_tokenizer()
124 127
125 128 def reset_tokenizer(self):
126 129 self.tokenizer = generate_tokens(self.get_line)
127 130
128 131 def get_line(self):
129 132 if self.line_used:
130 133 raise TokenError
131 134 self.line_used = True
132 135 return self.current_line
133 136
134 137 def push(self, line):
135 138 self.current_line += line + "\n"
136 139 if self.current_line.isspace():
137 140 return self.reset()
138 141
139 142 self.line_used = False
140 143 tokens = []
141 144 stop_at_NL = False
142 145 try:
143 146 for intok in self.tokenizer:
144 147 tokens.append(intok)
145 148 t = intok[0]
146 149 if t == tokenize2.NEWLINE or (stop_at_NL and t == tokenize2.NL):
147 150 # Stop before we try to pull a line we don't have yet
148 151 break
149 152 elif t == tokenize2.ERRORTOKEN:
150 153 stop_at_NL = True
151 154 except TokenError:
152 155 # Multi-line statement - stop and try again with the next line
153 156 self.reset_tokenizer()
154 157 return None
155 158
156 159 return self.output(tokens)
157 160
158 161 def output(self, tokens):
159 162 self.current_line = ""
160 163 self.reset_tokenizer()
161 164 return untokenize(self.func(tokens)).rstrip('\n')
162 165
163 166 def reset(self):
164 167 l = self.current_line
165 168 self.current_line = ""
166 169 self.reset_tokenizer()
167 170 if l:
168 171 return l.rstrip('\n')
169 172
170 173 class assemble_python_lines(TokenInputTransformer):
171 174 def __init__(self):
172 175 super(assemble_python_lines, self).__init__(None)
173 176
174 177 def output(self, tokens):
175 178 return self.reset()
176 179
177 180 @CoroutineInputTransformer.wrap
178 181 def assemble_logical_lines():
179 182 """Join lines following explicit line continuations (\)"""
180 183 line = ''
181 184 while True:
182 185 line = (yield line)
183 186 if not line or line.isspace():
184 187 continue
185 188
186 189 parts = []
187 190 while line is not None:
188 191 if line.endswith('\\') and (not has_comment(line)):
189 192 parts.append(line[:-1])
190 193 line = (yield None) # Get another line
191 194 else:
192 195 parts.append(line)
193 196 break
194 197
195 198 # Output
196 199 line = ''.join(parts)
197 200
198 201 # Utilities
199 202 def _make_help_call(target, esc, lspace, next_input=None):
200 203 """Prepares a pinfo(2)/psearch call from a target name and the escape
201 204 (i.e. ? or ??)"""
202 205 method = 'pinfo2' if esc == '??' \
203 206 else 'psearch' if '*' in target \
204 207 else 'pinfo'
205 208 arg = " ".join([method, target])
206 209 if next_input is None:
207 210 return '%sget_ipython().magic(%r)' % (lspace, arg)
208 211 else:
209 212 return '%sget_ipython().set_next_input(%r);get_ipython().magic(%r)' % \
210 213 (lspace, next_input, arg)
211 214
212 215 # These define the transformations for the different escape characters.
213 216 def _tr_system(line_info):
214 217 "Translate lines escaped with: !"
215 218 cmd = line_info.line.lstrip().lstrip(ESC_SHELL)
216 219 return '%sget_ipython().system(%r)' % (line_info.pre, cmd)
217 220
218 221 def _tr_system2(line_info):
219 222 "Translate lines escaped with: !!"
220 223 cmd = line_info.line.lstrip()[2:]
221 224 return '%sget_ipython().getoutput(%r)' % (line_info.pre, cmd)
222 225
223 226 def _tr_help(line_info):
224 227 "Translate lines escaped with: ?/??"
225 228 # A naked help line should just fire the intro help screen
226 229 if not line_info.line[1:]:
227 230 return 'get_ipython().show_usage()'
228 231
229 232 return _make_help_call(line_info.ifun, line_info.esc, line_info.pre)
230 233
231 234 def _tr_magic(line_info):
232 235 "Translate lines escaped with: %"
233 236 tpl = '%sget_ipython().magic(%r)'
234 237 if line_info.line.startswith(ESC_MAGIC2):
235 238 return line_info.line
236 239 cmd = ' '.join([line_info.ifun, line_info.the_rest]).strip()
237 240 return tpl % (line_info.pre, cmd)
238 241
239 242 def _tr_quote(line_info):
240 243 "Translate lines escaped with: ,"
241 244 return '%s%s("%s")' % (line_info.pre, line_info.ifun,
242 245 '", "'.join(line_info.the_rest.split()) )
243 246
244 247 def _tr_quote2(line_info):
245 248 "Translate lines escaped with: ;"
246 249 return '%s%s("%s")' % (line_info.pre, line_info.ifun,
247 250 line_info.the_rest)
248 251
249 252 def _tr_paren(line_info):
250 253 "Translate lines escaped with: /"
251 254 return '%s%s(%s)' % (line_info.pre, line_info.ifun,
252 255 ", ".join(line_info.the_rest.split()))
253 256
254 257 tr = { ESC_SHELL : _tr_system,
255 258 ESC_SH_CAP : _tr_system2,
256 259 ESC_HELP : _tr_help,
257 260 ESC_HELP2 : _tr_help,
258 261 ESC_MAGIC : _tr_magic,
259 262 ESC_QUOTE : _tr_quote,
260 263 ESC_QUOTE2 : _tr_quote2,
261 264 ESC_PAREN : _tr_paren }
262 265
263 266 @StatelessInputTransformer.wrap
264 267 def escaped_commands(line):
265 268 """Transform escaped commands - %magic, !system, ?help + various autocalls.
266 269 """
267 270 if not line or line.isspace():
268 271 return line
269 272 lineinf = LineInfo(line)
270 273 if lineinf.esc not in tr:
271 274 return line
272 275
273 276 return tr[lineinf.esc](lineinf)
274 277
275 278 _initial_space_re = re.compile(r'\s*')
276 279
277 280 _help_end_re = re.compile(r"""(%{0,2}
278 281 [a-zA-Z_*][\w*]* # Variable name
279 282 (\.[a-zA-Z_*][\w*]*)* # .etc.etc
280 283 )
281 284 (\?\??)$ # ? or ??
282 285 """,
283 286 re.VERBOSE)
284 287
285 288 # Extra pseudotokens for multiline strings and data structures
286 289 _MULTILINE_STRING = object()
287 290 _MULTILINE_STRUCTURE = object()
288 291
289 292 def _line_tokens(line):
290 293 """Helper for has_comment and ends_in_comment_or_string."""
291 294 readline = StringIO(line).readline
292 295 toktypes = set()
293 296 try:
294 297 for t in generate_tokens(readline):
295 298 toktypes.add(t[0])
296 299 except TokenError as e:
297 300 # There are only two cases where a TokenError is raised.
298 301 if 'multi-line string' in e.args[0]:
299 302 toktypes.add(_MULTILINE_STRING)
300 303 else:
301 304 toktypes.add(_MULTILINE_STRUCTURE)
302 305 return toktypes
303 306
304 307 def has_comment(src):
305 308 """Indicate whether an input line has (i.e. ends in, or is) a comment.
306 309
307 310 This uses tokenize, so it can distinguish comments from # inside strings.
308 311
309 312 Parameters
310 313 ----------
311 314 src : string
312 315 A single line input string.
313 316
314 317 Returns
315 318 -------
316 319 comment : bool
317 320 True if source has a comment.
318 321 """
319 322 return (tokenize2.COMMENT in _line_tokens(src))
320 323
321 324 def ends_in_comment_or_string(src):
322 325 """Indicates whether or not an input line ends in a comment or within
323 326 a multiline string.
324 327
325 328 Parameters
326 329 ----------
327 330 src : string
328 331 A single line input string.
329 332
330 333 Returns
331 334 -------
332 335 comment : bool
333 336 True if source ends in a comment or multiline string.
334 337 """
335 338 toktypes = _line_tokens(src)
336 339 return (tokenize2.COMMENT in toktypes) or (_MULTILINE_STRING in toktypes)
337 340
338 341
339 342 @StatelessInputTransformer.wrap
340 343 def help_end(line):
341 344 """Translate lines with ?/?? at the end"""
342 345 m = _help_end_re.search(line)
343 346 if m is None or ends_in_comment_or_string(line):
344 347 return line
345 348 target = m.group(1)
346 349 esc = m.group(3)
347 350 lspace = _initial_space_re.match(line).group(0)
348 351
349 352 # If we're mid-command, put it back on the next prompt for the user.
350 353 next_input = line.rstrip('?') if line.strip() != m.group(0) else None
351 354
352 355 return _make_help_call(target, esc, lspace, next_input)
353 356
354 357
355 358 @CoroutineInputTransformer.wrap
356 359 def cellmagic(end_on_blank_line=False):
357 360 """Captures & transforms cell magics.
358 361
359 362 After a cell magic is started, this stores up any lines it gets until it is
360 363 reset (sent None).
361 364 """
362 365 tpl = 'get_ipython().run_cell_magic(%r, %r, %r)'
363 366 cellmagic_help_re = re.compile('%%\w+\?')
364 367 line = ''
365 368 while True:
366 369 line = (yield line)
367 370 # consume leading empty lines
368 371 while not line:
369 372 line = (yield line)
370 373
371 374 if not line.startswith(ESC_MAGIC2):
372 375 # This isn't a cell magic, idle waiting for reset then start over
373 376 while line is not None:
374 377 line = (yield line)
375 378 continue
376 379
377 380 if cellmagic_help_re.match(line):
378 381 # This case will be handled by help_end
379 382 continue
380 383
381 384 first = line
382 385 body = []
383 386 line = (yield None)
384 387 while (line is not None) and \
385 388 ((line.strip() != '') or not end_on_blank_line):
386 389 body.append(line)
387 390 line = (yield None)
388 391
389 392 # Output
390 393 magic_name, _, first = first.partition(' ')
391 394 magic_name = magic_name.lstrip(ESC_MAGIC2)
392 395 line = tpl % (magic_name, first, u'\n'.join(body))
393 396
394 397
395 398 def _strip_prompts(prompt_re, initial_re=None):
396 399 """Remove matching input prompts from a block of input.
397 400
398 401 Parameters
399 402 ----------
400 403 prompt_re : regular expression
401 404 A regular expression matching any input prompt (including continuation)
402 405 initial_re : regular expression, optional
403 406 A regular expression matching only the initial prompt, but not continuation.
404 407 If no initial expression is given, prompt_re will be used everywhere.
405 408 Used mainly for plain Python prompts, where the continuation prompt
406 409 ``...`` is a valid Python expression in Python 3, so shouldn't be stripped.
407 410
408 411 If initial_re and prompt_re differ,
409 412 only initial_re will be tested against the first line.
410 413 If any prompt is found on the first two lines,
411 414 prompts will be stripped from the rest of the block.
412 415 """
413 416 if initial_re is None:
414 417 initial_re = prompt_re
415 418 line = ''
416 419 while True:
417 420 line = (yield line)
418 421
419 422 # First line of cell
420 423 if line is None:
421 424 continue
422 425 out, n1 = initial_re.subn('', line, count=1)
423 426 line = (yield out)
424 427
425 428 if line is None:
426 429 continue
427 430 # check for any prompt on the second line of the cell,
428 431 # because people often copy from just after the first prompt,
429 432 # so we might not see it in the first line.
430 433 out, n2 = prompt_re.subn('', line, count=1)
431 434 line = (yield out)
432 435
433 436 if n1 or n2:
434 437 # Found a prompt in the first two lines - check for it in
435 438 # the rest of the cell as well.
436 439 while line is not None:
437 440 line = (yield prompt_re.sub('', line, count=1))
438 441
439 442 else:
440 443 # Prompts not in input - wait for reset
441 444 while line is not None:
442 445 line = (yield line)
443 446
444 447 @CoroutineInputTransformer.wrap
445 448 def classic_prompt():
446 449 """Strip the >>>/... prompts of the Python interactive shell."""
447 450 # FIXME: non-capturing version (?:...) usable?
448 451 prompt_re = re.compile(r'^(>>> ?|\.\.\. ?)')
449 452 initial_re = re.compile(r'^(>>> ?)')
450 453 return _strip_prompts(prompt_re, initial_re)
451 454
452 455 @CoroutineInputTransformer.wrap
453 456 def ipy_prompt():
454 457 """Strip IPython's In [1]:/...: prompts."""
455 458 # FIXME: non-capturing version (?:...) usable?
456 459 # FIXME: r'^(In \[\d+\]: | {3}\.{3,}: )' clearer?
457 460 prompt_re = re.compile(r'^(In \[\d+\]: |\ \ \ \.\.\.+: )')
458 461 return _strip_prompts(prompt_re)
459 462
460 463
461 464 @CoroutineInputTransformer.wrap
462 465 def leading_indent():
463 466 """Remove leading indentation.
464 467
465 468 If the first line starts with a spaces or tabs, the same whitespace will be
466 469 removed from each following line until it is reset.
467 470 """
468 471 space_re = re.compile(r'^[ \t]+')
469 472 line = ''
470 473 while True:
471 474 line = (yield line)
472 475
473 476 if line is None:
474 477 continue
475 478
476 479 m = space_re.match(line)
477 480 if m:
478 481 space = m.group(0)
479 482 while line is not None:
480 483 if line.startswith(space):
481 484 line = line[len(space):]
482 485 line = (yield line)
483 486 else:
484 487 # No leading spaces - wait for reset
485 488 while line is not None:
486 489 line = (yield line)
487 490
488 491
489 492 @CoroutineInputTransformer.wrap
490 493 def strip_encoding_cookie():
491 494 """Remove encoding comment if found in first two lines
492 495
493 496 If the first or second line has the `# coding: utf-8` comment,
494 497 it will be removed.
495 498 """
496 499 line = ''
497 500 while True:
498 501 line = (yield line)
499 502 # check comment on first two lines
500 503 for i in range(2):
501 504 if line is None:
502 505 break
503 506 if cookie_comment_re.match(line):
504 507 line = (yield "")
505 508 else:
506 509 line = (yield line)
507 510
508 511 # no-op on the rest of the cell
509 512 while line is not None:
510 513 line = (yield line)
511 514
512 515
513 516 assign_system_re = re.compile(r'(?P<lhs>(\s*)([\w\.]+)((\s*,\s*[\w\.]+)*))'
514 517 r'\s*=\s*!\s*(?P<cmd>.*)')
515 518 assign_system_template = '%s = get_ipython().getoutput(%r)'
516 519 @StatelessInputTransformer.wrap
517 520 def assign_from_system(line):
518 521 """Transform assignment from system commands (e.g. files = !ls)"""
519 522 m = assign_system_re.match(line)
520 523 if m is None:
521 524 return line
522 525
523 526 return assign_system_template % m.group('lhs', 'cmd')
524 527
525 528 assign_magic_re = re.compile(r'(?P<lhs>(\s*)([\w\.]+)((\s*,\s*[\w\.]+)*))'
526 529 r'\s*=\s*%\s*(?P<cmd>.*)')
527 530 assign_magic_template = '%s = get_ipython().magic(%r)'
528 531 @StatelessInputTransformer.wrap
529 532 def assign_from_magic(line):
530 533 """Transform assignment from magic commands (e.g. a = %who_ls)"""
531 534 m = assign_magic_re.match(line)
532 535 if m is None:
533 536 return line
534 537
535 538 return assign_magic_template % m.group('lhs', 'cmd')
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
@@ -1,97 +1,50 b''
1 1 # coding: utf-8
2 2 """Tests for IPython.core.application"""
3 3
4 4 import os
5 5 import tempfile
6 import shutil
7
8 import nose.tools as nt
9 6
10 7 from IPython.core.application import BaseIPythonApplication
11 8 from IPython.testing import decorators as dec
12 from IPython.testing.tools import make_tempfile, ipexec
13 9 from IPython.utils import py3compat
14 10
15 11 @dec.onlyif_unicode_paths
16 12 def test_unicode_cwd():
17 13 """Check that IPython starts with non-ascii characters in the path."""
18 14 wd = tempfile.mkdtemp(suffix=u"€")
19 15
20 16 old_wd = py3compat.getcwd()
21 17 os.chdir(wd)
22 18 #raise Exception(repr(py3compat.getcwd()))
23 19 try:
24 20 app = BaseIPythonApplication()
25 21 # The lines below are copied from Application.initialize()
26 22 app.init_profile_dir()
27 23 app.init_config_files()
28 24 app.load_config_file(suppress_errors=False)
29 25 finally:
30 26 os.chdir(old_wd)
31 27
32 28 @dec.onlyif_unicode_paths
33 29 def test_unicode_ipdir():
34 30 """Check that IPython starts with non-ascii characters in the IP dir."""
35 31 ipdir = tempfile.mkdtemp(suffix=u"€")
36 32
37 33 # Create the config file, so it tries to load it.
38 34 with open(os.path.join(ipdir, 'ipython_config.py'), "w") as f:
39 35 pass
40 36
41 37 old_ipdir1 = os.environ.pop("IPYTHONDIR", None)
42 38 old_ipdir2 = os.environ.pop("IPYTHON_DIR", None)
43 39 os.environ["IPYTHONDIR"] = py3compat.unicode_to_str(ipdir, "utf-8")
44 40 try:
45 41 app = BaseIPythonApplication()
46 42 # The lines below are copied from Application.initialize()
47 43 app.init_profile_dir()
48 44 app.init_config_files()
49 45 app.load_config_file(suppress_errors=False)
50 46 finally:
51 47 if old_ipdir1:
52 48 os.environ["IPYTHONDIR"] = old_ipdir1
53 49 if old_ipdir2:
54 50 os.environ["IPYTHONDIR"] = old_ipdir2
55
56
57
58 TEST_SYNTAX_ERROR_CMDS = """
59 from IPython.core.inputtransformer import InputTransformer
60
61 %cpaste
62 class SyntaxErrorTransformer(InputTransformer):
63
64 def push(self, line):
65 if 'syntaxerror' in line:
66 raise SyntaxError('in input '+line)
67 return line
68
69 def reset(self):
70 pass
71 --
72
73 ip = get_ipython()
74 transformer = SyntaxErrorTransformer()
75 ip.input_splitter.python_line_transforms.append(transformer)
76 ip.input_transformer_manager.python_line_transforms.append(transformer)
77
78 # now the actual commands
79 1234
80 2345 # syntaxerror <- triggered here
81 3456
82 """
83
84 def test_syntax_error():
85 """Check that IPython does not abort if a SyntaxError is raised in an InputTransformer"""
86 try:
87 tmp = tempfile.mkdtemp()
88 filename = os.path.join(tmp, 'test_syntax_error.py')
89 with open(filename, 'w') as f:
90 f.write(TEST_SYNTAX_ERROR_CMDS)
91 out, err = ipexec(filename, pipe=True)
92 nt.assert_equal(err, '')
93 nt.assert_in('1234', out)
94 nt.assert_in('SyntaxError: in input 2345 # syntaxerror <- triggered here', out)
95 nt.assert_in('3456', out)
96 finally:
97 shutil.rmtree(tmp)
@@ -1,677 +1,715 b''
1 1 # -*- coding: utf-8 -*-
2 2 """Tests for the key interactiveshell module.
3 3
4 4 Historically the main classes in interactiveshell have been under-tested. This
5 5 module should grow as many single-method tests as possible to trap many of the
6 6 recurring bugs we seem to encounter with high-level interaction.
7 7
8 8 Authors
9 9 -------
10 10 * Fernando Perez
11 11 """
12 12 #-----------------------------------------------------------------------------
13 13 # Copyright (C) 2011 The IPython Development Team
14 14 #
15 15 # Distributed under the terms of the BSD License. The full license is in
16 16 # the file COPYING, distributed as part of this software.
17 17 #-----------------------------------------------------------------------------
18 18
19 19 #-----------------------------------------------------------------------------
20 20 # Imports
21 21 #-----------------------------------------------------------------------------
22 22 # stdlib
23 23 import ast
24 24 import os
25 25 import signal
26 26 import shutil
27 27 import sys
28 28 import tempfile
29 29 import unittest
30 30 from os.path import join
31 31
32 32 # third-party
33 33 import nose.tools as nt
34 34
35 35 # Our own
36 from IPython.core.inputtransformer import InputTransformer
36 37 from IPython.testing.decorators import skipif, skip_win32, onlyif_unicode_paths
37 38 from IPython.testing import tools as tt
38 39 from IPython.utils import io
39 40 from IPython.utils import py3compat
40 41 from IPython.utils.py3compat import unicode_type, PY3
41 42
42 43 if PY3:
43 44 from io import StringIO
44 45 else:
45 46 from StringIO import StringIO
46 47
47 48 #-----------------------------------------------------------------------------
48 49 # Globals
49 50 #-----------------------------------------------------------------------------
50 51 # This is used by every single test, no point repeating it ad nauseam
51 52 ip = get_ipython()
52 53
53 54 #-----------------------------------------------------------------------------
54 55 # Tests
55 56 #-----------------------------------------------------------------------------
56 57
57 58 class InteractiveShellTestCase(unittest.TestCase):
58 59 def test_naked_string_cells(self):
59 60 """Test that cells with only naked strings are fully executed"""
60 61 # First, single-line inputs
61 62 ip.run_cell('"a"\n')
62 63 self.assertEqual(ip.user_ns['_'], 'a')
63 64 # And also multi-line cells
64 65 ip.run_cell('"""a\nb"""\n')
65 66 self.assertEqual(ip.user_ns['_'], 'a\nb')
66 67
67 68 def test_run_empty_cell(self):
68 69 """Just make sure we don't get a horrible error with a blank
69 70 cell of input. Yes, I did overlook that."""
70 71 old_xc = ip.execution_count
71 72 ip.run_cell('')
72 73 self.assertEqual(ip.execution_count, old_xc)
73 74
74 75 def test_run_cell_multiline(self):
75 76 """Multi-block, multi-line cells must execute correctly.
76 77 """
77 78 src = '\n'.join(["x=1",
78 79 "y=2",
79 80 "if 1:",
80 81 " x += 1",
81 82 " y += 1",])
82 83 ip.run_cell(src)
83 84 self.assertEqual(ip.user_ns['x'], 2)
84 85 self.assertEqual(ip.user_ns['y'], 3)
85 86
86 87 def test_multiline_string_cells(self):
87 88 "Code sprinkled with multiline strings should execute (GH-306)"
88 89 ip.run_cell('tmp=0')
89 90 self.assertEqual(ip.user_ns['tmp'], 0)
90 91 ip.run_cell('tmp=1;"""a\nb"""\n')
91 92 self.assertEqual(ip.user_ns['tmp'], 1)
92 93
93 94 def test_dont_cache_with_semicolon(self):
94 95 "Ending a line with semicolon should not cache the returned object (GH-307)"
95 96 oldlen = len(ip.user_ns['Out'])
96 97 a = ip.run_cell('1;', store_history=True)
97 98 newlen = len(ip.user_ns['Out'])
98 99 self.assertEqual(oldlen, newlen)
99 100 #also test the default caching behavior
100 101 ip.run_cell('1', store_history=True)
101 102 newlen = len(ip.user_ns['Out'])
102 103 self.assertEqual(oldlen+1, newlen)
103 104
104 105 def test_In_variable(self):
105 106 "Verify that In variable grows with user input (GH-284)"
106 107 oldlen = len(ip.user_ns['In'])
107 108 ip.run_cell('1;', store_history=True)
108 109 newlen = len(ip.user_ns['In'])
109 110 self.assertEqual(oldlen+1, newlen)
110 111 self.assertEqual(ip.user_ns['In'][-1],'1;')
111 112
112 113 def test_magic_names_in_string(self):
113 114 ip.run_cell('a = """\n%exit\n"""')
114 115 self.assertEqual(ip.user_ns['a'], '\n%exit\n')
115 116
116 117 def test_trailing_newline(self):
117 118 """test that running !(command) does not raise a SyntaxError"""
118 119 ip.run_cell('!(true)\n', False)
119 120 ip.run_cell('!(true)\n\n\n', False)
120 121
121 122 def test_gh_597(self):
122 123 """Pretty-printing lists of objects with non-ascii reprs may cause
123 124 problems."""
124 125 class Spam(object):
125 126 def __repr__(self):
126 127 return "\xe9"*50
127 128 import IPython.core.formatters
128 129 f = IPython.core.formatters.PlainTextFormatter()
129 130 f([Spam(),Spam()])
130 131
131 132
132 133 def test_future_flags(self):
133 134 """Check that future flags are used for parsing code (gh-777)"""
134 135 ip.run_cell('from __future__ import print_function')
135 136 try:
136 137 ip.run_cell('prfunc_return_val = print(1,2, sep=" ")')
137 138 assert 'prfunc_return_val' in ip.user_ns
138 139 finally:
139 140 # Reset compiler flags so we don't mess up other tests.
140 141 ip.compile.reset_compiler_flags()
141 142
142 143 def test_future_unicode(self):
143 144 """Check that unicode_literals is imported from __future__ (gh #786)"""
144 145 try:
145 146 ip.run_cell(u'byte_str = "a"')
146 147 assert isinstance(ip.user_ns['byte_str'], str) # string literals are byte strings by default
147 148 ip.run_cell('from __future__ import unicode_literals')
148 149 ip.run_cell(u'unicode_str = "a"')
149 150 assert isinstance(ip.user_ns['unicode_str'], unicode_type) # strings literals are now unicode
150 151 finally:
151 152 # Reset compiler flags so we don't mess up other tests.
152 153 ip.compile.reset_compiler_flags()
153 154
154 155 def test_can_pickle(self):
155 156 "Can we pickle objects defined interactively (GH-29)"
156 157 ip = get_ipython()
157 158 ip.reset()
158 159 ip.run_cell(("class Mylist(list):\n"
159 160 " def __init__(self,x=[]):\n"
160 161 " list.__init__(self,x)"))
161 162 ip.run_cell("w=Mylist([1,2,3])")
162 163
163 164 from pickle import dumps
164 165
165 166 # We need to swap in our main module - this is only necessary
166 167 # inside the test framework, because IPython puts the interactive module
167 168 # in place (but the test framework undoes this).
168 169 _main = sys.modules['__main__']
169 170 sys.modules['__main__'] = ip.user_module
170 171 try:
171 172 res = dumps(ip.user_ns["w"])
172 173 finally:
173 174 sys.modules['__main__'] = _main
174 175 self.assertTrue(isinstance(res, bytes))
175 176
176 177 def test_global_ns(self):
177 178 "Code in functions must be able to access variables outside them."
178 179 ip = get_ipython()
179 180 ip.run_cell("a = 10")
180 181 ip.run_cell(("def f(x):\n"
181 182 " return x + a"))
182 183 ip.run_cell("b = f(12)")
183 184 self.assertEqual(ip.user_ns["b"], 22)
184 185
185 186 def test_bad_custom_tb(self):
186 187 """Check that InteractiveShell is protected from bad custom exception handlers"""
187 188 from IPython.utils import io
188 189 save_stderr = io.stderr
189 190 try:
190 191 # capture stderr
191 192 io.stderr = StringIO()
192 193 ip.set_custom_exc((IOError,), lambda etype,value,tb: 1/0)
193 194 self.assertEqual(ip.custom_exceptions, (IOError,))
194 195 ip.run_cell(u'raise IOError("foo")')
195 196 self.assertEqual(ip.custom_exceptions, ())
196 197 self.assertTrue("Custom TB Handler failed" in io.stderr.getvalue())
197 198 finally:
198 199 io.stderr = save_stderr
199 200
200 201 def test_bad_custom_tb_return(self):
201 202 """Check that InteractiveShell is protected from bad return types in custom exception handlers"""
202 203 from IPython.utils import io
203 204 save_stderr = io.stderr
204 205 try:
205 206 # capture stderr
206 207 io.stderr = StringIO()
207 208 ip.set_custom_exc((NameError,),lambda etype,value,tb, tb_offset=None: 1)
208 209 self.assertEqual(ip.custom_exceptions, (NameError,))
209 210 ip.run_cell(u'a=abracadabra')
210 211 self.assertEqual(ip.custom_exceptions, ())
211 212 self.assertTrue("Custom TB Handler failed" in io.stderr.getvalue())
212 213 finally:
213 214 io.stderr = save_stderr
214 215
215 216 def test_drop_by_id(self):
216 217 myvars = {"a":object(), "b":object(), "c": object()}
217 218 ip.push(myvars, interactive=False)
218 219 for name in myvars:
219 220 assert name in ip.user_ns, name
220 221 assert name in ip.user_ns_hidden, name
221 222 ip.user_ns['b'] = 12
222 223 ip.drop_by_id(myvars)
223 224 for name in ["a", "c"]:
224 225 assert name not in ip.user_ns, name
225 226 assert name not in ip.user_ns_hidden, name
226 227 assert ip.user_ns['b'] == 12
227 228 ip.reset()
228 229
229 230 def test_var_expand(self):
230 231 ip.user_ns['f'] = u'Ca\xf1o'
231 232 self.assertEqual(ip.var_expand(u'echo $f'), u'echo Ca\xf1o')
232 233 self.assertEqual(ip.var_expand(u'echo {f}'), u'echo Ca\xf1o')
233 234 self.assertEqual(ip.var_expand(u'echo {f[:-1]}'), u'echo Ca\xf1')
234 235 self.assertEqual(ip.var_expand(u'echo {1*2}'), u'echo 2')
235 236
236 237 ip.user_ns['f'] = b'Ca\xc3\xb1o'
237 238 # This should not raise any exception:
238 239 ip.var_expand(u'echo $f')
239 240
240 241 def test_var_expand_local(self):
241 242 """Test local variable expansion in !system and %magic calls"""
242 243 # !system
243 244 ip.run_cell('def test():\n'
244 245 ' lvar = "ttt"\n'
245 246 ' ret = !echo {lvar}\n'
246 247 ' return ret[0]\n')
247 248 res = ip.user_ns['test']()
248 249 nt.assert_in('ttt', res)
249 250
250 251 # %magic
251 252 ip.run_cell('def makemacro():\n'
252 253 ' macroname = "macro_var_expand_locals"\n'
253 254 ' %macro {macroname} codestr\n')
254 255 ip.user_ns['codestr'] = "str(12)"
255 256 ip.run_cell('makemacro()')
256 257 nt.assert_in('macro_var_expand_locals', ip.user_ns)
257 258
258 259 def test_var_expand_self(self):
259 260 """Test variable expansion with the name 'self', which was failing.
260 261
261 262 See https://github.com/ipython/ipython/issues/1878#issuecomment-7698218
262 263 """
263 264 ip.run_cell('class cTest:\n'
264 265 ' classvar="see me"\n'
265 266 ' def test(self):\n'
266 267 ' res = !echo Variable: {self.classvar}\n'
267 268 ' return res[0]\n')
268 269 nt.assert_in('see me', ip.user_ns['cTest']().test())
269 270
270 271 def test_bad_var_expand(self):
271 272 """var_expand on invalid formats shouldn't raise"""
272 273 # SyntaxError
273 274 self.assertEqual(ip.var_expand(u"{'a':5}"), u"{'a':5}")
274 275 # NameError
275 276 self.assertEqual(ip.var_expand(u"{asdf}"), u"{asdf}")
276 277 # ZeroDivisionError
277 278 self.assertEqual(ip.var_expand(u"{1/0}"), u"{1/0}")
278 279
279 280 def test_silent_nopostexec(self):
280 281 """run_cell(silent=True) doesn't invoke post-exec funcs"""
281 282 d = dict(called=False)
282 283 def set_called():
283 284 d['called'] = True
284 285
285 286 ip.register_post_execute(set_called)
286 287 ip.run_cell("1", silent=True)
287 288 self.assertFalse(d['called'])
288 289 # double-check that non-silent exec did what we expected
289 290 # silent to avoid
290 291 ip.run_cell("1")
291 292 self.assertTrue(d['called'])
292 293 # remove post-exec
293 294 ip._post_execute.pop(set_called)
294 295
295 296 def test_silent_noadvance(self):
296 297 """run_cell(silent=True) doesn't advance execution_count"""
297 298 ec = ip.execution_count
298 299 # silent should force store_history=False
299 300 ip.run_cell("1", store_history=True, silent=True)
300 301
301 302 self.assertEqual(ec, ip.execution_count)
302 303 # double-check that non-silent exec did what we expected
303 304 # silent to avoid
304 305 ip.run_cell("1", store_history=True)
305 306 self.assertEqual(ec+1, ip.execution_count)
306 307
307 308 def test_silent_nodisplayhook(self):
308 309 """run_cell(silent=True) doesn't trigger displayhook"""
309 310 d = dict(called=False)
310 311
311 312 trap = ip.display_trap
312 313 save_hook = trap.hook
313 314
314 315 def failing_hook(*args, **kwargs):
315 316 d['called'] = True
316 317
317 318 try:
318 319 trap.hook = failing_hook
319 320 ip.run_cell("1", silent=True)
320 321 self.assertFalse(d['called'])
321 322 # double-check that non-silent exec did what we expected
322 323 # silent to avoid
323 324 ip.run_cell("1")
324 325 self.assertTrue(d['called'])
325 326 finally:
326 327 trap.hook = save_hook
327 328
328 329 @skipif(sys.version_info[0] >= 3, "softspace removed in py3")
329 330 def test_print_softspace(self):
330 331 """Verify that softspace is handled correctly when executing multiple
331 332 statements.
332 333
333 334 In [1]: print 1; print 2
334 335 1
335 336 2
336 337
337 338 In [2]: print 1,; print 2
338 339 1 2
339 340 """
340 341
341 342 def test_ofind_line_magic(self):
342 343 from IPython.core.magic import register_line_magic
343 344
344 345 @register_line_magic
345 346 def lmagic(line):
346 347 "A line magic"
347 348
348 349 # Get info on line magic
349 350 lfind = ip._ofind('lmagic')
350 351 info = dict(found=True, isalias=False, ismagic=True,
351 352 namespace = 'IPython internal', obj= lmagic.__wrapped__,
352 353 parent = None)
353 354 nt.assert_equal(lfind, info)
354 355
355 356 def test_ofind_cell_magic(self):
356 357 from IPython.core.magic import register_cell_magic
357 358
358 359 @register_cell_magic
359 360 def cmagic(line, cell):
360 361 "A cell magic"
361 362
362 363 # Get info on cell magic
363 364 find = ip._ofind('cmagic')
364 365 info = dict(found=True, isalias=False, ismagic=True,
365 366 namespace = 'IPython internal', obj= cmagic.__wrapped__,
366 367 parent = None)
367 368 nt.assert_equal(find, info)
368 369
369 370 def test_custom_exception(self):
370 371 called = []
371 372 def my_handler(shell, etype, value, tb, tb_offset=None):
372 373 called.append(etype)
373 374 shell.showtraceback((etype, value, tb), tb_offset=tb_offset)
374 375
375 376 ip.set_custom_exc((ValueError,), my_handler)
376 377 try:
377 378 ip.run_cell("raise ValueError('test')")
378 379 # Check that this was called, and only once.
379 380 self.assertEqual(called, [ValueError])
380 381 finally:
381 382 # Reset the custom exception hook
382 383 ip.set_custom_exc((), None)
383 384
384 385 @skipif(sys.version_info[0] >= 3, "no differences with __future__ in py3")
385 386 def test_future_environment(self):
386 387 "Can we run code with & without the shell's __future__ imports?"
387 388 ip.run_cell("from __future__ import division")
388 389 ip.run_cell("a = 1/2", shell_futures=True)
389 390 self.assertEqual(ip.user_ns['a'], 0.5)
390 391 ip.run_cell("b = 1/2", shell_futures=False)
391 392 self.assertEqual(ip.user_ns['b'], 0)
392 393
393 394 ip.compile.reset_compiler_flags()
394 395 # This shouldn't leak to the shell's compiler
395 396 ip.run_cell("from __future__ import division \nc=1/2", shell_futures=False)
396 397 self.assertEqual(ip.user_ns['c'], 0.5)
397 398 ip.run_cell("d = 1/2", shell_futures=True)
398 399 self.assertEqual(ip.user_ns['d'], 0)
399 400
400 401
401 402 class TestSafeExecfileNonAsciiPath(unittest.TestCase):
402 403
403 404 @onlyif_unicode_paths
404 405 def setUp(self):
405 406 self.BASETESTDIR = tempfile.mkdtemp()
406 407 self.TESTDIR = join(self.BASETESTDIR, u"Γ₯Àâ")
407 408 os.mkdir(self.TESTDIR)
408 409 with open(join(self.TESTDIR, u"Γ₯Àâtestscript.py"), "w") as sfile:
409 410 sfile.write("pass\n")
410 411 self.oldpath = py3compat.getcwd()
411 412 os.chdir(self.TESTDIR)
412 413 self.fname = u"Γ₯Àâtestscript.py"
413 414
414 415 def tearDown(self):
415 416 os.chdir(self.oldpath)
416 417 shutil.rmtree(self.BASETESTDIR)
417 418
418 419 @onlyif_unicode_paths
419 420 def test_1(self):
420 421 """Test safe_execfile with non-ascii path
421 422 """
422 423 ip.safe_execfile(self.fname, {}, raise_exceptions=True)
423 424
424 425 class ExitCodeChecks(tt.TempFileMixin):
425 426 def test_exit_code_ok(self):
426 427 self.system('exit 0')
427 428 self.assertEqual(ip.user_ns['_exit_code'], 0)
428 429
429 430 def test_exit_code_error(self):
430 431 self.system('exit 1')
431 432 self.assertEqual(ip.user_ns['_exit_code'], 1)
432 433
433 434 @skipif(not hasattr(signal, 'SIGALRM'))
434 435 def test_exit_code_signal(self):
435 436 self.mktmp("import signal, time\n"
436 437 "signal.setitimer(signal.ITIMER_REAL, 0.1)\n"
437 438 "time.sleep(1)\n")
438 439 self.system("%s %s" % (sys.executable, self.fname))
439 440 self.assertEqual(ip.user_ns['_exit_code'], -signal.SIGALRM)
440 441
441 442 class TestSystemRaw(unittest.TestCase, ExitCodeChecks):
442 443 system = ip.system_raw
443 444
444 445 @onlyif_unicode_paths
445 446 def test_1(self):
446 447 """Test system_raw with non-ascii cmd
447 448 """
448 449 cmd = u'''python -c "'Γ₯Àâ'" '''
449 450 ip.system_raw(cmd)
450 451
451 452 # TODO: Exit codes are currently ignored on Windows.
452 453 class TestSystemPipedExitCode(unittest.TestCase, ExitCodeChecks):
453 454 system = ip.system_piped
454 455
455 456 @skip_win32
456 457 def test_exit_code_ok(self):
457 458 ExitCodeChecks.test_exit_code_ok(self)
458 459
459 460 @skip_win32
460 461 def test_exit_code_error(self):
461 462 ExitCodeChecks.test_exit_code_error(self)
462 463
463 464 @skip_win32
464 465 def test_exit_code_signal(self):
465 466 ExitCodeChecks.test_exit_code_signal(self)
466 467
467 468 class TestModules(unittest.TestCase, tt.TempFileMixin):
468 469 def test_extraneous_loads(self):
469 470 """Test we're not loading modules on startup that we shouldn't.
470 471 """
471 472 self.mktmp("import sys\n"
472 473 "print('numpy' in sys.modules)\n"
473 474 "print('IPython.parallel' in sys.modules)\n"
474 475 "print('IPython.kernel.zmq' in sys.modules)\n"
475 476 )
476 477 out = "False\nFalse\nFalse\n"
477 478 tt.ipexec_validate(self.fname, out)
478 479
479 480 class Negator(ast.NodeTransformer):
480 481 """Negates all number literals in an AST."""
481 482 def visit_Num(self, node):
482 483 node.n = -node.n
483 484 return node
484 485
485 486 class TestAstTransform(unittest.TestCase):
486 487 def setUp(self):
487 488 self.negator = Negator()
488 489 ip.ast_transformers.append(self.negator)
489 490
490 491 def tearDown(self):
491 492 ip.ast_transformers.remove(self.negator)
492 493
493 494 def test_run_cell(self):
494 495 with tt.AssertPrints('-34'):
495 496 ip.run_cell('print (12 + 22)')
496 497
497 498 # A named reference to a number shouldn't be transformed.
498 499 ip.user_ns['n'] = 55
499 500 with tt.AssertNotPrints('-55'):
500 501 ip.run_cell('print (n)')
501 502
502 503 def test_timeit(self):
503 504 called = set()
504 505 def f(x):
505 506 called.add(x)
506 507 ip.push({'f':f})
507 508
508 509 with tt.AssertPrints("best of "):
509 510 ip.run_line_magic("timeit", "-n1 f(1)")
510 511 self.assertEqual(called, set([-1]))
511 512 called.clear()
512 513
513 514 with tt.AssertPrints("best of "):
514 515 ip.run_cell_magic("timeit", "-n1 f(2)", "f(3)")
515 516 self.assertEqual(called, set([-2, -3]))
516 517
517 518 def test_time(self):
518 519 called = []
519 520 def f(x):
520 521 called.append(x)
521 522 ip.push({'f':f})
522 523
523 524 # Test with an expression
524 525 with tt.AssertPrints("Wall time: "):
525 526 ip.run_line_magic("time", "f(5+9)")
526 527 self.assertEqual(called, [-14])
527 528 called[:] = []
528 529
529 530 # Test with a statement (different code path)
530 531 with tt.AssertPrints("Wall time: "):
531 532 ip.run_line_magic("time", "a = f(-3 + -2)")
532 533 self.assertEqual(called, [5])
533 534
534 535 def test_macro(self):
535 536 ip.push({'a':10})
536 537 # The AST transformation makes this do a+=-1
537 538 ip.define_macro("amacro", "a+=1\nprint(a)")
538 539
539 540 with tt.AssertPrints("9"):
540 541 ip.run_cell("amacro")
541 542 with tt.AssertPrints("8"):
542 543 ip.run_cell("amacro")
543 544
544 545 class IntegerWrapper(ast.NodeTransformer):
545 546 """Wraps all integers in a call to Integer()"""
546 547 def visit_Num(self, node):
547 548 if isinstance(node.n, int):
548 549 return ast.Call(func=ast.Name(id='Integer', ctx=ast.Load()),
549 550 args=[node], keywords=[])
550 551 return node
551 552
552 553 class TestAstTransform2(unittest.TestCase):
553 554 def setUp(self):
554 555 self.intwrapper = IntegerWrapper()
555 556 ip.ast_transformers.append(self.intwrapper)
556 557
557 558 self.calls = []
558 559 def Integer(*args):
559 560 self.calls.append(args)
560 561 return args
561 562 ip.push({"Integer": Integer})
562 563
563 564 def tearDown(self):
564 565 ip.ast_transformers.remove(self.intwrapper)
565 566 del ip.user_ns['Integer']
566 567
567 568 def test_run_cell(self):
568 569 ip.run_cell("n = 2")
569 570 self.assertEqual(self.calls, [(2,)])
570 571
571 572 # This shouldn't throw an error
572 573 ip.run_cell("o = 2.0")
573 574 self.assertEqual(ip.user_ns['o'], 2.0)
574 575
575 576 def test_timeit(self):
576 577 called = set()
577 578 def f(x):
578 579 called.add(x)
579 580 ip.push({'f':f})
580 581
581 582 with tt.AssertPrints("best of "):
582 583 ip.run_line_magic("timeit", "-n1 f(1)")
583 584 self.assertEqual(called, set([(1,)]))
584 585 called.clear()
585 586
586 587 with tt.AssertPrints("best of "):
587 588 ip.run_cell_magic("timeit", "-n1 f(2)", "f(3)")
588 589 self.assertEqual(called, set([(2,), (3,)]))
589 590
590 591 class ErrorTransformer(ast.NodeTransformer):
591 592 """Throws an error when it sees a number."""
592 593 def visit_Num(self):
593 594 raise ValueError("test")
594 595
595 596 class TestAstTransformError(unittest.TestCase):
596 597 def test_unregistering(self):
597 598 err_transformer = ErrorTransformer()
598 599 ip.ast_transformers.append(err_transformer)
599 600
600 601 with tt.AssertPrints("unregister", channel='stderr'):
601 602 ip.run_cell("1 + 2")
602 603
603 604 # This should have been removed.
604 605 nt.assert_not_in(err_transformer, ip.ast_transformers)
605 606
606 607 def test__IPYTHON__():
607 608 # This shouldn't raise a NameError, that's all
608 609 __IPYTHON__
609 610
610 611
611 612 class DummyRepr(object):
612 613 def __repr__(self):
613 614 return "DummyRepr"
614 615
615 616 def _repr_html_(self):
616 617 return "<b>dummy</b>"
617 618
618 619 def _repr_javascript_(self):
619 620 return "console.log('hi');", {'key': 'value'}
620 621
621 622
622 623 def test_user_variables():
623 624 # enable all formatters
624 625 ip.display_formatter.active_types = ip.display_formatter.format_types
625 626
626 627 ip.user_ns['dummy'] = d = DummyRepr()
627 628 keys = set(['dummy', 'doesnotexist'])
628 629 r = ip.user_variables(keys)
629 630
630 631 nt.assert_equal(keys, set(r.keys()))
631 632 dummy = r['dummy']
632 633 nt.assert_equal(set(['status', 'data', 'metadata']), set(dummy.keys()))
633 634 nt.assert_equal(dummy['status'], 'ok')
634 635 data = dummy['data']
635 636 metadata = dummy['metadata']
636 637 nt.assert_equal(data.get('text/html'), d._repr_html_())
637 638 js, jsmd = d._repr_javascript_()
638 639 nt.assert_equal(data.get('application/javascript'), js)
639 640 nt.assert_equal(metadata.get('application/javascript'), jsmd)
640 641
641 642 dne = r['doesnotexist']
642 643 nt.assert_equal(dne['status'], 'error')
643 644 nt.assert_equal(dne['ename'], 'KeyError')
644 645
645 646 # back to text only
646 647 ip.display_formatter.active_types = ['text/plain']
647 648
648 649 def test_user_expression():
649 650 # enable all formatters
650 651 ip.display_formatter.active_types = ip.display_formatter.format_types
651 652 query = {
652 653 'a' : '1 + 2',
653 654 'b' : '1/0',
654 655 }
655 656 r = ip.user_expressions(query)
656 657 import pprint
657 658 pprint.pprint(r)
658 659 nt.assert_equal(r.keys(), query.keys())
659 660 a = r['a']
660 661 nt.assert_equal(set(['status', 'data', 'metadata']), set(a.keys()))
661 662 nt.assert_equal(a['status'], 'ok')
662 663 data = a['data']
663 664 metadata = a['metadata']
664 665 nt.assert_equal(data.get('text/plain'), '3')
665 666
666 667 b = r['b']
667 668 nt.assert_equal(b['status'], 'error')
668 669 nt.assert_equal(b['ename'], 'ZeroDivisionError')
669 670
670 671 # back to text only
671 672 ip.display_formatter.active_types = ['text/plain']
672 673
673 674
674 675
675 676
676 677
678 class TestSyntaxErrorTransformer(unittest.TestCase):
679 """Check that SyntaxError raised by an input transformer is handled by run_cell()"""
680
681 class SyntaxErrorTransformer(InputTransformer):
682
683 def push(self, line):
684 pos = line.find('syntaxerror')
685 if pos >= 0:
686 e = SyntaxError('input contains "syntaxerror"')
687 e.text = line
688 e.offset = pos + 1
689 raise e
690 return line
691
692 def reset(self):
693 pass
694
695 def setUp(self):
696 self.transformer = TestSyntaxErrorTransformer.SyntaxErrorTransformer()
697 ip.input_splitter.python_line_transforms.append(self.transformer)
698 ip.input_transformer_manager.python_line_transforms.append(self.transformer)
699
700 def tearDown(self):
701 ip.input_splitter.python_line_transforms.remove(self.transformer)
702 ip.input_transformer_manager.python_line_transforms.remove(self.transformer)
703
704 def test_syntaxerror_input_transformer(self):
705 with tt.AssertPrints('1234'):
706 ip.run_cell('1234')
707 with tt.AssertPrints('SyntaxError: invalid syntax'):
708 ip.run_cell('1 2 3') # plain python syntax error
709 with tt.AssertPrints('SyntaxError: input contains "syntaxerror"'):
710 ip.run_cell('2345 # syntaxerror') # input transformer syntax error
711 with tt.AssertPrints('3456'):
712 ip.run_cell('3456')
713
714
677 715
General Comments 0
You need to be logged in to leave comments. Login now