#![cfg(feature = "hotpath")]
#[cfg(test)]
pub mod tests {
use std::process::Command;
use hotpath::json::{JsonFunctionEntry, JsonReport};
fn run_example_with_features(features: &str, envs: &[(&str, &str)]) -> JsonReport {
let mut cmd = Command::new("cargo");
cmd.args([
"run",
"-p",
"test-all-features",
"--example",
"time_sampling",
"--features",
features,
]);
for name in [
"HOTPATH_TIME_SAMPLING_RATE",
"HOTPATH_FUNCTIONS_TIME_SAMPLING_RATE",
"HOTPATH_MUTEXES_TIME_SAMPLING_RATE",
"HOTPATH_RW_LOCKS_TIME_SAMPLING_RATE",
"HOTPATH_FUTURES_TIME_SAMPLING_RATE",
"HOTPATH_CHANNELS_TIME_SAMPLING_RATE",
] {
cmd.env_remove(name);
}
cmd.env("HOTPATH_OUTPUT_FORMAT", "json");
for (name, value) in envs {
cmd.env(name, value);
}
let output = cmd.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 json_start = stdout.find('{').expect("No JSON report in output");
serde_json::Deserializer::from_str(&stdout[json_start..])
.into_iter::<JsonReport>()
.next()
.expect("No JSON value in output")
.expect("Failed to parse JSON report")
}
fn run_example(envs: &[(&str, &str)]) -> JsonReport {
run_example_with_features("hotpath", envs)
}
fn function_entry<'a>(report: &'a JsonReport, name: &str) -> &'a JsonFunctionEntry {
report
.functions_timing
.as_ref()
.expect("No functions_timing section")
.data
.iter()
.find(|f| f.name.contains(name))
.unwrap_or_else(|| panic!("No `{name}` entry in functions_timing"))
}
#[test]
fn test_no_sampling_is_passthrough() {
let report = run_example(&[]);
assert!(report.time_sampling.is_none());
let work = function_entry(&report, "work");
assert_eq!(work.calls, 10);
assert_eq!(work.sampled_calls, 10);
let mutex = &report.mutexes.as_ref().expect("No mutexes section").data[0];
assert_eq!(mutex.count, 10);
assert_eq!(mutex.sampled_count, 10);
let channel = &report.channels.as_ref().expect("No channels section").data[0];
assert_eq!(channel.received_count, 10);
assert_eq!(channel.proc_sampled_count, Some(10));
}
#[test]
fn test_global_rate_applies_to_all_resources() {
let report = run_example(&[("HOTPATH_TIME_SAMPLING_RATE", "0.5")]);
let rates = report.time_sampling.as_ref().expect("No time_sampling");
for resource in ["functions", "mutexes", "rw_locks", "futures", "channels"] {
assert_eq!(rates.get(resource), Some(&0.5), "rate for {resource}");
}
let work = function_entry(&report, "work");
assert_eq!(work.calls, 10);
assert_eq!(work.sampled_calls, 5);
let main = function_entry(&report, "main");
assert_eq!(main.sampled_calls, main.calls);
let mutex = &report.mutexes.as_ref().expect("No mutexes section").data[0];
assert_eq!(mutex.count, 10);
assert_eq!(mutex.sampled_count, 5);
let rw = &report.rw_locks.as_ref().expect("No rw_locks section").data[0];
assert_eq!(rw.read_count, 10);
assert_eq!(rw.read_sampled_count, 5);
assert_eq!(rw.write_count, 4);
assert_eq!(rw.write_sampled_count, 2);
let channel = &report.channels.as_ref().expect("No channels section").data[0];
assert_eq!(channel.sent_count, 10);
assert_eq!(channel.received_count, 10);
assert_eq!(channel.proc_sampled_count, Some(5));
assert_ne!(channel.proc_avg.as_deref(), Some("-"));
}
#[test]
fn test_rate_zero_is_count_only() {
let report = run_example(&[("HOTPATH_TIME_SAMPLING_RATE", "0")]);
let work = function_entry(&report, "work");
assert_eq!(work.calls, 10);
assert_eq!(work.sampled_calls, 0);
assert_eq!(work.avg, "-");
assert_eq!(work.total, "-");
assert_eq!(work.percent_total, "-");
let main = function_entry(&report, "main");
assert_eq!(main.calls, 1);
assert_eq!(main.sampled_calls, 1);
let mutex = &report.mutexes.as_ref().expect("No mutexes section").data[0];
assert_eq!(mutex.count, 10);
assert_eq!(mutex.sampled_count, 0);
assert_eq!(mutex.wait_avg, "-");
let channel = &report.channels.as_ref().expect("No channels section").data[0];
assert_eq!(channel.received_count, 10);
assert_eq!(channel.proc_sampled_count, Some(0));
assert_eq!(channel.proc_avg.as_deref(), Some("-"));
}
#[test]
fn test_per_resource_env_beats_global() {
let report = run_example(&[
("HOTPATH_TIME_SAMPLING_RATE", "0"),
("HOTPATH_FUNCTIONS_TIME_SAMPLING_RATE", "1.0"),
]);
let work = function_entry(&report, "work");
assert_eq!(work.sampled_calls, 10);
let mutex = &report.mutexes.as_ref().expect("No mutexes section").data[0];
assert_eq!(mutex.sampled_count, 0);
}
#[test]
fn test_builder_rate_applies() {
let report = run_example(&[("TEST_BUILDER_TIME_SAMPLING_RATE", "0.5")]);
let work = function_entry(&report, "work");
assert_eq!(work.calls, 10);
assert_eq!(work.sampled_calls, 5);
let mutex = &report.mutexes.as_ref().expect("No mutexes section").data[0];
assert_eq!(mutex.sampled_count, 5);
}
#[test]
fn test_env_beats_builder() {
let report = run_example(&[
("TEST_BUILDER_FUNCTIONS_TIME_SAMPLING_RATE", "0"),
("HOTPATH_FUNCTIONS_TIME_SAMPLING_RATE", "1.0"),
]);
let work = function_entry(&report, "work");
assert_eq!(work.sampled_calls, 10);
}
#[test]
fn test_alloc_mode_samples_durations_only() {
let report = run_example_with_features(
"hotpath,hotpath-alloc",
&[("HOTPATH_TIME_SAMPLING_RATE", "0.5")],
);
let rates = report.time_sampling.as_ref().expect("No time_sampling");
assert_eq!(rates.get("functions"), Some(&0.5));
let work = function_entry(&report, "work");
assert_eq!(work.calls, 10);
assert_eq!(work.sampled_calls, 5);
assert_ne!(work.avg, "-");
let alloc_work = report
.functions_alloc
.as_ref()
.expect("No functions_alloc section")
.data
.iter()
.find(|f| f.name.contains("work"))
.expect("No `work` entry in functions_alloc");
assert_eq!(alloc_work.calls, 10);
assert_eq!(alloc_work.sampled_calls, 10);
}
#[test]
fn test_futures_count_only() {
let output = Command::new("cargo")
.args([
"run",
"-p",
"test-futures",
"--example",
"basic_futures",
"--features",
"hotpath",
])
.env("HOTPATH_OUTPUT_FORMAT", "json")
.env("HOTPATH_FUTURES_TIME_SAMPLING_RATE", "0")
.env("HOTPATH_REPORT", "futures")
.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 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");
let futures = report.futures.expect("No futures section");
assert!(futures.data.iter().any(|e| e.total_polls > 0));
for entry in &futures.data {
assert_eq!(entry.sampled_polls, 0, "sampled polls for {}", entry.label);
assert_eq!(
entry.total_poll_duration_ns, 0,
"duration recorded for {}",
entry.label
);
}
}
}