##// END OF EJS Templates
Add failing test for issue gh-1456
Thomas Kluyver -
Show More
@@ -0,0 +1,51 b''
1 """Tests for IPython.core.ultratb
2 """
3
4 import os.path
5 import unittest
6
7 from IPython.testing import tools as tt
8 from IPython.utils.syspathcontext import prepended_to_syspath
9 from IPython.utils.tempdir import TemporaryDirectory
10
11 ip = get_ipython()
12
13 file_1 = """1
14 2
15 3
16 def f():
17 1/0
18 """
19
20 file_2 = """def f():
21 1/0
22 """
23
24 class ChangedPyFileTest(unittest.TestCase):
25 def test_changing_py_file(self):
26 """Traceback produced if the line where the error occurred is missing?
27
28 https://github.com/ipython/ipython/issues/1456
29 """
30 with TemporaryDirectory() as td:
31 fname = os.path.join(td, "foo.py")
32 with open(fname, "w") as f:
33 f.write(file_1)
34
35 with prepended_to_syspath(td):
36 ip.run_cell("import foo")
37
38 with tt.AssertPrints("ZeroDivisionError"):
39 ip.run_cell("foo.f()")
40
41 # Make the file shorter, so the line of the error is missing.
42 with open(fname, "w") as f:
43 f.write(file_2)
44
45 # For some reason, this was failing on the *second* call after
46 # changing the file, so we call f() twice.
47 with tt.AssertNotPrints("Internal Python error", channel='stderr'):
48 with tt.AssertPrints("ZeroDivisionError"):
49 ip.run_cell("foo.f()")
50 with tt.AssertPrints("ZeroDivisionError"):
51 ip.run_cell("foo.f()")
@@ -1,382 +1,391 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-2011 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
29 29 try:
30 30 # These tools are used by parts of the runtime, so we make the nose
31 31 # dependency optional at this point. Nose is a hard dependency to run the
32 32 # test suite, but NOT to use ipython itself.
33 33 import nose.tools as nt
34 34 has_nose = True
35 35 except ImportError:
36 36 has_nose = False
37 37
38 38 from IPython.config.loader import Config
39 39 from IPython.utils.process import find_cmd, getoutputerror
40 40 from IPython.utils.text import list_strings
41 41 from IPython.utils.io import temp_pyfile, Tee
42 42 from IPython.utils import py3compat
43 43 from IPython.utils.encoding import DEFAULT_ENCODING
44 44
45 45 from . import decorators as dec
46 46 from . import skipdoctest
47 47
48 48 #-----------------------------------------------------------------------------
49 49 # Functions and classes
50 50 #-----------------------------------------------------------------------------
51 51
52 52 # The docstring for full_path doctests differently on win32 (different path
53 53 # separator) so just skip the doctest there. The example remains informative.
54 54 doctest_deco = skipdoctest.skip_doctest if sys.platform == 'win32' else dec.null_deco
55 55
56 56 @doctest_deco
57 57 def full_path(startPath,files):
58 58 """Make full paths for all the listed files, based on startPath.
59 59
60 60 Only the base part of startPath is kept, since this routine is typically
61 61 used with a script's __file__ variable as startPath. The base of startPath
62 62 is then prepended to all the listed files, forming the output list.
63 63
64 64 Parameters
65 65 ----------
66 66 startPath : string
67 67 Initial path to use as the base for the results. This path is split
68 68 using os.path.split() and only its first component is kept.
69 69
70 70 files : string or list
71 71 One or more files.
72 72
73 73 Examples
74 74 --------
75 75
76 76 >>> full_path('/foo/bar.py',['a.txt','b.txt'])
77 77 ['/foo/a.txt', '/foo/b.txt']
78 78
79 79 >>> full_path('/foo',['a.txt','b.txt'])
80 80 ['/a.txt', '/b.txt']
81 81
82 82 If a single file is given, the output is still a list:
83 83 >>> full_path('/foo','a.txt')
84 84 ['/a.txt']
85 85 """
86 86
87 87 files = list_strings(files)
88 88 base = os.path.split(startPath)[0]
89 89 return [ os.path.join(base,f) for f in files ]
90 90
91 91
92 92 def parse_test_output(txt):
93 93 """Parse the output of a test run and return errors, failures.
94 94
95 95 Parameters
96 96 ----------
97 97 txt : str
98 98 Text output of a test run, assumed to contain a line of one of the
99 99 following forms::
100 100 'FAILED (errors=1)'
101 101 'FAILED (failures=1)'
102 102 'FAILED (errors=1, failures=1)'
103 103
104 104 Returns
105 105 -------
106 106 nerr, nfail: number of errors and failures.
107 107 """
108 108
109 109 err_m = re.search(r'^FAILED \(errors=(\d+)\)', txt, re.MULTILINE)
110 110 if err_m:
111 111 nerr = int(err_m.group(1))
112 112 nfail = 0
113 113 return nerr, nfail
114 114
115 115 fail_m = re.search(r'^FAILED \(failures=(\d+)\)', txt, re.MULTILINE)
116 116 if fail_m:
117 117 nerr = 0
118 118 nfail = int(fail_m.group(1))
119 119 return nerr, nfail
120 120
121 121 both_m = re.search(r'^FAILED \(errors=(\d+), failures=(\d+)\)', txt,
122 122 re.MULTILINE)
123 123 if both_m:
124 124 nerr = int(both_m.group(1))
125 125 nfail = int(both_m.group(2))
126 126 return nerr, nfail
127 127
128 128 # If the input didn't match any of these forms, assume no error/failures
129 129 return 0, 0
130 130
131 131
132 132 # So nose doesn't think this is a test
133 133 parse_test_output.__test__ = False
134 134
135 135
136 136 def default_argv():
137 137 """Return a valid default argv for creating testing instances of ipython"""
138 138
139 139 return ['--quick', # so no config file is loaded
140 140 # Other defaults to minimize side effects on stdout
141 141 '--colors=NoColor', '--no-term-title','--no-banner',
142 142 '--autocall=0']
143 143
144 144
145 145 def default_config():
146 146 """Return a config object with good defaults for testing."""
147 147 config = Config()
148 148 config.TerminalInteractiveShell.colors = 'NoColor'
149 149 config.TerminalTerminalInteractiveShell.term_title = False,
150 150 config.TerminalInteractiveShell.autocall = 0
151 151 config.HistoryManager.hist_file = tempfile.mktemp(u'test_hist.sqlite')
152 152 config.HistoryManager.db_cache_size = 10000
153 153 return config
154 154
155 155
156 156 def ipexec(fname, options=None):
157 157 """Utility to call 'ipython filename'.
158 158
159 159 Starts IPython witha minimal and safe configuration to make startup as fast
160 160 as possible.
161 161
162 162 Note that this starts IPython in a subprocess!
163 163
164 164 Parameters
165 165 ----------
166 166 fname : str
167 167 Name of file to be executed (should have .py or .ipy extension).
168 168
169 169 options : optional, list
170 170 Extra command-line flags to be passed to IPython.
171 171
172 172 Returns
173 173 -------
174 174 (stdout, stderr) of ipython subprocess.
175 175 """
176 176 if options is None: options = []
177 177
178 178 # For these subprocess calls, eliminate all prompt printing so we only see
179 179 # output from script execution
180 180 prompt_opts = [ '--PromptManager.in_template=""',
181 181 '--PromptManager.in2_template=""',
182 182 '--PromptManager.out_template=""'
183 183 ]
184 184 cmdargs = ' '.join(default_argv() + prompt_opts + options)
185 185
186 186 _ip = get_ipython()
187 187 test_dir = os.path.dirname(__file__)
188 188
189 189 ipython_cmd = find_cmd('ipython3' if py3compat.PY3 else 'ipython')
190 190 # Absolute path for filename
191 191 full_fname = os.path.join(test_dir, fname)
192 192 full_cmd = '%s %s %s' % (ipython_cmd, cmdargs, full_fname)
193 193 #print >> sys.stderr, 'FULL CMD:', full_cmd # dbg
194 194 out, err = getoutputerror(full_cmd)
195 195 # `import readline` causes 'ESC[?1034h' to be output sometimes,
196 196 # so strip that out before doing comparisons
197 197 if out:
198 198 out = re.sub(r'\x1b\[[^h]+h', '', out)
199 199 return out, err
200 200
201 201
202 202 def ipexec_validate(fname, expected_out, expected_err='',
203 203 options=None):
204 204 """Utility to call 'ipython filename' and validate output/error.
205 205
206 206 This function raises an AssertionError if the validation fails.
207 207
208 208 Note that this starts IPython in a subprocess!
209 209
210 210 Parameters
211 211 ----------
212 212 fname : str
213 213 Name of the file to be executed (should have .py or .ipy extension).
214 214
215 215 expected_out : str
216 216 Expected stdout of the process.
217 217
218 218 expected_err : optional, str
219 219 Expected stderr of the process.
220 220
221 221 options : optional, list
222 222 Extra command-line flags to be passed to IPython.
223 223
224 224 Returns
225 225 -------
226 226 None
227 227 """
228 228
229 229 import nose.tools as nt
230 230
231 231 out, err = ipexec(fname, options)
232 232 #print 'OUT', out # dbg
233 233 #print 'ERR', err # dbg
234 234 # If there are any errors, we must check those befor stdout, as they may be
235 235 # more informative than simply having an empty stdout.
236 236 if err:
237 237 if expected_err:
238 238 nt.assert_equal(err.strip(), expected_err.strip())
239 239 else:
240 240 raise ValueError('Running file %r produced error: %r' %
241 241 (fname, err))
242 242 # If no errors or output on stderr was expected, match stdout
243 243 nt.assert_equal(out.strip(), expected_out.strip())
244 244
245 245
246 246 class TempFileMixin(object):
247 247 """Utility class to create temporary Python/IPython files.
248 248
249 249 Meant as a mixin class for test cases."""
250 250
251 251 def mktmp(self, src, ext='.py'):
252 252 """Make a valid python temp file."""
253 253 fname, f = temp_pyfile(src, ext)
254 254 self.tmpfile = f
255 255 self.fname = fname
256 256
257 257 def tearDown(self):
258 258 if hasattr(self, 'tmpfile'):
259 259 # If the tmpfile wasn't made because of skipped tests, like in
260 260 # win32, there's nothing to cleanup.
261 261 self.tmpfile.close()
262 262 try:
263 263 os.unlink(self.fname)
264 264 except:
265 265 # On Windows, even though we close the file, we still can't
266 266 # delete it. I have no clue why
267 267 pass
268 268
269 269 pair_fail_msg = ("Testing {0}\n\n"
270 270 "In:\n"
271 271 " {1!r}\n"
272 272 "Expected:\n"
273 273 " {2!r}\n"
274 274 "Got:\n"
275 275 " {3!r}\n")
276 276 def check_pairs(func, pairs):
277 277 """Utility function for the common case of checking a function with a
278 278 sequence of input/output pairs.
279 279
280 280 Parameters
281 281 ----------
282 282 func : callable
283 283 The function to be tested. Should accept a single argument.
284 284 pairs : iterable
285 285 A list of (input, expected_output) tuples.
286 286
287 287 Returns
288 288 -------
289 289 None. Raises an AssertionError if any output does not match the expected
290 290 value.
291 291 """
292 292 name = getattr(func, "func_name", getattr(func, "__name__", "<unknown>"))
293 293 for inp, expected in pairs:
294 294 out = func(inp)
295 295 assert out == expected, pair_fail_msg.format(name, inp, expected, out)
296 296
297 297
298 298 if py3compat.PY3:
299 299 MyStringIO = StringIO
300 300 else:
301 301 # In Python 2, stdout/stderr can have either bytes or unicode written to them,
302 302 # so we need a class that can handle both.
303 303 class MyStringIO(StringIO):
304 304 def write(self, s):
305 305 s = py3compat.cast_unicode(s, encoding=DEFAULT_ENCODING)
306 306 super(MyStringIO, self).write(s)
307 307
308 308 notprinted_msg = """Did not find {0!r} in printed output (on {1}):
309 {2!r}"""
309 -------
310 {2!s}
311 -------
312 """
310 313
311 314 class AssertPrints(object):
312 315 """Context manager for testing that code prints certain text.
313 316
314 317 Examples
315 318 --------
316 319 >>> with AssertPrints("abc", suppress=False):
317 320 ... print "abcd"
318 321 ... print "def"
319 322 ...
320 323 abcd
321 324 def
322 325 """
323 326 def __init__(self, s, channel='stdout', suppress=True):
324 327 self.s = s
325 328 self.channel = channel
326 329 self.suppress = suppress
327 330
328 331 def __enter__(self):
329 332 self.orig_stream = getattr(sys, self.channel)
330 333 self.buffer = MyStringIO()
331 334 self.tee = Tee(self.buffer, channel=self.channel)
332 335 setattr(sys, self.channel, self.buffer if self.suppress else self.tee)
333 336
334 337 def __exit__(self, etype, value, traceback):
335 338 self.tee.flush()
336 339 setattr(sys, self.channel, self.orig_stream)
337 340 printed = self.buffer.getvalue()
338 341 assert self.s in printed, notprinted_msg.format(self.s, self.channel, printed)
339 342 return False
340
343
344 printed_msg = """Found {0!r} in printed output (on {1}):
345 -------
346 {2!s}
347 -------
348 """
349
341 350 class AssertNotPrints(AssertPrints):
342 351 """Context manager for checking that certain output *isn't* produced.
343 352
344 353 Counterpart of AssertPrints"""
345 354 def __exit__(self, etype, value, traceback):
346 355 self.tee.flush()
347 356 setattr(sys, self.channel, self.orig_stream)
348 357 printed = self.buffer.getvalue()
349 assert self.s not in printed, notprinted_msg.format(self.s, self.channel, printed)
358 assert self.s not in printed, printed_msg.format(self.s, self.channel, printed)
350 359 return False
351 360
352 361 @contextmanager
353 362 def mute_warn():
354 363 from IPython.utils import warn
355 364 save_warn = warn.warn
356 365 warn.warn = lambda *a, **kw: None
357 366 try:
358 367 yield
359 368 finally:
360 369 warn.warn = save_warn
361 370
362 371 @contextmanager
363 372 def make_tempfile(name):
364 373 """ Create an empty, named, temporary file for the duration of the context.
365 374 """
366 375 f = open(name, 'w')
367 376 f.close()
368 377 try:
369 378 yield
370 379 finally:
371 380 os.unlink(name)
372 381
373 382
374 383 @contextmanager
375 384 def monkeypatch(obj, name, attr):
376 385 """
377 386 Context manager to replace attribute named `name` in `obj` with `attr`.
378 387 """
379 388 orig = getattr(obj, name)
380 389 setattr(obj, name, attr)
381 390 yield
382 391 setattr(obj, name, orig)
General Comments 0
You need to be logged in to leave comments. Login now