2026-04-21 21:38:22 -03:00
|
|
|
//! Contract tests for launcher-owned relay process lifetime.
|
|
|
|
|
//!
|
|
|
|
|
//! Scope: static guardrails around launcher runtime process management.
|
|
|
|
|
//! Targets: `client/src/launcher/ui_runtime.rs`.
|
|
|
|
|
//! Why: the launcher is the owner of the live relay. If it crashes, audio,
|
|
|
|
|
//! video, and input streams must not keep running as leaked child processes.
|
|
|
|
|
|
|
|
|
|
const UI_RUNTIME_SRC: &str = include_str!("../../client/src/launcher/ui_runtime.rs");
|
2026-04-21 22:15:47 -03:00
|
|
|
const UI_SRC: &str = include_str!("../../client/src/launcher/ui.rs");
|
2026-04-22 05:46:31 -03:00
|
|
|
const DEVICE_TEST_SRC: &str = include_str!("../../client/src/launcher/device_test.rs");
|
|
|
|
|
const CAMERA_SRC: &str = include_str!("../../client/src/input/camera.rs");
|
|
|
|
|
const MICROPHONE_SRC: &str = include_str!("../../client/src/input/microphone.rs");
|
2026-04-21 21:38:22 -03:00
|
|
|
const LAUNCHER_MOD_SRC: &str = include_str!("../../client/src/launcher/mod.rs");
|
|
|
|
|
const MAIN_SRC: &str = include_str!("../../client/src/main.rs");
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn relay_child_gets_parent_identity_from_launcher() {
|
|
|
|
|
assert!(UI_RUNTIME_SRC.contains("\"LESAVKA_LAUNCHER_PARENT_PID\""));
|
|
|
|
|
assert!(UI_RUNTIME_SRC.contains("std::process::id().to_string()"));
|
|
|
|
|
assert!(UI_RUNTIME_SRC.contains("LESAVKA_LAUNCHER_PARENT_START_TICKS"));
|
|
|
|
|
assert!(UI_RUNTIME_SRC.contains("launcher_parent_start_ticks()"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn relay_child_starts_safe_parent_watchdog_on_boot() {
|
|
|
|
|
assert!(MAIN_SRC.contains("launcher::start_launcher_child_parent_watchdog_from_env();"));
|
|
|
|
|
assert!(LAUNCHER_MOD_SRC.contains("start_launcher_child_parent_watchdog_from_env"));
|
|
|
|
|
assert!(LAUNCHER_MOD_SRC.contains("launcher-parent-watchdog"));
|
|
|
|
|
assert!(LAUNCHER_MOD_SRC.contains("std::process::exit(0);"));
|
|
|
|
|
assert!(LAUNCHER_MOD_SRC.contains("proc_stat_start_ticks"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn relay_address_entry_is_locked_while_relay_is_live() {
|
|
|
|
|
assert!(UI_RUNTIME_SRC.contains("widgets.server_entry.set_sensitive(!relay_live);"));
|
2026-04-22 00:56:03 -03:00
|
|
|
assert!(
|
|
|
|
|
UI_RUNTIME_SRC.contains(
|
|
|
|
|
".camera_combo\n .set_sensitive(!relay_live && state.channels.camera);"
|
|
|
|
|
)
|
|
|
|
|
);
|
2026-04-22 22:10:39 -03:00
|
|
|
assert!(UI_RUNTIME_SRC.contains(".camera_quality_combo"));
|
|
|
|
|
assert!(UI_RUNTIME_SRC.contains("widgets.camera_quality_combo.set_sensitive("));
|
|
|
|
|
assert!(UI_RUNTIME_SRC.contains("state.devices.camera.is_some()"));
|
|
|
|
|
assert!(UI_RUNTIME_SRC.contains("state.camera_quality.is_some()"));
|
2026-04-22 00:56:03 -03:00
|
|
|
assert!(UI_RUNTIME_SRC.contains(
|
|
|
|
|
".microphone_combo\n .set_sensitive(!relay_live && state.channels.microphone);"
|
|
|
|
|
));
|
|
|
|
|
assert!(
|
|
|
|
|
UI_RUNTIME_SRC.contains(
|
|
|
|
|
".speaker_combo\n .set_sensitive(!relay_live && state.channels.audio);"
|
|
|
|
|
)
|
|
|
|
|
);
|
2026-04-22 22:10:39 -03:00
|
|
|
assert!(UI_RUNTIME_SRC.contains(".audio_gain_scale"));
|
|
|
|
|
assert!(UI_RUNTIME_SRC.contains(".set_sensitive(!relay_live && state.channels.audio);"));
|
|
|
|
|
assert!(UI_RUNTIME_SRC.contains(".mic_gain_scale"));
|
|
|
|
|
assert!(UI_RUNTIME_SRC.contains(".set_sensitive(!relay_live && state.channels.microphone);"));
|
2026-04-22 00:56:03 -03:00
|
|
|
assert!(UI_RUNTIME_SRC.contains("widgets.keyboard_combo.set_sensitive(!relay_live);"));
|
|
|
|
|
assert!(UI_RUNTIME_SRC.contains("widgets.mouse_combo.set_sensitive(!relay_live);"));
|
|
|
|
|
assert!(UI_RUNTIME_SRC.contains("widgets.camera_channel_toggle.set_sensitive(!relay_live);"));
|
|
|
|
|
assert!(
|
|
|
|
|
UI_RUNTIME_SRC.contains("widgets.microphone_channel_toggle.set_sensitive(!relay_live);")
|
|
|
|
|
);
|
|
|
|
|
assert!(UI_RUNTIME_SRC.contains("widgets.audio_channel_toggle.set_sensitive(!relay_live);"));
|
2026-04-21 21:38:22 -03:00
|
|
|
assert!(UI_RUNTIME_SRC.contains("\"Connect\""));
|
|
|
|
|
assert!(UI_RUNTIME_SRC.contains("\"Disconnect\""));
|
|
|
|
|
}
|
2026-04-21 22:15:47 -03:00
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn audio_gain_slider_callback_never_panics_on_refresh_reentry() {
|
|
|
|
|
assert!(UI_SRC.contains("fn apply_audio_gain_change("));
|
2026-04-22 00:56:03 -03:00
|
|
|
assert!(UI_SRC.contains("fn apply_mic_gain_change("));
|
2026-04-21 22:15:47 -03:00
|
|
|
assert!(UI_SRC.contains("state.try_borrow_mut()"));
|
|
|
|
|
assert!(UI_SRC.contains("return false;"));
|
|
|
|
|
assert!(UI_SRC.contains("glib::idle_add_local_once"));
|
|
|
|
|
assert!(!UI_SRC.contains("let mut state = state.borrow_mut();\n if state.audio_gain_percent == percent"));
|
|
|
|
|
}
|
2026-04-22 00:56:03 -03:00
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn live_power_probe_failures_do_not_flip_relay_state_red() {
|
|
|
|
|
assert!(UI_SRC.contains("PowerMessage::Refresh(Err(err))"));
|
|
|
|
|
assert!(UI_SRC.contains("let relay_live = child_proc.borrow().is_some()"));
|
|
|
|
|
assert!(UI_SRC.contains("if relay_live"));
|
|
|
|
|
assert!(UI_SRC.contains("state.set_server_available(true);"));
|
|
|
|
|
assert!(UI_SRC.contains("if !state.capture_power.available"));
|
|
|
|
|
}
|
2026-04-22 05:46:31 -03:00
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn active_relay_keeps_local_upstream_camera_and_microphone_evidence_visible() {
|
|
|
|
|
assert!(UI_RUNTIME_SRC.contains("UPLINK_CAMERA_PREVIEW_ENV"));
|
|
|
|
|
assert!(UI_RUNTIME_SRC.contains("UPLINK_MIC_LEVEL_ENV"));
|
|
|
|
|
assert!(UI_RUNTIME_SRC.contains("command.env(UPLINK_CAMERA_PREVIEW_ENV"));
|
|
|
|
|
assert!(UI_RUNTIME_SRC.contains("command.env(UPLINK_MIC_LEVEL_ENV"));
|
|
|
|
|
assert!(UI_SRC.contains("stop_local_capture_for_relay"));
|
|
|
|
|
assert!(UI_SRC.contains("sync_relay_uplink_probe"));
|
|
|
|
|
|
|
|
|
|
assert!(DEVICE_TEST_SRC.contains("fn start_relay_file("));
|
|
|
|
|
assert!(DEVICE_TEST_SRC.contains("run_camera_file_preview_feed"));
|
|
|
|
|
assert!(DEVICE_TEST_SRC.contains("read_camera_preview_tap"));
|
|
|
|
|
assert!(DEVICE_TEST_SRC.contains("LocalMicrophoneLevelProbe"));
|
|
|
|
|
assert!(DEVICE_TEST_SRC.contains("read_microphone_level_tap"));
|
|
|
|
|
|
|
|
|
|
assert!(CAMERA_SRC.contains("LESAVKA_UPLINK_CAMERA_PREVIEW"));
|
|
|
|
|
assert!(CAMERA_SRC.contains("appsink name=preview_sink"));
|
|
|
|
|
assert!(CAMERA_SRC.contains("spawn_camera_preview_tap"));
|
|
|
|
|
assert!(MICROPHONE_SRC.contains("LESAVKA_UPLINK_MIC_LEVEL"));
|
|
|
|
|
assert!(MICROPHONE_SRC.contains("appsink name=level_sink"));
|
|
|
|
|
assert!(MICROPHONE_SRC.contains("spawn_mic_level_tap"));
|
|
|
|
|
}
|
2026-04-22 22:10:39 -03:00
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn launcher_webcam_quality_selection_reaches_preview_and_relay_env() {
|
|
|
|
|
assert!(UI_SRC.contains("selected_camera_quality(&camera_quality_combo"));
|
|
|
|
|
assert!(UI_SRC.contains("sync_camera_quality_selection"));
|
|
|
|
|
assert!(UI_SRC.contains("tests.set_camera_quality"));
|
|
|
|
|
assert!(DEVICE_TEST_SRC.contains("pub fn set_camera_quality"));
|
|
|
|
|
assert!(DEVICE_TEST_SRC.contains("build_camera_preview_pipeline(&device, mode)"));
|
|
|
|
|
assert!(DEVICE_TEST_SRC.contains("capsfilter caps=\\\"video/x-raw"));
|
|
|
|
|
assert!(CAMERA_SRC.contains("env_u32(\"LESAVKA_CAM_WIDTH\", cfg.map_or"));
|
|
|
|
|
assert!(LAUNCHER_MOD_SRC.contains("\"LESAVKA_CAM_WIDTH\""));
|
|
|
|
|
assert!(LAUNCHER_MOD_SRC.contains("\"LESAVKA_CAM_H264_KBIT\""));
|
|
|
|
|
}
|