mirror of
				https://github.com/django/daphne.git
				synced 2025-11-04 09:37:32 +03:00 
			
		
		
		
	test for regression in handling asyncio.CancelledError
This commit is contained in:
		
							parent
							
								
									7225fa7245
								
							
						
					
					
						commit
						49c00da715
					
				| 
						 | 
					@ -7,7 +7,7 @@ import traceback
 | 
				
			||||||
from concurrent.futures import CancelledError
 | 
					from concurrent.futures import CancelledError
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class DaphneTestingInstance:
 | 
					class BaseDaphneTestingInstance:
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    Launches an instance of Daphne in a subprocess, with a host and port
 | 
					    Launches an instance of Daphne in a subprocess, with a host and port
 | 
				
			||||||
    attribute allowing you to call it.
 | 
					    attribute allowing you to call it.
 | 
				
			||||||
| 
						 | 
					@ -17,17 +17,16 @@ class DaphneTestingInstance:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    startup_timeout = 2
 | 
					    startup_timeout = 2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self, xff=False, http_timeout=None, request_buffer_size=None):
 | 
					    def __init__(
 | 
				
			||||||
 | 
					        self, xff=False, http_timeout=None, request_buffer_size=None, *, application
 | 
				
			||||||
 | 
					    ):
 | 
				
			||||||
        self.xff = xff
 | 
					        self.xff = xff
 | 
				
			||||||
        self.http_timeout = http_timeout
 | 
					        self.http_timeout = http_timeout
 | 
				
			||||||
        self.host = "127.0.0.1"
 | 
					        self.host = "127.0.0.1"
 | 
				
			||||||
        self.lock = multiprocessing.Lock()
 | 
					 | 
				
			||||||
        self.request_buffer_size = request_buffer_size
 | 
					        self.request_buffer_size = request_buffer_size
 | 
				
			||||||
 | 
					        self.application = application
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __enter__(self):
 | 
					    def __enter__(self):
 | 
				
			||||||
        # Clear result storage
 | 
					 | 
				
			||||||
        TestApplication.delete_setup()
 | 
					 | 
				
			||||||
        TestApplication.delete_result()
 | 
					 | 
				
			||||||
        # Option Daphne features
 | 
					        # Option Daphne features
 | 
				
			||||||
        kwargs = {}
 | 
					        kwargs = {}
 | 
				
			||||||
        if self.request_buffer_size:
 | 
					        if self.request_buffer_size:
 | 
				
			||||||
| 
						 | 
					@ -42,7 +41,7 @@ class DaphneTestingInstance:
 | 
				
			||||||
        # Start up process
 | 
					        # Start up process
 | 
				
			||||||
        self.process = DaphneProcess(
 | 
					        self.process = DaphneProcess(
 | 
				
			||||||
            host=self.host,
 | 
					            host=self.host,
 | 
				
			||||||
            application=TestApplication(lock=self.lock),
 | 
					            application=self.application,
 | 
				
			||||||
            kwargs=kwargs,
 | 
					            kwargs=kwargs,
 | 
				
			||||||
            setup=self.process_setup,
 | 
					            setup=self.process_setup,
 | 
				
			||||||
            teardown=self.process_teardown,
 | 
					            teardown=self.process_teardown,
 | 
				
			||||||
| 
						 | 
					@ -76,6 +75,21 @@ class DaphneTestingInstance:
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        pass
 | 
					        pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_received(self):
 | 
				
			||||||
 | 
					        pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DaphneTestingInstance(BaseDaphneTestingInstance):
 | 
				
			||||||
 | 
					    def __init__(self, *args, **kwargs):
 | 
				
			||||||
 | 
					        self.lock = multiprocessing.Lock()
 | 
				
			||||||
 | 
					        super().__init__(*args, **kwargs, application=TestApplication(lock=self.lock))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __enter__(self):
 | 
				
			||||||
 | 
					        # Clear result storage
 | 
				
			||||||
 | 
					        TestApplication.delete_setup()
 | 
				
			||||||
 | 
					        TestApplication.delete_result()
 | 
				
			||||||
 | 
					        return super().__enter__()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_received(self):
 | 
					    def get_received(self):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        Returns the scope and messages the test application has received
 | 
					        Returns the scope and messages the test application has received
 | 
				
			||||||
| 
						 | 
					@ -149,7 +163,7 @@ class DaphneProcess(multiprocessing.Process):
 | 
				
			||||||
                self.server.run()
 | 
					                self.server.run()
 | 
				
			||||||
            finally:
 | 
					            finally:
 | 
				
			||||||
                self.teardown()
 | 
					                self.teardown()
 | 
				
			||||||
        except Exception as e:
 | 
					        except BaseException as e:
 | 
				
			||||||
            # Put the error on our queue so the parent gets it
 | 
					            # Put the error on our queue so the parent gets it
 | 
				
			||||||
            self.errors.put((e, traceback.format_exc()))
 | 
					            self.errors.put((e, traceback.format_exc()))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,6 +6,7 @@ from urllib import parse
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import http_strategies
 | 
					import http_strategies
 | 
				
			||||||
from http_base import DaphneTestCase, DaphneTestingInstance
 | 
					from http_base import DaphneTestCase, DaphneTestingInstance
 | 
				
			||||||
 | 
					from daphne.testing import BaseDaphneTestingInstance
 | 
				
			||||||
from hypothesis import given, settings
 | 
					from hypothesis import given, settings
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -261,3 +262,47 @@ class TestWebsocket(DaphneTestCase):
 | 
				
			||||||
            self.websocket_send_frame(sock, "still alive?")
 | 
					            self.websocket_send_frame(sock, "still alive?")
 | 
				
			||||||
            # Receive a frame and make sure it's correct
 | 
					            # Receive a frame and make sure it's correct
 | 
				
			||||||
            assert self.websocket_receive_frame(sock) == "cake"
 | 
					            assert self.websocket_receive_frame(sock) == "cake"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_application_checker_handles_asyncio_cancellederror(self):
 | 
				
			||||||
 | 
					        with CancellingTestingInstance() as app:
 | 
				
			||||||
 | 
					            # Connect to the websocket app, it will immediately raise
 | 
				
			||||||
 | 
					            # asyncio.CancelledError
 | 
				
			||||||
 | 
					            sock, _ = self.websocket_handshake(app)
 | 
				
			||||||
 | 
					            # Disconnect from the socket
 | 
				
			||||||
 | 
					            sock.close()
 | 
				
			||||||
 | 
					            # Wait for application_checker to clean up the applications for
 | 
				
			||||||
 | 
					            # disconnected clients, and for the server to be stopped.
 | 
				
			||||||
 | 
					            time.sleep(3)
 | 
				
			||||||
 | 
					            # Make sure we received either no error, or a ConnectionsNotEmpty
 | 
				
			||||||
 | 
					            while not app.process.errors.empty():
 | 
				
			||||||
 | 
					                err, _tb = app.process.errors.get()
 | 
				
			||||||
 | 
					                if not isinstance(err, ConnectionsNotEmpty):
 | 
				
			||||||
 | 
					                    raise err
 | 
				
			||||||
 | 
					                self.fail(
 | 
				
			||||||
 | 
					                    "Server connections were not cleaned up after an asyncio.CancelledError was raised"
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async def cancelling_application(scope, receive, send):
 | 
				
			||||||
 | 
					    import asyncio
 | 
				
			||||||
 | 
					    from twisted.internet import reactor
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    reactor.callLater(2, lambda: reactor.stop())
 | 
				
			||||||
 | 
					    await send({"type": "websocket.accept"})
 | 
				
			||||||
 | 
					    raise asyncio.CancelledError()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ConnectionsNotEmpty(Exception):
 | 
				
			||||||
 | 
					    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class CancellingTestingInstance(BaseDaphneTestingInstance):
 | 
				
			||||||
 | 
					    def __init__(self):
 | 
				
			||||||
 | 
					        super().__init__(application=cancelling_application)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def process_teardown(self):
 | 
				
			||||||
 | 
					        import multiprocessing
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        proc = multiprocessing.current_process()
 | 
				
			||||||
 | 
					        if proc.server.connections:
 | 
				
			||||||
 | 
					            raise ConnectionsNotEmpty()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue
	
	Block a user