Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
9713b38
Move the mod_wsgi tests from Evergreen to GitHub Actions
blink1073 Sep 18, 2026
5b4aad8
Create the venv in the Mod WSGI job, CI skips it in setup-dev-env
blink1073 Sep 18, 2026
b97c528
Source the Mod WSGI matrix from the Evergreen version lists
blink1073 Sep 18, 2026
a58e8ab
Fix the mod_wsgi 6 crash and unpin the dependency
blink1073 Sep 18, 2026
b9cfae4
Split the Mod WSGI jobs into standalone and embedded
blink1073 Sep 18, 2026
eee6712
Run the min and max deps tests in each Mod WSGI job
blink1073 Sep 18, 2026
dcd8738
Test MongoDB 8.0 as the lowest version; ubuntu runners cannot install…
blink1073 Sep 18, 2026
06e4fa0
Force a fresh C extension build in both Mod WSGI rounds
blink1073 Sep 18, 2026
9dec93a
Run the min deps round last; it rewrites the lock file
blink1073 Sep 18, 2026
950c310
Run the Mod WSGI jobs on ubuntu-22.04 and test MongoDB 6.0 for min deps
blink1073 Sep 18, 2026
bb36972
Split the Mod WSGI jobs by round: min deps and latest
blink1073 Sep 18, 2026
7ac7b40
PYTHON-6114 Pin drivers-evergreen-tools in the Mod WSGI jobs
blink1073 Sep 21, 2026
e1881e6
PYTHON-6114 Stop executor threads on subinterpreter shutdown
blink1073 Sep 20, 2026
ccb71a7
PYTHON-5418 Add a test using InterpreterPoolExecutor
blink1073 Sep 21, 2026
63cc5ac
PYTHON-5418 Submit exec to the InterpreterPoolExecutor
blink1073 Sep 21, 2026
e1588fa
PYTHON-6114 Address review feedback
blink1073 Sep 21, 2026
f77d46e
PYTHON-6114 Resolve the mod_wsgi group in the min deps job
blink1073 Sep 21, 2026
2b70790
PYTHON-6114 Install uv once in the smoke test root phase
blink1073 Sep 21, 2026
0890a32
PYTHON-5418 Note subinterpreter support in the changelog
blink1073 Sep 21, 2026
8ef3a0b
PYTHON-5418 Test the async client in subinterpreters
blink1073 Sep 21, 2026
316f07e
PYTHON-5418 Finalize idle interpreters and update the changelog
blink1073 Sep 21, 2026
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
74 changes: 0 additions & 74 deletions .evergreen/generated_configs/tasks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -316,80 +316,6 @@ tasks:
TOPOLOGY: sharded_cluster
tags: [test-min-support]

# Mod wsgi tests
- name: mod-wsgi-replica-set-python3.10
commands:
- func: run server
vars:
TOPOLOGY: replica_set
TOOLCHAIN_VERSION: "3.10"
- func: run tests
vars:
TEST_NAME: mod_wsgi
SUB_TEST_NAME: standalone
TOOLCHAIN_VERSION: "3.10"
tags: [mod_wsgi, pr]
- name: mod-wsgi-embedded-mode-replica-set-python3.11
commands:
- func: run server
vars:
TOPOLOGY: replica_set
TOOLCHAIN_VERSION: "3.11"
- func: run tests
vars:
TEST_NAME: mod_wsgi
SUB_TEST_NAME: embedded
TOOLCHAIN_VERSION: "3.11"
tags: [mod_wsgi, pr]
- name: mod-wsgi-replica-set-python3.12
commands:
- func: run server
vars:
TOPOLOGY: replica_set
TOOLCHAIN_VERSION: "3.12"
- func: run tests
vars:
TEST_NAME: mod_wsgi
SUB_TEST_NAME: standalone
TOOLCHAIN_VERSION: "3.12"
tags: [mod_wsgi, pr]
- name: mod-wsgi-embedded-mode-replica-set-python3.13
commands:
- func: run server
vars:
TOPOLOGY: replica_set
TOOLCHAIN_VERSION: "3.13"
- func: run tests
vars:
TEST_NAME: mod_wsgi
SUB_TEST_NAME: embedded
TOOLCHAIN_VERSION: "3.13"
tags: [mod_wsgi, pr]
- name: mod-wsgi-embedded-mode-replica-set-python3.14
commands:
- func: run server
vars:
TOPOLOGY: replica_set
TOOLCHAIN_VERSION: "3.14"
- func: run tests
vars:
TEST_NAME: mod_wsgi
SUB_TEST_NAME: embedded
TOOLCHAIN_VERSION: "3.14"
tags: [mod_wsgi, pr]
- name: mod-wsgi-embedded-mode-replica-set-python3.15
commands:
- func: run server
vars:
TOPOLOGY: replica_set
TOOLCHAIN_VERSION: "3.15"
- func: run tests
vars:
TEST_NAME: mod_wsgi
SUB_TEST_NAME: embedded
TOOLCHAIN_VERSION: "3.15"
tags: [mod_wsgi, pr]

# No orchestration tests
- name: test-no-orchestration-python3.10-min-deps
commands:
Expand Down
10 changes: 0 additions & 10 deletions .evergreen/generated_configs/variants.yml
Original file line number Diff line number Diff line change
Expand Up @@ -384,16 +384,6 @@ buildvariants:
TEST_NAME: mockupdb
tags: [pr]

# Mod wsgi tests
- name: mod_wsgi-ubuntu-22
tasks:
- name: .mod_wsgi
display_name: Mod_WSGI Ubuntu-22
run_on:
- ubuntu2204-small
expansions:
MOD_WSGI_VERSION: "4"

# No c ext tests
- name: no-c-ext-rhel8
tasks:
Expand Down
33 changes: 0 additions & 33 deletions .evergreen/scripts/generate_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -400,14 +400,6 @@ def create_no_c_ext_variants():
return [create_variant(tasks, display_name, host=host, expansions=expansions)]


def create_mod_wsgi_variants():
host = HOSTS["ubuntu22"]
tasks = [".mod_wsgi"]
expansions = dict(MOD_WSGI_VERSION="4")
display_name = get_variant_name("Mod_WSGI", host)
return [create_variant(tasks, display_name, host=host, expansions=expansions)]


def create_disable_test_commands_variants():
host = DEFAULT_HOST
expansions = dict(AUTH="auth", SSL="ssl", DISABLE_TEST_COMMANDS="1")
Expand Down Expand Up @@ -1012,31 +1004,6 @@ def create_oidc_tasks():
return tasks


def create_mod_wsgi_tasks():
tasks = []
for (test, topology), python in zip_cycle(
product(["standalone", "embedded-mode"], ["standalone", "replica_set"]), CPYTHONS
):
if "t" in python:
continue
if test == "standalone":
task_name = "mod-wsgi-"
else:
task_name = "mod-wsgi-embedded-mode-"
task_name += topology.replace("_", "-")
task_name = get_task_name(task_name, python=python)
server_vars = dict(TOPOLOGY=topology, TOOLCHAIN_VERSION=python)
server_func = FunctionCall(func="run server", vars=server_vars)
vars = dict(
TEST_NAME="mod_wsgi", SUB_TEST_NAME=test.split("-")[0], TOOLCHAIN_VERSION=python
)
test_func = FunctionCall(func="run tests", vars=vars)
tags = ["mod_wsgi", "pr"]
commands = [server_func, test_func]
tasks.append(EvgTask(name=task_name, tags=tags, commands=commands))
return tasks


def _create_ocsp_tasks(algo, variant, server_type, base_task_name):
tasks = []
file_name = f"{algo}-basic-tls-ocsp-{variant}.json"
Expand Down
22 changes: 22 additions & 0 deletions .evergreen/scripts/mod_wsgi_matrix.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from __future__ import annotations

import json

from generate_config_utils import ALL_VERSIONS, CPYTHONS

# MongoDB 6.0 with the oldest supported CPython and minimum dependencies, and
# the latest MongoDB with the newest supported CPython. The jobs run on
# ubuntu-22.04 runners, where MongoDB 6.0 is the oldest version that installs.
VERSIONS = [
{
"python-version": CPYTHONS[0],
"mongodb-version": "6.0",
},
{
"python-version": CPYTHONS[-1],
"mongodb-version": ALL_VERSIONS[-1],
},
]

if __name__ == "__main__":
print(json.dumps(VERSIONS))
96 changes: 96 additions & 0 deletions .evergreen/scripts/mod_wsgi_smoke_test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
#!/bin/bash
# Smoke test the mod_wsgi tests in an ubuntu:24.04 container, mirroring the
# Mod WSGI job in .github/workflows/test-python.yml.
set -eu

SCRIPT_DIR=$(dirname ${BASH_SOURCE:-$0})
ROOT=$(dirname "$(dirname "$SCRIPT_DIR")")

# Re-exec the script inside a container when run on the host.
if [ ! -f /.dockerenv ]; then
if ! command -v docker >/dev/null; then
echo "docker is required to run the mod_wsgi smoke test"
exit 1
fi
exec docker run --rm -v "$ROOT":/src:ro ubuntu:24.04 bash /src/.evergreen/scripts/mod_wsgi_smoke_test.sh
fi

if [ "$(id -u)" = "0" ]; then
# Apache and mongod must not run as root. Install system packages, copy the
# checkout into a home directory, and re-run this script unprivileged.
export DEBIAN_FRONTEND=noninteractive
apt-get update -qq
apt-get install -y -qq apache2 apache2-dev build-essential curl jq git >/dev/null
# Install uv to a system path from a pinned release, verifying the published
# checksum, rather than piping a mutable installer script into a shell.
UV_VERSION=0.12.17
case "$(uname -m)" in
x86_64) UV_TARGET=x86_64-unknown-linux-gnu ;;
aarch64 | arm64) UV_TARGET=aarch64-unknown-linux-gnu ;;
*) echo "Unsupported architecture" >&2; exit 1 ;;
esac
curl -fsSL -o "/tmp/uv-${UV_TARGET}.tar.gz" "https://github.com/astral-sh/uv/releases/download/${UV_VERSION}/uv-${UV_TARGET}.tar.gz"
curl -fsSL -o "/tmp/uv-${UV_TARGET}.tar.gz.sha256" "https://github.com/astral-sh/uv/releases/download/${UV_VERSION}/uv-${UV_TARGET}.tar.gz.sha256"
(cd /tmp && sha256sum -c "uv-${UV_TARGET}.tar.gz.sha256")
tar -xzf "/tmp/uv-${UV_TARGET}.tar.gz" -C /tmp
install -m 0755 "/tmp/uv-${UV_TARGET}/uv" /usr/local/bin/uv
rm -rf "/tmp/uv-${UV_TARGET}.tar.gz" "/tmp/uv-${UV_TARGET}.tar.gz.sha256" "/tmp/uv-${UV_TARGET}"
useradd -m smoke
mkdir -p /home/smoke/src
# Leave out the generated env files so they cannot override the versions
# set below.
tar -C /src -cf - --exclude=.git --exclude=.venv --exclude=.evergreen/scripts/env.sh \
--exclude=.evergreen/scripts/test-env.sh . | tar -C /home/smoke/src -xf -
chown -R smoke:smoke /home/smoke/src
exec su - smoke -c "bash /src/.evergreen/scripts/mod_wsgi_smoke_test.sh"
fi

export PATH="$HOME/.local/bin:$PATH"
uv tool install rust-just >/dev/null

cd /home/smoke/src

# mongod inherits the soft nofile limit; the 1024 default is exhausted by the
# connection storm the parallel test generates. Best effort: some container
# hosts set a lower hard limit.
ulimit -n 65536 2>/dev/null || true

# Mirror the GHA job and test the newest supported CPython.
LATEST_PYTHON=$(uv run --no-project --with 'shrub.py>=3.10.0' python .evergreen/scripts/mod_wsgi_matrix.py | jq -r '.[-1]."python-version"')
echo "Testing with CPython $LATEST_PYTHON"
uv python install "$LATEST_PYTHON" >/dev/null

export UV_PYTHON=$LATEST_PYTHON
export PYMONGO_C_EXT_MUST_BUILD=1
just install
uv sync --group mod_wsgi

# Start a single-node replica set.
MARCH=$(uname -m)
if [ "$MARCH" != "aarch64" ] && [ "$MARCH" != "x86_64" ]; then
echo "Unsupported architecture: $MARCH"
exit 1
fi
MONGODB_URL=$(curl -fsSL https://downloads.mongodb.org/current.json | jq -r "
.versions[] | select(.current and .production_release) | .downloads[] |
select(.target==\"ubuntu2404\" and .arch==\"$MARCH\") | .archive.url" | grep -v enterprise | head -1)
curl -fsSL "$MONGODB_URL" -o /tmp/mongo.tgz
MEMBER=$(tar -tzf /tmp/mongo.tgz | grep "bin/mongod$")
tar -xz -C /tmp --strip-components=2 -f /tmp/mongo.tgz "$MEMBER"
mkdir -p /tmp/db
/tmp/mongod --replSet rs0 --bind_ip 127.0.0.1 --port 27017 --dbpath /tmp/db --fork --logpath /tmp/mongod.log
uv run python -c "
from pymongo import MongoClient
client = MongoClient('127.0.0.1:27017', directConnection=True)
client.admin.command('replSetInitiate', {'_id': 'rs0', 'members': [{'_id': 0, 'host': '127.0.0.1:27017'}]})
MongoClient().admin.command('hello')
"

# The mod_wsgi group is part of the synced environment, so both modes can run
# without reinstalling.
for MODE in standalone embedded; do
bash .evergreen/scripts/setup-tests.sh mod_wsgi $MODE
bash .evergreen/run-tests.sh
bash .evergreen/scripts/teardown-tests.sh
done
echo "mod_wsgi smoke test passed"
44 changes: 31 additions & 13 deletions .evergreen/scripts/mod_wsgi_tester.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

import os
import socket
import sys
import time
import urllib.error
Expand All @@ -22,6 +23,17 @@ def make_request(url, timeout=10):
raise TimeoutError(f"Failed to access {url}")


def find_mod_wsgi_so() -> str:
# pip builds the Apache module against the installing interpreter, so the
# .so lives inside the installed package.
import mod_wsgi

so_files = list((Path(mod_wsgi.__file__).parent / "server").glob("mod_wsgi*.so"))
if len(so_files) != 1:
raise ValueError(f"Expected one mod_wsgi.so in {mod_wsgi.__file__}, found {so_files}")
return str(so_files[0])


def setup_mod_wsgi(sub_test_name: str) -> None:
env = os.environ.copy()
if sub_test_name == "embedded":
Expand All @@ -34,20 +46,14 @@ def setup_mod_wsgi(sub_test_name: str) -> None:
apache = which("apache2")
if not apache and Path("/usr/lib/apache2/mpm-prefork/apache2").exists():
apache = "/usr/lib/apache2/mpm-prefork/apache2"
if apache:
apache_config = "apache24ubuntu161404.conf"
else:
apache = which("httpd")
if not apache:
raise ValueError("Could not find apache2 or httpd")
apache_config = "apache22amazon.conf"
python_version = ".".join(str(val) for val in sys.version_info[:2])
mod_wsgi_version = 4
so_file = f"/opt/python/mod_wsgi/python_version/{python_version}/mod_wsgi_version/{mod_wsgi_version}/mod_wsgi.so"
if not apache:
raise ValueError("Could not find apache2")
apache_config = "apache24ubuntu.conf"
so_file = find_mod_wsgi_so()
write_env("MOD_WSGI_SO", so_file)
env["MOD_WSGI_SO"] = so_file
env["PYTHONHOME"] = f"/opt/python/{python_version}"
env["PROJECT_DIRECTORY"] = project_directory = str(ROOT)
write_env("PROJECT_DIRECTORY", project_directory)
write_env("APACHE_BINARY", apache)
write_env("APACHE_CONFIG", apache_config)
uri1 = f"http://localhost:8080/interpreter1{project_directory}"
Expand Down Expand Up @@ -77,9 +83,10 @@ def test_mod_wsgi() -> None:

args = f"-n 25000 serial {uri1} {uri2}"
main(*parse_args(args.split()))
except Exception as e:
except BaseException:
# The test client raises KeyboardInterrupt in the failing path.
LOGGER.error(Path("error_log").read_text())
raise e
raise


def teardown_mod_wsgi() -> None:
Expand All @@ -88,6 +95,17 @@ def teardown_mod_wsgi() -> None:

run_command(f"{apache} -k stop -f {ROOT}/test/mod_wsgi_test/{apache_config}")

# -k stop signals the master process and returns before it exits. Wait for
# the port to be released so another instance can bind it.
deadline = time.time() + 10
while time.time() < deadline:
try:
socket.create_connection(("127.0.0.1", 8080), timeout=1).close()
except OSError:
return
time.sleep(0.5)
raise ValueError("Apache did not release port 8080")


if __name__ == "__main__":
setup_mod_wsgi()
2 changes: 1 addition & 1 deletion .evergreen/scripts/setup_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@


# Map the test name to test group.
GROUP_MAP = dict(mockupdb="mockupdb", perf="perf")
GROUP_MAP = dict(mockupdb="mockupdb", perf="perf", mod_wsgi="mod_wsgi")

# The python version used for perf tests.
PERF_PYTHON_VERSION = "3.10.11"
Expand Down
Loading
Loading