Files
RustNotMC/launcher/tests/hidden_processes.rs
SashegDev 874b6f20c2 Клиент: начальный импорт кодовой базы
Основа — Horizon (MIT). История апстрима не переносится: этот репозиторий ведёт свою.
2026-10-29 22:04:28 +03:00

117 lines
3.7 KiB
Rust

//! Windows background processes must keep logs, input and exit status without a console.
#![cfg(windows)]
mod support;
use horizon_launcher::{
backend::{self, Job, Msg},
process,
};
use std::{fs, path::Path, sync::mpsc, time::Duration};
use support::console_probe as probe;
fn logged_child(fail: bool) -> (Vec<String>, Result<(), String>) {
let (sender, receiver) = mpsc::channel();
let mut command = std::process::Command::new(probe());
if fail {
command.arg("--fail");
}
let mut child = backend::spawn(Job::Prepare, command, sender).unwrap();
child.send("input reaches the child");
let mut lines = Vec::new();
loop {
match receiver.recv_timeout(Duration::from_secs(10)).unwrap() {
Msg::Line(Job::Prepare, line) => lines.push(line),
Msg::Exit(Job::Prepare, result) => return (lines, result),
_ => panic!("unexpected job message"),
}
}
}
#[test]
fn hidden_children_keep_stdout_stderr_stdin_and_success() {
let (lines, result) = logged_child(false);
assert!(result.is_ok(), "{result:?}");
for expected in [
"console_window_visible=false",
"stdout is captured",
"stderr is captured",
"stdin=input reaches the child",
] {
assert!(
lines.iter().any(|line| line == expected),
"missing {expected}: {lines:?}"
);
}
}
#[test]
fn hidden_children_report_failure_and_keep_their_logs() {
let (lines, result) = logged_child(true);
assert!(result.unwrap_err().contains('7'));
assert!(lines.iter().any(|line| line == "stderr is captured"));
}
#[test]
fn exporters_write_to_their_log_file_without_a_console() {
let root = probe().parent().unwrap();
let path = root.join("exporter.log");
let log = fs::File::create(&path).unwrap();
let jdk = horizon_prepare::java::Jdk {
java: probe().into(),
javac: probe().into(),
};
jdk.java(&[], Some(root), &log).unwrap();
let output = fs::read_to_string(path).unwrap();
for expected in [
"console_window_visible=false",
"stdout is captured",
"stderr is captured",
] {
assert!(output.contains(expected), "missing {expected}: {output}");
}
}
#[test]
fn cli_keeps_redirected_stdout_and_stderr() {
let executable = env!("CARGO_BIN_EXE_horizon-launcher");
let version = process::command(executable)
.arg("--version")
.output()
.unwrap();
assert!(version.status.success());
assert!(String::from_utf8_lossy(&version.stdout).starts_with("horizon-launcher "));
let invalid = process::command(executable)
.arg("--unknown-option")
.output()
.unwrap();
assert!(!invalid.status.success());
assert!(String::from_utf8_lossy(&invalid.stderr).contains("--unknown-option"));
}
#[test]
fn mcp_keeps_its_stdio_transport() {
let executable = Path::new(env!("CARGO_BIN_EXE_horizon-launcher"));
assert!(horizon_launcher::agents::self_test(executable).unwrap() > 0);
}
#[test]
fn desktop_executable_uses_the_windows_gui_subsystem() {
const PE_HEADER_POINTER: usize = 0x3c;
const PE_OPTIONAL_HEADER_OFFSET: usize = 24;
const SUBSYSTEM_OFFSET: usize = 68;
const WINDOWS_GUI_SUBSYSTEM: u16 = 2;
let executable = fs::read(env!("CARGO_BIN_EXE_horizon-launcher")).unwrap();
let header = u32::from_le_bytes(
executable[PE_HEADER_POINTER..PE_HEADER_POINTER + 4]
.try_into()
.unwrap(),
) as usize;
assert_eq!(&executable[header..header + 4], b"PE\0\0");
let subsystem = header + PE_OPTIONAL_HEADER_OFFSET + SUBSYSTEM_OFFSET;
assert_eq!(
u16::from_le_bytes(executable[subsystem..subsystem + 2].try_into().unwrap()),
WINDOWS_GUI_SUBSYSTEM
);
}