Files
soundcraft/crates/vst3-host/tests/module_exit_shutdown.rs
Ian MartinandClaude Opus 5.5 ced57be723 vst3-host: the late-exit test expects Windows' exit order
module_exit_shutdown asserts that the exit handler alone runs the module
exit too late. That holds where exit handlers share one list. Windows
runs the executable's handlers before a DLL's own, so the fixture's late
state is still alive and the child exits cleanly; the test failed there.
The Windows case now asserts the clean exit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 10:34:41 -06:00

51 lines
2.4 KiB
Rust

//! A plugin that sets up state while in use (HALion Sonic does) needs its module exit before the
//! process exit tears that state down: `shutdown()` runs the exits up front, while an exit
//! handler comes too late (`harness = false`, see `module_exit.rs`). Each case runs in a child
//! process with the fixture's late state on (`VST3_FIXTURE_LATE_STATE`); the fixture ends the
//! process with status 3 when its module exit comes after the teardown.
mod common;
use soundcraft_dsp::Plugin;
use std::process::{Command, Output};
/// Makes, uses and releases a plugin, then shuts plugin hosting down unless `exit_handler_only`.
fn child(exit_handler_only: bool) {
let dir = common::fixture_dir();
let gain = soundcraft_vst3_host::scan_paths(&[dir.to_path_buf()]).into_iter().find(|d| d.name == "Fixture Gain").expect("fixture").id;
let mut p = soundcraft_vst3_host::instantiate_plugin(&gain).expect("instantiate");
p.prepare(48_000.0, 64, 2);
let mut io = vec![vec![0.5f32; 64]; 2];
p.process(&mut io, 64);
drop(p);
if !exit_handler_only {
soundcraft_vst3_host::shutdown();
assert!(soundcraft_vst3_host::instantiate_plugin(&gain).is_err(), "loads fail after shutdown");
}
}
fn run(case: &str) -> Output {
Command::new(std::env::current_exe().expect("test binary")).arg(case).env("VST3_FIXTURE_LATE_STATE", "1").output().expect("run the child")
}
fn main() {
match std::env::args().nth(1).as_deref() {
Some("shutdown") => return child(false),
Some("exit-handler") => return child(true),
_ => {}
}
let up_front = run("shutdown");
assert!(up_front.status.success(), "shutdown before exit: {:?}\n{}", up_front.status, String::from_utf8_lossy(&up_front.stderr));
let too_late = run("exit-handler");
let err = String::from_utf8_lossy(&too_late.stderr);
if cfg!(windows) {
// Windows runs the executable's exit handlers before a DLL's own, so the fixture's late
// state is still there when the host's exit handler runs its module exit.
assert!(too_late.status.success(), "the exit handler alone: {:?}\n{err}", too_late.status);
} else {
assert_eq!(too_late.status.code(), Some(3), "the exit handler alone comes too late: {err}");
assert!(err.contains("module exit after the process exit tore down state"), "{err}");
}
println!("module_exit_shutdown: ok");
}