use super::{
HistogramBoundsError, HistogramQueryError, HistogramRange, MeasurementHistogram, quantile_rank,
};
#[test]
fn rejects_first_duplicate_or_descending_bound() {
for (bounds, index) in [([3, 3, 4], 1), ([3, 2, 4], 1), ([1, 4, 3], 2)] {
assert_eq!(
MeasurementHistogram::new(bounds),
Err(HistogramBoundsError { index })
);
assert_eq!(HistogramBoundsError { index }.index(), index);
}
}
#[test]
fn inclusive_bounds_assign_each_sample_to_one_bucket() {
let mut histogram = MeasurementHistogram::new([10, 20]).unwrap();
for value in [0, 9, 10, 11, 19, 20, 21, 22] {
histogram.record(value);
}
assert_eq!(histogram.upper_bounds(), &[10, 20]);
assert_eq!(histogram.bucket_counts(), &[3, 3]);
assert_eq!(histogram.overflow(), 2);
assert_eq!(histogram.aggregate().samples(), 8);
assert_eq!(histogram.aggregate().total(), 112);
assert_eq!(histogram.aggregate().maximum(), Some(22));
}
#[test]
fn empty_and_measured_zero_are_distinct() {
let mut histogram = MeasurementHistogram::new([0, 10]).unwrap();
assert_eq!(histogram.bucket_counts(), &[0, 0]);
assert_eq!(histogram.overflow(), 0);
assert_eq!(histogram.aggregate().samples(), 0);
assert_eq!(histogram.aggregate().maximum(), None);
histogram.record(0);
assert_eq!(histogram.bucket_counts(), &[1, 0]);
assert_eq!(histogram.overflow(), 0);
assert_eq!(histogram.aggregate().samples(), 1);
assert_eq!(histogram.aggregate().total(), 0);
assert_eq!(histogram.aggregate().maximum(), Some(0));
}
#[test]
fn no_bounds_count_all_samples_in_overflow() {
let mut histogram = MeasurementHistogram::new([]).unwrap();
for value in [0, 7, u64::MAX] {
histogram.record(value);
}
assert_eq!(histogram.upper_bounds(), &[]);
assert_eq!(histogram.bucket_counts(), &[]);
assert_eq!(histogram.overflow(), 3);
assert_eq!(histogram.aggregate().samples(), 3);
assert_eq!(histogram.aggregate().total(), u64::MAX);
assert_eq!(histogram.aggregate().maximum(), Some(u64::MAX));
}
#[test]
fn maximum_bound_includes_maximum_sample() {
let mut histogram = MeasurementHistogram::new([0, u64::MAX]).unwrap();
for value in [0, 1, u64::MAX] {
histogram.record(value);
}
assert_eq!(histogram.bucket_counts(), &[1, 2]);
assert_eq!(histogram.overflow(), 0);
assert_eq!(histogram.aggregate().samples(), 3);
assert_eq!(histogram.aggregate().total(), u64::MAX);
}
#[test]
fn saturated_bucket_does_not_freeze_other_buckets_or_summary() {
let mut histogram = MeasurementHistogram::new([2, 4]).unwrap();
histogram.bucket_counts[0] = u64::MAX - 1;
for value in [1, 2, 3, 5] {
histogram.record(value);
}
assert_eq!(histogram.bucket_counts(), &[u64::MAX, 1]);
assert_eq!(histogram.overflow(), 1);
assert_eq!(histogram.aggregate().samples(), 4);
assert_eq!(histogram.aggregate().total(), 11);
assert_eq!(histogram.aggregate().maximum(), Some(5));
}
#[test]
fn saturated_overflow_and_total_do_not_freeze_other_observations() {
let mut histogram = MeasurementHistogram::new([1]).unwrap();
histogram.overflow = u64::MAX - 1;
histogram.record(u64::MAX);
assert_eq!(histogram.overflow(), u64::MAX);
assert_eq!(histogram.aggregate().total(), u64::MAX);
histogram.record(u64::MAX);
histogram.record(1);
assert_eq!(histogram.bucket_counts(), &[1]);
assert_eq!(histogram.overflow(), u64::MAX);
assert_eq!(histogram.aggregate().samples(), 3);
assert_eq!(histogram.aggregate().maximum(), Some(u64::MAX));
}
#[test]
fn construction_rejection_and_recording_are_available_in_constant_evaluation() {
const HISTOGRAM: Result<MeasurementHistogram<2>, HistogramBoundsError> =
match MeasurementHistogram::new([0, 10]) {
Ok(mut histogram) => {
histogram.record(0);
histogram.record(5);
histogram.record(11);
Ok(histogram)
}
Err(error) => Err(error),
};
const INVALID: Result<MeasurementHistogram<2>, HistogramBoundsError> =
MeasurementHistogram::new([1, 1]);
const UNBOUNDED: Result<MeasurementHistogram<0>, HistogramBoundsError> =
MeasurementHistogram::new([]);
let histogram = HISTOGRAM.unwrap();
assert_eq!(histogram.bucket_counts(), &[1, 1]);
assert_eq!(histogram.overflow(), 1);
assert_eq!(histogram.aggregate().samples(), 3);
assert_eq!(histogram.aggregate().total(), 16);
assert_eq!(INVALID, Err(HistogramBoundsError { index: 1 }));
assert_eq!(UNBOUNDED.unwrap().aggregate().samples(), 0);
}
#[test]
fn cumulative_counts_use_only_the_requested_finite_prefix() {
let mut histogram = MeasurementHistogram::new([0, 10, 100]).unwrap();
assert_eq!(histogram.cumulative_count(1), Ok(0));
for value in [0, 0, 1, 10, 11, 100, 101] {
histogram.record(value);
}
for (index, expected) in [2, 4, 6].into_iter().enumerate() {
assert_eq!(histogram.cumulative_count(index), Ok(expected));
}
assert_eq!(
histogram.cumulative_count(3),
Err(HistogramQueryError::BucketOutOfBounds {
index: 3,
buckets: 3
})
);
histogram.bucket_counts[2] = u64::MAX;
histogram.bucket_saturation[2] = true;
histogram.overflow = u64::MAX;
assert_eq!(histogram.cumulative_count(1), Ok(4));
assert_eq!(
histogram.cumulative_count(2),
Err(HistogramQueryError::SaturatedCount)
);
histogram.bucket_counts = [u64::MAX - 1, 1, 0];
histogram.bucket_saturation = [false; 3];
assert_eq!(histogram.cumulative_count(0), Ok(u64::MAX - 1));
assert_eq!(histogram.cumulative_count(1), Ok(u64::MAX));
histogram.bucket_counts[1] = 2;
assert_eq!(
histogram.cumulative_count(1),
Err(HistogramQueryError::SaturatedCount)
);
}
#[test]
fn nearest_rank_quantiles_return_ranges_including_overflow() {
let mut histogram = MeasurementHistogram::new([0, 10, 100]).unwrap();
for value in [0, 0, 1, 10, 11, 100, 101, 1_000] {
histogram.record(value);
}
let before = histogram;
for (numerator, denominator, lower, upper) in [
(1, 8, None, Some(0)),
(1, 4, None, Some(0)),
(1, 3, Some(0), Some(10)), (1, 2, Some(0), Some(10)),
(3, 4, Some(10), Some(100)),
(95, 100, Some(100), None),
(1, 1, Some(100), None),
(u64::MAX, u64::MAX, Some(100), None),
] {
let range = histogram
.quantile_bucket(numerator, denominator)
.unwrap()
.unwrap();
assert_eq!(range.lower_exclusive(), lower);
assert_eq!(range.upper_inclusive(), upper);
}
assert_eq!(histogram, before);
}
#[test]
fn query_empty_zero_no_bounds_and_maximum_bound() {
let mut histogram = MeasurementHistogram::new([0, u64::MAX]).unwrap();
assert_eq!(histogram.quantile_bucket(1, 2), Ok(None));
histogram.record(0);
assert_eq!(
histogram.quantile_bucket(1, 2),
Ok(Some(HistogramRange {
lower_exclusive: None,
upper_inclusive: Some(0),
}))
);
histogram.record(u64::MAX);
histogram.record(1);
assert_eq!(histogram.aggregate().total(), u64::MAX);
assert_eq!(histogram.cumulative_count(1), Ok(3));
assert_eq!(
histogram.quantile_bucket(1, 1),
Ok(Some(HistogramRange {
lower_exclusive: Some(0),
upper_inclusive: Some(u64::MAX),
}))
);
let mut unbounded = MeasurementHistogram::new([]).unwrap();
assert_eq!(unbounded.quantile_bucket(1, 2), Ok(None));
assert_eq!(
unbounded.cumulative_count(0),
Err(HistogramQueryError::BucketOutOfBounds {
index: 0,
buckets: 0,
})
);
unbounded.record(0);
assert_eq!(
unbounded.quantile_bucket(1, 2),
Ok(Some(HistogramRange {
lower_exclusive: None,
upper_inclusive: None,
}))
);
}
#[test]
fn quantiles_reject_invalid_fractions_and_all_required_saturated_counts() {
let mut histogram = MeasurementHistogram::new([10, 100]).unwrap();
for (numerator, denominator) in [(0, 0), (1, 0), (0, 1), (2, 1)] {
assert_eq!(
histogram.quantile_bucket(numerator, denominator),
Err(HistogramQueryError::InvalidQuantile)
);
}
histogram.record(0);
histogram.bucket_counts[1] = u64::MAX;
histogram.bucket_saturation[1] = true;
assert_eq!(
histogram.quantile_bucket(1, 100),
Err(HistogramQueryError::SaturatedCount)
);
histogram.bucket_counts[1] = 0;
histogram.bucket_saturation[1] = false;
histogram.overflow = u64::MAX;
histogram.overflow_saturated = true;
assert_eq!(
histogram.quantile_bucket(1, 100),
Err(HistogramQueryError::SaturatedCount)
);
assert_eq!(
quantile_rank(u64::MAX, 1, 2),
Ok(Some(u128::from(u64::MAX).div_ceil(2)))
);
assert_eq!(
quantile_rank(u64::MAX, 0, 0),
Err(HistogramQueryError::InvalidQuantile)
);
assert_eq!(
quantile_rank(u64::MAX - 1, u64::MAX - 1, u64::MAX),
Ok(Some(u128::from(u64::MAX - 1)))
);
assert_eq!(quantile_rank(u64::MAX - 1, 1, u64::MAX), Ok(Some(1)));
}
#[test]
fn nearest_rank_matches_wide_oracle_across_product_boundaries() {
for (numerator, denominator) in [
(1, 1),
(1, 2),
(2, 3),
(95, 100),
(1, u64::MAX),
(u64::MAX / 2, u64::MAX),
(u64::MAX - 1, u64::MAX),
(u64::MAX, u64::MAX),
] {
let boundary = u64::MAX / numerator;
for samples in [
0,
1,
2,
3,
5,
100,
u64::from(u32::MAX),
u64::from(u32::MAX) + 1,
boundary - 1,
boundary,
boundary.saturating_add(1),
u64::MAX - 1,
u64::MAX,
] {
let product = u128::from(samples) * u128::from(numerator);
let divisor = u128::from(denominator);
let expected = if samples == 0 {
None
} else {
Some(product / divisor + u128::from(product % divisor != 0))
};
assert_eq!(quantile_rank(samples, numerator, denominator), Ok(expected));
}
}
for samples in [0, 1, u64::MAX] {
for (numerator, denominator) in [(0, 0), (0, 1), (1, 0), (u64::MAX, u64::MAX - 1)] {
assert_eq!(
quantile_rank(samples, numerator, denominator),
Err(HistogramQueryError::InvalidQuantile)
);
}
}
}
#[test]
fn reporting_queries_are_available_in_constant_evaluation() {
const REPORT: (
Result<u64, HistogramQueryError>,
Result<Option<HistogramRange>, HistogramQueryError>,
) = match MeasurementHistogram::new([10]) {
Ok(mut histogram) => {
histogram.record(0);
histogram.record(11);
(
histogram.cumulative_count(0),
histogram.quantile_bucket(1, 1),
)
}
Err(_) => panic!("test bounds are increasing"),
};
assert_eq!(REPORT.0, Ok(1));
assert_eq!(
REPORT.1,
Ok(Some(HistogramRange {
lower_exclusive: Some(10),
upper_inclusive: None
}))
);
}
#[test]
fn merged_partitions_match_recording_and_reject_different_bounds_atomically() {
let mut left = MeasurementHistogram::new([0, 10]).unwrap();
let mut right = left;
let mut expected = left;
for (index, value) in [0, 1, 10, 11, u64::MAX, 3].into_iter().enumerate() {
expected.record(value);
if index % 2 == 0 {
left.record(value);
} else {
right.record(value);
}
}
let source = right;
left.merge(&right).unwrap();
assert_eq!(left, expected);
assert_eq!(right, source);
let before = left;
let mut mismatched = MeasurementHistogram::new([0, 11]).unwrap();
mismatched.record(2);
assert_eq!(left.merge(&mismatched).unwrap_err().index(), 1);
assert_eq!(left, before);
assert_eq!(
left.quantile_bucket(1, 2)
.unwrap()
.unwrap()
.upper_inclusive(),
Some(10)
);
}
#[test]
fn exact_count_cap_is_queryable_until_real_overflow_and_stays_sticky() {
let mut histogram = MeasurementHistogram::new([0]).unwrap();
for _ in 0..64 {
let previous = histogram;
histogram.merge(&previous).unwrap();
histogram.record(0);
}
assert_eq!(histogram.cumulative_count(0), Ok(u64::MAX));
assert_eq!(histogram.bucket_saturation(), &[false]);
assert!(!histogram.aggregate().samples_saturated());
assert_eq!(
histogram
.quantile_bucket(1, 1)
.unwrap()
.unwrap()
.upper_inclusive(),
Some(0)
);
histogram.record(0);
assert_eq!(histogram.bucket_saturation(), &[true]);
assert!(histogram.aggregate().samples_saturated());
assert_eq!(
histogram.cumulative_count(0),
Err(HistogramQueryError::SaturatedCount)
);
assert_eq!(
histogram.quantile_bucket(1, 1),
Err(HistogramQueryError::SaturatedCount)
);
let mut destination = MeasurementHistogram::new([0]).unwrap();
destination.merge(&histogram).unwrap();
assert_eq!(destination, histogram);
destination
.merge(&MeasurementHistogram::new([0]).unwrap())
.unwrap();
assert!(destination.bucket_saturation()[0]);
}
#[test]
fn merging_no_bounds_and_exact_value_caps_preserves_overflow_provenance() {
let mut left = MeasurementHistogram::new([]).unwrap();
let mut right = left;
left.record(0);
right.record(u64::MAX);
left.merge(&right).unwrap();
assert_eq!(left.overflow(), 2);
assert!(!left.overflow_saturated());
assert_eq!(left.aggregate().mean(), Ok(Some(u64::MAX / 2)));
left.record(1);
assert!(left.aggregate().total_saturated());
assert_eq!(
left.quantile_bucket(1, 1)
.unwrap()
.unwrap()
.upper_inclusive(),
None
);
left.overflow = u64::MAX;
left.overflow_saturated = true;
let mut destination = MeasurementHistogram::new([]).unwrap();
destination.merge(&left).unwrap();
assert!(destination.overflow_saturated());
assert_eq!(
destination.quantile_bucket(1, 2),
Err(HistogramQueryError::SaturatedCount)
);
}
#[test]
fn histogram_merge_and_atomic_rejection_support_constant_evaluation() {
const MERGED: Result<MeasurementHistogram<1>, super::HistogramMergeError> = {
let Ok(mut left) = MeasurementHistogram::new([10]) else {
panic!("test bounds");
};
let mut right = left;
left.record(0);
right.record(11);
match left.merge(&right) {
Ok(()) => Ok(left),
Err(error) => Err(error),
}
};
let merged = MERGED.unwrap();
assert_eq!(merged.bucket_counts(), &[1]);
assert_eq!(merged.overflow(), 1);
assert_eq!(merged.aggregate().mean(), Ok(Some(5)));
}