Test builds open no audio device (#2388)
Every headless test app built `GamePlugins`, whose `SoundPlugin` opened the default output device unless `$WOW_NOSOUND` or `$WOW_CAPTURE` was set, and no test run sets either. Under nextest that is one CoreAudio client and speaker stream per test process. With a few runs going at once, `coreaudiod` sat near 300% CPU, every sound on the machine crackled, and each test blocked on the audio server's connect. A test build now takes the existing silent path, so its mixer is `None`, as with no device. Test: `a_test_client_opens_no_audio_device`. With the change undone it fails on a machine with a speaker, taking 96 s; with it, the test passes in 0.06 s. Fixes #2380 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit fbc90b9bbf272a784f3703eb0c68ea466fe90951) # Conflicts: # crates/benilla-app/src/game_plugins.rs # docs/DEVIATIONS.md Absorbed into the octo fork. The sound/mod.rs fix applies unchanged. Of the tests the conflict hunk carried, only this commit's own a_test_client_opens_no_audio_device is kept, retargeted from upstream's headless_client_unrun to our headless_client (same contract: every plugin's build runs, no schedule does); resource_writers and the_glue_preview_has_one_feeder come from other upstream commits and need glue_scene and GluePreview, which this fork has not got. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
This commit is contained in:
co-authored by
Claude Opus 5
parent
73f4bf808d
commit
91bcc19ac0
@@ -554,6 +554,15 @@ pub(crate) mod schedule_tests {
|
||||
c.systems.values().any(|s| s.name.contains("benilla_app::"))
|
||||
}
|
||||
|
||||
/// A test client opens no audio device: one stream per test process, one process per test,
|
||||
/// floods the system audio server and crackles every sound on the machine.
|
||||
#[test]
|
||||
fn a_test_client_opens_no_audio_device() {
|
||||
let app = headless_client();
|
||||
let output = app.world().non_send_resource::<crate::sound::SoundOutput>();
|
||||
assert!(output.mixer.is_none());
|
||||
}
|
||||
|
||||
/// One row of the class table: the type's name for the message, and its `TypeId`.
|
||||
type ClassRow = (&'static str, fn() -> TypeId);
|
||||
|
||||
|
||||
@@ -224,8 +224,8 @@ fn feed_music_suppression(
|
||||
}
|
||||
}
|
||||
|
||||
/// The backend output; `mixer` is `None` with no device or under `$WOW_NOSOUND`. Non-Send: the
|
||||
/// device stream is not `Send` on every platform, so audio systems run on the main thread.
|
||||
/// The backend output; `mixer` is `None` with no device, under `$WOW_NOSOUND` or in a test.
|
||||
/// Non-Send: the device stream is not `Send` on every platform, so audio systems run on the main thread.
|
||||
pub(crate) struct SoundOutput {
|
||||
pub(crate) mixer: Option<Mixer>,
|
||||
/// Live kit channels, pumped by [`kit::pump_channels`].
|
||||
@@ -351,19 +351,21 @@ impl Plugin for SoundPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
net::register(app);
|
||||
app.add_observer(on_cvar);
|
||||
// `$WOW_NOSOUND` (a silent unattended run) and `$WOW_CAPTURE` open no device at all, so
|
||||
// they never hold the audio hardware against a session someone is listening to.
|
||||
// `$WOW_NOSOUND` (a silent unattended run), `$WOW_CAPTURE` and a test build open no device
|
||||
// at all, so they never hold the audio hardware against a session someone is listening to.
|
||||
let silent = ["WOW_NOSOUND", "WOW_CAPTURE"]
|
||||
.into_iter()
|
||||
.find(|v| std::env::var_os(v).is_some());
|
||||
.find(|v| std::env::var_os(v).is_some())
|
||||
.map(|v| format!("${v} set"))
|
||||
.or_else(|| cfg!(test).then(|| "a test build".to_owned()));
|
||||
// Before the mixer: the probe's taps are main-track effects, fixed when kira builds it.
|
||||
let probe_dir = if silent.is_some() {
|
||||
None
|
||||
} else {
|
||||
probe::output_dir()
|
||||
};
|
||||
let mixer = if let Some(var) = silent {
|
||||
info!("${var} set — audio disabled");
|
||||
let mixer = if let Some(why) = silent {
|
||||
info!("{why} — audio disabled");
|
||||
None
|
||||
} else {
|
||||
match Mixer::new(probe_dir.as_deref()) {
|
||||
|
||||
Reference in New Issue
Block a user