#![cfg(all(target_os = "linux", feature = "qlog", feature = "noq"))]
#[path = "support.rs"]
mod support;
use std::net::UdpSocket;
use moq_uring::{Config, Error, Worker, quic, udp};
fn worker() -> Option<Worker> {
match Worker::new(Config::default()) {
Ok(worker) => Some(worker),
Err(Error::Unsupported(reason)) => {
eprintln!("skipping io_uring qlog test: {reason}");
None
}
Err(err) => panic!("worker setup failed: {err}"),
}
}
const ALPN: &str = "moq-uring-test";
fn records(path: &std::path::Path) -> Vec<serde_json::Value> {
let raw = std::fs::read_to_string(path).expect("read trace");
raw.split('\n')
.map(|line| line.trim_matches(|c: char| c == '\u{1e}' || c.is_whitespace()))
.filter(|line| !line.is_empty())
.map(|line| serde_json::from_str(line).unwrap_or_else(|err| panic!("{}: {err}: {line}", path.display())))
.collect()
}
#[test]
fn a_connection_writes_a_trace() {
let Some(mut worker) = worker() else { return };
let handle = worker.handle();
let certs = support::certs().expect("certificates");
let dir = tempfile::tempdir().expect("temp dir");
let sink = quic::qlog::Sink::directory(dir.path()).expect("qlog sink");
let mut server = quic::server::Config::new(quic::Identity::open(&certs.cert, &certs.key).expect("identity"));
server.alpn = vec![ALPN.to_string()];
server.transport.qlog = Some(sink.clone());
let server_sock = handle
.udp(UdpSocket::bind("127.0.0.1:0").expect("bind"), udp::Config::default())
.expect("server socket");
let server_addr = server_sock.local_addr().expect("server addr");
let client_sock = handle
.udp(UdpSocket::bind("127.0.0.1:0").expect("bind"), udp::Config::default())
.expect("client socket");
let mut dial = quic::client::Config::new(server_addr, "localhost");
dial.alpn = vec![ALPN.to_string()];
dial.verify = false;
dial.transport.qlog = Some(sink.clone());
handle.spawn(async move {
let conn = quic::server::accept(server_sock, &server).await.expect("quic accept");
std::future::pending::<()>().await;
drop(conn);
});
worker
.block_on(async move {
let conn = quic::client::connect(client_sock, &dial).await.expect("quic connect");
assert_eq!(
web_transport_trait::poll::Session::protocol(&conn),
Some(ALPN),
"negotiated ALPN"
);
})
.expect("worker");
drop(worker);
drop(sink);
let mut traces: Vec<_> = std::fs::read_dir(dir.path())
.expect("read dir")
.map(|entry| entry.expect("dir entry").path())
.filter(|path| path.extension().is_some_and(|ext| ext == "qlog"))
.collect();
traces.sort();
assert!(
!traces.is_empty(),
"no qlog traces were written to {}",
dir.path().display()
);
let mut sides = std::collections::BTreeSet::new();
for trace in &traces {
let name = trace.file_name().expect("file name").to_string_lossy().into_owned();
let events = records(trace);
assert!(!events.is_empty(), "{name} holds no records");
assert!(
events[0].get("trace").is_some(),
"{name} does not open with a qlog header: {}",
events[0]
);
sides.insert(match name.contains("-client.qlog") {
true => "client",
false => "server",
});
}
assert_eq!(
sides,
["client", "server"].into_iter().collect(),
"both ends should have written a trace: {traces:?}"
);
}
#[test]
fn a_missing_directory_is_refused() {
let dir = tempfile::tempdir().expect("temp dir");
let err = quic::qlog::Sink::directory(dir.path().join("nope")).expect_err("missing directory");
assert!(matches!(err, quic::Error::Qlog(_)), "{err}");
}