Exemplo n.º 1
0
class TestPailgunServer(unittest.TestCase):
  def setUp(self):
    self.mock_handler_inst = mock.Mock()
    self.mock_runner_factory = mock.Mock(side_effect=Exception('this should never be called'))
    self.mock_handler_class = mock.Mock(return_value=self.mock_handler_inst)
    self.lock = threading.RLock()

    @contextmanager
    def lock():
      with self.lock:
        yield

    with mock.patch.object(PailgunServer, 'server_bind'), \
         mock.patch.object(PailgunServer, 'server_activate'):
      self.server = PailgunServer(
        server_address=('0.0.0.0', 0),
        runner_factory=self.mock_runner_factory,
        handler_class=self.mock_handler_class,
        lifecycle_lock=lock
      )

  @mock.patch.object(TCPServer, 'server_bind', **PATCH_OPTS)
  def test_server_bind(self, mock_tcpserver_bind):
    mock_sock = mock.Mock()
    mock_sock.getsockname.return_value = ('0.0.0.0', 31337)
    self.server.socket = mock_sock
    self.server.server_bind()
    self.assertEqual(self.server.server_port, 31337)
    self.assertIs(mock_tcpserver_bind.called, True)

  @mock.patch.object(PailgunServer, 'close_request', **PATCH_OPTS)
  def test_process_request_thread(self, mock_close_request):
    mock_request = mock.Mock()
    self.server.process_request_thread(mock_request, ('1.2.3.4', 31338))
    self.assertIs(self.mock_handler_inst.handle_request.called, True)
    mock_close_request.assert_called_once_with(self.server, mock_request)

  @mock.patch.object(PailgunServer, 'shutdown_request', **PATCH_OPTS)
  def test_process_request_thread_error(self, mock_shutdown_request):
    mock_request = mock.Mock()
    self.mock_handler_inst.handle_request.side_effect = AttributeError('oops')
    self.server.process_request_thread(mock_request, ('1.2.3.4', 31338))
    self.assertIs(self.mock_handler_inst.handle_request.called, True)
    self.assertIs(self.mock_handler_inst.handle_error.called, True)
    mock_shutdown_request.assert_called_once_with(self.server, mock_request)
Exemplo n.º 2
0
class TestPailgunServer(unittest.TestCase):
  def setUp(self):
    self.mock_handler_inst = mock.Mock()
    self.mock_runner_factory = mock.Mock(side_effect=Exception('this should never be called'))
    self.mock_handler_class = mock.Mock(return_value=self.mock_handler_inst)
    self.lock = threading.RLock()

    @contextmanager
    def lock():
      with self.lock:
        yield

    self.after_request_callback_calls = 0

    def after_request_callback():
      self.after_request_callback_calls += 1

    with mock.patch.object(PailgunServer, 'server_bind'), \
         mock.patch.object(PailgunServer, 'server_activate'):
      self.server = PailgunServer(
        server_address=('0.0.0.0', 0),
        runner_factory=self.mock_runner_factory,
        handler_class=self.mock_handler_class,
        lifecycle_lock=lock,
        request_complete_callback=after_request_callback
      )

  @mock.patch.object(TCPServer, 'server_bind', **PATCH_OPTS)
  def test_server_bind(self, mock_tcpserver_bind):
    mock_sock = mock.Mock()
    mock_sock.getsockname.return_value = ('0.0.0.0', 31337)
    self.server.socket = mock_sock
    self.server.server_bind()
    self.assertEqual(self.server.server_port, 31337)
    self.assertIs(mock_tcpserver_bind.called, True)

  @mock.patch.object(PailgunServer, 'close_request', **PATCH_OPTS)
  def test_process_request_thread(self, mock_close_request):
    mock_request = mock.Mock()
    self.server.process_request_thread(mock_request, ('1.2.3.4', 31338))
    self.assertIs(self.mock_handler_inst.handle_request.called, True)
    mock_close_request.assert_called_once_with(self.server, mock_request)

  @mock.patch.object(PailgunServer, 'close_request', **PATCH_OPTS)
  def test_process_request_calls_callback(self, mock_close_request):
    mock_request = mock.Mock()
    self.server.process_request_thread(mock_request, ('1.2.3.4', 31338))
    self.assertIs(self.mock_handler_inst.handle_request.called, True)
    assert(self.after_request_callback_calls == 1)

  @mock.patch.object(PailgunServer, 'shutdown_request', **PATCH_OPTS)
  def test_process_request_thread_error(self, mock_shutdown_request):
    mock_request = mock.Mock()
    self.mock_handler_inst.handle_request.side_effect = AttributeError('oops')
    self.server.process_request_thread(mock_request, ('1.2.3.4', 31338))
    self.assertIs(self.mock_handler_inst.handle_request.called, True)
    self.assertIs(self.mock_handler_inst.handle_error.called, True)
    mock_shutdown_request.assert_called_once_with(self.server, mock_request)
Exemplo n.º 3
0
class TestPailgunServer(unittest.TestCase):
    def setUp(self):
        self.mock_handler_inst = unittest.mock.Mock()
        # Add a fake environment for this to not timeout.
        self.fake_environment = {"PANTSD_REQUEST_TIMEOUT_LIMIT": "-1"}
        self.mock_handler_inst.parsed_request.return_value = (None, None, [], self.fake_environment)

        self.mock_runner_factory = unittest.mock.Mock(
            side_effect=Exception("this should never be called")
        )
        self.mock_handler_class = unittest.mock.Mock(return_value=self.mock_handler_inst)
        self.lock = threading.RLock()

        @contextmanager
        def lock():
            with self.lock:
                yield

        self.after_request_callback_calls = 0

        def after_request_callback():
            self.after_request_callback_calls += 1

        with unittest.mock.patch.object(PailgunServer, "server_bind"), unittest.mock.patch.object(
            PailgunServer, "server_activate"
        ):
            self.server = PailgunServer(
                server_address=("0.0.0.0", 0),
                runner_factory=self.mock_runner_factory,
                handler_class=self.mock_handler_class,
                lifecycle_lock=lock,
                request_complete_callback=after_request_callback,
            )

    @unittest.mock.patch.object(TCPServer, "server_bind", **PATCH_OPTS)
    def test_server_bind(self, mock_tcpserver_bind):
        mock_sock = unittest.mock.Mock()
        mock_sock.getsockname.return_value = ("0.0.0.0", 31337)
        self.server.socket = mock_sock
        self.server.server_bind()
        self.assertEqual(self.server.server_port, 31337)
        self.assertIs(mock_tcpserver_bind.called, True)

    @unittest.mock.patch.object(PailgunServer, "close_request", **PATCH_OPTS)
    def test_process_request_thread(self, mock_close_request):
        mock_request = unittest.mock.Mock()
        self.server.process_request_thread(mock_request, ("1.2.3.4", 31338))
        self.assertIs(self.mock_handler_inst.handle_request.called, True)
        mock_close_request.assert_called_once_with(self.server, mock_request)

    @unittest.mock.patch.object(PailgunServer, "close_request", **PATCH_OPTS)
    def test_process_request_calls_callback(self, mock_close_request):
        mock_request = unittest.mock.Mock()
        self.server.process_request_thread(mock_request, ("1.2.3.4", 31338))
        self.assertIs(self.mock_handler_inst.handle_request.called, True)
        assert self.after_request_callback_calls == 1

    @unittest.mock.patch.object(PailgunServer, "shutdown_request", **PATCH_OPTS)
    def test_process_request_thread_error(self, mock_shutdown_request):
        mock_request = unittest.mock.Mock()
        self.mock_handler_inst.handle_request.side_effect = AttributeError("oops")
        self.server.process_request_thread(mock_request, ("1.2.3.4", 31338))
        self.assertIs(self.mock_handler_inst.handle_request.called, True)
        self.assertIs(self.mock_handler_inst.handle_error.called, True)
        mock_shutdown_request.assert_called_once_with(self.server, mock_request)

    def test_ensure_request_is_exclusive(self):
        """Launch many requests, assert that every one is trying to enter the critical section, and
        assert that only one is doing so at a time."""
        self.threads_to_start = 10

        # Queues are thread safe (https://docs.python.org/2/library/queue.html)
        self.thread_errors = Queue()

        def threaded_assert_equal(one, other, message):
            try:
                self.assertEqual(one, other, message)
            except AssertionError as error:
                self.thread_errors.put(error)

        self.threads_running_cond = threading.Condition()
        self.threads_running = 0

        def handle_thread_tried_to_handle_request():
            """Mark a thread as started, and block until every thread has been marked as
            starting."""
            self.threads_running_cond.acquire()
            self.threads_running += 1
            if self.threads_running == self.threads_to_start:
                self.threads_running_cond.notify_all()
            else:
                while self.threads_running != self.threads_to_start:
                    self.threads_running_cond.wait()

            threaded_assert_equal(
                self.threads_running,
                self.threads_to_start,
                "This thread is unblocked before all the threads had started.",
            )
            self.threads_running_cond.release()

        def handle_thread_finished():
            """Mark a thread as finished, and block until there are no more threads running."""
            self.threads_running_cond.acquire()
            self.threads_running -= 1
            print(f"Handle_thread_finished, threads_running are {self.threads_running}")
            if self.threads_running == 0:
                self.threads_running_cond.notify_all()
            else:
                while self.threads_running != 0:
                    self.threads_running_cond.wait()

            threaded_assert_equal(
                self.threads_running,
                0,
                "handle_thread_finished exited when there still were threads running.",
            )
            self.threads_running_cond.release()

        self.threads_handling_requests = 0
        self.threads_handling_requests_lock = threading.Lock()

        def handle_thread_starts_handling_request():
            with self.threads_handling_requests_lock:
                self.threads_handling_requests += 1
                threaded_assert_equal(
                    self.threads_handling_requests, 1, "A thread is already handling a request!"
                )

        def check_only_one_thread_is_handling_a_request():
            """Assert that there's only ever one thread inside the lock."""
            with self.threads_handling_requests_lock:
                threaded_assert_equal(
                    self.threads_handling_requests, 1, "A thread is already handling a request!"
                )

        def handle_thread_finishing_handling_request():
            """Assert that I was the only thread handling a request."""
            with self.threads_handling_requests_lock:
                self.threads_handling_requests -= 1
                threaded_assert_equal(
                    self.threads_handling_requests,
                    0,
                    "There were multiple threads handling a request when a thread finished",
                )

        # Wrap ensure_request_is_exclusive to notify when we acquire and release the lock.
        def mock_ensure_request_is_exclusive(request_lock_under_test):
            """Wrap the lock under test.

            Every thread that calls this function has reached the critical section.
            """

            @contextmanager
            def wrapper(environment, request):
                # Assert that all threads are trying to handle a request.
                handle_thread_tried_to_handle_request()
                with request_lock_under_test(environment, request):
                    try:
                        # Assert that only one is allowed to handle a request.
                        print("Thread has entered the request handling code.")
                        handle_thread_starts_handling_request()
                        check_only_one_thread_is_handling_a_request()
                        yield
                        check_only_one_thread_is_handling_a_request()
                        print("Thread has exited the request handling code.")
                    finally:
                        # Account for a thread finishing a request.
                        handle_thread_finishing_handling_request()
                # Notify that a thread is shutting down.
                handle_thread_finished()
                # At this point, we have asserted that all threads are finished.

            return wrapper

        self.server.ensure_request_is_exclusive = mock_ensure_request_is_exclusive(
            self.server.ensure_request_is_exclusive
        )

        # Create as many mock threads as needed. Lauch all of them, and wait for all of them to finish.
        mock_request = unittest.mock.Mock()

        def create_request_thread(port):
            return threading.Thread(
                target=self.server.process_request_thread,
                args=(mock_request, ("1.2.3.4", port)),
                name=f"MockThread-{port}",
            )

        threads = [create_request_thread(0) for _ in range(0, self.threads_to_start)]
        for thread in threads:
            thread.start()
            self.assertTrue(
                self.thread_errors.empty(),
                f"There were some errors in the threads:\n {self.thread_errors}",
            )

        for thread in threads:
            # If this fails because it times out, it's definitely a legitimate error.
            thread.join(10)
            self.assertTrue(
                self.thread_errors.empty(),
                f"There were some errors in the threads:\n {self.thread_errors}",
            )