use std::fs;
use std::path::{Path, PathBuf};
fn workspace_root() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.and_then(Path::parent)
.expect("crates/frust has a grandparent (the workspace root)")
.to_path_buf()
}
fn rust_files(dir: &Path, out: &mut Vec<PathBuf>) {
let Ok(entries) = fs::read_dir(dir) else {
return;
};
for entry in entries {
let path = entry
.unwrap_or_else(|e| panic!("dir entry under {}: {e}", dir.display()))
.path();
if path.is_dir() {
rust_files(&path, out);
} else if path.extension().is_some_and(|ext| ext == "rs") {
out.push(path);
}
}
}
fn all_crate_source_files() -> Vec<PathBuf> {
let root = workspace_root();
let crates_dir = root.join("crates");
let mut out = Vec::new();
let entries = fs::read_dir(&crates_dir)
.unwrap_or_else(|e| panic!("reading {}: {e}", crates_dir.display()));
for entry in entries {
let crate_dir = entry.unwrap_or_else(|e| panic!("dir entry: {e}")).path();
let src = crate_dir.join("src");
if src.is_dir() {
rust_files(&src, &mut out);
}
}
out.sort();
out
}
fn rel(path: &Path) -> String {
path.strip_prefix(workspace_root())
.unwrap_or(path)
.to_string_lossy()
.replace('\\', "/")
}
const ALLOWLIST: &[&str] = &[
"crates/frust-shell-common/src/surface_mode.rs",
"crates/frust-shell-android/src/jni_glue.rs",
"crates/frust-shell-ios/src/ffi_glue.rs",
];
const RESOLVED_PUBLISH_ALLOWLIST: &[&str] = &[
"crates/frust-shell-common/src/surface_mode.rs",
"crates/frust-shell-android/src/app/surface.rs",
"crates/frust-shell-ios/src/app/surface.rs",
];
fn is_comment_only(line: &str) -> bool {
line.trim_start().starts_with("//")
}
fn call_sites_outside(needle: &str, allowlist: &[&str]) -> Vec<String> {
let mut hits = Vec::new();
for path in all_crate_source_files() {
let relp = rel(&path);
if allowlist.contains(&relp.as_str()) {
continue;
}
let contents =
fs::read_to_string(&path).unwrap_or_else(|e| panic!("reading {}: {e}", path.display()));
for (i, line) in contents.lines().enumerate() {
if is_comment_only(line) {
continue;
}
if line.contains(needle) {
hits.push(format!("{relp}:{}: {}", i + 1, line.trim()));
}
}
}
hits
}
#[test]
fn only_shell_glue_may_declare_host_translucent_surface() {
let failures = call_sites_outside("declare_host_translucent_surface(", ALLOWLIST);
assert!(
failures.is_empty(),
"host-declared-translucency ban violated ({} hit(s)) — only the generated host's \
JNI/C-ABI entry points may declare host translucency; see \
crates/frust-shell-common/src/surface_mode.rs's module docs:\n{}",
failures.len(),
failures.join("\n"),
);
}
#[test]
fn only_shell_app_loops_may_publish_the_resolved_surface_mode() {
let failures = call_sites_outside("publish_resolved_surface_mode(", RESOLVED_PUBLISH_ALLOWLIST);
assert!(
failures.is_empty(),
"resolved-surface-mode publish ban violated ({} hit(s)) — only each mobile shell's \
own per-frame resolved-translucency sync may publish this slot; see \
crates/frust-shell-common/src/surface_mode.rs's module docs:\n{}",
failures.len(),
failures.join("\n"),
);
}
#[test]
fn facade_does_not_reexport_the_translucency_declaration() {
let facade = workspace_root().join("crates/frust/src/lib.rs");
let contents =
fs::read_to_string(&facade).unwrap_or_else(|e| panic!("reading {}: {e}", facade.display()));
let mut in_pub_use = false;
let mut depth: i32 = 0;
for (i, line) in contents.lines().enumerate() {
let trimmed = line.trim_start();
if !in_pub_use && !trimmed.starts_with("pub use") {
continue;
}
in_pub_use = true;
assert!(
!line.contains("declare_host_translucent_surface")
&& !line.contains("request_translucent_surface")
&& !line.contains("publish_resolved_surface_mode"),
"crates/frust/src/lib.rs:{}: facade re-exports a surface-mode WRITER — app Rust \
must never be able to set the declaration latch or fake a resolved \
verdict. Line: {}",
i + 1,
line.trim()
);
depth += line.matches('{').count() as i32 - line.matches('}').count() as i32;
if depth <= 0 && line.contains(';') {
in_pub_use = false;
depth = 0;
}
}
}
#[test]
fn facade_reexport_scan_sees_multi_line_groups() {
let planted = "pub use frust_shell_common::{\n declare_host_translucent_surface,\n};\n";
let mut in_pub_use = false;
let mut depth: i32 = 0;
let mut visited_banned = false;
for line in planted.lines() {
let trimmed = line.trim_start();
if !in_pub_use && !trimmed.starts_with("pub use") {
continue;
}
in_pub_use = true;
if line.contains("declare_host_translucent_surface") {
visited_banned = true;
}
depth += line.matches('{').count() as i32 - line.matches('}').count() as i32;
if depth <= 0 && line.contains(';') {
in_pub_use = false;
depth = 0;
}
}
assert!(
visited_banned,
"the scan walk must visit continuation lines of a grouped `pub use`"
);
}
#[test]
fn facade_exposes_the_resolved_reader_and_it_starts_unknown() {
let mode: frust::ResolvedSurfaceMode = frust::resolved_surface_mode();
assert_eq!(
mode,
frust::ResolvedSurfaceMode::Unknown,
"no shell published in this process, so the resolved slot must still read Unknown"
);
assert!(!mode.is_translucent());
assert!(
!mode.translucency_refused(),
"`Unknown` must never be mistaken for a refusal — a fallback branch keyed off it \
would fire on every desktop-preview run"
);
}