carlos / contra Public

test: the runner's python3 cannot make a venv — fall back to pip --target

Second CI run, second real finding. `python3 -m venv` fails outright on the
shared box: Debian splits ensurepip into python3-venv, which is not
installed, so there is no venv to be had there at all.

`make oracles` now tries venv, then `pip install --target .oracles`, and
test/oracle.mjs puts that directory on PYTHONPATH when it exists. Both give
the oracles their imports without touching the box, which is the constraint
that matters: its user-data is fixed at launch, so anything needing a change
there is a launch-template version and an instance replacement.

If neither works it now prints what it actually found — the python3 version
and the exact venv and pip errors — rather than failing with one of them and
leaving the other unknown. That is the whole reason this took two runs
instead of one.

Both paths verified locally: venv, and pip --target with .venv absent.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Paul Campbell pushed by paul@keymail.dev 99c2250bcd5b801a4093bdbd9c1b43d8d6c7a52f parent 2b0646a
3 files changed, +42 −4
  • .gitignore +1 −0
  • Makefile +27 −4
  • test/oracle.mjs +14 −0

CI failed — run details

CI log
=== vet ===
go vet ./...
=== fmt ===
=== test ===
go test ./...
?   	github.com/carlosframework/contra/cmd/contra	[no test files]
?   	github.com/carlosframework/contra/internal/contra	[no test files]
?   	github.com/carlosframework/contra/internal/contra/auth	[no test files]
ok  	github.com/carlosframework/contra/internal/contra/crypto	(cached)
?   	github.com/carlosframework/contra/internal/contra/icons	[no test files]
?   	github.com/carlosframework/contra/internal/contra/mail	[no test files]
?   	github.com/carlosframework/contra/internal/contra/model	[no test files]
?   	github.com/carlosframework/contra/internal/contra/store	[no test files]
?   	github.com/carlosframework/contra/internal/contra/vault	[no test files]
ok  	github.com/carlosframework/contra/internal/contratest	(cached)
=== js-test ===
make oracles: this python3 can neither create a venv nor run pip.
  python3: Python 3.12.3
  venv   : 
  pip    : /usr/bin/python3: No module named pip
  Install python3-venv or python3-pip, or set CONTRA_PYTHON to
  an interpreter that already has test/requirements.txt.
make: *** [Makefile:120: oracles] Error 1

amadan: step js-test failed: exit status 2

amadan: job failed at step js-test — its output is above, not at the tail of this log
diff --git a/.gitignore b/.gitignore
index 3868e97..922e7ba 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,3 +14,4 @@
# The oracle venv, built by `make oracles`. Pinned by test/requirements.txt,
# so it is reproducible rather than worth committing.
/.venv/
+/.oracles/
diff --git a/Makefile b/Makefile
index ead136b..c8053bc 100644
--- a/Makefile
+++ b/Makefile
@@ -109,13 +109,36 @@ test-js:
#
# Pinned in test/requirements.txt, because an oracle that drifts is an oracle
# that stops meaning what it said last week.
+# Two ways in, because a Debian python3 without the python3-venv package
+# cannot create a venv at all (no ensurepip) — which is exactly what the
+# shared runner turned out to be. A venv is preferred where it works; where
+# it does not, the packages go into .oracles/ and test/oracle.mjs puts that
+# on PYTHONPATH. Both give the oracles their imports without touching the
+# box, which is the requirement: its user-data is fixed at launch.
oracles:
@command -v python3 >/dev/null 2>&1 || { \
echo "make oracles: python3 is not installed."; exit 1; }
- python3 -m venv .venv
- .venv/bin/pip install --quiet --upgrade pip
- .venv/bin/pip install --quiet -r test/requirements.txt
- @echo "oracles ready: .venv/bin/python3"
+ @set -e; \
+ if python3 -m venv .venv >/dev/null 2>&1; then \
+ .venv/bin/pip install --quiet --upgrade pip; \
+ .venv/bin/pip install --quiet -r test/requirements.txt; \
+ echo "oracles ready: .venv/bin/python3"; \
+ elif python3 -m pip --version >/dev/null 2>&1; then \
+ rm -rf .venv; \
+ python3 -m pip install --quiet --target .oracles \
+ -r test/requirements.txt; \
+ echo "oracles ready: python3 with PYTHONPATH=.oracles"; \
+ else \
+ rm -rf .venv; \
+ echo "make oracles: this python3 can neither create a venv nor run pip."; \
+ echo " python3: $$(python3 --version 2>&1)"; \
+ echo " venv : $$(python3 -m venv /tmp/contra-venv-probe 2>&1 | tail -1)"; \
+ echo " pip : $$(python3 -m pip --version 2>&1 | tail -1)"; \
+ rm -rf /tmp/contra-venv-probe; \
+ echo " Install python3-venv or python3-pip, or set CONTRA_PYTHON to"; \
+ echo " an interpreter that already has test/requirements.txt."; \
+ exit 1; \
+ fi
# The browser crypto in WebKit, which is the engine this product can least
# afford to be wrong about and the one the Chromium rig never touches.
diff --git a/test/oracle.mjs b/test/oracle.mjs
index 6f7426e..a91cd19 100644
--- a/test/oracle.mjs
+++ b/test/oracle.mjs
@@ -31,6 +31,19 @@ export function oraclePython() {
return "python3";
}
+// oracleEnv adds .oracles/ to PYTHONPATH when `make oracles` had to fall back
+// to `pip --target` — which it does on a python3 with no ensurepip, where a
+// venv cannot be created at all. Harmless when the directory is absent.
+function oracleEnv() {
+ const target = join(repo, ".oracles");
+ if (!existsSync(target)) return process.env;
+ const existing = process.env.PYTHONPATH;
+ return {
+ ...process.env,
+ PYTHONPATH: existing ? `${target}:${existing}` : target,
+ };
+}
+
// run executes one oracle and returns its stdout.
//
// It does NOT skip when the packages are missing. A skipped oracle is
@@ -44,6 +57,7 @@ export function run(script, input) {
return execFileSync(python, [join(here, script)], {
input: JSON.stringify(input),
maxBuffer: 64 << 20,
+ env: oracleEnv(),
}).toString();
} catch (err) {
const text = String(err.stderr || err.message || "");