test: fail probe on required source setup errors
This commit is contained in:
parent
53bca123d9
commit
160cbffbd4
16
AGENTS.md
16
AGENTS.md
@ -393,3 +393,19 @@ evidence.
|
||||
- [x] Run shell syntax checks, focused contract tests, and package checks.
|
||||
- [x] Push clean semver `0.17.18` for installed client/server testing.
|
||||
- [ ] Re-run the mirrored probe only after confirming the intended microphone is physically present and selected.
|
||||
|
||||
## 0.17.19 Fatal Required Source Failure Checklist
|
||||
|
||||
Context: the 0.17.18 run proved fallback was blocked, but the headless client kept running
|
||||
camera-only after the required Bumblebee source failed to open. The server stayed in `acquiring`
|
||||
for all four segments (`awaiting both upstream media streams`), the analyzer saw no color-coded
|
||||
video pulses, and no calibration data was produced. Required-source failure must fail the probe,
|
||||
not degrade into camera-only evidence.
|
||||
|
||||
- [x] Treat the 0.17.18 run as a required-microphone setup failure, not a lip-sync measurement.
|
||||
- [x] Keep strict no-fallback behavior from 0.17.18.
|
||||
- [x] Abort the client process when an explicit required microphone source cannot start.
|
||||
- [x] Abort the client process when an explicit required camera source cannot start.
|
||||
- [x] Run shell syntax checks, focused contract tests, and package checks.
|
||||
- [x] Push clean semver `0.17.19` for installed client/server testing.
|
||||
- [ ] Re-run only after `LESAVKA_MIC_SOURCE` is listed by the local audio stack.
|
||||
|
||||
6
Cargo.lock
generated
6
Cargo.lock
generated
@ -1652,7 +1652,7 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
|
||||
|
||||
[[package]]
|
||||
name = "lesavka_client"
|
||||
version = "0.17.18"
|
||||
version = "0.17.19"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-stream",
|
||||
@ -1686,7 +1686,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lesavka_common"
|
||||
version = "0.17.18"
|
||||
version = "0.17.19"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64",
|
||||
@ -1698,7 +1698,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lesavka_server"
|
||||
version = "0.17.18"
|
||||
version = "0.17.19"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64",
|
||||
|
||||
@ -4,7 +4,7 @@ path = "src/main.rs"
|
||||
|
||||
[package]
|
||||
name = "lesavka_client"
|
||||
version = "0.17.18"
|
||||
version = "0.17.19"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
|
||||
@ -40,6 +40,7 @@ impl LesavkaClientApp {
|
||||
"🎤 microphone uplink setup failed for {:?}: {err:#}",
|
||||
active_source.as_deref().unwrap_or("auto")
|
||||
);
|
||||
abort_if_required_media_source_failed("microphone", "🎤", active_source.as_deref(), &err);
|
||||
delay = app_support::next_delay(delay);
|
||||
tokio::time::sleep(delay).await;
|
||||
continue;
|
||||
@ -47,6 +48,12 @@ impl LesavkaClientApp {
|
||||
Err(err) => {
|
||||
telemetry.record_disconnect(format!("microphone uplink setup task failed: {err}"));
|
||||
warn!("🎤 microphone uplink setup task failed before StreamMicrophone could start: {err}");
|
||||
abort_if_required_media_source_failed(
|
||||
"microphone",
|
||||
"🎤",
|
||||
active_source.as_deref(),
|
||||
&err,
|
||||
);
|
||||
delay = app_support::next_delay(delay);
|
||||
tokio::time::sleep(delay).await;
|
||||
continue;
|
||||
@ -216,6 +223,7 @@ impl LesavkaClientApp {
|
||||
"📸 webcam uplink setup failed for {:?}: {err:#}",
|
||||
active_source.as_deref().unwrap_or("auto")
|
||||
);
|
||||
abort_if_required_media_source_failed("camera", "📸", active_source.as_deref(), &err);
|
||||
delay = app_support::next_delay(delay);
|
||||
tokio::time::sleep(delay).await;
|
||||
continue;
|
||||
@ -223,6 +231,12 @@ impl LesavkaClientApp {
|
||||
Err(err) => {
|
||||
telemetry.record_disconnect(format!("webcam uplink setup task failed: {err}"));
|
||||
warn!("📸 webcam uplink setup task failed before StreamCamera could start: {err}");
|
||||
abort_if_required_media_source_failed(
|
||||
"camera",
|
||||
"📸",
|
||||
active_source.as_deref(),
|
||||
&err,
|
||||
);
|
||||
delay = app_support::next_delay(delay);
|
||||
tokio::time::sleep(delay).await;
|
||||
continue;
|
||||
@ -412,6 +426,39 @@ fn parse_camera_profile_id(raw: &str) -> Option<(u32, u32, u32)> {
|
||||
(width > 0 && height > 0 && fps > 0).then_some((width, height, fps))
|
||||
}
|
||||
|
||||
#[cfg(not(coverage))]
|
||||
fn abort_if_required_media_source_failed(
|
||||
kind: &str,
|
||||
icon: &str,
|
||||
source: Option<&str>,
|
||||
err: &dyn std::fmt::Display,
|
||||
) {
|
||||
if !explicit_media_sources_required() || source.is_none_or(|source| source.trim().is_empty()) {
|
||||
return;
|
||||
}
|
||||
let source = source.expect("checked source presence");
|
||||
error!(
|
||||
"{icon} required {kind} source '{source}' failed to start; aborting client because LESAVKA_REQUIRE_EXPLICIT_MEDIA_SOURCES=1: {err}"
|
||||
);
|
||||
eprintln!(
|
||||
"{icon} required {kind} source '{source}' failed to start; aborting client because LESAVKA_REQUIRE_EXPLICIT_MEDIA_SOURCES=1: {err}"
|
||||
);
|
||||
std::process::exit(2);
|
||||
}
|
||||
|
||||
#[cfg(not(coverage))]
|
||||
fn explicit_media_sources_required() -> bool {
|
||||
std::env::var("LESAVKA_REQUIRE_EXPLICIT_MEDIA_SOURCES")
|
||||
.ok()
|
||||
.is_some_and(|value| {
|
||||
let value = value.trim();
|
||||
value == "1"
|
||||
|| value.eq_ignore_ascii_case("true")
|
||||
|| value.eq_ignore_ascii_case("yes")
|
||||
|| value.eq_ignore_ascii_case("on")
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(not(coverage))]
|
||||
const VIDEO_UPLINK_QUEUE: crate::uplink_fresh_queue::FreshQueueConfig =
|
||||
crate::uplink_fresh_queue::FreshQueueConfig {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "lesavka_common"
|
||||
version = "0.17.18"
|
||||
version = "0.17.19"
|
||||
edition = "2024"
|
||||
build = "build.rs"
|
||||
|
||||
|
||||
@ -10,7 +10,7 @@ bench = false
|
||||
|
||||
[package]
|
||||
name = "lesavka_server"
|
||||
version = "0.17.18"
|
||||
version = "0.17.19"
|
||||
edition = "2024"
|
||||
autobins = false
|
||||
|
||||
|
||||
@ -121,3 +121,20 @@ fn sync_probe_audio_queue_preserves_bounded_marker_continuity() {
|
||||
);
|
||||
assert_queue_policy(block, "PROBE_AUDIO_QUEUE", "DrainOldest");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn strict_explicit_media_source_failures_abort_the_headless_probe_client() {
|
||||
for expected in [
|
||||
"LESAVKA_REQUIRE_EXPLICIT_MEDIA_SOURCES",
|
||||
"abort_if_required_media_source_failed",
|
||||
"required {kind} source '{source}' failed to start",
|
||||
"std::process::exit(2)",
|
||||
"abort_if_required_media_source_failed(\"microphone\"",
|
||||
"abort_if_required_media_source_failed(\"camera\"",
|
||||
] {
|
||||
assert!(
|
||||
UPLINK_MEDIA_SRC.contains(expected),
|
||||
"required-source setup failures must be fatal in strict probe mode: missing {expected}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user