use aethershell::value::Value;
use std::collections::BTreeMap;
use std::sync::Mutex;
static LOCK: Mutex<()> = Mutex::new(());
fn lock() -> std::sync::MutexGuard<'static, ()> {
LOCK.lock().unwrap_or_else(|e| e.into_inner())
}
fn rec(pairs: &[(&str, Value)]) -> Value {
let mut m = BTreeMap::new();
for (k, v) in pairs {
m.insert(k.to_string(), v.clone());
}
Value::Record(m)
}
fn call(name: &str, args: Vec<Value>) -> Value {
let mut env = aethershell::env::Env::new();
aethershell::builtins::call(name, args, &mut env).expect("builtin call")
}
fn try_call(name: &str, args: Vec<Value>) -> Result<Value, String> {
let mut env = aethershell::env::Env::new();
aethershell::builtins::call(name, args, &mut env).map_err(|e| e.to_string())
}
fn big_table(n: i64) -> Value {
Value::Array(
(0..n)
.map(|i| {
rec(&[
("id", Value::Int(1000 + i)),
("path", Value::Str(format!("/srv/app/module_{i:03}/main.rs"))),
("status", Value::Str(if i % 3 == 0 { "ok" } else { "stale" }.into())),
("bytes", Value::Int(4096 + i * 7)),
])
})
.collect(),
)
}
fn render(v: &Value) -> String {
aethershell::builtins::render_agent(v, None).expect("agent render")
}
#[test]
fn a_large_result_is_rendered_as_a_handle_not_as_data() {
let _g = lock();
aethershell::handles::clear();
let out = render(&big_table(400));
assert!(out.starts_with("@handle "), "expected a handle:\n{out}");
assert!(out.contains("@shape array<record{"), "shape stated:\n{out}");
assert!(out.contains("@items 400"), "size stated:\n{out}");
assert!(out.contains("@hint handle("), "usage taught inline:\n{out}");
}
#[test]
fn the_handle_is_lossless() {
let _g = lock();
aethershell::handles::clear();
let original = big_table(400);
render(&original);
let id = aethershell::handles::list()[0].id.clone();
let recovered = call("handle", vec![Value::Str(id)]);
assert_eq!(recovered, original, "a handle must return exactly what was computed");
}
fn tokens(s: &str) -> usize {
match call("tokens", vec![Value::Str(s.to_string())]) {
Value::Int(n) => n as usize,
Value::Record(m) => match m.get("tokens") {
Some(Value::Int(n)) => *n as usize,
other => panic!("unexpected tokens shape: {other:?}"),
},
other => panic!("unexpected tokens result: {other:?}"),
}
}
#[test]
fn the_handle_costs_far_less_than_the_data_and_the_gap_widens_with_size() {
let _g = lock();
let mut ratios = Vec::new();
for n in [400, 2000] {
aethershell::handles::clear();
let v = big_table(n);
let whole = match call("aecon", vec![v.clone()]) {
Value::Str(s) => s,
other => panic!("aecon should return a string, got {other:?}"),
};
let handled = render(&v);
let (wt, ht) = (tokens(&whole), tokens(&handled));
println!(
"{n} rows — whole: {wt} tokens; handle: {ht} tokens ({:.1}x)",
wt as f64 / ht as f64
);
assert!(ht * 8 < wt, "{n} rows: expected a large saving, {ht} vs {wt}");
ratios.push(wt as f64 / ht as f64);
}
assert!(
ratios[1] > ratios[0],
"the saving must grow with the result: {ratios:?}"
);
}
#[test]
fn a_preview_states_how_much_it_omits() {
let _g = lock();
aethershell::handles::clear();
let out = render(&big_table(400));
assert!(out.contains("@omitted 397"), "omission stated:\n{out}");
}
#[test]
fn a_small_result_is_returned_whole() {
let _g = lock();
aethershell::handles::clear();
let small = big_table(3);
let out = render(&small);
assert!(!out.starts_with("@handle"), "small results stay whole:\n{out}");
assert!(
aethershell::handles::list().is_empty(),
"a small result must not consume a handle"
);
}
#[test]
fn a_giant_string_is_not_handled_because_it_could_not_be_narrowed() {
let _g = lock();
aethershell::handles::clear();
let out = render(&Value::Array(vec![Value::Str("x".repeat(50_000))]));
let _ = out;
let huge = Value::Str("x".repeat(50_000));
let rendered = aethershell::builtins::render_agent(&huge, None).expect("render");
assert!(!rendered.starts_with("@handle"), "an opaque blob is sent as-is");
}
#[test]
fn handles_lists_what_is_live_without_the_data() {
let _g = lock();
aethershell::handles::clear();
render(&big_table(400));
render(&big_table(500));
let listed = call("handles", vec![]);
match listed {
Value::Array(rows) => {
assert_eq!(rows.len(), 2);
let first = format!("{:?}", rows[0]);
assert!(first.contains("items"), "listing carries size: {first}");
assert!(
!first.contains("module_001"),
"the listing must not re-send the data: {first}"
);
}
other => panic!("expected an array, got {other:?}"),
}
}
#[test]
fn an_unknown_handle_is_an_actionable_error_not_a_null() {
let _g = lock();
aethershell::handles::clear();
let err = try_call("handle", vec![Value::Str("h99".into())])
.expect_err("an unknown handle must error");
assert!(err.contains("h99"), "names the id: {err}");
assert!(
err.contains("no handles live") || err.contains("live:"),
"explains what is available: {err}"
);
}
#[test]
fn dropping_a_handle_reports_whether_it_existed() {
let _g = lock();
aethershell::handles::clear();
render(&big_table(400));
let id = aethershell::handles::list()[0].id.clone();
assert_eq!(
call("handle_drop", vec![Value::Str(id.clone())]),
Value::Bool(true)
);
assert_eq!(call("handle_drop", vec![Value::Str(id)]), Value::Bool(false));
}