Files
Verstack Local f73167de85 Add downloadable VMs and improve catalog processing
Package Apple Silicon VM exports and cached runtime switching with local cabinet controls and conagent provisioning. Preserve experimental SPIKE 2 emulation.

Improve preparation concurrency, Ghidra checkpoints, signature importing, sound indexing and client-side spectrograms. Include regression tests and validation notes.
2026-09-14 20:03:59 -05:00

110 lines
3.8 KiB
Rust

use verstack::analysis::*;
fn function(address: &str, hash: char) -> Function {
Function {
address: address.into(),
name: "unknown".into(),
symbol_source: "DEFAULT".into(),
size: 64,
thunk: false,
body_sha256: hash.to_string().repeat(64),
}
}
fn facts(functions: Vec<Function>) -> FunctionFacts {
FunctionFacts {
processing_revision: 1,
schema: 1,
input_sha256: "fixture".into(),
language: "x86:LE:64:default".into(),
compiler: "gcc".into(),
ghidra_version: "fixture".into(),
analysis_timed_out: false,
settings: serde_json::json!({}),
functions,
}
}
#[test]
fn unique_exact_matches_keep_ambiguous_and_small_functions_unmatched() {
let mut small = function("5", 'f');
small.size = 4;
let a = facts(vec![
function("1", 'a'),
function("2", 'b'),
function("3", 'b'),
small.clone(),
]);
let b = facts(vec![
function("10", 'a'),
function("20", 'b'),
function("30", 'c'),
small,
]);
let result = compare_functions(&a, &b).unwrap();
assert_eq!(result.matches.len(), 1);
assert_eq!(result.matches[0].before.address, "1");
assert_eq!(result.matches[0].after.address, "10");
assert_eq!(result.unmatched_after.len(), 3);
}
#[test]
fn incompatible_or_incomplete_analysis_is_rejected() {
let a = facts(vec![]);
let mut b = facts(vec![]);
b.language = "ARM".into();
assert!(compare_functions(&a, &b).is_err());
b.language = a.language.clone();
b.analysis_timed_out = true;
assert!(compare_functions(&a, &b).is_err());
}
#[test]
fn selection_paths_are_not_analysis_settings_but_other_options_are() {
let mut a = facts(vec![function("1", 'a')]);
let mut b = facts(vec![function("2", 'a')]);
a.settings = serde_json::json!({"paths":["pro/game"],"max_cpu":2,"future_option":true});
b.settings = serde_json::json!({"paths":["le/game"],"max_cpu":2,"future_option":true});
assert_eq!(compare_functions(&a, &b).unwrap().matches.len(), 1);
a.settings["checkpoint_dir"] = "/tmp/cache-one".into();
b.settings["checkpoint_dir"] = "/tmp/cache-two".into();
assert_eq!(compare_functions(&a, &b).unwrap().matches.len(), 1);
b.settings["future_option"] = false.into();
assert!(compare_functions(&a, &b).is_err());
}
#[test]
fn unmatched_evidence_distinguishes_exclusions_and_ambiguity() {
let a = facts(vec![function("1", 'a'), function("2", 'a')]);
let mut unavailable = function("30", 'g');
unavailable.body_sha256.clear();
let mut thunk = function("40", 'c');
thunk.thunk = true;
let b = facts(vec![
function("10", 'a'),
function("20", 'b'),
unavailable,
thunk,
]);
let r = compare_functions(&a, &b).unwrap();
assert_eq!(r.unmatched_before.len(), 2);
assert_eq!(r.unmatched_after_reasons["10"], "ambiguous_exact_body");
assert_eq!(r.unmatched_after_reasons["20"], "no_exact_body_match");
assert_eq!(r.unmatched_after_reasons["30"], "body_hash_unavailable");
assert_eq!(r.unmatched_after_reasons["40"], "thunk_excluded");
let duplicate = facts(vec![function("1", 'a'), function("1", 'b')]);
assert!(compare_functions(&duplicate, &b).is_err());
}
#[test]
fn refuses_incompatible_processing_revisions() {
let a = facts(vec![]);
let mut b = facts(vec![]);
b.processing_revision = 2;
assert!(compare_functions(&a, &b).is_err());
}
#[test]
fn automatic_game_selection_excludes_system_menu() {
assert!(is_game_program("partition-06/pokemon_pro/game"));
assert!(is_game_program("release/Game.exe"));
assert!(!is_game_program("partition-06/pokemon_pro/spike3/spike_menu/game"));
assert!(!is_game_program("partition-02/usr/lib/libmesa.so"));
}