##// END OF EJS Templates
Skip tests that now seem to pass on 3.8.
Matthias Bussonnier -
Show More
@@ -1,406 +1,408 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.
118 # Put this back into Context mode for later tests.
119 ip.run_cell("%xmode context")
119 ip.run_cell("%xmode context")
120
120
121 class NestedGenExprTestCase(unittest.TestCase):
121 class NestedGenExprTestCase(unittest.TestCase):
122 """
122 """
123 Regression test for the following issues:
123 Regression test for the following issues:
124 https://github.com/ipython/ipython/issues/8293
124 https://github.com/ipython/ipython/issues/8293
125 https://github.com/ipython/ipython/issues/8205
125 https://github.com/ipython/ipython/issues/8205
126 """
126 """
127 def test_nested_genexpr(self):
127 def test_nested_genexpr(self):
128 code = dedent(
128 code = dedent(
129 """\
129 """\
130 class SpecificException(Exception):
130 class SpecificException(Exception):
131 pass
131 pass
132
132
133 def foo(x):
133 def foo(x):
134 raise SpecificException("Success!")
134 raise SpecificException("Success!")
135
135
136 sum(sum(foo(x) for _ in [0]) for x in [0])
136 sum(sum(foo(x) for _ in [0]) for x in [0])
137 """
137 """
138 )
138 )
139 with tt.AssertPrints('SpecificException: Success!', suppress=False):
139 with tt.AssertPrints('SpecificException: Success!', suppress=False):
140 ip.run_cell(code)
140 ip.run_cell(code)
141
141
142
142
143 indentationerror_file = """if True:
143 indentationerror_file = """if True:
144 zoon()
144 zoon()
145 """
145 """
146
146
147 class IndentationErrorTest(unittest.TestCase):
147 class IndentationErrorTest(unittest.TestCase):
148 def test_indentationerror_shows_line(self):
148 def test_indentationerror_shows_line(self):
149 # See issue gh-2398
149 # See issue gh-2398
150 with tt.AssertPrints("IndentationError"):
150 with tt.AssertPrints("IndentationError"):
151 with tt.AssertPrints("zoon()", suppress=False):
151 with tt.AssertPrints("zoon()", suppress=False):
152 ip.run_cell(indentationerror_file)
152 ip.run_cell(indentationerror_file)
153
153
154 with TemporaryDirectory() as td:
154 with TemporaryDirectory() as td:
155 fname = os.path.join(td, "foo.py")
155 fname = os.path.join(td, "foo.py")
156 with open(fname, "w") as f:
156 with open(fname, "w") as f:
157 f.write(indentationerror_file)
157 f.write(indentationerror_file)
158
158
159 with tt.AssertPrints("IndentationError"):
159 with tt.AssertPrints("IndentationError"):
160 with tt.AssertPrints("zoon()", suppress=False):
160 with tt.AssertPrints("zoon()", suppress=False):
161 ip.magic('run %s' % fname)
161 ip.magic('run %s' % fname)
162
162
163 se_file_1 = """1
163 se_file_1 = """1
164 2
164 2
165 7/
165 7/
166 """
166 """
167
167
168 se_file_2 = """7/
168 se_file_2 = """7/
169 """
169 """
170
170
171 class SyntaxErrorTest(unittest.TestCase):
171 class SyntaxErrorTest(unittest.TestCase):
172 def test_syntaxerror_without_lineno(self):
172 def test_syntaxerror_without_lineno(self):
173 with tt.AssertNotPrints("TypeError"):
173 with tt.AssertNotPrints("TypeError"):
174 with tt.AssertPrints("line unknown"):
174 with tt.AssertPrints("line unknown"):
175 ip.run_cell("raise SyntaxError()")
175 ip.run_cell("raise SyntaxError()")
176
176
177 def test_syntaxerror_no_stacktrace_at_compile_time(self):
177 def test_syntaxerror_no_stacktrace_at_compile_time(self):
178 syntax_error_at_compile_time = """
178 syntax_error_at_compile_time = """
179 def foo():
179 def foo():
180 ..
180 ..
181 """
181 """
182 with tt.AssertPrints("SyntaxError"):
182 with tt.AssertPrints("SyntaxError"):
183 ip.run_cell(syntax_error_at_compile_time)
183 ip.run_cell(syntax_error_at_compile_time)
184
184
185 with tt.AssertNotPrints("foo()"):
185 with tt.AssertNotPrints("foo()"):
186 ip.run_cell(syntax_error_at_compile_time)
186 ip.run_cell(syntax_error_at_compile_time)
187
187
188 def test_syntaxerror_stacktrace_when_running_compiled_code(self):
188 def test_syntaxerror_stacktrace_when_running_compiled_code(self):
189 syntax_error_at_runtime = """
189 syntax_error_at_runtime = """
190 def foo():
190 def foo():
191 eval("..")
191 eval("..")
192
192
193 def bar():
193 def bar():
194 foo()
194 foo()
195
195
196 bar()
196 bar()
197 """
197 """
198 with tt.AssertPrints("SyntaxError"):
198 with tt.AssertPrints("SyntaxError"):
199 ip.run_cell(syntax_error_at_runtime)
199 ip.run_cell(syntax_error_at_runtime)
200 # Assert syntax error during runtime generate stacktrace
200 # Assert syntax error during runtime generate stacktrace
201 with tt.AssertPrints(["foo()", "bar()"]):
201 with tt.AssertPrints(["foo()", "bar()"]):
202 ip.run_cell(syntax_error_at_runtime)
202 ip.run_cell(syntax_error_at_runtime)
203
203
204 def test_changing_py_file(self):
204 def test_changing_py_file(self):
205 with TemporaryDirectory() as td:
205 with TemporaryDirectory() as td:
206 fname = os.path.join(td, "foo.py")
206 fname = os.path.join(td, "foo.py")
207 with open(fname, 'w') as f:
207 with open(fname, 'w') as f:
208 f.write(se_file_1)
208 f.write(se_file_1)
209
209
210 with tt.AssertPrints(["7/", "SyntaxError"]):
210 with tt.AssertPrints(["7/", "SyntaxError"]):
211 ip.magic("run " + fname)
211 ip.magic("run " + fname)
212
212
213 # Modify the file
213 # Modify the file
214 with open(fname, 'w') as f:
214 with open(fname, 'w') as f:
215 f.write(se_file_2)
215 f.write(se_file_2)
216
216
217 # The SyntaxError should point to the correct line
217 # The SyntaxError should point to the correct line
218 with tt.AssertPrints(["7/", "SyntaxError"]):
218 with tt.AssertPrints(["7/", "SyntaxError"]):
219 ip.magic("run " + fname)
219 ip.magic("run " + fname)
220
220
221 def test_non_syntaxerror(self):
221 def test_non_syntaxerror(self):
222 # SyntaxTB may be called with an error other than a SyntaxError
222 # SyntaxTB may be called with an error other than a SyntaxError
223 # See e.g. gh-4361
223 # See e.g. gh-4361
224 try:
224 try:
225 raise ValueError('QWERTY')
225 raise ValueError('QWERTY')
226 except ValueError:
226 except ValueError:
227 with tt.AssertPrints('QWERTY'):
227 with tt.AssertPrints('QWERTY'):
228 ip.showsyntaxerror()
228 ip.showsyntaxerror()
229
229
230
230
231 class Python3ChainedExceptionsTest(unittest.TestCase):
231 class Python3ChainedExceptionsTest(unittest.TestCase):
232 DIRECT_CAUSE_ERROR_CODE = """
232 DIRECT_CAUSE_ERROR_CODE = """
233 try:
233 try:
234 x = 1 + 2
234 x = 1 + 2
235 print(not_defined_here)
235 print(not_defined_here)
236 except Exception as e:
236 except Exception as e:
237 x += 55
237 x += 55
238 x - 1
238 x - 1
239 y = {}
239 y = {}
240 raise KeyError('uh') from e
240 raise KeyError('uh') from e
241 """
241 """
242
242
243 EXCEPTION_DURING_HANDLING_CODE = """
243 EXCEPTION_DURING_HANDLING_CODE = """
244 try:
244 try:
245 x = 1 + 2
245 x = 1 + 2
246 print(not_defined_here)
246 print(not_defined_here)
247 except Exception as e:
247 except Exception as e:
248 x += 55
248 x += 55
249 x - 1
249 x - 1
250 y = {}
250 y = {}
251 raise KeyError('uh')
251 raise KeyError('uh')
252 """
252 """
253
253
254 SUPPRESS_CHAINING_CODE = """
254 SUPPRESS_CHAINING_CODE = """
255 try:
255 try:
256 1/0
256 1/0
257 except Exception:
257 except Exception:
258 raise ValueError("Yikes") from None
258 raise ValueError("Yikes") from None
259 """
259 """
260
260
261 def test_direct_cause_error(self):
261 def test_direct_cause_error(self):
262 with tt.AssertPrints(["KeyError", "NameError", "direct cause"]):
262 with tt.AssertPrints(["KeyError", "NameError", "direct cause"]):
263 ip.run_cell(self.DIRECT_CAUSE_ERROR_CODE)
263 ip.run_cell(self.DIRECT_CAUSE_ERROR_CODE)
264
264
265 def test_exception_during_handling_error(self):
265 def test_exception_during_handling_error(self):
266 with tt.AssertPrints(["KeyError", "NameError", "During handling"]):
266 with tt.AssertPrints(["KeyError", "NameError", "During handling"]):
267 ip.run_cell(self.EXCEPTION_DURING_HANDLING_CODE)
267 ip.run_cell(self.EXCEPTION_DURING_HANDLING_CODE)
268
268
269 def test_suppress_exception_chaining(self):
269 def test_suppress_exception_chaining(self):
270 with tt.AssertNotPrints("ZeroDivisionError"), \
270 with tt.AssertNotPrints("ZeroDivisionError"), \
271 tt.AssertPrints("ValueError", suppress=False):
271 tt.AssertPrints("ValueError", suppress=False):
272 ip.run_cell(self.SUPPRESS_CHAINING_CODE)
272 ip.run_cell(self.SUPPRESS_CHAINING_CODE)
273
273
274
274
275 class RecursionTest(unittest.TestCase):
275 class RecursionTest(unittest.TestCase):
276 DEFINITIONS = """
276 DEFINITIONS = """
277 def non_recurs():
277 def non_recurs():
278 1/0
278 1/0
279
279
280 def r1():
280 def r1():
281 r1()
281 r1()
282
282
283 def r3a():
283 def r3a():
284 r3b()
284 r3b()
285
285
286 def r3b():
286 def r3b():
287 r3c()
287 r3c()
288
288
289 def r3c():
289 def r3c():
290 r3a()
290 r3a()
291
291
292 def r3o1():
292 def r3o1():
293 r3a()
293 r3a()
294
294
295 def r3o2():
295 def r3o2():
296 r3o1()
296 r3o1()
297 """
297 """
298 def setUp(self):
298 def setUp(self):
299 ip.run_cell(self.DEFINITIONS)
299 ip.run_cell(self.DEFINITIONS)
300
300
301 def test_no_recursion(self):
301 def test_no_recursion(self):
302 with tt.AssertNotPrints("frames repeated"):
302 with tt.AssertNotPrints("frames repeated"):
303 ip.run_cell("non_recurs()")
303 ip.run_cell("non_recurs()")
304
304
305 def test_recursion_one_frame(self):
305 def test_recursion_one_frame(self):
306 with tt.AssertPrints("1 frames repeated"):
306 with tt.AssertPrints("1 frames repeated"):
307 ip.run_cell("r1()")
307 ip.run_cell("r1()")
308
308
309 def test_recursion_three_frames(self):
309 def test_recursion_three_frames(self):
310 with tt.AssertPrints("3 frames repeated"):
310 with tt.AssertPrints("3 frames repeated"):
311 ip.run_cell("r3o2()")
311 ip.run_cell("r3o2()")
312
312
313 def test_find_recursion(self):
313 def test_find_recursion(self):
314 captured = []
314 captured = []
315 def capture_exc(*args, **kwargs):
315 def capture_exc(*args, **kwargs):
316 captured.append(sys.exc_info())
316 captured.append(sys.exc_info())
317 with mock.patch.object(ip, 'showtraceback', capture_exc):
317 with mock.patch.object(ip, 'showtraceback', capture_exc):
318 ip.run_cell("r3o2()")
318 ip.run_cell("r3o2()")
319
319
320 self.assertEqual(len(captured), 1)
320 self.assertEqual(len(captured), 1)
321 etype, evalue, tb = captured[0]
321 etype, evalue, tb = captured[0]
322 self.assertIn("recursion", str(evalue))
322 self.assertIn("recursion", str(evalue))
323
323
324 records = ip.InteractiveTB.get_records(tb, 3, ip.InteractiveTB.tb_offset)
324 records = ip.InteractiveTB.get_records(tb, 3, ip.InteractiveTB.tb_offset)
325 for r in records[:10]:
325 for r in records[:10]:
326 print(r[1:4])
326 print(r[1:4])
327
327
328 # The outermost frames should be:
328 # The outermost frames should be:
329 # 0: the 'cell' that was running when the exception came up
329 # 0: the 'cell' that was running when the exception came up
330 # 1: r3o2()
330 # 1: r3o2()
331 # 2: r3o1()
331 # 2: r3o1()
332 # 3: r3a()
332 # 3: r3a()
333 # Then repeating r3b, r3c, r3a
333 # Then repeating r3b, r3c, r3a
334 last_unique, repeat_length = find_recursion(etype, evalue, records)
334 last_unique, repeat_length = find_recursion(etype, evalue, records)
335 self.assertEqual(last_unique, 2)
335 self.assertEqual(last_unique, 2)
336 self.assertEqual(repeat_length, 3)
336 self.assertEqual(repeat_length, 3)
337
337
338
338
339 #----------------------------------------------------------------------------
339 #----------------------------------------------------------------------------
340
340
341 # module testing (minimal)
341 # module testing (minimal)
342 def test_handlers():
342 def test_handlers():
343 def spam(c, d_e):
343 def spam(c, d_e):
344 (d, e) = d_e
344 (d, e) = d_e
345 x = c + d
345 x = c + d
346 y = c * d
346 y = c * d
347 foo(x, y)
347 foo(x, y)
348
348
349 def foo(a, b, bar=1):
349 def foo(a, b, bar=1):
350 eggs(a, b + bar)
350 eggs(a, b + bar)
351
351
352 def eggs(f, g, z=globals()):
352 def eggs(f, g, z=globals()):
353 h = f + g
353 h = f + g
354 i = f - g
354 i = f - g
355 return h / i
355 return h / i
356
356
357 buff = io.StringIO()
357 buff = io.StringIO()
358
358
359 buff.write('')
359 buff.write('')
360 buff.write('*** Before ***')
360 buff.write('*** Before ***')
361 try:
361 try:
362 buff.write(spam(1, (2, 3)))
362 buff.write(spam(1, (2, 3)))
363 except:
363 except:
364 traceback.print_exc(file=buff)
364 traceback.print_exc(file=buff)
365
365
366 handler = ColorTB(ostream=buff)
366 handler = ColorTB(ostream=buff)
367 buff.write('*** ColorTB ***')
367 buff.write('*** ColorTB ***')
368 try:
368 try:
369 buff.write(spam(1, (2, 3)))
369 buff.write(spam(1, (2, 3)))
370 except:
370 except:
371 handler(*sys.exc_info())
371 handler(*sys.exc_info())
372 buff.write('')
372 buff.write('')
373
373
374 handler = VerboseTB(ostream=buff)
374 handler = VerboseTB(ostream=buff)
375 buff.write('*** VerboseTB ***')
375 buff.write('*** VerboseTB ***')
376 try:
376 try:
377 buff.write(spam(1, (2, 3)))
377 buff.write(spam(1, (2, 3)))
378 except:
378 except:
379 handler(*sys.exc_info())
379 handler(*sys.exc_info())
380 buff.write('')
380 buff.write('')
381
381
382 from IPython.testing.decorators import skipif
382
383
383 class TokenizeFailureTest(unittest.TestCase):
384 class TokenizeFailureTest(unittest.TestCase):
384 """Tests related to https://github.com/ipython/ipython/issues/6864."""
385 """Tests related to https://github.com/ipython/ipython/issues/6864."""
385
386
387 @skipif(sys.version_info > (3,8))
386 def testLogging(self):
388 def testLogging(self):
387 message = "An unexpected error occurred while tokenizing input"
389 message = "An unexpected error occurred while tokenizing input"
388 cell = 'raise ValueError("""a\nb""")'
390 cell = 'raise ValueError("""a\nb""")'
389
391
390 stream = io.StringIO()
392 stream = io.StringIO()
391 handler = logging.StreamHandler(stream)
393 handler = logging.StreamHandler(stream)
392 logger = logging.getLogger()
394 logger = logging.getLogger()
393 loglevel = logger.level
395 loglevel = logger.level
394 logger.addHandler(handler)
396 logger.addHandler(handler)
395 self.addCleanup(lambda: logger.removeHandler(handler))
397 self.addCleanup(lambda: logger.removeHandler(handler))
396 self.addCleanup(lambda: logger.setLevel(loglevel))
398 self.addCleanup(lambda: logger.setLevel(loglevel))
397
399
398 logger.setLevel(logging.INFO)
400 logger.setLevel(logging.INFO)
399 with tt.AssertNotPrints(message):
401 with tt.AssertNotPrints(message):
400 ip.run_cell(cell)
402 ip.run_cell(cell)
401 self.assertNotIn(message, stream.getvalue())
403 self.assertNotIn(message, stream.getvalue())
402
404
403 logger.setLevel(logging.DEBUG)
405 logger.setLevel(logging.DEBUG)
404 with tt.AssertNotPrints(message):
406 with tt.AssertNotPrints(message):
405 ip.run_cell(cell)
407 ip.run_cell(cell)
406 self.assertIn(message, stream.getvalue())
408 self.assertIn(message, stream.getvalue())
General Comments 0
You need to be logged in to leave comments. Login now