diff --git a/README.md b/README.md index d9ff2985e..b5479565f 100644 --- a/README.md +++ b/README.md @@ -511,6 +511,8 @@ When running tests locally, do it from the project root: poetry run pytest test/ +poetry run pytest -n auto --dist loadscope test/ # parallel testing using n CPU workers + poetry run pytest -s -v test/ # for more verbose output poetry run pytest test/ # for a single file diff --git a/poetry.lock b/poetry.lock index 5d241f770..1ed81d70c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -397,6 +397,17 @@ doc = ["Sphinx (>=1.6.5,<2)", "sphinx-rtd-theme (>=0.1.9,<2)", "towncrier (>=19. lint = ["black (>=18.6b4,<19)", "flake8 (==3.7.9)", "isort (>=4.2.15,<5)", "mypy (==0.720)", "pydocstyle (>=5.0.0,<6)", "pytest (>=3.4.1,<4.0.0)"] test = ["hypothesis (>=4.43.0,<5.0.0)", "pytest (==5.4.1)", "pytest-xdist", "tox (==3.14.6)"] +[[package]] +name = "execnet" +version = "1.9.0" +description = "execnet: rapid multi-Python deployment" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.extras] +testing = ["pre-commit"] + [[package]] name = "fastecdsa" version = "2.2.3" @@ -930,6 +941,36 @@ typing-extensions = {version = ">=3.7.2", markers = "python_version < \"3.8\""} [package.extras] testing = ["coverage (==6.2)", "hypothesis (>=5.7.1)", "flaky (>=3.5.0)", "mypy (==0.931)", "pytest-trio (>=0.7.0)"] +[[package]] +name = "pytest-forked" +version = "1.4.0" +description = "run tests in isolated forked subprocesses" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +py = "*" +pytest = ">=3.10" + +[[package]] +name = "pytest-xdist" +version = "2.5.0" +description = "pytest xdist plugin for distributed testing and loop-on-failing modes" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +execnet = ">=1.1" +pytest = ">=6.2.0" +pytest-forked = "*" + +[package.extras] +psutil = ["psutil (>=3.0)"] +setproctitle = ["setproctitle"] +testing = ["filelock"] + [[package]] name = "pywin32" version = "304" @@ -1172,7 +1213,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest- [metadata] lock-version = "1.1" python-versions = ">=3.7.2,<3.10" -content-hash = "98bec0c83e24edf979b6f0e1834f695e0a311c211df8dfb66b4df01971384d6f" +content-hash = "24ee88e402dddfa9522d2d379f693494e1ca064d302f811c75fe9bb1fc16944e" [metadata.files] aiohttp = [ @@ -1395,6 +1436,10 @@ eth-utils = [ {file = "eth-utils-1.10.0.tar.gz", hash = "sha256:bf82762a46978714190b0370265a7148c954d3f0adaa31c6f085ea375e4c61af"}, {file = "eth_utils-1.10.0-py3-none-any.whl", hash = "sha256:74240a8c6f652d085ed3c85f5f1654203d2f10ff9062f83b3bad0a12ff321c7a"}, ] +execnet = [ + {file = "execnet-1.9.0-py2.py3-none-any.whl", hash = "sha256:a295f7cc774947aac58dde7fdc85f4aa00c42adf5d8f5468fc630c1acf30a142"}, + {file = "execnet-1.9.0.tar.gz", hash = "sha256:8f694f3ba9cc92cab508b152dcfe322153975c29bda272e2fd7f3f00f36e47c5"}, +] fastecdsa = [ {file = "fastecdsa-2.2.3-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:c1f27c5b37aee4bafa8ee304f6da3382ba90200a6998764b3cedba506ef03ff5"}, {file = "fastecdsa-2.2.3-cp36-cp36m-macosx_11_0_x86_64.whl", hash = "sha256:05676e917fea8d56f15a7f00c81560d9a6be83767435dca17cc2a62741276656"}, @@ -1911,6 +1956,14 @@ pytest-asyncio = [ {file = "pytest_asyncio-0.18.3-1-py3-none-any.whl", hash = "sha256:16cf40bdf2b4fb7fc8e4b82bd05ce3fbcd454cbf7b92afc445fe299dabb88213"}, {file = "pytest_asyncio-0.18.3-py3-none-any.whl", hash = "sha256:8fafa6c52161addfd41ee7ab35f11836c5a16ec208f93ee388f752bea3493a84"}, ] +pytest-forked = [ + {file = "pytest-forked-1.4.0.tar.gz", hash = "sha256:8b67587c8f98cbbadfdd804539ed5455b6ed03802203485dd2f53c1422d7440e"}, + {file = "pytest_forked-1.4.0-py3-none-any.whl", hash = "sha256:bbbb6717efc886b9d64537b41fb1497cfaf3c9601276be8da2cccfea5a3c8ad8"}, +] +pytest-xdist = [ + {file = "pytest-xdist-2.5.0.tar.gz", hash = "sha256:4580deca3ff04ddb2ac53eba39d76cb5dd5edeac050cb6fbc768b0dd712b4edf"}, + {file = "pytest_xdist-2.5.0-py3-none-any.whl", hash = "sha256:6fe5c74fec98906deb8f2d2b616b5c782022744978e7bd4695d39c8f42d0ce65"}, +] pywin32 = [ {file = "pywin32-304-cp310-cp310-win32.whl", hash = "sha256:3c7bacf5e24298c86314f03fa20e16558a4e4138fc34615d7de4070c23e65af3"}, {file = "pywin32-304-cp310-cp310-win_amd64.whl", hash = "sha256:4f32145913a2447736dad62495199a8e280a77a0ca662daa2332acf849f0be48"}, diff --git a/pyproject.toml b/pyproject.toml index 85803906c..44f20eb8a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,6 +25,7 @@ pylint = "~2.12.2" web3 = "~5.28.0" psutil = "~5.9.1" jsonschema = "~3.2.0" +pytest-xdist = "~2.5.0" [build-system] requires = ["poetry-core>=1.0.0"] diff --git a/test/settings.py b/test/settings.py index 1cfc78c4c..3340be1d6 100644 --- a/test/settings.py +++ b/test/settings.py @@ -1,11 +1,16 @@ """Constants used in test files.""" +import socket + +def bind_free_port(host): + """return assigned free port and test base endpoint""" + sock = socket.socket() + sock.bind(("", 0)) + port = str(sock.getsockname()[1]) + return port, f"http://{host}:{port}" + HOST = "127.0.0.1" -PORT = "5050" -L1_HOST = "localhost" -L1_PORT = "8545" +PORT, APP_URL = bind_free_port(HOST) -APP_URL = f"http://{HOST}:{PORT}" -GATEWAY_URL = APP_URL -FEEDER_GATEWAY_URL = APP_URL -L1_URL = f"http://{L1_HOST}:{L1_PORT}/" +L1_HOST = "localhost" +L1_PORT, L1_URL = bind_free_port(L1_HOST) diff --git a/test/test_account.py b/test/test_account.py index 619c80029..c159deddb 100644 --- a/test/test_account.py +++ b/test/test_account.py @@ -1,7 +1,7 @@ """ Test account functionality. """ -from test.settings import GATEWAY_URL +from test.settings import APP_URL import json import requests @@ -54,7 +54,7 @@ def deploy_events_contract(): def get_account_balance(address: str) -> int: """Get balance (wei) of account with `address` (hex).""" - resp = requests.get(f"{GATEWAY_URL}/account_balance?address={address}") + resp = requests.get(f"{APP_URL}/account_balance?address={address}") assert resp.status_code == 200 return int(resp.json()["amount"]) @@ -239,7 +239,7 @@ def test_multicall(): def estimate_fee_local(req_dict: dict): """Estimate fee of a given transaction""" return requests.post( - f"{GATEWAY_URL}/feeder_gateway/estimate_fee", + f"{APP_URL}/feeder_gateway/estimate_fee", json=req_dict ) diff --git a/test/test_account_predeployed.py b/test/test_account_predeployed.py index 7a150fcd9..95cf0b081 100644 --- a/test/test_account_predeployed.py +++ b/test/test_account_predeployed.py @@ -1,15 +1,13 @@ """Predeployed account tests""" -from test.settings import GATEWAY_URL - import pytest import requests from starkware.starknet.core.os.class_hash import compute_class_hash - from starknet_devnet.account import Account from .util import assert_equal, devnet_in_background from .support.assertions import assert_valid_schema +from .settings import APP_URL ACCOUNTS_SEED_DEVNET_ARGS = [ "--accounts", @@ -34,6 +32,6 @@ def test_precomputed_contract_hash(): @devnet_in_background(*ACCOUNTS_SEED_DEVNET_ARGS) def test_predeployed_accounts_predefined_values(): """Test if --account --seed --initial-balance return exact calculated values""" - response = requests.get(f"{GATEWAY_URL}/predeployed_accounts") + response = requests.get(f"{APP_URL}/predeployed_accounts") assert response.status_code == 200 assert_valid_schema(response.json(), "predeployed_accounts_fixed_seed.json") diff --git a/test/test_dump.py b/test/test_dump.py index fa73ddf3c..de9f8c051 100644 --- a/test/test_dump.py +++ b/test/test_dump.py @@ -11,7 +11,7 @@ import pytest from .util import call, deploy, devnet_in_background, invoke, run_devnet_in_background, terminate_and_wait -from .settings import GATEWAY_URL +from .settings import APP_URL from .shared import CONTRACT_PATH, ABI_PATH DUMP_PATH = "dump.pkl" @@ -54,17 +54,17 @@ def run_before_and_after_test(): def send_dump_request(dump_path: str=None): """Send HTTP request to trigger dumping.""" json_load = { "path": dump_path } if dump_path else None - return requests.post(f"{GATEWAY_URL}/dump", json=json_load) + return requests.post(f"{APP_URL}/dump", json=json_load) def send_load_request(load_path: str=None): """Send HTTP request to trigger loading.""" json_load = { "path": load_path } if load_path else None - return requests.post(f"{GATEWAY_URL}/load", json=json_load) + return requests.post(f"{APP_URL}/load", json=json_load) def send_error_request(): """Send HTTP request to trigger error response.""" json_body = { "dummy": "dummy_value" } - return requests.post(f"{GATEWAY_URL}/dump", json=json_body) + return requests.post(f"{APP_URL}/dump", json=json_body) def assert_dump_present(dump_path: str, sleep_seconds=2): """Assert there is a non-empty dump file.""" @@ -87,7 +87,7 @@ def dump_and_assert(dump_path: str=None): def assert_not_alive(): """Assert devnet is not alive.""" try: - requests.get(f"{GATEWAY_URL}/is_alive") + requests.get(f"{APP_URL}/is_alive") raise RuntimeError("Should have failed before this line.") except requests.exceptions.ConnectionError: pass diff --git a/test/test_endpoints.py b/test/test_endpoints.py index 7151a1305..9b386e8a1 100644 --- a/test/test_endpoints.py +++ b/test/test_endpoints.py @@ -2,15 +2,13 @@ Test endpoints directly. """ -from test.util import load_file_content -from test.settings import GATEWAY_URL - import json import requests import pytest from starknet_devnet.server import app from .util import devnet_in_background, load_file_content +from .settings import APP_URL DEPLOY_CONTENT = load_file_content("deploy.json") INVOKE_CONTENT = load_file_content("invoke.json") @@ -120,14 +118,14 @@ def test_call_with_complete_request_data(): def send_transaction_with_requests(req_dict: dict): """Sends the dict in a POST request and returns the response data.""" return requests.post( - f"{GATEWAY_URL}/gateway/add_transaction", + f"{APP_URL}/gateway/add_transaction", json=json.dumps(req_dict) ) def send_call_with_requests(req_dict: dict): """Sends the call dict in a POST request and returns the response data.""" return requests.post( - f"{GATEWAY_URL}/feeder_gateway/call_contract", + f"{APP_URL}/feeder_gateway/call_contract", json=json.dumps(req_dict) ) @@ -135,38 +133,38 @@ def get_block_number(req_dict: dict): """Get block number from request dict""" block_number = req_dict["blockNumber"] return requests.get( - f"{GATEWAY_URL}/feeder_gateway/get_block?blockNumber={block_number}" + f"{APP_URL}/feeder_gateway/get_block?blockNumber={block_number}" ) def get_transaction_trace(transaction_hash:str): """Get transaction trace from request dict""" # transactionHash return requests.get( - f"{GATEWAY_URL}/feeder_gateway/get_transaction_trace?transactionHash={transaction_hash}" + f"{APP_URL}/feeder_gateway/get_transaction_trace?transactionHash={transaction_hash}" ) def get_full_contract(contract_adress): """Get full contract class of a contract at a specific address""" return requests.get( - f"{GATEWAY_URL}/feeder_gateway/get_full_contract?contractAddress={contract_adress}" + f"{APP_URL}/feeder_gateway/get_full_contract?contractAddress={contract_adress}" ) def get_class_by_hash(class_hash: str): """Get contract class by class hash""" return requests.get( - f"{GATEWAY_URL}/feeder_gateway/get_class_by_hash?classHash={class_hash}" + f"{APP_URL}/feeder_gateway/get_class_by_hash?classHash={class_hash}" ) def get_class_hash_at(contract_address: str): """Get class hash of a contract at the provided address""" return requests.get( - f"{GATEWAY_URL}/feeder_gateway/get_class_hash_at?contractAddress={contract_address}" + f"{APP_URL}/feeder_gateway/get_class_hash_at?contractAddress={contract_address}" ) def get_state_update(block_hash, block_number): """Get state update""" return requests.get( - f"{GATEWAY_URL}/feeder_gateway/get_state_update?blockHash={block_hash}&blockNumber={block_number}" + f"{APP_URL}/feeder_gateway/get_state_update?blockHash={block_hash}&blockNumber={block_number}" ) @devnet_in_background() diff --git a/test/test_postman.py b/test/test_postman.py index aa102b784..c8e263008 100644 --- a/test/test_postman.py +++ b/test/test_postman.py @@ -6,7 +6,7 @@ import subprocess from test.web3_util import web3_call, web3_deploy, web3_transact -from test.settings import L1_HOST, L1_PORT, L1_URL, GATEWAY_URL +from test.settings import APP_URL, L1_HOST, L1_PORT, L1_URL from test.util import call, deploy, devnet_in_background, ensure_server_alive, invoke, load_file_content, terminate_and_wait import psutil @@ -49,7 +49,7 @@ def run_before_and_after_test(): def flush(): """Flushes the postman messages. Returns response data""" res = requests.post( - f"{GATEWAY_URL}/postman/flush" + f"{APP_URL}/postman/flush" ) return res.json() @@ -90,7 +90,7 @@ def init_messaging_contract(): "networkUrl": L1_URL } resp = requests.post( - f"{GATEWAY_URL}/postman/load_l1_messaging_contract", + f"{APP_URL}/postman/load_l1_messaging_contract", json=deploy_messaging_contract_request ) return json.loads(resp.text) @@ -120,7 +120,7 @@ def load_messaging_contract(starknet_messaging_contract_address): } resp = requests.post( - f"{GATEWAY_URL}/postman/load_l1_messaging_contract", + f"{APP_URL}/postman/load_l1_messaging_contract", json=load_messaging_contract_request ) @@ -276,7 +276,7 @@ def test_postman(): def load_l1_messaging_contract(req_dict: dict): """Load L1 messaging contract""" return requests.post( - f"{GATEWAY_URL}/postman/load_l1_messaging_contract", + f"{APP_URL}/postman/load_l1_messaging_contract", json=(req_dict) ) diff --git a/test/test_restart.py b/test/test_restart.py index 70256c481..a37f2a1ec 100644 --- a/test/test_restart.py +++ b/test/test_restart.py @@ -5,7 +5,7 @@ import pytest import requests -from .settings import APP_URL, FEEDER_GATEWAY_URL +from .settings import APP_URL from .util import devnet_in_background, deploy, assert_transaction_not_received, assert_tx_status, call, invoke from .shared import CONTRACT_PATH, ABI_PATH @@ -15,7 +15,7 @@ def restart(): def get_state_update(): """Get state update""" - res = requests.get(f"{FEEDER_GATEWAY_URL}/feeder_gateway/get_state_update") + res = requests.get(f"{APP_URL}/feeder_gateway/get_state_update") return res.json() diff --git a/test/test_state_update.py b/test/test_state_update.py index f6a001db0..a0e7d215a 100644 --- a/test/test_state_update.py +++ b/test/test_state_update.py @@ -11,7 +11,7 @@ from .util import ( deploy, invoke, load_contract_class, devnet_in_background, get_block, assert_equal ) -from .settings import FEEDER_GATEWAY_URL +from .settings import APP_URL from .shared import STORAGE_CONTRACT_PATH, STORAGE_ABI_PATH STORAGE_KEY = hex(get_selector_from_name("storage")) @@ -24,7 +24,7 @@ def get_state_update_response(block_hash=None, block_number=None): } res = requests.get( - f"{FEEDER_GATEWAY_URL}/feeder_gateway/get_state_update", + f"{APP_URL}/feeder_gateway/get_state_update", params=params ) diff --git a/test/test_transaction_trace.py b/test/test_transaction_trace.py index c955a7fcb..88410f500 100644 --- a/test/test_transaction_trace.py +++ b/test/test_transaction_trace.py @@ -6,7 +6,7 @@ import requests from .util import deploy, invoke, load_json_from_path, devnet_in_background -from .settings import FEEDER_GATEWAY_URL +from .settings import APP_URL from .shared import ABI_PATH, CONTRACT_PATH, SIGNATURE, NONEXISTENT_TX_HASH def get_transaction_trace_response(tx_hash=None): @@ -16,7 +16,7 @@ def get_transaction_trace_response(tx_hash=None): } res = requests.get( - f"{FEEDER_GATEWAY_URL}/feeder_gateway/get_transaction_trace", + f"{APP_URL}/feeder_gateway/get_transaction_trace", params=params ) diff --git a/test/util.py b/test/util.py index c1d7b1779..53828b7b7 100644 --- a/test/util.py +++ b/test/util.py @@ -12,7 +12,7 @@ from starkware.starknet.services.api.contract_class import ContractClass from starknet_devnet.general_config import DEFAULT_GENERAL_CONFIG -from .settings import GATEWAY_URL, FEEDER_GATEWAY_URL, HOST, PORT, APP_URL +from .settings import HOST, PORT, APP_URL class ReturnCodeAssertionError(AssertionError): """Error to be raised when the return code of an executed process is not as expected.""" @@ -115,8 +115,8 @@ def run_starknet(args, raise_on_nonzero=True, add_gateway_urls=True): my_args = ["poetry", "run", "starknet", *args] if add_gateway_urls: my_args.extend([ - "--gateway_url", GATEWAY_URL, - "--feeder_gateway_url", FEEDER_GATEWAY_URL + "--gateway_url", APP_URL, + "--feeder_gateway_url", APP_URL ]) output = subprocess.run(my_args, encoding="utf-8", check=False, capture_output=True) if output.returncode != 0 and raise_on_nonzero: