from __future__ import print_function
from __future__ import absolute_import

import signal
from multiprocessing import Process

import greenlet
from . import _test_extension_cpp
from . import TestCase

def run_unhandled_exception_in_greenlet_aborts():
    # This is used in multiprocessing.Process and must be picklable
    # so it needs to be global.


    def _():
        _test_extension_cpp.test_exception_switch_and_do_in_g2(
            _test_extension_cpp.test_exception_throw
        )
    g1 = greenlet.greenlet(_)
    g1.switch()

class CPPTests(TestCase):
    def test_exception_switch(self):
        greenlets = []
        for i in range(4):
            g = greenlet.greenlet(_test_extension_cpp.test_exception_switch)
            g.switch(i)
            greenlets.append(g)
        for i, g in enumerate(greenlets):
            self.assertEqual(g.switch(), i)

    def _do_test_unhandled_exception(self, target):
        # TODO: On some versions of Python with some settings, this
        # spews a lot of garbage to stderr. It would be nice to capture and ignore that.
        import sys
        WIN = sys.platform.startswith("win")

        p = Process(target=target)
        p.start()
        p.join(10)
        # The child should be aborted in an unusual way. On POSIX
        # platforms, this is done with abort() and signal.SIGABRT,
        # which is reflected in a negative return value; however, on
        # Windows, even though we observe the child print "Fatal
        # Python error: Aborted" and in older versions of the C
        # runtime "This application has requested the Runtime to
        # terminate it in an unusual way," it always has an exit code
        # of 3. This is interesting because 3 is the error code for
        # ERROR_PATH_NOT_FOUND; BUT: the C runtime abort() function
        # also uses this code.
        #
        # See
        # https://devblogs.microsoft.com/oldnewthing/20110519-00/?p=10623
        # and
        # https://docs.microsoft.com/en-us/previous-versions/k089yyh0(v=vs.140)?redirectedfrom=MSDN
        expected_exit = (
            -signal.SIGABRT,
            # But beginning on Python 3.11, the faulthandler
            # that prints the C backtraces sometimes segfaults after
            # reporting the exception but before printing the stack.
            # This has only been seen on linux/gcc.
            -signal.SIGSEGV
        ) if not WIN else (
            3,
        )
        self.assertIn(p.exitcode, expected_exit)

    def test_unhandled_exception_aborts(self):
        # verify that plain unhandled throw aborts
        self._do_test_unhandled_exception(_test_extension_cpp.test_exception_throw)


    def test_unhandled_exception_in_greenlet_aborts(self):
        # verify that unhandled throw called in greenlet aborts too
        self._do_test_unhandled_exception(run_unhandled_exception_in_greenlet_aborts)


if __name__ == '__main__':
    __import__('unittest').main()
