def test_eof_received(self, tls_controller, client): # I don't like this. It's too intimately involved with the innards of the SMTP # class. But for the life of me, I can't figure out why coverage there fail # intermittently. # # I suspect it's a race condition, but with what, and how to prevent that from # happening, that's ... a mystery. # Entering portion of code where hang is possible (upon assertion fail), so # we must wrap with "try..finally". try: code, mesg = client.ehlo("example.com") assert code == 250 resp = client.starttls() assert resp == S.S220_READY_TLS # Need this to make SMTP update its internal session variable code, mesg = client.ehlo("example.com") assert code == 250 sess: Sess_ = tls_controller.smtpd.session assert sess.ssl is not None client.noop() catchup_delay() handler: EOFingHandler = tls_controller.handler assert handler.ssl_existed is True assert handler.result is False finally: tls_controller.stop()
def test_inet_loopstop(self, autostop_loop, runner): """ Verify behavior when the loop is stopped before controller is stopped """ autostop_loop.set_debug(True) cont = UnthreadedController(Sink(), loop=autostop_loop) cont.begin() # Make sure event loop is not running (will be started in thread) assert autostop_loop.is_running() is False runner(autostop_loop) # Make sure event loop is up and running (started within thread) assert autostop_loop.is_running() is True # Check we can connect with SMTPClient(cont.hostname, cont.port, timeout=AUTOSTOP_DELAY) as client: code, _ = client.helo("example.org") assert code == 250 # Wait until thread ends, which it will be when the loop autostops runner.join(timeout=AUTOSTOP_DELAY) assert runner.is_alive() is False catchup_delay() assert autostop_loop.is_running() is False # At this point, the loop _has_ stopped, but the task is still listening, # so rather than socket.timeout, we'll get a refusal instead, thus causing # SMTPServerDisconnected with pytest.raises(SMTPServerDisconnected): SMTPClient(cont.hostname, cont.port, timeout=0.1) cont.end() catchup_delay() cont.ended.wait() # Now the listener has gone away, and thus we will end up with socket.timeout # or ConnectionError (depending on OS) # noinspection PyTypeChecker with pytest.raises((socket.timeout, ConnectionError)): SMTPClient(cont.hostname, cont.port, timeout=0.1)
def test_unixsocket(self, safe_socket_dir, autostop_loop, runner): sockfile = safe_socket_dir / "smtp" cont = UnixSocketUnthreadedController(Sink(), unix_socket=sockfile, loop=autostop_loop) cont.begin() # Make sure event loop is not running (will be started in thread) assert autostop_loop.is_running() is False runner(autostop_loop) # Make sure event loop is up and running (started within thread) assert autostop_loop.is_running() is True # Check we can connect assert_smtp_socket(cont) # Wait until thread ends, which it will be when the loop autostops runner.join(timeout=AUTOSTOP_DELAY) assert runner.is_alive() is False catchup_delay() assert autostop_loop.is_running() is False # At this point, the loop _has_ stopped, but the task is still listening assert assert_smtp_socket(cont) is False # Stop the task cont.end() catchup_delay() # Now the listener has gone away # noinspection PyTypeChecker with pytest.raises((socket.timeout, ConnectionError)): assert_smtp_socket(cont)
def test_smtps(self, temp_event_loop): with watcher_process(watch_for_smtps) as retq: temp_event_loop.call_later(AUTOSTOP_DELAY, temp_event_loop.stop) main_n("--smtpscert", str(SERVER_CRT), "--smtpskey", str(SERVER_KEY)) catchup_delay() has_smtps = retq.get() assert has_smtps is True
def test_normal_situation(self): cont = Controller(Sink()) try: cont.start() catchup_delay() assert cont.smtpd is not None assert cont._thread_exception is None finally: cont.stop()
def test_tls_handshake_failing(self, tls_controller, client): handler = tls_controller.handler assert isinstance(handler, ExceptionCaptureHandler) try: client.ehlo("example.com") code, response = client.docmd("STARTTLS") with pytest.raises(SMTPServerDisconnected): client.docmd("SOMEFAILINGHANDSHAKE") catchup_delay() assert isinstance(handler.error, TLSSetupException) finally: tls_controller.stop()
def test_server_creation_ssl(self, safe_socket_dir, ssl_context_server): sockfile = safe_socket_dir / "smtp" cont = UnixSocketController(Sink(), unix_socket=sockfile, ssl_context=ssl_context_server) try: cont.start() # Allow additional time for SSL to kick in catchup_delay() assert_smtp_socket(cont) finally: cont.stop()
def test_tls_noreq(self, temp_event_loop): with watcher_process(watch_for_tls) as retq: temp_event_loop.call_later(AUTOSTOP_DELAY, temp_event_loop.stop) main_n( "--tlscert", str(SERVER_CRT), "--tlskey", str(SERVER_KEY), "--no-requiretls", ) catchup_delay() has_starttls = retq.get() assert has_starttls is True require_tls = retq.get() assert require_tls is False
def test_inet_contstop(self, temp_event_loop, runner): """ Verify behavior when the controller is stopped before loop is stopped """ cont = UnthreadedController(Sink(), loop=temp_event_loop) cont.begin() # Make sure event loop is not running (will be started in thread) assert temp_event_loop.is_running() is False runner(temp_event_loop) # Make sure event loop is up and running assert temp_event_loop.is_running() is True try: # Check that we can connect with SMTPClient(cont.hostname, cont.port, timeout=AUTOSTOP_DELAY) as client: code, _ = client.helo("example.org") assert code == 250 client.quit() catchup_delay() temp_event_loop.call_soon_threadsafe(cont.end) for _ in range(10): # 10 is arbitrary catchup_delay( ) # effectively yield to other threads/event loop if cont.ended.wait(1.0): break assert temp_event_loop.is_running() is True # Because we've called .end() there, the server listener should've gone # away, so we should end up with a socket.timeout or ConnectionError or # SMTPServerDisconnected (depending on lotsa factors) expect_errs = (socket.timeout, ConnectionError, SMTPServerDisconnected) # noinspection PyTypeChecker with pytest.raises(expect_errs): SMTPClient(cont.hostname, cont.port, timeout=0.1) finally: # Wrap up, or else we'll hang temp_event_loop.call_soon_threadsafe(cont.cancel_tasks) catchup_delay() runner.join() assert runner.is_alive() is False assert temp_event_loop.is_running() is False assert temp_event_loop.is_closed() is False
def assert_smtp_socket(controller: UnixSocketMixin) -> bool: assert Path(controller.unix_socket).exists() sockfile = controller.unix_socket ssl_context = controller.ssl_context with ExitStack() as stk: sock: socket.socket = stk.enter_context( socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)) sock.settimeout(AUTOSTOP_DELAY) sock.connect(str(sockfile)) if ssl_context: sock = stk.enter_context(ssl_context.wrap_socket(sock)) catchup_delay() try: resp = sock.recv(1024) except socket.timeout: return False if not resp: return False assert resp.startswith(b"220 ") assert resp.endswith(b"\r\n") sock.send(b"EHLO socket.test\r\n") # We need to "build" resparr because, especially when socket is wrapped # in SSL, the SMTP server takes it sweet time responding with the list # of ESMTP features ... resparr = bytearray() while not resparr.endswith(b"250 HELP\r\n"): catchup_delay() resp = sock.recv(1024) if not resp: break resparr += resp assert resparr.endswith(b"250 HELP\r\n") sock.send(b"QUIT\r\n") catchup_delay() resp = sock.recv(1024) assert resp.startswith(b"221") return True
def starter(loop: asyncio.AbstractEventLoop): nonlocal thread thread = Thread(target=_runner, args=(loop, )) thread.setDaemon(True) thread.start() catchup_delay()