use clap::Parser;
use std::path::PathBuf;
use tracing::{error, info};
use tracing_subscriber::EnvFilter;
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
#[arg(short, long, default_value = "tests/interop/interop-matrix.yaml")]
matrix: PathBuf,
#[arg(short, long, default_value = "interop-results")]
output: PathBuf,
#[arg(short, long)]
implementation: Option<String>,
#[arg(short, long)]
category: Option<String>,
#[arg(long)]
html: bool,
#[arg(long)]
json: bool,
#[arg(short, long, default_value = "30")]
timeout: u64,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt()
.with_env_filter(
EnvFilter::from_default_env()
.add_directive("ant_quic=debug".parse()?)
.add_directive("interop_test=info".parse()?),
)
.with_target(false)
.with_thread_ids(true)
.with_file(true)
.with_line_number(true)
.init();
let args = Args::parse();
info!("QUIC Interoperability Test Runner");
info!("=================================");
info!("Matrix file: {:?}", args.matrix);
info!("Output directory: {:?}", args.output);
std::fs::create_dir_all(&args.output)?;
if !args.matrix.exists() {
error!("Matrix file not found: {:?}", args.matrix);
error!("Please ensure the interop-matrix.yaml file exists at the specified path");
return Err("Matrix file not found".into());
}
let matrix_content = std::fs::read_to_string(&args.matrix)?;
info!("Loaded test matrix: {} bytes", matrix_content.len());
let matrix: serde_yaml::Value = serde_yaml::from_str(&matrix_content)?;
let implementations = matrix["implementations"]
.as_mapping()
.ok_or("Invalid matrix format: missing implementations")?;
info!("Found {} implementations to test", implementations.len());
for (impl_name, impl_data) in implementations {
let name = impl_name.as_str().unwrap_or("unknown");
if let Some(ref target) = args.implementation
&& name != target
{
continue;
}
info!("Testing implementation: {}", name);
if let Some(endpoints) = impl_data["endpoints"].as_sequence() {
for endpoint in endpoints {
if let Some(endpoint_str) = endpoint.as_str() {
info!(" Endpoint: {}", endpoint_str);
match test_endpoint(endpoint_str, args.timeout).await {
Ok(duration) => {
info!(" ✓ Connected successfully in {:?}", duration);
}
Err(e) => {
error!(" ✗ Failed to connect: {}", e);
}
}
}
}
}
}
if args.html || args.json {
info!("Generating reports...");
if args.html {
let html_path = args.output.join("report.html");
std::fs::write(&html_path, generate_html_report())?;
info!("HTML report written to: {:?}", html_path);
}
if args.json {
let json_path = args.output.join("report.json");
let json_report = serde_json::json!({
"version": "1.0",
"test_date": chrono::Utc::now().to_rfc3339(),
"summary": "Interoperability test results"
});
std::fs::write(&json_path, serde_json::to_string_pretty(&json_report)?)?;
info!("JSON report written to: {:?}", json_path);
}
}
info!("Interoperability tests completed");
Ok(())
}
async fn test_endpoint(
endpoint_str: &str,
timeout_secs: u64,
) -> Result<std::time::Duration, Box<dyn std::error::Error>> {
use ant_quic::high_level::Endpoint;
use std::sync::Arc;
use std::time::Instant;
let addr = endpoint_str.parse()?;
let start = Instant::now();
let socket = std::net::UdpSocket::bind("0.0.0.0:0")?;
let runtime = ant_quic::high_level::default_runtime()
.ok_or_else(|| std::io::Error::other("No compatible async runtime found"))?;
let endpoint = Endpoint::new(ant_quic::EndpointConfig::default(), None, socket, runtime)?;
#[cfg(feature = "platform-verifier")]
let client_config = ant_quic::ClientConfig::try_with_platform_verifier().unwrap_or_else(|_| {
let roots = rustls::RootCertStore::empty();
let crypto = rustls::ClientConfig::builder()
.with_root_certificates(roots)
.with_no_client_auth();
#[allow(clippy::unwrap_used)]
ant_quic::ClientConfig::new(Arc::new(
ant_quic::crypto::rustls::QuicClientConfig::try_from(crypto).unwrap(),
))
});
#[cfg(not(feature = "platform-verifier"))]
let client_config = {
let roots = rustls::RootCertStore::empty();
let crypto = rustls::ClientConfig::builder()
.with_root_certificates(roots)
.with_no_client_auth();
ant_quic::ClientConfig::new(Arc::new(
ant_quic::crypto::rustls::QuicClientConfig::try_from(crypto).unwrap(),
))
};
let server_name = endpoint_str.split(':').next().unwrap_or("unknown");
let connect_future = endpoint.connect_with(client_config, addr, server_name);
let connection = tokio::time::timeout(std::time::Duration::from_secs(timeout_secs), async {
match connect_future {
Ok(connecting) => connecting.await.map_err(|e| e.into()),
Err(e) => Err(Box::new(e) as Box<dyn std::error::Error>),
}
})
.await??;
let duration = start.elapsed();
connection.close(0u32.into(), b"test complete");
Ok(duration)
}
fn generate_html_report() -> String {
format!(
r#"<!DOCTYPE html>
<html>
<head>
<title>QUIC Interoperability Test Report</title>
<style>
body {{ font-family: Arial, sans-serif; margin: 20px; }}
h1 {{ color: #333; }}
.summary {{ background: #f0f0f0; padding: 10px; margin: 20px 0; }}
</style>
</head>
<body>
<h1>QUIC Interoperability Test Report</h1>
<div class="summary">
<p>Generated: {}</p>
<p>This is a placeholder report. Full implementation coming soon.</p>
</div>
</body>
</html>"#,
chrono::Utc::now()
)
}