Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/dwarves

dwarves-gui/tests/robot_two_doors.rs

//! **ROBOT-UI: one road, two doors** — the line the ⚒ node menu shows is the line the
//! CLI door judges, and both reach the same decision (laws B and C across the doors),
//! on the constellation's REAL declaration: the installer that consumes a donor kernel
//! with `kernel.*` opts.
//!
//! The claim, driven end to end without compiling anything:
//!
//! 1. ⚒ resolves the real recipe, picks the installer, selects its `kärna` node and
//!    CLICKS one kernel symbol off — through the tree's judge (the same `try_toggle`
//!    the CLI's `--opt kernel.<symbol>=off` goes through);
//! 2. the menu now shows the flag the click equals, `--opt kernel.<symbol>=off`
//!    (law C), and the flags ⚒ would hand BYGG are exactly the flags on screen;
//! 3. those flags, fed VERBATIM to the CLI door's judge
//!    (`dwarves::buildthing::resolve_kernel_axis` — what `dwarves buildthing <repo>
//!    --variant <installer> --opt …` resolves before it spawns anything), reach the
//!    SAME decision fragment the model reaches for the pane's state
//!    (`SettingsTree::kernel_decision`) — the same bytes, either door (law E's
//!    fragment-sha follows).
//!
//! SKIPS, saying so, when the real declaration is not on this disk: a fixture that
//! invented a donor would prove the fixture. A real BUILD is not run here — that is a
//! kernel compile — but the decision that build would run from is what is compared.

#![cfg(not(target_arch = "wasm32"))]

use std::collections::BTreeMap;

use facett_core::Facet;
use nornir_build_thing::tree::{SettingsTree, KERNEL_PATH};
use nornir_robotui::egui_kittest::Harness;
use nornir_robotui::{Observable, RobotSession};
use serde_json::{json, Value};

use dwarves::build_thing::ConstellationBuilds;
use dwarves_gui::build_forge::DeclaredForge;
use dwarves_gui::build_scene::BuildScene;

struct SceneApp(BuildScene);

impl Observable for SceneApp {
    fn state_json(&self) -> Value {
        Facet::state_json(&self.0)
    }
}

#[test]
fn the_flags_on_the_node_menu_reach_the_same_decision_through_the_cli_door() {
    // ── the REAL declaration: an installer with a kernel donor edge ─────────────
    // The constellation root: `DWARVES_CONSTELLATION` when set, else the directory this checkout
    // sits in (the test's cwd is the crate, whose parent is the repo, not the root).
    let root = std::env::var_os("DWARVES_CONSTELLATION")
        .map(std::path::PathBuf::from)
        .unwrap_or_else(|| std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../.."));
    let root = root.canonicalize().unwrap_or(root);
    let builds = ConstellationBuilds::discover(&root);
    let Some((repo, installer, donor)) = builds.repos.iter().find_map(|r| {
        r.set.installers.iter().find_map(|i| {
            i.kernel_donor.as_ref().map(|d| {
                (
                    r.repo.clone(),
                    i.name.clone(),
                    (d.repo.clone(), d.kernel.clone()),
                )
            })
        })
    }) else {
        eprintln!(
            "SKIP: no checkout under {} declares an installer with a kernel_donor",
            root.display()
        );
        return;
    };
    if builds.repo(&donor.0).is_none() {
        eprintln!(
            "SKIP: the donor `{}` is not checked out beside `{repo}`",
            donor.0
        );
        return;
    }
    eprintln!(
        "real declaration: {repo} · {installer} → {}/{}",
        donor.0, donor.1
    );
    let repo_root = builds
        .repo(&repo)
        .map(|r| r.root.clone())
        .expect("the repo is on disk");
    let set = builds
        .repo(&repo)
        .map(|r| r.set.clone())
        .expect("its declaration");

    // ── DOOR 1: ⚒ ─────────────────────────────────────────────────────────────
    let scene = BuildScene::new(Box::new(DeclaredForge::at(root.clone())));
    let harness = Harness::builder()
        .with_size(nornir_robotui::egui::Vec2::new(2400.0, 1600.0))
        .build_ui_state(
            |ui, app: &mut SceneApp| Facet::ui(&mut app.0, ui),
            SceneApp(scene),
        );
    let mut robot = RobotSession::new(harness, "dwarves-gui");
    robot.run(2);
    robot
        .type_into("artefakt", &repo)
        .expect("the artefakt field");
    robot.click_label("sök").expect("sök");
    robot.run(2);
    // The installer is a delivery column of the row it packs: select its node by id
    // (the headless twin of the canvas click), which picks row + column.
    robot.harness_mut().state_mut().0.select_node(&installer);
    robot.run(2);
    assert_eq!(
        robot.state_at("/menu/variant"),
        Some(json!(installer)),
        "the installer is the menu's subject"
    );
    // Open the `kärna` node: its menu lists the symbols.
    let kid = format!("{installer}/{KERNEL_PATH}");
    robot.harness_mut().state_mut().0.select_node(&kid);
    robot.run(2);
    assert_eq!(robot.state_at("/menu/node"), Some(json!(KERNEL_PATH)));
    let k = robot
        .state_at("/tree/roots")
        .and_then(|r| r.as_array().cloned())
        .and_then(|roots| roots.into_iter().find(|n| n["path"] == json!(KERNEL_PATH)))
        .expect("the kärna node on the installer");
    assert_eq!(
        k["edge"],
        json!(format!("{}/{} : kernel", donor.0, donor.1)),
        "the donor edge, typed"
    );
    // A symbol that is ON and not locked — one the judge will let go.
    let symbol = k["children"]
        .as_array()
        .unwrap()
        .iter()
        .find(|c| {
            c["kind"] == json!("feature")
                && c["value"] == json!("true")
                && c["locked_by"].as_array().is_some_and(|l| l.is_empty())
        })
        .cloned()
        .expect("an unlocked symbol in the pick");
    let path = symbol["path"].as_str().unwrap().to_string();
    let label = symbol["label"].as_str().unwrap().to_string();
    robot
        .click_label(&label)
        .unwrap_or_else(|e| panic!("the symbol `{label}` is a checkbox in the kärna menu: {e}"));
    robot.run(3);

    // LAW C: the flag beside the node is the one the click equals.
    let want_flag = format!("--opt {path}=off");
    let k = robot
        .state_at("/tree/roots")
        .and_then(|r| r.as_array().cloned())
        .and_then(|roots| roots.into_iter().find(|n| n["path"] == json!(KERNEL_PATH)))
        .unwrap();
    let after = k["children"]
        .as_array()
        .unwrap()
        .iter()
        .find(|c| c["path"] == json!(path))
        .cloned()
        .unwrap();
    assert_eq!(
        after["flag"],
        json!(want_flag),
        "the flag follows the click: {after} — menu note: {:?}",
        robot.state_at("/menu/note")
    );
    assert_eq!(after["value"], json!("false"));
    // …and what BYGG would hand the build: the same string, off the settings map.
    assert_eq!(
        robot.state_at(&format!("/settings/{path}")),
        Some(json!("false")),
        "the click landed in the settings BYGG carries"
    );

    // ── DOOR 2: the CLI's judge, fed the pane's flag verbatim ──────────────────
    let opt = want_flag.trim_start_matches("--opt ").to_string();
    let (decision, rest) = dwarves::buildthing::resolve_kernel_axis(
        &set,
        &repo_root,
        Some(&installer),
        &[opt.clone()],
    )
    .unwrap_or_else(|e| panic!("the CLI door refused the pane's own flag `{opt}`: {e:#}"));
    assert!(
        rest.is_empty(),
        "nothing but the kernel opt was passed: {rest:?}"
    );
    let decision = decision.expect("the installer consumes a kernel, so the opt decides one");
    let option = path.strip_prefix("kernel.").unwrap();
    assert!(
        decision
            .clicked_off
            .iter()
            .any(|s| nornir_build_thing::kernel_features::option_name(s) == option),
        "the CLI's decision vetoes the symbol the pane clicked: {:?}",
        decision.clicked_off
    );

    // ── ONE MODEL: the tree's own decision for the pane's state is the CLI's ───
    // Derived the way both doors derive it: the donor's kernel from the donor's OWN
    // checkout (the same lookup `DeclaredForge::derived_tree` and the CLI's donor
    // resolution make), not a default preset.
    let inst = set
        .installers
        .iter()
        .find(|i| i.name == installer)
        .expect("the installer");
    let donor_kernel = builds
        .repo(&donor.0)
        .and_then(|r| r.set.kernels.iter().find(|k| k.name == donor.1).cloned());
    let tree = SettingsTree::of_installer(inst, donor_kernel.as_ref());
    let mut state: BTreeMap<String, bool> = BTreeMap::new();
    state.insert(path.clone(), false);
    let vals: BTreeMap<String, String> = BTreeMap::new();
    let model = tree
        .kernel_decision(KERNEL_PATH, &state, &vals)
        .expect("the model's decision for the pane's state");
    assert_eq!(
        model.fragment, decision.fragment,
        "the same decision fragment through both doors — law B/C across the doors"
    );
    assert_eq!(model.preset, decision.preset);
    eprintln!(
        "both doors: {} · preset {} · off {:?}",
        want_flag,
        decision.preset.as_str(),
        decision.clicked_off
    );
}

/// **The CHAIN the pane plans is the chain the prompt plans** — wave 3, laws B/C
/// across the doors, on the constellation's real declaration.
///
/// | claim | RED when |
/// |---|---|
/// | ⚒'s dry pass names the same steps, in the same order, as `plan_build_steps` | the pane plans its own DAG (two orderings, the wave-3 finding) |
/// | every row carries the PLANNER's own sentence, the one `--list --build` prints | the console writes its own prose about a plan it did not make |
/// | the decision fragment the CLI door resolves is IN the row | the fragment stops at the door and the pane shows a plan for another choice |
/// | the dry pass starts nothing: every row `köad`, no receipt, no clock | a dry pass that builds, or a console that looks finished |
///
/// Driven through `plan_edge`, the DRY twin of `BYGG <kant>`: the same planner, the
/// pin asked what it WOULD do, nothing spawned. A robot must be able to compare the
/// doors without starting a kernel compile, which is why that twin exists.
///
/// SKIPS by name when the real declaration is not on this disk.
#[test]
fn the_chain_the_pane_plans_is_the_chain_the_prompt_plans() {
    let root = std::env::var_os("DWARVES_CONSTELLATION")
        .map(std::path::PathBuf::from)
        .unwrap_or_else(|| std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../.."));
    let root = root.canonicalize().unwrap_or(root);
    let builds = ConstellationBuilds::discover(&root);
    let Some((repo, installer, donor)) = builds.repos.iter().find_map(|r| {
        r.set.installers.iter().find_map(|i| {
            i.kernel_donor.as_ref().map(|d| {
                (
                    r.repo.clone(),
                    i.name.clone(),
                    (d.repo.clone(), d.kernel.clone()),
                )
            })
        })
    }) else {
        eprintln!(
            "SKIP: no checkout under {} declares an installer with a kernel_donor",
            root.display()
        );
        return;
    };
    if builds.repo(&donor.0).is_none() {
        eprintln!(
            "SKIP: the donor `{}` is not checked out beside `{repo}`",
            donor.0
        );
        return;
    }
    let repo_root = builds
        .repo(&repo)
        .map(|r| r.root.clone())
        .expect("the repo is on disk");
    let set = builds
        .repo(&repo)
        .map(|r| r.set.clone())
        .expect("its declaration");

    // ── DOOR 1: ⚒'s dry pass ──────────────────────────────────────────────────
    let scene = BuildScene::new(Box::new(DeclaredForge::at(root.clone())));
    let harness = Harness::builder()
        .with_size(nornir_robotui::egui::Vec2::new(2400.0, 1600.0))
        .build_ui_state(
            |ui, app: &mut SceneApp| Facet::ui(&mut app.0, ui),
            SceneApp(scene),
        );
    let mut robot = RobotSession::new(harness, "dwarves-gui");
    robot.run(2);
    robot
        .type_into("artefakt", &repo)
        .expect("the artefakt field");
    robot.click_label("sök").expect("sök");
    robot.run(2);
    robot.harness_mut().state_mut().0.select_node(&installer);
    robot.run(2);
    assert_eq!(robot.state_at("/menu/variant"), Some(json!(installer)));
    robot
        .harness_mut()
        .state_mut()
        .0
        .update_json(&format!(r#"{{"plan_edge":"{KERNEL_PATH}"}}"#));
    robot.run(3);
    let steps = robot
        .state_at("/steps")
        .filter(|v| !v.is_null())
        .unwrap_or_else(|| {
            panic!(
                "the dry pass planned nothing — menu note: {:?}",
                robot.state_at("/menu/note")
            )
        });
    let rows = steps["steps"].as_array().expect("rows").clone();
    // Nothing was started: the dry pass reads.
    for r in &rows {
        assert_eq!(
            r["state"],
            json!("queued"),
            "the dry pass starts nothing: {r}"
        );
    }
    assert_eq!(
        steps["receipt"],
        Value::Null,
        "no verb fired ⇒ no BYGG receipt"
    );
    assert_eq!(steps["elapsed_ms"], json!(0), "no clock on a plan");

    // The consuming line, off the screen: the flags the consumer row carries ARE
    // what ⚒ would hand the build (law C), so they are what door 2 is fed.
    let line: Vec<String> = rows
        .last()
        .and_then(|r| r["flags"].as_array().cloned())
        .unwrap_or_default()
        .iter()
        .filter_map(|f| {
            f.as_str()
                .and_then(|f| f.strip_prefix("--opt "))
                .map(str::to_string)
        })
        .collect();

    // ── DOOR 2: the prompt's planner, fed the same line ───────────────────────
    let builds_flag = [KERNEL_PATH.to_string()];
    let opts = dwarves::buildthing::expand_opts_for(&set, Some(&installer), &line, &[])
        .unwrap_or_else(|e| panic!("the CLI door refused the pane's own line {line:?}: {e:#}"));
    let (decision, _) =
        dwarves::buildthing::resolve_kernel_axis(&set, &repo_root, Some(&installer), &opts)
            .unwrap_or_else(|e| panic!("the CLI door refused the kernel half of {opts:?}: {e:#}"));
    let planned = dwarves::build_steps::plan_build_steps(
        &set,
        &repo_root,
        Some(&installer),
        &builds_flag,
        &opts,
    )
    .unwrap_or_else(|e| panic!("the prompt's planner refused: {e:#}"));
    let lines = dwarves::build_steps::describe_plan(&planned, decision.as_ref())
        .expect("the dry pass describes itself");

    // ONE ORDER: the same coordinates, in the same order, the consumer last.
    let pane: Vec<String> = rows
        .iter()
        .map(|r| r["coordinate"].as_str().unwrap_or_default().to_string())
        .collect();
    let mut prompt: Vec<String> = planned.iter().map(|s| s.coordinate().to_string()).collect();
    prompt.push(format!("{repo}/{installer}"));
    assert_eq!(
        pane, prompt,
        "one DAG, two doors — the pane must not plan its own"
    );
    assert!(
        !planned.is_empty(),
        "the real declaration has a kernel edge, so there is a donor step"
    );

    // ONE SENTENCE: the planner's own lines for a step are the row's, verbatim.
    // Not "the row says something about the step" — the row IS the prompt's dry pass,
    // because `describe_plan` wrote both.
    for (i, s) in planned.iter().enumerate() {
        let got = rows[i]["plan"].as_str().unwrap_or_default();
        let head = format!("step {}/{} ", i + 1, planned.len());
        assert!(
            got.starts_with(&head),
            "row {i} opens with the planner's own position: {got:?}"
        );
        let mine: Vec<&String> = {
            let from = lines
                .iter()
                .position(|l| l.starts_with(&head))
                .expect("the planner wrote this step");
            std::iter::once(&lines[from])
                .chain(
                    lines[from + 1..]
                        .iter()
                        .take_while(|l| !l.starts_with("step ")),
                )
                .collect()
        };
        assert!(mine.len() >= 1);
        for l in &mine {
            assert!(
                got.contains(l.trim()),
                "the row must carry the prompt's line verbatim.\n  row:    {got}\n  prompt: {}",
                l.trim()
            );
        }
        assert!(got.contains(s.coordinate()));
    }

    // THE CHOICE TRAVELS: the preset the CLI door resolved is the preset the row's
    // routed `--opt` line carries, and every symbol the door vetoed is spelled off
    // on it. The fragment SHA itself only appears on a plan whose donor has a BUILT
    // pin to compare against (`StepNeed::ByPin`); with nothing built, what the two
    // doors can be held to is the choice, and they are.
    if let Some(d) = &decision {
        let all: String = rows
            .iter()
            .filter_map(|r| r["plan"].as_str())
            .collect::<Vec<_>>()
            .join(" ");
        assert!(
            all.contains(&format!("--opt kernel.preset={}", d.preset.as_str())),
            "the resolved preset `{}` must be on the routed line the pane shows: {all}",
            d.preset.as_str()
        );
        for sym in &d.clicked_off {
            let opt = nornir_build_thing::kernel_features::option_name(sym);
            assert!(
                all.contains(&format!("--opt kernel.{opt}=off")),
                "the veto on {sym} must ride the donor's own line: {all}"
            );
        }
    }
    eprintln!("both doors planned: {prompt:?}");
}