Skip to content

Commit

Permalink
tests
Browse files Browse the repository at this point in the history
  • Loading branch information
miguelgrinberg committed Sep 10, 2023
1 parent 5122519 commit e84e3c5
Show file tree
Hide file tree
Showing 10 changed files with 203 additions and 11 deletions.
18 changes: 16 additions & 2 deletions src/socketio/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,22 @@ def instrument(self):
Socket._websocket_handler = functools.partialmethod(
self.__class__._eio_websocket_handler, self)

def uninstrument(self):
if self.mode == 'development':
self.sio.manager.connect = self.sio.manager.__connect
self.sio.manager.disconnect = self.sio.manager.__disconnect
self.sio.manager.enter_room = self.sio.manager.__enter_room
self.sio.manager.leave_room = self.sio.manager.__leave_room
self.sio.manager.emit = self.sio.manager.__emit
self.sio._handle_event_internal = self.sio.__handle_event_internal
self.sio.eio._ok = self.sio.eio.__ok

from engineio.socket import Socket
Socket.handle_post_request = Socket.__handle_post_request
Socket._websocket_handler = Socket.__websocket_handler

def admin_connect(self, sid, environ, client_auth):
if self.auth != None:
if self.auth:
authenticated = False
if isinstance(self.auth, dict):
authenticated = client_auth == self.auth
Expand Down Expand Up @@ -176,7 +190,7 @@ def admin_disconnect(self, _, namespace, close, room_filter=None):

def shutdown(self):
self.stop_stats_event.set()
self.stats_thread.join()
self.stats_task.join()

def _connect(self, eio_sid, namespace):
sid = self.sio.manager.__connect(eio_sid, namespace)
Expand Down
2 changes: 1 addition & 1 deletion src/socketio/asyncio_admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ def instrument(self):

async def admin_connect(self, sid, environ, client_auth):
authenticated = True
if self.auth != None:
if self.auth:
authenticated = False
if isinstance(self.auth, dict):
authenticated = client_auth == self.auth
Expand Down
2 changes: 1 addition & 1 deletion src/socketio/asyncio_simple_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ def sid(self):
The session ID is not guaranteed to remain constant throughout the life
of the connection, as reconnections can cause it to change.
"""
return self.client.sid if self.client else None
return self.client.get_sid(self.namespace) if self.client else None

@property
def transport(self):
Expand Down
2 changes: 1 addition & 1 deletion src/socketio/simple_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ def sid(self):
The session ID is not guaranteed to remain constant throughout the life
of the connection, as reconnections can cause it to change.
"""
return self.client.sid if self.client else None
return self.client.get_sid(self.namespace) if self.client else None

@property
def transport(self):
Expand Down
8 changes: 4 additions & 4 deletions tests/asyncio/test_asyncio_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,7 @@ def test_emit_to_invalid_namespace(self):
_run(self.bm.emit('my event', {'foo': 'bar'}, namespace='/foo'))

def test_emit_with_tuple(self):
sid = self.bm.connect('123', '/foo')
sid = _run(self.bm.connect('123', '/foo'))
_run(
self.bm.emit(
'my event', ('foo', 'bar'), namespace='/foo', room=sid
Expand All @@ -364,7 +364,7 @@ def test_emit_with_tuple(self):
assert pkt.encode() == '42/foo,["my event","foo","bar"]'

def test_emit_with_list(self):
sid = self.bm.connect('123', '/foo')
sid = _run(self.bm.connect('123', '/foo'))
_run(
self.bm.emit(
'my event', ['foo', 'bar'], namespace='/foo', room=sid
Expand All @@ -377,7 +377,7 @@ def test_emit_with_list(self):
assert pkt.encode() == '42/foo,["my event",["foo","bar"]]'

def test_emit_with_none(self):
sid = self.bm.connect('123', '/foo')
sid = _run(self.bm.connect('123', '/foo'))
_run(
self.bm.emit(
'my event', None, namespace='/foo', room=sid
Expand All @@ -390,7 +390,7 @@ def test_emit_with_none(self):
assert pkt.encode() == '42/foo,["my event"]'

def test_emit_binary(self):
sid = self.bm.connect('123', '/')
sid = _run(self.bm.connect('123', '/'))
_run(
self.bm.emit(
u'my event', b'my binary data', namespace='/', room=sid
Expand Down
3 changes: 2 additions & 1 deletion tests/asyncio/test_asyncio_simple_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ def test_connect_twice(self):

def test_properties(self):
client = AsyncSimpleClient()
client.client = mock.MagicMock(sid='sid', transport='websocket')
client.client = mock.MagicMock(transport='websocket')
client.client.get_sid.return_value = 'sid'
client.connected_event.set()
client.connected = True

Expand Down
109 changes: 109 additions & 0 deletions tests/common/test_admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import unittest
import socketio
from tests.web_server import SocketIOWebServer


class TestServer(unittest.TestCase):
def setUp(self):
sio = socketio.Server(async_mode='threading')
self.instrumented_server = sio.instrument(auth=False)

@sio.event
def enter_room(sid, data):
sio.enter_room(sid, data)

@sio.event(namespace='/foo')
def connect(sid, environ, auth):
pass

self.server = SocketIOWebServer(sio)
self.server.start()

import logging
logging.getLogger('engineio.client').setLevel(logging.DEBUG)
logging.getLogger('socketio.client').setLevel(logging.DEBUG)

def tearDown(self):
self.server.stop()
self.instrumented_server.shutdown()
self.instrumented_server.uninstrument()

import logging
logging.getLogger('engineio.client').setLevel(logging.NOTSET)
logging.getLogger('socketio.client').setLevel(logging.NOTSET)

def test_admin_connect_only_admin(self):
with socketio.SimpleClient() as client:
client.connect('http://localhost:8900', namespace='/admin')
sid = client.sid
expected = ['config', 'all_sockets', 'server_stats']
events = {}
while expected:
data = client.receive(timeout=5)
if data[0] in expected:
events[data[0]] = data[1]
expected.remove(data[0])
assert 'supportedFeatures' in events['config']
assert len(events['all_sockets']) == 1
assert events['all_sockets'][0]['id'] == sid
assert events['all_sockets'][0]['rooms'] == [sid]
assert events['server_stats']['clientsCount'] == 1
assert events['server_stats']['pollingClientsCount'] == 0
assert len(events['server_stats']['namespaces']) == 3
assert {'name': '/', 'socketsCount': 0} in \
events['server_stats']['namespaces']
assert {'name': '/foo', 'socketsCount': 0} in \
events['server_stats']['namespaces']
assert {'name': '/admin', 'socketsCount': 1} in \
events['server_stats']['namespaces']

def test_admin_connect_with_others(self):
client1 = socketio.SimpleClient()
client1.connect('http://localhost:8900')
client1.emit('enter_room', 'room')
sid1 = client1.sid

client2 = socketio.SimpleClient()
client2.connect('http://localhost:8900', namespace='/foo')
sid2 = client2.sid

client3 = socketio.SimpleClient()
client3.connect('http://localhost:8900', namespace='/admin')
sid3 = client3.sid

with socketio.SimpleClient() as client:
client.connect('http://localhost:8900', namespace='/admin')
sid = client.sid
expected = ['config', 'all_sockets', 'server_stats']
events = {}
while expected:
data = client.receive(timeout=5)
if data[0] in expected:
events[data[0]] = data[1]
expected.remove(data[0])
client3.disconnect()
client2.disconnect()
client1.disconnect()

assert 'supportedFeatures' in events['config']
assert len(events['all_sockets']) == 4
assert events['server_stats']['clientsCount'] == 4
assert events['server_stats']['pollingClientsCount'] == 0
assert len(events['server_stats']['namespaces']) == 3
assert {'name': '/', 'socketsCount': 1} in \
events['server_stats']['namespaces']
assert {'name': '/foo', 'socketsCount': 1} in \
events['server_stats']['namespaces']
assert {'name': '/admin', 'socketsCount': 2} in \
events['server_stats']['namespaces']

for socket in events['all_sockets']:
if socket['id'] == sid:
assert socket['rooms'] == [sid]
elif socket['id'] == sid1:
assert socket['rooms'] == [sid1, 'room']
elif socket['id'] == sid2:
assert socket['rooms'] == [sid2]
elif socket['id'] == sid3:
assert socket['rooms'] == [sid3]

3 changes: 2 additions & 1 deletion tests/common/test_simple_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ def test_connect_twice(self):

def test_properties(self):
client = SimpleClient()
client.client = mock.MagicMock(sid='sid', transport='websocket')
client.client = mock.MagicMock(transport='websocket')
client.client.get_sid.return_value = 'sid'
client.connected_event.set()
client.connected = True

Expand Down
62 changes: 62 additions & 0 deletions tests/web_server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import threading
from socketserver import ThreadingMixIn
from wsgiref.simple_server import make_server, WSGIServer, WSGIRequestHandler
import socketio


class SocketIOWebServer:
"""A simple web server used for running Socket.IO servers in tests.
:param sio: a Socket.IO server instance.
Note 1: This class is not production-ready and is intended for testing.
Note 2: This class only supports the "threading" async_mode, with WebSocket
support provided by the simple-websocket package.
"""
def __init__(self, sio):
if sio.async_mode != 'threading':
raise ValueError('The async_mode must be "threading"')
self.app = socketio.WSGIApp(sio)
self.httpd = None
self.thread = None

def start(self, port=8900):
"""Start the web server.
:param port: the port to listen on. Defaults to 8900.
The server is started in a background thread.
"""
class ThreadingWSGIServer(ThreadingMixIn, WSGIServer):
pass

class WebSocketRequestHandler(WSGIRequestHandler):
def get_environ(self):
env = super().get_environ()

# pass the raw socket to the WSGI app so that it can be used
# by WebSocket connections (hack copied from gunicorn)
env['gunicorn.socket'] = self.connection
return env

self.httpd = make_server('', port, self._app_wrapper,
ThreadingWSGIServer, WebSocketRequestHandler)
self.thread = threading.Thread(target=self.httpd.serve_forever)
self.thread.start()

def stop(self):
"""Stop the web server."""
self.httpd.shutdown()
self.httpd.server_close()
self.thread.join()
self.httpd = None
self.thread = None

def _app_wrapper(self, environ, start_response):
try:
return self.app(environ, start_response)
except StopIteration:
# end the WebSocket request without sending a response
# (this is a hack that was copied from gunicorn's threaded worker)
start_response('200 OK', [])
return []
5 changes: 5 additions & 0 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ commands=
pip install -e .
pytest -p no:logging --cov=socketio --cov-branch --cov-report=term-missing --cov-report=xml
deps=
simple-websocket
uvicorn
requests
websocket-client
aiohttp
msgpack
pytest
pytest-cov
Expand Down

0 comments on commit e84e3c5

Please sign in to comment.