1import copy 2import warnings 3import numpy as np 4 5import numba 6from numba.core.transforms import find_setupwiths, with_lifting 7from numba.core.withcontexts import bypass_context, call_context, objmode_context 8from numba.core.bytecode import FunctionIdentity, ByteCode 9from numba.core.interpreter import Interpreter 10from numba.core import typing, errors, cpu 11from numba.core.registry import cpu_target 12from numba.core.compiler import compile_ir, DEFAULT_FLAGS 13from numba import njit, typeof, objmode 14from numba.core.extending import overload 15from numba.tests.support import (MemoryLeak, TestCase, captured_stdout, 16 skip_unless_scipy) 17import unittest 18 19 20def get_func_ir(func): 21 func_id = FunctionIdentity.from_function(func) 22 bc = ByteCode(func_id=func_id) 23 interp = Interpreter(func_id) 24 func_ir = interp.interpret(bc) 25 return func_ir 26 27 28def lift1(): 29 print("A") 30 with bypass_context: 31 print("B") 32 b() 33 print("C") 34 35 36def lift2(): 37 x = 1 38 print("A", x) 39 x = 1 40 with bypass_context: 41 print("B", x) 42 x += 100 43 b() 44 x += 1 45 with bypass_context: 46 print("C", x) 47 b() 48 x += 10 49 x += 1 50 print("D", x) 51 52 53def lift3(): 54 x = 1 55 y = 100 56 print("A", x, y) 57 with bypass_context: 58 print("B") 59 b() 60 x += 100 61 with bypass_context: 62 print("C") 63 y += 100000 64 b() 65 x += 1 66 y += 1 67 print("D", x, y) 68 69 70def lift4(): 71 x = 0 72 print("A", x) 73 x += 10 74 with bypass_context: 75 print("B") 76 b() 77 x += 1 78 for i in range(10): 79 with bypass_context: 80 print("C") 81 b() 82 x += i 83 with bypass_context: 84 print("D") 85 b() 86 if x: 87 x *= 10 88 x += 1 89 print("E", x) 90 91 92def lift5(): 93 print("A") 94 95 96def liftcall1(): 97 x = 1 98 print("A", x) 99 with call_context: 100 x += 1 101 print("B", x) 102 return x 103 104 105def liftcall2(): 106 x = 1 107 print("A", x) 108 with call_context: 109 x += 1 110 print("B", x) 111 with call_context: 112 x += 10 113 print("C", x) 114 return x 115 116 117def liftcall3(): 118 x = 1 119 print("A", x) 120 with call_context: 121 if x > 0: 122 x += 1 123 print("B", x) 124 with call_context: 125 for i in range(10): 126 x += i 127 print("C", x) 128 return x 129 130 131def liftcall4(): 132 with call_context: 133 with call_context: 134 pass 135 136 137def lift_undefiend(): 138 with undefined_global_var: 139 pass 140 141 142bogus_contextmanager = object() 143 144 145def lift_invalid(): 146 with bogus_contextmanager: 147 pass 148 149 150class TestWithFinding(TestCase): 151 def check_num_of_with(self, func, expect_count): 152 the_ir = get_func_ir(func) 153 ct = len(find_setupwiths(the_ir.blocks)) 154 self.assertEqual(ct, expect_count) 155 156 def test_lift1(self): 157 self.check_num_of_with(lift1, expect_count=1) 158 159 def test_lift2(self): 160 self.check_num_of_with(lift2, expect_count=2) 161 162 def test_lift3(self): 163 self.check_num_of_with(lift3, expect_count=1) 164 165 def test_lift4(self): 166 self.check_num_of_with(lift4, expect_count=2) 167 168 def test_lift5(self): 169 self.check_num_of_with(lift5, expect_count=0) 170 171 172class BaseTestWithLifting(TestCase): 173 def setUp(self): 174 super(BaseTestWithLifting, self).setUp() 175 self.typingctx = typing.Context() 176 self.targetctx = cpu.CPUContext(self.typingctx) 177 self.flags = DEFAULT_FLAGS 178 179 def check_extracted_with(self, func, expect_count, expected_stdout): 180 the_ir = get_func_ir(func) 181 new_ir, extracted = with_lifting( 182 the_ir, self.typingctx, self.targetctx, self.flags, 183 locals={}, 184 ) 185 self.assertEqual(len(extracted), expect_count) 186 cres = self.compile_ir(new_ir) 187 188 with captured_stdout() as out: 189 cres.entry_point() 190 191 self.assertEqual(out.getvalue(), expected_stdout) 192 193 def compile_ir(self, the_ir, args=(), return_type=None): 194 typingctx = self.typingctx 195 targetctx = self.targetctx 196 flags = self.flags 197 # Register the contexts in case for nested @jit or @overload calls 198 with cpu_target.nested_context(typingctx, targetctx): 199 return compile_ir(typingctx, targetctx, the_ir, args, 200 return_type, flags, locals={}) 201 202 203class TestLiftByPass(BaseTestWithLifting): 204 205 def test_lift1(self): 206 self.check_extracted_with(lift1, expect_count=1, 207 expected_stdout="A\nC\n") 208 209 def test_lift2(self): 210 self.check_extracted_with(lift2, expect_count=2, 211 expected_stdout="A 1\nD 3\n") 212 213 def test_lift3(self): 214 self.check_extracted_with(lift3, expect_count=1, 215 expected_stdout="A 1 100\nD 2 101\n") 216 217 def test_lift4(self): 218 self.check_extracted_with(lift4, expect_count=2, 219 expected_stdout="A 0\nE 11\n") 220 221 def test_lift5(self): 222 self.check_extracted_with(lift5, expect_count=0, 223 expected_stdout="A\n") 224 225 226class TestLiftCall(BaseTestWithLifting): 227 228 def check_same_semantic(self, func): 229 """Ensure same semantic with non-jitted code 230 """ 231 jitted = njit(func) 232 with captured_stdout() as got: 233 jitted() 234 235 with captured_stdout() as expect: 236 func() 237 238 self.assertEqual(got.getvalue(), expect.getvalue()) 239 240 def test_liftcall1(self): 241 self.check_extracted_with(liftcall1, expect_count=1, 242 expected_stdout="A 1\nB 2\n") 243 self.check_same_semantic(liftcall1) 244 245 def test_liftcall2(self): 246 self.check_extracted_with(liftcall2, expect_count=2, 247 expected_stdout="A 1\nB 2\nC 12\n") 248 self.check_same_semantic(liftcall2) 249 250 def test_liftcall3(self): 251 self.check_extracted_with(liftcall3, expect_count=2, 252 expected_stdout="A 1\nB 2\nC 47\n") 253 self.check_same_semantic(liftcall3) 254 255 def test_liftcall4(self): 256 with self.assertRaises(errors.TypingError) as raises: 257 njit(liftcall4)() 258 # Known error. We only support one context manager per function 259 # for body that are lifted. 260 self.assertIn("re-entrant", str(raises.exception)) 261 262 263def expected_failure_for_list_arg(fn): 264 def core(self, *args, **kwargs): 265 with self.assertRaises(errors.TypingError) as raises: 266 fn(self, *args, **kwargs) 267 self.assertIn('Does not support list type', 268 str(raises.exception)) 269 return core 270 271 272def expected_failure_for_function_arg(fn): 273 def core(self, *args, **kwargs): 274 with self.assertRaises(errors.TypingError) as raises: 275 fn(self, *args, **kwargs) 276 self.assertIn('Does not support function type', 277 str(raises.exception)) 278 return core 279 280 281class TestLiftObj(MemoryLeak, TestCase): 282 283 def setUp(self): 284 warnings.simplefilter("error", errors.NumbaWarning) 285 286 def tearDown(self): 287 warnings.resetwarnings() 288 289 def assert_equal_return_and_stdout(self, pyfunc, *args): 290 py_args = copy.deepcopy(args) 291 c_args = copy.deepcopy(args) 292 cfunc = njit(pyfunc) 293 294 with captured_stdout() as stream: 295 expect_res = pyfunc(*py_args) 296 expect_out = stream.getvalue() 297 298 # avoid compiling during stdout-capturing for easier print-debugging 299 cfunc.compile(tuple(map(typeof, c_args))) 300 with captured_stdout() as stream: 301 got_res = cfunc(*c_args) 302 got_out = stream.getvalue() 303 304 self.assertEqual(expect_out, got_out) 305 self.assertPreciseEqual(expect_res, got_res) 306 307 def test_lift_objmode_basic(self): 308 def bar(ival): 309 print("ival =", {'ival': ival // 2}) 310 311 def foo(ival): 312 ival += 1 313 with objmode_context: 314 bar(ival) 315 return ival + 1 316 317 def foo_nonglobal(ival): 318 ival += 1 319 with numba.objmode: 320 bar(ival) 321 return ival + 1 322 323 self.assert_equal_return_and_stdout(foo, 123) 324 self.assert_equal_return_and_stdout(foo_nonglobal, 123) 325 326 def test_lift_objmode_array_in(self): 327 def bar(arr): 328 print({'arr': arr // 2}) 329 # arr is modified. the effect is visible outside. 330 arr *= 2 331 332 def foo(nelem): 333 arr = np.arange(nelem).astype(np.int64) 334 with objmode_context: 335 # arr is modified inplace inside bar() 336 bar(arr) 337 return arr + 1 338 339 nelem = 10 340 self.assert_equal_return_and_stdout(foo, nelem) 341 342 def test_lift_objmode_define_new_unused(self): 343 def bar(y): 344 print(y) 345 346 def foo(x): 347 with objmode_context(): 348 y = 2 + x # defined but unused outside 349 a = np.arange(y) # defined but unused outside 350 bar(a) 351 return x 352 353 arg = 123 354 self.assert_equal_return_and_stdout(foo, arg) 355 356 def test_lift_objmode_return_simple(self): 357 def inverse(x): 358 print(x) 359 return 1 / x 360 361 def foo(x): 362 with objmode_context(y="float64"): 363 y = inverse(x) 364 return x, y 365 366 def foo_nonglobal(x): 367 with numba.objmode(y="float64"): 368 y = inverse(x) 369 return x, y 370 371 arg = 123 372 self.assert_equal_return_and_stdout(foo, arg) 373 self.assert_equal_return_and_stdout(foo_nonglobal, arg) 374 375 def test_lift_objmode_return_array(self): 376 def inverse(x): 377 print(x) 378 return 1 / x 379 380 def foo(x): 381 with objmode_context(y="float64[:]", z="int64"): 382 y = inverse(x) 383 z = int(y[0]) 384 return x, y, z 385 386 arg = np.arange(1, 10, dtype=np.float64) 387 self.assert_equal_return_and_stdout(foo, arg) 388 389 @expected_failure_for_list_arg 390 def test_lift_objmode_using_list(self): 391 def foo(x): 392 with objmode_context(y="float64[:]"): 393 print(x) 394 x[0] = 4 395 print(x) 396 y = [1, 2, 3] + x 397 y = np.asarray([1 / i for i in y]) 398 return x, y 399 400 arg = [1, 2, 3] 401 self.assert_equal_return_and_stdout(foo, arg) 402 403 def test_lift_objmode_var_redef(self): 404 def foo(x): 405 for x in range(x): 406 pass 407 if x: 408 x += 1 409 with objmode_context(x="intp"): 410 print(x) 411 x -= 1 412 print(x) 413 for i in range(x): 414 x += i 415 print(x) 416 return x 417 418 arg = 123 419 self.assert_equal_return_and_stdout(foo, arg) 420 421 @expected_failure_for_list_arg 422 def test_case01_mutate_list_ahead_of_ctx(self): 423 def foo(x, z): 424 x[2] = z 425 426 with objmode_context(): 427 # should print [1, 2, 15] but prints [1, 2, 3] 428 print(x) 429 430 with objmode_context(): 431 x[2] = 2 * z 432 # should print [1, 2, 30] but prints [1, 2, 15] 433 print(x) 434 435 return x 436 437 self.assert_equal_return_and_stdout(foo, [1, 2, 3], 15) 438 439 def test_case02_mutate_array_ahead_of_ctx(self): 440 def foo(x, z): 441 x[2] = z 442 443 with objmode_context(): 444 # should print [1, 2, 15] 445 print(x) 446 447 with objmode_context(): 448 x[2] = 2 * z 449 # should print [1, 2, 30] 450 print(x) 451 452 return x 453 454 x = np.array([1, 2, 3]) 455 self.assert_equal_return_and_stdout(foo, x, 15) 456 457 @expected_failure_for_list_arg 458 def test_case03_create_and_mutate(self): 459 def foo(x): 460 with objmode_context(y='List(int64)'): 461 y = [1, 2, 3] 462 with objmode_context(): 463 y[2] = 10 464 return y 465 self.assert_equal_return_and_stdout(foo, 1) 466 467 def test_case04_bogus_variable_type_info(self): 468 469 def foo(x): 470 # should specifying nonsense type info be considered valid? 471 with objmode_context(k="float64[:]"): 472 print(x) 473 return x 474 475 x = np.array([1, 2, 3]) 476 cfoo = njit(foo) 477 with self.assertRaises(errors.TypingError) as raises: 478 cfoo(x) 479 self.assertIn( 480 "Invalid type annotation on non-outgoing variables", 481 str(raises.exception), 482 ) 483 484 def test_case05_bogus_type_info(self): 485 def foo(x): 486 # should specifying the wrong type info be considered valid? 487 # z is complex. 488 # Note: for now, we will coerce for scalar and raise for array 489 with objmode_context(z="float64[:]"): 490 z = x + 1.j 491 return z 492 493 x = np.array([1, 2, 3]) 494 cfoo = njit(foo) 495 with self.assertRaises(TypeError) as raises: 496 got = cfoo(x) 497 self.assertIn( 498 ("can't unbox array from PyObject into native value." 499 " The object maybe of a different type"), 500 str(raises.exception), 501 ) 502 503 def test_case06_double_objmode(self): 504 def foo(x): 505 # would nested ctx in the same scope ever make sense? Is this 506 # pattern useful? 507 with objmode_context(): 508 #with npmmode_context(): not implemented yet 509 with objmode_context(): 510 print(x) 511 return x 512 513 with self.assertRaises(errors.TypingError) as raises: 514 njit(foo)(123) 515 # Check that an error occurred in with-lifting in objmode 516 pat = ("During: resolving callee type: " 517 "type\(ObjModeLiftedWith\(<.*>\)\)") 518 self.assertRegexpMatches(str(raises.exception), pat) 519 520 def test_case07_mystery_key_error(self): 521 # this raises a key error 522 def foo(x): 523 with objmode_context(): 524 t = {'a': x} 525 return x, t 526 x = np.array([1, 2, 3]) 527 cfoo = njit(foo) 528 with self.assertRaises(errors.TypingError) as raises: 529 cfoo(x) 530 self.assertIn( 531 "missing type annotation on outgoing variables", 532 str(raises.exception), 533 ) 534 535 def test_case08_raise_from_external(self): 536 # this segfaults, expect its because the dict needs to raise as '2' is 537 # not in the keys until a later loop (looking for `d['0']` works fine). 538 d = dict() 539 540 def foo(x): 541 for i in range(len(x)): 542 with objmode_context(): 543 k = str(i) 544 v = x[i] 545 d[k] = v 546 print(d['2']) 547 return x 548 549 x = np.array([1, 2, 3]) 550 cfoo = njit(foo) 551 with self.assertRaises(KeyError) as raises: 552 cfoo(x) 553 self.assertEqual(str(raises.exception), "'2'") 554 555 def test_case09_explicit_raise(self): 556 def foo(x): 557 with objmode_context(): 558 raise ValueError() 559 return x 560 561 x = np.array([1, 2, 3]) 562 cfoo = njit(foo) 563 with self.assertRaises(errors.CompilerError) as raises: 564 cfoo(x) 565 self.assertIn( 566 ('unsupported controlflow due to return/raise statements inside ' 567 'with block'), 568 str(raises.exception), 569 ) 570 571 @expected_failure_for_list_arg 572 def test_case10_mutate_across_contexts(self): 573 # This shouldn't work due to using List as input. 574 def foo(x): 575 with objmode_context(y='List(int64)'): 576 y = [1, 2, 3] 577 with objmode_context(): 578 y[2] = 10 579 return y 580 581 x = np.array([1, 2, 3]) 582 self.assert_equal_return_and_stdout(foo, x) 583 584 def test_case10_mutate_array_across_contexts(self): 585 # Sub-case of case-10. 586 def foo(x): 587 with objmode_context(y='int64[:]'): 588 y = np.asarray([1, 2, 3], dtype='int64') 589 with objmode_context(): 590 # Note: `y` is not an output. 591 y[2] = 10 592 return y 593 594 x = np.array([1, 2, 3]) 595 self.assert_equal_return_and_stdout(foo, x) 596 597 def test_case11_define_function_in_context(self): 598 # should this work? no, global name 'bar' is not defined 599 def foo(x): 600 with objmode_context(): 601 def bar(y): 602 return y + 1 603 return x 604 605 x = np.array([1, 2, 3]) 606 cfoo = njit(foo) 607 with self.assertRaises(NameError) as raises: 608 cfoo(x) 609 self.assertIn( 610 "global name 'bar' is not defined", 611 str(raises.exception), 612 ) 613 614 def test_case12_njit_inside_a_objmode_ctx(self): 615 # TODO: is this still the cases? 616 # this works locally but not inside this test, probably due to the way 617 # compilation is being done 618 def bar(y): 619 return y + 1 620 621 def foo(x): 622 with objmode_context(y='int64[:]'): 623 y = njit(bar)(x).astype('int64') 624 return x + y 625 626 x = np.array([1, 2, 3]) 627 self.assert_equal_return_and_stdout(foo, x) 628 629 def test_case13_branch_to_objmode_ctx(self): 630 # Checks for warning in dataflow.py due to mishandled stack offset 631 # dataflow.py:57: RuntimeWarning: inconsistent stack offset ... 632 def foo(x, wobj): 633 if wobj: 634 with objmode_context(y='int64[:]'): 635 y = (x + 1).astype('int64') 636 else: 637 y = x + 2 638 639 return x + y 640 641 x = np.array([1, 2, 3], dtype='int64') 642 643 with warnings.catch_warnings(record=True) as w: 644 warnings.simplefilter("always", RuntimeWarning) 645 self.assert_equal_return_and_stdout(foo, x, True) 646 # Assert no warnings from dataflow.py 647 for each in w: 648 self.assertFalse(each.filename.endswith('dataflow.py'), 649 msg='there were warnings in dataflow.py') 650 651 def test_case14_return_direct_from_objmode_ctx(self): 652 # fails with: 653 # AssertionError: Failed in nopython mode pipeline (step: Handle with contexts) 654 # ending offset is not a label 655 def foo(x): 656 with objmode_context(x='int64[:]'): 657 return x 658 x = np.array([1, 2, 3]) 659 cfoo = njit(foo) 660 with self.assertRaises(errors.CompilerError) as raises: 661 cfoo(x) 662 self.assertIn( 663 ('unsupported controlflow due to return/raise statements inside ' 664 'with block'), 665 str(raises.exception), 666 ) 667 668 # No easy way to handle this yet. 669 @unittest.expectedFailure 670 def test_case15_close_over_objmode_ctx(self): 671 # Fails with Unsupported constraint encountered: enter_with $phi8.1 672 def foo(x): 673 j = 10 674 675 def bar(x): 676 with objmode_context(x='int64[:]'): 677 print(x) 678 return x + j 679 return bar(x) + 2 680 x = np.array([1, 2, 3]) 681 self.assert_equal_return_and_stdout(foo, x) 682 683 @skip_unless_scipy 684 def test_case16_scipy_call_in_objmode_ctx(self): 685 from scipy import sparse as sp 686 687 def foo(x): 688 with objmode_context(k='int64'): 689 print(x) 690 spx = sp.csr_matrix(x) 691 # the np.int64 call is pointless, works around: 692 # https://github.com/scipy/scipy/issues/10206 693 # which hit the SciPy 1.3 release. 694 k = np.int64(spx[0, 0]) 695 return k 696 x = np.array([1, 2, 3]) 697 self.assert_equal_return_and_stdout(foo, x) 698 699 def test_case17_print_own_bytecode(self): 700 import dis 701 702 def foo(x): 703 with objmode_context(): 704 dis.dis(foo) 705 x = np.array([1, 2, 3]) 706 self.assert_equal_return_and_stdout(foo, x) 707 708 @expected_failure_for_function_arg 709 def test_case18_njitfunc_passed_to_objmode_ctx(self): 710 def foo(func, x): 711 with objmode_context(): 712 func(x[0]) 713 714 x = np.array([1, 2, 3]) 715 fn = njit(lambda z: z + 5) 716 self.assert_equal_return_and_stdout(foo, fn, x) 717 718 def test_case19_recursion(self): 719 def foo(x): 720 with objmode_context(): 721 if x == 0: 722 return 7 723 ret = foo(x - 1) 724 return ret 725 x = np.array([1, 2, 3]) 726 cfoo = njit(foo) 727 with self.assertRaises(errors.CompilerError) as raises: 728 cfoo(x) 729 msg = "Does not support with-context that contain branches" 730 self.assertIn(msg, str(raises.exception)) 731 732 @unittest.expectedFailure 733 def test_case20_rng_works_ok(self): 734 def foo(x): 735 np.random.seed(0) 736 y = np.random.rand() 737 with objmode_context(z="float64"): 738 # It's known that the random state does not sync 739 z = np.random.rand() 740 return x + z + y 741 742 x = np.array([1, 2, 3]) 743 self.assert_equal_return_and_stdout(foo, x) 744 745 def test_case21_rng_seed_works_ok(self): 746 def foo(x): 747 np.random.seed(0) 748 y = np.random.rand() 749 with objmode_context(z="float64"): 750 # Similar to test_case20_rng_works_ok but call seed 751 np.random.seed(0) 752 z = np.random.rand() 753 return x + z + y 754 755 x = np.array([1, 2, 3]) 756 self.assert_equal_return_and_stdout(foo, x) 757 758 def test_example01(self): 759 # Example from _ObjModeContextType.__doc__ 760 def bar(x): 761 return np.asarray(list(reversed(x.tolist()))) 762 763 @njit 764 def foo(): 765 x = np.arange(5) 766 with objmode(y='intp[:]'): # annotate return type 767 # this region is executed by object-mode. 768 y = x + bar(x) 769 return y 770 771 self.assertPreciseEqual(foo(), foo.py_func()) 772 self.assertIs(objmode, objmode_context) 773 774 def test_objmode_in_overload(self): 775 def foo(s): 776 pass 777 778 @overload(foo) 779 def foo_overload(s): 780 def impl(s): 781 with objmode(out='intp'): 782 out = s + 3 783 return out 784 return impl 785 786 @numba.njit 787 def f(): 788 return foo(1) 789 790 self.assertEqual(f(), 1 + 3) 791 792 @staticmethod 793 def case_objmode_cache(x): 794 with objmode(output='float64'): 795 output = x / 10 796 return output 797 798 799def case_inner_pyfunc(x): 800 return x / 10 801 802 803def case_objmode_cache(x): 804 with objmode(output='float64'): 805 output = case_inner_pyfunc(x) 806 return output 807 808 809class TestLiftObjCaching(MemoryLeak, TestCase): 810 # Warnings in this test class are converted to errors 811 812 def setUp(self): 813 warnings.simplefilter("error", errors.NumbaWarning) 814 815 def tearDown(self): 816 warnings.resetwarnings() 817 818 def check(self, py_func): 819 first = njit(cache=True)(py_func) 820 self.assertEqual(first(123), 12.3) 821 822 second = njit(cache=True)(py_func) 823 self.assertFalse(second._cache_hits) 824 self.assertEqual(second(123), 12.3) 825 self.assertTrue(second._cache_hits) 826 827 def test_objmode_caching_basic(self): 828 def pyfunc(x): 829 with objmode(output='float64'): 830 output = x / 10 831 return output 832 833 self.check(pyfunc) 834 835 def test_objmode_caching_call_closure_bad(self): 836 def other_pyfunc(x): 837 return x / 10 838 839 def pyfunc(x): 840 with objmode(output='float64'): 841 output = other_pyfunc(x) 842 return output 843 844 self.check(pyfunc) 845 846 def test_objmode_caching_call_closure_good(self): 847 self.check(case_objmode_cache) 848 849 850class TestBogusContext(BaseTestWithLifting): 851 def test_undefined_global(self): 852 the_ir = get_func_ir(lift_undefiend) 853 854 with self.assertRaises(errors.CompilerError) as raises: 855 with_lifting( 856 the_ir, self.typingctx, self.targetctx, self.flags, locals={}, 857 ) 858 self.assertIn( 859 "Undefined variable used as context manager", 860 str(raises.exception), 861 ) 862 863 def test_invalid(self): 864 the_ir = get_func_ir(lift_invalid) 865 866 with self.assertRaises(errors.CompilerError) as raises: 867 with_lifting( 868 the_ir, self.typingctx, self.targetctx, self.flags, locals={}, 869 ) 870 self.assertIn( 871 "Unsupported context manager in use", 872 str(raises.exception), 873 ) 874 875 876if __name__ == '__main__': 877 unittest.main() 878