From 4f97f31508fe129c11e0edddb9eb0b567a9f7f04 Mon Sep 17 00:00:00 2001 From: Francis CLAIRICIA-ROSE-CLAIRE-JOSEPHINE Date: Fri, 18 Oct 2024 07:58:27 +0200 Subject: [PATCH] [WIP] CI: Add tests on FreeBSD --- .github/workflows/test.yml | 36 +++++++++++++++++++ pdm.lock | 14 ++++---- pyproject.toml | 4 ++- .../test_communication/conftest.py | 12 +++++-- .../test_async/test_client/test_udp.py | 4 +-- .../test_async/test_server/test_tcp.py | 1 + .../test_sync/test_client/test_udp.py | 4 +-- tests/tools.py | 5 +++ tox.ini | 14 +++++--- 9 files changed, 76 insertions(+), 18 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index fc3c6476..d8fe1830 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -108,3 +108,39 @@ jobs: test-functional, OS-${{ runner.os }}, Py-${{ matrix.python_version }} + + test-freebsd: + # TODO: Add this when the workflow is stable. + # if: | + # (github.event_name != 'push' || !startsWith(github.event.head_commit.message, 'Bump version:')) + # && (github.event_name != 'pull_request' || (github.event.pull_request.draft != true && !contains(github.event.pull_request.labels.*.name, 'pr-skip-test'))) + runs-on: ubuntu-24.04 + + name: test (FreeBSD, 3.11) + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Launch tests + # Add 10 minutes timeout because some wheels are long to build + timeout-minutes: 30 + uses: vmactions/freebsd-vm@v1 + with: + release: '14.1' + # py311-sqlite3 is needed for coverage.py + prepare: | + set -e + pkg install -y curl git python311 py311-sqlite3 + curl -sSL https://pdm-project.org/install-pdm.py | python3.11 - --version=2.19.3 --path=/usr/local + pdm config check_update false + pdm config install.cache true + run: | + pdm install --frozen-lockfile --global --project=. --no-self --no-default --dev --group=tox + tox --version + tox run -f py311 -vv + tox run -f coverage + rm -rf .tox + find . -name '__pycache__' | xargs rm -rf + - name: Check files in workspace + if: always() + run: ls -lA diff --git a/pdm.lock b/pdm.lock index 7ec20233..e8cc49c9 100644 --- a/pdm.lock +++ b/pdm.lock @@ -2,10 +2,10 @@ # It is not intended for manual editing. [metadata] -groups = ["default", "bandit", "benchmark-servers", "benchmark-servers-deps", "build", "cbor", "coverage", "dev", "doc", "flake8", "format", "micro-benchmark", "msgpack", "mypy", "pre-commit", "test", "test-trio", "tox", "trio", "types-msgpack", "uvloop"] +groups = ["default", "bandit", "benchmark-servers", "benchmark-servers-deps", "build", "cbor", "coverage", "dev", "doc", "flake8", "format", "micro-benchmark", "msgpack", "mypy", "pre-commit", "test", "test-ssl", "test-trio", "tox", "trio", "types-msgpack", "uvloop"] strategy = ["inherit_metadata"] lock_version = "4.5.0" -content_hash = "sha256:eb460b604f68d10abee577441ab768d16ae2aa47c4569b5c9daef982b27a4c4f" +content_hash = "sha256:7838a09608f8525cc33ede511d78e3065055c3ab0a035b0f4f7ce53635d6d485" [[metadata.targets]] requires_python = ">=3.11" @@ -317,7 +317,7 @@ name = "cffi" version = "1.17.1" requires_python = ">=3.8" summary = "Foreign Function Interface for Python calling C code." -groups = ["benchmark-servers-deps", "dev", "test", "test-trio", "trio"] +groups = ["benchmark-servers-deps", "dev", "test-ssl", "test-trio", "trio"] dependencies = [ "pycparser", ] @@ -573,7 +573,7 @@ name = "cryptography" version = "43.0.1" requires_python = ">=3.7" summary = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." -groups = ["dev", "test"] +groups = ["dev", "test-ssl"] dependencies = [ "cffi>=1.12; platform_python_implementation != \"PyPy\"", ] @@ -906,7 +906,7 @@ name = "idna" version = "3.10" requires_python = ">=3.6" summary = "Internationalized Domain Names in Applications (IDNA)" -groups = ["benchmark-servers", "benchmark-servers-deps", "dev", "doc", "test", "test-trio", "trio"] +groups = ["benchmark-servers", "benchmark-servers-deps", "dev", "doc", "test-ssl", "test-trio", "trio"] files = [ {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, @@ -1414,7 +1414,7 @@ name = "pycparser" version = "2.22" requires_python = ">=3.8" summary = "C parser in Python" -groups = ["benchmark-servers-deps", "dev", "test", "test-trio", "trio"] +groups = ["benchmark-servers-deps", "dev", "test-ssl", "test-trio", "trio"] files = [ {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, @@ -2218,7 +2218,7 @@ name = "trustme" version = "1.2.0" requires_python = ">=3.9" summary = "#1 quality TLS certs while you wait, for the discerning tester" -groups = ["test"] +groups = ["test-ssl"] dependencies = [ "cryptography>=3.1", "idna>=2.0", diff --git a/pyproject.toml b/pyproject.toml index 62c2382b..8b40c050 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -106,11 +106,13 @@ test = [ "pytest-cov~=5.0", "pytest-asyncio~=0.24.0", "trove-classifiers>=2023.11.9", - "trustme~=1.0", # "pytest-retry~=1.6", # Temporary use VCS to get the modifications added on main (c.f. https://github.com/str0zzapreti/pytest-retry/pull/39) "pytest-retry @ git+https://github.com/str0zzapreti/pytest-retry.git@bb465fff6f01f3f90a77229468f7e08a3bdbce20", ] +test-ssl = [ + "trustme~=1.0", +] test-trio = [ "pytest-trio~=0.8.0", ] diff --git a/tests/functional_test/test_communication/conftest.py b/tests/functional_test/test_communication/conftest.py index 61169a3f..dd55706a 100644 --- a/tests/functional_test/test_communication/conftest.py +++ b/tests/functional_test/test_communication/conftest.py @@ -5,15 +5,18 @@ from contextlib import ExitStack from functools import partial from socket import AF_INET, AF_INET6, SOCK_DGRAM, SOCK_STREAM, has_ipv6 as HAS_IPV6, socket as Socket -from typing import Any +from typing import TYPE_CHECKING, Any from easynetwork.protocol import AnyStreamProtocolType, BufferedStreamProtocol, DatagramProtocol, StreamProtocol import pytest -import trustme from .serializer import BadSerializeStringSerializer, NotGoodStringSerializer, StringSerializer +if TYPE_CHECKING: + import trustme + + _FAMILY_TO_LOCALHOST: dict[int, str] = { AF_INET: "127.0.0.1", AF_INET6: "::1", @@ -123,6 +126,11 @@ def socket_pair(localhost_ip: str, tcp_socket_factory: Callable[[], Socket]) -> @pytest.fixture(scope="session") def ssl_certificate_authority() -> trustme.CA: + try: + import trustme + except ModuleNotFoundError: + pytest.skip("trustme is not installed") + return trustme.CA() diff --git a/tests/functional_test/test_communication/test_async/test_client/test_udp.py b/tests/functional_test/test_communication/test_async/test_client/test_udp.py index 08c89589..9db25d49 100644 --- a/tests/functional_test/test_communication/test_async/test_client/test_udp.py +++ b/tests/functional_test/test_communication/test_async/test_client/test_udp.py @@ -87,7 +87,7 @@ async def test____send_packet____default(self, client: AsyncUDPNetworkClient[str async with asyncio.timeout(3): assert await server.recvfrom() == (b"ABCDEF", client.get_local_address()) - @PlatformMarkers.runs_only_on_platform("linux", "Windows and MacOS do not raise error") + @PlatformMarkers.runs_only_on_platform("linux", "Windows, MacOS and BSD-like do not raise error") async def test____send_packet____connection_refused( self, client: AsyncUDPNetworkClient[str, str], @@ -97,7 +97,7 @@ async def test____send_packet____connection_refused( with pytest.raises(ConnectionRefusedError): await client.send_packet("ABCDEF") - @PlatformMarkers.runs_only_on_platform("linux", "Windows and MacOS do not raise error") + @PlatformMarkers.runs_only_on_platform("linux", "Windows, MacOS and BSD-like do not raise error") async def test____send_packet____connection_refused____after_previous_successful_try( self, client: AsyncUDPNetworkClient[str, str], diff --git a/tests/functional_test/test_communication/test_async/test_server/test_tcp.py b/tests/functional_test/test_communication/test_async/test_server/test_tcp.py index 1dfa6c64..512fbf19 100644 --- a/tests/functional_test/test_communication/test_async/test_server/test_tcp.py +++ b/tests/functional_test/test_communication/test_async/test_server/test_tcp.py @@ -638,6 +638,7 @@ async def test____serve_forever____accept_client____client_sent_RST_packet_right await asyncio.sleep(0.1) # On Linux: ENOTCONN error should not create a big Traceback error + # On BSD: ECONNABORTED error on accept() should not create a big Traceback error assert len(caplog.records) == 0 async def test____serve_forever____client_extra_attributes( diff --git a/tests/functional_test/test_communication/test_sync/test_client/test_udp.py b/tests/functional_test/test_communication/test_sync/test_client/test_udp.py index 549f5309..2073793e 100644 --- a/tests/functional_test/test_communication/test_sync/test_client/test_udp.py +++ b/tests/functional_test/test_communication/test_sync/test_client/test_udp.py @@ -58,13 +58,13 @@ def test____send_packet____default(self, client: UDPNetworkClient[str, str], ser client.send_packet("ABCDEF") assert server.recvfrom(1024) == (b"ABCDEF", client.get_local_address()) - @PlatformMarkers.runs_only_on_platform("linux", "Windows and MacOS do not raise error") + @PlatformMarkers.runs_only_on_platform("linux", "Windows, MacOS and BSD-like do not raise error") def test____send_packet____connection_refused(self, client: UDPNetworkClient[str, str], server: Socket) -> None: server.close() with pytest.raises(ConnectionRefusedError): client.send_packet("ABCDEF") - @PlatformMarkers.runs_only_on_platform("linux", "Windows and MacOS do not raise error") + @PlatformMarkers.runs_only_on_platform("linux", "Windows, MacOS and BSD-like do not raise error") def test____send_packet____connection_refused____after_previous_successful_try( self, client: UDPNetworkClient[str, str], diff --git a/tests/tools.py b/tests/tools.py index 230d7de2..a3dadb71 100644 --- a/tests/tools.py +++ b/tests/tools.py @@ -52,9 +52,14 @@ def skipif_platform_macOS_because(reason: str, *, skip_only_on_ci: bool = False) def skipif_platform_linux_because(reason: str, *, skip_only_on_ci: bool = False) -> pytest.MarkDecorator: return _make_skipif_platform("linux", reason, skip_only_on_ci=skip_only_on_ci) + @staticmethod + def skipif_platform_bsd_because(reason: str, *, skip_only_on_ci: bool = False) -> pytest.MarkDecorator: + return _make_skipif_platform(("freebsd", "openbsd", "netbsd"), reason, skip_only_on_ci=skip_only_on_ci) + skipif_platform_win32 = skipif_platform_win32_because("cannot run on Windows") skipif_platform_macOS = skipif_platform_macOS_because("cannot run on MacOS") skipif_platform_linux = skipif_platform_linux_because("cannot run on Linux") + skipif_platform_bsd = skipif_platform_bsd_because("Cannot run on BSD-related platforms (e.g. FreeBSD)") ###### RESTRICT TESTS FOR PLATFORMS ###### diff --git a/tox.ini b/tox.ini index a505b214..83860368 100644 --- a/tox.ini +++ b/tox.ini @@ -67,11 +67,15 @@ commands = docstrings: pytest --doctest-modules {posargs} {[docs]examples_dir}{/}tutorials{/}ftp_server docstrings: pytest --doctest-glob="*.rst" {posargs} {[docs]source_dir} -[testenv:{py311,py312,py313}-{unit,functional}-{standard,cbor,msgpack,trio}] +[testenv:{py311,py312,py313}-{unit,functional}-{standard,cbor,msgpack,trio}{,-bsd}] package = wheel wheel_build_env = {[base]wheel_build_env} +platform = + !bsd: ((?!bsd).)* + bsd: (free|open|net)bsd.* groups = test + functional-standard-!bsd: test-ssl coverage cbor: cbor msgpack: msgpack @@ -96,7 +100,7 @@ commands = msgpack: pytest -m "feature_msgpack" {posargs} {env:TESTS_ROOTDIR} trio: pytest -n "{env:PYTEST_MAX_WORKERS:auto}" -m "feature_trio" {posargs} {env:TESTS_ROOTDIR} -[testenv:{py311,py312,py313}-functional-{asyncio_proactor,uvloop}] +[testenv:{py311,py312,py313}-functional-{asyncio_proactor,uvloop}{,-bsd}] package = wheel wheel_build_env = {[base]wheel_build_env} platform = @@ -104,6 +108,7 @@ platform = uvloop: linux|darwin groups = test + test-ssl coverage uvloop: uvloop setenv = @@ -125,8 +130,8 @@ commands = [testenv:coverage-{unit,functional,full}] skip_install = true depends = - unit: {py311,py312,py313}-unit-{standard,cbor,msgpack,trio} - functional: {py311,py312,py313}-functional-{standard,cbor,msgpack,trio,asyncio_proactor,uvloop} + unit: {py311,py312,py313}-unit-{standard,cbor,msgpack,trio}{,-bsd} + functional: {py311,py312,py313}-functional-{standard,cbor,msgpack,trio,asyncio_proactor,uvloop}{,-bsd} full: coverage-{unit,functional} parallel_show_output = full: True @@ -181,6 +186,7 @@ platform = groups = mypy test: test + test: test-ssl full,test,micro_benchmarks: cbor full,test,micro_benchmarks: msgpack full,test,micro_benchmarks: types-msgpack