@@ -696,6 +696,70 @@ def testSendMessageMultipleResentRaises(self):
696696 smtp .send_message (m )
697697 smtp .close ()
698698
699+ class LocalHostnameTests (unittest .TestCase ):
700+
701+ def test_connected_address_literal (self ):
702+ cases = (
703+ (socket .AF_INET , ('192.0.2.1' , 1234 ), '[192.0.2.1]' ),
704+ (socket .AF_INET6 , ('2001:db8::1' , 1234 , 0 , 0 ),
705+ '[IPv6:2001:db8::1]' ),
706+ (socket .AF_INET6 , ('fe80::1%eth0' , 1234 , 0 , 1 ), '[IPv6:fe80::1]' ),
707+ )
708+ for family , address , expected in cases :
709+ for connect_in_init in (True , False ):
710+ with self .subTest (family = family , address = address ,
711+ connect_in_init = connect_in_init ):
712+ sock = Mock (family = family )
713+ sock .getsockname .return_value = address
714+ with mock .patch ('smtplib.socket.getfqdn' , return_value = 'host' ), \
715+ mock .patch ('smtplib.socket.gethostbyname' ,
716+ return_value = '127.0.0.1' ), \
717+ mock .patch .object (smtplib .SMTP , '_get_socket' ,
718+ return_value = sock ), \
719+ mock .patch .object (smtplib .SMTP , 'getreply' ,
720+ return_value = (220 , b'OK' )):
721+ smtp = smtplib .SMTP ('server' if connect_in_init else '' )
722+ self .addCleanup (smtp .close )
723+ if not connect_in_init :
724+ smtp .connect ('server' )
725+ self .assertEqual (smtp .local_hostname , expected )
726+ smtp .ehlo ()
727+ sock .sendall .assert_called_with (
728+ ('ehlo ' + expected + '\r \n ' ).encode ('ascii' ))
729+
730+ def test_address_literal_updated_on_reconnect (self ):
731+ sock = Mock (family = socket .AF_INET6 )
732+ sock .getsockname .side_effect = [
733+ ('2001:db8::1' , 1234 , 0 , 0 ), ('2001:db8::2' , 1234 , 0 , 0 )]
734+ with mock .patch ('smtplib.socket.getfqdn' , return_value = 'host' ), \
735+ mock .patch ('smtplib.socket.gethostbyname' , return_value = '127.0.0.1' ), \
736+ mock .patch .object (smtplib .SMTP , '_get_socket' , return_value = sock ), \
737+ mock .patch .object (smtplib .SMTP , 'getreply' , return_value = (220 , b'OK' )):
738+ smtp = smtplib .SMTP ('server' )
739+ self .addCleanup (smtp .close )
740+ self .assertEqual (smtp .local_hostname , '[IPv6:2001:db8::1]' )
741+ smtp .close ()
742+ smtp .connect ('other-server' )
743+ self .assertEqual (smtp .local_hostname , '[IPv6:2001:db8::2]' )
744+
745+ def test_hostname_preserved (self ):
746+ for local_hostname , fqdn , expected in (
747+ ('custom' , 'host' , 'custom' ),
748+ (None , 'host.example' , 'host.example' ),
749+ ):
750+ with self .subTest (local_hostname = local_hostname , fqdn = fqdn ):
751+ sock = Mock (family = socket .AF_INET6 )
752+ with mock .patch ('smtplib.socket.getfqdn' , return_value = fqdn ), \
753+ mock .patch .object (smtplib .SMTP , '_get_socket' ,
754+ return_value = sock ), \
755+ mock .patch .object (smtplib .SMTP , 'getreply' ,
756+ return_value = (220 , b'OK' )):
757+ smtp = smtplib .SMTP ('server' , local_hostname = local_hostname )
758+ self .addCleanup (smtp .close )
759+ self .assertEqual (smtp .local_hostname , expected )
760+ sock .getsockname .assert_not_called ()
761+
762+
699763class NonConnectingTests (unittest .TestCase ):
700764
701765 def testNotConnected (self ):
0 commit comments