Skip to content
Merged
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
14 changes: 14 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ RUN apt-get -y update \
&& apt-get install -y --no-install-recommends \
libxmlsec1-dev \
tk-dev \
lsb-release \
software-properties-common \
gnupg \
&& rm -rf /var/lib/apt/lists/*

RUN git clone -b v2.6.9 --depth 1 https://github.com/pyenv/pyenv.git $PYENV_ROOT
Expand All @@ -43,6 +46,16 @@ RUN /build_python.sh 3.14.0
FROM builder-py-base AS builder-py-3_14t
RUN /build_python.sh 3.14.0t
# ------------------------------------------------------------------------------
FROM builder-py-base AS builder-py-3_14j

# Following guidance from https://github.com/python/cpython/blob/main/Tools/jit/README.md
RUN curl -o /tmp/llvm.sh https://apt.llvm.org/llvm.sh \
&& chmod +x /tmp/llvm.sh \
&& /tmp/llvm.sh 19 \
&& rm /tmp/llvm.sh

RUN /build_python.sh 3.14.0j
# ------------------------------------------------------------------------------
FROM python:3.13-slim-bookworm AS base

ENV PIP_DISABLE_PIP_VERSION_CHECK=1 \
Expand All @@ -60,6 +73,7 @@ COPY --link --from=builder-nsjail /nsjail/nsjail /usr/sbin/
COPY --link --from=builder-py-3_13 /snekbin/ /snekbin/
COPY --link --from=builder-py-3_14 /snekbin/ /snekbin/
COPY --link --from=builder-py-3_14t /snekbin/ /snekbin/
COPY --link --from=builder-py-3_14j /snekbin/ /snekbin/

RUN chmod +x /usr/sbin/nsjail \
&& ln -s /snekbin/python/3.14/ /snekbin/python/default
Expand Down
9 changes: 8 additions & 1 deletion scripts/build_python.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,18 @@ py_version="${1}"
# By dropping everything after, and including, the last period or hyphen.
install_path="${py_version%[-.]*}"

# If python version ends with a t, then ensure Python is installed to a dir ending with a t.
# Ensure the suffix letter is retained for free-threaded or JIT versions of Python.
if [[ $py_version == *t ]]; then
install_path+="t"
fi

if [[ $py_version == *j ]]; then
# Enable JIT mode when passed a version that ends with a "j"
install_path+="j"
py_version="${py_version%j}"
PYTHON_CONFIGURE_OPTS+=" --enable-experimental-jit"
fi

"${PYENV_ROOT}/plugins/python-build/bin/python-build" \
"${py_version}" \
"/snekbin/python/${install_path}"
Expand Down
27 changes: 27 additions & 0 deletions tests/test_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,33 @@ def test_gil_status(self):
self.assertEqual(status, 200)
self.assertEqual(json.loads(response)["stdout"], expected)

def test_jit_status(self):
"""Tests that JIT builds have the JIT available and enabled."""
with run_gunicorn():
get_jit_status = {"input": "import sys; print(sys._jit.is_enabled())"}

# This test will only work on tests where the sys._jit namespace exists.
#
# The namespace was added in 3.14 but is not guaranteed to be there for other
# implementations of Python, see https://docs.python.org/3/library/sys.html#sys._jit
# for full information.
#
# For now, the two below test cases are guaranteed to have the sys._jit module available
# and (if compiled correctly) should return the below values.
cases = [
("3.14", "False\n"),
("3.14j", "True\n"),
]
for version, expected in cases:
with self.subTest(version=version, expected=expected):
payload = {
"executable_path": f"/snekbin/python/{version}/bin/python",
**get_jit_status,
}
response, status = snekbox_request(payload)
self.assertEqual(status, 200)
self.assertEqual(json.loads(response)["stdout"], expected)

def invalid_executable_paths(self):
"""Test that passing invalid executable paths result in no code execution."""
with run_gunicorn():
Expand Down
18 changes: 13 additions & 5 deletions tests/test_nsjail.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,12 +213,19 @@ def test_write_hidden_exclude(self):
self.assertEqual(result.files[0].content, b"a")

def test_forkbomb_resource_unavailable(self):
# Using the production max PIDs causes processes to be killed due to memory instead of
# PID allocation exhaustion. For this test case, the PID limit is reduced to ensure
# that PID exhaustion is still something that is guarded against.

previous_pids_max = self.nsjail.config.cgroup_pids_max
# Using the production max PIDs causes processes to be killed due to
# memory instead of PID allocation exhaustion.
#
# For this test, we disable the cgroup memory limit & lower the PID
# limit so that the only reason the test code should be killed is due to
# PID exhaustion.

previous_pids_max, previous_mem_max = (
self.nsjail.config.cgroup_pids_max,
self.nsjail.config.cgroup_mem_max,
)
self.nsjail.config.cgroup_pids_max = 5
self.nsjail.config.cgroup_mem_max = 0

code = dedent(
"""
Expand All @@ -235,6 +242,7 @@ def test_forkbomb_resource_unavailable(self):
self.assertEqual(result.stderr, None)
finally:
self.nsjail.config.cgroup_pids_max = previous_pids_max
self.nsjail.config.cgroup_mem_max = previous_mem_max

def test_file_parsing_timeout(self):
code = dedent(
Expand Down
Loading