import sys
import unittest

import pywintypes
import win32api


class TestError1(Exception):
    pass


class TestError2(Exception):
    pass


# A class that will never die vie refcounting, but will die via GC.
class Cycle:
    def __init__(self, handle):
        self.cycle = self
        self.handle = handle


class PyHandleTestCase(unittest.TestCase):
    def testCleanup1(self):
        # We used to clobber all outstanding exceptions.
        def f1(invalidate):
            import win32event

            h = win32event.CreateEvent(None, 0, 0, None)
            if invalidate:
                win32api.CloseHandle(int(h))
            raise TestError1
            # If we invalidated, then the object destruction code will attempt
            # to close an invalid handle.  We don't wan't an exception in
            # this case

        def f2(invalidate):
            """This function should throw an OSError."""
            try:
                f1(invalidate)
            except TestError1:
                raise TestError2

        self.assertRaises(TestError2, f2, False)
        # Now do it again, but so the auto object destruction
        # actually fails.
        self.assertRaises(TestError2, f2, True)

    def testCleanup2(self):
        # Cause an exception during object destruction.
        # The worst this does is cause an ".XXX undetected error (why=3)"
        # So avoiding that is the goal
        import win32event

        h = win32event.CreateEvent(None, 0, 0, None)
        # Close the handle underneath the object.
        win32api.CloseHandle(int(h))
        # Object destructor runs with the implicit close failing
        h = None

    def testCleanup3(self):
        # And again with a class - no __del__
        import win32event

        class Test:
            def __init__(self):
                self.h = win32event.CreateEvent(None, 0, 0, None)
                win32api.CloseHandle(int(self.h))

        t = Test()
        t = None

    def testCleanupGood(self):
        # And check that normal error semantics *do* work.
        import win32event

        h = win32event.CreateEvent(None, 0, 0, None)
        win32api.CloseHandle(int(h))
        self.assertRaises(win32api.error, h.Close)
        # A following Close is documented as working
        h.Close()

    def testInvalid(self):
        h = pywintypes.HANDLE(-2)
        try:
            h.Close()
            # Ideally, we'd:
            #     self.assertRaises(win32api.error, h.Close)
            # and everywhere markh has tried, that would pass - but not on
            # GitHub automation, where the .Close apparently works fine.
            # (same for -1. Using 0 appears to work fine everywhere)
            # There still seems value in testing it though, so we just accept
            # either working or failing.
        except win32api.error:
            pass

    def testOtherHandle(self):
        h = pywintypes.HANDLE(1)
        h2 = pywintypes.HANDLE(h)
        self.assertEqual(h, h2)
        # but the above doesn't really test everything - we want a way to
        # pass the handle directly into PyWinLong_AsVoidPtr.  One way to
        # to that is to abuse win32api.GetProcAddress() - the 2nd param
        # is passed to PyWinLong_AsVoidPtr() if it's not a string.
        # passing a handle value of '1' should work - there is something
        # at that ordinal
        win32api.GetProcAddress(sys.dllhandle, h)

    def testHandleInDict(self):
        h = pywintypes.HANDLE(1)
        d = {"foo": h}
        self.assertEqual(d["foo"], h)

    def testHandleInDictThenInt(self):
        h = pywintypes.HANDLE(1)
        d = {"foo": h}
        self.assertEqual(d["foo"], 1)

    def testHandleCompareNone(self):
        h = pywintypes.HANDLE(1)
        self.assertNotEqual(h, None)
        self.assertNotEqual(None, h)
        # ensure we use both __eq__ and __ne__ ops
        self.assertFalse(h is None)
        self.assertTrue(h is not None)

    def testHandleCompareInt(self):
        h = pywintypes.HANDLE(1)
        self.assertNotEqual(h, 0)
        self.assertEqual(h, 1)
        # ensure we use both __eq__ and __ne__ ops
        self.assertTrue(h == 1)
        self.assertTrue(1 == h)
        self.assertFalse(h != 1)
        self.assertFalse(1 != h)
        self.assertFalse(h == 0)
        self.assertFalse(0 == h)
        self.assertTrue(h != 0)
        self.assertTrue(0 != h)

    def testHandleNonZero(self):
        h = pywintypes.HANDLE(0)
        self.assertFalse(h)

        h = pywintypes.HANDLE(1)
        self.assertTrue(h)

    def testLong(self):
        # sys.maxsize+1 should always be a 'valid' handle, treated as an
        # unsigned int, even though it is a long. Although pywin32 should not
        # directly create such longs, using struct.unpack() with a P format
        # may well return them. eg:
        # >>> struct.unpack("P", struct.pack("P", -1))
        # (4294967295L,)
        pywintypes.HANDLE(sys.maxsize + 1)

    def testGC(self):
        # This used to provoke:
        # Fatal Python error: unexpected exception during garbage collection
        def make():
            h = pywintypes.HANDLE(-2)
            c = Cycle(h)

        import gc

        make()
        gc.collect()

    def testTypes(self):
        self.assertRaises(TypeError, pywintypes.HANDLE, "foo")
        self.assertRaises(TypeError, pywintypes.HANDLE, ())


if __name__ == "__main__":
    unittest.main()
