From 70ce60414a8b7c086fbb4fa4acb5a54b8af3d74e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francis=20Clairicia-Rose-Claire-Jos=C3=A9phine?= Date: Sat, 15 Jun 2024 17:11:24 +0200 Subject: [PATCH] Project's development setup described (#297) --- .gitignore | 1 + .pre-commit-config.yaml | 44 ------------ .vscode/extensions.json | 10 +++ DEVELOPMENT.md | 86 ++++++++++++++++++++++++ benchmark_server/run_benchmark | 6 ++ pdm.toml | 5 ++ pyproject.toml | 4 +- tests/conftest.py | 15 +++-- tests/pytest_plugins/xdist_for_vscode.py | 18 +++++ tox.ini | 22 +++--- 10 files changed, 148 insertions(+), 63 deletions(-) create mode 100644 .vscode/extensions.json create mode 100644 DEVELOPMENT.md create mode 100644 pdm.toml create mode 100644 tests/pytest_plugins/xdist_for_vscode.py diff --git a/.gitignore b/.gitignore index e365fff4..c59ba97c 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ src/easynetwork/version.py # Benchmark reports +.benchmarks benchmark_reports benchmark*.json benchmark*.html diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a1d44195..b09de2db 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,7 +3,6 @@ default_language_version: minimum_pre_commit_version: '2.20.0' ci: - skip: [mypy] autoupdate_branch: 'pre-commit/autoupdate' autoupdate_schedule: quarterly @@ -68,46 +67,3 @@ repos: - id: check-json - id: check-toml - id: check-yaml - - repo: local - hooks: - - id: mypy - name: mypy (project) - entry: tox run -q -e mypy-full - language: system - files: ^(src/) - types_or: [python, pyi] - require_serial: true - pass_filenames: false - - id: mypy - name: mypy (tests) - files: ^((src|tests)/) - entry: tox run -q -e mypy-test - language: system - types_or: [python, pyi] - require_serial: true - pass_filenames: false - - id: mypy - name: mypy (docs) - files: ^((src|docs/source)/) - exclude: ^(docs/source/conf.py)$ - entry: tox run -q -e mypy-docs - language: system - types_or: [python, pyi] - require_serial: true - pass_filenames: false - - id: mypy - name: mypy (benchmark/servers) - files: ^((src|benchmark_server)/) - entry: tox run -q -e mypy-benchmark_server - language: system - types_or: [python, pyi] - require_serial: true - pass_filenames: false - - id: mypy - name: mypy (benchmark/micro-benchs) - files: ^((src|micro_benchmarks)/) - entry: tox run -q -e mypy-micro_benchmarks - language: system - types_or: [python, pyi] - require_serial: true - pass_filenames: false diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 00000000..b12d6af0 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,10 @@ +{ + "recommendations": [ + "ms-python.black-formatter", + "ms-python.flake8", + "ms-python.isort", + "ms-python.mypy-type-checker", + "ms-python.python", + "ms-python.vscode-pylance" + ] +} diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md new file mode 100644 index 00000000..60f400b1 --- /dev/null +++ b/DEVELOPMENT.md @@ -0,0 +1,86 @@ +# Development Environment + +## Project Setup + +### System Requirements + +- CPython >= 3.11.0 +- [PDM](https://pdm-project.org/latest/#installation) >= 2.15 + +### Virtual environment + +#### Option 1: Use an already created virtual environment via `pyenv-virtualenv` + +```sh +# Create the virtual environment +pyenv virtualenv 3.11 easynetwork-3.11 + +# Set the local python (3.12 and upward are still needed for tox) +pyenv local "easynetwork-3.11" 3.12 + +# Tell pdm to use your virtualenv +pdm use -f $VIRTUAL_ENV +# -> Using Python interpreter: /path/to/.pyenv/versions/3.11.x/envs/easynetwork-3.11/bin/python3 (3.11) +``` + +#### Option 2: Let `pdm` create a `.venv` folder + +0. For the `pyenv` users, set the local python : +```sh +pyenv local 3.11 3.12 +``` + +1. Create the virtual environment : +```sh +# Creates the virtual environment ( in .venv directory ) +pdm venv create 3.11 + +# Tell pdm to use this virtualenv +pdm use --venv in-project +``` + +2. Activate the virtual environment in the current shell using either : + - the [manual way](https://docs.python.org/3.11/library/venv.html#how-venvs-work) + - the [pdm venv CLI tool](https://pdm-project.org/latest/usage/venv/#activate-a-virtualenv) + +### Installation + +1. Install the project with its dependencies and development tools : +```sh +pdm install -G:all +``` + +2. If it is a clone of the `git` project, run : +```sh +pre-commit install +``` + +3. Check the installation : +```sh +# Run pre-commit hooks +pre-commit run --all-files + +# Run mypy against all the project +tox run -q -f mypy +``` + +### Configure the IDE + +#### Visual Studio Code + +1. The recommended extensions are in [.vscode/extensions.json](.vscode/extensions.json) + +2. Copy [.vscode/settings.example.json](.vscode/settings.example.json) to `.vscode/settings.json` + +3. (Optional) To enable VS code's integrated testing tool, add this in your `settings.json`: +```json +{ + "python.testing.unittestEnabled": false, + "python.testing.pytestEnabled": true, + "python.testing.pytestArgs": [ + "-n", + "auto" + ] +} +``` +> :warning: **NEVER** run all the test suite with VS code integrated testing tool ! There are 8000+ tests. diff --git a/benchmark_server/run_benchmark b/benchmark_server/run_benchmark index a67e44f7..1cb78094 100755 --- a/benchmark_server/run_benchmark +++ b/benchmark_server/run_benchmark @@ -699,6 +699,12 @@ def main() -> None: html_output_file: Path | None = args.save_html json_output_file: Path | None = args.save_json report_file_with_date: bool = args.report_file_with_date + if html_output_file: + html_output_file = html_output_file.absolute() + html_output_file.parent.mkdir(parents=True, exist_ok=True) + if json_output_file: + json_output_file = json_output_file.absolute() + json_output_file.parent.mkdir(parents=True, exist_ok=True) variations: list[_BenchmarkVariationDef] = [ { diff --git a/pdm.toml b/pdm.toml new file mode 100644 index 00000000..5d1671f3 --- /dev/null +++ b/pdm.toml @@ -0,0 +1,5 @@ +[python] +use_venv = true + +[venv] +backend = "venv" diff --git a/pyproject.toml b/pyproject.toml index 0af66802..6f77dc9f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,8 +21,6 @@ classifiers = [ "Operating System :: Microsoft :: Windows", "Operating System :: POSIX :: BSD", "Operating System :: POSIX :: Linux", - "Programming Language :: Python", - "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", @@ -227,7 +225,7 @@ ignore_missing_imports = true [tool.pytest.ini_options] asyncio_mode = "strict" # Avoid some unwanted behaviour -addopts = "--strict-markers -p 'no:anyio' -p 'no:benchmark'" # hatch CLI dependencies installs anyio +addopts = "--dist=worksteal --strict-markers -p 'no:anyio' -p 'no:benchmark'" # hatch CLI dependencies installs anyio minversion = "7.1.2" testpaths = ["tests"] norecursedirs = ["scripts"] diff --git a/tests/conftest.py b/tests/conftest.py index f09947a6..9e6032d1 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,14 +4,19 @@ import random from typing import TYPE_CHECKING +import pytest + random.seed(42) # Fully deterministic random output -def pytest_report_header() -> list[str]: +def pytest_report_header(config: pytest.Config) -> list[str]: + headers: list[str] = [] addopts: str = os.environ.get("PYTEST_ADDOPTS", "") - if not addopts: - return [] - return [f"PYTEST_ADDOPTS: {addopts}"] + if addopts: + headers.append(f"PYTEST_ADDOPTS: {addopts}") + if config.pluginmanager.has_plugin("xdist") and config.getoption("numprocesses", 0): + headers.append(f"distribution: {config.getoption('dist', 'no')}") + return headers PYTEST_PLUGINS_PACKAGE = f"{__package__}.pytest_plugins" @@ -23,6 +28,7 @@ def pytest_report_header() -> list[str]: f"{PYTEST_PLUGINS_PACKAGE}.auto_markers", f"{PYTEST_PLUGINS_PACKAGE}.extra_features", f"{PYTEST_PLUGINS_PACKAGE}.ssl_module", + f"{PYTEST_PLUGINS_PACKAGE}.xdist_for_vscode", ] if TYPE_CHECKING: @@ -34,4 +40,5 @@ def pytest_report_header() -> list[str]: auto_markers as auto_markers, extra_features as extra_features, ssl_module as ssl_module, + xdist_for_vscode as xdist_for_vscode, ) diff --git a/tests/pytest_plugins/xdist_for_vscode.py b/tests/pytest_plugins/xdist_for_vscode.py new file mode 100644 index 00000000..2f2eb25c --- /dev/null +++ b/tests/pytest_plugins/xdist_for_vscode.py @@ -0,0 +1,18 @@ +from __future__ import annotations + +from collections.abc import Generator + +import pytest + + +@pytest.hookimpl(wrapper=True) +def pytest_xdist_auto_num_workers(config: pytest.Config) -> Generator[None, int, int]: + """determine how many workers to use based on how many tests were selected in the test explorer""" + num_workers = yield + if "vscode_pytest" in config.option.plugins: + nb_launched_tests = len(config.option.file_or_dir) + if nb_launched_tests == 1: + # "0" means no workers + return 0 + return min(num_workers, nb_launched_tests) + return num_workers diff --git a/tox.ini b/tox.ini index b27ccd48..cf561af6 100644 --- a/tox.ini +++ b/tox.ini @@ -8,10 +8,10 @@ envlist = build # Tests (3.11) py311-other-{tests,docstrings} - py311-{unit,functional}-{__standard__,cbor,msgpack} + py311-{unit,functional}-{standard,cbor,msgpack} py311-functional-{sniffio,asyncio_proactor,uvloop} # Tests (3.12) - py312-{unit,functional}-{__standard__,cbor,msgpack} + py312-{unit,functional}-{standard,cbor,msgpack} py312-functional-{sniffio,asyncio_proactor,uvloop} # Report coverage @@ -37,7 +37,6 @@ setenv = addopts = -p "no:cacheprovider" {tty:--color=yes} unit_tests_rootdir = tests{/}unit_test functional_tests_rootdir = tests{/}functional_test -xdist_dist = worksteal cov_opts = --cov --cov-report='' [testenv:py311-other-{tests,docstrings}] @@ -58,7 +57,7 @@ commands = docstrings: pytest --doctest-modules {posargs} {[docs]examples_dir}{/}tutorials{/}ftp_server docstrings: pytest --doctest-glob="*.rst" {posargs} {[docs]source_dir} -[testenv:{py311,py312}-{unit,functional}-{__standard__,cbor,msgpack}] +[testenv:{py311,py312}-{unit,functional}-{standard,cbor,msgpack}] package = wheel wheel_build_env = {[base]wheel_build_env} groups = @@ -78,7 +77,7 @@ setenv = passenv = PYTEST_MAX_WORKERS commands = - __standard__: pytest -n "{env:PYTEST_MAX_WORKERS:auto}" --dist={[pytest-conf]xdist_dist} -m "not feature" {posargs} {env:TESTS_ROOTDIR} + standard: pytest -n "{env:PYTEST_MAX_WORKERS:auto}" -m "not feature" {posargs} {env:TESTS_ROOTDIR} cbor: pytest -m "feature_cbor" {posargs} {env:TESTS_ROOTDIR} msgpack: pytest -m "feature_msgpack" {posargs} {env:TESTS_ROOTDIR} @@ -123,12 +122,12 @@ setenv = passenv = PYTEST_MAX_WORKERS commands = - pytest -n "{env:PYTEST_MAX_WORKERS:auto}" --dist={[pytest-conf]xdist_dist} --asyncio-event-loop="{env:ASYNCIO_EVENTLOOP}" -m "asyncio and not feature" {posargs} {env:TESTS_ROOTDIR} + pytest -n "{env:PYTEST_MAX_WORKERS:auto}" --asyncio-event-loop="{env:ASYNCIO_EVENTLOOP}" -m "asyncio and not feature" {posargs} {env:TESTS_ROOTDIR} [testenv:coverage] skip_install = true depends = - {py311,py312}-{unit,functional}-{__standard__,cbor,msgpack} + {py311,py312}-{unit,functional}-{standard,cbor,msgpack} {py311,py312}-functional-{sniffio,asyncio_proactor,uvloop} parallel_show_output = True groups = @@ -202,7 +201,6 @@ groups = pre-commit setenv = {[base]setenv} - SKIP=mypy passenv = PRE_COMMIT_HOME XDG_CACHE_HOME @@ -213,7 +211,7 @@ commands = package = wheel wheel_build_env = {[base]wheel_build_env} groups = - micro-benchmarks + micro-benchmark cbor msgpack setenv = @@ -222,7 +220,7 @@ setenv = PYTHONHASHSEED = 0 PYTEST_ADDOPTS = {[pytest-conf]addopts} commands = - pytest -c pytest-benchmark.ini {posargs:--benchmark-histogram=benchmark_reports{/}benchmark} + pytest -c pytest-benchmark.ini {posargs:--benchmark-histogram=benchmark_reports{/}micro_benches{/}benchmark} [testenv:benchmark-server-{tcpecho,sslecho,readline,udpecho}] skip_install = true @@ -244,8 +242,8 @@ setenv = readline: BENCHMARK_PATTERN = ^readline # Report files - BENCHMARK_REPORT_JSON = {toxinidir}{/}benchmark_reports{/}{envname}-{env:BENCHMARK_PYTHON_VERSION}-report.json - BENCHMARK_REPORT_HTML = {toxinidir}{/}benchmark_reports{/}{envname}-{env:BENCHMARK_PYTHON_VERSION}-report.html + BENCHMARK_REPORT_JSON = {toxinidir}{/}benchmark_reports{/}json{/}{envname}-{env:BENCHMARK_PYTHON_VERSION}-report.json + BENCHMARK_REPORT_HTML = {toxinidir}{/}benchmark_reports{/}html{/}{envname}-{env:BENCHMARK_PYTHON_VERSION}-report.html passenv = BENCHMARK_PYTHON_VERSION DOCKER_HOST