Files
qingjian/apps/windows/server/tests/ipc_loop.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

128 lines
3.8 KiB
Rust

//! 传输层测试:帧编解码,以及在内存流上跑 serve 循环;不碰命名管道,跨平台可跑。
use std::io::{Cursor, Read, Write};
use std::path::PathBuf;
use qingjian_core::Language;
use qingjian_platform::protocol::{
ClientMessage, KeyEvent, PROTOCOL_VERSION, ServerMessage, SessionId,
};
use qingjian_windows_server::ipc::{read_message, serve, write_message};
use qingjian_windows_server::{AssemblySpec, Router, RouterConfig, assembly};
const SESSION: SessionId = SessionId(1);
fn router() -> Router {
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");
Router::new(engine, RouterConfig::default())
}
fn letter(c: char) -> KeyEvent {
KeyEvent::new(c.to_ascii_uppercase() as u32, Some(c), Default::default())
}
#[test]
fn codec_round_trips_a_message() {
let original = ClientMessage::Key {
session: SESSION,
event: letter('n'),
};
let mut buffer = Vec::new();
write_message(&mut buffer, &original).unwrap();
let mut reader = Cursor::new(buffer);
let decoded: Option<ClientMessage> = read_message(&mut reader).unwrap();
assert_eq!(decoded, Some(original));
let end: Option<ClientMessage> = read_message(&mut reader).unwrap();
assert_eq!(end, None);
}
#[test]
fn serve_runs_the_open_type_loop_over_a_stream() {
let mut input = Vec::new();
write_message(
&mut input,
&ClientMessage::OpenSession {
session: SESSION,
app: None,
protocol: PROTOCOL_VERSION,
},
)
.unwrap();
for c in "nihao".chars() {
write_message(
&mut input,
&ClientMessage::Key {
session: SESSION,
event: letter(c),
},
)
.unwrap();
}
let mut router = router();
let mut stream = Duplex {
inbound: Cursor::new(input),
outbound: Vec::new(),
};
serve(&mut stream, &mut router).unwrap();
let mut responses = Vec::new();
let mut out = Cursor::new(stream.outbound);
while let Some(message) = read_message::<_, ServerMessage>(&mut out).unwrap() {
responses.push(message);
}
// 开会话先回一条 `SessionOpened`(把按键行为设置带下来),之后五个按键各回一条 `KeyResult`。
assert_eq!(responses.len(), 6, "一条 SessionOpened 加五条 KeyResult");
assert!(
matches!(responses.first(), Some(ServerMessage::SessionOpened { .. })),
"首条应是 SessionOpened,实际:{:?}",
responses.first()
);
let ServerMessage::KeyResult { frame, .. } = responses.last().unwrap() else {
panic!("末条应是 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:?}"
);
}
/// 预置输入 + 输出缓冲拼成的流(先发后收够测)。
struct Duplex {
inbound: Cursor<Vec<u8>>,
outbound: Vec<u8>,
}
impl Read for Duplex {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
self.inbound.read(buf)
}
}
impl Write for Duplex {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.outbound.write(buf)
}
fn flush(&mut self) -> std::io::Result<()> {
self.outbound.flush()
}
}