Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide a way to start smtp server on random free port #388

Open
dgrunwald-qt opened this issue Nov 14, 2023 · 0 comments
Open

Provide a way to start smtp server on random free port #388

dgrunwald-qt opened this issue Nov 14, 2023 · 0 comments

Comments

@dgrunwald-qt
Copy link

The underlying event loop supports port=0 for letting the operating system pick a random free port.
This is useful when using the smtp server in unit tests.
However, aiosmtp does not work with port=0 because it tries to connect to this port in _trigger_server, not to the port actually chosen by the OS:

mock_mail_server.py:30: in start
    s._controller.start()
venv-3.12\Lib\site-packages\aiosmtpd\controller.py:306: in start
    self._trigger_server()
venv-3.12\Lib\site-packages\aiosmtpd\controller.py:501: in _trigger_server
    InetMixin._trigger_server(self)
venv-3.12\Lib\site-packages\aiosmtpd\controller.py:444: in _trigger_server
    s = stk.enter_context(create_connection((hostname, self.port), 1.0))
C:\Program Files\Python312\Lib\socket.py:852: in create_connection
E               OSError: [WinError 10049] The requested address is not valid in its context

portpicker is a possible workaround, but requires complex setup (port server daemon) to avoid race conditions.
It would be better if aiosmtpd directly supported port=0. This would involve using server.sockets[0].get_sock_name() for finding the port actually used. There would also need to be some way for the code starting the server to wait until the socket is opened so that the port is known and can be passed to the code under test.

I've hacked together an external solution by deriving from Controller:

class ControllerWithSupportForRandomPort(aiosmtpd.controller.Controller):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._port_known = threading.Event()

    def _trigger_server(self):
        # The InetMixin base class expects to be able to connect to
        # self.hostname/self.port, which are the parameters passed to __init__.
        # If port==0 (as in our tests), this does not work as the server chooses a random
        # free port. Instead use the port that was chosen by the operating system.
        assert self.server is not None
        actual_name = self.server.sockets[0].getsockname()
        self.hostname = actual_name[0]
        self.port = actual_name[1]
        self._port_known.set()
        return super()._trigger_server()

    def wait_for_port(self):
        # Allows the unit test to wait until `self.port` is set correctly
        # before accessing that.
        self._port_known.wait()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant