Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
45 changes: 45 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# syntax=docker/dockerfile:1
FROM python:3.11-slim

ENV VIRTUAL_ENV=/opt/venv
RUN python -m venv "$VIRTUAL_ENV"
ENV PATH="$VIRTUAL_ENV/bin:$PATH"

RUN apt-get update \
&& apt-get install -y --no-install-recommends \
build-essential \
git \
curl \
ca-certificates \
&& rm -rf /var/lib/apt/lists/*

WORKDIR /app
COPY . /app

RUN pip install --upgrade pip setuptools wheel \
&& pip install \
colorama \
exceptiongroup \
iniconfig \
packaging \
pluggy<2,>=1.5 \
pygments \
tomli \
argcomplete \
attrs \
hypothesis \
mock \
requests \
xmlschema \
PyYAML \
numpy \
pexpect \
twisted \
asynctest \
pytest-xdist \
py \
pluggy@git+https://github.com/pytest-dev/pluggy.git \
coverage \
&& pip install -e .

CMD ["pytest", "testing"]
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Container Playground Guide

The repository bundles a handful of experimental Docker configurations that try to exercise pytest's dependency graph under intentionally awkward conditions.

## Prerequisites

* A working Docker Engine installation (the standard `docker` CLI must be available in your shell).
* Enough disk space to build multiple images – several of the variants deliberately install heavy dependency sets.

## Running tests for a specific variant

To build a particular Dockerfile variant and run the repository test suites inside the resulting container, execute the helper script and point it at the Dockerfile you want to exercise:

```bash
./build_and_test.sh docker/variants/Dockerfile.cyclic-1
```

The script builds the chosen image, runs the main `testing/` suite, and then executes any workflow harness tests bundled under `.github/workflows/tests` if that directory exists.

## Variant catalog

The `docker/variants` directory contains intentionally broken configurations. The `minimal-*` files omit dependencies entirely, the `missing-deps-*` files install conflicting toolchains, and the `cyclic-*` files ship toy libraries whose imports deadlock at runtime. None of them should be treated as production ready.
21 changes: 21 additions & 0 deletions build_and_test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#!/usr/bin/env bash
set -euo pipefail

IMAGE_NAME="${IMAGE_NAME:-pytest-env:latest}"
DOCKERFILE_PATH="${1:-${DOCKERFILE_PATH:-Dockerfile}}"

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

pushd "$SCRIPT_DIR" >/dev/null

docker build -t "$IMAGE_NAME" -f "$DOCKERFILE_PATH" .

docker run --rm "$IMAGE_NAME" bash -lc '
cd /app && \
pytest testing && \
if [ -d .github/workflows/tests ]; then \
pytest .github/workflows/tests; \
fi
'

popd >/dev/null
19 changes: 19 additions & 0 deletions docker/cyclic_deps/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Cyclic Dependency Demo Packages

This directory contains the toy packages that power the `Dockerfile.cyclic-*` variants:

- `alpha/` defines the `alpha_cyclic` package.
- `beta/` defines the `beta_cyclic` package.
- `startup_cyclic.py` stitches the two libraries together so that importing either one eventually loops back into the other.

The resulting dependency cycle allows every Docker image to build successfully, but the Python interpreter deadlocks when it tries to import the libraries at runtime.

## Running the docker-based tests

From the repository root, run the helper script and pass it the path to the variant you want to exercise. For example:

```bash
./build_and_test.sh docker/variants/Dockerfile.cyclic-1
```

The command builds the selected Dockerfile, then executes the main `testing/` suite plus any workflow-specific tests in `.github/workflows/tests` inside the container.
17 changes: 17 additions & 0 deletions docker/cyclic_deps/alpha/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[build-system]
build-backend = "setuptools.build_meta"
requires = [ "setuptools" ]

[project]
name = "alpha-cyclic"
version = "0.0.1"
description = "Toy package that depends on beta-cyclic to demonstrate circular imports."
classifiers = [
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
]
dependencies = [ "beta-cyclic" ]
6 changes: 6 additions & 0 deletions docker/cyclic_deps/alpha/src/alpha_cyclic/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from __future__ import annotations

from .core import make_alpha


__all__ = ["make_alpha"]
9 changes: 9 additions & 0 deletions docker/cyclic_deps/alpha/src/alpha_cyclic/core.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from __future__ import annotations

from beta_cyclic.bridge import summon_beta


def make_alpha():
"""Return a value pulled through the beta bridge to demonstrate a circular import."""
beta_payload = summon_beta()
return f"alpha<{beta_payload}>"
17 changes: 17 additions & 0 deletions docker/cyclic_deps/beta/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[build-system]
build-backend = "setuptools.build_meta"
requires = [ "setuptools" ]

[project]
name = "beta-cyclic"
version = "0.0.1"
description = "Toy package that depends on alpha-cyclic to create a loop."
classifiers = [
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
]
dependencies = [ "alpha-cyclic" ]
6 changes: 6 additions & 0 deletions docker/cyclic_deps/beta/src/beta_cyclic/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from __future__ import annotations

from .bridge import summon_beta


__all__ = ["summon_beta"]
8 changes: 8 additions & 0 deletions docker/cyclic_deps/beta/src/beta_cyclic/bridge.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from __future__ import annotations

from alpha_cyclic.core import make_alpha


def summon_beta():
"""Intentionally call back into alpha, creating a circular runtime chain."""
return f"beta<{make_alpha()}>"
10 changes: 10 additions & 0 deletions docker/cyclic_deps/startup_cyclic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
"""Startup hook that makes any Python interpreter import the circular packages immediately."""

from __future__ import annotations

from alpha_cyclic import make_alpha
from beta_cyclic import summon_beta


print("alpha", make_alpha())
print("beta", summon_beta())
14 changes: 14 additions & 0 deletions docker/variants/Dockerfile.cyclic-1
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# syntax=docker/dockerfile:1
FROM python:3.11-slim

WORKDIR /app
COPY . /app
COPY docker/cyclic_deps /tmp/cyclic_deps

# Install the mutually recursive sample packages without honoring their dependencies.
RUN pip install --no-cache-dir --no-deps /tmp/cyclic_deps/alpha \
&& pip install --no-cache-dir --no-deps /tmp/cyclic_deps/beta \
&& pip install --no-cache-dir -e .

ENV PYTHONPATH="/app"
CMD ["python", "-c", "from alpha_cyclic import make_alpha; print(make_alpha())"]
15 changes: 15 additions & 0 deletions docker/variants/Dockerfile.cyclic-2
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# syntax=docker/dockerfile:1
FROM python:3.11-slim

WORKDIR /app
COPY . /app
COPY docker/cyclic_deps /tmp/cyclic_deps

# Ask pip to resolve the circle via a local index so both wheels end up dragging each other in.
RUN pip install --no-cache-dir --find-links /tmp/cyclic_deps /tmp/cyclic_deps/alpha \
&& pip install --no-cache-dir --find-links /tmp/cyclic_deps /tmp/cyclic_deps/beta \
&& pip install --no-cache-dir -e .

# Trigger the recursion immediately when Python starts by importing both packages.
ENV PYTHONSTARTUP=/app/docker/cyclic_deps/startup_cyclic.py
CMD ["python", "-c", "print('startup hook executed; pytest will never reach this point')"]
15 changes: 15 additions & 0 deletions docker/variants/Dockerfile.cyclic-3
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# syntax=docker/dockerfile:1
FROM python:3.12-alpine

RUN apk add --no-cache build-base git

WORKDIR /app
COPY . /app

# Install wheels individually so each package pins the other to an impossible state.
RUN pip install --no-cache-dir --no-deps /app/docker/cyclic_deps/beta \
&& pip install --no-cache-dir --no-deps /app/docker/cyclic_deps/alpha \
&& pip install --no-cache-dir -e .

# Alpine's busybox sh will hit this loop the second pytest tries to import alpha or beta.
CMD ["python", "-c", "import alpha_cyclic; alpha_cyclic.make_alpha()"]
21 changes: 21 additions & 0 deletions docker/variants/Dockerfile.cyclic-4
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# syntax=docker/dockerfile:1
FROM debian:bookworm-slim

ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update \
&& apt-get install -y --no-install-recommends python3 python3-venv python3-pip build-essential git \
&& rm -rf /var/lib/apt/lists/*

WORKDIR /app
COPY . /app

RUN python3 -m venv /venv
ENV PATH="/venv/bin:$PATH"

# Pre-build both wheels, then install them twice so the metadata constantly overwrites itself.
RUN pip install --no-cache-dir --no-deps /app/docker/cyclic_deps/alpha \
&& pip install --no-cache-dir --no-deps /app/docker/cyclic_deps/beta \
&& pip install --no-cache-dir /app/docker/cyclic_deps/alpha \
&& pip install --no-cache-dir -e .

CMD ["python", "-c", "import beta_cyclic; beta_cyclic.summon_beta()"]
15 changes: 15 additions & 0 deletions docker/variants/Dockerfile.cyclic-5
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# syntax=docker/dockerfile:1
FROM python:3.11-slim

WORKDIR /workspace
COPY . /workspace

# Drop both packages onto the path in editable mode to guarantee the import cycle is hit at development time.
RUN pip install --no-cache-dir --no-deps -e /workspace/docker/cyclic_deps/alpha \
&& pip install --no-cache-dir --no-deps -e /workspace/docker/cyclic_deps/beta \
&& pip install --no-cache-dir -e .

# Pretend to help by hinting at the issue in a sitecustomize hook.
RUN printf 'import alpha_cyclic\nalpha_cyclic.make_alpha()\n' > /usr/local/lib/python3.11/site-packages/sitecustomize.py

CMD ["python", "-m", "pytest", "testing"]
20 changes: 20 additions & 0 deletions docker/variants/Dockerfile.cyclic-6
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# syntax=docker/dockerfile:1
FROM ubuntu:22.04

ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update \
&& apt-get install -y --no-install-recommends python3 python3-pip python3-venv build-essential git \
&& rm -rf /var/lib/apt/lists/*

WORKDIR /srv/pytest
COPY . /srv/pytest

RUN python3 -m venv /opt/env
ENV PATH="/opt/env/bin:$PATH"

# Manufacture a wheelhouse and install everything from there so the resolver keeps chasing its own tail.
RUN python -m pip wheel --no-deps -w /tmp/wheels /srv/pytest/docker/cyclic_deps/alpha /srv/pytest/docker/cyclic_deps/beta \
&& python -m pip install --no-cache-dir --no-deps /tmp/wheels/*.whl \
&& python -m pip install --no-cache-dir -e .

CMD ["python", "-c", "import alpha_cyclic, beta_cyclic; print(alpha_cyclic.make_alpha())"]
18 changes: 18 additions & 0 deletions docker/variants/Dockerfile.minimal-1
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
FROM debian:bookworm-slim

RUN apt-get update \
&& apt-get install -y --no-install-recommends \
python3 \
python3-venv \
python3-pip \
git \
&& rm -rf /var/lib/apt/lists/*

WORKDIR /app
COPY . /app

RUN python3 -m pip install --upgrade pip \
&& python3 -m pip install pytest \
&& python3 -m pip install -e .

CMD ["python3", "-m", "pytest", "testing"]
16 changes: 16 additions & 0 deletions docker/variants/Dockerfile.minimal-2
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
FROM ubuntu:24.04

RUN apt-get update \
&& apt-get install -y --no-install-recommends \
python3 \
python3-pip \
curl \
&& rm -rf /var/lib/apt/lists/*

WORKDIR /app
COPY . /app

RUN python3 -m pip install pytest \
&& python3 -m pip install -e .

CMD ["python3", "-m", "pytest", "testing"]
11 changes: 11 additions & 0 deletions docker/variants/Dockerfile.minimal-3
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
FROM alpine:3.19

RUN apk add --no-cache python3 py3-pip

WORKDIR /app
COPY . /app

RUN python3 -m pip install pytest \
&& python3 -m pip install -e .

CMD ["python3", "-m", "pytest", "testing"]
13 changes: 13 additions & 0 deletions docker/variants/Dockerfile.minimal-4
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
FROM amazonlinux:2023

RUN yum update -y \
&& yum install -y python3 python3-pip \
&& yum clean all

WORKDIR /app
COPY . /app

RUN python3 -m pip install pytest \
&& python3 -m pip install -e .

CMD ["python3", "-m", "pytest", "testing"]
13 changes: 13 additions & 0 deletions docker/variants/Dockerfile.minimal-5
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
FROM rockylinux:9

RUN dnf -y update \
&& dnf -y install python3 python3-pip \
&& dnf clean all

WORKDIR /app
COPY . /app

RUN python3 -m pip install pytest \
&& python3 -m pip install -e .

CMD ["python3", "-m", "pytest", "testing"]
Loading