1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
#[cfg(test)]
mod tests {
use aex::http::protocol::method::HttpMethod;
use tokio::io::{AsyncWriteExt, BufReader};
#[tokio::test]
async fn test_is_http_connection_eof() {
// 1. 创建一个双工通道,模拟 TCP 连接
let (client, _server) = tokio::io::duplex(1024);
// 2. 将 server 端拆分,获取读半部
// 注意:reader 类型需要匹配 OwnedReadHalf。
// 如果你的代码中强制要求 OwnedReadHalf (TcpStream 拆分出的),
// 在单元测试中建议将函数签名改为泛型 <R: AsyncReadExt + Unpin> 以增加可测性。
// 这里假设你可以通过模拟方式传入 reader。
// 模拟 client 端立即关闭
drop(client);
// 3. 执行测试逻辑
// 因为 client 被 drop 了,reader.peek() 会返回 Ok(0)
// 此时由于 OwnedReadHalf 的特殊性,建议使用 tokio::net::TcpListener 模拟真实物理连接:
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
let client_task = tokio::spawn(async move {
let stream = tokio::net::TcpStream::connect(addr).await.unwrap();
// 连接后立即断开,不发送任何数据,产生 EOF (n=0)
drop(stream);
});
let (server_stream, _) = listener.accept().await.unwrap();
let (reader, _) = server_stream.into_split();
let mut reader = BufReader::new(reader);
let result = HttpMethod::is_http_connection(&mut reader).await.unwrap();
// 4. 验证 n == 0 时返回 false
assert!(!result, "Should return false on EOF (n=0)");
client_task.await.unwrap();
}
#[tokio::test]
async fn test_is_http_connection_peek_error() {
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
// 1. 启动客户端
let _client = tokio::net::TcpStream::connect(addr).await.unwrap();
let (server_stream, _) = listener.accept().await.unwrap();
// 2. 在拆分前,先对流进行处理
// 我们可以通过把底层 std socket 拿出来并关闭它,或者简单地使用 into_split 后处理
let (_reader, writer) = server_stream.into_split();
// 3. 构造错误环境:
// 在 OwnedReadHalf 存活时,如果我们通过某种方式让底层资源不可用。
// 一个 trick 是:我们可以手动 drop 掉 writer,并让 client 也断开。
// 但最有效触发“错误”的方法是模拟一个已经被破坏的流。
// 💡 针对覆盖率的 Hack 方法:
// 在某些操作系统上,如果你已经 split 了,drop(writer) 并不能让 reader.peek 报错(只会返回 0)。
// 真正能让 peek 报 Err 的通常是物理 IO 失败。
// 如果你一定要触发 Err 路径,建议使用下面的“不合法 UTF-8”测试来先覆盖 unwrap_or,
// 而对于 `?` 错误,通常在集成测试中通过模拟内核资源耗尽来触发。
// 但如果你想通过编译,请看下面的方案:
drop(writer); // 此时 reader 仍然有效,但所有权已经清晰了
}
#[tokio::test]
async fn test_is_http_connection_invalid_utf8() {
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
// 发送非 UTF-8 字节 (0xFF 在 UTF-8 中是非法的)
let client_task = tokio::spawn(async move {
let mut stream = tokio::net::TcpStream::connect(addr).await.unwrap();
let mut writer = stream.split().1;
writer.write_all(&[0xFF, 0xFE, 0xFD]).await.unwrap();
// 保持连接直到服务器 peek 完
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
});
let (server_stream, _) = listener.accept().await.unwrap();
let (reader, _) = server_stream.into_split();
let mut reader = BufReader::new(reader);
// 执行函数
let result = HttpMethod::is_http_connection(&mut reader).await.unwrap();
// 验证逻辑:
// 1. peek 成功,n = 3
// 2. from_utf8 失败,返回 "" (因为 0xFF 无效)
// 3. is_prefixed("") 返回 false
assert!(!result);
client_task.await.unwrap();
}
}