Files
qingjian/apps/windows/server/tests/pipe.rs
T
ym lee 96820dde4c fix(windows): 按 review 收尾:CI 两处、激活时缺省值盖设置、判键回到原口径
- `ipc_loop.rs` / `pipe.rs`:开会话现在多回一条 `SessionOpened`,期望的条数从 5 改成 6
  (`pipe.rs` 那个是读 6 条、只取其中的 `KeyResult`)
- `protocol_loop.rs`:`EngineClient::open` 改成返回元组之后这个文件没跟着改 ——
  它是 `#![cfg(unix)]`,Windows 上根本编不到,只有 CI 会红
- `Activate`:`connect()` 已经把 Server 下发的设置应用了,紧接着的
  `apply_input_settings(InputSettings::default())` 又把它盖成缺省值,要等下一拍 `SyncMode` 才改回来 ——
  关掉内置英文模式的人每次激活都会先登记上中 / 英按钮、之后也不会撤。缺省值改成只在没连上 Server 时用。
- `eats_key`:字母判定回到原来的口径(组句外 Shift + 大写归应用,V / U / I 例外),
  `push_shifted` / `apply_chinese` 那条属于另一个改动,这个 PR 不带。

另外按 review 去掉了「`-` `=` 由壳自己插入」那个提交:main 上已经有通用机制
(Server 放行没吃掉的按键时由 DLL 自己把字符插进文档),不用为它单开一条路。
2026-09-17 22:22:36 +08:00

103 lines
3.3 KiB
Rust

//! 真命名管道的端到端测试(仅 Windows):监听线程 + 文件句柄客户端,走完「开会话 → 敲 nihao → 收候选」。
#![cfg(windows)]
use std::fs::OpenOptions;
use std::path::PathBuf;
use std::thread;
use std::time::Duration;
use qingjian_core::Language;
use qingjian_platform::protocol::{
ClientMessage, KeyEvent, PROTOCOL_VERSION, ServerMessage, SessionId,
};
use qingjian_windows_server::ipc::pipe::serve_pipe;
use qingjian_windows_server::ipc::{read_message, write_message};
use qingjian_windows_server::{AssemblySpec, Router, RouterConfig, assembly};
const SESSION: SessionId = SessionId(1);
fn letter(c: char) -> KeyEvent {
KeyEvent::new(c.to_ascii_uppercase() as u32, Some(c), Default::default())
}
/// 客户端重试打开管道,直到监听线程建好实例。
fn connect(name: &str) -> std::fs::File {
for _ in 0..50 {
match OpenOptions::new().read(true).write(true).open(name) {
Ok(file) => return file,
Err(_) => thread::sleep(Duration::from_millis(50)),
}
}
panic!("连不上管道 {name}");
}
#[test]
fn named_pipe_round_trips_the_open_type_loop() {
let name = format!(r"\\.\pipe\qingjian-test-{}", std::process::id());
// 监听线程服务完一个客户端后阻塞等下一个,随进程退出即可。
let server_name = name.clone();
thread::spawn(move || {
let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../..");
let engine = assembly::assemble(&AssemblySpec {
glossary: Some((
Language::English,
root.join("assets/sample/glossary-en.tsv"),
)),
..AssemblySpec::new(root.join("assets/sample/dict.tsv"))
})
.expect("assemble engine from sample data");
let mut router = Router::new(engine, RouterConfig::default());
let (work_tx, work_rx) = std::sync::mpsc::channel();
let _ = serve_pipe(&server_name, &mut router, work_tx, work_rx);
});
let mut client = connect(&name);
write_message(
&mut client,
&ClientMessage::OpenSession {
session: SESSION,
app: None,
protocol: PROTOCOL_VERSION,
},
)
.unwrap();
for c in "nihao".chars() {
write_message(
&mut client,
&ClientMessage::Key {
session: SESSION,
event: letter(c),
},
)
.unwrap();
}
// 开会话先回一条 `SessionOpened`,之后五个按键各回一条 `KeyResult`。
let mut last_frame = None;
for _ in 0..6 {
let message: ServerMessage = read_message(&mut client)
.expect("read response")
.expect("server closed early");
if let ServerMessage::KeyResult { frame, .. } = message {
last_frame = Some(frame);
}
}
let frame = last_frame.expect("至少一条 KeyResult");
let preedit: String = frame.preedit.iter().map(|s| s.text.as_str()).collect();
assert_eq!(preedit, "ni'hao");
let texts: Vec<&str> = frame
.candidates
.items
.iter()
.map(|c| c.text.as_str())
.collect();
assert!(
texts.contains(&"你好"),
"候选里应有「你好」,实际:{texts:?}"
);
}