#![allow(dead_code)]
use std::io::{BufRead, BufReader, Write};
use std::net::{SocketAddr, TcpListener, TcpStream};
use std::sync::{Arc, Mutex};
use std::thread::{self, JoinHandle};
use std::time::Duration;
pub struct MockServer {
pub addr: SocketAddr,
join: Option<JoinHandle<()>>,
}
impl Drop for MockServer {
fn drop(&mut self) {
if let Some(h) = self.join.take() {
let _ = h.join();
}
}
}
pub fn spawn_mock_server<F>(handler: F) -> MockServer
where
F: FnOnce(BufReader<TcpStream>, TcpStream) + Send + 'static,
{
let listener = TcpListener::bind("127.0.0.1:0").expect("bind ephemeral port");
let addr = listener.local_addr().expect("local_addr");
let join = thread::spawn(move || {
let (stream, _peer) = match listener.accept() {
Ok(s) => s,
Err(_) => return,
};
stream.set_read_timeout(Some(Duration::from_secs(5))).ok();
stream.set_write_timeout(Some(Duration::from_secs(5))).ok();
let writer = stream.try_clone().expect("try_clone");
let reader = BufReader::new(stream);
handler(reader, writer);
});
MockServer {
addr,
join: Some(join),
}
}
pub fn writeln_crlf(w: &mut TcpStream, line: &str) {
let _ = w.write_all(line.as_bytes());
let _ = w.write_all(b"\r\n");
let _ = w.flush();
}
pub fn read_line(r: &mut BufReader<TcpStream>) -> String {
let mut buf = String::new();
if r.read_line(&mut buf).is_err() {
return String::new();
}
buf.trim_end_matches(['\r', '\n']).to_string()
}
pub struct LogCapture {
lines: Arc<Mutex<Vec<String>>>,
_guard: tracing::subscriber::DefaultGuard,
}
impl LogCapture {
pub fn install() -> Self {
use tracing_subscriber::layer::SubscriberExt;
let lines: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(Vec::new()));
let layer = CaptureLayer {
lines: lines.clone(),
};
let subscriber = tracing_subscriber::registry().with(layer);
let guard = tracing::subscriber::set_default(subscriber);
Self {
lines,
_guard: guard,
}
}
pub fn lines(&self) -> Vec<String> {
self.lines.lock().expect("log capture mutex").clone()
}
pub fn contains(&self, needle: &str) -> bool {
self.lines().iter().any(|l| l.contains(needle))
}
}
struct CaptureLayer {
lines: Arc<Mutex<Vec<String>>>,
}
impl<S> tracing_subscriber::Layer<S> for CaptureLayer
where
S: tracing::Subscriber,
{
fn on_event(
&self,
event: &tracing::Event<'_>,
_ctx: tracing_subscriber::layer::Context<'_, S>,
) {
let mut visitor = MsgVisitor::default();
event.record(&mut visitor);
if let Ok(mut g) = self.lines.lock() {
g.push(visitor.msg);
}
}
}
#[derive(Default)]
struct MsgVisitor {
msg: String,
}
impl tracing::field::Visit for MsgVisitor {
fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) {
if field.name() == "message" {
let s = format!("{value:?}");
self.msg = s
.strip_prefix('"')
.and_then(|s| s.strip_suffix('"'))
.map(str::to_string)
.unwrap_or(s);
} else {
self.msg.push_str(&format!(" {}={value:?}", field.name()));
}
}
fn record_str(&mut self, field: &tracing::field::Field, value: &str) {
if field.name() == "message" {
self.msg = value.to_string();
} else {
self.msg.push_str(&format!(" {}={value}", field.name()));
}
}
}