Repository navigation
PYTHON-6114 Run the mod_wsgi tests on GitHub Actions #3069
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
c3b8d84
PYTHON-6114 Restore mod_wsgi test infrastructure
blink1073 2e78181
PYTHON-6114 Add GitHub and local testing
blink1073 bb2eae5
PYTHON-6114 Address Copilot review findings
blink1073 60b277e
PYTHON-6114 Restore workflow action path syntax
blink1073 9210315
PYTHON-6114 Address latest Copilot review findings
blink1073 68e1092
PYTHON-6114 Validate fallback Apache pids before signaling
blink1073 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| #!/bin/bash | ||
| # Route the just test recipes to the mod_wsgi tests or the Evergreen test | ||
| # runner. The mod_wsgi setup writes TEST_NAME to the test env file. | ||
| set -eu | ||
|
|
||
| SCRIPT_DIR=$(dirname "${BASH_SOURCE:-$0}") | ||
| RECIPE="${1:-}" | ||
| if [ "$#" -gt 0 ]; then | ||
| shift | ||
| fi | ||
|
|
||
| mod_wsgi_env() { | ||
| [ -f "$SCRIPT_DIR/test-env.sh" ] \ | ||
| && grep -q 'TEST_NAME="mod_wsgi"' "$SCRIPT_DIR/test-env.sh" \ | ||
| && bash "$SCRIPT_DIR/mod_wsgi.sh" active | ||
| } | ||
|
|
||
| case "$RECIPE" in | ||
| setup-tests) | ||
| if [ "${1:-}" = "mod_wsgi" ]; then | ||
| shift | ||
| exec bash "$SCRIPT_DIR/mod_wsgi.sh" setup "${1:-}" | ||
| fi | ||
| exec bash "$SCRIPT_DIR/setup-tests.sh" "$@" | ||
| ;; | ||
| run-tests) | ||
| if mod_wsgi_env; then | ||
| if [ "$#" -gt 0 ]; then | ||
| echo "Ignoring arguments for the mod_wsgi tests: $*" >&2 | ||
| fi | ||
| exec bash "$SCRIPT_DIR/mod_wsgi.sh" test | ||
| fi | ||
| exec bash "$(dirname "$SCRIPT_DIR")/run-tests.sh" "$@" | ||
| ;; | ||
| teardown-tests) | ||
| if [ -f "$SCRIPT_DIR/test-env.sh" ] \ | ||
| && grep -q 'TEST_NAME="mod_wsgi"' "$SCRIPT_DIR/test-env.sh"; then | ||
| exec bash "$SCRIPT_DIR/mod_wsgi.sh" teardown | ||
| fi | ||
| exec bash "$SCRIPT_DIR/teardown-tests.sh" "$@" | ||
| ;; | ||
| *) | ||
| echo "Unknown recipe: $RECIPE" | ||
| exit 1 | ||
| ;; | ||
| esac |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,262 @@ | ||
| #!/bin/bash | ||
| # Set up, run, or tear down the mod_wsgi tests. On Linux the test runs against | ||
| # the host's Apache; on other hosts it runs in an ubuntu:24.04 container with | ||
| # its own MongoDB replica set. | ||
| # | ||
| # Usage: mod_wsgi.sh setup <standalone|embedded> | test | teardown | active | ||
| set -euo pipefail | ||
|
|
||
| SCRIPT_DIR=$(dirname "${BASH_SOURCE:-$0}") | ||
| ROOT=$(dirname "$(dirname "$SCRIPT_DIR")") | ||
| CMD="${1:-}" | ||
| MODE="${2:-}" | ||
| CONTAINER=pymongo-mod-wsgi | ||
| HOME_SRC=/home/smoke/src | ||
| # The venv, MongoDB install, and the recorded version selection live outside | ||
| # the source copy, so refreshing the checkout never reinstalls them. | ||
| VENV=/home/smoke/venv | ||
| VERSIONS=/home/smoke/work/versions | ||
|
|
||
| cd "$ROOT" | ||
|
|
||
| case "$CMD" in | ||
| setup) | ||
| if [ "$MODE" != "standalone" ] && [ "$MODE" != "embedded" ]; then | ||
| echo "Usage: mod_wsgi.sh setup <standalone|embedded>" | ||
| exit 1 | ||
| fi | ||
| # The harness recipes (run-tests, teardown-tests) dispatch on this. | ||
| echo "export TEST_NAME=\"mod_wsgi\"" > .evergreen/scripts/test-env.sh | ||
| echo "export SUB_TEST_NAME=\"$MODE\"" >> .evergreen/scripts/test-env.sh | ||
| # A failed setup leaves a partially started session behind. While the | ||
| # marker still routes to this session, tear it down; then remove the | ||
| # marker, so later just recipes do not route to the half-set-up state. | ||
| # The teardown is what stops the server or container setup may have | ||
| # started, and the next setup attempt stops any leftovers as well. | ||
| cleanup_marker() { | ||
| status=$? | ||
| if [ "$status" -ne 0 ]; then | ||
| bash "$SCRIPT_DIR/mod_wsgi.sh" teardown || true | ||
| rm -f .evergreen/scripts/test-env.sh | ||
| fi | ||
| } | ||
| trap cleanup_marker EXIT | ||
| ;; | ||
| test|teardown|active) ;; | ||
| *) | ||
| echo "Usage: mod_wsgi.sh setup <standalone|embedded> | test | teardown | active" | ||
| exit 1 | ||
| ;; | ||
| esac | ||
|
|
||
| if [ "$CMD" = "active" ]; then | ||
| # Exit 0 when a mod_wsgi session is active in this environment, so the | ||
| # dispatch script can route to us without a stale marker. Uses the system | ||
| # python and the tester's own STATE_FILE constant, so a missing or | ||
| # mismatched uv cannot break the dispatch and the path cannot drift. | ||
| if [ "$(uname -s)" = "Linux" ] && [ -z "${MOD_WSGI_DOCKER:-}" ]; then | ||
| if python3 -c " | ||
| import sys | ||
| sys.path.insert(0, '.evergreen/scripts') | ||
| from mod_wsgi_tester import STATE_FILE | ||
| sys.exit(0 if STATE_FILE.exists() else 1) | ||
| "; then | ||
| exit 0 | ||
| fi | ||
| exit 1 | ||
| fi | ||
| # The docker branch below handles the container case. | ||
| fi | ||
|
|
||
| if [ "$(uname -s)" = "Linux" ] && [ -z "${MOD_WSGI_DOCKER:-}" ]; then | ||
| # Native: Apache runs on the host; MongoDB is already running, started by | ||
| # "just run-server" or the CI workflow. Set MOD_WSGI_DOCKER to force the | ||
| # container path on Linux. | ||
| if [ "$CMD" != "teardown" ]; then | ||
| # Other recipes' exact uv syncs — including run-tests' resync — prune the | ||
| # mod_wsgi group; restore it before setup or test when missing. Checking | ||
| # first keeps the sync from reconciling dependencies that a caller | ||
| # resolved differently, like the min-deps job's lowest-direct install. | ||
| if ! uv run --no-sync python -c "import mod_wsgi" 2>/dev/null; then | ||
| uv sync --group mod_wsgi --quiet | ||
| fi | ||
| fi | ||
| if [ "$CMD" = "setup" ]; then | ||
| uv run --no-sync python .evergreen/scripts/mod_wsgi_tester.py setup "$MODE" | ||
| else | ||
| uv run --no-sync python .evergreen/scripts/mod_wsgi_tester.py "$CMD" | ||
| fi | ||
| # Clear the dispatch marker so later just recipes use the harness. | ||
| if [ "$CMD" = "teardown" ]; then | ||
| rm -f .evergreen/scripts/test-env.sh | ||
| fi | ||
| exit 0 | ||
| fi | ||
|
|
||
| # Docker path for hosts that cannot run Apache and mongod natively. | ||
| container_exists() { docker container inspect "$CONTAINER" >/dev/null 2>&1; } | ||
| container_running() { | ||
| [ "$(docker container inspect -f '{{.State.Running}}' "$CONTAINER" 2>/dev/null)" = "true" ] | ||
| } | ||
|
|
||
| if [ "$CMD" = "active" ]; then | ||
| if container_exists; then | ||
| exit 0 | ||
| fi | ||
| exit 1 | ||
| fi | ||
|
|
||
| # Copy the checkout into the container's home directory, so root-owned test | ||
| # artifacts never pollute it. | ||
| sync_source() { | ||
| docker exec "$CONTAINER" rm -rf "$HOME_SRC" | ||
| docker exec -u smoke "$CONTAINER" mkdir -p "$HOME_SRC" | ||
| tar -C "$ROOT" -cf - --exclude=.git --exclude=.venv \ | ||
| --exclude=access_log --exclude=error_log . | docker exec -i -u smoke "$CONTAINER" tar -C "$HOME_SRC" -xf - | ||
| } | ||
|
|
||
| # Start a single-node replica set, if it is not already running. | ||
| ensure_mongod() { | ||
| docker exec --user smoke -w "$HOME_SRC" "$CONTAINER" bash -c 'set -euo pipefail | ||
| export PATH="$HOME/.local/bin:$PATH" | ||
| mkdir -p "$HOME/work/db" | ||
| if ! pgrep -x mongod >/dev/null; then | ||
| "$HOME/work/bin/mongod" --replSet rs0 --bind_ip 127.0.0.1 --port 27017 --dbpath "$HOME/work/db" --fork --logpath "$HOME/work/mongod.log" | ||
| fi | ||
| ' | ||
| } | ||
|
|
||
| # Create the container and install the environment (idempotent). | ||
| bootstrap() { | ||
| if ! container_exists; then | ||
| docker run --name "$CONTAINER" -d ubuntu:24.04 sleep infinity | ||
| # A transient failure during initialization (for example in apt-get) | ||
| # would leave the half-created container behind, and later bootstraps | ||
| # would skip initialization and fail when sync_source expects the | ||
| # missing smoke user. Remove the container on failure, so the next | ||
| # attempt initializes a fresh one. | ||
| initialize() { | ||
| docker exec "$CONTAINER" apt-get update -qq | ||
| docker exec "$CONTAINER" apt-get install -y -qq apache2 apache2-dev build-essential curl jq git tar gzip ca-certificates | ||
| docker exec "$CONTAINER" useradd -m smoke | ||
| } | ||
| if ! initialize; then | ||
| docker rm -f "$CONTAINER" >/dev/null 2>&1 || true | ||
| exit 1 | ||
| fi | ||
| fi | ||
| if ! container_running; then | ||
| docker start "$CONTAINER" | ||
| fi | ||
| # Always refresh the checkout copy, so reused containers test the current | ||
| # source. The venv and versions file live outside it and survive the | ||
| # refresh. | ||
| sync_source | ||
| # Re-evaluate the newest Python and MongoDB on every bootstrap, so a reused | ||
| # container tracks matrix and server releases, but only reinstall when the | ||
| # recorded selection differs, since the downloads are slow. | ||
| docker exec --user smoke -w "$HOME_SRC" \ | ||
| -e VERSIONS="$VERSIONS" -e VENV="$VENV" "$CONTAINER" bash -c 'set -euo pipefail | ||
| export PATH="$HOME/.local/bin:$PATH" | ||
| export UV_PYTHON_INSTALL_DIR="$HOME/uv-python" | ||
| export UV_PROJECT_ENVIRONMENT="$VENV" | ||
| if ! command -v uv >/dev/null; then | ||
| curl -LsSf https://astral.sh/uv/install.sh | sh >/dev/null | ||
| fi | ||
| # Mirror the CI jobs 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\"") | ||
| case "$(uname -m)" in | ||
| aarch64|arm64) MARCH=aarch64 ;; | ||
| x86_64|amd64) MARCH=x86_64 ;; | ||
| *) echo "Unsupported architecture: $(uname -m)"; exit 1 ;; | ||
| esac | ||
| # MongoDB builds are per-distro; the container is ubuntu:24.04. | ||
| 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) | ||
| DESIRED="python=$LATEST_PYTHON mongodb=$MONGODB_URL" | ||
| if [ "$(cat "$VERSIONS" 2>/dev/null || true)" != "$DESIRED" ]; then | ||
| uv python install "$LATEST_PYTHON" >/dev/null | ||
| mkdir -p "$HOME/work/bin" | ||
| curl -fsSL "$MONGODB_URL" -o "$HOME/work/mongo.tgz" | ||
| MEMBER=$(tar -tzf "$HOME/work/mongo.tgz" | grep "bin/mongod$") | ||
| # Strip the two leading path components, so ensure_mongod finds the | ||
| # binary at "$HOME/work/bin/mongod". | ||
| tar -xz -C "$HOME/work/bin" --strip-components=2 -f "$HOME/work/mongo.tgz" "$MEMBER" | ||
| # A new interpreter needs a fresh venv; the sync below repopulates it. | ||
| rm -rf "$VENV" | ||
| printf "%s\n" "$DESIRED" > "$VERSIONS" | ||
| fi | ||
| # Rebuild the in-place C extensions on every bootstrap; the source | ||
| # refresh may carry artifacts built by a different interpreter. | ||
| export PYMONGO_C_EXT_MUST_BUILD=1 | ||
| uv sync --group mod_wsgi --refresh-package pymongo | ||
| ' | ||
| ensure_mongod | ||
| docker exec --user smoke -w "$HOME_SRC" "$CONTAINER" bash -c 'set -euo pipefail | ||
| export PATH="$HOME/.local/bin:$PATH" | ||
| export UV_PROJECT_ENVIRONMENT="'"$VENV"'" | ||
| # Probe replSetGetStatus over a direct connection: it returns immediately | ||
| # for an uninitialized replica set, while the default hello-based server | ||
| # selection would block for the full server selection timeout first. | ||
| if ! uv run --no-sync python -c " | ||
| from pymongo import MongoClient | ||
| client = MongoClient(\"127.0.0.1:27017\", directConnection=True) | ||
| try: | ||
| client.admin.command(\"replSetGetStatus\") | ||
| except Exception: | ||
| # NotYetInitialized (or a transient failure): initiation is still needed. | ||
| raise SystemExit(1) | ||
| " 2>/dev/null; then | ||
| uv run --no-sync 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\"}]}) | ||
| " | ||
| fi | ||
| ' | ||
| } | ||
|
|
||
| # Bring a running session to readiness for a test run without refreshing the | ||
| # checkout copy: Apache may be live in it, and sync_source would unlink its | ||
| # pid file and error log. Only a missing, stopped, or never-bootstrapped | ||
| # container needs the full bootstrap, since then nothing is live to disturb. | ||
| ensure_ready() { | ||
| if container_exists && container_running \ | ||
| && docker exec "$CONTAINER" test -e "$VERSIONS"; then | ||
| ensure_mongod | ||
| else | ||
| bootstrap | ||
| fi | ||
| } | ||
|
|
||
| case "$CMD" in | ||
| setup) | ||
| bootstrap | ||
| docker exec --user smoke -w "$HOME_SRC" "$CONTAINER" bash -c 'set -euo pipefail | ||
| export PATH="$HOME/.local/bin:$PATH" | ||
| export UV_PYTHON_INSTALL_DIR="$HOME/uv-python" | ||
| export UV_PROJECT_ENVIRONMENT="'"$VENV"'" | ||
| uv run --no-sync python .evergreen/scripts/mod_wsgi_tester.py setup '"$MODE"' | ||
| ' | ||
| ;; | ||
| test) | ||
| ensure_ready | ||
| docker exec --user smoke -w "$HOME_SRC" "$CONTAINER" bash -c 'set -euo pipefail | ||
| export PATH="$HOME/.local/bin:$PATH" | ||
| export UV_PROJECT_ENVIRONMENT="'"$VENV"'" | ||
| uv run --no-sync python .evergreen/scripts/mod_wsgi_tester.py test | ||
| ' | ||
| ;; | ||
| teardown) | ||
| if container_running; then | ||
| docker exec --user smoke -w "$HOME_SRC" "$CONTAINER" bash -c 'set -euo pipefail | ||
| export PATH="$HOME/.local/bin:$PATH" | ||
| export UV_PROJECT_ENVIRONMENT="'"$VENV"'" | ||
| uv run --no-sync python .evergreen/scripts/mod_wsgi_tester.py teardown | ||
| ' || true | ||
| docker stop -t 1 "$CONTAINER" | ||
| fi | ||
| rm -f .evergreen/scripts/test-env.sh | ||
| ;; | ||
| esac | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| from __future__ import annotations | ||
|
|
||
| import json | ||
| import sys | ||
|
|
||
| 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. MongoDB 6.0 is the | ||
| # oldest version that installs on the GitHub runners. | ||
| VERSIONS = [ | ||
| { | ||
| "python-version": CPYTHONS[0], | ||
| "mongodb-version": "6.0", | ||
| }, | ||
| { | ||
| "python-version": CPYTHONS[-1], | ||
| "mongodb-version": ALL_VERSIONS[-1], | ||
| }, | ||
| ] | ||
|
|
||
| if __name__ == "__main__": | ||
| if len(sys.argv) == 3 and sys.argv[1] == "--index": | ||
| # Emit one version pair in GITHUB_OUTPUT format. | ||
| entry = VERSIONS[int(sys.argv[2])] | ||
| print(f"python-version={entry['python-version']}") | ||
| print(f"mongodb-version={entry['mongodb-version']}") | ||
| else: | ||
| print(json.dumps(VERSIONS)) |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.