Use a testserver in tests. (#194)

This is a step towards allowing our tests to run without network access,
which will make them more resilient and faster.

Replace the URL in one instance of an HTTPS test that didn't need HTTPS.
This commit is contained in:
Jacob Hoffman-Andrews
2020-10-19 00:27:40 -07:00
committed by GitHub
parent 6e997909bf
commit 75bc803cf1
5 changed files with 62 additions and 9 deletions

View File

@@ -1,10 +1,10 @@
use crate::error::Error;
use crate::stream::Stream;
use crate::unit::Unit;
use crate::{error::Error, Agent};
use crate::{stream::Stream, AgentBuilder};
use once_cell::sync::Lazy;
use std::collections::HashMap;
use std::io::{Cursor, Write};
use std::sync::{Arc, Mutex};
use std::{collections::HashMap, net::ToSocketAddrs};
mod agent_test;
mod auth;
@@ -14,9 +14,44 @@ mod query_string;
mod range;
mod redirect;
mod simple;
mod testserver;
pub(crate) mod testserver;
mod timeout;
// An agent to be installed by default for tests and doctests, such
// that all hostnames resolve to a TestServer on localhost.
pub(crate) fn test_agent() -> Agent {
use std::io;
use std::net::{SocketAddr, TcpStream};
let testserver = testserver::TestServer::new(|mut stream: TcpStream| -> io::Result<()> {
testserver::read_headers(&stream);
stream.write_all(b"HTTP/1.1 200 OK\r\n")?;
stream.write_all(b"Transfer-Encoding: chunked\r\n")?;
stream.write_all(b"Content-Type: text/html; charset=ISO-8859-1\r\n")?;
stream.write_all(b"\r\n")?;
stream.write_all(b"7\r\n")?;
stream.write_all(b"success\r\n")?;
stream.write_all(b"0\r\n")?;
stream.write_all(b"\r\n")?;
Ok(())
});
// Slightly tricky thing here: we want to make sure the TestServer lives
// as long as the agent. This is accomplished by `move`ing it into the
// closure, which becomes owned by the agent.
AgentBuilder::new()
.resolver(move |h: &str| -> io::Result<Vec<SocketAddr>> {
// Don't override resolution for HTTPS requests yet, since we
// don't have a setup for an HTTPS testserver. Also, skip localhost
// resolutions since those may come from a unittest that set up
// its own, specific testserver.
if h.ends_with(":443") || h.starts_with("localhost:") {
return Ok(h.to_socket_addrs()?.collect::<Vec<_>>());
}
let addr: SocketAddr = format!("127.0.0.1:{}", testserver.port).parse().unwrap();
Ok(vec![addr])
})
.build()
}
type RequestHandler = dyn Fn(&Unit) -> Result<Stream, Error> + Send + 'static;
pub(crate) static TEST_HANDLERS: Lazy<Arc<Mutex<HashMap<String, Box<RequestHandler>>>>> =

View File

@@ -85,6 +85,9 @@ fn redirect_get() {
#[test]
fn redirect_host() {
// Set up a redirect to a host that doesn't exist; it should fail.
// TODO: This actually relies on the network for the DNS lookup
// of example.invalid. We can probably do better by, e.g.
// overriding the resolver.
let srv = TestServer::new(|mut stream: TcpStream| -> io::Result<()> {
testserver::read_headers(&stream);
write!(stream, "HTTP/1.1 302 Found\r\n")?;
@@ -93,8 +96,13 @@ fn redirect_host() {
Ok(())
});
let url = format!("http://localhost:{}/", srv.port);
let resp = crate::get(&url).call();
assert!(matches!(resp.err(), Some(Error::DnsFailed(_))));
let resp = crate::Agent::default().get(&url).call();
let err = resp.err();
assert!(
matches!(err, Some(Error::DnsFailed(_))),
"expected DnsFailed, got: {:?}",
err
);
}
#[test]

View File

@@ -34,7 +34,10 @@ pub fn read_headers(stream: &TcpStream) -> TestHeaders {
let mut results = vec![];
for line in BufReader::new(stream).lines() {
match line {
Err(e) => panic!(e),
Err(e) => {
eprintln!("testserver: in read_headers: {}", e);
break;
}
Ok(line) if line == "" => break,
Ok(line) => results.push(line),
};
@@ -50,6 +53,10 @@ impl TestServer {
let done_clone = done.clone();
thread::spawn(move || {
for stream in listener.incoming() {
if let Err(e) = stream {
eprintln!("testserver: handling just-accepted stream: {}", e);
break;
}
thread::spawn(move || handler(stream.unwrap()));
if done.load(Ordering::Relaxed) {
break;