mod categories;
mod helpers;
#[path = "../test_infra/mod.rs"]
mod test_infra;
use helpers::client::E2eClient;
use helpers::config::ConfigManager;
#[allow(unused_imports)]
use helpers::constants::{
category_enabled, elastic_url, external_proxy, meili1_url, meili2_url, opensearch_url,
proxy_url, qdrant_url, Suite,
};
use helpers::metrics::snapshot_and_reset;
use helpers::proxy::ProxyProcess;
use helpers::report::TestReport;
use std::path::PathBuf;
use std::time::Duration;
#[test]
#[ignore = "E2E: requires Docker services + proxy"]
fn e2e_proxy_suite() {
let suite = Suite::from_env();
let project_root = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let client = E2eClient::new(proxy_url());
let mut config = ConfigManager::new(&project_root);
let mut report = TestReport::new(&suite.to_string());
let proxy_external = client.is_up();
let ext_proxy = proxy_external || external_proxy();
let mut _proxy_process: Option<ProxyProcess> = None;
if !ext_proxy {
{
use std::io::Write;
let mut stderr = std::io::stderr().lock();
let _ = writeln!(
stderr,
"\x1b[32m[INFO]\x1b[0m Proxy not running, starting it..."
);
let _ = stderr.flush();
}
config.inject_suite_config(suite);
let log_path = std::env::var("E2E_OUTPUT_DIR")
.ok()
.map(|d| PathBuf::from(d).join("proxy_logs.txt"));
let mut proxy = if let Some(path) = log_path {
ProxyProcess::start_with_log("127.0.0.1:8080", path)
} else {
ProxyProcess::start("127.0.0.1:8080")
};
if let Err(e) = proxy.wait_healthy(Duration::from_secs(10)) {
panic!("Proxy failed to start: {e}");
}
_proxy_process = Some(proxy);
}
assert!(client.is_up(), "Proxy not responding at {}", proxy_url());
#[cfg(target_os = "linux")]
let (_proc_monitor_handle, mut _full_profiler) = {
let do_profile = std::env::var("E2E_PROFILE").unwrap_or_default() == "1";
if do_profile {
if let Some(ref proxy) = _proxy_process {
if let Some(pid) = proxy.pid() {
{
use std::io::Write;
let mut stderr = std::io::stderr().lock();
let _ = writeln!(
stderr,
"\x1b[36m[PROFILE]\x1b[0m Starting process monitor (PID: {pid})..."
);
let _ = stderr.flush();
}
let lightweight =
Some(test_infra::proc_monitor::ProcMonitor::spawn_background(
pid,
std::time::Duration::from_secs(2),
));
let output_dir = std::env::var("E2E_OUTPUT_DIR").ok().map(PathBuf::from);
let conproxy_bin = helpers::proxy::conproxy_bin_path();
let full = spawn_full_profiler(pid, &conproxy_bin, output_dir.as_deref());
(lightweight, full)
} else {
(None, None)
}
} else {
(None, None)
}
} else {
(None, None)
}
};
#[cfg(not(target_os = "linux"))]
let mut _full_profiler: Option<std::process::Child> = None;
{
use std::io::Write;
let mut stderr = std::io::stderr().lock();
let _ = writeln!(stderr);
let _ = writeln!(stderr, "\x1b[1mE2E Proxy Tests (suite: {suite})\x1b[0m");
let _ = writeln!(stderr, "============================================");
let _ = stderr.flush();
}
categories::compression::run(&mut report);
categories::socket_tuning::run(&client, &mut report);
categories::health::run(&client, suite, &mut report);
categories::cache::run(&client, suite, &mut report);
categories::query::run(&client, suite, &mut report);
categories::cache::run_clear_and_warmup(&client, suite, &mut report);
categories::query::run_suite_specific(&client, suite, &mut report);
let s = snapshot_and_reset(&client, "health+cache+query", &mut report);
assert_section!(report, s, "health+cache+query", min_hits: 1, max_errors: 0);
categories::operational::run(&client, suite, &mut report);
let s = snapshot_and_reset(&client, "operational", &mut report);
assert_section!(report, s, "operational", max_errors: 0);
categories::metrics_cat::run(&client, suite, &mut report);
categories::cache::run_correctness(&client, suite, &mut report);
let s = snapshot_and_reset(&client, "metrics", &mut report);
assert_section!(report, s, "metrics", min_hits: 1, max_errors: 0);
categories::cli::run(suite, &mut report);
let s = snapshot_and_reset(&client, "cli", &mut report);
assert_section!(report, s, "cli", min_hits: 1, max_errors: 0);
categories::content::run(&client, suite, &mut report);
let s = snapshot_and_reset(&client, "content", &mut report);
assert_section!(report, s, "content", max_errors: 0);
categories::relevance::run(&client, suite, &mut report);
let s = snapshot_and_reset(&client, "relevance", &mut report);
assert_section!(report, s, "relevance", min_hits: 3, min_hit_rate: 0.30, max_errors: 0);
categories::warmup::run(&client, suite, &mut report);
let s = snapshot_and_reset(&client, "warmup", &mut report);
assert_section!(report, s, "warmup", min_hits: 3, max_errors: 0);
categories::resilience::run(&client, suite, &mut report);
categories::cache::run_eviction(&client, suite, &mut report);
let _s = snapshot_and_reset(&client, "resilience", &mut report);
categories::reload::run(&client, suite, &config, &mut report);
let _s = snapshot_and_reset(&client, "reload", &mut report);
categories::cascade::run(&client, suite, &mut report);
let s = snapshot_and_reset(&client, "cascade", &mut report);
assert_section!(report, s, "cascade", max_errors: 0);
categories::efficiency::run(&client, &mut report);
let s = snapshot_and_reset(&client, "efficiency", &mut report);
assert_section!(report, s, "efficiency", min_hits: 3, min_hit_rate: 0.80, max_errors: 0);
if !ext_proxy {
if let Some(ref mut proxy) = _proxy_process {
proxy.stop();
}
categories::advanced::run(suite, &mut report);
if _proxy_process.is_some() {
config.inject_suite_config(suite);
let mut proxy = ProxyProcess::start("127.0.0.1:8080");
let _ = proxy.wait_healthy(Duration::from_secs(10));
_proxy_process = Some(proxy);
}
} else {
eprintln!("[SKIP] advanced: external-proxy mode (k8s)");
report.skip_category("advanced");
}
categories::cache_observability::run(&client, suite, &mut report);
let _s = snapshot_and_reset(&client, "cache_observability", &mut report);
categories::context_isolation::run(&client, suite, &mut report);
let _s = snapshot_and_reset(&client, "context_isolation", &mut report);
if !ext_proxy {
if let Some(ref mut proxy) = _proxy_process {
proxy.stop();
}
categories::context_rooted::run(&mut report);
if _proxy_process.is_some() {
config.inject_suite_config(suite);
let mut proxy = ProxyProcess::start("127.0.0.1:8080");
let _ = proxy.wait_healthy(Duration::from_secs(10));
_proxy_process = Some(proxy);
}
} else {
eprintln!("[SKIP] context_rooted: external-proxy mode (k8s)");
report.skip_category("context_rooted");
}
categories::grpc_parity::run(&mut report);
let _s = snapshot_and_reset(&client, "grpc_parity", &mut report);
categories::sdk_parity::run(&mut report);
let _s = snapshot_and_reset(&client, "sdk_parity", &mut report);
if !ext_proxy {
if let Some(ref mut proxy) = _proxy_process {
proxy.stop();
}
categories::agent_mgmt::run(&mut report);
categories::error_handling::run(&mut report);
categories::circuit_breaker::run(&mut report);
categories::coalescing::run(&mut report);
categories::federated_search::run(&mut report);
categories::security::run(&mut report);
if _proxy_process.is_some() {
config.inject_suite_config(suite);
let mut proxy = ProxyProcess::start("127.0.0.1:8080");
let _ = proxy.wait_healthy(Duration::from_secs(10));
_proxy_process = Some(proxy);
}
} else {
for cat in &[
"agent_mgmt",
"error_handling",
"circuit_breaker",
"coalescing",
"federated_search",
"security",
] {
eprintln!("[SKIP] {cat}: external-proxy mode (k8s)");
report.skip_category(cat);
}
}
if client.is_up() {
measure_cache_hit_latency(&client);
}
if let Some(mut proxy) = _proxy_process.take() {
proxy.stop();
}
if let Some(ref mut child) = _full_profiler {
eprintln!("\x1b[36m[PROFILE]\x1b[0m Waiting for full profiler to finish...");
let _ = child.wait();
}
#[cfg(target_os = "linux")]
let resource_data: Option<serde_json::Value> = _proc_monitor_handle.map(|h| {
eprintln!("\x1b[36m[PROFILE]\x1b[0m Stopping process monitor...");
let monitor = h.stop();
let data = monitor.to_json();
eprintln!(
"\x1b[36m[PROFILE]\x1b[0m Collected {} snapshots",
data["snapshots"].as_array().map(|a| a.len()).unwrap_or(0)
);
data
});
#[cfg(not(target_os = "linux"))]
let resource_data: Option<serde_json::Value> = None;
report.print_summary();
if let Ok(dir) = std::env::var("E2E_OUTPUT_DIR") {
let output_dir = PathBuf::from(&dir);
let profile_data: Option<serde_json::Value> =
std::fs::read_to_string(output_dir.join("profile_results.json"))
.ok()
.and_then(|s| serde_json::from_str(&s).ok());
report.write_json(&output_dir.join("results.json"));
report.write_html(
&output_dir.join("results.html"),
resource_data.as_ref(),
profile_data.as_ref(),
);
write_endpoint_snapshots(&client, &output_dir);
if let Some(ref data) = resource_data {
let resource_path = output_dir.join("resource_profile.json");
if let Ok(json_str) = serde_json::to_string_pretty(data) {
let _ = std::fs::write(&resource_path, json_str);
eprintln!("Resource profile written to {}", resource_path.display());
}
}
}
report.assert_all_passed();
}
#[test]
#[ignore = "SMOKE: requires running proxy (no Docker)"]
fn e2e_smoke_test() {
let client = E2eClient::new(proxy_url());
assert!(
client.is_up(),
"Proxy not responding at {}. Start it first.",
proxy_url()
);
let mut report = TestReport::new("smoke");
categories::smoke::run(&client, &mut report);
report.print_summary();
if let Ok(dir) = std::env::var("E2E_OUTPUT_DIR") {
let output_dir = PathBuf::from(&dir);
report.write_json(&output_dir.join("smoke_results.json"));
report.write_html(&output_dir.join("smoke_results.html"), None, None);
}
report.assert_all_passed();
}
fn measure_cache_hit_latency(client: &E2eClient) {
eprintln!();
eprintln!("\x1b[36mPerformance Check:\x1b[0m");
let _ = client.query("latency test");
let iterations = 10;
let start = std::time::Instant::now();
for _ in 0..iterations {
let _ = client.query("latency test");
}
let elapsed = start.elapsed();
let avg_ms = elapsed.as_secs_f64() * 1000.0 / iterations as f64;
eprintln!(" Cache hit avg latency: {avg_ms:.2}ms (over {iterations} requests)");
}
fn spawn_full_profiler(
proxy_pid: u32,
conproxy_bin: &std::path::Path,
output_dir: Option<&std::path::Path>,
) -> Option<std::process::Child> {
let output_dir = output_dir?;
let target_dir = conproxy_bin.parent()?;
let test_runner = target_dir.join("test_runner");
if !test_runner.exists() {
eprintln!(
"\x1b[33m[PROFILE]\x1b[0m test_runner not found at {} — skipping full profiler",
test_runner.display()
);
return None;
}
let mut cmd = std::process::Command::new(&test_runner);
cmd.args([
"proc-monitor",
"--pid",
&proxy_pid.to_string(),
"--output-dir",
&output_dir.to_string_lossy(),
"--perf",
"--bpftrace",
"--dhat",
]);
cmd.stdout(std::process::Stdio::null());
let log_file = std::fs::File::create(output_dir.join("profiler.log"));
match log_file {
Ok(file) => {
cmd.stderr(std::process::Stdio::from(file));
}
Err(_) => {
cmd.stderr(std::process::Stdio::null());
}
};
match cmd.spawn() {
Ok(child) => {
eprintln!(
"\x1b[36m[PROFILE]\x1b[0m Full profiler started (PID: {}, targeting proxy PID: {proxy_pid})",
child.id()
);
Some(child)
}
Err(e) => {
eprintln!(
"\x1b[33m[PROFILE]\x1b[0m Failed to spawn full profiler: {e} — falling back to lightweight monitor only"
);
None
}
}
}
fn write_endpoint_snapshots(client: &E2eClient, dir: &std::path::Path) {
let _ = std::fs::create_dir_all(dir);
let endpoints: &[(&str, &str)] = &[
("/stats", "stats.json"),
("/metrics", "metrics.json"),
("/pool", "pool.json"),
("/circuit", "circuit.json"),
("/health", "health.json"),
("/ready", "ready.json"),
("/clients", "clients.json"),
("/audit", "audit.json"),
];
for (path, filename) in endpoints {
let (status, body) = client.get_json(path);
let json = if status == 200 {
serde_json::to_string_pretty(&body).unwrap_or_else(|_| "{}".to_string())
} else {
"{}".to_string()
};
let _ = std::fs::write(dir.join(filename), json);
}
let (status, text) = client.prometheus();
let prom = if status == 200 { text } else { String::new() };
let _ = std::fs::write(dir.join("prometheus.txt"), prom);
eprintln!("Endpoint snapshots written to {}", dir.display());
}