rust - 如何测试绑定到 tokio TcpStream 的未来?
问题描述
我有一个未来,它Framed
使用LinesCodec
.
当我尝试将其包装在测试中时,大约 20% 的时间会阻塞未来,但因为我没有在尝试连接的套接字上监听任何内容,所以我希望总是会收到错误消息:
thread 'tokio-runtime-worker-0' panicked at 'error: Os { code: 111, kind: ConnectionRefused, message: "Connection refused" }', src/lib.rs:35:24 note: Run with 'RUST_BACKTRACE=1' for a backtrace.
这是我使用的测试代码:
#[macro_use(try_ready)]
extern crate futures; // 0.1.24
extern crate tokio; // 0.1.8
use std::io;
use std::net::SocketAddr;
use tokio::codec::{Framed, LinesCodec};
use tokio::net::TcpStream;
use tokio::prelude::*;
struct MyFuture {
addr: SocketAddr,
}
impl Future for MyFuture {
type Item = Framed<TcpStream, LinesCodec>;
type Error = io::Error;
fn poll(&mut self) -> Result<Async<Framed<TcpStream, LinesCodec>>, io::Error> {
let strm = try_ready!(TcpStream::connect(&self.addr).poll());
Ok(Async::Ready(Framed::new(strm, LinesCodec::new())))
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::net::Shutdown;
#[test]
fn connect() {
let addr: SocketAddr = "127.0.0.1:4222".parse().unwrap();
let fut = MyFuture { addr: addr }
.and_then(|f| {
println!("connected");
let cn = f.get_ref();
cn.shutdown(Shutdown::Both)
}).map_err(|e| panic!("error: {:?}", e));
tokio::run(fut)
}
}
我在其他语言中看到了一些模式,其中测试二进制文件本身提供了一种异步返回结果的机制,但还没有找到在 Rust 中使用类似机制的好方法。
解决方案
测试异步代码的一种简单方法可能是为每个测试使用专用运行时:启动它,等待未来完成并在测试结束时关闭运行时。
#[test]
fn my_case() {
// setup future f
// ...
tokio::run(f);
}
我不知道 Rust 生态系统中是否已经有统一的模式;请参阅有关对基于未来的代码的测试支持的演变的讨论。
为什么您的代码无法按预期工作
当您调用poll()
时,将查询未来以检查值是否可用。
如果某个值不可用,则会注册一个兴趣,以便poll()
在发生可以解决未来的事情时再次调用该兴趣。
当你MyFuture::poll()
被调用时:
TcpStream::connect
创造新的未来TcpStreamNew
TcpStreamNew::poll
仅在步骤 1 中创建未来时立即调用一次。- 未来超出范围,因此下次调用时,
MyFuture::poll
您永远不会解析先前创建的未来。
您已经注册了对未来的兴趣,如果您第一次轮询它时没有解决,您永远不会再次询问(轮询)已解决的值或错误。
“不确定”行为的原因是因为第一个poll
有时会立即解决并出现ConnectionRefused
错误,有时它会永远等待未来的连接事件或永远无法检索的失败。
看看mio::sys::unix::tcp::TcpStream
Tokio 使用的:
impl TcpStream {
pub fn connect(stream: net::TcpStream, addr: &SocketAddr) -> io::Result<TcpStream> {
set_nonblock(stream.as_raw_fd())?;
match stream.connect(addr) {
Ok(..) => {}
Err(ref e) if e.raw_os_error() == Some(libc::EINPROGRESS) => {}
Err(e) => return Err(e),
}
Ok(TcpStream {
inner: stream,
})
}
当您connect
使用非阻塞套接字时,系统调用可能会立即连接/失败或返回EINPROGRESS
,在最后一种情况下,必须触发轮询以检索错误的值。
推荐阅读
- javascript - 将 html 选择值放入 js 变量时出错
- api - 如何通过使用订阅 id 以外的自定义 apiKey 在 azure api 管理中使用速率限制?
- visual-studio - 如何集中 XSD 模式以供其他项目/解决方案使用?
- sql - 由于某种原因,查询返回除了 2 条记录之外的所有记录,即使它在技术上符合条件
- java - Storing usernames and passwords securely
- git - What exactly happens in a git push? Why isn't a git push considered just like a git merge?
- c# - TargetInvocationException 在 mscorlib.dll 中发生,但在使用 DbGeometry 时未在用户代码中处理
- mongodb - 如何从 MongoDB 的 ISODate 字段中查询分钟值?
- java - How to intercept a RequestRejectedException in Spring?
- python - 无法安装 Github 下载