mirror of
https://github.com/saymrwulf/QuantumLearning.git
synced 2026-05-14 20:58:00 +00:00
Harden Jupyter state lifecycle and cleanup
This commit is contained in:
parent
ef778073de
commit
377c96de8a
9 changed files with 268 additions and 95 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -4,6 +4,8 @@
|
|||
.ipython/
|
||||
.jupyter_runtime/
|
||||
.jupyter_data/lab/workspaces/
|
||||
.jupyter_data/nbsignatures.db
|
||||
.jupyter_data/notebook_secret
|
||||
__pycache__/
|
||||
.pytest_cache/
|
||||
.ruff_cache/
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -1,18 +0,0 @@
|
|||
SrXNuCCg+wrEA1L1jVumnXf3ChsNATdtqH3UiZud+nbMUHxjUqqRRPcHmyufdB8eMh0TPu+cIe8Z
|
||||
t0caRkarRaz/ouQ9rwyFjxgt4UMFFRHXqAofkPH4zF89S4s83U+C2mZ911tUoAr7rql6lW0UDrro
|
||||
U4RYWQFd356AfiDwmGhC2AVO7/Yt2NmOgawxWvqsuAaIzSIjDvFY/kU+UNu2nyW7FrhumjgmBp93
|
||||
Hk4BOZb8xnEcz24E9wVQ/9Fn2XXcIFGB+DowTQPkai3xIo2zx1o5+6+Vbsq1b7dfESlliXj6Nh0o
|
||||
R3haoHFr4B6Q/biz7YMZq3njh2Mq/6NzoK71exnJ4vjm6/ucLlX94DEeyvlsK8XtBf+rO/Ux2kQ4
|
||||
P8U3VqqEcsFE36k7jVGqMv00m/PKL1W4A/C2HdutD6WsGDu8F6+jI3lKgrWVcylwqZf0t0TQ9zKf
|
||||
pqlFO1PmsIubDRwd6rfiZbBC8S1fMzBMWfs4jM2FVWALIz+fQZZ2pUBxmMPsk3M7SmmKw8Zyc/nj
|
||||
U+SroBGM1eb4W7UERCCsa990B00zmNCz5s/VJkrn9TVwAT48vVuxphKPxMeyiwtHuEHiSkN2fb/l
|
||||
8aaaL1DiFk8KSogKsJZ9JgEpnqMXqxEJ6JF+IRJrfHHD8jPuatXjPqVjdMgpSnWG6ngrbAd2cw4A
|
||||
lHOqJG8EuRieYh/vx/xc+b8+LumXol0m1Gz7W1pVRXXrh+rVrMk3eUJO4yAbcXIZM3/Sqe8N7ZAH
|
||||
yxtzFCce5mgNGdk+1I3GfLTU999fbdERodpsnlKoSX7eB+fT+8z68kucSx4OYswIaXLCk58Fjz+1
|
||||
ZuvO8SsiNoSoQ9tIv1B0aKFGST4BLyQ1NccTIJjhAGiE65jzpDuaqRl9HyTXF3Wj7TeSlOYbOJLh
|
||||
1H3CupTXl3PjH9i6K1zrx4TrOEe5J4b/iaIXlw8PzUSBL/yeaGHou2NzW2oTOQ1Ev9P9n9SfyIU3
|
||||
BuCvqiUTbm2k8Ktk9g7mcHUw+63syE0b9CTPXzdAlvI30j+K2s+zs6fWtpZXsIy+Xz9rcBX3Nztm
|
||||
nl/Z3yLa3+khqS7hY/kxgURjvm0XxPwuGoWt9dJFkkoQXLiOrzKiCD773qwrLHEyHx0TI4ntfFV0
|
||||
TducvMBpiAvaN1KnlJU8K6yyeqZdJytxJngZGcRgtz/IjySByb75zLpInoHf0HKxgjPfSHvXCVlP
|
||||
vuKq5a16PU7Dlj01u3szIR16bh/qXeKn3h89gslw0fR+ILWnzTuy8VlqtbgfSW+Ohj5X5VCok6k/
|
||||
dr21sWY2J1GlZmu9N/4Hpj49kORRj/lb5UXZIGsqxSeK4yHDGTGR1YBK+GPmARkzzvF/qDY1FQ==
|
||||
|
|
@ -23,6 +23,13 @@ Use this the first time you open the platform, or anytime you want a clean local
|
|||
bash ./scripts/app.sh start --open
|
||||
```
|
||||
|
||||
If JupyterLab later looks haunted by old tabs or stale session state, reset the repo-local Jupyter state and start again:
|
||||
|
||||
```bash
|
||||
bash ./scripts/app.sh reset-state
|
||||
bash ./scripts/app.sh start --open
|
||||
```
|
||||
|
||||
4. Open the localhost URL printed by the script.
|
||||
5. Confirm the kernel in the top-right corner is `QuantumLearning (.venv)`.
|
||||
6. Run `Kernel -> Restart Kernel and Run All Cells...` inside `notebooks/COURSE_BLUEPRINT.ipynb`.
|
||||
|
|
|
|||
|
|
@ -77,6 +77,7 @@ Other normal consumer commands:
|
|||
bash ./scripts/app.sh status
|
||||
bash ./scripts/app.sh restart
|
||||
bash ./scripts/app.sh stop
|
||||
bash ./scripts/app.sh reset-state
|
||||
bash ./scripts/app.sh logs -f
|
||||
```
|
||||
|
||||
|
|
@ -106,11 +107,18 @@ It reports:
|
|||
- whether `.venv/` exists and which Python version it uses
|
||||
- whether the project-local Playwright browser runtime is installed
|
||||
- whether a repo-local Jupyter server is running and, if so, its full URL
|
||||
- whether saved Jupyter UI state is still present and how to reset it
|
||||
|
||||
If you lose the Jupyter URL, `bash ./scripts/app.sh status` is the fastest recovery path.
|
||||
|
||||
In restricted automation sandboxes, the script may report that the live process probe is unavailable. In a normal terminal on your Mac, it will report the running server directly when the runtime file and process are both visible.
|
||||
|
||||
If JupyterLab reopens old tabs or appears to remember stale notebook activity, that is usually saved workspace state rather than a still-running repo-local server. Clear it with:
|
||||
|
||||
```bash
|
||||
bash ./scripts/app.sh reset-state
|
||||
```
|
||||
|
||||
## Validation Modes
|
||||
|
||||
Quick local validation:
|
||||
|
|
@ -176,6 +184,7 @@ If something feels wrong, use this order:
|
|||
2. `bash ./scripts/app.sh validate --quick`
|
||||
3. if needed, `bash ./scripts/app.sh validate --full`
|
||||
4. restart Jupyter with `bash ./scripts/app.sh restart`
|
||||
5. if the UI still looks stale, run `bash ./scripts/app.sh reset-state`
|
||||
|
||||
If `.venv/` is missing or broken, re-run:
|
||||
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ bash ./scripts/app.sh start --open
|
|||
bash ./scripts/app.sh status
|
||||
bash ./scripts/app.sh restart
|
||||
bash ./scripts/app.sh stop
|
||||
bash ./scripts/app.sh reset-state
|
||||
```
|
||||
|
||||
If you want a fast operational check immediately after bootstrap:
|
||||
|
|
@ -96,6 +97,12 @@ If you want to inspect the live log:
|
|||
bash ./scripts/app.sh logs -f
|
||||
```
|
||||
|
||||
If JupyterLab reopens old tabs, looks like notebooks are still running, or you want a true clean slate for the repo-local Jupyter state:
|
||||
|
||||
```bash
|
||||
bash ./scripts/app.sh reset-state
|
||||
```
|
||||
|
||||
Run the tests:
|
||||
|
||||
```bash
|
||||
|
|
|
|||
231
scripts/app.sh
231
scripts/app.sh
|
|
@ -22,6 +22,9 @@ Commands:
|
|||
stop
|
||||
Stop the repo-local Jupyter server for this project.
|
||||
|
||||
reset-state
|
||||
Remove repo-local Jupyter runtime and saved UI state files.
|
||||
|
||||
restart [--port PORT] [--open] [--foreground]
|
||||
Restart the repo-local Jupyter server.
|
||||
|
||||
|
|
@ -46,6 +49,7 @@ Examples:
|
|||
bash ./scripts/app.sh restart
|
||||
bash ./scripts/app.sh status
|
||||
bash ./scripts/app.sh stop
|
||||
bash ./scripts/app.sh reset-state
|
||||
bash ./scripts/app.sh validate --quick
|
||||
EOF
|
||||
}
|
||||
|
|
@ -224,6 +228,90 @@ if verbose:
|
|||
PY
|
||||
}
|
||||
|
||||
purge_runtime_artifacts() {
|
||||
"$ROOT_DIR/.venv/bin/python" - <<'PY' "$ROOT_DIR" "${1:-0}"
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
root = Path(sys.argv[1]).resolve()
|
||||
verbose = sys.argv[2] == "1"
|
||||
runtime_dir = root / ".jupyter_runtime"
|
||||
patterns = (
|
||||
"jpserver-*.json",
|
||||
"jpserver-*-open.html",
|
||||
"nbserver-*.json",
|
||||
"nbserver-*-open.html",
|
||||
"kernel-*.json",
|
||||
)
|
||||
removed = []
|
||||
|
||||
for pattern in patterns:
|
||||
for path in sorted(runtime_dir.glob(pattern)):
|
||||
if path.exists():
|
||||
path.unlink()
|
||||
removed.append(path)
|
||||
|
||||
if verbose:
|
||||
for path in removed:
|
||||
print(path)
|
||||
PY
|
||||
}
|
||||
|
||||
purge_saved_ui_state() {
|
||||
"$ROOT_DIR/.venv/bin/python" - <<'PY' "$ROOT_DIR" "${1:-0}"
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
root = Path(sys.argv[1]).resolve()
|
||||
verbose = sys.argv[2] == "1"
|
||||
data_dir = root / ".jupyter_data"
|
||||
paths = [
|
||||
data_dir / "nbsignatures.db",
|
||||
data_dir / "notebook_secret",
|
||||
]
|
||||
workspace_dir = data_dir / "lab" / "workspaces"
|
||||
removed = []
|
||||
|
||||
for path in paths:
|
||||
if path.exists():
|
||||
path.unlink()
|
||||
removed.append(path)
|
||||
|
||||
for path in sorted(workspace_dir.glob("*.jupyterlab-workspace")):
|
||||
if path.exists():
|
||||
path.unlink()
|
||||
removed.append(path)
|
||||
|
||||
if verbose:
|
||||
for path in removed:
|
||||
print(path)
|
||||
PY
|
||||
}
|
||||
|
||||
print_ui_state_summary() {
|
||||
local workspace_count="0"
|
||||
if compgen -G "$ROOT_DIR/.jupyter_data/lab/workspaces/*.jupyterlab-workspace" >/dev/null; then
|
||||
workspace_count="$(find "$ROOT_DIR/.jupyter_data/lab/workspaces" -maxdepth 1 -name '*.jupyterlab-workspace' | wc -l | tr -d ' ')"
|
||||
fi
|
||||
|
||||
local trust_db="absent"
|
||||
local notebook_secret="absent"
|
||||
[[ -f "$ROOT_DIR/.jupyter_data/nbsignatures.db" ]] && trust_db="present"
|
||||
[[ -f "$ROOT_DIR/.jupyter_data/notebook_secret" ]] && notebook_secret="present"
|
||||
|
||||
if [[ "$workspace_count" == "0" && "$trust_db" == "absent" && "$notebook_secret" == "absent" ]]; then
|
||||
echo "Jupyter UI state: clean"
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "Jupyter UI state: workspaces=$workspace_count, trust-db=$trust_db, notebook-secret=$notebook_secret"
|
||||
echo " Clean slate: bash ./scripts/app.sh reset-state"
|
||||
}
|
||||
|
||||
have_running_server() {
|
||||
local json_path
|
||||
cleanup_stale_runtime_files >/dev/null
|
||||
|
|
@ -256,24 +344,34 @@ stop_server() {
|
|||
ensure_venv
|
||||
ensure_dirs
|
||||
|
||||
local json_path pid
|
||||
local json_path pid removed_paths
|
||||
cleanup_stale_runtime_files >/dev/null
|
||||
json_path="$(server_json_for_root || true)"
|
||||
if [[ -z "$json_path" ]]; then
|
||||
echo "No repo-local Jupyter runtime file found."
|
||||
removed_paths="$(purge_runtime_artifacts 1 || true)"
|
||||
if [[ -n "$removed_paths" ]]; then
|
||||
echo "Removed stale runtime files:"
|
||||
echo "$removed_paths"
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
|
||||
pid="$(server_field "$json_path" "pid")"
|
||||
if ! kill -0 "$pid" >/dev/null 2>&1; then
|
||||
local removed_paths
|
||||
removed_paths="$(cleanup_stale_runtime_files 1 || true)"
|
||||
local extra_removed
|
||||
extra_removed="$(purge_runtime_artifacts 1 || true)"
|
||||
echo "Jupyter runtime file exists, but the process is not running."
|
||||
echo "Runtime file: $json_path"
|
||||
if [[ -n "$removed_paths" ]]; then
|
||||
echo "Removed stale runtime files:"
|
||||
echo "$removed_paths"
|
||||
fi
|
||||
if [[ -n "$extra_removed" ]]; then
|
||||
echo "Removed leftover runtime artifacts:"
|
||||
echo "$extra_removed"
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
|
||||
|
|
@ -281,6 +379,7 @@ stop_server() {
|
|||
for _ in $(seq 1 30); do
|
||||
if ! kill -0 "$pid" >/dev/null 2>&1; then
|
||||
cleanup_stale_runtime_files >/dev/null
|
||||
purge_runtime_artifacts >/dev/null
|
||||
echo "Stopped Jupyter (pid $pid)."
|
||||
return 0
|
||||
fi
|
||||
|
|
@ -317,6 +416,8 @@ start_server() {
|
|||
fi
|
||||
fi
|
||||
|
||||
purge_runtime_artifacts >/dev/null
|
||||
|
||||
if [[ "$foreground" == "1" ]]; then
|
||||
exec "$ROOT_DIR/.venv/bin/jupyter" lab \
|
||||
--no-browser \
|
||||
|
|
@ -378,6 +479,124 @@ validate_command() {
|
|||
bash "$ROOT_DIR/scripts/run_validation.sh" "$@"
|
||||
}
|
||||
|
||||
reset_state_command() {
|
||||
ensure_venv
|
||||
ensure_dirs
|
||||
|
||||
if have_running_server; then
|
||||
stop_server >/dev/null
|
||||
else
|
||||
cleanup_stale_runtime_files >/dev/null
|
||||
purge_runtime_artifacts >/dev/null
|
||||
fi
|
||||
|
||||
local removed_runtime removed_ui
|
||||
removed_runtime="$(purge_runtime_artifacts 1 || true)"
|
||||
removed_ui="$(purge_saved_ui_state 1 || true)"
|
||||
|
||||
if [[ -z "$removed_runtime" && -z "$removed_ui" ]]; then
|
||||
echo "Jupyter state was already clean."
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ -n "$removed_runtime" ]]; then
|
||||
echo "Removed runtime artifacts:"
|
||||
echo "$removed_runtime"
|
||||
fi
|
||||
|
||||
if [[ -n "$removed_ui" ]]; then
|
||||
echo "Removed saved UI state:"
|
||||
echo "$removed_ui"
|
||||
fi
|
||||
}
|
||||
|
||||
status_command() {
|
||||
cd "$ROOT_DIR"
|
||||
ensure_dirs
|
||||
|
||||
local branch commit git_state
|
||||
branch="$(git branch --show-current)"
|
||||
commit="$(git rev-parse --short HEAD)"
|
||||
if [[ -n "$(git status --short)" ]]; then
|
||||
git_state="dirty"
|
||||
else
|
||||
git_state="clean"
|
||||
fi
|
||||
|
||||
echo "Root: $ROOT_DIR"
|
||||
echo "Git: $branch @ $commit ($git_state)"
|
||||
|
||||
if [[ -x "$ROOT_DIR/.venv/bin/python" ]]; then
|
||||
local python_version removed_runtime json_path pid url
|
||||
python_version="$("$ROOT_DIR/.venv/bin/python" -c 'import sys; print(sys.version.split()[0])')"
|
||||
echo "Venv: present (Python $python_version)"
|
||||
cleanup_stale_runtime_files >/dev/null
|
||||
if ! have_running_server; then
|
||||
removed_runtime="$(purge_runtime_artifacts 1 || true)"
|
||||
else
|
||||
removed_runtime=""
|
||||
fi
|
||||
json_path="$(server_json_for_root || true)"
|
||||
|
||||
if compgen -G "$ROOT_DIR/.playwright-browsers/chromium-*" >/dev/null; then
|
||||
echo "Browser runtime: installed"
|
||||
else
|
||||
echo "Browser runtime: missing"
|
||||
fi
|
||||
|
||||
if [[ -f "$LOG_FILE" ]]; then
|
||||
echo "Jupyter log: $LOG_FILE"
|
||||
else
|
||||
echo "Jupyter log: none yet"
|
||||
fi
|
||||
|
||||
if [[ -n "$json_path" ]]; then
|
||||
pid="$(server_field "$json_path" "pid")"
|
||||
url="$(server_url_from_json "$json_path")"
|
||||
if kill -0 "$pid" >/dev/null 2>&1; then
|
||||
echo "Jupyter: running (pid $pid)"
|
||||
echo " URL: $url"
|
||||
echo " Runtime file: $json_path"
|
||||
else
|
||||
echo "Jupyter: runtime file exists but process is not running"
|
||||
echo " Runtime file: $json_path"
|
||||
fi
|
||||
else
|
||||
echo "Jupyter: no repo-local runtime file found"
|
||||
if [[ -n "${removed_runtime:-}" ]]; then
|
||||
echo " Purged stale runtime artifacts:"
|
||||
echo "$removed_runtime"
|
||||
fi
|
||||
fi
|
||||
|
||||
print_ui_state_summary
|
||||
else
|
||||
echo "Venv: missing"
|
||||
if compgen -G "$ROOT_DIR/.playwright-browsers/chromium-*" >/dev/null; then
|
||||
echo "Browser runtime: installed"
|
||||
else
|
||||
echo "Browser runtime: missing"
|
||||
fi
|
||||
if [[ -f "$LOG_FILE" ]]; then
|
||||
echo "Jupyter log: $LOG_FILE"
|
||||
else
|
||||
echo "Jupyter log: none yet"
|
||||
fi
|
||||
echo "Jupyter: repo-local environment is missing"
|
||||
print_ui_state_summary
|
||||
fi
|
||||
|
||||
echo "Consumer:"
|
||||
echo " Start: bash ./scripts/app.sh start --open"
|
||||
echo " Stop: bash ./scripts/app.sh stop"
|
||||
echo " Restart: bash ./scripts/app.sh restart"
|
||||
echo " Reset: bash ./scripts/app.sh reset-state"
|
||||
echo " Logs: bash ./scripts/app.sh logs -f"
|
||||
echo "Validation:"
|
||||
echo " Quick: bash ./scripts/app.sh validate --quick"
|
||||
echo " Full: bash ./scripts/app.sh validate --full"
|
||||
}
|
||||
|
||||
start_command() {
|
||||
local port="8888"
|
||||
local foreground="0"
|
||||
|
|
@ -441,11 +660,17 @@ case "$COMMAND" in
|
|||
stop)
|
||||
stop_server
|
||||
;;
|
||||
reset-state)
|
||||
reset_state_command "$@"
|
||||
;;
|
||||
restart)
|
||||
restart_command "$@"
|
||||
;;
|
||||
status)
|
||||
exec bash "$ROOT_DIR/scripts/project_status.sh"
|
||||
status_command
|
||||
;;
|
||||
_status_internal)
|
||||
status_command
|
||||
;;
|
||||
url)
|
||||
ensure_venv
|
||||
|
|
|
|||
77
scripts/project_status.sh
Executable file → Normal file
77
scripts/project_status.sh
Executable file → Normal file
|
|
@ -8,8 +8,8 @@ usage() {
|
|||
Usage: bash ./scripts/project_status.sh
|
||||
|
||||
Print the current local operational state of the QuantumLearning repo:
|
||||
git state, repo-local Python environment, Playwright browser runtime, and
|
||||
the latest repo-local Jupyter server if one is running.
|
||||
git state, repo-local Python environment, Playwright browser runtime,
|
||||
repo-local Jupyter status, and saved Jupyter UI state.
|
||||
EOF
|
||||
}
|
||||
|
||||
|
|
@ -18,75 +18,4 @@ if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
|
|||
exit 0
|
||||
fi
|
||||
|
||||
cd "$ROOT_DIR"
|
||||
|
||||
BRANCH="$(git branch --show-current)"
|
||||
COMMIT="$(git rev-parse --short HEAD)"
|
||||
if [[ -n "$(git status --short)" ]]; then
|
||||
GIT_STATE="dirty"
|
||||
else
|
||||
GIT_STATE="clean"
|
||||
fi
|
||||
|
||||
echo "Root: $ROOT_DIR"
|
||||
echo "Git: $BRANCH @ $COMMIT ($GIT_STATE)"
|
||||
|
||||
if [[ -x "$ROOT_DIR/.venv/bin/python" ]]; then
|
||||
PYTHON_VERSION="$("$ROOT_DIR/.venv/bin/python" -c 'import sys; print(sys.version.split()[0])')"
|
||||
echo "Venv: present (Python $PYTHON_VERSION)"
|
||||
else
|
||||
echo "Venv: missing"
|
||||
fi
|
||||
|
||||
if compgen -G "$ROOT_DIR/.playwright-browsers/chromium-*" >/dev/null; then
|
||||
echo "Browser runtime: installed"
|
||||
else
|
||||
echo "Browser runtime: missing"
|
||||
fi
|
||||
|
||||
if [[ -f "$ROOT_DIR/.logs/jupyterlab.log" ]]; then
|
||||
echo "Jupyter log: $ROOT_DIR/.logs/jupyterlab.log"
|
||||
else
|
||||
echo "Jupyter log: none yet"
|
||||
fi
|
||||
|
||||
if compgen -G "$ROOT_DIR/.jupyter_runtime/jpserver-*.json" >/dev/null; then
|
||||
LATEST_SERVER_JSON="$(ls -t "$ROOT_DIR"/.jupyter_runtime/jpserver-*.json | head -n 1)"
|
||||
"$ROOT_DIR/.venv/bin/python" - <<'PY' "$LATEST_SERVER_JSON"
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
path = Path(sys.argv[1])
|
||||
data = json.loads(path.read_text())
|
||||
pid = int(data["pid"])
|
||||
|
||||
try:
|
||||
os.kill(pid, 0)
|
||||
except ProcessLookupError:
|
||||
print("Jupyter: runtime file exists but process is not running")
|
||||
print(f" Runtime file: {path}")
|
||||
except PermissionError:
|
||||
print("Jupyter: runtime file present; live process probe is unavailable in this environment")
|
||||
print(f" URL: {data['url']}lab/tree/notebooks/COURSE_BLUEPRINT.ipynb?token={data['token']}")
|
||||
print(f" Runtime file: {path}")
|
||||
else:
|
||||
print(f"Jupyter: running (pid {pid})")
|
||||
print(f" URL: {data['url']}lab/tree/notebooks/COURSE_BLUEPRINT.ipynb?token={data['token']}")
|
||||
print(f" Runtime file: {path}")
|
||||
PY
|
||||
else
|
||||
echo "Jupyter: no repo-local runtime file found"
|
||||
fi
|
||||
|
||||
echo "Consumer:"
|
||||
echo " Start: bash ./scripts/app.sh start --open"
|
||||
echo " Stop: bash ./scripts/app.sh stop"
|
||||
echo " Restart: bash ./scripts/app.sh restart"
|
||||
echo " Logs: bash ./scripts/app.sh logs -f"
|
||||
echo "Validation:"
|
||||
echo " Quick: bash ./scripts/app.sh validate --quick"
|
||||
echo " Full: bash ./scripts/app.sh validate --full"
|
||||
exec bash "$ROOT_DIR/scripts/app.sh" _status_internal
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ def test_project_status_script_runs():
|
|||
assert result.returncode == 0
|
||||
assert "Git:" in result.stdout
|
||||
assert "Validation:" in result.stdout
|
||||
assert "Jupyter UI state:" in result.stdout
|
||||
|
||||
|
||||
def test_app_status_runs():
|
||||
|
|
@ -69,12 +70,15 @@ def test_app_status_runs():
|
|||
|
||||
assert result.returncode == 0
|
||||
assert "Consumer:" in result.stdout
|
||||
assert "Reset:" in result.stdout
|
||||
|
||||
|
||||
def test_app_script_uses_detached_launch_and_stale_runtime_cleanup():
|
||||
text = (project_root() / "scripts" / "app.sh").read_text()
|
||||
|
||||
assert "cleanup_stale_runtime_files" in text
|
||||
assert "purge_runtime_artifacts" in text
|
||||
assert "reset-state" in text
|
||||
assert "nohup " in text
|
||||
|
||||
|
||||
|
|
@ -83,6 +87,7 @@ def test_operations_docs_use_tested_command_forms():
|
|||
expected_commands = [
|
||||
"bash ./scripts/app.sh bootstrap",
|
||||
"bash ./scripts/app.sh status",
|
||||
"bash ./scripts/app.sh reset-state",
|
||||
"bash ./scripts/app.sh validate --quick",
|
||||
]
|
||||
|
||||
|
|
@ -90,3 +95,10 @@ def test_operations_docs_use_tested_command_forms():
|
|||
text = (root / relative_path).read_text()
|
||||
for command in expected_commands:
|
||||
assert command in text
|
||||
|
||||
|
||||
def test_jupyter_runtime_noise_is_ignored():
|
||||
text = (project_root() / ".gitignore").read_text()
|
||||
|
||||
assert ".jupyter_data/nbsignatures.db" in text
|
||||
assert ".jupyter_data/notebook_secret" in text
|
||||
|
|
|
|||
Loading…
Reference in a new issue