diff --git a/heart/checks/verify_install.sh b/heart/checks/verify_install.sh index 7cf95b1..99e59d4 100755 --- a/heart/checks/verify_install.sh +++ b/heart/checks/verify_install.sh @@ -15,6 +15,7 @@ # C conda install flow (python=3.12) + start_here.py + welcome.py # D pip install "autolens[optional]" resolves # E pip install autolens==2026.2.26.4 still installs by explicit pin +# F Colab simulation: fake google.colab + the injected setup cell + run a cell # # Each check creates its own throwaway venv / conda env and reports # PASS / FAIL / SKIP. A failed check never aborts the suite. Cleanup runs at @@ -55,6 +56,8 @@ Checks: C conda install flow (python=3.12) + start_here.py + welcome.py D pip install "autolens[optional]" resolves and imports E pip install autolens==2026.2.26.4 (yanked) installs by explicit pin + F Colab simulation: fake google.colab, run the injected setup cell + (pip bootstrap + setup_colab + workspace clone), then a notebook cell Default: run all checks. @@ -108,7 +111,7 @@ while [ $# -gt 0 ]; do REPORT_JSON="$2" shift 2 ;; - A|B|C|D|E|all) + A|B|C|D|E|F|all) REQUESTED_CHECKS+=("$1") shift ;; @@ -140,7 +143,7 @@ fi SELECTED=() for c in "${REQUESTED_CHECKS[@]}"; do if [ "$c" = "all" ]; then - SELECTED=(A B C D E) + SELECTED=(A B C D E F) break fi SELECTED+=("$c") @@ -229,7 +232,7 @@ print(f'autoconf: {autoconf.__version__}') step "cloning autolens_workspace" if ! git clone --depth 1 \ - https://github.com/Jammy2211/autolens_workspace.git "$workspace"; then + https://github.com/PyAutoLabs/autolens_workspace.git "$workspace"; then RESULTS+=("A|FAIL|workspace clone failed") deactivate return @@ -381,7 +384,7 @@ check_c() { step "cloning autolens_workspace" if ! git clone --depth 1 \ - https://github.com/Jammy2211/autolens_workspace.git "$workspace"; then + https://github.com/PyAutoLabs/autolens_workspace.git "$workspace"; then RESULTS+=("C|FAIL|workspace clone failed") return fi @@ -502,6 +505,114 @@ check_e() { fi } +# ----- check F: Colab simulation — the injected setup cell end-to-end ----- + +check_f() { + echo + echo "=== Check F: Colab simulation (fake google.colab + setup cell + notebook cell) ===" + + local venv="/tmp/autolens_verify_F_$TS" + local ws_dir="/tmp/colab_sim_workspace_F_$TS" + ARTEFACTS+=("$venv" "$ws_dir") + + step "creating venv with python3 at $venv" + if ! make_venv "$venv" python3; then + RESULTS+=("F|FAIL|could not create venv with python3") + return + fi + + # shellcheck source=/dev/null + source "$venv/bin/activate" + pip install --upgrade pip > /dev/null 2>&1 + + # Emulate the Colab base environment: Colab preinstalls the scientific + # stack and JAX; the injected setup cell installs the PyAuto packages + # --no-deps on top of that. + step "pip install $PIP_INSTALL_TARGET jax (emulating Colab's preinstalled env)" + if ! pip install "${PIP_INDEX_ARGS[@]}" "$PIP_INSTALL_TARGET" jax |& tee /tmp/F_pip.log; then + RESULTS+=("F|FAIL|pip install $PIP_INSTALL_TARGET jax failed") + tail_log "Check F pip output" "$(cat /tmp/F_pip.log 2>/dev/null)" + deactivate + return + fi + + # A fake google.colab package makes `import google.colab` — the probe both + # the injected cell and setup_colab use — succeed, activating the real + # on-Colab code path. + step "installing fake google.colab stub into the venv" + local site + site=$(python -c "import sysconfig; print(sysconfig.get_paths()['purelib'])") + mkdir -p "$site/google/colab" + touch "$site/google/__init__.py" "$site/google/colab/__init__.py" + # JAX detects Colab too: importing jax with google.colab present triggers + # `from google.colab import output` (jax._src.debugger.colab_lib), so the + # stub needs the submodule real Colab provides. + touch "$site/google/colab/output.py" + + # The driver is the injected notebook cell verbatim, plus assertions and + # one real cell from the imaging start_here. Exit 3 = SKIP (installed + # autoconf predates the setup_colab registry). + step "running the Colab bootstrap driver" + cat > /tmp/F_driver.py <<'PYEOF' +import os +import subprocess +import sys + +WS_DIR = os.environ["COLAB_SIM_WORKSPACE_DIR"] + +# --- the injected setup cell, verbatim --- +try: + import google.colab + + subprocess.check_call( + [sys.executable, "-m", "pip", "install", "autoconf", "--no-deps"] + ) +except ImportError: + pass + +from autoconf import setup_colab + +if not hasattr(setup_colab, "setup"): + print( + "SKIP: installed autoconf predates the setup_colab registry " + "(ships with the next release)" + ) + sys.exit(3) + +setup_colab.setup("autolens", raise_error_if_not_gpu=False, workspace_dir=WS_DIR) + +# --- assertions: clone happened, cwd moved into the workspace --- +assert os.getcwd() == WS_DIR, f"cwd is {os.getcwd()}, expected {WS_DIR}" +assert os.path.isdir(os.path.join(WS_DIR, "config")), "workspace config/ missing" +assert os.path.isdir(os.path.join(WS_DIR, "dataset")), "workspace dataset/ missing" + +# --- one real notebook cell (the top of imaging/start_here) --- +import autolens as al + +dataset = al.Imaging.from_fits( + data_path="dataset/imaging/simple/data.fits", + noise_map_path="dataset/imaging/simple/noise_map.fits", + psf_path="dataset/imaging/simple/psf.fits", + pixel_scales=0.1, +) +print(f"cell OK: loaded imaging dataset, shape {dataset.data.shape_native}") +PYEOF + local drv_rc=0 + COLAB_SIM_WORKSPACE_DIR="$ws_dir" python /tmp/F_driver.py |& tee /tmp/F_driver.log + drv_rc=${PIPESTATUS[0]} + + deactivate + + if [ "$drv_rc" -eq 0 ]; then + RESULTS+=("F|PASS|Colab bootstrap + workspace clone + notebook cell") + elif [ "$drv_rc" -eq 3 ]; then + RESULTS+=("F|SKIP|installed autoconf predates setup_colab registry (next release)") + else + RESULTS+=("F|FAIL|driver rc=$drv_rc") + tail_log "Check F driver output" "$(cat /tmp/F_driver.log 2>/dev/null)" + fi +} + # ----- runner ----- START_TS=$(date +%H:%M:%S) @@ -514,6 +625,7 @@ for letter in "${SELECTED[@]}"; do C) check_c ;; D) check_d ;; E) check_e ;; + F) check_f ;; *) echo "verify_install: unknown check '$letter'" >&2 ;; esac done diff --git a/skills/verify_install/verify_install.md b/skills/verify_install/verify_install.md index c52b2c2..4bbcc83 100644 --- a/skills/verify_install/verify_install.md +++ b/skills/verify_install/verify_install.md @@ -18,6 +18,7 @@ about cleanup if they ran with `--keep`. | C | The conda flow from `installation/conda.rst` works end-to-end (`conda create … python=3.12` → `pip install autolens` → clone workspace → run `welcome.py` + `start_here.py`). | | D | `pip install "autolens[optional]"` resolves cleanly and imports. | | E | `pip install autolens==2026.2.26.4` (a yanked release the docs reference) still installs by explicit pin. | +| F | The Colab bootstrap path end-to-end: a venv emulating Colab's preinstalled env (`autolens` + `jax` from PyPI), a fake `google.colab` package so the on-Colab branch activates, then the injected setup cell verbatim (`pip install autoconf --no-deps` → `setup_colab.setup("autolens")` → workspace clone at the release tag) and one real notebook cell (`al.Imaging.from_fits` on `dataset/imaging/simple`). SKIPs while the installed autoconf predates the `setup_colab` registry (self-activates at the next release). | A check that cannot run on the current host (interpreter missing, conda missing) is reported as **SKIP** and does not count toward overall failure. diff --git a/tests/test_verify_install_script.py b/tests/test_verify_install_script.py new file mode 100644 index 0000000..4cfcc27 --- /dev/null +++ b/tests/test_verify_install_script.py @@ -0,0 +1,50 @@ +"""tests/test_verify_install_script.py — fast argument/surface checks only. + +The checks themselves create venvs and install from PyPI (minutes-class, +network-bound) and are exercised by running `pyauto-heart verify_install`, +never from the unit suite. +""" + +import subprocess +from pathlib import Path + +SCRIPT = Path(__file__).resolve().parent.parent / "heart" / "checks" / "verify_install.sh" + + +def run(*args): + return subprocess.run( + ["bash", str(SCRIPT), *args], capture_output=True, text=True + ) + + +def test_bash_syntax(): + result = subprocess.run( + ["bash", "-n", str(SCRIPT)], capture_output=True, text=True + ) + assert result.returncode == 0, result.stderr + + +def test_help_lists_all_checks_including_colab_simulation(): + result = run("--help") + assert result.returncode == 0 + for letter in "ABCDEF": + assert f"\n {letter} " in result.stdout, f"check {letter} missing from help" + assert "Colab simulation" in result.stdout + + +def test_unknown_argument_rejected(): + result = run("--bogus") + assert result.returncode == 2 + assert "unknown argument" in result.stderr + + +def test_check_f_wired_into_selection_and_runner(): + text = SCRIPT.read_text() + assert "SELECTED=(A B C D E F)" in text + assert "F) check_f ;;" in text + assert "A|B|C|D|E|F|all)" in text + + +def test_no_stale_workspace_owner(): + # The workspaces moved Jammy2211 -> PyAutoLabs; clones must use the new owner. + assert "github.com/Jammy2211/autolens_workspace" not in SCRIPT.read_text()