Simplify ReadWrite interface (#530)
Previously, ReadWrite had methods `is_poolable` and `written_bytes`, which were solely for the use of unittests. This replaces `written_bytes` and `TestStream` with a `struct Recorder` that implements `ReadWrite` and allows unittests to access its recorded bytes via an `Arc<Mutex<Vec<u8>>>`. It eliminates `is_poolable`; it's fine to pool a Stream of any kind. The new `Recorder` also has some convenience methods that abstract away boilerplate code from many of our unittests. I got rid of `Stream::from_vec` and `Stream::from_vec_poolable` because they depended on `TestStream`. They've been replaced by `NoopStream` for the pool.rs tests, and `ReadOnlyStream` for constructing `Response`s from `&str` and some test cases.
This commit is contained in:
committed by
GitHub
parent
0cf1f8dbb9
commit
9908c446d6
@@ -1,79 +1,61 @@
|
||||
use crate::test;
|
||||
use crate::test::Recorder;
|
||||
|
||||
use super::super::*;
|
||||
|
||||
#[test]
|
||||
fn content_length_on_str() {
|
||||
test::set_handler("/content_length_on_str", |_unit| {
|
||||
test::make_response(200, "OK", vec![], vec![])
|
||||
});
|
||||
let resp = post("test://host/content_length_on_str")
|
||||
let recorder = Recorder::register("/content_length_on_str");
|
||||
post("test://host/content_length_on_str")
|
||||
.send_string("Hello World!!!")
|
||||
.unwrap();
|
||||
let vec = resp.into_written_bytes();
|
||||
let s = String::from_utf8_lossy(&vec);
|
||||
assert!(s.contains("\r\nContent-Length: 14\r\n"));
|
||||
assert!(recorder.contains("\r\nContent-Length: 14\r\n"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn user_set_content_length_on_str() {
|
||||
test::set_handler("/user_set_content_length_on_str", |_unit| {
|
||||
test::make_response(200, "OK", vec![], vec![])
|
||||
});
|
||||
let resp = post("test://host/user_set_content_length_on_str")
|
||||
let recorder = Recorder::register("/user_set_content_length_on_str");
|
||||
post("test://host/user_set_content_length_on_str")
|
||||
.set("Content-Length", "12345")
|
||||
.send_string("Hello World!!!")
|
||||
.unwrap();
|
||||
let vec = resp.into_written_bytes();
|
||||
let s = String::from_utf8_lossy(&vec);
|
||||
assert!(s.contains("\r\nContent-Length: 12345\r\n"));
|
||||
assert!(recorder.contains("\r\nContent-Length: 12345\r\n"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "json")]
|
||||
fn content_length_on_json() {
|
||||
test::set_handler("/content_length_on_json", |_unit| {
|
||||
test::make_response(200, "OK", vec![], vec![])
|
||||
});
|
||||
let recorder = Recorder::register("/content_length_on_json");
|
||||
let mut json = serde_json::Map::new();
|
||||
json.insert(
|
||||
"Hello".to_string(),
|
||||
serde_json::Value::String("World!!!".to_string()),
|
||||
);
|
||||
let resp = post("test://host/content_length_on_json")
|
||||
post("test://host/content_length_on_json")
|
||||
.send_json(serde_json::Value::Object(json))
|
||||
.unwrap();
|
||||
let vec = resp.into_written_bytes();
|
||||
let s = String::from_utf8_lossy(&vec);
|
||||
assert!(s.contains("\r\nContent-Length: 20\r\n"));
|
||||
assert!(recorder.contains("\r\nContent-Length: 20\r\n"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn content_length_and_chunked() {
|
||||
test::set_handler("/content_length_and_chunked", |_unit| {
|
||||
test::make_response(200, "OK", vec![], vec![])
|
||||
});
|
||||
let resp = post("test://host/content_length_and_chunked")
|
||||
let recorder = Recorder::register("/content_length_and_chunked");
|
||||
post("test://host/content_length_and_chunked")
|
||||
.set("Transfer-Encoding", "chunked")
|
||||
.send_string("Hello World!!!")
|
||||
.unwrap();
|
||||
let vec = resp.into_written_bytes();
|
||||
let s = String::from_utf8_lossy(&vec);
|
||||
assert!(s.contains("Transfer-Encoding: chunked\r\n"));
|
||||
assert!(!s.contains("\r\nContent-Length:\r\n"));
|
||||
assert!(recorder.contains("Transfer-Encoding: chunked\r\n"));
|
||||
assert!(!recorder.contains("\r\nContent-Length:\r\n"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "charset")]
|
||||
fn str_with_encoding() {
|
||||
test::set_handler("/str_with_encoding", |_unit| {
|
||||
test::make_response(200, "OK", vec![], vec![])
|
||||
});
|
||||
let resp = post("test://host/str_with_encoding")
|
||||
let recorder = Recorder::register("/str_with_encoding");
|
||||
post("test://host/str_with_encoding")
|
||||
.set("Content-Type", "text/plain; charset=iso-8859-1")
|
||||
.send_string("Hällo Wörld!!!")
|
||||
.unwrap();
|
||||
let vec = resp.into_written_bytes();
|
||||
let vec = recorder.to_vec();
|
||||
assert_eq!(
|
||||
&vec[vec.len() - 14..],
|
||||
//H ä l l o _ W ö r l d ! ! !
|
||||
@@ -84,38 +66,30 @@ fn str_with_encoding() {
|
||||
#[test]
|
||||
#[cfg(feature = "json")]
|
||||
fn content_type_on_json() {
|
||||
test::set_handler("/content_type_on_json", |_unit| {
|
||||
test::make_response(200, "OK", vec![], vec![])
|
||||
});
|
||||
let recorder = Recorder::register("/content_type_on_json");
|
||||
let mut json = serde_json::Map::new();
|
||||
json.insert(
|
||||
"Hello".to_string(),
|
||||
serde_json::Value::String("World!!!".to_string()),
|
||||
);
|
||||
let resp = post("test://host/content_type_on_json")
|
||||
post("test://host/content_type_on_json")
|
||||
.send_json(serde_json::Value::Object(json))
|
||||
.unwrap();
|
||||
let vec = resp.into_written_bytes();
|
||||
let s = String::from_utf8_lossy(&vec);
|
||||
assert!(s.contains("\r\nContent-Type: application/json\r\n"));
|
||||
assert!(recorder.contains("\r\nContent-Type: application/json\r\n"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "json")]
|
||||
fn content_type_not_overriden_on_json() {
|
||||
test::set_handler("/content_type_not_overriden_on_json", |_unit| {
|
||||
test::make_response(200, "OK", vec![], vec![])
|
||||
});
|
||||
let recorder = Recorder::register("/content_type_not_overriden_on_json");
|
||||
let mut json = serde_json::Map::new();
|
||||
json.insert(
|
||||
"Hello".to_string(),
|
||||
serde_json::Value::String("World!!!".to_string()),
|
||||
);
|
||||
let resp = post("test://host/content_type_not_overriden_on_json")
|
||||
post("test://host/content_type_not_overriden_on_json")
|
||||
.set("content-type", "text/plain")
|
||||
.send_json(serde_json::Value::Object(json))
|
||||
.unwrap();
|
||||
let vec = resp.into_written_bytes();
|
||||
let s = String::from_utf8_lossy(&vec);
|
||||
assert!(s.contains("\r\ncontent-type: text/plain\r\n"));
|
||||
assert!(recorder.contains("\r\ncontent-type: text/plain\r\n"));
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
use crate::error::Error;
|
||||
use crate::stream::Stream;
|
||||
use crate::stream::{ReadOnlyStream, Stream};
|
||||
use crate::unit::Unit;
|
||||
use crate::ReadWrite;
|
||||
use once_cell::sync::Lazy;
|
||||
use std::collections::HashMap;
|
||||
use std::io::Write;
|
||||
use std::fmt;
|
||||
use std::io::{self, Cursor, Read, Write};
|
||||
use std::net::TcpStream;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
mod agent_test;
|
||||
@@ -48,7 +51,7 @@ pub(crate) fn make_response(
|
||||
}
|
||||
write!(&mut buf, "\r\n").ok();
|
||||
buf.append(&mut body);
|
||||
Ok(Stream::from_vec(buf))
|
||||
Ok(Stream::new(ReadOnlyStream::new(buf)))
|
||||
}
|
||||
|
||||
pub(crate) fn resolve_handler(unit: &Unit) -> Result<Stream, Error> {
|
||||
@@ -66,3 +69,87 @@ pub(crate) fn resolve_handler(unit: &Unit) -> Result<Stream, Error> {
|
||||
drop(handlers);
|
||||
handler(unit)
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub(crate) struct Recorder {
|
||||
contents: Arc<Mutex<Vec<u8>>>,
|
||||
}
|
||||
|
||||
impl Recorder {
|
||||
fn register(path: &str) -> Self {
|
||||
let recorder = Recorder::default();
|
||||
let recorder2 = recorder.clone();
|
||||
set_handler(path, move |_unit| Ok(recorder.stream()));
|
||||
recorder2
|
||||
}
|
||||
|
||||
#[cfg(feature = "charset")]
|
||||
fn to_vec(self) -> Vec<u8> {
|
||||
self.contents.lock().unwrap().clone()
|
||||
}
|
||||
|
||||
fn contains(&self, s: &str) -> bool {
|
||||
std::str::from_utf8(&self.contents.lock().unwrap())
|
||||
.unwrap()
|
||||
.contains(s)
|
||||
}
|
||||
|
||||
fn stream(&self) -> Stream {
|
||||
let cursor = Cursor::new(b"HTTP/1.1 200 OK\r\n\r\n");
|
||||
Stream::new(TestStream::new(cursor, self.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Write for Recorder {
|
||||
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||
self.contents.lock().unwrap().write(buf)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> std::io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct TestStream(
|
||||
Box<dyn Read + Send + Sync>,
|
||||
Box<dyn Write + Send + Sync>,
|
||||
bool,
|
||||
);
|
||||
|
||||
impl TestStream {
|
||||
#[cfg(test)]
|
||||
pub(crate) fn new(
|
||||
response: impl Read + Send + Sync + 'static,
|
||||
recorder: impl Write + Send + Sync + 'static,
|
||||
) -> Self {
|
||||
Self(Box::new(response), Box::new(recorder), false)
|
||||
}
|
||||
}
|
||||
|
||||
impl ReadWrite for TestStream {
|
||||
fn socket(&self) -> Option<&TcpStream> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl Read for TestStream {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
self.0.read(buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl Write for TestStream {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
self.1.write(buf)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for TestStream {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_tuple("TestStream").finish()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,58 +1,37 @@
|
||||
use crate::test;
|
||||
|
||||
use super::super::*;
|
||||
use super::Recorder;
|
||||
use crate::get;
|
||||
|
||||
#[test]
|
||||
fn no_query_string() {
|
||||
test::set_handler("/no_query_string", |_unit| {
|
||||
test::make_response(200, "OK", vec![], vec![])
|
||||
});
|
||||
let resp = get("test://host/no_query_string").call().unwrap();
|
||||
let vec = resp.into_written_bytes();
|
||||
let s = String::from_utf8_lossy(&vec);
|
||||
assert!(s.contains("GET /no_query_string HTTP/1.1"))
|
||||
let recorder = Recorder::register("/no_query_string");
|
||||
get("test://host/no_query_string").call().unwrap();
|
||||
assert!(recorder.contains("GET /no_query_string HTTP/1.1"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn escaped_query_string() {
|
||||
test::set_handler("/escaped_query_string", |_unit| {
|
||||
test::make_response(200, "OK", vec![], vec![])
|
||||
});
|
||||
let resp = get("test://host/escaped_query_string")
|
||||
let recorder = Recorder::register("/escaped_query_string");
|
||||
get("test://host/escaped_query_string")
|
||||
.query("foo", "bar")
|
||||
.query("baz", "yo lo")
|
||||
.call()
|
||||
.unwrap();
|
||||
let vec = resp.into_written_bytes();
|
||||
let s = String::from_utf8_lossy(&vec);
|
||||
assert!(
|
||||
s.contains("GET /escaped_query_string?foo=bar&baz=yo+lo HTTP/1.1"),
|
||||
"req: {}",
|
||||
s
|
||||
);
|
||||
assert!(recorder.contains("GET /escaped_query_string?foo=bar&baz=yo+lo HTTP/1.1"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn query_in_path() {
|
||||
test::set_handler("/query_in_path", |_unit| {
|
||||
test::make_response(200, "OK", vec![], vec![])
|
||||
});
|
||||
let resp = get("test://host/query_in_path?foo=bar").call().unwrap();
|
||||
let vec = resp.into_written_bytes();
|
||||
let s = String::from_utf8_lossy(&vec);
|
||||
assert!(s.contains("GET /query_in_path?foo=bar HTTP/1.1"))
|
||||
let recorder = Recorder::register("/query_in_path");
|
||||
get("test://host/query_in_path?foo=bar").call().unwrap();
|
||||
assert!(recorder.contains("GET /query_in_path?foo=bar HTTP/1.1"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn query_in_path_and_req() {
|
||||
test::set_handler("/query_in_path_and_req", |_unit| {
|
||||
test::make_response(200, "OK", vec![], vec![])
|
||||
});
|
||||
let resp = get("test://host/query_in_path_and_req?foo=bar")
|
||||
let recorder = Recorder::register("/query_in_path_and_req");
|
||||
get("test://host/query_in_path_and_req?foo=bar")
|
||||
.query("baz", "1 2 3")
|
||||
.call()
|
||||
.unwrap();
|
||||
let vec = resp.into_written_bytes();
|
||||
let s = String::from_utf8_lossy(&vec);
|
||||
assert!(s.contains("GET /query_in_path_and_req?foo=bar&baz=1+2+3 HTTP/1.1"))
|
||||
assert!(recorder.contains("GET /query_in_path_and_req?foo=bar&baz=1+2+3 HTTP/1.1"));
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::test;
|
||||
use std::io::Read;
|
||||
|
||||
use super::super::*;
|
||||
use super::{super::*, Recorder};
|
||||
|
||||
#[test]
|
||||
fn header_passing() {
|
||||
@@ -116,13 +116,9 @@ fn body_as_reader() {
|
||||
|
||||
#[test]
|
||||
fn escape_path() {
|
||||
test::set_handler("/escape_path%20here", |_unit| {
|
||||
test::make_response(200, "OK", vec![], vec![])
|
||||
});
|
||||
let resp = get("test://host/escape_path here").call().unwrap();
|
||||
let vec = resp.into_written_bytes();
|
||||
let s = String::from_utf8_lossy(&vec);
|
||||
assert!(s.contains("GET /escape_path%20here HTTP/1.1"))
|
||||
let recorder = Recorder::register("/escape_path%20here");
|
||||
get("test://host/escape_path here").call().unwrap();
|
||||
assert!(recorder.contains("GET /escape_path%20here HTTP/1.1"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -194,22 +190,14 @@ pub fn header_with_spaces_before_value() {
|
||||
|
||||
#[test]
|
||||
pub fn host_no_port() {
|
||||
test::set_handler("/host_no_port", |_| {
|
||||
test::make_response(200, "OK", vec![], vec![])
|
||||
});
|
||||
let resp = get("test://myhost/host_no_port").call().unwrap();
|
||||
let vec = resp.into_written_bytes();
|
||||
let s = String::from_utf8_lossy(&vec);
|
||||
assert!(s.contains("\r\nHost: myhost\r\n"));
|
||||
let recorder = Recorder::register("/host_no_port");
|
||||
get("test://myhost/host_no_port").call().unwrap();
|
||||
assert!(recorder.contains("\r\nHost: myhost\r\n"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn host_with_port() {
|
||||
test::set_handler("/host_with_port", |_| {
|
||||
test::make_response(200, "OK", vec![], vec![])
|
||||
});
|
||||
let resp = get("test://myhost:234/host_with_port").call().unwrap();
|
||||
let vec = resp.into_written_bytes();
|
||||
let s = String::from_utf8_lossy(&vec);
|
||||
assert!(s.contains("\r\nHost: myhost:234\r\n"));
|
||||
let recorder = Recorder::register("/host_with_port");
|
||||
get("test://myhost:234/host_with_port").call().unwrap();
|
||||
assert!(recorder.contains("\r\nHost: myhost:234\r\n"));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user