@@ -96,6 +96,77 @@ def test_fatal_error_no_name_error(self):
9696 # Restore error logging.
9797 log .logger .setLevel (log_level )
9898
99+ def test_shutdown_error_closes_after_flushing (self ):
100+ app_proto = mock .Mock (spec = asyncio .Protocol )
101+ app_proto .eof_received .return_value = False
102+ ssl_proto = self .ssl_protocol (proto = app_proto )
103+ ssl_proto ._state = sslproto .SSLProtocolState .SHUTDOWN
104+ ssl_proto ._app_state = sslproto .AppProtocolState .STATE_CON_MADE
105+ transport = mock .Mock ()
106+ ssl_proto ._transport = transport
107+ ssl_proto ._sslobj = mock .Mock ()
108+ shutdown_exc = ssl .SSLError (ssl .SSL_ERROR_SSL , 'shutdown failed' )
109+ ssl_proto ._sslobj .unwrap .side_effect = shutdown_exc
110+ ssl_proto ._outgoing = mock .Mock ()
111+ ssl_proto ._outgoing .read .side_effect = [b'close notify' , b'' , b'' ]
112+ ssl_proto ._outgoing .pending = 0
113+ timeout_handle = mock .Mock ()
114+ ssl_proto ._shutdown_timeout_handle = timeout_handle
115+
116+ ssl_proto ._do_shutdown ()
117+ ssl_proto .eof_received ()
118+
119+ transport .write .assert_called_once_with (b'close notify' )
120+ transport ._force_close .assert_not_called ()
121+ test_utils .run_briefly (self .loop )
122+ transport .close .assert_called_once_with ()
123+ timeout_handle .cancel .assert_not_called ()
124+
125+ ssl_proto .connection_lost (None )
126+ test_utils .run_briefly (self .loop )
127+ app_proto .connection_lost .assert_called_once_with (shutdown_exc )
128+ timeout_handle .cancel .assert_called_once_with ()
129+
130+ def test_shutdown_waits_for_resume_writing_before_close (self ):
131+ app_proto = mock .Mock (spec = asyncio .Protocol )
132+ ssl_proto = self .ssl_protocol (proto = app_proto )
133+ ssl_proto ._state = sslproto .SSLProtocolState .SHUTDOWN
134+ ssl_proto ._app_state = sslproto .AppProtocolState .STATE_CON_MADE
135+ transport = mock .Mock ()
136+ ssl_proto ._transport = transport
137+ ssl_proto ._sslobj = mock .Mock ()
138+ shutdown_exc = ssl .SSLError (ssl .SSL_ERROR_SSL , 'shutdown failed' )
139+ ssl_proto ._sslobj .unwrap .side_effect = shutdown_exc
140+ ssl_proto ._outgoing = mock .Mock ()
141+ ssl_proto ._outgoing .read .return_value = b'close notify'
142+ ssl_proto ._outgoing .pending = 0
143+ ssl_proto ._ssl_writing_paused = True
144+
145+ ssl_proto ._do_shutdown ()
146+ test_utils .run_briefly (self .loop )
147+
148+ transport .write .assert_not_called ()
149+ transport .close .assert_not_called ()
150+
151+ ssl_proto .resume_writing ()
152+ transport .write .assert_called_once_with (b'close notify' )
153+ test_utils .run_briefly (self .loop )
154+ transport .close .assert_called_once_with ()
155+
156+ def test_shutdown_raw_error_takes_precedence (self ):
157+ app_proto = mock .Mock (spec = asyncio .Protocol )
158+ ssl_proto = self .ssl_protocol (proto = app_proto )
159+ ssl_proto ._state = sslproto .SSLProtocolState .SHUTDOWN
160+ ssl_proto ._app_state = sslproto .AppProtocolState .STATE_CON_MADE
161+ ssl_proto ._shutdown_exc = ssl .SSLError (
162+ ssl .SSL_ERROR_SSL , 'shutdown failed' )
163+ raw_exc = ConnectionResetError ('raw write failed' )
164+
165+ ssl_proto .connection_lost (raw_exc )
166+ test_utils .run_briefly (self .loop )
167+
168+ app_proto .connection_lost .assert_called_once_with (raw_exc )
169+
99170 def test_connection_lost (self ):
100171 # From issue #472.
101172 # yield from waiter hang if lost_connection was called.
0 commit comments