import sys
import unittest
import errno
import select
from supervisor.tests.base import Mock

from supervisor.poller import SelectPoller, PollPoller, KQueuePoller
from supervisor.poller import implements_poll, implements_kqueue
from supervisor.tests.base import DummyOptions

# this base class is used instead of unittest.TestCase to hide
# a TestCase subclass from test runner when the implementation is
# not available
SkipTestCase = object

class BasePollerTests(unittest.TestCase):
    def _makeOne(self, options):
        from supervisor.poller import BasePoller
        return BasePoller(options)

    def test_register_readable(self):
        inst = self._makeOne(None)
        self.assertRaises(NotImplementedError, inst.register_readable, None)

    def test_register_writable(self):
        inst = self._makeOne(None)
        self.assertRaises(NotImplementedError, inst.register_writable, None)

    def test_unregister_readable(self):
        inst = self._makeOne(None)
        self.assertRaises(NotImplementedError, inst.unregister_readable, None)

    def test_unregister_writable(self):
        inst = self._makeOne(None)
        self.assertRaises(NotImplementedError, inst.unregister_writable, None)

    def test_poll(self):
        inst = self._makeOne(None)
        self.assertRaises(NotImplementedError, inst.poll, None)

    def test_before_daemonize(self):
        inst = self._makeOne(None)
        self.assertEqual(inst.before_daemonize(), None)

    def test_after_daemonize(self):
        inst = self._makeOne(None)
        self.assertEqual(inst.after_daemonize(), None)

class SelectPollerTests(unittest.TestCase):

    def _makeOne(self, options):
        return SelectPoller(options)

    def test_register_readable(self):
        poller = self._makeOne(DummyOptions())
        poller.register_readable(6)
        poller.register_readable(7)
        self.assertEqual(sorted(poller.readables), [6,7])

    def test_register_writable(self):
        poller = self._makeOne(DummyOptions())
        poller.register_writable(6)
        poller.register_writable(7)
        self.assertEqual(sorted(poller.writables), [6,7])

    def test_unregister_readable(self):
        poller = self._makeOne(DummyOptions())
        poller.register_readable(6)
        poller.register_readable(7)
        poller.register_writable(8)
        poller.register_writable(9)
        poller.unregister_readable(6)
        poller.unregister_readable(9)
        poller.unregister_readable(100)  # not registered, ignore error
        self.assertEqual(list(poller.readables), [7])
        self.assertEqual(list(poller.writables), [8, 9])

    def test_unregister_writable(self):
        poller = self._makeOne(DummyOptions())
        poller.register_readable(6)
        poller.register_readable(7)
        poller.register_writable(8)
        poller.register_writable(6)
        poller.unregister_writable(7)
        poller.unregister_writable(6)
        poller.unregister_writable(100)  # not registered, ignore error
        self.assertEqual(list(poller.readables), [6, 7])
        self.assertEqual(list(poller.writables), [8])

    def test_poll_returns_readables_and_writables(self):
        _select = DummySelect(result={'readables': [6],
                                      'writables': [8]})
        poller = self._makeOne(DummyOptions())
        poller._select = _select
        poller.register_readable(6)
        poller.register_readable(7)
        poller.register_writable(8)
        readables, writables = poller.poll(1)
        self.assertEqual(readables, [6])
        self.assertEqual(writables, [8])

    def test_poll_ignores_eintr(self):
        _select = DummySelect(error=errno.EINTR)
        options = DummyOptions()
        poller = self._makeOne(options)
        poller._select = _select
        poller.register_readable(6)
        poller.poll(1)
        self.assertEqual(options.logger.data[0], 'EINTR encountered in poll')

    def test_poll_ignores_ebadf(self):
        _select = DummySelect(error=errno.EBADF)
        options = DummyOptions()
        poller = self._makeOne(options)
        poller._select = _select
        poller.register_readable(6)
        poller.poll(1)
        self.assertEqual(options.logger.data[0], 'EBADF encountered in poll')
        self.assertEqual(list(poller.readables), [])
        self.assertEqual(list(poller.writables), [])

    def test_poll_uncaught_exception(self):
        _select = DummySelect(error=errno.EPERM)
        options = DummyOptions()
        poller = self._makeOne(options)
        poller._select = _select
        poller.register_readable(6)
        self.assertRaises(select.error, poller.poll, 1)

if implements_kqueue():
    KQueuePollerTestsBase = unittest.TestCase
else:
    KQueuePollerTestsBase = SkipTestCase

class KQueuePollerTests(KQueuePollerTestsBase):

    def _makeOne(self, options):
        return KQueuePoller(options)

    def test_register_readable(self):
        kqueue = DummyKQueue()
        poller = self._makeOne(DummyOptions())
        poller._kqueue = kqueue
        poller.register_readable(6)
        self.assertEqual(list(poller.readables), [6])
        self.assertEqual(len(kqueue.registered_kevents), 1)
        self.assertReadEventAdded(kqueue.registered_kevents[0], 6)

    def test_register_writable(self):
        kqueue = DummyKQueue()
        poller = self._makeOne(DummyOptions())
        poller._kqueue = kqueue
        poller.register_writable(7)
        self.assertEqual(list(poller.writables), [7])
        self.assertEqual(len(kqueue.registered_kevents), 1)
        self.assertWriteEventAdded(kqueue.registered_kevents[0], 7)

    def test_unregister_readable(self):
        kqueue = DummyKQueue()
        poller = self._makeOne(DummyOptions())
        poller._kqueue = kqueue
        poller.register_writable(7)
        poller.register_readable(8)
        poller.unregister_readable(7)
        poller.unregister_readable(8)
        poller.unregister_readable(100)  # not registered, ignore error
        self.assertEqual(list(poller.writables), [7])
        self.assertEqual(list(poller.readables), [])
        self.assertWriteEventAdded(kqueue.registered_kevents[0], 7)
        self.assertReadEventAdded(kqueue.registered_kevents[1], 8)
        self.assertReadEventDeleted(kqueue.registered_kevents[2], 7)
        self.assertReadEventDeleted(kqueue.registered_kevents[3], 8)

    def test_unregister_writable(self):
        kqueue = DummyKQueue()
        poller = self._makeOne(DummyOptions())
        poller._kqueue = kqueue
        poller.register_writable(7)
        poller.register_readable(8)
        poller.unregister_writable(7)
        poller.unregister_writable(8)
        poller.unregister_writable(100)  # not registered, ignore error
        self.assertEqual(list(poller.writables), [])
        self.assertEqual(list(poller.readables), [8])
        self.assertWriteEventAdded(kqueue.registered_kevents[0], 7)
        self.assertReadEventAdded(kqueue.registered_kevents[1], 8)
        self.assertWriteEventDeleted(kqueue.registered_kevents[2], 7)
        self.assertWriteEventDeleted(kqueue.registered_kevents[3], 8)

    def test_poll_returns_readables_and_writables(self):
        kqueue = DummyKQueue(result=[(6, select.KQ_FILTER_READ),
                                     (7, select.KQ_FILTER_READ),
                                     (8, select.KQ_FILTER_WRITE)])
        poller = self._makeOne(DummyOptions())
        poller._kqueue = kqueue
        poller.register_readable(6)
        poller.register_readable(7)
        poller.register_writable(8)
        readables, writables = poller.poll(1000)
        self.assertEqual(readables, [6,7])
        self.assertEqual(writables, [8])

    def test_poll_ignores_eintr(self):
        kqueue = DummyKQueue(raise_errno_poll=errno.EINTR)
        options = DummyOptions()
        poller = self._makeOne(options)
        poller._kqueue = kqueue
        poller.register_readable(6)
        poller.poll(1000)
        self.assertEqual(options.logger.data[0], 'EINTR encountered in poll')

    def test_register_readable_and_writable_ignores_ebadf(self):
        _kqueue = DummyKQueue(raise_errno_register=errno.EBADF)
        options = DummyOptions()
        poller = self._makeOne(options)
        poller._kqueue = _kqueue
        poller.register_readable(6)
        poller.register_writable(7)
        self.assertEqual(options.logger.data[0],
                         'EBADF encountered in kqueue. Invalid file descriptor 6')
        self.assertEqual(options.logger.data[1],
                         'EBADF encountered in kqueue. Invalid file descriptor 7')

    def test_register_uncaught_exception(self):
        _kqueue = DummyKQueue(raise_errno_register=errno.ENOMEM)
        options = DummyOptions()
        poller = self._makeOne(options)
        poller._kqueue = _kqueue
        self.assertRaises(OSError, poller.register_readable, 5)

    def test_poll_uncaught_exception(self):
        kqueue = DummyKQueue(raise_errno_poll=errno.EINVAL)
        options = DummyOptions()
        poller = self._makeOne(options)
        poller._kqueue = kqueue
        poller.register_readable(6)
        self.assertRaises(OSError, poller.poll, 1000)

    def test_before_daemonize_closes_kqueue(self):
        mock_kqueue = Mock()
        options = DummyOptions()
        poller = self._makeOne(options)
        poller._kqueue = mock_kqueue
        poller.before_daemonize()
        mock_kqueue.close.assert_called_once_with()
        self.assertEqual(poller._kqueue, None)

    def test_after_daemonize_restores_kqueue(self):
        options = DummyOptions()
        poller = self._makeOne(options)
        poller.readables = [1]
        poller.writables = [3]
        poller.register_readable = Mock()
        poller.register_writable = Mock()
        poller.after_daemonize()
        self.assertTrue(isinstance(poller._kqueue, select.kqueue))
        poller.register_readable.assert_called_with(1)
        poller.register_writable.assert_called_with(3)

    def test_close_closes_kqueue(self):
        mock_kqueue = Mock()
        options = DummyOptions()
        poller = self._makeOne(options)
        poller._kqueue = mock_kqueue
        poller.close()
        mock_kqueue.close.assert_called_once_with()
        self.assertEqual(poller._kqueue, None)

    def assertReadEventAdded(self, kevent, fd):
        self.assertKevent(kevent, fd, select.KQ_FILTER_READ, select.KQ_EV_ADD)

    def assertWriteEventAdded(self, kevent, fd):
        self.assertKevent(kevent, fd, select.KQ_FILTER_WRITE, select.KQ_EV_ADD)

    def assertReadEventDeleted(self, kevent, fd):
        self.assertKevent(kevent, fd, select.KQ_FILTER_READ, select.KQ_EV_DELETE)

    def assertWriteEventDeleted(self, kevent, fd):
        self.assertKevent(kevent, fd, select.KQ_FILTER_WRITE, select.KQ_EV_DELETE)

    def assertKevent(self, kevent, ident, filter, flags):
        self.assertEqual(kevent.ident, ident)
        self.assertEqual(kevent.filter, filter)
        self.assertEqual(kevent.flags, flags)

if implements_poll():
    PollerPollTestsBase = unittest.TestCase
else:
    PollerPollTestsBase = SkipTestCase

class PollerPollTests(PollerPollTestsBase):

    def _makeOne(self, options):
        return PollPoller(options)

    def test_register_readable(self):
        select_poll = DummySelectPoll()
        poller = self._makeOne(DummyOptions())
        poller._poller = select_poll
        poller.register_readable(6)
        poller.register_readable(7)
        self.assertEqual(select_poll.registered_as_readable, [6,7])

    def test_register_writable(self):
        select_poll = DummySelectPoll()
        poller = self._makeOne(DummyOptions())
        poller._poller = select_poll
        poller.register_writable(6)
        self.assertEqual(select_poll.registered_as_writable, [6])

    def test_poll_returns_readables_and_writables(self):
        select_poll = DummySelectPoll(result=[(6, select.POLLIN),
                                              (7, select.POLLPRI),
                                              (8, select.POLLOUT),
                                              (9, select.POLLHUP)])
        poller = self._makeOne(DummyOptions())
        poller._poller = select_poll
        poller.register_readable(6)
        poller.register_readable(7)
        poller.register_writable(8)
        poller.register_readable(9)
        readables, writables = poller.poll(1000)
        self.assertEqual(readables, [6,7,9])
        self.assertEqual(writables, [8])

    def test_poll_ignores_eintr(self):
        select_poll = DummySelectPoll(error=errno.EINTR)
        options = DummyOptions()
        poller = self._makeOne(options)
        poller._poller = select_poll
        poller.register_readable(9)
        poller.poll(1000)
        self.assertEqual(options.logger.data[0], 'EINTR encountered in poll')

    def test_poll_uncaught_exception(self):
        select_poll = DummySelectPoll(error=errno.EBADF)
        options = DummyOptions()
        poller = self._makeOne(options)
        poller._poller = select_poll
        poller.register_readable(9)
        self.assertRaises(select.error, poller.poll, 1000)

    def test_poll_ignores_and_unregisters_closed_fd(self):
        select_poll = DummySelectPoll(result=[(6, select.POLLNVAL),
                                              (7, select.POLLPRI)])
        poller = self._makeOne(DummyOptions())
        poller._poller = select_poll
        poller.register_readable(6)
        poller.register_readable(7)
        readables, writables = poller.poll(1000)
        self.assertEqual(readables, [7])
        self.assertEqual(select_poll.unregistered, [6])

class DummySelect(object):
    '''
    Fake implementation of select.select()
    '''
    def __init__(self, result=None, error=None):
        result = result or {}
        self.readables = result.get('readables', [])
        self.writables = result.get('writables', [])
        self.error = error

    def select(self, r, w, x, timeout):
        if self.error:
            raise select.error(self.error)
        return self.readables, self.writables, []

class DummySelectPoll(object):
    '''
    Fake implementation of select.poll()
    '''
    def __init__(self, result=None, error=None):
        self.result = result or []
        self.error = error
        self.registered_as_readable = []
        self.registered_as_writable = []
        self.unregistered = []

    def register(self, fd, eventmask):
        if eventmask == select.POLLIN | select.POLLPRI | select.POLLHUP:
            self.registered_as_readable.append(fd)
        elif eventmask == select.POLLOUT:
            self.registered_as_writable.append(fd)
        else:
            raise ValueError("Registered a fd on unknown eventmask: '{0}'".format(eventmask))

    def unregister(self, fd):
        self.unregistered.append(fd)

    def poll(self, timeout):
        if self.error:
            raise select.error(self.error)
        return self.result


class DummyKQueue(object):
    '''
    Fake implementation of select.kqueue()
    '''
    def __init__(self, result=None, raise_errno_poll=None, raise_errno_register=None):
        self.result = result or []
        self.errno_poll = raise_errno_poll
        self.errno_register = raise_errno_register
        self.registered_kevents = []
        self.registered_flags = []

    def control(self, kevents, max_events, timeout=None):
        if kevents is None:    # being called on poll()
            self.assert_max_events_on_poll(max_events)
            self.raise_error(self.errno_poll)
            return self.build_result()

        self.assert_max_events_on_register(max_events)
        self.raise_error(self.errno_register)
        self.registered_kevents.extend(kevents)

    def raise_error(self, err):
        if not err: return
        ex = OSError()
        ex.errno = err
        raise ex

    def build_result(self):
        return [FakeKEvent(ident, filter) for ident,filter in self.result]

    def assert_max_events_on_poll(self, max_events):
        assert max_events == KQueuePoller.max_events, (
            "`max_events` parameter of `kqueue.control() should be %d"
            % KQueuePoller.max_events)

    def assert_max_events_on_register(self, max_events):
        assert max_events == 0, (
            "`max_events` parameter of `kqueue.control()` should be 0 on register")

class FakeKEvent(object):
    def __init__(self, ident, filter):
        self.ident = ident
        self.filter = filter


def test_suite():
    return unittest.findTestCases(sys.modules[__name__])

if __name__ == '__main__':
    unittest.main(defaultTest='test_suite')
