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