//! Contract tests for launcher-owned relay process lifetime. //! //! Scope: static guardrails around launcher runtime process management. //! Targets: split launcher UI/runtime modules under `client/src/launcher/`. //! 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 = concat!( include_str!("../../client/src/launcher/ui_runtime.rs"), include_str!("../../client/src/launcher/ui_runtime/control_paths.rs"), include_str!("../../client/src/launcher/ui_runtime/process_logs.rs"), include_str!("../../client/src/launcher/ui_runtime/status_refresh.rs"), include_str!("../../client/src/launcher/ui_runtime/status_details.rs"), ); const UI_SRC: &str = concat!( include_str!("../../client/src/launcher/ui.rs"), include_str!("../../client/src/launcher/ui/message_and_network_state.rs"), include_str!("../../client/src/launcher/ui/control_requests.rs"), include_str!("../../client/src/launcher/ui/activation_setup.rs"), include_str!("../../client/src/launcher/ui/device_refresh_binding.rs"), include_str!("../../client/src/launcher/ui/local_test_bindings.rs"), include_str!("../../client/src/launcher/ui/relay_input_bindings.rs"), include_str!("../../client/src/launcher/ui/runtime_poll.rs"), include_str!("../../client/src/launcher/ui/stage_device_bindings.rs"), ); const DEVICE_TEST_SRC: &str = concat!( include_str!("../../client/src/launcher/device_test.rs"), include_str!("../../client/src/launcher/device_test/controller.rs"), include_str!("../../client/src/launcher/device_test/local_preview.rs"), include_str!("../../client/src/launcher/device_test/pipeline_helpers.rs"), ); const CAMERA_SRC: &str = concat!( include_str!("../../client/src/input/camera.rs"), include_str!("../../client/src/input/camera/capture_pipeline.rs"), include_str!("../../client/src/input/camera/encoder_selection.rs"), include_str!("../../client/src/input/camera/preview_tap.rs"), include_str!("../../client/src/input/camera/source_description.rs"), ); const MICROPHONE_SRC: &str = include_str!("../../client/src/input/microphone.rs"); const LAUNCHER_MOD_SRC: &str = include_str!("../../client/src/launcher/mod.rs"); const MAIN_SRC: &str = include_str!("../../client/src/main.rs"); const UI_COMPONENTS_SRC: &str = concat!( include_str!("../../client/src/launcher/ui_components.rs"), include_str!("../../client/src/launcher/ui_components/build_shell.rs"), ); const PREVIEW_SRC: &str = concat!( include_str!("../../client/src/launcher/preview.rs"), include_str!("../../client/src/launcher/preview/status_pipeline.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);")); assert!( UI_RUNTIME_SRC.contains( ".camera_combo\n .set_sensitive(!relay_live && state.channels.camera);" ) ); 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()")); 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);" ) ); 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);")); 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);")); assert!(UI_RUNTIME_SRC.contains("\"Connect\"")); assert!(UI_RUNTIME_SRC.contains("\"Disconnect\"")); } #[test] fn audio_gain_slider_callback_never_panics_on_refresh_reentry() { assert!(UI_SRC.contains("fn apply_audio_gain_change(")); assert!(UI_SRC.contains("fn apply_mic_gain_change(")); 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")); } #[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")); } #[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")); } #[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("selected_camera_quality(&camera_quality_combo_read")); assert!(UI_SRC.contains("sync_camera_quality_selection")); assert!(UI_SRC.contains("let camera_quality_syncing = Rc::new(Cell::new(false));")); assert!(UI_SRC.contains("camera_quality_syncing.set(true);")); assert!(UI_SRC.contains("if camera_quality_syncing.get()")); assert!(UI_SRC.contains("state.try_borrow_mut()")); 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\"")); } #[test] fn server_chip_distinguishes_reachable_from_connected() { assert!(UI_RUNTIME_SRC.contains("fn server_light_state(")); assert!(UI_RUNTIME_SRC.contains("if relay_live")); assert!(UI_RUNTIME_SRC.contains("} else if state.server_available {")); assert!(UI_RUNTIME_SRC.contains("StatusLightState::Caution")); assert!(UI_RUNTIME_SRC.contains("fn server_version_label(")); assert!(UI_RUNTIME_SRC.contains("return \"-\".to_string();")); } #[test] fn launcher_brand_uses_readable_icon_size() { assert!(UI_COMPONENTS_SRC.contains("brand_icon.set_pixel_size(44);")); assert!(UI_COMPONENTS_SRC.contains("brand_icon.set_valign(gtk::Align::Center);")); assert!(UI_COMPONENTS_SRC.contains("heading.set_valign(gtk::Align::Center);")); } #[test] fn preview_logs_are_actionable_with_short_mythic_flavor() { assert!(PREVIEW_SRC.contains("waking {eye} eye preview")); assert!(PREVIEW_SRC.contains("connecting {eye} eye feed")); assert!(PREVIEW_SRC.contains("waiting for first frame")); assert!(PREVIEW_SRC.contains("Preview stream error. See session log.")); assert!(PREVIEW_SRC.contains("«У лукоморья дуб зелёный; златая цепь на дубе том…»")); assert!(PREVIEW_SRC.contains("«Там чудеса: там леший бродит, русалка на ветвях сидит…»")); assert!(PREVIEW_SRC.contains("«Подымите мне веки: не вижу!»")); assert!(PREVIEW_SRC.contains("«Идёт направо — песнь заводит, налево — сказку говорит…»")); assert!( PREVIEW_SRC .contains("«Там царь Кащей над златом чахнет; там русский дух… там Русью пахнет!»") ); assert!(PREVIEW_SRC.contains("«Там на неведомых дорожках следы невиданных зверей…»")); assert!(PREVIEW_SRC.contains("«Избушка, избушка! Встань к лесу задом, ко мне передом.»")); assert!(PREVIEW_SRC.contains("«Фу-фу! Русским духом пахнет!»")); assert!(PREVIEW_SRC.contains("«Дела давно минувших дней, преданья старины глубокой…»")); assert!(!PREVIEW_SRC.contains("is waking the preview spell")); assert!(!PREVIEW_SRC.contains("opens the watchfire")); assert!(!PREVIEW_SRC.contains("log spellbook")); }