From 60dc6e403146e76fae18e4f0c6635619f89e0abf Mon Sep 17 00:00:00 2001 From: Pedro Brochado Date: Mon, 9 Mar 2026 16:30:37 -0300 Subject: [PATCH 1/3] Pin uvloop<0.22 and add sanity test for it 0.22.1 is known to broke some existing behavior. Maybe there is a fix in our code, but we'll wait if they can revert it. See . Closes: #7213 --- .../workflows/scripts/post_before_script.sh | 8 +++ .github/workflows/scripts/test_uvloop.sh | 62 +++++++++++++++++++ CHANGES/7213.bugfix | 1 + pyproject.toml | 2 +- 4 files changed, 72 insertions(+), 1 deletion(-) create mode 100755 .github/workflows/scripts/post_before_script.sh create mode 100755 .github/workflows/scripts/test_uvloop.sh create mode 100644 CHANGES/7213.bugfix diff --git a/.github/workflows/scripts/post_before_script.sh b/.github/workflows/scripts/post_before_script.sh new file mode 100755 index 0000000000..763d0c7efa --- /dev/null +++ b/.github/workflows/scripts/post_before_script.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +set -eu -o pipefail + +# make sure this script runs at the repo root +cd "$(dirname "$(realpath -e "$0")")"/../../.. + +source .github/workflows/scripts/test_uvloop.sh diff --git a/.github/workflows/scripts/test_uvloop.sh b/.github/workflows/scripts/test_uvloop.sh new file mode 100755 index 0000000000..c2b96a16f5 --- /dev/null +++ b/.github/workflows/scripts/test_uvloop.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash +# Test that pulpcore can be installed with uvloop and that pulpcore-content starts correctly + +set -eu -o pipefail + +UVLOOP_TMP="$(mktemp -d)" +UVLOOP_SETTINGS_FILE="${UVLOOP_TMP}/settings.py" +export PULP_DJANGO_SETTINGS_FILE="${UVLOOP_SETTINGS_FILE}" + +function setup-venv(){ + python3 -m venv "${UVLOOP_TMP}/venv" +} +function run-pip(){ + "${UVLOOP_TMP}/venv/bin/pip" "$@" +} +function print-title(){ + echo "" + echo "=== $* ===" +} + +# Configuration +print-title "Creating configuration" +openssl rand -base64 32 > "${UVLOOP_TMP}/symmetric.key" +cat > "${UVLOOP_SETTINGS_FILE}" << EOF +UVLOOP_ENABLED = True +FILE_UPLOAD_TEMP_DIR = "${UVLOOP_TMP}" +WORKING_DIRECTORY = "${UVLOOP_TMP}" +DB_ENCRYPTION_KEY = "${UVLOOP_TMP}/symmetric.key" +EOF + +echo "::group::Configuration" +cat "${UVLOOP_SETTINGS_FILE}" +echo "::endgroup::" + +# Install +print-title "Installing pulpcore with uvloop" +setup-venv +run-pip install --upgrade pip +run-pip install "${PWD}[uvloop]" setuptools -c ".ci/assets/ci_constraints.txt" + +echo "::group::Uvloop version" +run-pip show uvloop +echo "::endgroup::" + +# Sanity test +print-title "Starting pulpcore-content with uvloop" +if timeout 5s "${UVLOOP_TMP}/venv/bin/pulpcore-content"; then + echo "ERROR: pulpcore-content exited prematurely with exit code 0" + exit 1 +else + EXIT_CODE=$? + if [ $EXIT_CODE -eq 124 ]; then + echo "pulpcore-content started successfully and ran for 5 seconds" + else + echo "ERROR: pulpcore-content exited prematurely with exit code $EXIT_CODE" + exit 1 + fi +fi + +# Cleanup +rm -rf "${UVLOOP_TMP}" +echo "Passed sanity checking!" diff --git a/CHANGES/7213.bugfix b/CHANGES/7213.bugfix new file mode 100644 index 0000000000..1fc13c599a --- /dev/null +++ b/CHANGES/7213.bugfix @@ -0,0 +1 @@ +Pinned uvloop (optional dependency) to prevent a known bug in a newer releaes. diff --git a/pyproject.toml b/pyproject.toml index 34de4905a4..654829a1b9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -79,7 +79,7 @@ kafka = [ "confluent-kafka>=2.4.0,<2.14.0", ] diagnostics = ["pyinstrument~=5.0", "memray~=1.17"] -uvloop = ["uvloop>=0.20,<0.23"] +uvloop = ["uvloop>=0.20,<0.22"] [project.urls] Homepage = "https://pulpproject.org" From 15c9823ff797582813ae811c3e3a215ed7c1d784 Mon Sep 17 00:00:00 2001 From: Pedro Brochado Date: Wed, 11 Mar 2026 16:40:30 -0300 Subject: [PATCH 2/3] Remove shell-based tests --- .../workflows/scripts/post_before_script.sh | 8 --- .github/workflows/scripts/test_uvloop.sh | 62 ------------------- 2 files changed, 70 deletions(-) delete mode 100755 .github/workflows/scripts/post_before_script.sh delete mode 100755 .github/workflows/scripts/test_uvloop.sh diff --git a/.github/workflows/scripts/post_before_script.sh b/.github/workflows/scripts/post_before_script.sh deleted file mode 100755 index 763d0c7efa..0000000000 --- a/.github/workflows/scripts/post_before_script.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env bash - -set -eu -o pipefail - -# make sure this script runs at the repo root -cd "$(dirname "$(realpath -e "$0")")"/../../.. - -source .github/workflows/scripts/test_uvloop.sh diff --git a/.github/workflows/scripts/test_uvloop.sh b/.github/workflows/scripts/test_uvloop.sh deleted file mode 100755 index c2b96a16f5..0000000000 --- a/.github/workflows/scripts/test_uvloop.sh +++ /dev/null @@ -1,62 +0,0 @@ -#!/usr/bin/env bash -# Test that pulpcore can be installed with uvloop and that pulpcore-content starts correctly - -set -eu -o pipefail - -UVLOOP_TMP="$(mktemp -d)" -UVLOOP_SETTINGS_FILE="${UVLOOP_TMP}/settings.py" -export PULP_DJANGO_SETTINGS_FILE="${UVLOOP_SETTINGS_FILE}" - -function setup-venv(){ - python3 -m venv "${UVLOOP_TMP}/venv" -} -function run-pip(){ - "${UVLOOP_TMP}/venv/bin/pip" "$@" -} -function print-title(){ - echo "" - echo "=== $* ===" -} - -# Configuration -print-title "Creating configuration" -openssl rand -base64 32 > "${UVLOOP_TMP}/symmetric.key" -cat > "${UVLOOP_SETTINGS_FILE}" << EOF -UVLOOP_ENABLED = True -FILE_UPLOAD_TEMP_DIR = "${UVLOOP_TMP}" -WORKING_DIRECTORY = "${UVLOOP_TMP}" -DB_ENCRYPTION_KEY = "${UVLOOP_TMP}/symmetric.key" -EOF - -echo "::group::Configuration" -cat "${UVLOOP_SETTINGS_FILE}" -echo "::endgroup::" - -# Install -print-title "Installing pulpcore with uvloop" -setup-venv -run-pip install --upgrade pip -run-pip install "${PWD}[uvloop]" setuptools -c ".ci/assets/ci_constraints.txt" - -echo "::group::Uvloop version" -run-pip show uvloop -echo "::endgroup::" - -# Sanity test -print-title "Starting pulpcore-content with uvloop" -if timeout 5s "${UVLOOP_TMP}/venv/bin/pulpcore-content"; then - echo "ERROR: pulpcore-content exited prematurely with exit code 0" - exit 1 -else - EXIT_CODE=$? - if [ $EXIT_CODE -eq 124 ]; then - echo "pulpcore-content started successfully and ran for 5 seconds" - else - echo "ERROR: pulpcore-content exited prematurely with exit code $EXIT_CODE" - exit 1 - fi -fi - -# Cleanup -rm -rf "${UVLOOP_TMP}" -echo "Passed sanity checking!" From ef6d12a007c3eed4b9444fc76065af0d9abfdd1f Mon Sep 17 00:00:00 2001 From: Pedro Brochado Date: Wed, 11 Mar 2026 16:20:14 -0300 Subject: [PATCH 3/3] Add tests for uvloop startup --- CHANGES/7213.bugfix | 2 +- pulpcore/tests/unit/conftest.py | 6 +- pulpcore/tests/unit/test_startup.py | 105 ++++++++++++++++++++++++++++ unittest_requirements.txt | 1 + 4 files changed, 110 insertions(+), 4 deletions(-) create mode 100644 pulpcore/tests/unit/test_startup.py diff --git a/CHANGES/7213.bugfix b/CHANGES/7213.bugfix index 1fc13c599a..da0ddd31dc 100644 --- a/CHANGES/7213.bugfix +++ b/CHANGES/7213.bugfix @@ -1 +1 @@ -Pinned uvloop (optional dependency) to prevent a known bug in a newer releaes. +Pinned uvloop (optional dependency) to prevent a known bug in a newer releases. diff --git a/pulpcore/tests/unit/conftest.py b/pulpcore/tests/unit/conftest.py index 03b9536618..dc98722949 100644 --- a/pulpcore/tests/unit/conftest.py +++ b/pulpcore/tests/unit/conftest.py @@ -1,11 +1,11 @@ import pytest from uuid import uuid4 -from pulpcore.app.models import Domain -from pulpcore.app.util import set_domain - @pytest.fixture def fake_domain(): """A fixture to prevent `get_domain` to call out to the database.""" + from pulpcore.app.models import Domain + from pulpcore.app.util import set_domain + set_domain(Domain(pk=uuid4(), name=uuid4())) diff --git a/pulpcore/tests/unit/test_startup.py b/pulpcore/tests/unit/test_startup.py new file mode 100644 index 0000000000..7797d1be61 --- /dev/null +++ b/pulpcore/tests/unit/test_startup.py @@ -0,0 +1,105 @@ +"""App startup scenarios which doesn't require a full instance.""" + +import base64 +import os +import secrets +import subprocess +from textwrap import dedent +from pathlib import Path + +import pytest + + +@pytest.fixture +def pulpcore_source_dir() -> Path: + """The pulpcore source directory (repository root).""" + source_dir = Path(__file__).parents[3] + lookup_dirs = ( + Path(__file__).parents[3], + Path("/pulpcore"), + Path("/src/pulpcore"), + ) + source_dir = None + for dir in lookup_dirs: + if (dir / "pyproject.toml").exists(): + source_dir = dir + break + if not source_dir: + raise RuntimeError("Couldn't find pulpcore's source") + return source_dir + + +@pytest.fixture +def pulpcore_dist(pulpcore_source_dir) -> Path | None: + pulpcore_dist_glob = list((pulpcore_source_dir / "dist").glob("*.whl")) + pulpcore_dist = pulpcore_dist_glob[0] if len(pulpcore_dist_glob) else None + return pulpcore_dist + + +@pytest.fixture +def encryption_key_file(tmp_path: Path) -> Path: + """A file with a symmetric encryption key.""" + key_file = tmp_path / "symmetric.key" + key = base64.urlsafe_b64encode(secrets.token_bytes(32)) + key_file.write_bytes(key) + return key_file + + +class TestUvloop: + def test_uvloop_startup( + self, pulpcore_source_dir: Path, pulpcore_dist: Path | None, pulp_env: dict[str, str] + ): + """ + Test that pulpcore-content can start successfully with uvloop enabled. + """ + TIMEOUT_PROGRAM_EXIT_CODE = 124 # means 'timeout N' exited after N seconds + ci_constraints = pulpcore_source_dir / ".ci" / "assets" / "ci_constraints.txt" + install_source = pulpcore_dist or pulpcore_source_dir + + cmd = [ + "uv", + "run", + "--isolated", + "--no-project", + "--with", + f"{str(install_source.resolve())}[uvloop]", + "--with-requirements", + str(ci_constraints.resolve()), + "timeout", + "5", + "pulpcore-content", + "--bind", + "127.0.0.1:0", # Use random available port + ] + + result = subprocess.run(cmd, env=pulp_env, capture_output=True) + + stderr = result.stderr.decode() + assert result.returncode == TIMEOUT_PROGRAM_EXIT_CODE, ( + f"pulpcore-content exited prematurely with exit code {result.returncode}.\n" + f"Output: {stderr}" + ) + assert "Using uvloop as the asyncio event loop" in stderr + assert ( + "Connection in use:" not in stderr + ), "Some other program is using the port shown in stderr" + + @pytest.fixture + def settings_file(self, tmp_path: Path, encryption_key_file: Path) -> Path: + settings_file = tmp_path / "settings.py" + settings_content = f""" + UVLOOP_ENABLED = True + FILE_UPLOAD_TEMP_DIR = "{tmp_path.resolve()}" + WORKING_DIRECTORY = "{tmp_path.resolve()}" + DB_ENCRYPTION_KEY = "{encryption_key_file.resolve()}" + """ + settings_file.write_text(dedent(settings_content)) + return settings_file + + @pytest.fixture + def pulp_env(self, settings_file: Path) -> dict[str, str]: + """Configured environment.""" + env = {} + env["PATH"] = os.environ["PATH"] # enable subprocess to find system binaries + env["PULP_SETTINGS"] = str(settings_file) + return env diff --git a/unittest_requirements.txt b/unittest_requirements.txt index 251cad542a..2468c66d09 100644 --- a/unittest_requirements.txt +++ b/unittest_requirements.txt @@ -6,3 +6,4 @@ pytest-django pytest-asyncio pytest-aiohttp pytest-redis<4.1.0 # https://github.com/ClearcodeHQ/pytest-redis/issues/679 +uv