Files
benilla-octo/crates/benilla-app/tests/ui_script_wall.rs
T
gromandClaude Opus 5 101ddc3d97 tests: the UiScript wall's ceiling follows the four files that argued past it
`ui_script_wall.rs` has been failing since `2af91704`, 888 commits ago: 43
gameplay files name `UiScript` against a ceiling of 40. A ratchet that cannot
pass notices nothing, which is the one thing it is for — so the ceiling
follows the count, with each newcomer argued in place, as the test's own
message asks.

Three of the four do not cross the line the wall draws:

- `spell/net.rs` names the identifier only to say it holds none — three doc
  comments explaining why its work is queued instead. The count reads doc
  comments by design, so documenting the line reads as crossing it.
- `target/by_name.rs` and `target/scan.rs` hold the VM because `TargetByName`
  and the scan must select inside the Lua call as `0x489d60` does; that is
  `bbe2e3aa`, without which an addon's own target restore cannot see what it
  selected.
- `debug_panel/{mod,lighting_controls}.rs` write CVars through
  `UiScript::set_cvar_engine`, the queue Lua's `SetCVar` feeds, so the dev
  panel cannot hold a different value than the interface.

Nothing moves and nothing is excused wholesale: the next file to learn that
the VM exists still fails this test.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
2026-10-18 12:26:23 -06:00

155 lines
6.3 KiB
Rust

//! A ratchet on how many `benilla-app` files outside `ui_script/` name the Lua VM's `UiScript`:
//! only the feed layer should know the FrameXML VM exists, and everything else produces model
//! values.
//!
//! A file counts if it contains the identifier `UiScript` anywhere (a type, a `use`, a doc
//! comment). `CEILING` is the standing count of gameplay files and `SLACK` how far under it the
//! count may sit, as in `world_api_wall.rs`. `WOW_UISCRIPT_DUMP=1` prints the files.
//!
//! Window feeds (`ui_*`), `capture/` probes and test modules are reported; only gameplay files,
//! the ones that can reach zero, are gated.
use std::path::{Path, PathBuf};
/// What kind of file names the VM.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
enum Family {
/// Gameplay code that knows the FrameXML VM exists: the one family this wall gates.
Game,
/// `ui_*`, a window's feed: the legitimate caller. Reported, never gated.
Feed,
/// `capture/`, a live probe that asks the stock binding on purpose. Reported.
Probe,
/// A test module beside the code it tests. Reported.
Test,
}
fn family(rel: &str) -> Family {
let file = rel.rsplit('/').next().unwrap_or(rel);
if rel.starts_with("capture/") {
Family::Probe
} else if file == "tests.rs" || file.ends_with("_tests.rs") || rel.contains("/tests/") {
Family::Test
} else if rel.starts_with("ui_") {
Family::Feed
} else {
Family::Game
}
}
/// The standing count of `Game` files; raise it only with the reason written down.
///
/// 40 was the count the last time this test passed, 888 commits back (`2af91704`): the chain has
/// been red here long enough that the wall could no longer notice a newcomer, which is the one
/// thing it is for. The four files that have joined since, each argued rather than moved:
///
/// - `spell/net.rs` names the identifier **only to say it holds none** — three doc comments of the
/// form "these handlers hold no `UiScript`", explaining why their work is queued instead. The
/// count is deliberately coarse (it reads doc comments too, see above), so a file that documents
/// the line is counted as if it crossed it.
/// - `target/by_name.rs` and `target/scan.rs` hold the VM on purpose: `TargetByName` and the scan
/// must select **inside** the Lua call, as `0x489d60` does, or an addon's
/// `prevTarget ~= foundTarget` restore cannot see what it just selected (`bbe2e3aa`, which is
/// what made unitscan stop stealing the target).
/// - `debug_panel/mod.rs` and `debug_panel/lighting_controls.rs` drive CVars through
/// `UiScript::set_cvar_engine`, the same queue Lua's `SetCVar` feeds, so the dev panel and the
/// interface cannot hold different values for one CVar.
const CEILING: usize = 43;
/// How far under [`CEILING`] the count may sit before the test asks for the ceiling to follow it.
const SLACK: usize = 4;
#[test]
fn the_vm_is_named_by_no_more_files_than_the_ceiling_says() {
let src = Path::new(env!("CARGO_MANIFEST_DIR")).join("src");
let mut files: Vec<(Family, String)> = rs_files(&src)
.into_iter()
.filter_map(|p| {
let rel = p
.strip_prefix(&src)
.unwrap_or(&p)
.to_string_lossy()
.replace('\\', "/");
if rel.starts_with("ui_script/") || rel == "ui_script.rs" {
return None;
}
let text = std::fs::read_to_string(&p).ok()?;
names_ident(&text, "UiScript").then(|| (family(&rel), rel))
})
.collect();
files.sort();
let count = |f: Family| files.iter().filter(|(fam, _)| *fam == f).count();
let n = count(Family::Game);
eprintln!(
"files outside ui_script/ naming UiScript: {} — {n} game (ceiling {CEILING}, slack \
{SLACK}), {} feeds, {} probes, {} tests",
files.len(),
count(Family::Feed),
count(Family::Probe),
count(Family::Test),
);
if std::env::var("WOW_UISCRIPT_DUMP").is_ok() {
for (fam, rel) in &files {
eprintln!(" {fam:?}\t{rel}");
}
}
assert!(
n <= CEILING,
"{n} gameplay files outside ui_script/ and the feeds name UiScript; the ceiling is \
{CEILING}.\nA new file learned that the FrameXML VM exists. The line is that \
only the feed layer may know: feed a model value instead, or — if this file genuinely is \
feed code — it belongs under a `ui_*` module, and a probe under `capture/`. Raise CEILING \
here only with the reason, the way world_api_wall.rs requires.\nWOW_UISCRIPT_DUMP=1 lists \
the files by family."
);
assert!(
n + SLACK >= CEILING,
"only {n} gameplay files name UiScript and the ceiling still says {CEILING}. Lower CEILING \
to {n} in this file so the next coupling has to earn its place — the ratchet only holds \
if the number follows the work down."
);
}
/// Does `text` contain `ident` as a whole identifier (not as part of a longer one)?
fn names_ident(text: &str, ident: &str) -> bool {
let is_ident = |c: char| c.is_alphanumeric() || c == '_';
let mut from = 0;
while let Some(i) = text[from..].find(ident) {
let at = from + i;
let end = at + ident.len();
let before_ok = at == 0 || !text[..at].chars().next_back().is_some_and(is_ident);
let after_ok = !text[end..].chars().next().is_some_and(is_ident);
if before_ok && after_ok {
return true;
}
from = end;
}
false
}
fn rs_files(dir: &Path) -> Vec<PathBuf> {
let mut out = Vec::new();
let mut stack = vec![dir.to_path_buf()];
while let Some(d) = stack.pop() {
for e in std::fs::read_dir(&d).into_iter().flatten().flatten() {
let p = e.path();
if p.is_dir() {
stack.push(p);
} else if p.extension().is_some_and(|x| x == "rs") {
out.push(p);
}
}
}
out.sort();
out
}
#[test]
fn the_identifier_match_is_whole_word() {
assert!(names_ident("fn f(s: NonSendMut<UiScript>)", "UiScript"));
assert!(names_ident("UiScript", "UiScript"));
assert!(!names_ident("UiScriptPlugin only", "UiScript"));
assert!(!names_ident("MyUiScript", "UiScript"));
assert!(names_ident("MyUiScript and UiScript", "UiScript"));
}