##// END OF EJS Templates
TST: Put %xmode back after testing new Minimal mode.
Dan Allan -
Show More
@@ -1,403 +1,406 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """Tests for IPython.core.ultratb
2 """Tests for IPython.core.ultratb
3 """
3 """
4 import io
4 import io
5 import logging
5 import logging
6 import sys
6 import sys
7 import os.path
7 import os.path
8 from textwrap import dedent
8 from textwrap import dedent
9 import traceback
9 import traceback
10 import unittest
10 import unittest
11 from unittest import mock
11 from unittest import mock
12
12
13 from ..ultratb import ColorTB, VerboseTB, find_recursion
13 from ..ultratb import ColorTB, VerboseTB, find_recursion
14
14
15
15
16 from IPython.testing import tools as tt
16 from IPython.testing import tools as tt
17 from IPython.testing.decorators import onlyif_unicode_paths
17 from IPython.testing.decorators import onlyif_unicode_paths
18 from IPython.utils.syspathcontext import prepended_to_syspath
18 from IPython.utils.syspathcontext import prepended_to_syspath
19 from IPython.utils.tempdir import TemporaryDirectory
19 from IPython.utils.tempdir import TemporaryDirectory
20
20
21 ip = get_ipython()
21 ip = get_ipython()
22
22
23 file_1 = """1
23 file_1 = """1
24 2
24 2
25 3
25 3
26 def f():
26 def f():
27 1/0
27 1/0
28 """
28 """
29
29
30 file_2 = """def f():
30 file_2 = """def f():
31 1/0
31 1/0
32 """
32 """
33
33
34 class ChangedPyFileTest(unittest.TestCase):
34 class ChangedPyFileTest(unittest.TestCase):
35 def test_changing_py_file(self):
35 def test_changing_py_file(self):
36 """Traceback produced if the line where the error occurred is missing?
36 """Traceback produced if the line where the error occurred is missing?
37
37
38 https://github.com/ipython/ipython/issues/1456
38 https://github.com/ipython/ipython/issues/1456
39 """
39 """
40 with TemporaryDirectory() as td:
40 with TemporaryDirectory() as td:
41 fname = os.path.join(td, "foo.py")
41 fname = os.path.join(td, "foo.py")
42 with open(fname, "w") as f:
42 with open(fname, "w") as f:
43 f.write(file_1)
43 f.write(file_1)
44
44
45 with prepended_to_syspath(td):
45 with prepended_to_syspath(td):
46 ip.run_cell("import foo")
46 ip.run_cell("import foo")
47
47
48 with tt.AssertPrints("ZeroDivisionError"):
48 with tt.AssertPrints("ZeroDivisionError"):
49 ip.run_cell("foo.f()")
49 ip.run_cell("foo.f()")
50
50
51 # Make the file shorter, so the line of the error is missing.
51 # Make the file shorter, so the line of the error is missing.
52 with open(fname, "w") as f:
52 with open(fname, "w") as f:
53 f.write(file_2)
53 f.write(file_2)
54
54
55 # For some reason, this was failing on the *second* call after
55 # For some reason, this was failing on the *second* call after
56 # changing the file, so we call f() twice.
56 # changing the file, so we call f() twice.
57 with tt.AssertNotPrints("Internal Python error", channel='stderr'):
57 with tt.AssertNotPrints("Internal Python error", channel='stderr'):
58 with tt.AssertPrints("ZeroDivisionError"):
58 with tt.AssertPrints("ZeroDivisionError"):
59 ip.run_cell("foo.f()")
59 ip.run_cell("foo.f()")
60 with tt.AssertPrints("ZeroDivisionError"):
60 with tt.AssertPrints("ZeroDivisionError"):
61 ip.run_cell("foo.f()")
61 ip.run_cell("foo.f()")
62
62
63 iso_8859_5_file = u'''# coding: iso-8859-5
63 iso_8859_5_file = u'''# coding: iso-8859-5
64
64
65 def fail():
65 def fail():
66 """Π΄Π±Π˜Π–"""
66 """Π΄Π±Π˜Π–"""
67 1/0 # Π΄Π±Π˜Π–
67 1/0 # Π΄Π±Π˜Π–
68 '''
68 '''
69
69
70 class NonAsciiTest(unittest.TestCase):
70 class NonAsciiTest(unittest.TestCase):
71 @onlyif_unicode_paths
71 @onlyif_unicode_paths
72 def test_nonascii_path(self):
72 def test_nonascii_path(self):
73 # Non-ascii directory name as well.
73 # Non-ascii directory name as well.
74 with TemporaryDirectory(suffix=u'Γ©') as td:
74 with TemporaryDirectory(suffix=u'Γ©') as td:
75 fname = os.path.join(td, u"fooΓ©.py")
75 fname = os.path.join(td, u"fooΓ©.py")
76 with open(fname, "w") as f:
76 with open(fname, "w") as f:
77 f.write(file_1)
77 f.write(file_1)
78
78
79 with prepended_to_syspath(td):
79 with prepended_to_syspath(td):
80 ip.run_cell("import foo")
80 ip.run_cell("import foo")
81
81
82 with tt.AssertPrints("ZeroDivisionError"):
82 with tt.AssertPrints("ZeroDivisionError"):
83 ip.run_cell("foo.f()")
83 ip.run_cell("foo.f()")
84
84
85 def test_iso8859_5(self):
85 def test_iso8859_5(self):
86 with TemporaryDirectory() as td:
86 with TemporaryDirectory() as td:
87 fname = os.path.join(td, 'dfghjkl.py')
87 fname = os.path.join(td, 'dfghjkl.py')
88
88
89 with io.open(fname, 'w', encoding='iso-8859-5') as f:
89 with io.open(fname, 'w', encoding='iso-8859-5') as f:
90 f.write(iso_8859_5_file)
90 f.write(iso_8859_5_file)
91
91
92 with prepended_to_syspath(td):
92 with prepended_to_syspath(td):
93 ip.run_cell("from dfghjkl import fail")
93 ip.run_cell("from dfghjkl import fail")
94
94
95 with tt.AssertPrints("ZeroDivisionError"):
95 with tt.AssertPrints("ZeroDivisionError"):
96 with tt.AssertPrints(u'Π΄Π±Π˜Π–', suppress=False):
96 with tt.AssertPrints(u'Π΄Π±Π˜Π–', suppress=False):
97 ip.run_cell('fail()')
97 ip.run_cell('fail()')
98
98
99 def test_nonascii_msg(self):
99 def test_nonascii_msg(self):
100 cell = u"raise Exception('Γ©')"
100 cell = u"raise Exception('Γ©')"
101 expected = u"Exception('Γ©')"
101 expected = u"Exception('Γ©')"
102 ip.run_cell("%xmode plain")
102 ip.run_cell("%xmode plain")
103 with tt.AssertPrints(expected):
103 with tt.AssertPrints(expected):
104 ip.run_cell(cell)
104 ip.run_cell(cell)
105
105
106 ip.run_cell("%xmode verbose")
106 ip.run_cell("%xmode verbose")
107 with tt.AssertPrints(expected):
107 with tt.AssertPrints(expected):
108 ip.run_cell(cell)
108 ip.run_cell(cell)
109
109
110 ip.run_cell("%xmode context")
110 ip.run_cell("%xmode context")
111 with tt.AssertPrints(expected):
111 with tt.AssertPrints(expected):
112 ip.run_cell(cell)
112 ip.run_cell(cell)
113
113
114 ip.run_cell("%xmode minimal")
114 ip.run_cell("%xmode minimal")
115 with tt.AssertPrints(u"Exception: Γ©"):
115 with tt.AssertPrints(u"Exception: Γ©"):
116 ip.run_cell(cell)
116 ip.run_cell(cell)
117
117
118 # Put this back into Context mode for later tests.
119 ip.run_cell("%xmode context")
120
118 class NestedGenExprTestCase(unittest.TestCase):
121 class NestedGenExprTestCase(unittest.TestCase):
119 """
122 """
120 Regression test for the following issues:
123 Regression test for the following issues:
121 https://github.com/ipython/ipython/issues/8293
124 https://github.com/ipython/ipython/issues/8293
122 https://github.com/ipython/ipython/issues/8205
125 https://github.com/ipython/ipython/issues/8205
123 """
126 """
124 def test_nested_genexpr(self):
127 def test_nested_genexpr(self):
125 code = dedent(
128 code = dedent(
126 """\
129 """\
127 class SpecificException(Exception):
130 class SpecificException(Exception):
128 pass
131 pass
129
132
130 def foo(x):
133 def foo(x):
131 raise SpecificException("Success!")
134 raise SpecificException("Success!")
132
135
133 sum(sum(foo(x) for _ in [0]) for x in [0])
136 sum(sum(foo(x) for _ in [0]) for x in [0])
134 """
137 """
135 )
138 )
136 with tt.AssertPrints('SpecificException: Success!', suppress=False):
139 with tt.AssertPrints('SpecificException: Success!', suppress=False):
137 ip.run_cell(code)
140 ip.run_cell(code)
138
141
139
142
140 indentationerror_file = """if True:
143 indentationerror_file = """if True:
141 zoon()
144 zoon()
142 """
145 """
143
146
144 class IndentationErrorTest(unittest.TestCase):
147 class IndentationErrorTest(unittest.TestCase):
145 def test_indentationerror_shows_line(self):
148 def test_indentationerror_shows_line(self):
146 # See issue gh-2398
149 # See issue gh-2398
147 with tt.AssertPrints("IndentationError"):
150 with tt.AssertPrints("IndentationError"):
148 with tt.AssertPrints("zoon()", suppress=False):
151 with tt.AssertPrints("zoon()", suppress=False):
149 ip.run_cell(indentationerror_file)
152 ip.run_cell(indentationerror_file)
150
153
151 with TemporaryDirectory() as td:
154 with TemporaryDirectory() as td:
152 fname = os.path.join(td, "foo.py")
155 fname = os.path.join(td, "foo.py")
153 with open(fname, "w") as f:
156 with open(fname, "w") as f:
154 f.write(indentationerror_file)
157 f.write(indentationerror_file)
155
158
156 with tt.AssertPrints("IndentationError"):
159 with tt.AssertPrints("IndentationError"):
157 with tt.AssertPrints("zoon()", suppress=False):
160 with tt.AssertPrints("zoon()", suppress=False):
158 ip.magic('run %s' % fname)
161 ip.magic('run %s' % fname)
159
162
160 se_file_1 = """1
163 se_file_1 = """1
161 2
164 2
162 7/
165 7/
163 """
166 """
164
167
165 se_file_2 = """7/
168 se_file_2 = """7/
166 """
169 """
167
170
168 class SyntaxErrorTest(unittest.TestCase):
171 class SyntaxErrorTest(unittest.TestCase):
169 def test_syntaxerror_without_lineno(self):
172 def test_syntaxerror_without_lineno(self):
170 with tt.AssertNotPrints("TypeError"):
173 with tt.AssertNotPrints("TypeError"):
171 with tt.AssertPrints("line unknown"):
174 with tt.AssertPrints("line unknown"):
172 ip.run_cell("raise SyntaxError()")
175 ip.run_cell("raise SyntaxError()")
173
176
174 def test_syntaxerror_no_stacktrace_at_compile_time(self):
177 def test_syntaxerror_no_stacktrace_at_compile_time(self):
175 syntax_error_at_compile_time = """
178 syntax_error_at_compile_time = """
176 def foo():
179 def foo():
177 ..
180 ..
178 """
181 """
179 with tt.AssertPrints("SyntaxError"):
182 with tt.AssertPrints("SyntaxError"):
180 ip.run_cell(syntax_error_at_compile_time)
183 ip.run_cell(syntax_error_at_compile_time)
181
184
182 with tt.AssertNotPrints("foo()"):
185 with tt.AssertNotPrints("foo()"):
183 ip.run_cell(syntax_error_at_compile_time)
186 ip.run_cell(syntax_error_at_compile_time)
184
187
185 def test_syntaxerror_stacktrace_when_running_compiled_code(self):
188 def test_syntaxerror_stacktrace_when_running_compiled_code(self):
186 syntax_error_at_runtime = """
189 syntax_error_at_runtime = """
187 def foo():
190 def foo():
188 eval("..")
191 eval("..")
189
192
190 def bar():
193 def bar():
191 foo()
194 foo()
192
195
193 bar()
196 bar()
194 """
197 """
195 with tt.AssertPrints("SyntaxError"):
198 with tt.AssertPrints("SyntaxError"):
196 ip.run_cell(syntax_error_at_runtime)
199 ip.run_cell(syntax_error_at_runtime)
197 # Assert syntax error during runtime generate stacktrace
200 # Assert syntax error during runtime generate stacktrace
198 with tt.AssertPrints(["foo()", "bar()"]):
201 with tt.AssertPrints(["foo()", "bar()"]):
199 ip.run_cell(syntax_error_at_runtime)
202 ip.run_cell(syntax_error_at_runtime)
200
203
201 def test_changing_py_file(self):
204 def test_changing_py_file(self):
202 with TemporaryDirectory() as td:
205 with TemporaryDirectory() as td:
203 fname = os.path.join(td, "foo.py")
206 fname = os.path.join(td, "foo.py")
204 with open(fname, 'w') as f:
207 with open(fname, 'w') as f:
205 f.write(se_file_1)
208 f.write(se_file_1)
206
209
207 with tt.AssertPrints(["7/", "SyntaxError"]):
210 with tt.AssertPrints(["7/", "SyntaxError"]):
208 ip.magic("run " + fname)
211 ip.magic("run " + fname)
209
212
210 # Modify the file
213 # Modify the file
211 with open(fname, 'w') as f:
214 with open(fname, 'w') as f:
212 f.write(se_file_2)
215 f.write(se_file_2)
213
216
214 # The SyntaxError should point to the correct line
217 # The SyntaxError should point to the correct line
215 with tt.AssertPrints(["7/", "SyntaxError"]):
218 with tt.AssertPrints(["7/", "SyntaxError"]):
216 ip.magic("run " + fname)
219 ip.magic("run " + fname)
217
220
218 def test_non_syntaxerror(self):
221 def test_non_syntaxerror(self):
219 # SyntaxTB may be called with an error other than a SyntaxError
222 # SyntaxTB may be called with an error other than a SyntaxError
220 # See e.g. gh-4361
223 # See e.g. gh-4361
221 try:
224 try:
222 raise ValueError('QWERTY')
225 raise ValueError('QWERTY')
223 except ValueError:
226 except ValueError:
224 with tt.AssertPrints('QWERTY'):
227 with tt.AssertPrints('QWERTY'):
225 ip.showsyntaxerror()
228 ip.showsyntaxerror()
226
229
227
230
228 class Python3ChainedExceptionsTest(unittest.TestCase):
231 class Python3ChainedExceptionsTest(unittest.TestCase):
229 DIRECT_CAUSE_ERROR_CODE = """
232 DIRECT_CAUSE_ERROR_CODE = """
230 try:
233 try:
231 x = 1 + 2
234 x = 1 + 2
232 print(not_defined_here)
235 print(not_defined_here)
233 except Exception as e:
236 except Exception as e:
234 x += 55
237 x += 55
235 x - 1
238 x - 1
236 y = {}
239 y = {}
237 raise KeyError('uh') from e
240 raise KeyError('uh') from e
238 """
241 """
239
242
240 EXCEPTION_DURING_HANDLING_CODE = """
243 EXCEPTION_DURING_HANDLING_CODE = """
241 try:
244 try:
242 x = 1 + 2
245 x = 1 + 2
243 print(not_defined_here)
246 print(not_defined_here)
244 except Exception as e:
247 except Exception as e:
245 x += 55
248 x += 55
246 x - 1
249 x - 1
247 y = {}
250 y = {}
248 raise KeyError('uh')
251 raise KeyError('uh')
249 """
252 """
250
253
251 SUPPRESS_CHAINING_CODE = """
254 SUPPRESS_CHAINING_CODE = """
252 try:
255 try:
253 1/0
256 1/0
254 except Exception:
257 except Exception:
255 raise ValueError("Yikes") from None
258 raise ValueError("Yikes") from None
256 """
259 """
257
260
258 def test_direct_cause_error(self):
261 def test_direct_cause_error(self):
259 with tt.AssertPrints(["KeyError", "NameError", "direct cause"]):
262 with tt.AssertPrints(["KeyError", "NameError", "direct cause"]):
260 ip.run_cell(self.DIRECT_CAUSE_ERROR_CODE)
263 ip.run_cell(self.DIRECT_CAUSE_ERROR_CODE)
261
264
262 def test_exception_during_handling_error(self):
265 def test_exception_during_handling_error(self):
263 with tt.AssertPrints(["KeyError", "NameError", "During handling"]):
266 with tt.AssertPrints(["KeyError", "NameError", "During handling"]):
264 ip.run_cell(self.EXCEPTION_DURING_HANDLING_CODE)
267 ip.run_cell(self.EXCEPTION_DURING_HANDLING_CODE)
265
268
266 def test_suppress_exception_chaining(self):
269 def test_suppress_exception_chaining(self):
267 with tt.AssertNotPrints("ZeroDivisionError"), \
270 with tt.AssertNotPrints("ZeroDivisionError"), \
268 tt.AssertPrints("ValueError", suppress=False):
271 tt.AssertPrints("ValueError", suppress=False):
269 ip.run_cell(self.SUPPRESS_CHAINING_CODE)
272 ip.run_cell(self.SUPPRESS_CHAINING_CODE)
270
273
271
274
272 class RecursionTest(unittest.TestCase):
275 class RecursionTest(unittest.TestCase):
273 DEFINITIONS = """
276 DEFINITIONS = """
274 def non_recurs():
277 def non_recurs():
275 1/0
278 1/0
276
279
277 def r1():
280 def r1():
278 r1()
281 r1()
279
282
280 def r3a():
283 def r3a():
281 r3b()
284 r3b()
282
285
283 def r3b():
286 def r3b():
284 r3c()
287 r3c()
285
288
286 def r3c():
289 def r3c():
287 r3a()
290 r3a()
288
291
289 def r3o1():
292 def r3o1():
290 r3a()
293 r3a()
291
294
292 def r3o2():
295 def r3o2():
293 r3o1()
296 r3o1()
294 """
297 """
295 def setUp(self):
298 def setUp(self):
296 ip.run_cell(self.DEFINITIONS)
299 ip.run_cell(self.DEFINITIONS)
297
300
298 def test_no_recursion(self):
301 def test_no_recursion(self):
299 with tt.AssertNotPrints("frames repeated"):
302 with tt.AssertNotPrints("frames repeated"):
300 ip.run_cell("non_recurs()")
303 ip.run_cell("non_recurs()")
301
304
302 def test_recursion_one_frame(self):
305 def test_recursion_one_frame(self):
303 with tt.AssertPrints("1 frames repeated"):
306 with tt.AssertPrints("1 frames repeated"):
304 ip.run_cell("r1()")
307 ip.run_cell("r1()")
305
308
306 def test_recursion_three_frames(self):
309 def test_recursion_three_frames(self):
307 with tt.AssertPrints("3 frames repeated"):
310 with tt.AssertPrints("3 frames repeated"):
308 ip.run_cell("r3o2()")
311 ip.run_cell("r3o2()")
309
312
310 def test_find_recursion(self):
313 def test_find_recursion(self):
311 captured = []
314 captured = []
312 def capture_exc(*args, **kwargs):
315 def capture_exc(*args, **kwargs):
313 captured.append(sys.exc_info())
316 captured.append(sys.exc_info())
314 with mock.patch.object(ip, 'showtraceback', capture_exc):
317 with mock.patch.object(ip, 'showtraceback', capture_exc):
315 ip.run_cell("r3o2()")
318 ip.run_cell("r3o2()")
316
319
317 self.assertEqual(len(captured), 1)
320 self.assertEqual(len(captured), 1)
318 etype, evalue, tb = captured[0]
321 etype, evalue, tb = captured[0]
319 self.assertIn("recursion", str(evalue))
322 self.assertIn("recursion", str(evalue))
320
323
321 records = ip.InteractiveTB.get_records(tb, 3, ip.InteractiveTB.tb_offset)
324 records = ip.InteractiveTB.get_records(tb, 3, ip.InteractiveTB.tb_offset)
322 for r in records[:10]:
325 for r in records[:10]:
323 print(r[1:4])
326 print(r[1:4])
324
327
325 # The outermost frames should be:
328 # The outermost frames should be:
326 # 0: the 'cell' that was running when the exception came up
329 # 0: the 'cell' that was running when the exception came up
327 # 1: r3o2()
330 # 1: r3o2()
328 # 2: r3o1()
331 # 2: r3o1()
329 # 3: r3a()
332 # 3: r3a()
330 # Then repeating r3b, r3c, r3a
333 # Then repeating r3b, r3c, r3a
331 last_unique, repeat_length = find_recursion(etype, evalue, records)
334 last_unique, repeat_length = find_recursion(etype, evalue, records)
332 self.assertEqual(last_unique, 2)
335 self.assertEqual(last_unique, 2)
333 self.assertEqual(repeat_length, 3)
336 self.assertEqual(repeat_length, 3)
334
337
335
338
336 #----------------------------------------------------------------------------
339 #----------------------------------------------------------------------------
337
340
338 # module testing (minimal)
341 # module testing (minimal)
339 def test_handlers():
342 def test_handlers():
340 def spam(c, d_e):
343 def spam(c, d_e):
341 (d, e) = d_e
344 (d, e) = d_e
342 x = c + d
345 x = c + d
343 y = c * d
346 y = c * d
344 foo(x, y)
347 foo(x, y)
345
348
346 def foo(a, b, bar=1):
349 def foo(a, b, bar=1):
347 eggs(a, b + bar)
350 eggs(a, b + bar)
348
351
349 def eggs(f, g, z=globals()):
352 def eggs(f, g, z=globals()):
350 h = f + g
353 h = f + g
351 i = f - g
354 i = f - g
352 return h / i
355 return h / i
353
356
354 buff = io.StringIO()
357 buff = io.StringIO()
355
358
356 buff.write('')
359 buff.write('')
357 buff.write('*** Before ***')
360 buff.write('*** Before ***')
358 try:
361 try:
359 buff.write(spam(1, (2, 3)))
362 buff.write(spam(1, (2, 3)))
360 except:
363 except:
361 traceback.print_exc(file=buff)
364 traceback.print_exc(file=buff)
362
365
363 handler = ColorTB(ostream=buff)
366 handler = ColorTB(ostream=buff)
364 buff.write('*** ColorTB ***')
367 buff.write('*** ColorTB ***')
365 try:
368 try:
366 buff.write(spam(1, (2, 3)))
369 buff.write(spam(1, (2, 3)))
367 except:
370 except:
368 handler(*sys.exc_info())
371 handler(*sys.exc_info())
369 buff.write('')
372 buff.write('')
370
373
371 handler = VerboseTB(ostream=buff)
374 handler = VerboseTB(ostream=buff)
372 buff.write('*** VerboseTB ***')
375 buff.write('*** VerboseTB ***')
373 try:
376 try:
374 buff.write(spam(1, (2, 3)))
377 buff.write(spam(1, (2, 3)))
375 except:
378 except:
376 handler(*sys.exc_info())
379 handler(*sys.exc_info())
377 buff.write('')
380 buff.write('')
378
381
379
382
380 class TokenizeFailureTest(unittest.TestCase):
383 class TokenizeFailureTest(unittest.TestCase):
381 """Tests related to https://github.com/ipython/ipython/issues/6864."""
384 """Tests related to https://github.com/ipython/ipython/issues/6864."""
382
385
383 def testLogging(self):
386 def testLogging(self):
384 message = "An unexpected error occurred while tokenizing input"
387 message = "An unexpected error occurred while tokenizing input"
385 cell = 'raise ValueError("""a\nb""")'
388 cell = 'raise ValueError("""a\nb""")'
386
389
387 stream = io.StringIO()
390 stream = io.StringIO()
388 handler = logging.StreamHandler(stream)
391 handler = logging.StreamHandler(stream)
389 logger = logging.getLogger()
392 logger = logging.getLogger()
390 loglevel = logger.level
393 loglevel = logger.level
391 logger.addHandler(handler)
394 logger.addHandler(handler)
392 self.addCleanup(lambda: logger.removeHandler(handler))
395 self.addCleanup(lambda: logger.removeHandler(handler))
393 self.addCleanup(lambda: logger.setLevel(loglevel))
396 self.addCleanup(lambda: logger.setLevel(loglevel))
394
397
395 logger.setLevel(logging.INFO)
398 logger.setLevel(logging.INFO)
396 with tt.AssertNotPrints(message):
399 with tt.AssertNotPrints(message):
397 ip.run_cell(cell)
400 ip.run_cell(cell)
398 self.assertNotIn(message, stream.getvalue())
401 self.assertNotIn(message, stream.getvalue())
399
402
400 logger.setLevel(logging.DEBUG)
403 logger.setLevel(logging.DEBUG)
401 with tt.AssertNotPrints(message):
404 with tt.AssertNotPrints(message):
402 ip.run_cell(cell)
405 ip.run_cell(cell)
403 self.assertIn(message, stream.getvalue())
406 self.assertIn(message, stream.getvalue())
General Comments 0
You need to be logged in to leave comments. Login now