use std::sync::Mutex;
use vb6interpret::Interpreter;
use vb6parse::files::ModuleFile;
use vb6parse::io::SourceFile;
static RES_TEST_LOCK: Mutex<()> = Mutex::new(());
fn res_record(res_type: u16, name: u16, data: &[u8]) -> Vec<u8> {
let mut bytes = Vec::new();
bytes.extend_from_slice(&(data.len() as u32).to_le_bytes());
bytes.extend_from_slice(&32u32.to_le_bytes()); bytes.extend_from_slice(&0xFFFFu16.to_le_bytes()); bytes.extend_from_slice(&res_type.to_le_bytes());
bytes.extend_from_slice(&0xFFFFu16.to_le_bytes()); bytes.extend_from_slice(&name.to_le_bytes());
bytes.extend_from_slice(&[0u8; 16]); bytes.extend_from_slice(data);
bytes.resize(bytes.len().next_multiple_of(4), 0);
bytes
}
fn res_string_bundle(slots: &[&str]) -> Vec<u8> {
let mut bytes = Vec::new();
for slot in 0..16 {
let text = slots.get(slot).copied().unwrap_or("");
let units: Vec<u16> = text.encode_utf16().collect();
bytes.extend_from_slice(&(units.len() as u16).to_le_bytes());
for unit in units {
bytes.extend_from_slice(&unit.to_le_bytes());
}
}
bytes
}
fn sample_res_image() -> Vec<u8> {
let mut image = res_record(0, 0, &[]); image.extend(res_record(
6,
1,
&res_string_bundle(&["zero", "one", "two"]),
));
image.extend(res_record(10, 101, b"custom-payload"));
image
}
fn stage_res_file(interpreter: &Interpreter, image: &[u8]) -> String {
interpreter.set_file_backend(Box::new(
vb6runtime::state::file::memory::MemoryBackend::new(),
));
vb6runtime::state::file::set_root("/");
vb6runtime::state::file::write_memory_file("/app.res", image).unwrap();
"/app.res".to_string()
}
#[test]
fn loadresstring_reads_the_linked_resource_file() {
let _guard = RES_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let mut interpreter = Interpreter::new();
let path = stage_res_file(&interpreter, &sample_res_image());
interpreter.set_resource_file(&path);
let source = "Attribute VB_Name = \"M\"\n\
Sub Main()\n\
Debug.Print LoadResString(1)\n\
Debug.Print LoadResString(2)\n\
End Sub\n";
let source_file = SourceFile::from_string("m.bas", source);
let module = ModuleFile::parse(&source_file).unwrap_or_fail();
interpreter.run_module(&module).expect("run failed");
assert_eq!(
interpreter.output(),
&["one".to_string(), "two".to_string()]
);
interpreter.clear_resource_file();
interpreter.reset_file_backend();
}
#[test]
fn loadresdata_reads_the_linked_resource_file() {
let _guard = RES_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let mut interpreter = Interpreter::new();
let path = stage_res_file(&interpreter, &sample_res_image());
interpreter.set_resource_file(&path);
let source = "Attribute VB_Name = \"M\"\n\
Sub Main()\n\
Dim data\n\
data = LoadResData(101, 10)\n\
Debug.Print UBound(data)\n\
End Sub\n";
let source_file = SourceFile::from_string("m.bas", source);
let module = ModuleFile::parse(&source_file).unwrap_or_fail();
interpreter.run_module(&module).expect("run failed");
assert_eq!(interpreter.output(), &["13".to_string()]);
interpreter.clear_resource_file();
interpreter.reset_file_backend();
}
#[test]
fn resource_file_survives_clear_and_reapplies_each_run() {
let _guard = RES_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let mut interpreter = Interpreter::new();
let path = stage_res_file(&interpreter, &sample_res_image());
interpreter.set_resource_file(&path);
let source = "Attribute VB_Name = \"M\"\n\
Sub Main()\n\
Debug.Print LoadResString(0)\n\
End Sub\n";
let source_file = SourceFile::from_string("m.bas", source);
let module = ModuleFile::parse(&source_file).unwrap_or_fail();
interpreter.run_module(&module).expect("first run failed");
assert_eq!(interpreter.output(), &["zero".to_string()]);
interpreter.clear();
assert_eq!(interpreter.resource_file(), Some(path.as_str()));
interpreter.run_module(&module).expect("second run failed");
assert_eq!(interpreter.output(), &["zero".to_string()]);
interpreter.clear_resource_file();
interpreter.reset_file_backend();
}
#[test]
fn without_a_linked_resource_file_loadres_raises_326() {
let _guard = RES_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let mut interpreter = Interpreter::new();
stage_res_file(&interpreter, &sample_res_image());
assert_eq!(interpreter.resource_file(), None);
let source = "Attribute VB_Name = \"M\"\n\
Sub Main()\n\
Debug.Print LoadResString(1)\n\
End Sub\n";
let source_file = SourceFile::from_string("m.bas", source);
let module = ModuleFile::parse(&source_file).unwrap_or_fail();
let error = interpreter.run_module(&module).unwrap_err();
assert!(
format!("{error}").contains("326") || format!("{error}").contains("Resource"),
"expected a resource error, got: {error}"
);
interpreter.reset_file_backend();
}
#[test]
fn clearing_the_resource_file_unlinks_it_for_later_runs() {
let _guard = RES_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let mut interpreter = Interpreter::new();
let path = stage_res_file(&interpreter, &sample_res_image());
interpreter.set_resource_file(&path);
let source = "Attribute VB_Name = \"M\"\n\
Sub Main()\n\
Debug.Print LoadResString(1)\n\
End Sub\n";
let source_file = SourceFile::from_string("m.bas", source);
let module = ModuleFile::parse(&source_file).unwrap_or_fail();
interpreter.run_module(&module).expect("linked run failed");
interpreter.clear_resource_file();
assert_eq!(interpreter.resource_file(), None);
assert!(
interpreter.run_module(&module).is_err(),
"unlinked run should fail"
);
interpreter.reset_file_backend();
}
#[test]
fn relinking_picks_up_rewritten_resource_contents() {
let _guard = RES_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let mut interpreter = Interpreter::new();
let path = stage_res_file(&interpreter, &sample_res_image());
interpreter.set_resource_file(&path);
let source = "Attribute VB_Name = \"M\"\n\
Sub Main()\n\
Debug.Print LoadResString(1)\n\
End Sub\n";
let source_file = SourceFile::from_string("m.bas", source);
let module = ModuleFile::parse(&source_file).unwrap_or_fail();
interpreter.run_module(&module).expect("first run failed");
assert_eq!(interpreter.output(), &["one".to_string()]);
let mut replacement = res_record(0, 0, &[]);
replacement.extend(res_record(6, 1, &res_string_bundle(&["A", "B"])));
vb6runtime::state::file::write_memory_file("/app.res", &replacement).unwrap();
interpreter.run_module(&module).expect("second run failed");
assert_eq!(interpreter.output(), &["B".to_string()]);
interpreter.clear_resource_file();
interpreter.reset_file_backend();
}