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] Run shell syntax checks, focused contract tests, and package checks.
|
||||||
- [x] Push clean semver `0.17.18` for installed client/server testing.
|
- [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.
|
- [ ] 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]]
|
[[package]]
|
||||||
name = "lesavka_client"
|
name = "lesavka_client"
|
||||||
version = "0.17.18"
|
version = "0.17.19"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-stream",
|
"async-stream",
|
||||||
@ -1686,7 +1686,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lesavka_common"
|
name = "lesavka_common"
|
||||||
version = "0.17.18"
|
version = "0.17.19"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64",
|
"base64",
|
||||||
@ -1698,7 +1698,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lesavka_server"
|
name = "lesavka_server"
|
||||||
version = "0.17.18"
|
version = "0.17.19"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64",
|
"base64",
|
||||||
|
|||||||
@ -4,7 +4,7 @@ path = "src/main.rs"
|
|||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "lesavka_client"
|
name = "lesavka_client"
|
||||||
version = "0.17.18"
|
version = "0.17.19"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|||||||
@ -40,6 +40,7 @@ impl LesavkaClientApp {
|
|||||||
"🎤 microphone uplink setup failed for {:?}: {err:#}",
|
"🎤 microphone uplink setup failed for {:?}: {err:#}",
|
||||||
active_source.as_deref().unwrap_or("auto")
|
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);
|
delay = app_support::next_delay(delay);
|
||||||
tokio::time::sleep(delay).await;
|
tokio::time::sleep(delay).await;
|
||||||
continue;
|
continue;
|
||||||
@ -47,6 +48,12 @@ impl LesavkaClientApp {
|
|||||||
Err(err) => {
|
Err(err) => {
|
||||||
telemetry.record_disconnect(format!("microphone uplink setup task failed: {err}"));
|
telemetry.record_disconnect(format!("microphone uplink setup task failed: {err}"));
|
||||||
warn!("🎤 microphone uplink setup task failed before StreamMicrophone could start: {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);
|
delay = app_support::next_delay(delay);
|
||||||
tokio::time::sleep(delay).await;
|
tokio::time::sleep(delay).await;
|
||||||
continue;
|
continue;
|
||||||
@ -216,6 +223,7 @@ impl LesavkaClientApp {
|
|||||||
"📸 webcam uplink setup failed for {:?}: {err:#}",
|
"📸 webcam uplink setup failed for {:?}: {err:#}",
|
||||||
active_source.as_deref().unwrap_or("auto")
|
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);
|
delay = app_support::next_delay(delay);
|
||||||
tokio::time::sleep(delay).await;
|
tokio::time::sleep(delay).await;
|
||||||
continue;
|
continue;
|
||||||
@ -223,6 +231,12 @@ impl LesavkaClientApp {
|
|||||||
Err(err) => {
|
Err(err) => {
|
||||||
telemetry.record_disconnect(format!("webcam uplink setup task failed: {err}"));
|
telemetry.record_disconnect(format!("webcam uplink setup task failed: {err}"));
|
||||||
warn!("📸 webcam uplink setup task failed before StreamCamera could start: {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);
|
delay = app_support::next_delay(delay);
|
||||||
tokio::time::sleep(delay).await;
|
tokio::time::sleep(delay).await;
|
||||||
continue;
|
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))
|
(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))]
|
#[cfg(not(coverage))]
|
||||||
const VIDEO_UPLINK_QUEUE: crate::uplink_fresh_queue::FreshQueueConfig =
|
const VIDEO_UPLINK_QUEUE: crate::uplink_fresh_queue::FreshQueueConfig =
|
||||||
crate::uplink_fresh_queue::FreshQueueConfig {
|
crate::uplink_fresh_queue::FreshQueueConfig {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "lesavka_common"
|
name = "lesavka_common"
|
||||||
version = "0.17.18"
|
version = "0.17.19"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
build = "build.rs"
|
build = "build.rs"
|
||||||
|
|
||||||
|
|||||||
@ -10,7 +10,7 @@ bench = false
|
|||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "lesavka_server"
|
name = "lesavka_server"
|
||||||
version = "0.17.18"
|
version = "0.17.19"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
autobins = false
|
autobins = false
|
||||||
|
|
||||||
|
|||||||
@ -121,3 +121,20 @@ fn sync_probe_audio_queue_preserves_bounded_marker_continuity() {
|
|||||||
);
|
);
|
||||||
assert_queue_policy(block, "PROBE_AUDIO_QUEUE", "DrainOldest");
|
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