@@ -696,6 +696,69 @@ 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 , connect_in_init = connect_in_init ):
711+ sock = Mock (family = family )
712+ sock .getsockname .return_value = address
713+ with mock .patch ('smtplib.socket.getfqdn' , return_value = 'host' ), \
714+ mock .patch ('smtplib.socket.gethostbyname' ,
715+ return_value = '127.0.0.1' ), \
716+ mock .patch .object (smtplib .SMTP , '_get_socket' ,
717+ return_value = sock ), \
718+ mock .patch .object (smtplib .SMTP , 'getreply' ,
719+ return_value = (220 , b'OK' )):
720+ smtp = smtplib .SMTP ('server' if connect_in_init else '' )
721+ self .addCleanup (smtp .close )
722+ if not connect_in_init :
723+ smtp .connect ('server' )
724+ self .assertEqual (smtp .local_hostname , expected )
725+ smtp .ehlo ()
726+ sock .sendall .assert_called_with (
727+ ('ehlo ' + expected + '\r \n ' ).encode ('ascii' ))
728+
729+ def test_address_literal_updated_on_reconnect (self ):
730+ sock = Mock (family = socket .AF_INET6 )
731+ sock .getsockname .side_effect = [
732+ ('2001:db8::1' , 1234 , 0 , 0 ), ('2001:db8::2' , 1234 , 0 , 0 )]
733+ with mock .patch ('smtplib.socket.getfqdn' , return_value = 'host' ), \
734+ mock .patch ('smtplib.socket.gethostbyname' , return_value = '127.0.0.1' ), \
735+ mock .patch .object (smtplib .SMTP , '_get_socket' , return_value = sock ), \
736+ mock .patch .object (smtplib .SMTP , 'getreply' , return_value = (220 , b'OK' )):
737+ smtp = smtplib .SMTP ('server' )
738+ self .addCleanup (smtp .close )
739+ self .assertEqual (smtp .local_hostname , '[IPv6:2001:db8::1]' )
740+ smtp .close ()
741+ smtp .connect ('other-server' )
742+ self .assertEqual (smtp .local_hostname , '[IPv6:2001:db8::2]' )
743+
744+ def test_hostname_preserved (self ):
745+ for local_hostname , fqdn , expected in (
746+ ('custom' , 'host' , 'custom' ),
747+ (None , 'host.example' , 'host.example' ),
748+ ):
749+ with self .subTest (local_hostname = local_hostname , fqdn = fqdn ):
750+ sock = Mock (family = socket .AF_INET6 )
751+ with mock .patch ('smtplib.socket.getfqdn' , return_value = fqdn ), \
752+ mock .patch .object (smtplib .SMTP , '_get_socket' ,
753+ return_value = sock ), \
754+ mock .patch .object (smtplib .SMTP , 'getreply' ,
755+ return_value = (220 , b'OK' )):
756+ smtp = smtplib .SMTP ('server' , local_hostname = local_hostname )
757+ self .addCleanup (smtp .close )
758+ self .assertEqual (smtp .local_hostname , expected )
759+ sock .getsockname .assert_not_called ()
760+
761+
699762class NonConnectingTests (unittest .TestCase ):
700763
701764 def testNotConnected (self ):
0 commit comments