#![expect(clippy::arc_with_non_send_sync)]
use std::{collections::BTreeMap, num::NonZero, ops::RangeInclusive, sync::Arc, time::Duration};
use anyhow::Context;
use trustfall::Schema;
use trustfall::provider::{Eid, Vid};
use super::ptrace::{ExpHistogram, FunctionCall, Summary, TracingAdapter};
use crate::RustdocAdapter;
macro_rules! get_test_data {
($data:ident, $case:ident) => {
let rustdoc_path = format!("./localdata/test_data/{}/rustdoc.json", stringify!($case));
let content = std::fs::read_to_string(&rustdoc_path)
.with_context(|| format!("Could not load {rustdoc_path} file, did you forget to run ./scripts/regenerate_test_rustdocs.sh ?"))
.expect("failed to load rustdoc");
let crate_ = serde_json::from_str(&content).expect("failed to parse rustdoc");
let manifest_path = format!("./test_crates/{}/Cargo.toml", stringify!($case));
let mut metadata = cargo_metadata::MetadataCommand::new().manifest_path(&manifest_path).no_deps().exec().expect("failed to run cargo metadata");
assert_eq!(metadata.packages.len(), 1, "{metadata:?}");
let package = metadata.packages.pop().expect("failed to pop only item in vec");
let storage = crate::PackageStorage::from_rustdoc_and_package(
crate_,
package,
);
let $data = crate::PackageIndex::from_storage(&storage);
}
}
#[test]
fn exp_histogram() {
let mut hist = ExpHistogram::new();
assert_eq!(hist.count(), 0);
assert!(hist.buckets().iter().all(|&b| b == 0));
assert_eq!(
hist.boundaries(),
&[
100,
300,
1000,
3000,
10000,
30000,
100000,
300000,
1000000,
3000000,
10000000,
30000000,
100000000,
300000000,
1000000000,
u64::MAX
],
);
for (i, upper_lim) in hist.boundaries()[..15].iter().enumerate() {
hist.add(*upper_lim);
hist.add(*upper_lim + 1);
hist.add(*upper_lim - 1);
if i == 0 {
assert_eq!(hist.buckets()[i], 2);
} else {
assert_eq!(hist.buckets()[i], 3);
}
assert_eq!(hist.count() as usize, 3 * (i + 1));
}
hist.add(u64::MAX);
hist.add(10u64.pow(17));
assert_eq!(hist.buckets()[15], 3);
}
#[test]
fn summary() {
let dur = Duration::from_nanos(100);
let mut summary = Summary::new(dur);
assert_eq!(summary.count(), 1);
assert_eq!(summary.histogram().count(), 1);
assert_eq!(summary.min(), dur);
assert_eq!(summary.max(), dur);
assert_eq!(summary.total(), dur);
assert_eq!(summary.mean(), dur);
summary.update(Duration::from_nanos(50));
summary.update(Duration::from_nanos(200));
assert_eq!(summary.count(), 3);
assert_eq!(summary.histogram().count(), 3);
assert_eq!(summary.min(), Duration::from_nanos(50));
assert_eq!(summary.max(), Duration::from_nanos(200));
assert_eq!(summary.total(), Duration::from_nanos(350));
assert_eq!(summary.mean(), Duration::from_nanos(350 / 3));
}
#[test]
fn tracing_adapter() {
get_test_data!(data, function_has_body);
let adapter = RustdocAdapter::new(&data, None);
let query = r#"
{
Crate {
item {
... on Trait {
owner: name @output
method {
name @output
has_body @output
}
}
}
}
}
"#;
let variables: BTreeMap<&str, &str> = BTreeMap::default();
let schema = Schema::parse(include_str!("../../rustdoc_schema.graphql"))
.expect("schema failed to parse");
let tracing_adapter = Arc::new(TracingAdapter::new(&adapter));
let mut _results: Vec<_> =
trustfall::execute_query(&schema, tracing_adapter.clone(), query, variables.clone())
.expect("failed to run query")
.collect();
let tracer = tracing_adapter.finish();
let desired: [(FunctionCall, RangeInclusive<u32>); 8] = [
(
FunctionCall::ResolveProperty(
Vid::new(NonZero::new(2).unwrap()),
"Trait".into(),
"name".into(),
),
2..=2,
),
(
FunctionCall::ResolveProperty(
Vid::new(NonZero::new(3).unwrap()),
"Method".into(),
"has_body".into(),
),
2..=2,
),
(
FunctionCall::ResolveProperty(
Vid::new(NonZero::new(3).unwrap()),
"Method".into(),
"name".into(),
),
2..=2,
),
(
FunctionCall::ResolveNeighbors(
Vid::new(NonZero::new(1).unwrap()),
"Crate".into(),
Eid::new(NonZero::new(1).unwrap()),
),
1..=1,
),
(
FunctionCall::ResolveNeighbors(
Vid::new(NonZero::new(2).unwrap()),
"Trait".into(),
Eid::new(NonZero::new(2).unwrap()),
),
1..=1,
),
(
FunctionCall::ResolveNeighborsInner(
Vid::new(NonZero::new(1).unwrap()),
"Crate".into(),
Eid::new(NonZero::new(1).unwrap()),
),
20..=21,
),
(
FunctionCall::ResolveNeighborsInner(
Vid::new(NonZero::new(2).unwrap()),
"Trait".into(),
Eid::new(NonZero::new(2).unwrap()),
),
2..=2,
),
(
FunctionCall::ResolveCoercion(
Vid::new(NonZero::new(2).unwrap()),
"Item".into(),
"Trait".into(),
),
20..=21,
),
];
assert_eq!(tracer.calls.len(), desired.len());
for ((call, summary), (expected_call, expected_count_range)) in
tracer.calls.iter().zip(desired.iter())
{
assert_eq!(call, expected_call);
let count = summary.count();
assert!(
expected_count_range.contains(&count),
"unexpected count for {call:?}: got {count}, expected {expected_count_range:?}",
);
}
}