Files
MetaCrate/programs/tests/prim_inspector_cli.rs
Chili Palmer 4504abcc64
All checks were successful
Native Rust workspace compile / compile (push) Successful in 21m40s
Implement native PrimInspector
2026-08-11 08:32:22 +00:00

261 lines
7.7 KiB
Rust

use std::fs;
use std::path::{Path, PathBuf};
use std::process::{Command, Output, Stdio};
use std::sync::atomic::{AtomicU64, Ordering};
const EXIT_USAGE: i32 = 2;
const EXIT_INPUT: i32 = 3;
const EXIT_OUTPUT: i32 = 5;
static TEMP_ID: AtomicU64 = AtomicU64::new(0);
struct TestDir(PathBuf);
impl TestDir {
fn new(name: &str) -> Self {
let id = TEMP_ID.fetch_add(1, Ordering::Relaxed);
let path = std::env::temp_dir().join(format!(
"metacrate-prim-inspector-{name}-{}-{id}",
std::process::id()
));
fs::create_dir(&path).expect("create test directory");
Self(path)
}
fn path(&self, name: &str) -> PathBuf {
self.0.join(name)
}
}
impl Drop for TestDir {
fn drop(&mut self) {
let _ = fs::remove_dir_all(&self.0);
}
}
fn run(args: &[&str]) -> Output {
Command::new(env!("CARGO_BIN_EXE_prim-inspector"))
.args(args)
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.output()
.expect("run prim-inspector")
}
fn text(bytes: &[u8]) -> &str {
std::str::from_utf8(bytes).expect("UTF-8 output")
}
fn path_text(path: &Path) -> &str {
path.to_str().expect("UTF-8 path")
}
fn assert_exit(output: &Output, expected: i32) {
assert_eq!(
output.status.code(),
Some(expected),
"stdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
}
fn fixture(directory: &TestDir) -> PathBuf {
let path = directory.path("objects.json");
fs::write(
&path,
r#"{
"region":"Fixture Region",
"agent_position":[0,0,0],
"objects":[
{
"id":"11111111-1111-1111-1111-111111111111",
"localid":30,
"name":"Far Lamp",
"description":"A bright fixture",
"position":[6,8,0],
"rotation":[0,0,0,1],
"scale":[1,2,3],
"parentid":7,
"material":3,
"pcode":9,
"physical":true,
"light":{"color":[0.2,0.4,0.6,1],"intensity":0.75,"radius":12,"cutoff":0.5,"falloff":0.25},
"flex":{"simulate_lod":2,"gravity":-0.5,"air_friction":1,"wind_sensitivity":0.2,"tension":0.8,"user_force":[0,0,0]},
"owner":"aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
"creator":"bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
"sale_type":"copy",
"sale_price":25,
"hover_text":"Welcome visitor",
"property_state":"ready"
},
{
"id":"22222222-2222-2222-2222-222222222222",
"localid":20,
"name":"Near Lamp",
"description":"Hydrated later",
"position":[3,4,0],
"rotation":[0,0,0,1],
"scale":[1,1,1],
"sculpt":{"texture":"cccccccc-cccc-cccc-cccc-cccccccccccc","type":5},
"owner":"dddddddd-dddd-dddd-dddd-dddddddddddd",
"creator":"eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee",
"property_state":"delayed"
},
{
"id":"33333333-3333-3333-3333-333333333333",
"localid":10,
"name":"Lost Lamp",
"position":[1,0,0],
"rotation":[0,0,0,1],
"scale":[1,1,1],
"property_state":"missing"
},
{
"id":"44444444-4444-4444-4444-444444444444",
"localid":40,
"name":"Chair",
"position":[-1,0,0],
"rotation":[0,0,0,1],
"scale":[1,1,1]
}
]
}"#,
)
.expect("write fixture");
path
}
#[test]
fn help_preserves_upstream_arguments_and_documents_bounded_controls() {
let output = run(&["--help"]);
assert!(output.status.success());
let help = text(&output.stdout);
for marker in [
"[FIRSTNAME]",
"[LASTNAME]",
"[PASSWORD]",
"[SEARCH_TERM]",
"--fake-grid",
"--search",
"--limit",
"--discovery-delay-ms",
"--property-timeout-ms",
"--max-output-bytes",
] {
assert!(help.contains(marker), "help omitted {marker}:\n{help}");
}
let output = run(&[]);
assert_exit(&output, EXIT_USAGE);
assert!(text(&output.stderr).contains("Usage: prim-inspector"));
}
#[test]
fn fake_grid_golden_output_hydrates_orders_filters_and_formats_details() {
let directory = TestDir::new("golden");
let fixture = fixture(&directory);
let output = run(&[
"--fake-grid",
path_text(&fixture),
"--search",
"Lamp",
"--limit",
"2",
"--property-timeout-ms",
"17",
]);
assert!(output.status.success(), "{}", text(&output.stderr));
assert!(output.stderr.is_empty());
let output = text(&output.stdout);
for expected in [
"Fake grid connected",
"Region: Fixture Region",
"Agent position: <0.000, 0.000, 0.000>",
"Found 4 primitives",
"CALL select-object local_id=20",
"PROPERTY received local_id=20",
"CALL select-object local_id=10",
"Property timeout after 17 ms: 1 primitives remain unknown",
"Inspecting 2 nearest matching primitives",
"Object: Near Lamp (ID: 22222222-2222-2222-2222-222222222222)",
"Distance: 5.00 m",
"Sculpt Type: Mesh",
"Sculpt Texture: cccccccc-cccc-cccc-cccc-cccccccccccc",
"Owner: dddddddd-dddd-dddd-dddd-dddddddddddd",
"Description: Hydrated later",
"Object: Far Lamp (ID: 11111111-1111-1111-1111-111111111111)",
"Distance: 10.00 m",
"Parent ID: 7",
"Material:",
"PCode: Prim",
"Light Intensity: 0.750",
"Light Radius: 12.000",
"Flexible Softness: 2",
"Flexible Gravity: -0.500",
"For Sale: Copy for L$25",
"Hover Text: Welcome visitor",
"Fake grid logout complete; active_tasks=0 open_sockets=0",
] {
assert!(
output.contains(expected),
"output omitted {expected}:\n{output}"
);
}
assert!(output.find("Object: Near Lamp").unwrap() < output.find("Object: Far Lamp").unwrap());
assert!(!output.contains("Object: Chair"));
assert!(!output.contains("Object: Lost Lamp"));
}
#[test]
fn nearest_limit_has_deterministic_distance_and_local_id_tie_breaking() {
let directory = TestDir::new("ordering");
let fixture = fixture(&directory);
let output = run(&["--fake-grid", path_text(&fixture), "--limit", "3"]);
assert!(output.status.success(), "{}", text(&output.stderr));
let output = text(&output.stdout);
let lost = output.find("Object: Unknown").unwrap();
let chair = output.find("Object: Chair").unwrap();
let near = output.find("Object: Near Lamp").unwrap();
assert!(lost < chair && chair < near, "unexpected order:\n{output}");
assert!(!output.contains("Object: Far Lamp"));
}
#[test]
fn malformed_limits_and_live_credentials_fail_without_partial_or_secret_output() {
let directory = TestDir::new("invalid");
let malformed = directory.path("malformed.json");
fs::write(&malformed, "{not-json}").expect("write malformed fixture");
let output = run(&["--fake-grid", path_text(&malformed)]);
assert_exit(&output, EXIT_INPUT);
assert!(output.stdout.is_empty());
assert!(text(&output.stderr).contains("could not parse LLSD JSON"));
let fixture = fixture(&directory);
let output = run(&[
"--fake-grid",
path_text(&fixture),
"--max-output-bytes",
"10",
]);
assert_exit(&output, EXIT_OUTPUT);
assert!(output.stdout.is_empty());
assert!(text(&output.stderr).contains("output byte limit"));
let password = "do-not-print-this-password";
let output = run(&[
"First",
"Last",
password,
"--fake-grid",
path_text(&fixture),
]);
assert_exit(&output, EXIT_USAGE);
assert!(!text(&output.stdout).contains(password));
assert!(!text(&output.stderr).contains(password));
let output = run(&["--fake-grid", path_text(&fixture), "--limit", "0"]);
assert_exit(&output, EXIT_USAGE);
}