#[cfg(all(test, feature = "hotpath"))]
pub mod tests {
use std::process::Command;
use hotpath::json::{JsonChannelsList, JsonReport};
fn path_sep() -> &'static str {
if cfg!(windows) {
"\\"
} else {
"/"
}
}
#[test]
fn test_basic_output() {
let output = Command::new("cargo")
.args([
"run",
"-p",
"test-channels-asc",
"--example",
"basic_asc",
"--features",
"hotpath",
])
.output()
.expect("Failed to execute command");
assert!(
output.status.success(),
"Command failed with status: {}",
output.status
);
assert!(!output.stderr.is_empty(), "Stderr is empty");
let all_expected = ["Actor 1", "bounded-channel", "unbounded", "bounded[10]"];
let stdout = String::from_utf8_lossy(&output.stdout);
for expected in all_expected {
assert!(
stdout.contains(expected),
"Expected:\n{expected}\n\nGot:\n{stdout}",
);
}
}
#[test]
fn test_basic_json_output() {
let output = Command::new("cargo")
.args([
"run",
"-p",
"test-channels-asc",
"--example",
"basic_json_asc",
"--features",
"hotpath",
])
.output()
.expect("Failed to execute command");
assert!(
output.status.success(),
"Command failed with status: {}",
output.status
);
let all_expected = ["\"label\": \"unbounded\"", "\"label\": \"bounded\""];
let stdout = String::from_utf8_lossy(&output.stdout);
for expected in all_expected {
assert!(
stdout.contains(expected),
"Expected:\n{expected}\n\nGot:\n{stdout}",
);
}
}
#[test]
fn test_closed_channels_output() {
let output = Command::new("cargo")
.args([
"run",
"-p",
"test-channels-asc",
"--example",
"closed_asc",
"--features",
"hotpath",
])
.output()
.expect("Failed to execute command");
assert!(
output.status.success(),
"Command failed with status: {}",
output.status
);
}
#[test]
fn test_iter_output() {
let output = Command::new("cargo")
.args([
"run",
"-p",
"test-channels-asc",
"--example",
"iter_asc",
"--features",
"hotpath",
])
.output()
.expect("Failed to execute command");
assert!(
output.status.success(),
"Command failed with status: {}",
output.status
);
let stdout = String::from_utf8_lossy(&output.stdout);
let sep = path_sep();
let iter_27 = format!("examples{sep}iter_asc.rs:27");
let iter_27_2 = format!("examples{sep}iter_asc.rs:27-2");
let iter_27_3 = format!("examples{sep}iter_asc.rs:27-3");
let iter_41 = format!("examples{sep}iter_asc.rs:41");
let iter_41_2 = format!("examples{sep}iter_asc.rs:41-2");
let iter_41_3 = format!("examples{sep}iter_asc.rs:41-3");
let all_expected = [
iter_27.as_str(),
iter_27_2.as_str(),
iter_27_3.as_str(),
iter_41.as_str(),
iter_41_2.as_str(),
iter_41_3.as_str(),
];
for expected in all_expected {
assert!(
stdout.contains(expected),
"Expected:\n{expected}\n\nGot:\n{stdout}",
);
}
}
#[test]
fn test_slow_consumer_no_panic() {
let output = Command::new("cargo")
.args([
"run",
"-p",
"test-channels-asc",
"--example",
"slow_consumer_asc",
"--features",
"hotpath",
])
.output()
.expect("Failed to execute command");
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
output.status.success(),
"Command failed with status: {}\nStdout:\n{}\nStderr:\n{}",
output.status,
stdout,
stderr
);
assert!(
stdout.contains("Slow consumer example completed!"),
"Expected completion message not found.\nOutput:\n{}",
stdout
);
}
#[test]
fn test_data_endpoints() {
use std::{thread::sleep, time::Duration};
let mut child = Command::new("cargo")
.args([
"run",
"-p",
"test-channels-asc",
"--example",
"basic_asc",
"--features",
"hotpath",
])
.env("HOTPATH_METRICS_PORT", "6772")
.env("TEST_SLEEP_SECONDS", "10")
.spawn()
.expect("Failed to spawn command");
let mut json_text = String::new();
let mut last_error = None;
let all_expected = ["basic_asc.rs", "bounded-channel", "Actor 1"];
for _attempt in 0..12 {
sleep(Duration::from_millis(750));
match ureq::get("http://localhost:6772/channels").call() {
Ok(mut response) => {
json_text = response
.body_mut()
.read_to_string()
.expect("Failed to read response body");
last_error = None;
if all_expected.iter().all(|e| json_text.contains(e)) {
break;
}
}
Err(e) => {
last_error = Some(format!("Request error: {}", e));
}
}
}
if let Some(error) = last_error {
let _ = child.kill();
panic!("Failed after 12 retries: {}", error);
}
for expected in all_expected {
assert!(
json_text.contains(expected),
"Expected:\n{expected}\n\nGot:\n{json_text}",
);
}
let channels: JsonChannelsList =
serde_json::from_str(&json_text).expect("Failed to parse channels JSON");
if let Some(channel) = channels.data.first() {
let logs_url = format!("http://localhost:6772/channels/{}/logs", channel.id);
let response = ureq::get(&logs_url)
.call()
.expect("Failed to call /channels/:id/logs endpoint");
assert_eq!(
response.status(),
200,
"Expected status 200 for /channels/:id/logs endpoint"
);
}
let _ = child.kill();
let _ = child.wait();
}
fn parse_channels(stdout: &str) -> JsonChannelsList {
let json_start = stdout.find('{').expect("No JSON report in output");
let report: JsonReport = serde_json::Deserializer::from_str(&stdout[json_start..])
.into_iter::<JsonReport>()
.next()
.expect("No JSON value in output")
.expect("Failed to parse JSON report");
report.channels.expect("No channels section in report")
}
fn run_example(name: &str) -> String {
let output = Command::new("cargo")
.args([
"run",
"-p",
"test-channels-asc",
"--example",
name,
"--features",
"hotpath",
])
.output()
.expect("Failed to execute command");
assert!(
output.status.success(),
"Command failed with status: {}",
output.status
);
String::from_utf8_lossy(&output.stdout).into_owned()
}
#[test]
fn test_exact_queue_depth() {
let stdout = run_example("wrap_asc");
let channels = parse_channels(&stdout);
let entry = channels
.data
.iter()
.find(|c| c.label == "wrap-queue")
.expect("wrap-queue channel not found");
assert_eq!(entry.sent_count, 50, "expected 50 sends");
assert_eq!(
entry.received_count, 0,
"expected 0 receives at report time"
);
assert_eq!(
entry.queue_size,
Some(50),
"expected exact queue depth of 50"
);
assert_eq!(
entry.max_queue_size,
Some(50),
"expected max queue depth of 50"
);
}
#[test]
fn test_receiver_dropped_closes() {
let stdout = run_example("wrap_closed_asc");
let channels = parse_channels(&stdout);
let entry = channels
.data
.iter()
.find(|c| c.label == "recv-dropped")
.expect("recv-dropped channel not found");
assert_eq!(
entry.state.as_deref(),
Some("closed"),
"expected closed state after receiver drop"
);
}
#[test]
fn test_receiver_clone_dropped_closes_with_sender_alive() {
let stdout = run_example("wrap_recv_clone_closed_asc");
let channels = parse_channels(&stdout);
let entry = channels
.data
.iter()
.find(|c| c.label == "recv-clone-dropped")
.expect("recv-clone-dropped channel not found");
assert_eq!(
entry.state.as_deref(),
Some("closed"),
"expected closed state after all receivers dropped while senders alive"
);
}
#[test]
fn test_processing_histogram() {
let stdout = run_example("wrap_latency_asc");
let channels = parse_channels(&stdout);
assert_eq!(channels.percentiles, vec![50.0, 95.0]);
let latency = channels
.data
.iter()
.find(|c| c.label == "wrap-latency")
.expect("wrap-latency channel not found");
let proc_avg = latency
.proc_avg
.as_deref()
.expect("channel should report proc_avg in JSON");
assert!(!proc_avg.is_empty(), "proc_avg should not be empty");
assert_ne!(
proc_avg, "0ns",
"expected non-zero send->receive latency (~20ms held in channel)"
);
assert!(
latency.proc_percentiles.contains_key("p50"),
"expected p50 latency percentile in JSON, got {:?}",
latency.proc_percentiles
);
assert!(
latency.proc_percentiles.contains_key("p95"),
"expected p95 latency percentile in JSON, got {:?}",
latency.proc_percentiles
);
}
}