import asyncore
import unittest
import select
import os
import socket
import threading
import sys
import time
import errno

from test import support
from test.support import TESTFN, run_unittest, unlink
from io import BytesIO
from io import StringIO

HOST = support.HOST

class dummysocket:
    def __init__(self):
        self.closed = False

    def close(self):
        self.closed = True

    def fileno(self):
        return 42

class dummychannel:
    def __init__(self):
        self.socket = dummysocket()

    def close(self):
        self.socket.close()

class exitingdummy:
    def __init__(self):
        pass

    def handle_read_event(self):
        raise asyncore.ExitNow()

    handle_write_event = handle_read_event
    handle_close = handle_read_event
    handle_expt_event = handle_read_event

class crashingdummy:
    def __init__(self):
        self.error_handled = False

    def handle_read_event(self):
        raise Exception()

    handle_write_event = handle_read_event
    handle_close = handle_read_event
    handle_expt_event = handle_read_event

    def handle_error(self):
        self.error_handled = True

# used when testing senders; just collects what it gets until newline is sent
def capture_server(evt, buf, serv):
    try:
        serv.listen(5)
        conn, addr = serv.accept()
    except socket.timeout:
        pass
    else:
        n = 200
        while n > 0:
            r, w, e = select.select([conn], [], [])
            if r:
                data = conn.recv(10)
                # keep everything except for the newline terminator
                buf.write(data.replace(b'\n', b''))
                if b'\n' in data:
                    break
            n -= 1
            time.sleep(0.01)

        conn.close()
    finally:
        serv.close()
        evt.set()


class HelperFunctionTests(unittest.TestCase):
    def test_readwriteexc(self):
        # Check exception handling behavior of read, write and _exception

        # check that ExitNow exceptions in the object handler method
        # bubbles all the way up through asyncore read/write/_exception calls
        tr1 = exitingdummy()
        self.assertRaises(asyncore.ExitNow, asyncore.read, tr1)
        self.assertRaises(asyncore.ExitNow, asyncore.write, tr1)
        self.assertRaises(asyncore.ExitNow, asyncore._exception, tr1)

        # check that an exception other than ExitNow in the object handler
        # method causes the handle_error method to get called
        tr2 = crashingdummy()
        asyncore.read(tr2)
        self.assertEqual(tr2.error_handled, True)

        tr2 = crashingdummy()
        asyncore.write(tr2)
        self.assertEqual(tr2.error_handled, True)

        tr2 = crashingdummy()
        asyncore._exception(tr2)
        self.assertEqual(tr2.error_handled, True)

    # asyncore.readwrite uses constants in the select module that
    # are not present in Windows systems (see this thread:
    # http://mail.python.org/pipermail/python-list/2001-October/109973.html)
    # These constants should be present as long as poll is available

    if hasattr(select, 'poll'):
        def test_readwrite(self):
            # Check that correct methods are called by readwrite()

            attributes = ('read', 'expt', 'write', 'closed', 'error_handled')

            expected = (
                (select.POLLIN, 'read'),
                (select.POLLPRI, 'expt'),
                (select.POLLOUT, 'write'),
                (select.POLLERR, 'closed'),
                (select.POLLHUP, 'closed'),
                (select.POLLNVAL, 'closed'),
                )

            class testobj:
                def __init__(self):
                    self.read = False
                    self.write = False
                    self.closed = False
                    self.expt = False
                    self.error_handled = False

                def handle_read_event(self):
                    self.read = True

                def handle_write_event(self):
                    self.write = True

                def handle_close(self):
                    self.closed = True

                def handle_expt_event(self):
                    self.expt = True

                def handle_error(self):
                    self.error_handled = True

            for flag, expectedattr in expected:
                tobj = testobj()
                self.assertEqual(getattr(tobj, expectedattr), False)
                asyncore.readwrite(tobj, flag)

                # Only the attribute modified by the routine we expect to be
                # called should be True.
                for attr in attributes:
                    self.assertEqual(getattr(tobj, attr), attr==expectedattr)

                # check that ExitNow exceptions in the object handler method
                # bubbles all the way up through asyncore readwrite call
                tr1 = exitingdummy()
                self.assertRaises(asyncore.ExitNow, asyncore.readwrite, tr1, flag)

                # check that an exception other than ExitNow in the object handler
                # method causes the handle_error method to get called
                tr2 = crashingdummy()
                self.assertEqual(tr2.error_handled, False)
                asyncore.readwrite(tr2, flag)
                self.assertEqual(tr2.error_handled, True)

    def test_closeall(self):
        self.closeall_check(False)

    def test_closeall_default(self):
        self.closeall_check(True)

    def closeall_check(self, usedefault):
        # Check that close_all() closes everything in a given map

        l = []
        testmap = {}
        for i in range(10):
            c = dummychannel()
            l.append(c)
            self.assertEqual(c.socket.closed, False)
            testmap[i] = c

        if usedefault:
            socketmap = asyncore.socket_map
            try:
                asyncore.socket_map = testmap
                asyncore.close_all()
            finally:
                testmap, asyncore.socket_map = asyncore.socket_map, socketmap
        else:
            asyncore.close_all(testmap)

        self.assertEqual(len(testmap), 0)

        for c in l:
            self.assertEqual(c.socket.closed, True)

    def test_compact_traceback(self):
        try:
            raise Exception("I don't like spam!")
        except:
            real_t, real_v, real_tb = sys.exc_info()
            r = asyncore.compact_traceback()
        else:
            self.fail("Expected exception")

        (f, function, line), t, v, info = r
        self.assertEqual(os.path.split(f)[-1], 'test_asyncore.py')
        self.assertEqual(function, 'test_compact_traceback')
        self.assertEqual(t, real_t)
        self.assertEqual(v, real_v)
        self.assertEqual(info, '[%s|%s|%s]' % (f, function, line))


class DispatcherTests(unittest.TestCase):
    def setUp(self):
        pass

    def tearDown(self):
        asyncore.close_all()

    def test_basic(self):
        d = asyncore.dispatcher()
        self.assertEqual(d.readable(), True)
        self.assertEqual(d.writable(), True)

    def test_repr(self):
        d = asyncore.dispatcher()
        self.assertEqual(repr(d), '<asyncore.dispatcher at %#x>' % id(d))

    def test_log(self):
        d = asyncore.dispatcher()

        # capture output of dispatcher.log() (to stderr)
        fp = StringIO()
        stderr = sys.stderr
        l1 = "Lovely spam! Wonderful spam!"
        l2 = "I don't like spam!"
        try:
            sys.stderr = fp
            d.log(l1)
            d.log(l2)
        finally:
            sys.stderr = stderr

        lines = fp.getvalue().splitlines()
        self.assertEqual(lines, ['log: %s' % l1, 'log: %s' % l2])

    def test_log_info(self):
        d = asyncore.dispatcher()

        # capture output of dispatcher.log_info() (to stdout via print)
        fp = StringIO()
        stdout = sys.stdout
        l1 = "Have you got anything without spam?"
        l2 = "Why can't she have egg bacon spam and sausage?"
        l3 = "THAT'S got spam in it!"
        try:
            sys.stdout = fp
            d.log_info(l1, 'EGGS')
            d.log_info(l2)
            d.log_info(l3, 'SPAM')
        finally:
            sys.stdout = stdout

        lines = fp.getvalue().splitlines()
        expected = ['EGGS: %s' % l1, 'info: %s' % l2, 'SPAM: %s' % l3]

        self.assertEqual(lines, expected)

    def test_unhandled(self):
        d = asyncore.dispatcher()
        d.ignore_log_types = ()

        # capture output of dispatcher.log_info() (to stdout via print)
        fp = StringIO()
        stdout = sys.stdout
        try:
            sys.stdout = fp
            d.handle_expt()
            d.handle_read()
            d.handle_write()
            d.handle_connect()
            d.handle_accept()
        finally:
            sys.stdout = stdout

        lines = fp.getvalue().splitlines()
        expected = ['warning: unhandled incoming priority event',
                    'warning: unhandled read event',
                    'warning: unhandled write event',
                    'warning: unhandled connect event',
                    'warning: unhandled accept event']
        self.assertEqual(lines, expected)

    def test_issue_8594(self):
        d = asyncore.dispatcher(socket.socket())
        # make sure the error message no longer refers to the socket
        # object but the dispatcher instance instead
        try:
            d.foo
        except AttributeError as err:
            self.assertTrue('dispatcher instance' in str(err))
        else:
            self.fail("exception not raised")
        # test cheap inheritance with the underlying socket
        self.assertEqual(d.family, socket.AF_INET)

    def test_strerror(self):
        # refers to bug #8573
        err = asyncore._strerror(errno.EPERM)
        if hasattr(os, 'strerror'):
            self.assertEqual(err, os.strerror(errno.EPERM))
        err = asyncore._strerror(-1)
        self.assertTrue("unknown error" in err.lower())


class dispatcherwithsend_noread(asyncore.dispatcher_with_send):
    def readable(self):
        return False

    def handle_connect(self):
        pass

class DispatcherWithSendTests(unittest.TestCase):
    usepoll = False

    def setUp(self):
        pass

    def tearDown(self):
        asyncore.close_all()

    def test_send(self):
        self.evt = threading.Event()
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.sock.settimeout(3)
        self.port = support.bind_port(self.sock)

        cap = BytesIO()
        args = (self.evt, cap, self.sock)
        threading.Thread(target=capture_server, args=args).start()

        # wait a little longer for the server to initialize (it sometimes
        # refuses connections on slow machines without this wait)
        time.sleep(0.2)

        data = b"Suppose there isn't a 16-ton weight?"
        d = dispatcherwithsend_noread()
        d.create_socket(socket.AF_INET, socket.SOCK_STREAM)
        d.connect((HOST, self.port))

        # give time for socket to connect
        time.sleep(0.1)

        d.send(data)
        d.send(data)
        d.send(b'\n')

        n = 1000
        while d.out_buffer and n > 0:
            asyncore.poll()
            n -= 1

        self.evt.wait()

        self.assertEqual(cap.getvalue(), data*2)


class DispatcherWithSendTests_UsePoll(DispatcherWithSendTests):
    usepoll = True

if hasattr(asyncore, 'file_wrapper'):
    class FileWrapperTest(unittest.TestCase):
        def setUp(self):
            self.d = b"It's not dead, it's sleeping!"
            open(TESTFN, 'wb').write(self.d)

        def tearDown(self):
            unlink(TESTFN)

        def test_recv(self):
            fd = os.open(TESTFN, os.O_RDONLY)
            w = asyncore.file_wrapper(fd)
            os.close(fd)

            self.assertNotEqual(w.fd, fd)
            self.assertNotEqual(w.fileno(), fd)
            self.assertEqual(w.recv(13), b"It's not dead")
            self.assertEqual(w.read(6), b", it's")
            w.close()
            self.assertRaises(OSError, w.read, 1)

        def test_send(self):
            d1 = b"Come again?"
            d2 = b"I want to buy some cheese."
            fd = os.open(TESTFN, os.O_WRONLY | os.O_APPEND)
            w = asyncore.file_wrapper(fd)
            os.close(fd)

            w.write(d1)
            w.send(d2)
            w.close()
            self.assertEqual(open(TESTFN, 'rb').read(), self.d + d1 + d2)

        @unittest.skipUnless(hasattr(asyncore, 'file_dispatcher'),
                             '    asyncore.file_dispatcher required')
        def test_dispatcher(self):
            fd = os.open(TESTFN, os.O_RDONLY)
            data = []
            class FileDispatcher(asyncore.file_dispatcher):
                def handle_read(self):
                    data.append(self.recv(29))
            s = FileDispatcher(fd)
            os.close(fd)
            asyncore.loop(timeout=0.01, use_poll=True, count=2)
            self.assertEqual(b"".join(data), self.d)


def test_main():
    tests = [HelperFunctionTests, DispatcherTests, DispatcherWithSendTests,
             DispatcherWithSendTests_UsePoll]
    if hasattr(asyncore, 'file_wrapper'):
        tests.append(FileWrapperTest)

    run_unittest(*tests)

if __name__ == "__main__":
    test_main()
