##// END OF EJS Templates
Fix shellapp tests
Thomas Kluyver -
Show More
@@ -1,70 +1,68 b''
1 1 # -*- coding: utf-8 -*-
2 2 """Tests for shellapp module.
3 3
4 4 Authors
5 5 -------
6 6 * Bradley Froehle
7 7 """
8 8 #-----------------------------------------------------------------------------
9 9 # Copyright (C) 2012 The IPython Development Team
10 10 #
11 11 # Distributed under the terms of the BSD License. The full license is in
12 12 # the file COPYING, distributed as part of this software.
13 13 #-----------------------------------------------------------------------------
14 14
15 15 #-----------------------------------------------------------------------------
16 16 # Imports
17 17 #-----------------------------------------------------------------------------
18 18 import unittest
19 19
20 20 from IPython.testing import decorators as dec
21 21 from IPython.testing import tools as tt
22 22 from IPython.utils.py3compat import PY3
23 23
24 24 sqlite_err_maybe = dec.module_not_available('sqlite3')
25 25 SQLITE_NOT_AVAILABLE_ERROR = ('WARNING: IPython History requires SQLite,'
26 26 ' your history will not be saved\n')
27 27
28 28 class TestFileToRun(unittest.TestCase, tt.TempFileMixin):
29 29 """Test the behavior of the file_to_run parameter."""
30 30
31 31 def test_py_script_file_attribute(self):
32 32 """Test that `__file__` is set when running `ipython file.py`"""
33 33 src = "print(__file__)\n"
34 34 self.mktmp(src)
35 35
36 36 err = SQLITE_NOT_AVAILABLE_ERROR if sqlite_err_maybe else None
37 37 tt.ipexec_validate(self.fname, self.fname, err)
38 38
39 39 def test_ipy_script_file_attribute(self):
40 40 """Test that `__file__` is set when running `ipython file.ipy`"""
41 41 src = "print(__file__)\n"
42 42 self.mktmp(src, ext='.ipy')
43 43
44 44 err = SQLITE_NOT_AVAILABLE_ERROR if sqlite_err_maybe else None
45 45 tt.ipexec_validate(self.fname, self.fname, err)
46 46
47 47 # The commands option to ipexec_validate doesn't work on Windows, and it
48 48 # doesn't seem worth fixing
49 49 @dec.skip_win32
50 50 def test_py_script_file_attribute_interactively(self):
51 51 """Test that `__file__` is not set after `ipython -i file.py`"""
52 52 src = "True\n"
53 53 self.mktmp(src)
54 54
55 out = 'In [1]: False\n\nIn [2]:'
56 err = SQLITE_NOT_AVAILABLE_ERROR if sqlite_err_maybe else None
57 tt.ipexec_validate(self.fname, out, err, options=['-i'],
55 out, err = tt.ipexec(self.fname, options=['-i'],
58 56 commands=['"__file__" in globals()', 'exit()'])
57 self.assertIn("False", out)
59 58
60 59 @dec.skip_win32
61 60 @dec.skipif(PY3)
62 61 def test_py_script_file_compiler_directive(self):
63 62 """Test `__future__` compiler directives with `ipython -i file.py`"""
64 63 src = "from __future__ import division\n"
65 64 self.mktmp(src)
66 65
67 out = 'In [1]: float\n\nIn [2]:'
68 err = SQLITE_NOT_AVAILABLE_ERROR if sqlite_err_maybe else None
69 tt.ipexec_validate(self.fname, out, err, options=['-i'],
66 out, err = tt.ipexec(self.fname, options=['-i'],
70 67 commands=['type(1/2)', 'exit()'])
68 self.assertIn('float', out)
@@ -1,474 +1,468 b''
1 1 """Generic testing tools.
2 2
3 3 Authors
4 4 -------
5 5 - Fernando Perez <Fernando.Perez@berkeley.edu>
6 6 """
7 7
8 8 from __future__ import absolute_import
9 9
10 10 #-----------------------------------------------------------------------------
11 11 # Copyright (C) 2009 The IPython Development Team
12 12 #
13 13 # Distributed under the terms of the BSD License. The full license is in
14 14 # the file COPYING, distributed as part of this software.
15 15 #-----------------------------------------------------------------------------
16 16
17 17 #-----------------------------------------------------------------------------
18 18 # Imports
19 19 #-----------------------------------------------------------------------------
20 20
21 21 import os
22 22 import re
23 23 import sys
24 24 import tempfile
25 25
26 26 from contextlib import contextmanager
27 27 from io import StringIO
28 28 from subprocess import Popen, PIPE
29 29
30 30 try:
31 31 # These tools are used by parts of the runtime, so we make the nose
32 32 # dependency optional at this point. Nose is a hard dependency to run the
33 33 # test suite, but NOT to use ipython itself.
34 34 import nose.tools as nt
35 35 has_nose = True
36 36 except ImportError:
37 37 has_nose = False
38 38
39 39 from traitlets.config.loader import Config
40 40 from IPython.utils.process import get_output_error_code
41 41 from IPython.utils.text import list_strings
42 42 from IPython.utils.io import temp_pyfile, Tee
43 43 from IPython.utils import py3compat
44 44 from IPython.utils.encoding import DEFAULT_ENCODING
45 45
46 46 from . import decorators as dec
47 47 from . import skipdoctest
48 48
49 49 #-----------------------------------------------------------------------------
50 50 # Functions and classes
51 51 #-----------------------------------------------------------------------------
52 52
53 53 # The docstring for full_path doctests differently on win32 (different path
54 54 # separator) so just skip the doctest there. The example remains informative.
55 55 doctest_deco = skipdoctest.skip_doctest if sys.platform == 'win32' else dec.null_deco
56 56
57 57 @doctest_deco
58 58 def full_path(startPath,files):
59 59 """Make full paths for all the listed files, based on startPath.
60 60
61 61 Only the base part of startPath is kept, since this routine is typically
62 62 used with a script's ``__file__`` variable as startPath. The base of startPath
63 63 is then prepended to all the listed files, forming the output list.
64 64
65 65 Parameters
66 66 ----------
67 67 startPath : string
68 68 Initial path to use as the base for the results. This path is split
69 69 using os.path.split() and only its first component is kept.
70 70
71 71 files : string or list
72 72 One or more files.
73 73
74 74 Examples
75 75 --------
76 76
77 77 >>> full_path('/foo/bar.py',['a.txt','b.txt'])
78 78 ['/foo/a.txt', '/foo/b.txt']
79 79
80 80 >>> full_path('/foo',['a.txt','b.txt'])
81 81 ['/a.txt', '/b.txt']
82 82
83 83 If a single file is given, the output is still a list::
84 84
85 85 >>> full_path('/foo','a.txt')
86 86 ['/a.txt']
87 87 """
88 88
89 89 files = list_strings(files)
90 90 base = os.path.split(startPath)[0]
91 91 return [ os.path.join(base,f) for f in files ]
92 92
93 93
94 94 def parse_test_output(txt):
95 95 """Parse the output of a test run and return errors, failures.
96 96
97 97 Parameters
98 98 ----------
99 99 txt : str
100 100 Text output of a test run, assumed to contain a line of one of the
101 101 following forms::
102 102
103 103 'FAILED (errors=1)'
104 104 'FAILED (failures=1)'
105 105 'FAILED (errors=1, failures=1)'
106 106
107 107 Returns
108 108 -------
109 109 nerr, nfail
110 110 number of errors and failures.
111 111 """
112 112
113 113 err_m = re.search(r'^FAILED \(errors=(\d+)\)', txt, re.MULTILINE)
114 114 if err_m:
115 115 nerr = int(err_m.group(1))
116 116 nfail = 0
117 117 return nerr, nfail
118 118
119 119 fail_m = re.search(r'^FAILED \(failures=(\d+)\)', txt, re.MULTILINE)
120 120 if fail_m:
121 121 nerr = 0
122 122 nfail = int(fail_m.group(1))
123 123 return nerr, nfail
124 124
125 125 both_m = re.search(r'^FAILED \(errors=(\d+), failures=(\d+)\)', txt,
126 126 re.MULTILINE)
127 127 if both_m:
128 128 nerr = int(both_m.group(1))
129 129 nfail = int(both_m.group(2))
130 130 return nerr, nfail
131 131
132 132 # If the input didn't match any of these forms, assume no error/failures
133 133 return 0, 0
134 134
135 135
136 136 # So nose doesn't think this is a test
137 137 parse_test_output.__test__ = False
138 138
139 139
140 140 def default_argv():
141 141 """Return a valid default argv for creating testing instances of ipython"""
142 142
143 143 return ['--quick', # so no config file is loaded
144 144 # Other defaults to minimize side effects on stdout
145 145 '--colors=NoColor', '--no-term-title','--no-banner',
146 146 '--autocall=0']
147 147
148 148
149 149 def default_config():
150 150 """Return a config object with good defaults for testing."""
151 151 config = Config()
152 152 config.TerminalInteractiveShell.colors = 'NoColor'
153 153 config.TerminalTerminalInteractiveShell.term_title = False,
154 154 config.TerminalInteractiveShell.autocall = 0
155 155 f = tempfile.NamedTemporaryFile(suffix=u'test_hist.sqlite', delete=False)
156 156 config.HistoryManager.hist_file = f.name
157 157 f.close()
158 158 config.HistoryManager.db_cache_size = 10000
159 159 return config
160 160
161 161
162 162 def get_ipython_cmd(as_string=False):
163 163 """
164 164 Return appropriate IPython command line name. By default, this will return
165 165 a list that can be used with subprocess.Popen, for example, but passing
166 166 `as_string=True` allows for returning the IPython command as a string.
167 167
168 168 Parameters
169 169 ----------
170 170 as_string: bool
171 171 Flag to allow to return the command as a string.
172 172 """
173 173 ipython_cmd = [sys.executable, "-m", "IPython"]
174 174
175 175 if as_string:
176 176 ipython_cmd = " ".join(ipython_cmd)
177 177
178 178 return ipython_cmd
179 179
180 180 def ipexec(fname, options=None, commands=()):
181 181 """Utility to call 'ipython filename'.
182 182
183 183 Starts IPython with a minimal and safe configuration to make startup as fast
184 184 as possible.
185 185
186 186 Note that this starts IPython in a subprocess!
187 187
188 188 Parameters
189 189 ----------
190 190 fname : str
191 191 Name of file to be executed (should have .py or .ipy extension).
192 192
193 193 options : optional, list
194 194 Extra command-line flags to be passed to IPython.
195 195
196 196 commands : optional, list
197 197 Commands to send in on stdin
198 198
199 199 Returns
200 200 -------
201 201 (stdout, stderr) of ipython subprocess.
202 202 """
203 203 if options is None: options = []
204 204
205 # For these subprocess calls, eliminate all prompt printing so we only see
206 # output from script execution
207 prompt_opts = [ '--PromptManager.in_template=""',
208 '--PromptManager.in2_template=""',
209 '--PromptManager.out_template=""'
210 ]
211 cmdargs = default_argv() + prompt_opts + options
205 cmdargs = default_argv() + options
212 206
213 207 test_dir = os.path.dirname(__file__)
214 208
215 209 ipython_cmd = get_ipython_cmd()
216 210 # Absolute path for filename
217 211 full_fname = os.path.join(test_dir, fname)
218 212 full_cmd = ipython_cmd + cmdargs + [full_fname]
219 213 env = os.environ.copy()
220 214 # FIXME: ignore all warnings in ipexec while we have shims
221 215 # should we keep suppressing warnings here, even after removing shims?
222 216 env['PYTHONWARNINGS'] = 'ignore'
223 217 # env.pop('PYTHONWARNINGS', None) # Avoid extraneous warnings appearing on stderr
224 218 for k, v in env.items():
225 219 # Debug a bizarre failure we've seen on Windows:
226 220 # TypeError: environment can only contain strings
227 221 if not isinstance(v, str):
228 222 print(k, v)
229 223 p = Popen(full_cmd, stdout=PIPE, stderr=PIPE, stdin=PIPE, env=env)
230 224 out, err = p.communicate(input=py3compat.str_to_bytes('\n'.join(commands)) or None)
231 225 out, err = py3compat.bytes_to_str(out), py3compat.bytes_to_str(err)
232 226 # `import readline` causes 'ESC[?1034h' to be output sometimes,
233 227 # so strip that out before doing comparisons
234 228 if out:
235 229 out = re.sub(r'\x1b\[[^h]+h', '', out)
236 230 return out, err
237 231
238 232
239 233 def ipexec_validate(fname, expected_out, expected_err='',
240 234 options=None, commands=()):
241 235 """Utility to call 'ipython filename' and validate output/error.
242 236
243 237 This function raises an AssertionError if the validation fails.
244 238
245 239 Note that this starts IPython in a subprocess!
246 240
247 241 Parameters
248 242 ----------
249 243 fname : str
250 244 Name of the file to be executed (should have .py or .ipy extension).
251 245
252 246 expected_out : str
253 247 Expected stdout of the process.
254 248
255 249 expected_err : optional, str
256 250 Expected stderr of the process.
257 251
258 252 options : optional, list
259 253 Extra command-line flags to be passed to IPython.
260 254
261 255 Returns
262 256 -------
263 257 None
264 258 """
265 259
266 260 import nose.tools as nt
267 261
268 262 out, err = ipexec(fname, options, commands)
269 263 #print 'OUT', out # dbg
270 264 #print 'ERR', err # dbg
271 265 # If there are any errors, we must check those befor stdout, as they may be
272 266 # more informative than simply having an empty stdout.
273 267 if err:
274 268 if expected_err:
275 269 nt.assert_equal("\n".join(err.strip().splitlines()), "\n".join(expected_err.strip().splitlines()))
276 270 else:
277 271 raise ValueError('Running file %r produced error: %r' %
278 272 (fname, err))
279 273 # If no errors or output on stderr was expected, match stdout
280 274 nt.assert_equal("\n".join(out.strip().splitlines()), "\n".join(expected_out.strip().splitlines()))
281 275
282 276
283 277 class TempFileMixin(object):
284 278 """Utility class to create temporary Python/IPython files.
285 279
286 280 Meant as a mixin class for test cases."""
287 281
288 282 def mktmp(self, src, ext='.py'):
289 283 """Make a valid python temp file."""
290 284 fname, f = temp_pyfile(src, ext)
291 285 self.tmpfile = f
292 286 self.fname = fname
293 287
294 288 def tearDown(self):
295 289 if hasattr(self, 'tmpfile'):
296 290 # If the tmpfile wasn't made because of skipped tests, like in
297 291 # win32, there's nothing to cleanup.
298 292 self.tmpfile.close()
299 293 try:
300 294 os.unlink(self.fname)
301 295 except:
302 296 # On Windows, even though we close the file, we still can't
303 297 # delete it. I have no clue why
304 298 pass
305 299
306 300 def __enter__(self):
307 301 return self
308 302
309 303 def __exit__(self, exc_type, exc_value, traceback):
310 304 self.tearDown()
311 305
312 306
313 307 pair_fail_msg = ("Testing {0}\n\n"
314 308 "In:\n"
315 309 " {1!r}\n"
316 310 "Expected:\n"
317 311 " {2!r}\n"
318 312 "Got:\n"
319 313 " {3!r}\n")
320 314 def check_pairs(func, pairs):
321 315 """Utility function for the common case of checking a function with a
322 316 sequence of input/output pairs.
323 317
324 318 Parameters
325 319 ----------
326 320 func : callable
327 321 The function to be tested. Should accept a single argument.
328 322 pairs : iterable
329 323 A list of (input, expected_output) tuples.
330 324
331 325 Returns
332 326 -------
333 327 None. Raises an AssertionError if any output does not match the expected
334 328 value.
335 329 """
336 330 name = getattr(func, "func_name", getattr(func, "__name__", "<unknown>"))
337 331 for inp, expected in pairs:
338 332 out = func(inp)
339 333 assert out == expected, pair_fail_msg.format(name, inp, expected, out)
340 334
341 335
342 336 if py3compat.PY3:
343 337 MyStringIO = StringIO
344 338 else:
345 339 # In Python 2, stdout/stderr can have either bytes or unicode written to them,
346 340 # so we need a class that can handle both.
347 341 class MyStringIO(StringIO):
348 342 def write(self, s):
349 343 s = py3compat.cast_unicode(s, encoding=DEFAULT_ENCODING)
350 344 super(MyStringIO, self).write(s)
351 345
352 346 _re_type = type(re.compile(r''))
353 347
354 348 notprinted_msg = """Did not find {0!r} in printed output (on {1}):
355 349 -------
356 350 {2!s}
357 351 -------
358 352 """
359 353
360 354 class AssertPrints(object):
361 355 """Context manager for testing that code prints certain text.
362 356
363 357 Examples
364 358 --------
365 359 >>> with AssertPrints("abc", suppress=False):
366 360 ... print("abcd")
367 361 ... print("def")
368 362 ...
369 363 abcd
370 364 def
371 365 """
372 366 def __init__(self, s, channel='stdout', suppress=True):
373 367 self.s = s
374 368 if isinstance(self.s, (py3compat.string_types, _re_type)):
375 369 self.s = [self.s]
376 370 self.channel = channel
377 371 self.suppress = suppress
378 372
379 373 def __enter__(self):
380 374 self.orig_stream = getattr(sys, self.channel)
381 375 self.buffer = MyStringIO()
382 376 self.tee = Tee(self.buffer, channel=self.channel)
383 377 setattr(sys, self.channel, self.buffer if self.suppress else self.tee)
384 378
385 379 def __exit__(self, etype, value, traceback):
386 380 try:
387 381 if value is not None:
388 382 # If an error was raised, don't check anything else
389 383 return False
390 384 self.tee.flush()
391 385 setattr(sys, self.channel, self.orig_stream)
392 386 printed = self.buffer.getvalue()
393 387 for s in self.s:
394 388 if isinstance(s, _re_type):
395 389 assert s.search(printed), notprinted_msg.format(s.pattern, self.channel, printed)
396 390 else:
397 391 assert s in printed, notprinted_msg.format(s, self.channel, printed)
398 392 return False
399 393 finally:
400 394 self.tee.close()
401 395
402 396 printed_msg = """Found {0!r} in printed output (on {1}):
403 397 -------
404 398 {2!s}
405 399 -------
406 400 """
407 401
408 402 class AssertNotPrints(AssertPrints):
409 403 """Context manager for checking that certain output *isn't* produced.
410 404
411 405 Counterpart of AssertPrints"""
412 406 def __exit__(self, etype, value, traceback):
413 407 try:
414 408 if value is not None:
415 409 # If an error was raised, don't check anything else
416 410 self.tee.close()
417 411 return False
418 412 self.tee.flush()
419 413 setattr(sys, self.channel, self.orig_stream)
420 414 printed = self.buffer.getvalue()
421 415 for s in self.s:
422 416 if isinstance(s, _re_type):
423 417 assert not s.search(printed),printed_msg.format(
424 418 s.pattern, self.channel, printed)
425 419 else:
426 420 assert s not in printed, printed_msg.format(
427 421 s, self.channel, printed)
428 422 return False
429 423 finally:
430 424 self.tee.close()
431 425
432 426 @contextmanager
433 427 def mute_warn():
434 428 from IPython.utils import warn
435 429 save_warn = warn.warn
436 430 warn.warn = lambda *a, **kw: None
437 431 try:
438 432 yield
439 433 finally:
440 434 warn.warn = save_warn
441 435
442 436 @contextmanager
443 437 def make_tempfile(name):
444 438 """ Create an empty, named, temporary file for the duration of the context.
445 439 """
446 440 f = open(name, 'w')
447 441 f.close()
448 442 try:
449 443 yield
450 444 finally:
451 445 os.unlink(name)
452 446
453 447
454 448 def help_output_test(subcommand=''):
455 449 """test that `ipython [subcommand] -h` works"""
456 450 cmd = get_ipython_cmd() + [subcommand, '-h']
457 451 out, err, rc = get_output_error_code(cmd)
458 452 nt.assert_equal(rc, 0, err)
459 453 nt.assert_not_in("Traceback", err)
460 454 nt.assert_in("Options", out)
461 455 nt.assert_in("--help-all", out)
462 456 return out, err
463 457
464 458
465 459 def help_all_output_test(subcommand=''):
466 460 """test that `ipython [subcommand] --help-all` works"""
467 461 cmd = get_ipython_cmd() + [subcommand, '--help-all']
468 462 out, err, rc = get_output_error_code(cmd)
469 463 nt.assert_equal(rc, 0, err)
470 464 nt.assert_not_in("Traceback", err)
471 465 nt.assert_in("Options", out)
472 466 nt.assert_in("Class parameters", out)
473 467 return out, err
474 468
General Comments 0
You need to be logged in to leave comments. Login now