facett-ops/tests/the_demo_can_import_itself.rs
//! **The Streamlit demo must be able to import its own package — it could not.**
//!
//! MEASURED on oden, 2026-09-06, running the whole chain end to end: `wrap-wasm` →
//! `publish-pip` → `publish-holger` → `holger-server` → `cargo xtask streamlit`. The
//! wheel installed from our own PEP 503 index exactly as a consumer would
//! ("Successfully installed facet-wrapped-web-0.1.19", "27 components"), Streamlit
//! bound :8501, `xtask` printed `✔ streamlit demo running` and exited 0, and `curl`
//! answered **200**.
//!
//! Every one of those 200s was a traceback page:
//!
//! ```text
//! ModuleNotFoundError: No module named 'facett_demo_python'
//! File "/app/facett_demo_python/app.py", line 20, in <module>
//! from facett_demo_python.buildinfo import build_info, version_string
//! ```
//!
//! `streamlit run /app/facett_demo_python/app.py` puts the SCRIPT's directory on
//! `sys.path` — `/app/facett_demo_python` — never the package's parent. The demo had
//! not run at all.
//!
//! # Four things were true at once, and each one alone looks like success
//!
//! 1. `test_demo()` — four functions from the launcher, in the same file and the same
//! image — already did `cd /app` and `PYTHONPATH=/app`. **The tested road set the
//! path and the shipped road did not.** One app, two launchers, and the correct one
//! is the one nobody demos from. A LAW 5 seam, so the fix is to take the line from
//! the sibling rather than to invent one.
//! 2. `py/tests/test_app.py` drives Streamlit's `AppTest` **in-process**, from a pytest
//! that already has `/app` on `PYTHONPATH`. It imports the app the one way the
//! container never does, and is green on a container that cannot start it.
//! 3. **HTTP 200 is not a check.** Streamlit renders its exception INTO the page and
//! answers 200, so a status-code health probe reads a crashed app as healthy.
//! 4. `xtask streamlit` boots the container and returns, so its `✔` only ever meant
//! "podman accepted the spec", never "the app is up".
//!
//! And the one test that WOULD have caught it cannot run: `py/tests/robot_streamlit.py`
//! (223 lines, whose docstring says it "drives the RUNNING APP in a real browser …
//! proves the whole chain actually renders") imports `playwright`, which is in no image
//! here and on no host here, and no Rust verb, test or document invokes the file.
//!
//! # What this test asserts, and what it cannot
//!
//! It reads the REAL launch string out of `holger::streamlit_launch_command()` — the
//! same one `start_streamlit` runs, not a copy — and holds it against its own sibling
//! `test_demo`'s script. It cannot prove the app renders; that needs the running
//! container, and the pre-flight import inside the script is what makes THAT failure
//! loud (with `set -eu` the container exits instead of serving a traceback page).
//!
//! # Since 0.2.0 the source-tree road is GONE, and the guards say so
//!
//! The demo is a wheel now (`facett-demo`, import `facett_demo`) with a console script.
//! Both launchers `pip install` the built wheels and neither puts a source tree on
//! `sys.path`: the app runs from site-packages, so "the app cannot import itself from
//! /app" cannot happen — and a launcher that re-introduced `PYTHONPATH=/app` would
//! quietly test the source instead of the release. The pre-flight import stays (a
//! broken INSTALL must still kill the container rather than serve a traceback page).
fn launch() -> String {
facett_ops::holger::streamlit_launch_command()
}
#[test]
fn the_launcher_runs_the_installed_wheel_not_a_source_tree() {
let s = launch();
assert!(s.contains("pip install"), "the launcher installs nothing:\n{s}");
assert!(s.contains("'facett-demo[korp]'"), "the launcher does not install the demo WHEEL:\n{s}");
assert!(s.contains("exec facett-demo run"), "the launcher does not start the installed console script:\n{s}");
for banned in ["PYTHONPATH", "cd /app", "streamlit run /app"] {
assert!(!s.contains(banned), "the launcher still reaches for a source tree (`{banned}`):\n{s}");
}
}
#[test]
fn the_launcher_proves_the_app_imports_before_it_serves_anything() {
// Under `set -eu` the container dies with the import error instead of binding a
// port and serving tracebacks with HTTP 200.
let s = launch();
let import = facett_ops::holger::STREAMLIT_PREFLIGHT_IMPORT;
assert!(
s.contains(import),
"the launcher does not pre-flight `import {import}` before starting the app:\n{s}"
);
let at_import = s.find(import).expect("checked above");
let at_exec = s.find("exec facett-demo run").expect("the launcher execs the console script");
assert!(at_import < at_exec, "the pre-flight import runs after the app is exec'd, which is never:\n{s}");
}
#[test]
fn the_preflight_does_not_import_the_app_module_itself() {
// `facett_demo/app.py` calls `main()` at import time and has no `__main__` guard —
// deliberately, because Streamlit re-executes it top-to-bottom on every rerun.
let import = facett_ops::holger::STREAMLIT_PREFLIGHT_IMPORT;
assert!(!import.ends_with(".app"), "the pre-flight imports the app module: {import}");
assert!(
import.starts_with("facett_demo."),
"the pre-flight must reach INSIDE the package (the pane imports are most of the \
app's import chain): {import}"
);
}
#[test]
fn the_two_launchers_of_this_one_app_install_the_same_thing() {
// `test_demo` and `streamlit_script` are two ways into the same app in the same
// image; the suite must exercise exactly what the demo serves.
let shipped = launch();
let tested = facett_ops::holger::test_demo_command();
let install = facett_ops::holger::WheelSource::Local.pip_install("facett-demo[korp]");
assert!(shipped.contains(&install), "shipped launcher:\n{shipped}");
assert!(tested.contains(&install), "test launcher:\n{tested}");
for t in [&shipped, &tested] {
assert!(!t.contains("PYTHONPATH"), "a launcher puts a source tree on the path:\n{t}");
}
}