use std::error::Error;
use std::net::{TcpListener, TcpStream};
use std::sync::{Arc, Mutex};
use beamr::native::native_process::NativeOutcome;
use beamr::process::ExitReason;
use tracing_subscriber::prelude::*;
use super::ConnectionProcess;
use crate::ServerError;
use crate::server::connection::supervisor::runtime::ConnectionRuntime;
use crate::server::connection::worker_front_door::WorkerFrontDoorServices;
pub(in crate::server::connection) fn logged_while<T>(
run: impl FnOnce() -> T,
) -> (T, Vec<(tracing::Level, String)>) {
let lines = Arc::new(Mutex::new(Vec::new()));
let subscriber = tracing_subscriber::registry().with(Lines(Arc::clone(&lines)));
let value = tracing::subscriber::with_default(subscriber, run);
let logged = lines.lock().map(|lines| lines.clone()).unwrap_or_default();
(value, logged)
}
struct Lines(Arc<Mutex<Vec<(tracing::Level, String)>>>);
struct Message(String);
impl tracing::field::Visit for Message {
fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) {
if field.name() == "message" {
self.0 = format!("{value:?}");
}
}
}
impl<S: tracing::Subscriber> tracing_subscriber::Layer<S> for Lines {
fn on_event(&self, event: &tracing::Event<'_>, _: tracing_subscriber::layer::Context<'_, S>) {
let mut message = Message(String::new());
event.record(&mut message);
if let Ok(mut lines) = self.0.lock() {
lines.push((*event.metadata().level(), message.0));
}
}
}
pub(in crate::server::connection) fn check_stop_and_failure(
stopped: (NativeOutcome, Vec<(tracing::Level, String)>),
failed: (NativeOutcome, Vec<(tracing::Level, String)>),
) {
let (stopped, stopped_lines) = stopped;
let (failed, failed_lines) = failed;
assert!(
stopped_lines
.iter()
.all(|(level, _)| *level != tracing::Level::ERROR),
"a connection the stop closed logs no ERROR: {stopped_lines:?}"
);
assert!(
stopped_lines
.iter()
.any(|(level, message)| *level == tracing::Level::DEBUG
&& message.contains("closed by the server's stop")),
"the stop's close is logged at DEBUG: {stopped_lines:?}"
);
assert!(
failed_lines
.iter()
.any(|(level, message)| *level == tracing::Level::ERROR
&& message.contains("connection readiness contract failed")),
"a real readiness failure still logs ERROR: {failed_lines:?}"
);
assert!(matches!(stopped, NativeOutcome::Stop(ExitReason::Error)));
assert!(matches!(failed, NativeOutcome::Stop(ExitReason::Error)));
}
fn tcp_process() -> Result<(ConnectionProcess, TcpStream), Box<dyn Error>> {
let listener = TcpListener::bind("127.0.0.1:0")?;
let client = TcpStream::connect(listener.local_addr()?)?;
let (server, _) = listener.accept()?;
server.set_nonblocking(true)?;
let runtime = Arc::new(ConnectionRuntime::for_tests(Arc::new(
WorkerFrontDoorServices::new(),
)));
let holder = Arc::new(Mutex::new(Some(server)));
Ok((
ConnectionProcess::from_holder(runtime, None, &holder, None),
client,
))
}
#[test]
fn a_tcp_connection_the_stop_closes_logs_no_error_and_is_torn_down_as_a_failed_one()
-> Result<(), Box<dyn Error>> {
let (mut stopped, _stopped_client) = tcp_process()?;
let (mut failed, _failed_client) = tcp_process()?;
check_stop_and_failure(
logged_while(|| stopped.fail_slice(7, &ServerError::ReadinessClosedByStop { pid: 7 })),
logged_while(|| {
failed.fail_slice(
8,
&ServerError::ListenerAccept {
message:
"failed to register connection readiness: readiness poll thread has failed"
.to_owned(),
},
)
}),
);
Ok(())
}