// 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!(concat!( env!("CARGO_MANIFEST_DIR"), "/client/src/launcher/ui_runtime.rs" )), include_str!(concat!( env!("CARGO_MANIFEST_DIR"), "/client/src/launcher/ui_runtime/control_paths.rs" )), include_str!(concat!( env!("CARGO_MANIFEST_DIR"), "/client/src/launcher/ui_runtime/process_logs.rs" )), include_str!(concat!( env!("CARGO_MANIFEST_DIR"), "/client/src/launcher/ui_runtime/status_refresh.rs" )), include_str!(concat!( env!("CARGO_MANIFEST_DIR"), "/client/src/launcher/ui_runtime/status_details.rs" )), ); const UI_SRC: &str = concat!( include_str!(concat!( env!("CARGO_MANIFEST_DIR"), "/client/src/launcher/ui.rs" )), include_str!(concat!( env!("CARGO_MANIFEST_DIR"), "/client/src/launcher/ui/message_and_network_state.rs" )), include_str!(concat!( env!("CARGO_MANIFEST_DIR"), "/client/src/launcher/ui/control_requests.rs" )), include_str!(concat!( env!("CARGO_MANIFEST_DIR"), "/client/src/launcher/ui/activation_setup.rs" )), include_str!(concat!( env!("CARGO_MANIFEST_DIR"), "/client/src/launcher/ui/device_refresh_binding.rs" )), include_str!(concat!( env!("CARGO_MANIFEST_DIR"), "/client/src/launcher/ui/local_test_bindings.rs" )), include_str!(concat!( env!("CARGO_MANIFEST_DIR"), "/client/src/launcher/ui/media_device_bindings.rs" )), include_str!(concat!( env!("CARGO_MANIFEST_DIR"), "/client/src/launcher/ui/relay_input_bindings.rs" )), include_str!(concat!( env!("CARGO_MANIFEST_DIR"), "/client/src/launcher/ui/runtime_poll.rs" )), include_str!(concat!( env!("CARGO_MANIFEST_DIR"), "/client/src/launcher/ui/runtime_poll/runtime_monitor_tick.rs" )), include_str!(concat!( env!("CARGO_MANIFEST_DIR"), "/client/src/launcher/ui/stage_device_bindings.rs" )), include_str!(concat!( env!("CARGO_MANIFEST_DIR"), "/client/src/launcher/ui/eye_capture_bindings.rs" )), include_str!(concat!( env!("CARGO_MANIFEST_DIR"), "/client/src/launcher/ui/utility_button_bindings.rs" )), ); const DEVICE_TEST_SRC: &str = concat!( include_str!(concat!( env!("CARGO_MANIFEST_DIR"), "/client/src/launcher/device_test.rs" )), include_str!(concat!( env!("CARGO_MANIFEST_DIR"), "/client/src/launcher/device_test/controller.rs" )), include_str!(concat!( env!("CARGO_MANIFEST_DIR"), "/client/src/launcher/device_test/local_preview.rs" )), include_str!(concat!( env!("CARGO_MANIFEST_DIR"), "/client/src/launcher/device_test/pipeline_helpers.rs" )), ); const CAMERA_SRC: &str = concat!( include_str!(concat!( env!("CARGO_MANIFEST_DIR"), "/client/src/input/camera.rs" )), include_str!(concat!( env!("CARGO_MANIFEST_DIR"), "/client/src/input/camera/capture_pipeline.rs" )), include_str!(concat!( env!("CARGO_MANIFEST_DIR"), "/client/src/input/camera/encoder_selection.rs" )), include_str!(concat!( env!("CARGO_MANIFEST_DIR"), "/client/src/input/camera/preview_tap.rs" )), include_str!(concat!( env!("CARGO_MANIFEST_DIR"), "/client/src/input/camera/source_description.rs" )), ); const MICROPHONE_SRC: &str = include_str!(concat!( env!("CARGO_MANIFEST_DIR"), "/client/src/input/microphone.rs" )); const LAUNCHER_MOD_SRC: &str = include_str!(concat!( env!("CARGO_MANIFEST_DIR"), "/client/src/launcher/mod.rs" )); const MAIN_SRC: &str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/client/src/main.rs")); const UI_COMPONENTS_SRC: &str = concat!( include_str!(concat!( env!("CARGO_MANIFEST_DIR"), "/client/src/launcher/ui_components.rs" )), include_str!(concat!( env!("CARGO_MANIFEST_DIR"), "/client/src/launcher/ui_components/build_shell.rs" )), ); const PREVIEW_SRC: &str = concat!( include_str!(concat!( env!("CARGO_MANIFEST_DIR"), "/client/src/launcher/preview.rs" )), include_str!(concat!( env!("CARGO_MANIFEST_DIR"), "/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_locks_but_media_staging_stays_available_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(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(state.channels.microphone);") ); assert!( UI_RUNTIME_SRC.contains(".speaker_combo\n .set_sensitive(state.channels.audio);") ); assert!(UI_RUNTIME_SRC.contains(".audio_gain_scale")); assert!(UI_RUNTIME_SRC.contains(".set_sensitive(state.channels.audio);")); assert!(UI_RUNTIME_SRC.contains(".mic_gain_scale")); assert!(UI_RUNTIME_SRC.contains(".set_sensitive(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(".camera_channel_toggle\n .set_sensitive(!relay_live || state.devices.camera.is_some());") ); assert!( UI_RUNTIME_SRC.contains( ".microphone_channel_toggle\n .set_sensitive(!relay_live || state.devices.microphone.is_some());" ) ); assert!( UI_RUNTIME_SRC.contains( ".audio_channel_toggle\n .set_sensitive(!relay_live || state.devices.speaker.is_some());" ) ); assert!(UI_RUNTIME_SRC.contains("Soft-pause or resume this feed in the running relay")); assert!(UI_SRC.contains("{feed_label} selection is staged for the next relay launch")); assert!(UI_SRC.contains("Camera quality")); assert!(UI_SRC.contains("Microphone")); assert!(UI_SRC.contains("Speaker")); 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("fn resolved_capture_profile")); assert!(CAMERA_SRC.contains("LESAVKA_CAM_LOCK_TO_SERVER_PROFILE")); assert!(CAMERA_SRC.contains("LESAVKA_CAM_EMIT_UI_PROFILE")); assert!(LAUNCHER_MOD_SRC.contains("\"LESAVKA_CAM_WIDTH\"")); assert!(LAUNCHER_MOD_SRC.contains("\"LESAVKA_CAM_H264_KBIT\"")); } #[test] fn launcher_audio_transport_selection_reaches_relay_env_and_chips() { assert!(LAUNCHER_MOD_SRC.contains("\"LESAVKA_UPLINK_AUDIO_CODEC\"")); assert!(LAUNCHER_MOD_SRC.contains("\"LESAVKA_MIC_NOISE_SUPPRESSION\"")); assert!(UI_RUNTIME_SRC.contains("state.upstream_audio_transport.as_common_codec()")); assert!(UI_RUNTIME_SRC.contains("state.mic_noise_suppression")); assert!(UI_RUNTIME_SRC.contains("state.upstream_audio_transport.label()")); assert!(UI_RUNTIME_SRC.contains("Upstream microphone transport:")); assert!(UI_RUNTIME_SRC.contains("Noise suppression is enabled before transport.")); assert!( UI_RUNTIME_SRC.contains("Changing upstream audio transport restarts the microphone path") ); assert!( UI_RUNTIME_SRC.contains( "Choose Opus for compressed upstream audio or PCM as the known-good fallback." ) ); assert!(UI_SRC.contains("audio_combo.connect_changed")); assert!(UI_SRC.contains("toggle.connect_toggled")); } #[test] fn launcher_utility_buttons_still_bind_to_live_actions() { assert!(UI_SRC.contains("widgets.clipboard_button.connect_clicked")); assert!(UI_SRC.contains("certs_button.connect_clicked")); assert!(UI_SRC.contains("Choose Lesavka Client TLS Bundle")); assert!(UI_SRC.contains("install_client_pki_bundle(&bundle)")); assert!(UI_SRC.contains(".config/lesavka/pki")); assert!(UI_SRC.contains("send_clipboard_text_to_remote(&server_addr, &text)")); assert!(UI_SRC.contains("Start the relay before sending clipboard text.")); assert!(!UI_SRC.contains("widgets.probe_button.connect_clicked")); assert!(!UI_SRC.contains("Quality probe command copied to the local clipboard.")); assert!(UI_SRC.contains("pane.save_button.connect_clicked")); assert!(UI_SRC.contains("Choose Eye Capture Folder")); assert!(UI_SRC.contains("pane.clip_button.connect_clicked")); assert!(UI_SRC.contains("clip saved to")); assert!(UI_SRC.contains("record_button.connect_clicked")); assert!(UI_SRC.contains("recording saved to")); assert!(UI_SRC.contains("press Stop to finish.")); assert!(UI_SRC.contains("widgets.usb_recover_button.connect_clicked")); assert!(UI_SRC.contains("recover_usb_soft(&server_addr)")); assert!(UI_SRC.contains("recover_uac_soft(&server_addr)")); assert!(UI_SRC.contains("recover_uvc_soft(&server_addr)")); assert!(UI_SRC.contains("Recover HID 2/3: handles reopened.")); assert!(UI_SRC.contains("Recover Audio 2/3: old epoch released.")); assert!(UI_SRC.contains("Recover Video 2/3: webcam sink retired.")); } #[test] fn launcher_normalizes_bare_relay_addresses_to_https() { assert!(UI_RUNTIME_SRC.contains("pub fn normalize_server_addr(raw: &str) -> String")); assert!(UI_RUNTIME_SRC.contains("trimmed.contains(\"://\")")); assert!(UI_RUNTIME_SRC.contains("format!(\"https://{trimmed}\")")); assert!(UI_RUNTIME_SRC.contains("entry.set_text(&normalized);")); } #[test] fn server_chip_distinguishes_reachable_from_connected() { assert!(UI_RUNTIME_SRC.contains("fn server_light_state(")); assert!(UI_RUNTIME_SRC.contains("StatusLightState::Connected")); assert!(UI_RUNTIME_SRC.contains("server_versions_match(state)")); assert!(UI_RUNTIME_SRC.contains("} else if relay_live {")); 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 relay_action_button_marks_disconnect_as_destructive() { assert!(UI_RUNTIME_SRC.contains("set_rail_button_label(")); assert!(UI_RUNTIME_SRC.contains("widgets.start_button.add_css_class(\"destructive-action\")")); assert!( UI_RUNTIME_SRC.contains("widgets.start_button.remove_css_class(\"destructive-action\")") ); assert!(UI_RUNTIME_SRC.contains("widgets.start_button.add_css_class(\"suggested-action\")")); } #[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")); }