Skip to content

Run several tests in parallel #1573

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 9 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 15 additions & 15 deletions .github/workflows/python-app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,18 +67,18 @@ jobs:
run: pyright
- name: Test with tox
run: tox -e py
- name: Coveralls Parallel
uses: coverallsapp/github-action@v2
with:
flag-name: run-${{ join(matrix.*, '-') }}
parallel: true
finish:
needs: build
if: ${{ always() }}
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Coveralls Finished
uses: coverallsapp/github-action@v2
with:
parallel-finished: true
# - name: Coveralls Parallel
# uses: coverallsapp/github-action@v2
# with:
# flag-name: run-${{ join(matrix.*, '-') }}
# parallel: true
# finish:
# needs: build
# if: ${{ always() }}
# runs-on: ubuntu-latest
# timeout-minutes: 5
# steps:
# - name: Coveralls Finished
# uses: coverallsapp/github-action@v2
# with:
# parallel-finished: true
15 changes: 10 additions & 5 deletions binance/ws/websocket_api.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Dict
from typing import Dict, Optional
import asyncio

from websockets import WebSocketClientProtocol # type: ignore
Expand All @@ -13,10 +13,15 @@ def __init__(self, url: str, tld: str = "com", testnet: bool = False):
self._tld = tld
self._testnet = testnet
self._responses: Dict[str, asyncio.Future] = {}
self._connection_lock = (
asyncio.Lock()
) # used to ensure only one connection is established at a time
self._connection_lock: Optional[asyncio.Lock] = None
super().__init__(url=url, prefix="", path="", is_binary=False)

@property
def connection_lock(self) -> asyncio.Lock:
if self._connection_lock is None:
loop = asyncio.get_event_loop()
self._connection_lock = asyncio.Lock()
return self._connection_lock

def _handle_message(self, msg):
"""Override message handling to support request-response"""
Expand Down Expand Up @@ -51,7 +56,7 @@ async def _ensure_ws_connection(self) -> None:
3. Wait for connection to be ready
4. Handle reconnection if needed
"""
async with self._connection_lock:
async with self.connection_lock:
try:
if (
self.ws is None
Expand Down
24 changes: 18 additions & 6 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,20 +62,32 @@ def futuresClient():


@pytest.fixture(scope="function")
def clientAsync():
return AsyncClient(api_key, api_secret, https_proxy=proxy, testnet=testnet)
async def clientAsync():
client = AsyncClient(api_key, api_secret, https_proxy=proxy, testnet=testnet)
try:
yield client
finally:
await client.close_connection()


@pytest.fixture(scope="function")
def futuresClientAsync():
return AsyncClient(
async def futuresClientAsync():
client = AsyncClient(
futures_api_key, futures_api_secret, https_proxy=proxy, testnet=testnet
)
try:
yield client
finally:
await client.close_connection()


@pytest.fixture(scope="function")
def liveClientAsync():
return AsyncClient(api_key, api_secret, https_proxy=proxy, testnet=False)
async def liveClientAsync():
client = AsyncClient(api_key, api_secret, https_proxy=proxy, testnet=False)
try:
yield client
finally:
await client.close_connection()

@pytest.fixture(scope="function")
def manager():
Expand Down
69 changes: 45 additions & 24 deletions tests/test_ws_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,38 +114,58 @@ async def test_testnet_url():
@pytest.mark.asyncio
async def test_message_handling(clientAsync):
"""Test message handling with various message types"""
# Test valid message
future = asyncio.Future()
clientAsync.ws_api._responses["123"] = future
valid_msg = {"id": "123", "status": 200, "result": {"test": "data"}}
clientAsync.ws_api._handle_message(json.dumps(valid_msg))
result = await clientAsync.ws_api._responses["123"]
assert result == valid_msg

@pytest.mark.asyncio
async def test_message_handling_raise_exception(clientAsync):
with pytest.raises(BinanceAPIException):
try:
# Test valid message
future = asyncio.Future()
clientAsync.ws_api._responses["123"] = future
valid_msg = {"id": "123", "status": 400, "error": {"code": "0", "msg": "error message"}}
valid_msg = {"id": "123", "status": 200, "result": {"test": "data"}}
clientAsync.ws_api._handle_message(json.dumps(valid_msg))
await future
result = await clientAsync.ws_api._responses["123"]
assert result == valid_msg
finally:
# Ensure cleanup
await clientAsync.close_connection()


@pytest.mark.asyncio
async def test_message_handling_raise_exception(clientAsync):
try:
with pytest.raises(BinanceAPIException):
future = asyncio.Future()
clientAsync.ws_api._responses["123"] = future
valid_msg = {"id": "123", "status": 400, "error": {"code": "0", "msg": "error message"}}
clientAsync.ws_api._handle_message(json.dumps(valid_msg))
await future
finally:
# Ensure cleanup
await clientAsync.close_connection()


@pytest.mark.asyncio
async def test_message_handling_raise_exception_without_id(clientAsync):
with pytest.raises(BinanceAPIException):
future = asyncio.Future()
clientAsync.ws_api._responses["123"] = future
valid_msg = {"id": "123", "status": 400, "error": {"code": "0", "msg": "error message"}}
clientAsync.ws_api._handle_message(json.dumps(valid_msg))
await future

try:
with pytest.raises(BinanceAPIException):
future = asyncio.Future()
clientAsync.ws_api._responses["123"] = future
valid_msg = {"id": "123", "status": 400, "error": {"code": "0", "msg": "error message"}}
clientAsync.ws_api._handle_message(json.dumps(valid_msg))
await future
finally:
# Ensure cleanup
await clientAsync.close_connection()


@pytest.mark.asyncio
async def test_message_handling_invalid_json(clientAsync):
with pytest.raises(json.JSONDecodeError):
clientAsync.ws_api._handle_message("invalid json")
try:
with pytest.raises(json.JSONDecodeError):
clientAsync.ws_api._handle_message("invalid json")

with pytest.raises(json.JSONDecodeError):
clientAsync.ws_api._handle_message("invalid json")
with pytest.raises(json.JSONDecodeError):
clientAsync.ws_api._handle_message("invalid json")
finally:
# Ensure cleanup
await clientAsync.close_connection()


@pytest.mark.asyncio(scope="function")
Expand Down Expand Up @@ -199,6 +219,7 @@ async def test_ws_queue_overflow(clientAsync):
# Restore original queue size
clientAsync.ws_api.MAX_QUEUE_SIZE = original_size


@pytest.mark.skipif(sys.version_info < (3, 8), reason="websockets_proxy Python 3.8+")
@pytest.mark.asyncio
async def test_ws_api_with_stream(clientAsync):
Expand Down
2 changes: 1 addition & 1 deletion tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ passenv =
TEST_API_SECRET
TEST_FUTURES_API_KEY
TEST_FUTURES_API_SECRET
commands = pytest -n 1 -v tests/ --doctest-modules --cov binance --cov-report term-missing --reruns 3 --reruns-delay 120
commands = pytest -n 5 -v tests/ --doctest-modules --cov binance --cov-report term-missing --reruns 3 --reruns-delay 120

[pep8]
ignore = E501
Loading