use std::hash::Hash;
pub trait ClosedAxis: Copy + Eq + Hash + 'static {
const ALL: &'static [Self];
}
pub fn axis_iter<A: ClosedAxis>() -> impl Iterator<Item = A> {
A::ALL.iter().copied()
}
#[must_use]
pub fn axis_cardinality<A: ClosedAxis>() -> usize {
A::ALL.len()
}
#[must_use]
pub fn axis_ordinal<A: ClosedAxis>(value: A) -> usize {
match A::ALL.iter().position(|&v| v == value) {
Some(i) => i,
None => unreachable!(
"ClosedAxis::ALL must contain every value of the axis (discipline violation: \
`Self::ALL` omitted a reachable value)",
),
}
}
#[must_use]
pub fn axis_at<A: ClosedAxis>(ordinal: usize) -> Option<A> {
A::ALL.get(ordinal).copied()
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct AxisHistogram<A: ClosedAxis> {
counts: Vec<usize>,
_marker: std::marker::PhantomData<A>,
}
impl<A: ClosedAxis> Default for AxisHistogram<A> {
fn default() -> Self {
Self::empty()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
pub enum ModalityClass {
Empty,
StrictModalStrictAntimodal,
TiedModalStrictAntimodal,
StrictModalTiedAntimodal,
TiedModalTiedAntimodal,
}
impl ModalityClass {
pub const ALL: &'static [Self] = &[
Self::Empty,
Self::StrictModalStrictAntimodal,
Self::TiedModalStrictAntimodal,
Self::StrictModalTiedAntimodal,
Self::TiedModalTiedAntimodal,
];
#[must_use]
pub const fn is_empty(self) -> bool {
matches!(self, Self::Empty)
}
#[must_use]
pub const fn is_modally_tied(self) -> bool {
matches!(
self,
Self::TiedModalStrictAntimodal | Self::TiedModalTiedAntimodal
)
}
#[must_use]
pub const fn is_antimodally_tied(self) -> bool {
matches!(
self,
Self::StrictModalTiedAntimodal | Self::TiedModalTiedAntimodal
)
}
#[must_use]
pub const fn is_strictly_modally_unique(self) -> bool {
matches!(
self,
Self::StrictModalStrictAntimodal | Self::StrictModalTiedAntimodal
)
}
#[must_use]
pub const fn is_strictly_antimodally_unique(self) -> bool {
matches!(
self,
Self::StrictModalStrictAntimodal | Self::TiedModalStrictAntimodal
)
}
#[must_use]
pub const fn is_doubly_strict_unique(self) -> bool {
matches!(self, Self::StrictModalStrictAntimodal)
}
#[must_use]
pub const fn is_doubly_tied(self) -> bool {
matches!(self, Self::TiedModalTiedAntimodal)
}
#[must_use]
pub const fn is_only_modally_tied(self) -> bool {
matches!(self, Self::TiedModalStrictAntimodal)
}
#[must_use]
pub const fn is_only_antimodally_tied(self) -> bool {
matches!(self, Self::StrictModalTiedAntimodal)
}
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Empty => "empty",
Self::StrictModalStrictAntimodal => "strict-modal-strict-antimodal",
Self::TiedModalStrictAntimodal => "tied-modal-strict-antimodal",
Self::StrictModalTiedAntimodal => "strict-modal-tied-antimodal",
Self::TiedModalTiedAntimodal => "tied-modal-tied-antimodal",
}
}
#[must_use]
pub fn from_canonical_str(s: &str) -> Option<Self> {
Self::ALL
.iter()
.copied()
.find(|v| v.as_str().eq_ignore_ascii_case(s))
}
}
impl std::fmt::Display for ModalityClass {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct ParseModalityClassError {
pub label: String,
}
impl std::fmt::Display for ParseModalityClassError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "unknown modality class label {:?}", self.label)
}
}
impl std::error::Error for ParseModalityClassError {}
impl std::str::FromStr for ModalityClass {
type Err = ParseModalityClassError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::from_canonical_str(s).ok_or_else(|| ParseModalityClassError {
label: s.to_owned(),
})
}
}
impl serde::Serialize for ModalityClass {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.collect_str(self)
}
}
impl<'de> serde::Deserialize<'de> for ModalityClass {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
struct ModalityClassVisitor;
impl serde::de::Visitor<'_> for ModalityClassVisitor {
type Value = ModalityClass;
fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(
"a canonical ModalityClass kebab-case label \
(`empty`, `strict-modal-strict-antimodal`, \
`tied-modal-strict-antimodal`, \
`strict-modal-tied-antimodal`, \
`tied-modal-tied-antimodal`)",
)
}
fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
v.parse::<ModalityClass>().map_err(E::custom)
}
}
deserializer.deserialize_str(ModalityClassVisitor)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
pub enum SupportCardinalityClass {
Empty,
SingularSupport,
StrictPartialCover,
SingularGap,
FullCover,
}
impl SupportCardinalityClass {
pub const ALL: &'static [Self] = &[
Self::Empty,
Self::SingularSupport,
Self::StrictPartialCover,
Self::SingularGap,
Self::FullCover,
];
#[must_use]
pub const fn is_empty(self) -> bool {
matches!(self, Self::Empty)
}
#[must_use]
pub const fn is_singular_support(self) -> bool {
matches!(self, Self::SingularSupport)
}
#[must_use]
pub const fn is_strict_partial_cover(self) -> bool {
matches!(self, Self::StrictPartialCover)
}
#[must_use]
pub const fn is_singular_gap(self) -> bool {
matches!(self, Self::SingularGap)
}
#[must_use]
pub const fn is_full_cover(self) -> bool {
matches!(self, Self::FullCover)
}
#[must_use]
pub const fn is_partial_cover(self) -> bool {
matches!(
self,
Self::SingularSupport | Self::StrictPartialCover | Self::SingularGap,
)
}
#[must_use]
pub const fn is_boundary(self) -> bool {
matches!(self, Self::Empty | Self::FullCover)
}
#[must_use]
pub const fn is_singular(self) -> bool {
matches!(self, Self::SingularSupport | Self::SingularGap)
}
#[must_use]
pub const fn is_low_support(self) -> bool {
matches!(self, Self::Empty | Self::SingularSupport)
}
#[must_use]
pub const fn is_high_support(self) -> bool {
matches!(self, Self::SingularGap | Self::FullCover)
}
#[must_use]
pub const fn support_boundary_distance(self) -> SupportBoundaryDistance {
match self {
Self::Empty | Self::FullCover => SupportBoundaryDistance::Boundary,
Self::SingularSupport | Self::SingularGap => SupportBoundaryDistance::Singular,
Self::StrictPartialCover => SupportBoundaryDistance::StrictInterior,
}
}
#[must_use]
pub const fn support_magnitude_direction(self) -> SupportMagnitudeDirection {
match self {
Self::Empty | Self::SingularSupport => SupportMagnitudeDirection::Low,
Self::StrictPartialCover => SupportMagnitudeDirection::StrictInterior,
Self::SingularGap | Self::FullCover => SupportMagnitudeDirection::High,
}
}
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Empty => "empty",
Self::SingularSupport => "singular-support",
Self::StrictPartialCover => "strict-partial-cover",
Self::SingularGap => "singular-gap",
Self::FullCover => "full-cover",
}
}
#[must_use]
pub fn from_canonical_str(s: &str) -> Option<Self> {
Self::ALL
.iter()
.copied()
.find(|v| v.as_str().eq_ignore_ascii_case(s))
}
}
impl std::fmt::Display for SupportCardinalityClass {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct ParseSupportCardinalityClassError {
pub label: String,
}
impl std::fmt::Display for ParseSupportCardinalityClassError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"unknown support cardinality class label {:?}",
self.label
)
}
}
impl std::error::Error for ParseSupportCardinalityClassError {}
impl std::str::FromStr for SupportCardinalityClass {
type Err = ParseSupportCardinalityClassError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::from_canonical_str(s).ok_or_else(|| ParseSupportCardinalityClassError {
label: s.to_owned(),
})
}
}
impl serde::Serialize for SupportCardinalityClass {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.collect_str(self)
}
}
impl<'de> serde::Deserialize<'de> for SupportCardinalityClass {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
struct SupportCardinalityClassVisitor;
impl serde::de::Visitor<'_> for SupportCardinalityClassVisitor {
type Value = SupportCardinalityClass;
fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(
"a canonical SupportCardinalityClass kebab-case label \
(`empty`, `singular-support`, `strict-partial-cover`, \
`singular-gap`, `full-cover`)",
)
}
fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
v.parse::<SupportCardinalityClass>().map_err(E::custom)
}
}
deserializer.deserialize_str(SupportCardinalityClassVisitor)
}
}
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Ord, PartialOrd)]
pub enum SupportBoundaryDistance {
Boundary,
Singular,
StrictInterior,
}
impl SupportBoundaryDistance {
pub const ALL: &'static [Self] = &[Self::Boundary, Self::Singular, Self::StrictInterior];
#[must_use]
pub const fn is_boundary(self) -> bool {
matches!(self, Self::Boundary)
}
#[must_use]
pub const fn is_singular(self) -> bool {
matches!(self, Self::Singular)
}
#[must_use]
pub const fn is_strict_interior(self) -> bool {
matches!(self, Self::StrictInterior)
}
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Boundary => "boundary",
Self::Singular => "singular",
Self::StrictInterior => "strict-interior",
}
}
#[must_use]
pub fn from_canonical_str(s: &str) -> Option<Self> {
Self::ALL
.iter()
.copied()
.find(|v| v.as_str().eq_ignore_ascii_case(s))
}
}
impl std::fmt::Display for SupportBoundaryDistance {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct ParseSupportBoundaryDistanceError {
pub label: String,
}
impl std::fmt::Display for ParseSupportBoundaryDistanceError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"unknown support boundary distance label {:?}",
self.label
)
}
}
impl std::error::Error for ParseSupportBoundaryDistanceError {}
impl std::str::FromStr for SupportBoundaryDistance {
type Err = ParseSupportBoundaryDistanceError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::from_canonical_str(s).ok_or_else(|| ParseSupportBoundaryDistanceError {
label: s.to_owned(),
})
}
}
impl serde::Serialize for SupportBoundaryDistance {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.collect_str(self)
}
}
impl<'de> serde::Deserialize<'de> for SupportBoundaryDistance {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
struct SupportBoundaryDistanceVisitor;
impl serde::de::Visitor<'_> for SupportBoundaryDistanceVisitor {
type Value = SupportBoundaryDistance;
fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(
"a canonical SupportBoundaryDistance kebab-case label \
(`boundary`, `singular`, `strict-interior`)",
)
}
fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
v.parse::<SupportBoundaryDistance>().map_err(E::custom)
}
}
deserializer.deserialize_str(SupportBoundaryDistanceVisitor)
}
}
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Ord, PartialOrd)]
pub enum SupportMagnitudeDirection {
Low,
StrictInterior,
High,
}
impl SupportMagnitudeDirection {
pub const ALL: &'static [Self] = &[Self::Low, Self::StrictInterior, Self::High];
#[must_use]
pub const fn is_low(self) -> bool {
matches!(self, Self::Low)
}
#[must_use]
pub const fn is_strict_interior(self) -> bool {
matches!(self, Self::StrictInterior)
}
#[must_use]
pub const fn is_high(self) -> bool {
matches!(self, Self::High)
}
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Low => "low",
Self::StrictInterior => "strict-interior",
Self::High => "high",
}
}
#[must_use]
pub fn from_canonical_str(s: &str) -> Option<Self> {
Self::ALL
.iter()
.copied()
.find(|v| v.as_str().eq_ignore_ascii_case(s))
}
}
impl std::fmt::Display for SupportMagnitudeDirection {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct ParseSupportMagnitudeDirectionError {
pub label: String,
}
impl std::fmt::Display for ParseSupportMagnitudeDirectionError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"unknown support magnitude direction label {:?}",
self.label
)
}
}
impl std::error::Error for ParseSupportMagnitudeDirectionError {}
impl std::str::FromStr for SupportMagnitudeDirection {
type Err = ParseSupportMagnitudeDirectionError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::from_canonical_str(s).ok_or_else(|| ParseSupportMagnitudeDirectionError {
label: s.to_owned(),
})
}
}
impl serde::Serialize for SupportMagnitudeDirection {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.collect_str(self)
}
}
impl<'de> serde::Deserialize<'de> for SupportMagnitudeDirection {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
struct SupportMagnitudeDirectionVisitor;
impl serde::de::Visitor<'_> for SupportMagnitudeDirectionVisitor {
type Value = SupportMagnitudeDirection;
fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(
"a canonical SupportMagnitudeDirection kebab-case label \
(`low`, `strict-interior`, `high`)",
)
}
fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
v.parse::<SupportMagnitudeDirection>().map_err(E::custom)
}
}
deserializer.deserialize_str(SupportMagnitudeDirectionVisitor)
}
}
impl<A: ClosedAxis> AxisHistogram<A> {
#[must_use]
pub fn empty() -> Self {
Self {
counts: vec![0usize; axis_cardinality::<A>()],
_marker: std::marker::PhantomData,
}
}
pub fn observe(&mut self, value: A) {
self.counts[axis_ordinal(value)] += 1;
}
pub fn clear(&mut self) {
self.counts.fill(0);
}
#[must_use]
pub fn count(&self, value: A) -> usize {
self.counts[axis_ordinal(value)]
}
#[must_use]
pub fn total(&self) -> usize {
self.counts.iter().sum()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.counts.iter().all(|&c| c == 0)
}
pub fn iter(&self) -> impl Iterator<Item = (A, usize)> + '_ {
axis_iter::<A>()
.enumerate()
.map(|(i, v)| (v, self.counts[i]))
}
pub fn iter_mut(&mut self) -> AxisHistogramIterMut<'_, A> {
AxisHistogramIterMut {
counts: self.counts.iter_mut().enumerate(),
_marker: std::marker::PhantomData,
}
}
pub fn nonzero(&self) -> impl Iterator<Item = (A, usize)> + '_ {
self.iter().filter(|&(_, c)| c > 0)
}
pub fn observed(&self) -> impl Iterator<Item = A> + '_ {
self.iter().filter(|&(_, c)| c > 0).map(|(v, _)| v)
}
pub fn unobserved(&self) -> impl Iterator<Item = A> + '_ {
self.iter().filter(|&(_, c)| c == 0).map(|(v, _)| v)
}
#[must_use]
pub fn distinct_cells(&self) -> usize {
self.counts.iter().filter(|&&c| c > 0).count()
}
#[must_use]
pub fn unobserved_cells(&self) -> usize {
self.counts.iter().filter(|&&c| c == 0).count()
}
#[must_use]
pub fn is_full_cover(&self) -> bool {
self.counts.iter().all(|&c| c > 0)
}
#[must_use]
pub fn dominant_cell(&self) -> Option<A> {
let mut iter = self.iter().filter(|&(_, c)| c > 0);
let first = iter.next()?;
Some(
iter.fold(
first,
|best, current| {
if current.1 > best.1 { current } else { best }
},
)
.0,
)
}
#[must_use]
pub fn recessive_cell(&self) -> Option<A> {
let mut iter = self.iter().filter(|&(_, c)| c > 0);
let first = iter.next()?;
Some(
iter.fold(
first,
|best, current| {
if current.1 < best.1 { current } else { best }
},
)
.0,
)
}
#[must_use]
pub fn peak_count(&self) -> usize {
self.counts.iter().copied().max().unwrap_or(0)
}
#[must_use]
pub fn dominant_observation(&self) -> Option<(A, usize)> {
let mut iter = self.iter().filter(|&(_, c)| c > 0);
let first = iter.next()?;
Some(iter.fold(
first,
|best, current| {
if current.1 > best.1 { current } else { best }
},
))
}
#[must_use]
pub fn peak_multiplicity(&self) -> usize {
let mut max = 0usize;
let mut multiplicity = 0usize;
for &c in &self.counts {
if c > max {
max = c;
multiplicity = 1;
} else if c == max && c > 0 {
multiplicity += 1;
}
}
multiplicity
}
#[must_use]
pub fn trough_count(&self) -> usize {
self.counts
.iter()
.copied()
.filter(|&c| c > 0)
.min()
.unwrap_or(0)
}
#[must_use]
pub fn recessive_observation(&self) -> Option<(A, usize)> {
let mut iter = self.iter().filter(|&(_, c)| c > 0);
let first = iter.next()?;
Some(iter.fold(
first,
|best, current| {
if current.1 < best.1 { current } else { best }
},
))
}
#[must_use]
pub fn trough_multiplicity(&self) -> usize {
let mut min = usize::MAX;
let mut multiplicity = 0usize;
for &c in &self.counts {
if c == 0 {
continue;
}
if c < min {
min = c;
multiplicity = 1;
} else if c == min {
multiplicity += 1;
}
}
multiplicity
}
#[must_use]
pub fn modality_degree(&self) -> (usize, usize) {
let mut max = 0usize;
let mut peak_mult = 0usize;
let mut min = usize::MAX;
let mut trough_mult = 0usize;
for &c in &self.counts {
if c == 0 {
continue;
}
if c > max {
max = c;
peak_mult = 1;
} else if c == max {
peak_mult += 1;
}
if c < min {
min = c;
trough_mult = 1;
} else if c == min {
trough_mult += 1;
}
}
(peak_mult, trough_mult)
}
#[must_use]
pub fn is_strictly_modally_unique(&self) -> bool {
self.peak_multiplicity() == 1
}
#[must_use]
pub fn is_strictly_antimodally_unique(&self) -> bool {
self.trough_multiplicity() == 1
}
#[must_use]
pub fn is_modally_tied(&self) -> bool {
self.peak_multiplicity() >= 2
}
#[must_use]
pub fn is_antimodally_tied(&self) -> bool {
self.trough_multiplicity() >= 2
}
#[must_use]
pub fn modality_class(&self) -> ModalityClass {
match self.modality_degree() {
(0, 0) => ModalityClass::Empty,
(1, 1) => ModalityClass::StrictModalStrictAntimodal,
(_, 1) => ModalityClass::TiedModalStrictAntimodal,
(1, _) => ModalityClass::StrictModalTiedAntimodal,
_ => ModalityClass::TiedModalTiedAntimodal,
}
}
#[must_use]
pub fn spread(&self) -> usize {
self.peak_count() - self.trough_count()
}
#[must_use]
pub fn is_uniform_count(&self) -> bool {
let mut nonzero = self.counts.iter().copied().filter(|&c| c > 0);
match nonzero.next() {
None => true,
Some(first) => nonzero.all(|c| c == first),
}
}
#[must_use]
pub fn has_singular_support(&self) -> bool {
let mut nonzero = self.counts.iter().filter(|&&c| c > 0);
nonzero.next().is_some() && nonzero.next().is_none()
}
#[must_use]
pub fn has_partial_cover(&self) -> bool {
let mut saw_zero = false;
let mut saw_nonzero = false;
for &c in &self.counts {
if c == 0 {
saw_zero = true;
} else {
saw_nonzero = true;
}
if saw_zero && saw_nonzero {
return true;
}
}
false
}
#[must_use]
pub fn has_singular_gap(&self) -> bool {
let mut zeros = self.counts.iter().filter(|&&c| c == 0);
zeros.next().is_some() && zeros.next().is_none()
}
#[must_use]
pub fn has_strict_partial_cover(&self) -> bool {
let mut zeros: u8 = 0;
let mut nonzeros: u8 = 0;
for &c in &self.counts {
if c == 0 {
zeros = zeros.saturating_add(1);
} else {
nonzeros = nonzeros.saturating_add(1);
}
if zeros >= 2 && nonzeros >= 2 {
return true;
}
}
false
}
#[must_use]
pub fn has_low_support(&self) -> bool {
let mut nonzeros: u8 = 0;
for &c in &self.counts {
if c > 0 {
nonzeros = nonzeros.saturating_add(1);
if nonzeros >= 2 {
return false;
}
}
}
true
}
#[must_use]
pub fn has_high_support(&self) -> bool {
let mut zeros: u8 = 0;
let mut nonzeros: u8 = 0;
for &c in &self.counts {
if c == 0 {
zeros = zeros.saturating_add(1);
if zeros >= 2 {
return false;
}
} else {
nonzeros = nonzeros.saturating_add(1);
}
}
zeros == 0 || nonzeros >= 2
}
#[must_use]
pub fn has_boundary(&self) -> bool {
let mut saw_zero = false;
let mut saw_nonzero = false;
for &c in &self.counts {
if c == 0 {
saw_zero = true;
} else {
saw_nonzero = true;
}
if saw_zero && saw_nonzero {
return false;
}
}
true
}
#[must_use]
pub fn has_singular(&self) -> bool {
let mut zeros: u8 = 0;
let mut nonzeros: u8 = 0;
for &c in &self.counts {
if c == 0 {
zeros = zeros.saturating_add(1);
} else {
nonzeros = nonzeros.saturating_add(1);
}
if zeros >= 2 && nonzeros >= 2 {
return false;
}
}
zeros == 1 || nonzeros == 1
}
#[must_use]
pub fn support_cardinality_class(&self) -> SupportCardinalityClass {
let support = self.distinct_cells();
let cardinality = axis_cardinality::<A>();
if support == 0 {
SupportCardinalityClass::Empty
} else if support == cardinality {
SupportCardinalityClass::FullCover
} else if support == 1 {
SupportCardinalityClass::SingularSupport
} else if support + 1 == cardinality {
SupportCardinalityClass::SingularGap
} else {
SupportCardinalityClass::StrictPartialCover
}
}
#[must_use]
pub fn support_boundary_distance(&self) -> SupportBoundaryDistance {
self.support_cardinality_class().support_boundary_distance()
}
#[must_use]
pub fn support_magnitude_direction(&self) -> SupportMagnitudeDirection {
self.support_cardinality_class()
.support_magnitude_direction()
}
#[must_use]
pub fn merge(mut self, other: &Self) -> Self {
self += other;
self
}
#[must_use]
pub fn pointwise_max(mut self, other: &Self) -> Self {
self |= other;
self
}
#[must_use]
pub fn pointwise_min(mut self, other: &Self) -> Self {
self &= other;
self
}
#[must_use]
pub fn is_dominated_by(&self, other: &Self) -> bool {
self.counts
.iter()
.zip(other.counts.iter())
.all(|(lhs, rhs)| lhs <= rhs)
}
#[must_use]
pub fn dominates(&self, other: &Self) -> bool {
self.counts
.iter()
.zip(other.counts.iter())
.all(|(lhs, rhs)| lhs >= rhs)
}
#[must_use]
pub fn is_strictly_dominated_by(&self, other: &Self) -> bool {
let mut any_strict = false;
for (lhs, rhs) in self.counts.iter().zip(other.counts.iter()) {
if lhs > rhs {
return false;
}
if lhs < rhs {
any_strict = true;
}
}
any_strict
}
#[must_use]
pub fn strictly_dominates(&self, other: &Self) -> bool {
let mut any_strict = false;
for (lhs, rhs) in self.counts.iter().zip(other.counts.iter()) {
if lhs < rhs {
return false;
}
if lhs > rhs {
any_strict = true;
}
}
any_strict
}
#[must_use]
pub fn is_disjoint_from(&self, other: &Self) -> bool {
self.counts
.iter()
.zip(other.counts.iter())
.all(|(&lhs, &rhs)| lhs == 0 || rhs == 0)
}
#[must_use]
pub fn intersects(&self, other: &Self) -> bool {
self.counts
.iter()
.zip(other.counts.iter())
.any(|(&lhs, &rhs)| lhs > 0 && rhs > 0)
}
#[must_use]
pub fn symmetric_difference(mut self, other: &Self) -> Self {
self ^= other;
self
}
}
impl<A: ClosedAxis> FromIterator<A> for AxisHistogram<A> {
fn from_iter<I: IntoIterator<Item = A>>(iter: I) -> Self {
let mut hist = Self::empty();
hist.extend(iter);
hist
}
}
impl<A: ClosedAxis> Extend<A> for AxisHistogram<A> {
fn extend<I: IntoIterator<Item = A>>(&mut self, iter: I) {
for value in iter {
self.observe(value);
}
}
}
impl<A: ClosedAxis> From<A> for AxisHistogram<A> {
fn from(value: A) -> Self {
let mut hist = Self::empty();
hist.observe(value);
hist
}
}
impl<A: ClosedAxis, const N: usize> From<[A; N]> for AxisHistogram<A> {
fn from(values: [A; N]) -> Self {
values.into_iter().collect()
}
}
impl<A: ClosedAxis> FromIterator<(A, usize)> for AxisHistogram<A> {
fn from_iter<I: IntoIterator<Item = (A, usize)>>(iter: I) -> Self {
let mut hist = Self::empty();
hist.extend(iter);
hist
}
}
impl<A: ClosedAxis> Extend<(A, usize)> for AxisHistogram<A> {
fn extend<I: IntoIterator<Item = (A, usize)>>(&mut self, iter: I) {
for (cell, count) in iter {
self.counts[axis_ordinal(cell)] += count;
}
}
}
impl<A: ClosedAxis, const N: usize> From<[(A, usize); N]> for AxisHistogram<A> {
fn from(values: [(A, usize); N]) -> Self {
values.into_iter().collect()
}
}
impl<'a, A: ClosedAxis> FromIterator<&'a A> for AxisHistogram<A> {
fn from_iter<I: IntoIterator<Item = &'a A>>(iter: I) -> Self {
let mut hist = Self::empty();
hist.extend(iter);
hist
}
}
impl<'a, A: ClosedAxis> Extend<&'a A> for AxisHistogram<A> {
fn extend<I: IntoIterator<Item = &'a A>>(&mut self, iter: I) {
for &value in iter {
self.observe(value);
}
}
}
#[derive(Debug, Clone)]
pub struct AxisHistogramIter<'a, A: ClosedAxis> {
counts: std::iter::Enumerate<std::slice::Iter<'a, usize>>,
_marker: std::marker::PhantomData<A>,
}
impl<A: ClosedAxis> Iterator for AxisHistogramIter<'_, A> {
type Item = (A, usize);
fn next(&mut self) -> Option<Self::Item> {
self.counts.next().map(|(i, &c)| (A::ALL[i], c))
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.counts.size_hint()
}
}
impl<A: ClosedAxis> ExactSizeIterator for AxisHistogramIter<'_, A> {}
impl<A: ClosedAxis> std::iter::FusedIterator for AxisHistogramIter<'_, A> {}
impl<A: ClosedAxis> DoubleEndedIterator for AxisHistogramIter<'_, A> {
fn next_back(&mut self) -> Option<Self::Item> {
self.counts.next_back().map(|(i, &c)| (A::ALL[i], c))
}
}
#[derive(Debug, Clone)]
pub struct AxisHistogramIntoIter<A: ClosedAxis> {
counts: std::iter::Enumerate<std::vec::IntoIter<usize>>,
_marker: std::marker::PhantomData<A>,
}
impl<A: ClosedAxis> Iterator for AxisHistogramIntoIter<A> {
type Item = (A, usize);
fn next(&mut self) -> Option<Self::Item> {
self.counts.next().map(|(i, c)| (A::ALL[i], c))
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.counts.size_hint()
}
}
impl<A: ClosedAxis> ExactSizeIterator for AxisHistogramIntoIter<A> {}
impl<A: ClosedAxis> std::iter::FusedIterator for AxisHistogramIntoIter<A> {}
impl<A: ClosedAxis> DoubleEndedIterator for AxisHistogramIntoIter<A> {
fn next_back(&mut self) -> Option<Self::Item> {
self.counts.next_back().map(|(i, c)| (A::ALL[i], c))
}
}
impl<'a, A: ClosedAxis> IntoIterator for &'a AxisHistogram<A> {
type Item = (A, usize);
type IntoIter = AxisHistogramIter<'a, A>;
fn into_iter(self) -> Self::IntoIter {
AxisHistogramIter {
counts: self.counts.iter().enumerate(),
_marker: std::marker::PhantomData,
}
}
}
impl<A: ClosedAxis> IntoIterator for AxisHistogram<A> {
type Item = (A, usize);
type IntoIter = AxisHistogramIntoIter<A>;
fn into_iter(self) -> Self::IntoIter {
AxisHistogramIntoIter {
counts: self.counts.into_iter().enumerate(),
_marker: std::marker::PhantomData,
}
}
}
#[derive(Debug)]
pub struct AxisHistogramIterMut<'a, A: ClosedAxis> {
counts: std::iter::Enumerate<std::slice::IterMut<'a, usize>>,
_marker: std::marker::PhantomData<A>,
}
impl<'a, A: ClosedAxis> Iterator for AxisHistogramIterMut<'a, A> {
type Item = (A, &'a mut usize);
fn next(&mut self) -> Option<Self::Item> {
self.counts.next().map(|(i, c)| (A::ALL[i], c))
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.counts.size_hint()
}
}
impl<A: ClosedAxis> ExactSizeIterator for AxisHistogramIterMut<'_, A> {}
impl<A: ClosedAxis> std::iter::FusedIterator for AxisHistogramIterMut<'_, A> {}
impl<A: ClosedAxis> DoubleEndedIterator for AxisHistogramIterMut<'_, A> {
fn next_back(&mut self) -> Option<Self::Item> {
self.counts.next_back().map(|(i, c)| (A::ALL[i], c))
}
}
impl<'a, A: ClosedAxis> IntoIterator for &'a mut AxisHistogram<A> {
type Item = (A, &'a mut usize);
type IntoIter = AxisHistogramIterMut<'a, A>;
fn into_iter(self) -> Self::IntoIter {
AxisHistogramIterMut {
counts: self.counts.iter_mut().enumerate(),
_marker: std::marker::PhantomData,
}
}
}
impl<A: ClosedAxis> std::iter::Sum<AxisHistogram<A>> for AxisHistogram<A> {
fn sum<I: IntoIterator<Item = AxisHistogram<A>>>(iter: I) -> Self {
iter.into_iter().fold(Self::empty(), |acc, h| acc.merge(&h))
}
}
impl<'a, A: ClosedAxis> std::iter::Sum<&'a AxisHistogram<A>> for AxisHistogram<A> {
fn sum<I: IntoIterator<Item = &'a AxisHistogram<A>>>(iter: I) -> Self {
iter.into_iter().fold(Self::empty(), AxisHistogram::merge)
}
}
impl<A: ClosedAxis> std::ops::AddAssign<&AxisHistogram<A>> for AxisHistogram<A> {
fn add_assign(&mut self, other: &AxisHistogram<A>) {
for (slot, &delta) in self.counts.iter_mut().zip(other.counts.iter()) {
*slot += delta;
}
}
}
impl<A: ClosedAxis> std::ops::AddAssign<AxisHistogram<A>> for AxisHistogram<A> {
fn add_assign(&mut self, other: AxisHistogram<A>) {
*self += &other;
}
}
impl<A: ClosedAxis> std::ops::Add<&AxisHistogram<A>> for AxisHistogram<A> {
type Output = AxisHistogram<A>;
fn add(mut self, other: &AxisHistogram<A>) -> Self::Output {
self += other;
self
}
}
impl<A: ClosedAxis> std::ops::Add<AxisHistogram<A>> for AxisHistogram<A> {
type Output = AxisHistogram<A>;
fn add(mut self, other: AxisHistogram<A>) -> Self::Output {
self += &other;
self
}
}
impl<A: ClosedAxis> std::ops::SubAssign<&AxisHistogram<A>> for AxisHistogram<A> {
fn sub_assign(&mut self, other: &AxisHistogram<A>) {
for (slot, &delta) in self.counts.iter_mut().zip(other.counts.iter()) {
*slot = slot.saturating_sub(delta);
}
}
}
impl<A: ClosedAxis> std::ops::SubAssign<AxisHistogram<A>> for AxisHistogram<A> {
fn sub_assign(&mut self, other: AxisHistogram<A>) {
*self -= &other;
}
}
impl<A: ClosedAxis> std::ops::Sub<&AxisHistogram<A>> for AxisHistogram<A> {
type Output = AxisHistogram<A>;
fn sub(mut self, other: &AxisHistogram<A>) -> Self::Output {
self -= other;
self
}
}
impl<A: ClosedAxis> std::ops::Sub<AxisHistogram<A>> for AxisHistogram<A> {
type Output = AxisHistogram<A>;
fn sub(mut self, other: AxisHistogram<A>) -> Self::Output {
self -= &other;
self
}
}
impl<A: ClosedAxis> std::ops::BitXorAssign<&AxisHistogram<A>> for AxisHistogram<A> {
fn bitxor_assign(&mut self, other: &AxisHistogram<A>) {
for (slot, &delta) in self.counts.iter_mut().zip(other.counts.iter()) {
*slot = (*slot).abs_diff(delta);
}
}
}
impl<A: ClosedAxis> std::ops::BitXorAssign<AxisHistogram<A>> for AxisHistogram<A> {
fn bitxor_assign(&mut self, other: AxisHistogram<A>) {
*self ^= &other;
}
}
impl<A: ClosedAxis> std::ops::BitXor<&AxisHistogram<A>> for AxisHistogram<A> {
type Output = AxisHistogram<A>;
fn bitxor(mut self, other: &AxisHistogram<A>) -> Self::Output {
self ^= other;
self
}
}
impl<A: ClosedAxis> std::ops::BitXor<AxisHistogram<A>> for AxisHistogram<A> {
type Output = AxisHistogram<A>;
fn bitxor(mut self, other: AxisHistogram<A>) -> Self::Output {
self ^= &other;
self
}
}
impl<A: ClosedAxis> std::ops::BitOrAssign<&AxisHistogram<A>> for AxisHistogram<A> {
fn bitor_assign(&mut self, other: &AxisHistogram<A>) {
for (slot, &delta) in self.counts.iter_mut().zip(other.counts.iter()) {
if delta > *slot {
*slot = delta;
}
}
}
}
impl<A: ClosedAxis> std::ops::BitOrAssign<AxisHistogram<A>> for AxisHistogram<A> {
fn bitor_assign(&mut self, other: AxisHistogram<A>) {
*self |= &other;
}
}
impl<A: ClosedAxis> std::ops::BitOr<&AxisHistogram<A>> for AxisHistogram<A> {
type Output = AxisHistogram<A>;
fn bitor(mut self, other: &AxisHistogram<A>) -> Self::Output {
self |= other;
self
}
}
impl<A: ClosedAxis> std::ops::BitOr<AxisHistogram<A>> for AxisHistogram<A> {
type Output = AxisHistogram<A>;
fn bitor(mut self, other: AxisHistogram<A>) -> Self::Output {
self |= &other;
self
}
}
impl<A: ClosedAxis> std::ops::BitAndAssign<&AxisHistogram<A>> for AxisHistogram<A> {
fn bitand_assign(&mut self, other: &AxisHistogram<A>) {
for (slot, &delta) in self.counts.iter_mut().zip(other.counts.iter()) {
if delta < *slot {
*slot = delta;
}
}
}
}
impl<A: ClosedAxis> std::ops::BitAndAssign<AxisHistogram<A>> for AxisHistogram<A> {
fn bitand_assign(&mut self, other: AxisHistogram<A>) {
*self &= &other;
}
}
impl<A: ClosedAxis> std::ops::BitAnd<&AxisHistogram<A>> for AxisHistogram<A> {
type Output = AxisHistogram<A>;
fn bitand(mut self, other: &AxisHistogram<A>) -> Self::Output {
self &= other;
self
}
}
impl<A: ClosedAxis> std::ops::BitAnd<AxisHistogram<A>> for AxisHistogram<A> {
type Output = AxisHistogram<A>;
fn bitand(mut self, other: AxisHistogram<A>) -> Self::Output {
self &= &other;
self
}
}
impl<A: ClosedAxis> std::ops::MulAssign<usize> for AxisHistogram<A> {
fn mul_assign(&mut self, factor: usize) {
for slot in &mut self.counts {
*slot *= factor;
}
}
}
impl<A: ClosedAxis> std::ops::Mul<usize> for AxisHistogram<A> {
type Output = AxisHistogram<A>;
fn mul(mut self, factor: usize) -> Self::Output {
self *= factor;
self
}
}
impl<A: ClosedAxis> std::ops::DivAssign<usize> for AxisHistogram<A> {
fn div_assign(&mut self, divisor: usize) {
for slot in &mut self.counts {
*slot /= divisor;
}
}
}
impl<A: ClosedAxis> std::ops::Div<usize> for AxisHistogram<A> {
type Output = AxisHistogram<A>;
fn div(mut self, divisor: usize) -> Self::Output {
self /= divisor;
self
}
}
impl<A: ClosedAxis> std::ops::RemAssign<usize> for AxisHistogram<A> {
fn rem_assign(&mut self, divisor: usize) {
for slot in &mut self.counts {
*slot %= divisor;
}
}
}
impl<A: ClosedAxis> std::ops::Rem<usize> for AxisHistogram<A> {
type Output = AxisHistogram<A>;
fn rem(mut self, divisor: usize) -> Self::Output {
self %= divisor;
self
}
}
impl<A: ClosedAxis> std::ops::Mul<AxisHistogram<A>> for usize {
type Output = AxisHistogram<A>;
fn mul(self, hist: AxisHistogram<A>) -> Self::Output {
hist * self
}
}
impl<A: ClosedAxis> std::ops::Mul<&AxisHistogram<A>> for usize {
type Output = AxisHistogram<A>;
fn mul(self, hist: &AxisHistogram<A>) -> Self::Output {
self * hist.clone()
}
}
impl<A: ClosedAxis> std::ops::Mul<usize> for &AxisHistogram<A> {
type Output = AxisHistogram<A>;
fn mul(self, factor: usize) -> Self::Output {
self.clone() * factor
}
}
impl<A: ClosedAxis> std::ops::Div<usize> for &AxisHistogram<A> {
type Output = AxisHistogram<A>;
fn div(self, divisor: usize) -> Self::Output {
self.clone() / divisor
}
}
impl<A: ClosedAxis> std::ops::Rem<usize> for &AxisHistogram<A> {
type Output = AxisHistogram<A>;
fn rem(self, divisor: usize) -> Self::Output {
self.clone() % divisor
}
}
impl<A: ClosedAxis> std::ops::Index<A> for AxisHistogram<A> {
type Output = usize;
fn index(&self, value: A) -> &usize {
&self.counts[axis_ordinal(value)]
}
}
impl<A: ClosedAxis> std::ops::IndexMut<A> for AxisHistogram<A> {
fn index_mut(&mut self, value: A) -> &mut usize {
&mut self.counts[axis_ordinal(value)]
}
}
impl<A: ClosedAxis> std::cmp::PartialOrd for AxisHistogram<A> {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
use std::cmp::Ordering;
let mut ord = Ordering::Equal;
for (lhs, rhs) in self.counts.iter().zip(other.counts.iter()) {
match (ord, lhs.cmp(rhs)) {
(Ordering::Equal, cell) => ord = cell,
(Ordering::Less, Ordering::Greater) | (Ordering::Greater, Ordering::Less) => {
return None;
}
_ => {}
}
}
Some(ord)
}
}
impl<A: ClosedAxisLabel> std::fmt::Display for AxisHistogram<A> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut first = true;
for (cell, count) in self.iter() {
if !first {
f.write_str(", ")?;
}
first = false;
write!(f, "{}={}", cell.as_str(), count)?;
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum ParseAxisHistogramError {
MissingEquals {
pair: String,
},
UnknownLabel {
label: String,
},
InvalidCount {
label: String,
count: String,
},
DuplicateLabel {
label: String,
},
}
impl std::fmt::Display for ParseAxisHistogramError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::MissingEquals { pair } => {
write!(f, "missing '=' separator in pair {pair:?}")
}
Self::UnknownLabel { label } => {
write!(f, "unknown axis label {label:?}")
}
Self::InvalidCount { label, count } => {
write!(
f,
"invalid count {count:?} for label {label:?} (expected usize)"
)
}
Self::DuplicateLabel { label } => {
write!(f, "duplicate label {label:?}")
}
}
}
}
impl std::error::Error for ParseAxisHistogramError {}
impl<A: ClosedAxisLabel> std::str::FromStr for AxisHistogram<A> {
type Err = ParseAxisHistogramError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut hist = Self::empty();
if s.is_empty() {
return Ok(hist);
}
let cardinality = axis_cardinality::<A>();
let mut seen = vec![false; cardinality];
for pair in s.split(", ") {
let (label, count) =
pair.split_once('=')
.ok_or_else(|| ParseAxisHistogramError::MissingEquals {
pair: pair.to_owned(),
})?;
let cell = <A as ClosedAxisLabel>::from_canonical_str(label).ok_or_else(|| {
ParseAxisHistogramError::UnknownLabel {
label: label.to_owned(),
}
})?;
let count: usize =
count
.parse()
.map_err(|_| ParseAxisHistogramError::InvalidCount {
label: label.to_owned(),
count: count.to_owned(),
})?;
let ordinal = axis_ordinal(cell);
if seen[ordinal] {
return Err(ParseAxisHistogramError::DuplicateLabel {
label: label.to_owned(),
});
}
seen[ordinal] = true;
hist.counts[ordinal] = count;
}
Ok(hist)
}
}
impl<A: ClosedAxisLabel> serde::Serialize for AxisHistogram<A> {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.collect_str(self)
}
}
impl<'de, A: ClosedAxisLabel> serde::Deserialize<'de> for AxisHistogram<A> {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
struct AxisHistogramVisitor<A: ClosedAxisLabel>(std::marker::PhantomData<fn() -> A>);
impl<A: ClosedAxisLabel> serde::de::Visitor<'_> for AxisHistogramVisitor<A> {
type Value = AxisHistogram<A>;
fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"a comma-separated `<label>=<count>` histogram string for axis {}",
std::any::type_name::<A>(),
)
}
fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
v.parse::<AxisHistogram<A>>().map_err(E::custom)
}
}
deserializer.deserialize_str(AxisHistogramVisitor::<A>(std::marker::PhantomData))
}
}
#[must_use]
pub fn axis_histogram<A: ClosedAxis, I: IntoIterator<Item = A>>(items: I) -> AxisHistogram<A> {
items.into_iter().collect()
}
pub trait ClosedAxisLabel: ClosedAxis {
fn as_str(self) -> &'static str;
fn from_canonical_str(s: &str) -> Option<Self> {
Self::ALL
.iter()
.copied()
.find(|v| v.as_str().eq_ignore_ascii_case(s))
}
}
#[must_use]
pub fn axis_label<L: ClosedAxisLabel>(value: L) -> &'static str {
value.as_str()
}
#[must_use]
pub fn axis_from_label<L: ClosedAxisLabel>(s: &str) -> Option<L> {
L::from_canonical_str(s)
}
pub trait ProductCube: ClosedAxis {
fn is_realizable(self) -> bool;
}
pub fn realizable_iter<C: ProductCube>() -> impl Iterator<Item = C> {
C::ALL.iter().copied().filter(|c| c.is_realizable())
}
pub fn unrealizable_iter<C: ProductCube>() -> impl Iterator<Item = C> {
C::ALL.iter().copied().filter(|c| !c.is_realizable())
}
#[must_use]
pub fn realizable_count<C: ProductCube>() -> usize {
realizable_iter::<C>().count()
}
#[must_use]
pub fn unrealizable_count<C: ProductCube>() -> usize {
unrealizable_iter::<C>().count()
}
#[must_use]
pub fn realizable_ordinal<C: ProductCube>(cell: C) -> Option<usize> {
realizable_iter::<C>().position(|c| c == cell)
}
#[must_use]
pub fn realizable_at<C: ProductCube>(ordinal: usize) -> Option<C> {
realizable_iter::<C>().nth(ordinal)
}
#[must_use]
pub fn unrealizable_ordinal<C: ProductCube>(cell: C) -> Option<usize> {
unrealizable_iter::<C>().position(|c| c == cell)
}
#[must_use]
pub fn unrealizable_at<C: ProductCube>(ordinal: usize) -> Option<C> {
unrealizable_iter::<C>().nth(ordinal)
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
pub enum PartitionFace {
Realizable,
Unrealizable,
}
impl PartitionFace {
pub const ALL: &'static [Self] = &[Self::Realizable, Self::Unrealizable];
#[must_use]
pub const fn is_realizable(self) -> bool {
matches!(self, Self::Realizable)
}
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Realizable => "realizable",
Self::Unrealizable => "unrealizable",
}
}
}
impl std::fmt::Display for PartitionFace {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct ParsePartitionFaceError {
pub label: String,
}
impl std::fmt::Display for ParsePartitionFaceError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "unknown partition face label {:?}", self.label)
}
}
impl std::error::Error for ParsePartitionFaceError {}
impl std::str::FromStr for PartitionFace {
type Err = ParsePartitionFaceError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
<Self as ClosedAxisLabel>::from_canonical_str(s).ok_or_else(|| ParsePartitionFaceError {
label: s.to_owned(),
})
}
}
impl serde::Serialize for PartitionFace {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.collect_str(self)
}
}
impl<'de> serde::Deserialize<'de> for PartitionFace {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
struct PartitionFaceVisitor;
impl serde::de::Visitor<'_> for PartitionFaceVisitor {
type Value = PartitionFace;
fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(
"a canonical PartitionFace lowercase label \
(`realizable`, `unrealizable`)",
)
}
fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
v.parse::<PartitionFace>().map_err(E::custom)
}
}
deserializer.deserialize_str(PartitionFaceVisitor)
}
}
impl ClosedAxis for PartitionFace {
const ALL: &'static [Self] = Self::ALL;
}
impl ClosedAxisLabel for PartitionFace {
fn as_str(self) -> &'static str {
Self::as_str(self)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
pub enum PartitionOrdinal {
Realizable(usize),
Unrealizable(usize),
}
impl PartitionOrdinal {
#[must_use]
pub const fn face(self) -> PartitionFace {
match self {
Self::Realizable(_) => PartitionFace::Realizable,
Self::Unrealizable(_) => PartitionFace::Unrealizable,
}
}
#[must_use]
pub const fn face_ordinal(self) -> usize {
match self {
Self::Realizable(i) | Self::Unrealizable(i) => i,
}
}
}
impl std::fmt::Display for PartitionOrdinal {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}:{}", self.face().as_str(), self.face_ordinal())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum ParsePartitionOrdinalError {
MissingSeparator {
input: String,
},
UnknownFace {
label: String,
},
MalformedOrdinal {
ordinal: String,
source: std::num::ParseIntError,
},
}
impl std::fmt::Display for ParsePartitionOrdinalError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::MissingSeparator { input } => {
write!(f, "PartitionOrdinal input missing `:` separator: {input:?}",)
}
Self::UnknownFace { label } => {
write!(f, "unknown partition face label {label:?}")
}
Self::MalformedOrdinal { ordinal, source } => write!(
f,
"malformed PartitionOrdinal face_ordinal {ordinal:?}: {source}",
),
}
}
}
impl std::error::Error for ParsePartitionOrdinalError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::MalformedOrdinal { source, .. } => Some(source),
Self::MissingSeparator { .. } | Self::UnknownFace { .. } => None,
}
}
}
impl std::str::FromStr for PartitionOrdinal {
type Err = ParsePartitionOrdinalError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (face_str, ordinal_str) =
s.split_once(':')
.ok_or_else(|| ParsePartitionOrdinalError::MissingSeparator {
input: s.to_owned(),
})?;
let face = face_str
.parse::<PartitionFace>()
.map_err(|e| ParsePartitionOrdinalError::UnknownFace { label: e.label })?;
let face_ordinal = ordinal_str.parse::<usize>().map_err(|source| {
ParsePartitionOrdinalError::MalformedOrdinal {
ordinal: ordinal_str.to_owned(),
source,
}
})?;
Ok(match face {
PartitionFace::Realizable => Self::Realizable(face_ordinal),
PartitionFace::Unrealizable => Self::Unrealizable(face_ordinal),
})
}
}
impl serde::Serialize for PartitionOrdinal {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.collect_str(self)
}
}
impl<'de> serde::Deserialize<'de> for PartitionOrdinal {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
struct PartitionOrdinalVisitor;
impl serde::de::Visitor<'_> for PartitionOrdinalVisitor {
type Value = PartitionOrdinal;
fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(
"a canonical PartitionOrdinal `<face>:<face_ordinal>` scalar \
(e.g. `realizable:42`, `unrealizable:7`)",
)
}
fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
v.parse::<PartitionOrdinal>().map_err(E::custom)
}
}
deserializer.deserialize_str(PartitionOrdinalVisitor)
}
}
#[must_use]
pub fn partition_ordinal<C: ProductCube>(cell: C) -> PartitionOrdinal {
if ProductCube::is_realizable(cell) {
PartitionOrdinal::Realizable(
realizable_ordinal::<C>(cell).expect(
"ProductCube discipline: is_realizable(cell) => realizable_ordinal is Some",
),
)
} else {
PartitionOrdinal::Unrealizable(
unrealizable_ordinal::<C>(cell).expect(
"ProductCube discipline: !is_realizable(cell) => unrealizable_ordinal is Some",
),
)
}
}
#[must_use]
pub fn at_partition_ordinal<C: ProductCube>(p: PartitionOrdinal) -> Option<C> {
match p {
PartitionOrdinal::Realizable(i) => realizable_at::<C>(i),
PartitionOrdinal::Unrealizable(i) => unrealizable_at::<C>(i),
}
}
pub trait PartialInverseCube: ProductCube {
type Image: ClosedAxis + std::fmt::Debug;
fn invert(self) -> Option<Self::Image>;
fn forward(image: Self::Image) -> Self;
}
pub fn realizable_images<C: PartialInverseCube>() -> impl Iterator<Item = C::Image> {
C::ALL
.iter()
.copied()
.filter_map(PartialInverseCube::invert)
}
pub fn forward_iter<C: PartialInverseCube>() -> impl Iterator<Item = C> {
<C::Image as ClosedAxis>::ALL
.iter()
.copied()
.map(C::forward)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
AttributionAxis, AttributionConfidence, AttributionCoordinates,
AttributionNameKindCoordinates, AttributionRule, AttributionSourceKindCoordinates,
ConfigSourceKind, ConfigTierKind, DiffLineKind, EnvMetadataTagKind,
ErrorLocalizationCoordinates, FieldPathLocalization, FigmentNameTagKind, FigmentSourceKind,
Format, FormatCoordinates, FormatProvenance, SecretBackendKind, SecretRefShape,
ShikumiErrorKind, WatchEventClass,
secret_client::{SecretClientKind, SecretErrorKind, SecretOperation},
};
macro_rules! for_each_closed_axis_primitive {
($cb:ident) => {
$cb!(Format);
$cb!(FormatProvenance);
$cb!(ConfigSourceKind);
$cb!(FigmentSourceKind);
$cb!(ShikumiErrorKind);
$cb!(FieldPathLocalization);
$cb!(AttributionRule);
$cb!(AttributionConfidence);
$cb!(AttributionAxis);
$cb!(PartitionFace);
$cb!(ConfigTierKind);
$cb!(WatchEventClass);
$cb!(FigmentNameTagKind);
$cb!(EnvMetadataTagKind);
$cb!(SecretBackendKind);
$cb!(SecretRefShape);
$cb!(SecretOperation);
$cb!(SecretErrorKind);
$cb!(SecretClientKind);
$cb!(DiffLineKind);
};
}
macro_rules! for_each_product_cube {
($cb:ident) => {
$cb!(FormatCoordinates);
$cb!(AttributionCoordinates);
$cb!(ErrorLocalizationCoordinates);
$cb!(AttributionSourceKindCoordinates);
$cb!(AttributionNameKindCoordinates);
};
}
macro_rules! for_each_partial_inverse_cube {
($cb:ident) => {
$cb!(FormatCoordinates);
$cb!(AttributionCoordinates);
};
}
macro_rules! for_each_closed_axis_implementor {
($cb:ident) => {
for_each_closed_axis_primitive!($cb);
for_each_product_cube!($cb);
};
}
macro_rules! for_each_closed_axis_label_implementor {
($cb:ident) => {
$cb!(PartitionFace);
$cb!(ConfigTierKind);
$cb!(Format);
$cb!(FormatProvenance);
$cb!(ConfigSourceKind);
$cb!(FigmentSourceKind);
$cb!(AttributionConfidence);
$cb!(AttributionAxis);
$cb!(ShikumiErrorKind);
$cb!(FieldPathLocalization);
$cb!(AttributionRule);
$cb!(WatchEventClass);
$cb!(FigmentNameTagKind);
$cb!(EnvMetadataTagKind);
$cb!(SecretBackendKind);
$cb!(SecretRefShape);
$cb!(SecretOperation);
$cb!(SecretErrorKind);
$cb!(SecretClientKind);
$cb!(DiffLineKind);
};
}
fn assert_trait_matches_inherent<A>(inherent_all: &[A])
where
A: ClosedAxis + std::fmt::Debug,
{
assert_eq!(
<A as ClosedAxis>::ALL.len(),
inherent_all.len(),
"trait ALL cardinality must equal inherent ALL cardinality",
);
for (i, (trait_cell, inherent_cell)) in <A as ClosedAxis>::ALL
.iter()
.zip(inherent_all.iter())
.enumerate()
{
assert_eq!(
trait_cell, inherent_cell,
"trait ALL[{i}] must equal inherent ALL[{i}]",
);
}
}
fn assert_trait_is_realizable_matches_inherent<C>(
inherent_all: &[C],
inherent_is_realizable: fn(C) -> bool,
) where
C: ProductCube + std::fmt::Debug,
{
for cell in inherent_all.iter().copied() {
assert_eq!(
ProductCube::is_realizable(cell),
inherent_is_realizable(cell),
"cell {cell:?}: trait is_realizable must equal inherent is_realizable",
);
}
}
#[test]
fn format_coordinates_trait_all_matches_inherent_all() {
assert_trait_matches_inherent::<FormatCoordinates>(FormatCoordinates::ALL);
}
#[test]
fn attribution_coordinates_trait_all_matches_inherent_all() {
assert_trait_matches_inherent::<AttributionCoordinates>(AttributionCoordinates::ALL);
}
#[test]
fn error_localization_coordinates_trait_all_matches_inherent_all() {
assert_trait_matches_inherent::<ErrorLocalizationCoordinates>(
ErrorLocalizationCoordinates::ALL,
);
}
#[test]
fn attribution_source_kind_coordinates_trait_all_matches_inherent_all() {
assert_trait_matches_inherent::<AttributionSourceKindCoordinates>(
AttributionSourceKindCoordinates::ALL,
);
}
#[test]
fn format_coordinates_trait_is_realizable_matches_inherent() {
assert_trait_is_realizable_matches_inherent::<FormatCoordinates>(
FormatCoordinates::ALL,
FormatCoordinates::is_realizable,
);
}
#[test]
fn attribution_coordinates_trait_is_realizable_matches_inherent() {
assert_trait_is_realizable_matches_inherent::<AttributionCoordinates>(
AttributionCoordinates::ALL,
AttributionCoordinates::is_realizable,
);
}
#[test]
fn error_localization_coordinates_trait_is_realizable_matches_inherent() {
assert_trait_is_realizable_matches_inherent::<ErrorLocalizationCoordinates>(
ErrorLocalizationCoordinates::ALL,
ErrorLocalizationCoordinates::is_realizable,
);
}
#[test]
fn attribution_source_kind_coordinates_trait_is_realizable_matches_inherent() {
assert_trait_is_realizable_matches_inherent::<AttributionSourceKindCoordinates>(
AttributionSourceKindCoordinates::ALL,
AttributionSourceKindCoordinates::is_realizable,
);
}
fn assert_realizable_partitions_all<C>(inherent_all: &[C])
where
C: ProductCube + std::fmt::Debug,
{
let realizable: Vec<C> = realizable_iter::<C>().collect();
let unrealizable: Vec<C> = unrealizable_iter::<C>().collect();
assert_eq!(
realizable.len() + unrealizable.len(),
inherent_all.len(),
"realizable + unrealizable cardinalities must sum to ALL cardinality",
);
for cell in &realizable {
assert!(
ProductCube::is_realizable(*cell),
"cell {cell:?} in realizable_iter must satisfy is_realizable",
);
assert!(
!unrealizable.contains(cell),
"cell {cell:?} must not appear in both partitions",
);
}
for cell in &unrealizable {
assert!(
!ProductCube::is_realizable(*cell),
"cell {cell:?} in unrealizable_iter must not satisfy is_realizable",
);
}
}
#[test]
fn format_coordinates_generic_realizable_partitions_all() {
assert_realizable_partitions_all::<FormatCoordinates>(FormatCoordinates::ALL);
}
#[test]
fn attribution_coordinates_generic_realizable_partitions_all() {
assert_realizable_partitions_all::<AttributionCoordinates>(AttributionCoordinates::ALL);
}
#[test]
fn error_localization_coordinates_generic_realizable_partitions_all() {
assert_realizable_partitions_all::<ErrorLocalizationCoordinates>(
ErrorLocalizationCoordinates::ALL,
);
}
#[test]
fn attribution_source_kind_coordinates_generic_realizable_partitions_all() {
assert_realizable_partitions_all::<AttributionSourceKindCoordinates>(
AttributionSourceKindCoordinates::ALL,
);
}
#[test]
fn format_coordinates_generic_realizable_count_is_four() {
assert_eq!(realizable_count::<FormatCoordinates>(), 4);
assert_eq!(unrealizable_count::<FormatCoordinates>(), 4);
assert_eq!(
realizable_count::<FormatCoordinates>() + unrealizable_count::<FormatCoordinates>(),
FormatCoordinates::ALL.len(),
);
}
#[test]
fn attribution_coordinates_generic_realizable_count_is_five() {
assert_eq!(realizable_count::<AttributionCoordinates>(), 5);
assert_eq!(unrealizable_count::<AttributionCoordinates>(), 7);
assert_eq!(
realizable_count::<AttributionCoordinates>()
+ unrealizable_count::<AttributionCoordinates>(),
AttributionCoordinates::ALL.len(),
);
}
#[test]
fn error_localization_coordinates_generic_realizable_count_is_eight() {
assert_eq!(realizable_count::<ErrorLocalizationCoordinates>(), 8);
assert_eq!(unrealizable_count::<ErrorLocalizationCoordinates>(), 10);
assert_eq!(
realizable_count::<ErrorLocalizationCoordinates>()
+ unrealizable_count::<ErrorLocalizationCoordinates>(),
ErrorLocalizationCoordinates::ALL.len(),
);
}
#[test]
fn attribution_source_kind_coordinates_generic_realizable_count_is_two() {
assert_eq!(realizable_count::<AttributionSourceKindCoordinates>(), 2);
assert_eq!(unrealizable_count::<AttributionSourceKindCoordinates>(), 7);
assert_eq!(
realizable_count::<AttributionSourceKindCoordinates>()
+ unrealizable_count::<AttributionSourceKindCoordinates>(),
AttributionSourceKindCoordinates::ALL.len(),
);
}
#[test]
fn all_four_product_cubes_have_nonempty_all() {
assert!(!<FormatCoordinates as ClosedAxis>::ALL.is_empty());
assert!(!<AttributionCoordinates as ClosedAxis>::ALL.is_empty());
assert!(!<ErrorLocalizationCoordinates as ClosedAxis>::ALL.is_empty());
assert!(!<AttributionSourceKindCoordinates as ClosedAxis>::ALL.is_empty());
}
fn assert_partial_inverse_some_iff_is_realizable<C>()
where
C: PartialInverseCube + std::fmt::Debug,
{
for cell in <C as ClosedAxis>::ALL.iter().copied() {
assert_eq!(
cell.invert().is_some(),
ProductCube::is_realizable(cell),
"cell {cell:?}: invert().is_some() must equal is_realizable()",
);
}
}
#[test]
fn format_coordinates_partial_inverse_some_iff_is_realizable() {
assert_partial_inverse_some_iff_is_realizable::<FormatCoordinates>();
}
#[test]
fn attribution_coordinates_partial_inverse_some_iff_is_realizable() {
assert_partial_inverse_some_iff_is_realizable::<AttributionCoordinates>();
}
#[test]
fn format_coordinates_realizable_images_cardinality_matches_realizable_count() {
assert_eq!(
realizable_images::<FormatCoordinates>().count(),
realizable_count::<FormatCoordinates>(),
);
}
#[test]
fn attribution_coordinates_realizable_images_cardinality_matches_realizable_count() {
assert_eq!(
realizable_images::<AttributionCoordinates>().count(),
realizable_count::<AttributionCoordinates>(),
);
}
fn assert_realizable_images_equals_image_all<C>()
where
C: PartialInverseCube + std::fmt::Debug,
{
use std::collections::HashSet;
let images: HashSet<C::Image> = realizable_images::<C>().collect();
let expected: HashSet<C::Image> = <C::Image as ClosedAxis>::ALL.iter().copied().collect();
assert_eq!(
images, expected,
"realizable_images must equal Image::ALL as a set",
);
}
#[test]
fn format_coordinates_realizable_images_equals_format_all() {
assert_realizable_images_equals_image_all::<FormatCoordinates>();
}
#[test]
fn attribution_coordinates_realizable_images_equals_rule_all() {
assert_realizable_images_equals_image_all::<AttributionCoordinates>();
}
fn assert_axis_iter_matches_trait_all<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let iter_collected: Vec<A> = axis_iter::<A>().collect();
let all_collected: Vec<A> = <A as ClosedAxis>::ALL.to_vec();
assert_eq!(
iter_collected.len(),
all_collected.len(),
"axis_iter cardinality must equal trait ALL cardinality",
);
for (i, (from_iter, from_all)) in
iter_collected.iter().zip(all_collected.iter()).enumerate()
{
assert_eq!(
from_iter, from_all,
"axis_iter[{i}] must equal trait ALL[{i}]",
);
}
}
fn assert_axis_cardinality_matches_trait_all<A>(expected: usize)
where
A: ClosedAxis + std::fmt::Debug,
{
assert_eq!(
axis_cardinality::<A>(),
<A as ClosedAxis>::ALL.len(),
"axis_cardinality must equal trait ALL slice length",
);
assert_eq!(
axis_cardinality::<A>(),
expected,
"axis_cardinality must equal today's pinned variant count",
);
}
#[test]
fn format_trait_all_matches_inherent_all() {
assert_trait_matches_inherent::<Format>(Format::ALL);
}
#[test]
fn format_provenance_trait_all_matches_inherent_all() {
assert_trait_matches_inherent::<FormatProvenance>(FormatProvenance::ALL);
}
#[test]
fn config_source_kind_trait_all_matches_inherent_all() {
assert_trait_matches_inherent::<ConfigSourceKind>(ConfigSourceKind::ALL);
}
#[test]
fn figment_source_kind_trait_all_matches_inherent_all() {
assert_trait_matches_inherent::<FigmentSourceKind>(FigmentSourceKind::ALL);
}
#[test]
fn shikumi_error_kind_trait_all_matches_inherent_all() {
assert_trait_matches_inherent::<ShikumiErrorKind>(ShikumiErrorKind::ALL);
}
#[test]
fn field_path_localization_trait_all_matches_inherent_all() {
assert_trait_matches_inherent::<FieldPathLocalization>(FieldPathLocalization::ALL);
}
#[test]
fn attribution_rule_trait_all_matches_inherent_all() {
assert_trait_matches_inherent::<AttributionRule>(AttributionRule::ALL);
}
#[test]
fn attribution_confidence_trait_all_matches_inherent_all() {
assert_trait_matches_inherent::<AttributionConfidence>(AttributionConfidence::ALL);
}
#[test]
fn attribution_axis_trait_all_matches_inherent_all() {
assert_trait_matches_inherent::<AttributionAxis>(AttributionAxis::ALL);
}
#[test]
fn partition_face_trait_all_matches_inherent_all() {
assert_trait_matches_inherent::<PartitionFace>(PartitionFace::ALL);
}
#[test]
fn watch_event_class_trait_all_matches_inherent_all() {
assert_trait_matches_inherent::<WatchEventClass>(WatchEventClass::ALL);
}
#[test]
fn figment_name_tag_kind_trait_all_matches_inherent_all() {
assert_trait_matches_inherent::<FigmentNameTagKind>(FigmentNameTagKind::ALL);
}
#[test]
fn axis_iter_matches_trait_all_for_every_closed_enum_axis() {
macro_rules! check {
($ty:ident) => {
assert_axis_iter_matches_trait_all::<$ty>();
};
}
for_each_closed_axis_primitive!(check);
}
#[test]
fn axis_iter_matches_trait_all_for_every_product_cube() {
macro_rules! check {
($ty:ident) => {
assert_axis_iter_matches_trait_all::<$ty>();
};
}
for_each_product_cube!(check);
}
#[test]
fn axis_cardinality_pins_todays_counts_across_twenty_implementors() {
assert_axis_cardinality_matches_trait_all::<Format>(4);
assert_axis_cardinality_matches_trait_all::<FormatProvenance>(2);
assert_axis_cardinality_matches_trait_all::<ConfigSourceKind>(3);
assert_axis_cardinality_matches_trait_all::<FigmentSourceKind>(3);
assert_axis_cardinality_matches_trait_all::<ShikumiErrorKind>(6);
assert_axis_cardinality_matches_trait_all::<FieldPathLocalization>(3);
assert_axis_cardinality_matches_trait_all::<AttributionRule>(5);
assert_axis_cardinality_matches_trait_all::<AttributionConfidence>(2);
assert_axis_cardinality_matches_trait_all::<AttributionAxis>(2);
assert_axis_cardinality_matches_trait_all::<PartitionFace>(2);
assert_axis_cardinality_matches_trait_all::<ConfigTierKind>(4);
assert_axis_cardinality_matches_trait_all::<WatchEventClass>(3);
assert_axis_cardinality_matches_trait_all::<FigmentNameTagKind>(2);
assert_axis_cardinality_matches_trait_all::<EnvMetadataTagKind>(2);
assert_axis_cardinality_matches_trait_all::<DiffLineKind>(3);
assert_axis_cardinality_matches_trait_all::<FormatCoordinates>(8);
assert_axis_cardinality_matches_trait_all::<AttributionCoordinates>(12);
assert_axis_cardinality_matches_trait_all::<ErrorLocalizationCoordinates>(18);
assert_axis_cardinality_matches_trait_all::<AttributionSourceKindCoordinates>(9);
assert_axis_cardinality_matches_trait_all::<AttributionNameKindCoordinates>(6);
}
fn assert_axis_ordinal_round_trips<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
for (i, value) in A::ALL.iter().copied().enumerate() {
let ordinal = axis_ordinal::<A>(value);
assert_eq!(
ordinal, i,
"axis_ordinal(A::ALL[{i}]) must equal {i}; got {ordinal}",
);
assert_eq!(
A::ALL[ordinal],
value,
"A::ALL[axis_ordinal(v)] must equal v for v = A::ALL[{i}]",
);
}
}
fn assert_axis_ordinal_injective<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
use std::collections::HashSet;
let ordinals: HashSet<usize> = axis_iter::<A>().map(axis_ordinal::<A>).collect();
let expected: HashSet<usize> = (0..axis_cardinality::<A>()).collect();
assert_eq!(
ordinals, expected,
"axis_ordinal image over A::ALL must equal 0..axis_cardinality::<A>() as a set",
);
}
#[test]
fn axis_ordinal_round_trips_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_axis_ordinal_round_trips::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_ordinal_injective_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_axis_ordinal_injective::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_ordinal_pins_first_and_last_positions_for_every_implementor() {
fn assert_endpoints<A: ClosedAxis>() {
let n = axis_cardinality::<A>();
assert!(n > 0, "ClosedAxis::ALL must be non-empty");
assert_eq!(axis_ordinal::<A>(A::ALL[0]), 0);
assert_eq!(axis_ordinal::<A>(A::ALL[n - 1]), n - 1);
}
macro_rules! check {
($ty:ident) => {
assert_endpoints::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
fn assert_axis_at_round_trips_value_side<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
for value in axis_iter::<A>() {
let ordinal = axis_ordinal::<A>(value);
assert_eq!(
axis_at::<A>(ordinal),
Some(value),
"axis_at(axis_ordinal({value:?})) must equal Some({value:?})",
);
}
}
fn assert_axis_at_round_trips_ordinal_side<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
for i in 0..axis_cardinality::<A>() {
let recovered = axis_at::<A>(i).map(axis_ordinal::<A>);
assert_eq!(
recovered,
Some(i),
"axis_at({i}).map(axis_ordinal) must equal Some({i}) for in-range ordinal",
);
}
}
fn assert_axis_at_none_on_out_of_range<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let n = axis_cardinality::<A>();
for i in [n, n + 1, n + 7, usize::MAX] {
assert!(
axis_at::<A>(i).is_none(),
"axis_at({i}) must be None for ordinal >= axis_cardinality (n = {n})",
);
}
}
#[test]
fn axis_at_round_trips_value_side_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_axis_at_round_trips_value_side::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_at_round_trips_ordinal_side_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_axis_at_round_trips_ordinal_side::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_at_returns_none_on_out_of_range_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_axis_at_none_on_out_of_range::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_at_agrees_with_axis_iter_pointwise_for_every_implementor() {
fn assert_pointwise<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
for (i, from_iter) in axis_iter::<A>().enumerate() {
assert_eq!(
axis_at::<A>(i),
Some(from_iter),
"axis_at({i}) must equal Some(axis_iter[{i}]) = Some({from_iter:?})",
);
}
}
macro_rules! check {
($ty:ident) => {
assert_pointwise::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_iter_for_product_cube_agrees_with_realizable_plus_unrealizable() {
fn assert_axis_iter_recovers_partition<C>()
where
C: ProductCube + std::fmt::Debug,
{
use std::collections::HashSet;
let whole: HashSet<C> = axis_iter::<C>().collect();
let realizable: HashSet<C> = realizable_iter::<C>().collect();
let unrealizable: HashSet<C> = unrealizable_iter::<C>().collect();
assert!(
realizable.is_disjoint(&unrealizable),
"realizable and unrealizable halves must be disjoint",
);
let recovered: HashSet<C> = realizable.union(&unrealizable).copied().collect();
assert_eq!(
whole, recovered,
"axis_iter must equal realizable ∪ unrealizable as a set",
);
}
macro_rules! check {
($ty:ident) => {
assert_axis_iter_recovers_partition::<$ty>();
};
}
for_each_product_cube!(check);
}
fn assert_realizable_ordinal_some_iff_is_realizable<C>()
where
C: ProductCube + std::fmt::Debug,
{
for cell in axis_iter::<C>() {
assert_eq!(
realizable_ordinal::<C>(cell).is_some(),
ProductCube::is_realizable(cell),
"cell {cell:?}: realizable_ordinal(...).is_some() must equal is_realizable(...)",
);
}
}
fn assert_realizable_at_some_iff_in_realizable_prefix<C>()
where
C: ProductCube + std::fmt::Debug,
{
let n = realizable_count::<C>();
for i in 0..n {
assert!(
realizable_at::<C>(i).is_some(),
"realizable_at({i}) must be Some for in-range ordinal (n = {n})",
);
}
for i in [n, n + 1, n + 7, usize::MAX] {
assert!(
realizable_at::<C>(i).is_none(),
"realizable_at({i}) must be None for ordinal >= realizable_count (n = {n})",
);
}
}
fn assert_realizable_round_trips_cell_side<C>()
where
C: ProductCube + std::fmt::Debug,
{
for cell in realizable_iter::<C>() {
let ordinal = realizable_ordinal::<C>(cell)
.expect("realizable_iter must yield only ordinal-Some cells");
assert_eq!(
realizable_at::<C>(ordinal),
Some(cell),
"realizable_at(realizable_ordinal({cell:?}).unwrap()) must equal Some({cell:?})",
);
}
}
fn assert_realizable_round_trips_ordinal_side<C>()
where
C: ProductCube + std::fmt::Debug,
{
for i in 0..realizable_count::<C>() {
let recovered = realizable_at::<C>(i).and_then(realizable_ordinal::<C>);
assert_eq!(
recovered,
Some(i),
"realizable_at({i}).and_then(realizable_ordinal) must equal Some({i})",
);
}
}
fn assert_realizable_at_image_is_realizable<C>()
where
C: ProductCube + std::fmt::Debug,
{
for i in 0..realizable_count::<C>() {
let cell = realizable_at::<C>(i)
.expect("in-range realizable_at must yield Some by partiality invariant");
assert!(
ProductCube::is_realizable(cell),
"realizable_at({i}) = {cell:?} must satisfy is_realizable",
);
}
}
fn assert_realizable_ordinal_image_equals_realizable_prefix<C>()
where
C: ProductCube + std::fmt::Debug,
{
use std::collections::HashSet;
let ordinals: HashSet<usize> = realizable_iter::<C>()
.map(|c| {
realizable_ordinal::<C>(c)
.expect("realizable_iter must yield only ordinal-Some cells")
})
.collect();
let expected: HashSet<usize> = (0..realizable_count::<C>()).collect();
assert_eq!(
ordinals, expected,
"realizable_ordinal image over realizable_iter must equal 0..realizable_count as a set",
);
}
#[test]
fn realizable_ordinal_some_iff_is_realizable() {
macro_rules! check {
($ty:ident) => {
assert_realizable_ordinal_some_iff_is_realizable::<$ty>();
};
}
for_each_product_cube!(check);
}
#[test]
fn realizable_at_some_iff_in_realizable_prefix() {
macro_rules! check {
($ty:ident) => {
assert_realizable_at_some_iff_in_realizable_prefix::<$ty>();
};
}
for_each_product_cube!(check);
}
#[test]
fn realizable_round_trips_cell_side() {
macro_rules! check {
($ty:ident) => {
assert_realizable_round_trips_cell_side::<$ty>();
};
}
for_each_product_cube!(check);
}
#[test]
fn realizable_round_trips_ordinal_side() {
macro_rules! check {
($ty:ident) => {
assert_realizable_round_trips_ordinal_side::<$ty>();
};
}
for_each_product_cube!(check);
}
#[test]
fn realizable_at_image_is_realizable() {
macro_rules! check {
($ty:ident) => {
assert_realizable_at_image_is_realizable::<$ty>();
};
}
for_each_product_cube!(check);
}
#[test]
fn realizable_ordinal_image_equals_realizable_prefix() {
macro_rules! check {
($ty:ident) => {
assert_realizable_ordinal_image_equals_realizable_prefix::<$ty>();
};
}
for_each_product_cube!(check);
}
#[test]
fn realizable_ordinal_pins_format_coordinates_dense_ordinals() {
use crate::{FormatCoordinates, FormatProvenance};
let yaml_figment = FormatCoordinates {
format: Format::Yaml,
provenance: FormatProvenance::FigmentBuiltin,
};
let toml_figment = FormatCoordinates {
format: Format::Toml,
provenance: FormatProvenance::FigmentBuiltin,
};
let lisp_shikumi = FormatCoordinates {
format: Format::Lisp,
provenance: FormatProvenance::ShikumiBuilt,
};
let nix_shikumi = FormatCoordinates {
format: Format::Nix,
provenance: FormatProvenance::ShikumiBuilt,
};
assert_eq!(
realizable_ordinal::<FormatCoordinates>(yaml_figment),
Some(0)
);
assert_eq!(
realizable_ordinal::<FormatCoordinates>(toml_figment),
Some(1)
);
assert_eq!(
realizable_ordinal::<FormatCoordinates>(lisp_shikumi),
Some(2)
);
assert_eq!(
realizable_ordinal::<FormatCoordinates>(nix_shikumi),
Some(3)
);
assert_eq!(axis_ordinal::<FormatCoordinates>(yaml_figment), 0);
assert_eq!(axis_ordinal::<FormatCoordinates>(toml_figment), 2);
assert_eq!(axis_ordinal::<FormatCoordinates>(lisp_shikumi), 5);
assert_eq!(axis_ordinal::<FormatCoordinates>(nix_shikumi), 7);
let yaml_shikumi = FormatCoordinates {
format: Format::Yaml,
provenance: FormatProvenance::ShikumiBuilt,
};
assert_eq!(realizable_ordinal::<FormatCoordinates>(yaml_shikumi), None);
assert_eq!(axis_ordinal::<FormatCoordinates>(yaml_shikumi), 1);
}
fn assert_unrealizable_ordinal_some_iff_not_is_realizable<C>()
where
C: ProductCube + std::fmt::Debug,
{
for cell in axis_iter::<C>() {
assert_eq!(
unrealizable_ordinal::<C>(cell).is_some(),
!ProductCube::is_realizable(cell),
"cell {cell:?}: unrealizable_ordinal(...).is_some() must equal !is_realizable(...)",
);
}
}
fn assert_unrealizable_at_some_iff_in_unrealizable_prefix<C>()
where
C: ProductCube + std::fmt::Debug,
{
let n = unrealizable_count::<C>();
for i in 0..n {
assert!(
unrealizable_at::<C>(i).is_some(),
"unrealizable_at({i}) must be Some for in-range ordinal (n = {n})",
);
}
for i in [n, n + 1, n + 7, usize::MAX] {
assert!(
unrealizable_at::<C>(i).is_none(),
"unrealizable_at({i}) must be None for ordinal >= unrealizable_count (n = {n})",
);
}
}
fn assert_unrealizable_round_trips_cell_side<C>()
where
C: ProductCube + std::fmt::Debug,
{
for cell in unrealizable_iter::<C>() {
let ordinal = unrealizable_ordinal::<C>(cell)
.expect("unrealizable_iter must yield only ordinal-Some cells");
assert_eq!(
unrealizable_at::<C>(ordinal),
Some(cell),
"unrealizable_at(unrealizable_ordinal({cell:?}).unwrap()) must equal Some({cell:?})",
);
}
}
fn assert_unrealizable_round_trips_ordinal_side<C>()
where
C: ProductCube + std::fmt::Debug,
{
for i in 0..unrealizable_count::<C>() {
let recovered = unrealizable_at::<C>(i).and_then(unrealizable_ordinal::<C>);
assert_eq!(
recovered,
Some(i),
"unrealizable_at({i}).and_then(unrealizable_ordinal) must equal Some({i})",
);
}
}
fn assert_unrealizable_at_image_is_unrealizable<C>()
where
C: ProductCube + std::fmt::Debug,
{
for i in 0..unrealizable_count::<C>() {
let cell = unrealizable_at::<C>(i)
.expect("in-range unrealizable_at must yield Some by partiality invariant");
assert!(
!ProductCube::is_realizable(cell),
"unrealizable_at({i}) = {cell:?} must NOT satisfy is_realizable",
);
}
}
fn assert_unrealizable_ordinal_image_equals_unrealizable_prefix<C>()
where
C: ProductCube + std::fmt::Debug,
{
use std::collections::HashSet;
let ordinals: HashSet<usize> = unrealizable_iter::<C>()
.map(|c| {
unrealizable_ordinal::<C>(c)
.expect("unrealizable_iter must yield only ordinal-Some cells")
})
.collect();
let expected: HashSet<usize> = (0..unrealizable_count::<C>()).collect();
assert_eq!(
ordinals, expected,
"unrealizable_ordinal image over unrealizable_iter must equal 0..unrealizable_count as a set",
);
}
#[test]
fn unrealizable_ordinal_some_iff_not_is_realizable() {
macro_rules! check {
($ty:ident) => {
assert_unrealizable_ordinal_some_iff_not_is_realizable::<$ty>();
};
}
for_each_product_cube!(check);
}
#[test]
fn unrealizable_at_some_iff_in_unrealizable_prefix() {
macro_rules! check {
($ty:ident) => {
assert_unrealizable_at_some_iff_in_unrealizable_prefix::<$ty>();
};
}
for_each_product_cube!(check);
}
#[test]
fn unrealizable_round_trips_cell_side() {
macro_rules! check {
($ty:ident) => {
assert_unrealizable_round_trips_cell_side::<$ty>();
};
}
for_each_product_cube!(check);
}
#[test]
fn unrealizable_round_trips_ordinal_side() {
macro_rules! check {
($ty:ident) => {
assert_unrealizable_round_trips_ordinal_side::<$ty>();
};
}
for_each_product_cube!(check);
}
#[test]
fn unrealizable_at_image_is_unrealizable() {
macro_rules! check {
($ty:ident) => {
assert_unrealizable_at_image_is_unrealizable::<$ty>();
};
}
for_each_product_cube!(check);
}
#[test]
fn unrealizable_ordinal_image_equals_unrealizable_prefix() {
macro_rules! check {
($ty:ident) => {
assert_unrealizable_ordinal_image_equals_unrealizable_prefix::<$ty>();
};
}
for_each_product_cube!(check);
}
#[test]
fn realizable_and_unrealizable_ordinals_partition_cube() {
fn assert_xor_complementary<C>()
where
C: ProductCube + std::fmt::Debug,
{
for cell in axis_iter::<C>() {
let r = realizable_ordinal::<C>(cell).is_some();
let u = unrealizable_ordinal::<C>(cell).is_some();
assert!(
r ^ u,
"cell {cell:?}: exactly one of realizable_ordinal / unrealizable_ordinal \
must be Some (got realizable={r}, unrealizable={u})",
);
}
}
macro_rules! check {
($ty:ident) => {
assert_xor_complementary::<$ty>();
};
}
for_each_product_cube!(check);
}
#[test]
fn unrealizable_ordinal_pins_format_coordinates_dense_ordinals() {
use crate::{FormatCoordinates, FormatProvenance};
let yaml_shikumi = FormatCoordinates {
format: Format::Yaml,
provenance: FormatProvenance::ShikumiBuilt,
};
let toml_shikumi = FormatCoordinates {
format: Format::Toml,
provenance: FormatProvenance::ShikumiBuilt,
};
let lisp_figment = FormatCoordinates {
format: Format::Lisp,
provenance: FormatProvenance::FigmentBuiltin,
};
let nix_figment = FormatCoordinates {
format: Format::Nix,
provenance: FormatProvenance::FigmentBuiltin,
};
assert_eq!(
unrealizable_ordinal::<FormatCoordinates>(yaml_shikumi),
Some(0),
);
assert_eq!(
unrealizable_ordinal::<FormatCoordinates>(toml_shikumi),
Some(1),
);
assert_eq!(
unrealizable_ordinal::<FormatCoordinates>(lisp_figment),
Some(2),
);
assert_eq!(
unrealizable_ordinal::<FormatCoordinates>(nix_figment),
Some(3),
);
assert_eq!(axis_ordinal::<FormatCoordinates>(yaml_shikumi), 1);
assert_eq!(axis_ordinal::<FormatCoordinates>(toml_shikumi), 3);
assert_eq!(axis_ordinal::<FormatCoordinates>(lisp_figment), 4);
assert_eq!(axis_ordinal::<FormatCoordinates>(nix_figment), 6);
let yaml_figment = FormatCoordinates {
format: Format::Yaml,
provenance: FormatProvenance::FigmentBuiltin,
};
assert_eq!(
unrealizable_ordinal::<FormatCoordinates>(yaml_figment),
None,
);
}
fn assert_partition_ordinal_variant_agrees_with_is_realizable<C>()
where
C: ProductCube + std::fmt::Debug,
{
for cell in axis_iter::<C>() {
match partition_ordinal::<C>(cell) {
PartitionOrdinal::Realizable(_) => assert!(
ProductCube::is_realizable(cell),
"cell {cell:?}: partition_ordinal returned Realizable but is_realizable is false",
),
PartitionOrdinal::Unrealizable(_) => assert!(
!ProductCube::is_realizable(cell),
"cell {cell:?}: partition_ordinal returned Unrealizable but is_realizable is true",
),
}
}
}
fn assert_partition_ordinal_inner_matches_dense_ordinal<C>()
where
C: ProductCube + std::fmt::Debug,
{
for cell in axis_iter::<C>() {
match partition_ordinal::<C>(cell) {
PartitionOrdinal::Realizable(i) => assert_eq!(
Some(i),
realizable_ordinal::<C>(cell),
"cell {cell:?}: PartitionOrdinal::Realizable({i}) must equal realizable_ordinal",
),
PartitionOrdinal::Unrealizable(i) => assert_eq!(
Some(i),
unrealizable_ordinal::<C>(cell),
"cell {cell:?}: PartitionOrdinal::Unrealizable({i}) must equal unrealizable_ordinal",
),
}
}
}
fn assert_partition_ordinal_round_trips_cell_side<C>()
where
C: ProductCube + std::fmt::Debug,
{
for cell in axis_iter::<C>() {
let p = partition_ordinal::<C>(cell);
assert_eq!(
at_partition_ordinal::<C>(p),
Some(cell),
"cell {cell:?}: at_partition_ordinal(partition_ordinal(cell)) must equal Some(cell)",
);
}
}
fn assert_partition_ordinal_round_trips_ordinal_side<C>()
where
C: ProductCube + std::fmt::Debug,
{
for i in 0..realizable_count::<C>() {
let p = PartitionOrdinal::Realizable(i);
let recovered = at_partition_ordinal::<C>(p).map(partition_ordinal::<C>);
assert_eq!(
recovered,
Some(p),
"at_partition_ordinal(Realizable({i})).map(partition_ordinal) must equal Some(Realizable({i}))",
);
}
for i in 0..unrealizable_count::<C>() {
let p = PartitionOrdinal::Unrealizable(i);
let recovered = at_partition_ordinal::<C>(p).map(partition_ordinal::<C>);
assert_eq!(
recovered,
Some(p),
"at_partition_ordinal(Unrealizable({i})).map(partition_ordinal) must equal Some(Unrealizable({i}))",
);
}
}
fn assert_at_partition_ordinal_none_on_out_of_range<C>()
where
C: ProductCube + std::fmt::Debug,
{
let nr = realizable_count::<C>();
for i in [nr, nr + 1, nr + 7, usize::MAX] {
assert!(
at_partition_ordinal::<C>(PartitionOrdinal::Realizable(i)).is_none(),
"at_partition_ordinal(Realizable({i})) must be None for ordinal >= realizable_count (n = {nr})",
);
}
let nu = unrealizable_count::<C>();
for i in [nu, nu + 1, nu + 7, usize::MAX] {
assert!(
at_partition_ordinal::<C>(PartitionOrdinal::Unrealizable(i)).is_none(),
"at_partition_ordinal(Unrealizable({i})) must be None for ordinal >= unrealizable_count (n = {nu})",
);
}
}
fn assert_at_partition_ordinal_image_matches_variant_tag<C>()
where
C: ProductCube + std::fmt::Debug,
{
for i in 0..realizable_count::<C>() {
let cell = at_partition_ordinal::<C>(PartitionOrdinal::Realizable(i))
.expect("in-range Realizable(i) must yield Some by partiality invariant");
assert!(
ProductCube::is_realizable(cell),
"at_partition_ordinal(Realizable({i})) = {cell:?} must satisfy is_realizable",
);
}
for i in 0..unrealizable_count::<C>() {
let cell = at_partition_ordinal::<C>(PartitionOrdinal::Unrealizable(i))
.expect("in-range Unrealizable(i) must yield Some by partiality invariant");
assert!(
!ProductCube::is_realizable(cell),
"at_partition_ordinal(Unrealizable({i})) = {cell:?} must NOT satisfy is_realizable",
);
}
}
#[test]
fn partition_ordinal_variant_agrees_with_is_realizable() {
macro_rules! check {
($ty:ident) => {
assert_partition_ordinal_variant_agrees_with_is_realizable::<$ty>();
};
}
for_each_product_cube!(check);
}
#[test]
fn partition_ordinal_inner_matches_dense_ordinal() {
macro_rules! check {
($ty:ident) => {
assert_partition_ordinal_inner_matches_dense_ordinal::<$ty>();
};
}
for_each_product_cube!(check);
}
#[test]
fn partition_ordinal_round_trips_cell_side() {
macro_rules! check {
($ty:ident) => {
assert_partition_ordinal_round_trips_cell_side::<$ty>();
};
}
for_each_product_cube!(check);
}
#[test]
fn partition_ordinal_round_trips_ordinal_side() {
macro_rules! check {
($ty:ident) => {
assert_partition_ordinal_round_trips_ordinal_side::<$ty>();
};
}
for_each_product_cube!(check);
}
#[test]
fn at_partition_ordinal_none_on_out_of_range() {
macro_rules! check {
($ty:ident) => {
assert_at_partition_ordinal_none_on_out_of_range::<$ty>();
};
}
for_each_product_cube!(check);
}
#[test]
fn at_partition_ordinal_image_matches_variant_tag() {
macro_rules! check {
($ty:ident) => {
assert_at_partition_ordinal_image_matches_variant_tag::<$ty>();
};
}
for_each_product_cube!(check);
}
#[test]
fn partition_ordinal_pins_format_coordinates_dense_ordinals() {
use crate::{FormatCoordinates, FormatProvenance};
let yaml_figment = FormatCoordinates {
format: Format::Yaml,
provenance: FormatProvenance::FigmentBuiltin,
};
let yaml_shikumi = FormatCoordinates {
format: Format::Yaml,
provenance: FormatProvenance::ShikumiBuilt,
};
let nix_shikumi = FormatCoordinates {
format: Format::Nix,
provenance: FormatProvenance::ShikumiBuilt,
};
let nix_figment = FormatCoordinates {
format: Format::Nix,
provenance: FormatProvenance::FigmentBuiltin,
};
assert_eq!(
partition_ordinal::<FormatCoordinates>(yaml_figment),
PartitionOrdinal::Realizable(0),
);
assert_eq!(
partition_ordinal::<FormatCoordinates>(nix_shikumi),
PartitionOrdinal::Realizable(3),
);
assert_eq!(
partition_ordinal::<FormatCoordinates>(yaml_shikumi),
PartitionOrdinal::Unrealizable(0),
);
assert_eq!(
partition_ordinal::<FormatCoordinates>(nix_figment),
PartitionOrdinal::Unrealizable(3),
);
assert_eq!(
at_partition_ordinal::<FormatCoordinates>(PartitionOrdinal::Realizable(0)),
Some(yaml_figment),
);
assert_eq!(
at_partition_ordinal::<FormatCoordinates>(PartitionOrdinal::Unrealizable(3)),
Some(nix_figment),
);
}
#[test]
fn partition_ordinal_ord_matches_face_then_face_ordinal() {
use std::cmp::Ordering;
let sample = [
PartitionOrdinal::Realizable(0),
PartitionOrdinal::Realizable(1),
PartitionOrdinal::Realizable(usize::MAX),
PartitionOrdinal::Unrealizable(0),
PartitionOrdinal::Unrealizable(1),
PartitionOrdinal::Unrealizable(usize::MAX),
];
assert!(PartitionOrdinal::Realizable(0) < PartitionOrdinal::Realizable(1));
assert!(PartitionOrdinal::Realizable(1) < PartitionOrdinal::Realizable(usize::MAX));
assert!(PartitionOrdinal::Unrealizable(0) < PartitionOrdinal::Unrealizable(1));
assert!(PartitionOrdinal::Unrealizable(1) < PartitionOrdinal::Unrealizable(usize::MAX));
assert!(PartitionOrdinal::Realizable(usize::MAX) < PartitionOrdinal::Unrealizable(0));
assert!(PartitionOrdinal::Realizable(0) < PartitionOrdinal::Unrealizable(0));
assert!(PartitionOrdinal::Realizable(0) < PartitionOrdinal::Unrealizable(usize::MAX));
for a in sample {
for b in sample {
let lex = a
.face()
.cmp(&b.face())
.then(a.face_ordinal().cmp(&b.face_ordinal()));
assert_eq!(
a.cmp(&b),
lex,
"PartitionOrdinal Ord must equal (face, face_ordinal) lex: \
{a:?} vs {b:?} got {:?} expected {lex:?}",
a.cmp(&b),
);
if a == b {
assert_eq!(a.cmp(&b), Ordering::Equal);
}
}
}
let mut sorted = sample;
sorted.sort();
assert_eq!(sorted, sample);
}
#[test]
fn partition_face_all_has_two_entries() {
assert_eq!(PartitionFace::ALL.len(), 2);
assert_eq!(PartitionFace::ALL[0], PartitionFace::Realizable);
assert_eq!(PartitionFace::ALL[1], PartitionFace::Unrealizable);
}
#[test]
fn partition_face_is_realizable_matches_variant() {
assert!(PartitionFace::Realizable.is_realizable());
assert!(!PartitionFace::Unrealizable.is_realizable());
}
fn assert_partition_ordinal_face_agrees_with_is_realizable<C>()
where
C: ProductCube + std::fmt::Debug,
{
for cell in <C as ClosedAxis>::ALL.iter().copied() {
let face = partition_ordinal::<C>(cell).face();
assert_eq!(
face.is_realizable(),
ProductCube::is_realizable(cell),
"cell {cell:?}: partition_ordinal(cell).face().is_realizable() must equal \
ProductCube::is_realizable(cell)",
);
let expected = if ProductCube::is_realizable(cell) {
PartitionFace::Realizable
} else {
PartitionFace::Unrealizable
};
assert_eq!(
face, expected,
"cell {cell:?}: face tag must match is_realizable-derived expected face",
);
}
}
#[test]
fn partition_ordinal_face_agrees_with_is_realizable() {
macro_rules! check {
($ty:ident) => {
assert_partition_ordinal_face_agrees_with_is_realizable::<$ty>();
};
}
for_each_product_cube!(check);
}
fn assert_partition_ordinal_face_ordinal_round_trips<C>()
where
C: ProductCube + std::fmt::Debug,
{
for i in 0..realizable_count::<C>() {
let p = PartitionOrdinal::Realizable(i);
assert_eq!(
p.face(),
PartitionFace::Realizable,
"Realizable({i}): face must be Realizable",
);
assert_eq!(
p.face_ordinal(),
i,
"Realizable({i}): face_ordinal must be {i}"
);
}
for i in 0..unrealizable_count::<C>() {
let p = PartitionOrdinal::Unrealizable(i);
assert_eq!(
p.face(),
PartitionFace::Unrealizable,
"Unrealizable({i}): face must be Unrealizable",
);
assert_eq!(
p.face_ordinal(),
i,
"Unrealizable({i}): face_ordinal must be {i}",
);
}
}
#[test]
fn partition_ordinal_face_ordinal_round_trips_across_every_cube() {
macro_rules! check {
($ty:ident) => {
assert_partition_ordinal_face_ordinal_round_trips::<$ty>();
};
}
for_each_product_cube!(check);
}
fn assert_partition_ordinal_recomposes_from_face_and_ordinal<C>()
where
C: ProductCube + std::fmt::Debug,
{
for cell in <C as ClosedAxis>::ALL.iter().copied() {
let p = partition_ordinal::<C>(cell);
let face = p.face();
let ordinal = p.face_ordinal();
let recombined = match face {
PartitionFace::Realizable => PartitionOrdinal::Realizable(ordinal),
PartitionFace::Unrealizable => PartitionOrdinal::Unrealizable(ordinal),
};
assert_eq!(
recombined, p,
"cell {cell:?}: recombining (face, face_ordinal) must equal partition_ordinal(cell)",
);
}
}
#[test]
fn partition_ordinal_recomposes_from_face_and_ordinal_across_every_cube() {
macro_rules! check {
($ty:ident) => {
assert_partition_ordinal_recomposes_from_face_and_ordinal::<$ty>();
};
}
for_each_product_cube!(check);
}
fn assert_forward_always_lands_on_realizable<C>()
where
C: PartialInverseCube + std::fmt::Debug,
{
for image in <C::Image as ClosedAxis>::ALL.iter().copied() {
let cell = C::forward(image);
assert!(
ProductCube::is_realizable(cell),
"image {image:?}: forward must land on a realizable cell of the cube",
);
}
}
fn assert_round_trip_invert_after_forward<C>()
where
C: PartialInverseCube + std::fmt::Debug,
{
for image in <C::Image as ClosedAxis>::ALL.iter().copied() {
let recovered = C::forward(image).invert();
assert_eq!(
recovered,
Some(image),
"image {image:?}: invert(forward(image)) must equal Some(image)",
);
}
}
fn assert_round_trip_forward_after_invert<C>()
where
C: PartialInverseCube + std::fmt::Debug,
{
for cell in realizable_iter::<C>() {
let image = cell
.invert()
.expect("realizable_iter must yield only invert-Some cells");
let recovered = C::forward(image);
assert_eq!(
recovered, cell,
"cell {cell:?}: forward(invert(cell).unwrap()) must equal cell",
);
}
}
#[test]
fn format_coordinates_forward_always_lands_on_realizable_cell() {
assert_forward_always_lands_on_realizable::<FormatCoordinates>();
}
#[test]
fn attribution_coordinates_forward_always_lands_on_realizable_cell() {
assert_forward_always_lands_on_realizable::<AttributionCoordinates>();
}
#[test]
fn format_coordinates_round_trip_invert_after_forward() {
assert_round_trip_invert_after_forward::<FormatCoordinates>();
}
#[test]
fn attribution_coordinates_round_trip_invert_after_forward() {
assert_round_trip_invert_after_forward::<AttributionCoordinates>();
}
#[test]
fn format_coordinates_round_trip_forward_after_invert() {
assert_round_trip_forward_after_invert::<FormatCoordinates>();
}
#[test]
fn attribution_coordinates_round_trip_forward_after_invert() {
assert_round_trip_forward_after_invert::<AttributionCoordinates>();
}
#[test]
fn forward_image_of_image_all_equals_realizable_iter() {
fn assert_forward_image_equals_realizable<C>()
where
C: PartialInverseCube + std::fmt::Debug,
{
use std::collections::HashSet;
let from_forward: HashSet<C> = forward_iter::<C>().collect();
let from_realizable: HashSet<C> = realizable_iter::<C>().collect();
assert_eq!(
from_forward, from_realizable,
"forward_iter must equal realizable_iter as a set",
);
}
macro_rules! check {
($ty:ident) => {
assert_forward_image_equals_realizable::<$ty>();
};
}
for_each_partial_inverse_cube!(check);
}
#[test]
fn forward_iter_cardinality_equals_image_all_cardinality() {
fn assert_cardinalities_agree<C: PartialInverseCube>() {
assert_eq!(
forward_iter::<C>().count(),
axis_cardinality::<<C as PartialInverseCube>::Image>(),
);
assert_eq!(forward_iter::<C>().count(), realizable_count::<C>());
}
macro_rules! check {
($ty:ident) => {
assert_cardinalities_agree::<$ty>();
};
}
for_each_partial_inverse_cube!(check);
}
#[test]
fn for_each_closed_axis_primitive_macro_covers_twenty_axes() {
let mut count = 0usize;
macro_rules! tally {
($ty:ident) => {
count += 1;
};
}
for_each_closed_axis_primitive!(tally);
assert_eq!(
count, 20,
"for_each_closed_axis_primitive! must expand to twenty arms",
);
}
#[test]
fn for_each_product_cube_macro_covers_five_cubes() {
let mut count = 0usize;
macro_rules! tally {
($ty:ident) => {
count += 1;
};
}
for_each_product_cube!(tally);
assert_eq!(count, 5, "for_each_product_cube! must expand to five arms");
}
#[test]
fn for_each_partial_inverse_cube_macro_covers_two_cubes() {
let mut count = 0usize;
macro_rules! tally {
($ty:ident) => {
count += 1;
};
}
for_each_partial_inverse_cube!(tally);
assert_eq!(
count, 2,
"for_each_partial_inverse_cube! must expand to two arms",
);
}
#[test]
fn for_each_closed_axis_implementor_macro_covers_twenty_five_types() {
let mut count = 0usize;
macro_rules! tally {
($ty:ident) => {
count += 1;
};
}
for_each_closed_axis_implementor!(tally);
assert_eq!(
count, 25,
"for_each_closed_axis_implementor! must expand to twenty-five arms (20 axes + 5 cubes)",
);
}
#[test]
fn for_each_closed_axis_implementor_expands_to_distinct_closed_axis_types() {
fn axis_card<A: ClosedAxis>() -> usize {
axis_cardinality::<A>()
}
let mut total = 0usize;
macro_rules! add {
($ty:ident) => {
total += axis_card::<$ty>();
};
}
for_each_closed_axis_implementor!(add);
assert_eq!(
total, 127,
"macro must emit each implementor exactly once \
(today's axis_cardinality checksum is 127)",
);
}
fn assert_round_trips_through_canonical_str<L>()
where
L: ClosedAxisLabel + std::fmt::Debug,
{
for value in L::ALL.iter().copied() {
let rendered = value.as_str();
let parsed = <L as ClosedAxisLabel>::from_canonical_str(rendered);
assert_eq!(
parsed,
Some(value),
"round-trip failed for {value:?}: as_str={rendered:?} did not parse back to Some({value:?})",
);
}
}
fn assert_round_trips_case_insensitively<L>()
where
L: ClosedAxisLabel + std::fmt::Debug,
{
for value in L::ALL.iter().copied() {
let rendered_upper = value.as_str().to_ascii_uppercase();
let parsed = <L as ClosedAxisLabel>::from_canonical_str(&rendered_upper);
assert_eq!(
parsed,
Some(value),
"case-insensitive round-trip failed for {value:?}: uppercase {rendered_upper:?} did not parse back",
);
}
}
fn assert_labels_pairwise_distinct<L>()
where
L: ClosedAxisLabel + std::fmt::Debug,
{
let labels: Vec<(L, &'static str)> =
L::ALL.iter().copied().map(|v| (v, v.as_str())).collect();
for (i, (a, label_a)) in labels.iter().enumerate() {
for (b, label_b) in labels.iter().skip(i + 1) {
assert_ne!(
label_a, label_b,
"distinct values {a:?} and {b:?} must have distinct labels (both produced {label_a:?})",
);
}
}
}
fn assert_labels_nonempty<L>()
where
L: ClosedAxisLabel + std::fmt::Debug,
{
for value in L::ALL.iter().copied() {
let rendered = value.as_str();
assert!(
!rendered.is_empty(),
"as_str must never return empty for {value:?}",
);
}
}
fn assert_rejects_empty_string<L>()
where
L: ClosedAxisLabel + std::fmt::Debug,
{
assert_eq!(
<L as ClosedAxisLabel>::from_canonical_str(""),
None,
"from_canonical_str(\"\") must be None",
);
}
#[test]
fn closed_axis_label_round_trips_for_every_implementor() {
macro_rules! check {
($ty:ident) => {
assert_round_trips_through_canonical_str::<$ty>();
};
}
for_each_closed_axis_label_implementor!(check);
}
#[test]
fn closed_axis_label_round_trips_case_insensitively_for_every_implementor() {
macro_rules! check {
($ty:ident) => {
assert_round_trips_case_insensitively::<$ty>();
};
}
for_each_closed_axis_label_implementor!(check);
}
#[test]
fn closed_axis_label_as_str_distinct_for_every_implementor() {
macro_rules! check {
($ty:ident) => {
assert_labels_pairwise_distinct::<$ty>();
};
}
for_each_closed_axis_label_implementor!(check);
}
#[test]
fn closed_axis_label_as_str_nonempty_for_every_implementor() {
macro_rules! check {
($ty:ident) => {
assert_labels_nonempty::<$ty>();
};
}
for_each_closed_axis_label_implementor!(check);
}
#[test]
fn closed_axis_label_rejects_empty_string_for_every_implementor() {
macro_rules! check {
($ty:ident) => {
assert_rejects_empty_string::<$ty>();
};
}
for_each_closed_axis_label_implementor!(check);
}
#[test]
fn for_each_closed_axis_label_implementor_macro_covers_twenty_implementors() {
let mut count = 0usize;
macro_rules! tally {
($ty:ident) => {
count += 1;
};
}
for_each_closed_axis_label_implementor!(tally);
assert_eq!(
count, 20,
"for_each_closed_axis_label_implementor! must expand to twenty arms",
);
}
#[test]
fn for_each_closed_axis_label_implementor_expands_to_distinct_label_axes() {
fn axis_card<L: ClosedAxisLabel>() -> usize {
axis_cardinality::<L>()
}
let mut total = 0usize;
macro_rules! add {
($ty:ident) => {
total += axis_card::<$ty>();
};
}
for_each_closed_axis_label_implementor!(add);
assert_eq!(
total, 74,
"macro must emit each ClosedAxisLabel implementor exactly once \
(today's axis_cardinality checksum is 74: \
PartitionFace=2 + ConfigTierKind=4 + Format=4 + FormatProvenance=2 \
+ ConfigSourceKind=3 + FigmentSourceKind=3 + AttributionConfidence=2 \
+ AttributionAxis=2 + ShikumiErrorKind=6 + FieldPathLocalization=3 \
+ AttributionRule=5 + WatchEventClass=3 + FigmentNameTagKind=2 \
+ EnvMetadataTagKind=2 + SecretBackendKind=8 + SecretRefShape=2 \
+ SecretOperation=6 + SecretErrorKind=5 + SecretClientKind=7 \
+ DiffLineKind=3)",
);
}
#[test]
fn partition_face_as_str_yields_canonical_lowercase_names() {
assert_eq!(PartitionFace::Realizable.as_str(), "realizable");
assert_eq!(PartitionFace::Unrealizable.as_str(), "unrealizable");
}
#[test]
fn partition_face_display_matches_as_str_for_every_variant() {
for &v in PartitionFace::ALL {
assert_eq!(
format!("{v}"),
v.as_str(),
"Display must equal as_str for {v:?}",
);
}
}
#[test]
fn partition_face_from_str_round_trips_through_display() {
for &v in PartitionFace::ALL {
let rendered = v.to_string();
let parsed: PartitionFace = rendered.parse().unwrap();
assert_eq!(parsed, v, "Display / FromStr round-trip for {v:?}");
}
}
#[test]
fn partition_face_from_str_is_case_insensitive() {
for &v in PartitionFace::ALL {
let parsed: PartitionFace = v
.as_str()
.to_ascii_uppercase()
.parse()
.unwrap_or_else(|e| panic!("uppercase parse for {v:?} failed: {e}"));
assert_eq!(parsed, v, "case-insensitive parse must recover {v:?}");
}
}
#[test]
fn partition_face_from_str_rejects_unknown_label_with_label_verbatim() {
let sentinel = "__shikumi_unknown_partition_face_sentinel__";
match sentinel.parse::<PartitionFace>() {
Err(e) => {
assert_eq!(e.label, sentinel);
let rendered = format!("{e}");
assert!(
rendered.contains(sentinel),
"Display impl must carry the unknown sentinel verbatim, got: {rendered}",
);
}
Ok(other) => panic!("unknown label must reject, got {other:?}"),
}
}
#[test]
fn partition_face_from_str_rejects_empty_string() {
assert!("".parse::<PartitionFace>().is_err());
}
#[test]
fn partition_face_serde_yaml_round_trips_over_every_variant() {
for &v in PartitionFace::ALL {
let yaml = serde_yaml::to_string(&v).unwrap();
let parsed: PartitionFace = serde_yaml::from_str(&yaml)
.unwrap_or_else(|e| panic!("YAML round-trip for {v:?} failed: {e}"));
assert_eq!(
parsed, v,
"serde YAML round-trip must be identity for {v:?}"
);
}
}
#[test]
fn partition_face_serde_json_round_trips_over_every_variant() {
for &v in PartitionFace::ALL {
let json = serde_json::to_string(&v).unwrap();
assert_eq!(
json,
format!("\"{}\"", v.as_str()),
"JSON emission for {v:?} must be the quoted canonical label",
);
let parsed: PartitionFace = serde_json::from_str(&json).unwrap_or_else(|e| {
panic!("JSON round-trip for {v:?} failed: {e}\n json: {json}")
});
assert_eq!(
parsed, v,
"serde JSON round-trip must be identity for {v:?}"
);
}
}
#[test]
fn partition_face_serde_yaml_is_case_insensitive() {
for &v in PartitionFace::ALL {
let upper = v.as_str().to_ascii_uppercase();
let yaml = format!("\"{upper}\"\n");
let parsed: PartitionFace = serde_yaml::from_str(&yaml).unwrap_or_else(|e| {
panic!("uppercase YAML scalar for {v:?} must deserialize: {e}\n yaml: {yaml:?}")
});
assert_eq!(parsed, v);
}
}
#[test]
fn partition_face_serde_yaml_unknown_label_error_carries_label_verbatim() {
let sentinel = "__shikumi_unknown_partition_face_sentinel__";
let yaml = format!("\"{sentinel}\"\n");
let result: Result<PartitionFace, _> = serde_yaml::from_str(&yaml);
match result {
Err(e) => {
let rendered = format!("{e}");
assert!(
rendered.contains(sentinel),
"serde YAML error must carry the unknown sentinel verbatim, got: {rendered}",
);
}
Ok(other) => panic!("YAML carrying unknown label must reject, got {other:?}"),
}
}
#[test]
fn partition_face_ord_matches_declaration_order() {
assert!(PartitionFace::Realizable < PartitionFace::Unrealizable);
for window in PartitionFace::ALL.windows(2) {
assert!(
window[0] < window[1],
"Ord must be strictly monotone in PartitionFace::ALL position: \
{:?} < {:?} failed",
window[0],
window[1],
);
}
}
fn partition_ordinal_sample() -> [PartitionOrdinal; 8] {
[
PartitionOrdinal::Realizable(0),
PartitionOrdinal::Realizable(1),
PartitionOrdinal::Realizable(42),
PartitionOrdinal::Realizable(usize::MAX),
PartitionOrdinal::Unrealizable(0),
PartitionOrdinal::Unrealizable(1),
PartitionOrdinal::Unrealizable(7),
PartitionOrdinal::Unrealizable(usize::MAX),
]
}
#[test]
fn partition_ordinal_display_renders_canonical_face_colon_ordinal() {
assert_eq!(
format!("{}", PartitionOrdinal::Realizable(0)),
"realizable:0",
);
assert_eq!(
format!("{}", PartitionOrdinal::Realizable(42)),
"realizable:42",
);
assert_eq!(
format!("{}", PartitionOrdinal::Unrealizable(0)),
"unrealizable:0",
);
assert_eq!(
format!("{}", PartitionOrdinal::Unrealizable(7)),
"unrealizable:7",
);
}
#[test]
fn partition_ordinal_display_matches_face_and_face_ordinal_for_every_sample() {
for v in partition_ordinal_sample() {
assert_eq!(
format!("{v}"),
format!("{}:{}", v.face().as_str(), v.face_ordinal()),
"Display must equal `<face>:<face_ordinal>` for {v:?}",
);
}
}
#[test]
fn partition_ordinal_from_str_round_trips_through_display() {
for v in partition_ordinal_sample() {
let rendered = v.to_string();
let parsed: PartitionOrdinal = rendered.parse().unwrap_or_else(|e| {
panic!("FromStr round-trip for {v:?} failed: {e}\n rendered: {rendered:?}")
});
assert_eq!(parsed, v, "Display / FromStr round-trip for {v:?}");
}
}
#[test]
fn partition_ordinal_from_str_face_half_is_case_insensitive() {
for v in partition_ordinal_sample() {
let face_upper = v.face().as_str().to_ascii_uppercase();
let upper = format!("{face_upper}:{}", v.face_ordinal());
let parsed: PartitionOrdinal = upper
.parse()
.unwrap_or_else(|e| panic!("uppercase parse for {v:?} failed: {e}"));
assert_eq!(
parsed, v,
"case-insensitive face half must recover {v:?} (from {upper:?})",
);
}
}
#[test]
fn partition_ordinal_from_str_rejects_missing_separator() {
let sentinel = "realizable42";
match sentinel.parse::<PartitionOrdinal>() {
Err(ParsePartitionOrdinalError::MissingSeparator { input }) => {
assert_eq!(input, sentinel);
let rendered = format!(
"{}",
ParsePartitionOrdinalError::MissingSeparator {
input: input.clone(),
},
);
assert!(
rendered.contains(sentinel),
"Display impl must carry the offending input verbatim, got: {rendered}",
);
}
other => {
panic!("missing-separator input must reject as MissingSeparator, got {other:?}")
}
}
}
#[test]
fn partition_ordinal_from_str_rejects_unknown_face_with_label_verbatim() {
let sentinel_face = "__shikumi_unknown_partition_face_sentinel__";
let input = format!("{sentinel_face}:42");
match input.parse::<PartitionOrdinal>() {
Err(ParsePartitionOrdinalError::UnknownFace { label }) => {
assert_eq!(label, sentinel_face);
let rendered = format!(
"{}",
ParsePartitionOrdinalError::UnknownFace {
label: label.clone(),
},
);
assert!(
rendered.contains(sentinel_face),
"Display impl must carry the unknown face label verbatim, got: {rendered}",
);
}
other => panic!("unknown face label must reject as UnknownFace, got {other:?}"),
}
}
#[test]
fn partition_ordinal_from_str_rejects_malformed_ordinal_with_substring_and_source() {
use std::error::Error as _;
let sentinel_ord = "not_a_number";
let input = format!("realizable:{sentinel_ord}");
match input.parse::<PartitionOrdinal>() {
Err(e @ ParsePartitionOrdinalError::MalformedOrdinal { .. }) => {
if let ParsePartitionOrdinalError::MalformedOrdinal { ordinal, .. } = &e {
assert_eq!(ordinal, sentinel_ord);
}
let rendered = format!("{e}");
assert!(
rendered.contains(sentinel_ord),
"Display impl must carry the malformed ordinal verbatim, got: {rendered}",
);
assert!(
e.source().is_some(),
"MalformedOrdinal must thread ParseIntError through Error::source",
);
}
other => panic!("malformed ordinal must reject as MalformedOrdinal, got {other:?}",),
}
}
#[test]
fn partition_ordinal_from_str_rejects_empty_string() {
match "".parse::<PartitionOrdinal>() {
Err(ParsePartitionOrdinalError::MissingSeparator { input }) => {
assert_eq!(input, "");
}
other => panic!("empty string must reject as MissingSeparator, got {other:?}"),
}
}
#[test]
fn partition_ordinal_from_str_uses_leftmost_colon_only() {
let input = "realizable:1:2";
match input.parse::<PartitionOrdinal>() {
Err(ParsePartitionOrdinalError::MalformedOrdinal { ordinal, .. }) => {
assert_eq!(ordinal, "1:2");
}
other => panic!(
"leftmost-`:`-only split must forward the rest into the ordinal half, got {other:?}",
),
}
}
#[test]
fn partition_ordinal_serde_yaml_round_trips_over_sample() {
for v in partition_ordinal_sample() {
let yaml = serde_yaml::to_string(&v).unwrap();
let parsed: PartitionOrdinal = serde_yaml::from_str(&yaml).unwrap_or_else(|e| {
panic!("YAML round-trip for {v:?} failed: {e}\n yaml: {yaml:?}")
});
assert_eq!(
parsed, v,
"serde YAML round-trip must be identity for {v:?}",
);
}
}
#[test]
fn partition_ordinal_serde_json_round_trips_over_sample() {
for v in partition_ordinal_sample() {
let json = serde_json::to_string(&v).unwrap();
assert_eq!(
json,
format!("\"{v}\""),
"JSON emission for {v:?} must be the quoted canonical scalar",
);
let parsed: PartitionOrdinal = serde_json::from_str(&json).unwrap_or_else(|e| {
panic!("JSON round-trip for {v:?} failed: {e}\n json: {json}")
});
assert_eq!(
parsed, v,
"serde JSON round-trip must be identity for {v:?}",
);
}
}
#[test]
fn partition_ordinal_serde_yaml_face_is_case_insensitive() {
for v in partition_ordinal_sample() {
let face_upper = v.face().as_str().to_ascii_uppercase();
let yaml = format!("\"{face_upper}:{}\"\n", v.face_ordinal());
let parsed: PartitionOrdinal = serde_yaml::from_str(&yaml).unwrap_or_else(|e| {
panic!("uppercase YAML scalar for {v:?} must deserialize: {e}\n yaml: {yaml:?}")
});
assert_eq!(parsed, v);
}
}
#[test]
fn partition_ordinal_serde_yaml_unknown_face_error_carries_label_verbatim() {
let sentinel = "__shikumi_unknown_partition_face_sentinel__";
let yaml = format!("\"{sentinel}:42\"\n");
let result: Result<PartitionOrdinal, _> = serde_yaml::from_str(&yaml);
match result {
Err(e) => {
let rendered = format!("{e}");
assert!(
rendered.contains(sentinel),
"serde YAML error must carry the unknown face label verbatim, got: {rendered}",
);
}
Ok(other) => panic!("YAML carrying unknown face must reject, got {other:?}"),
}
}
#[test]
fn partition_ordinal_serde_yaml_malformed_ordinal_error_carries_substring_verbatim() {
let sentinel = "not_a_number";
let yaml = format!("\"realizable:{sentinel}\"\n");
let result: Result<PartitionOrdinal, _> = serde_yaml::from_str(&yaml);
match result {
Err(e) => {
let rendered = format!("{e}");
assert!(
rendered.contains(sentinel),
"serde YAML error must carry the malformed ordinal verbatim, got: {rendered}",
);
}
Ok(other) => panic!("YAML carrying malformed ordinal must reject, got {other:?}"),
}
}
fn assert_axis_label_free_fn_matches_trait<L>()
where
L: ClosedAxisLabel + std::fmt::Debug,
{
for value in L::ALL.iter().copied() {
assert_eq!(
axis_label(value),
value.as_str(),
"axis_label free fn must agree with ClosedAxisLabel::as_str for {value:?}",
);
}
}
fn assert_axis_from_label_free_fn_matches_trait<L>()
where
L: ClosedAxisLabel + std::fmt::Debug,
{
for value in L::ALL.iter().copied() {
let rendered = value.as_str();
assert_eq!(
axis_from_label::<L>(rendered),
<L as ClosedAxisLabel>::from_canonical_str(rendered),
"axis_from_label must agree with from_canonical_str on {rendered:?}",
);
let upper = rendered.to_ascii_uppercase();
assert_eq!(
axis_from_label::<L>(&upper),
<L as ClosedAxisLabel>::from_canonical_str(&upper),
"axis_from_label must agree with from_canonical_str on {upper:?}",
);
}
for probe in ["", "\u{0}not-a-canonical-label\u{0}"] {
assert_eq!(
axis_from_label::<L>(probe),
<L as ClosedAxisLabel>::from_canonical_str(probe),
"axis_from_label must agree with from_canonical_str on non-label {probe:?}",
);
}
}
fn assert_axis_label_free_fn_round_trips<L>()
where
L: ClosedAxisLabel + std::fmt::Debug,
{
for value in L::ALL.iter().copied() {
assert_eq!(
axis_from_label::<L>(axis_label(value)),
Some(value),
"free-fn round-trip failed for {value:?}",
);
}
}
#[test]
fn axis_label_free_fn_matches_trait_as_str_for_every_implementor() {
macro_rules! check {
($ty:ident) => {
assert_axis_label_free_fn_matches_trait::<$ty>();
};
}
for_each_closed_axis_label_implementor!(check);
}
#[test]
fn axis_from_label_free_fn_matches_trait_for_every_implementor() {
macro_rules! check {
($ty:ident) => {
assert_axis_from_label_free_fn_matches_trait::<$ty>();
};
}
for_each_closed_axis_label_implementor!(check);
}
#[test]
fn axis_label_free_fn_round_trips_for_every_implementor() {
macro_rules! check {
($ty:ident) => {
assert_axis_label_free_fn_round_trips::<$ty>();
};
}
for_each_closed_axis_label_implementor!(check);
}
fn assert_empty_histogram_is_zero_on_every_cell<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let hist = AxisHistogram::<A>::empty();
assert_eq!(
hist.total(),
0,
"empty histogram total must be 0 for axis {}",
std::any::type_name::<A>(),
);
assert!(
hist.is_empty(),
"empty histogram is_empty must be true for axis {}",
std::any::type_name::<A>(),
);
for value in axis_iter::<A>() {
assert_eq!(
hist.count(value),
0,
"empty histogram count must be 0 for cell {value:?} on axis {}",
std::any::type_name::<A>(),
);
}
assert_eq!(
hist.nonzero().count(),
0,
"empty histogram must have no nonzero cells on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_singleton_histogram_pins_observed_cell<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
for observed in axis_iter::<A>() {
let hist: AxisHistogram<A> = std::iter::once(observed).collect();
assert_eq!(hist.total(), 1, "singleton total must equal 1");
assert!(!hist.is_empty(), "singleton must not be empty");
for cell in axis_iter::<A>() {
let expected = usize::from(cell == observed);
assert_eq!(
hist.count(cell),
expected,
"singleton on {observed:?}: count({cell:?}) must be {expected}",
);
}
let nonzero: Vec<(A, usize)> = hist.nonzero().collect();
assert_eq!(nonzero, vec![(observed, 1)], "singleton nonzero set");
}
}
fn assert_from_cell_equals_iter_once_collect<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
for observed in axis_iter::<A>() {
let via_from: AxisHistogram<A> = AxisHistogram::from(observed);
let via_into: AxisHistogram<A> = observed.into();
let via_iter_once: AxisHistogram<A> = std::iter::once(observed).collect();
assert_eq!(
via_from,
via_iter_once,
"AxisHistogram::from(cell) must equal iter::once(cell).collect() on {observed:?} \
for axis {}",
std::any::type_name::<A>(),
);
assert_eq!(
via_into,
via_iter_once,
"Into::into for cell must equal iter::once(cell).collect() on {observed:?} \
for axis {}",
std::any::type_name::<A>(),
);
assert_eq!(
via_from.total(),
1,
"From-built singleton total must equal 1 on axis {}",
std::any::type_name::<A>(),
);
assert_eq!(
via_from.count(observed),
1,
"From-built singleton must observe the lifted cell on {observed:?}",
);
assert_eq!(
via_from.dominant_cell(),
Some(observed),
"From-built singleton dominant_cell must be the lifted cell on {observed:?}",
);
}
}
fn assert_from_empty_array_equals_empty<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let via_from: AxisHistogram<A> = AxisHistogram::from([] as [A; 0]);
let via_empty: AxisHistogram<A> = AxisHistogram::empty();
let via_default: AxisHistogram<A> = AxisHistogram::default();
assert_eq!(
via_from,
via_empty,
"AxisHistogram::from([] as [A; 0]) must equal AxisHistogram::empty() on axis {}",
std::any::type_name::<A>(),
);
assert_eq!(
via_from,
via_default,
"AxisHistogram::from([] as [A; 0]) must equal AxisHistogram::default() on axis {}",
std::any::type_name::<A>(),
);
assert_eq!(
via_from.total(),
0,
"AxisHistogram::from([] as [A; 0]).total() must equal 0 on axis {}",
std::any::type_name::<A>(),
);
assert!(
via_from.is_empty(),
"AxisHistogram::from([] as [A; 0]) must be is_empty on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_from_cell_array_equals_from_iter_collect<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
for observed in axis_iter::<A>() {
let via_from_array: AxisHistogram<A> = AxisHistogram::from([observed]);
let via_from_cell: AxisHistogram<A> = AxisHistogram::from(observed);
let via_iter_once: AxisHistogram<A> = std::iter::once(observed).collect();
let via_into_iter_collect: AxisHistogram<A> = [observed].into_iter().collect();
assert_eq!(
via_from_array,
via_from_cell,
"AxisHistogram::from([cell]) must equal AxisHistogram::from(cell) on {observed:?} \
for axis {}",
std::any::type_name::<A>(),
);
assert_eq!(
via_from_array,
via_iter_once,
"AxisHistogram::from([cell]) must equal iter::once(cell).collect() on {observed:?} \
for axis {}",
std::any::type_name::<A>(),
);
assert_eq!(
via_from_array,
via_into_iter_collect,
"AxisHistogram::from([cell]) must equal [cell].into_iter().collect() on \
{observed:?} for axis {}",
std::any::type_name::<A>(),
);
assert_eq!(
via_from_array.total(),
1,
"AxisHistogram::from([cell]).total() must equal 1 on axis {}",
std::any::type_name::<A>(),
);
assert_eq!(
via_from_array.count(observed),
1,
"AxisHistogram::from([cell]) must observe the lifted cell on {observed:?}",
);
}
}
fn assert_all_observed_once_yields_uniform_histogram<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let hist: AxisHistogram<A> = axis_iter::<A>().collect();
assert_eq!(
hist.total(),
axis_cardinality::<A>(),
"axis-cover histogram total must equal axis_cardinality on {}",
std::any::type_name::<A>(),
);
for cell in axis_iter::<A>() {
assert_eq!(hist.count(cell), 1, "every cell must be 1 in axis-cover");
}
assert_eq!(
hist.nonzero().count(),
axis_cardinality::<A>(),
"every cell nonzero in axis-cover",
);
}
fn assert_iter_matches_axis_iter_pointwise<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let hist = AxisHistogram::<A>::empty();
let values_via_hist: Vec<A> = hist.iter().map(|(v, _)| v).collect();
let values_via_axis: Vec<A> = axis_iter::<A>().collect();
assert_eq!(
values_via_hist,
values_via_axis,
"AxisHistogram::iter value sequence must equal axis_iter on {}",
std::any::type_name::<A>(),
);
}
fn assert_total_equals_input_length<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let input: Vec<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
let expected = input.len();
let hist = axis_histogram(input);
assert_eq!(
hist.total(),
expected,
"axis_histogram total must equal input length on {}",
std::any::type_name::<A>(),
);
for cell in axis_iter::<A>() {
assert_eq!(hist.count(cell), 2, "every cell observed twice");
}
}
fn assert_merge_is_pointwise_sum<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let lhs: AxisHistogram<A> = axis_iter::<A>().collect();
let rhs: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
let merged = lhs.clone().merge(&rhs);
for cell in axis_iter::<A>() {
assert_eq!(
merged.count(cell),
lhs.count(cell) + rhs.count(cell),
"merge must be pointwise sum on {cell:?} for axis {}",
std::any::type_name::<A>(),
);
}
assert_eq!(
merged.total(),
lhs.total() + rhs.total(),
"merged total equals sum of totals on {}",
std::any::type_name::<A>(),
);
let id_right = lhs.clone().merge(&AxisHistogram::<A>::empty());
assert_eq!(
id_right,
lhs,
"empty is right identity under merge on {}",
std::any::type_name::<A>(),
);
let id_left = AxisHistogram::<A>::empty().merge(&lhs);
assert_eq!(
id_left,
lhs,
"empty is left identity under merge on {}",
std::any::type_name::<A>(),
);
}
#[test]
fn axis_histogram_empty_is_zero_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_empty_histogram_is_zero_on_every_cell::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_singleton_pins_observed_cell_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_singleton_histogram_pins_observed_cell::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_from_cell_equals_iter_once_collect_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_from_cell_equals_iter_once_collect::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_from_empty_array_equals_empty_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_from_empty_array_equals_empty::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_from_cell_array_equals_from_iter_collect_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_from_cell_array_equals_from_iter_collect::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_axis_cover_is_uniform_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_all_observed_once_yields_uniform_histogram::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_iter_matches_axis_iter_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_iter_matches_axis_iter_pointwise::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_total_equals_input_length_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_total_equals_input_length::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_merge_is_monoid_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_merge_is_pointwise_sum::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_default_equals_empty() {
let via_default: AxisHistogram<DiffLineKind> = AxisHistogram::default();
let via_empty: AxisHistogram<DiffLineKind> = AxisHistogram::empty();
assert_eq!(via_default, via_empty);
}
#[test]
fn axis_histogram_free_fn_equals_collect_for_diff_line_kind() {
let input = [
DiffLineKind::Removed,
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Context,
];
let via_fn = axis_histogram::<DiffLineKind, _>(input.iter().copied());
let via_collect: AxisHistogram<DiffLineKind> = input.iter().copied().collect();
assert_eq!(via_fn, via_collect);
assert_eq!(via_fn.count(DiffLineKind::Removed), 1);
assert_eq!(via_fn.count(DiffLineKind::Added), 2);
assert_eq!(via_fn.count(DiffLineKind::Context), 1);
assert_eq!(via_fn.total(), 4);
}
#[test]
fn axis_histogram_from_cell_constructs_singleton_for_diff_line_kind() {
let via_from = AxisHistogram::from(DiffLineKind::Added);
let via_into: AxisHistogram<DiffLineKind> = DiffLineKind::Added.into();
let via_iter_once: AxisHistogram<DiffLineKind> =
std::iter::once(DiffLineKind::Added).collect();
let via_observe = {
let mut h: AxisHistogram<DiffLineKind> = AxisHistogram::empty();
h.observe(DiffLineKind::Added);
h
};
assert_eq!(via_from, via_into);
assert_eq!(via_from, via_iter_once);
assert_eq!(via_from, via_observe);
assert_eq!(via_from.count(DiffLineKind::Added), 1);
assert_eq!(via_from.count(DiffLineKind::Removed), 0);
assert_eq!(via_from.count(DiffLineKind::Context), 0);
assert_eq!(via_from.total(), 1);
assert!(!via_from.is_empty());
assert_eq!(via_from.dominant_cell(), Some(DiffLineKind::Added));
}
#[test]
fn axis_histogram_from_cell_array_constructs_pointwise_histogram_for_diff_line_kind() {
let via_from = AxisHistogram::from([
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
]);
let via_into: AxisHistogram<DiffLineKind> = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
]
.into();
let via_into_iter_collect: AxisHistogram<DiffLineKind> = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
]
.into_iter()
.collect();
let via_observe = {
let mut h: AxisHistogram<DiffLineKind> = AxisHistogram::empty();
h.observe(DiffLineKind::Added);
h.observe(DiffLineKind::Added);
h.observe(DiffLineKind::Removed);
h
};
assert_eq!(via_from, via_into);
assert_eq!(via_from, via_into_iter_collect);
assert_eq!(via_from, via_observe);
assert_eq!(via_from.count(DiffLineKind::Added), 2);
assert_eq!(via_from.count(DiffLineKind::Removed), 1);
assert_eq!(via_from.count(DiffLineKind::Context), 0);
assert_eq!(via_from.total(), 3);
assert!(!via_from.is_empty());
assert_eq!(via_from.dominant_cell(), Some(DiffLineKind::Added));
}
#[test]
fn axis_histogram_from_empty_array_equals_empty_for_diff_line_kind() {
let via_from: AxisHistogram<DiffLineKind> = AxisHistogram::from([] as [DiffLineKind; 0]);
let via_empty: AxisHistogram<DiffLineKind> = AxisHistogram::empty();
let via_default: AxisHistogram<DiffLineKind> = AxisHistogram::default();
assert_eq!(via_from, via_empty);
assert_eq!(via_from, via_default);
assert_eq!(via_from.total(), 0);
assert!(via_from.is_empty());
assert_eq!(via_from.count(DiffLineKind::Added), 0);
assert_eq!(via_from.count(DiffLineKind::Removed), 0);
assert_eq!(via_from.count(DiffLineKind::Context), 0);
}
#[test]
fn axis_histogram_observe_bumps_only_target_cell() {
let mut hist: AxisHistogram<DiffLineKind> = AxisHistogram::empty();
hist.observe(DiffLineKind::Added);
assert_eq!(hist.count(DiffLineKind::Added), 1);
assert_eq!(hist.count(DiffLineKind::Removed), 0);
assert_eq!(hist.count(DiffLineKind::Context), 0);
hist.observe(DiffLineKind::Added);
assert_eq!(hist.count(DiffLineKind::Added), 2);
hist.observe(DiffLineKind::Removed);
assert_eq!(hist.count(DiffLineKind::Added), 2);
assert_eq!(hist.count(DiffLineKind::Removed), 1);
assert_eq!(hist.total(), 3);
}
#[test]
fn axis_histogram_indexes_through_axis_ordinal() {
let hist: AxisHistogram<DiffLineKind> = axis_iter::<DiffLineKind>().collect();
for cell in axis_iter::<DiffLineKind>() {
let ordinal = axis_ordinal::<DiffLineKind>(cell);
assert!(
ordinal < axis_cardinality::<DiffLineKind>(),
"ordinal must be in-range for {cell:?}",
);
assert_eq!(hist.count(cell), 1, "cell {cell:?} count must equal 1");
}
}
fn assert_extend_empty_input_is_identity<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let mut empty = AxisHistogram::<A>::empty();
empty.extend(std::iter::empty::<A>());
assert_eq!(
empty,
AxisHistogram::<A>::empty(),
"extend(empty) on empty must be identity on axis {}",
std::any::type_name::<A>(),
);
let cover_pre: AxisHistogram<A> = axis_iter::<A>().collect();
let mut cover_post = cover_pre.clone();
cover_post.extend(std::iter::empty::<A>());
assert_eq!(
cover_post,
cover_pre,
"extend(empty) on axis-cover must be identity on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_extend_starting_empty_equals_from_iter<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let mut via_extend = AxisHistogram::<A>::empty();
via_extend.extend(axis_iter::<A>());
let via_from_iter: AxisHistogram<A> = axis_iter::<A>().collect();
assert_eq!(
via_extend,
via_from_iter,
"extend(axis_iter) on empty must equal axis_iter.collect() on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_extend_grows_total_by_input_length<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let mut hist: AxisHistogram<A> = axis_iter::<A>().collect();
let before = hist.total();
let input: Vec<A> = axis_iter::<A>().collect();
let input_len = input.len();
hist.extend(input);
assert_eq!(
hist.total(),
before + input_len,
"extend must grow total by input length on axis {}",
std::any::type_name::<A>(),
);
for cell in axis_iter::<A>() {
assert_eq!(
hist.count(cell),
2,
"cell {cell:?} must read 2 after axis-cover extended by axis-cover on {}",
std::any::type_name::<A>(),
);
}
}
fn assert_extend_chained_equals_two_step_extend<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let all: Vec<A> = axis_iter::<A>().collect();
let mid = all.len() / 2;
let head: Vec<A> = all[..mid].to_vec();
let tail: Vec<A> = all[mid..].to_vec();
let mut two_step = AxisHistogram::<A>::empty();
two_step.extend(head.iter().copied());
two_step.extend(tail.iter().copied());
let mut chained = AxisHistogram::<A>::empty();
chained.extend(head.iter().copied().chain(tail.iter().copied()));
assert_eq!(
two_step,
chained,
"two-step extend must equal chained extend on axis {}",
std::any::type_name::<A>(),
);
}
#[test]
fn axis_histogram_extend_empty_input_is_identity_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_extend_empty_input_is_identity::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_extend_starting_empty_equals_from_iter_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_extend_starting_empty_equals_from_iter::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_extend_grows_total_by_input_length_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_extend_grows_total_by_input_length::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_extend_chained_equals_two_step_extend_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_extend_chained_equals_two_step_extend::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_extend_equals_observe_loop_for_diff_line_kind() {
let inputs: [&[DiffLineKind]; 4] = [
&[],
&[DiffLineKind::Added],
&[
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Added,
],
&[
DiffLineKind::Context,
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Added,
DiffLineKind::Removed,
],
];
for input in inputs {
let mut via_extend: AxisHistogram<DiffLineKind> = AxisHistogram::empty();
via_extend.extend(input.iter().copied());
let mut via_loop: AxisHistogram<DiffLineKind> = AxisHistogram::empty();
for &v in input {
via_loop.observe(v);
}
assert_eq!(
via_extend,
via_loop,
"extend must equal observe-loop on input of length {}",
input.len(),
);
}
}
#[test]
fn axis_histogram_extend_equals_merge_of_collected_for_diff_line_kind() {
let prior = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Context,
];
let next = [
DiffLineKind::Removed,
DiffLineKind::Added,
DiffLineKind::Removed,
];
let mut via_extend: AxisHistogram<DiffLineKind> = prior.iter().copied().collect();
via_extend.extend(next.iter().copied());
let prior_hist: AxisHistogram<DiffLineKind> = prior.iter().copied().collect();
let next_hist: AxisHistogram<DiffLineKind> = next.iter().copied().collect();
let via_merge = prior_hist.merge(&next_hist);
assert_eq!(via_extend, via_merge);
assert_eq!(via_extend.count(DiffLineKind::Added), 3);
assert_eq!(via_extend.count(DiffLineKind::Removed), 2);
assert_eq!(via_extend.count(DiffLineKind::Context), 1);
assert_eq!(via_extend.total(), 6);
}
#[test]
fn axis_histogram_from_iter_lowers_to_extend_for_diff_line_kind() {
let input = [
DiffLineKind::Removed,
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Context,
DiffLineKind::Added,
];
let via_from_iter: AxisHistogram<DiffLineKind> = input.iter().copied().collect();
let mut via_default_extend: AxisHistogram<DiffLineKind> = AxisHistogram::default();
via_default_extend.extend(input.iter().copied());
assert_eq!(via_from_iter, via_default_extend);
}
fn assert_ref_extend_empty_input_is_identity<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let mut empty = AxisHistogram::<A>::empty();
empty.extend(std::iter::empty::<&A>());
assert_eq!(
empty,
AxisHistogram::<A>::empty(),
"ref-extend(empty) on empty must be identity on axis {}",
std::any::type_name::<A>(),
);
let cover_pre: AxisHistogram<A> = axis_iter::<A>().collect();
let mut cover_post = cover_pre.clone();
cover_post.extend(std::iter::empty::<&A>());
assert_eq!(
cover_post,
cover_pre,
"ref-extend(empty) on axis-cover must be identity on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_ref_from_iter_equals_owned_from_iter<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let cover: Vec<A> = axis_iter::<A>().collect();
let via_refs: AxisHistogram<A> = cover.iter().collect();
let via_owned: AxisHistogram<A> = cover.iter().copied().collect();
assert_eq!(
via_refs,
via_owned,
"FromIterator<&A> via cover.iter() must equal FromIterator<A> via cover.iter().copied() on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_ref_from_iter_lowers_to_default_plus_ref_extend<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let cover: Vec<A> = axis_iter::<A>().collect();
let via_from_iter: AxisHistogram<A> = cover.iter().collect();
let mut via_default_extend: AxisHistogram<A> = AxisHistogram::default();
via_default_extend.extend(cover.iter());
assert_eq!(
via_from_iter,
via_default_extend,
"FromIterator<&A>::from_iter must equal default + Extend<&A>::extend on axis {}",
std::any::type_name::<A>(),
);
}
#[test]
fn axis_histogram_ref_extend_empty_input_is_identity_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_ref_extend_empty_input_is_identity::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_ref_from_iter_equals_owned_from_iter_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_ref_from_iter_equals_owned_from_iter::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_ref_from_iter_lowers_to_default_plus_ref_extend_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_ref_from_iter_lowers_to_default_plus_ref_extend::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_ref_from_iter_drops_copied_adaptor_for_diff_line_kind() {
let observations = [
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Added,
DiffLineKind::Context,
DiffLineKind::Added,
];
let via_refs: AxisHistogram<DiffLineKind> = observations.iter().collect();
let via_copied: AxisHistogram<DiffLineKind> = observations.iter().copied().collect();
assert_eq!(via_refs, via_copied);
assert_eq!(via_refs.count(DiffLineKind::Added), 3);
assert_eq!(via_refs.count(DiffLineKind::Removed), 1);
assert_eq!(via_refs.count(DiffLineKind::Context), 1);
assert_eq!(via_refs.total(), 5);
let mut via_ref_extend: AxisHistogram<DiffLineKind> = AxisHistogram::empty();
via_ref_extend.extend(observations.iter());
let mut via_copied_extend: AxisHistogram<DiffLineKind> = AxisHistogram::empty();
via_copied_extend.extend(observations.iter().copied());
assert_eq!(via_ref_extend, via_copied_extend);
assert_eq!(via_ref_extend, via_refs);
}
fn assert_pair_extend_empty_input_is_identity<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let mut empty = AxisHistogram::<A>::empty();
empty.extend(std::iter::empty::<(A, usize)>());
assert_eq!(
empty,
AxisHistogram::<A>::empty(),
"pair-extend(empty) on empty must be identity on axis {}",
std::any::type_name::<A>(),
);
let cover_pre: AxisHistogram<A> = axis_iter::<A>().collect();
let mut cover_post = cover_pre.clone();
cover_post.extend(std::iter::empty::<(A, usize)>());
assert_eq!(
cover_post,
cover_pre,
"pair-extend(empty) on axis-cover must be identity on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_pair_extend_zero_count_pair_is_identity<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let cover_pre: AxisHistogram<A> = axis_iter::<A>().collect();
for cell in axis_iter::<A>() {
let mut hist = cover_pre.clone();
hist.extend(std::iter::once((cell, 0usize)));
assert_eq!(
hist,
cover_pre,
"pair-extend((cell, 0)) must be identity on cell {cell:?} of axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_pair_extend_is_additive_on_repeated_cells<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
for cell in axis_iter::<A>() {
let mut hist = AxisHistogram::<A>::empty();
hist.extend([(cell, 3usize), (cell, 4usize)]);
assert_eq!(
hist.count(cell),
7,
"pair-extend must be additive (3 + 4 = 7) on cell {cell:?} of axis {}",
std::any::type_name::<A>(),
);
assert_eq!(
hist.total(),
7,
"pair-extend total must equal sum of pair counts on cell {cell:?} of axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_pair_from_iter_lowers_to_pair_extend<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let pairs: Vec<(A, usize)> = axis_iter::<A>().map(|c| (c, 1usize)).collect();
let via_from_iter: AxisHistogram<A> = pairs.iter().copied().collect();
let mut via_default_extend = AxisHistogram::<A>::default();
via_default_extend.extend(pairs.iter().copied());
assert_eq!(
via_from_iter,
via_default_extend,
"pair-input FromIterator must lower to default + pair-input Extend on axis {}",
std::any::type_name::<A>(),
);
}
#[test]
fn axis_histogram_pair_extend_empty_input_is_identity_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_pair_extend_empty_input_is_identity::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_pair_extend_zero_count_pair_is_identity_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_pair_extend_zero_count_pair_is_identity::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_pair_extend_is_additive_on_repeated_cells_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_pair_extend_is_additive_on_repeated_cells::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_pair_from_iter_lowers_to_pair_extend_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_pair_from_iter_lowers_to_pair_extend::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_pair_from_iter_equals_observe_expansion_for_diff_line_kind() {
let pairs: [(DiffLineKind, usize); 3] = [
(DiffLineKind::Added, 12),
(DiffLineKind::Removed, 4),
(DiffLineKind::Context, 53),
];
let via_pairs: AxisHistogram<DiffLineKind> = pairs.iter().copied().collect();
let via_expansion: AxisHistogram<DiffLineKind> = pairs
.iter()
.copied()
.flat_map(|(c, n)| std::iter::repeat_n(c, n))
.collect();
assert_eq!(via_pairs, via_expansion);
assert_eq!(via_pairs.count(DiffLineKind::Added), 12);
assert_eq!(via_pairs.count(DiffLineKind::Removed), 4);
assert_eq!(via_pairs.count(DiffLineKind::Context), 53);
assert_eq!(via_pairs.total(), 69);
}
#[test]
fn axis_histogram_pair_extend_round_trips_through_iter_for_diff_line_kind() {
let prior: AxisHistogram<DiffLineKind> = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Context,
DiffLineKind::Added,
DiffLineKind::Context,
]
.into_iter()
.collect();
let pairs: Vec<(DiffLineKind, usize)> = prior.iter().collect();
let restored: AxisHistogram<DiffLineKind> = pairs.into_iter().collect();
assert_eq!(prior, restored);
}
fn assert_from_empty_pair_array_equals_empty<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let via_from: AxisHistogram<A> = AxisHistogram::from([] as [(A, usize); 0]);
let via_empty: AxisHistogram<A> = AxisHistogram::empty();
let via_default: AxisHistogram<A> = AxisHistogram::default();
assert_eq!(
via_from,
via_empty,
"AxisHistogram::from([] as [(A, usize); 0]) must equal AxisHistogram::empty() on axis {}",
std::any::type_name::<A>(),
);
assert_eq!(
via_from,
via_default,
"AxisHistogram::from([] as [(A, usize); 0]) must equal AxisHistogram::default() on axis {}",
std::any::type_name::<A>(),
);
assert_eq!(
via_from.total(),
0,
"AxisHistogram::from([] as [(A, usize); 0]).total() must equal 0 on axis {}",
std::any::type_name::<A>(),
);
assert!(
via_from.is_empty(),
"AxisHistogram::from([] as [(A, usize); 0]) must be is_empty on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_from_singleton_pair_one_equals_singleton_cell<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
for observed in axis_iter::<A>() {
let via_pair_array: AxisHistogram<A> = AxisHistogram::from([(observed, 1usize)]);
let via_cell: AxisHistogram<A> = AxisHistogram::from(observed);
let via_cell_array: AxisHistogram<A> = AxisHistogram::from([observed]);
let via_iter_once: AxisHistogram<A> = std::iter::once(observed).collect();
assert_eq!(
via_pair_array,
via_cell,
"AxisHistogram::from([(cell, 1)]) must equal AxisHistogram::from(cell) \
on {observed:?} for axis {}",
std::any::type_name::<A>(),
);
assert_eq!(
via_pair_array,
via_cell_array,
"AxisHistogram::from([(cell, 1)]) must equal AxisHistogram::from([cell]) \
on {observed:?} for axis {}",
std::any::type_name::<A>(),
);
assert_eq!(
via_pair_array,
via_iter_once,
"AxisHistogram::from([(cell, 1)]) must equal iter::once(cell).collect() \
on {observed:?} for axis {}",
std::any::type_name::<A>(),
);
assert_eq!(
via_pair_array.total(),
1,
"AxisHistogram::from([(cell, 1)]).total() must equal 1 on axis {}",
std::any::type_name::<A>(),
);
assert_eq!(
via_pair_array.count(observed),
1,
"AxisHistogram::from([(cell, 1)]) must observe the lifted cell on {observed:?}",
);
}
}
fn assert_from_zero_count_pair_equals_empty<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let via_empty: AxisHistogram<A> = AxisHistogram::empty();
for observed in axis_iter::<A>() {
let via_zero_pair: AxisHistogram<A> = AxisHistogram::from([(observed, 0usize)]);
assert_eq!(
via_zero_pair,
via_empty,
"AxisHistogram::from([(cell, 0)]) must equal AxisHistogram::empty() \
on {observed:?} for axis {}",
std::any::type_name::<A>(),
);
assert_eq!(
via_zero_pair.total(),
0,
"AxisHistogram::from([(cell, 0)]).total() must equal 0 on {observed:?} \
for axis {}",
std::any::type_name::<A>(),
);
assert!(
via_zero_pair.is_empty(),
"AxisHistogram::from([(cell, 0)]) must be is_empty on {observed:?} \
for axis {}",
std::any::type_name::<A>(),
);
}
}
#[test]
fn axis_histogram_from_empty_pair_array_equals_empty_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_from_empty_pair_array_equals_empty::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_from_singleton_pair_one_equals_singleton_cell_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_from_singleton_pair_one_equals_singleton_cell::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_from_zero_count_pair_equals_empty_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_from_zero_count_pair_equals_empty::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_from_pair_array_constructs_pointwise_histogram_for_diff_line_kind() {
let via_from = AxisHistogram::from([
(DiffLineKind::Added, 12),
(DiffLineKind::Removed, 4),
(DiffLineKind::Context, 53),
]);
let via_into: AxisHistogram<DiffLineKind> = [
(DiffLineKind::Added, 12),
(DiffLineKind::Removed, 4),
(DiffLineKind::Context, 53),
]
.into();
let via_into_iter_collect: AxisHistogram<DiffLineKind> = [
(DiffLineKind::Added, 12),
(DiffLineKind::Removed, 4),
(DiffLineKind::Context, 53),
]
.into_iter()
.collect();
let via_extend = {
let mut h: AxisHistogram<DiffLineKind> = AxisHistogram::empty();
h.extend([
(DiffLineKind::Added, 12),
(DiffLineKind::Removed, 4),
(DiffLineKind::Context, 53),
]);
h
};
assert_eq!(via_from, via_into);
assert_eq!(via_from, via_into_iter_collect);
assert_eq!(via_from, via_extend);
assert_eq!(via_from.count(DiffLineKind::Added), 12);
assert_eq!(via_from.count(DiffLineKind::Removed), 4);
assert_eq!(via_from.count(DiffLineKind::Context), 53);
assert_eq!(via_from.total(), 69);
assert!(!via_from.is_empty());
assert_eq!(via_from.dominant_cell(), Some(DiffLineKind::Context));
}
#[test]
fn axis_histogram_from_pair_array_is_additive_on_repeated_cells_for_diff_line_kind() {
let via_from = AxisHistogram::from([
(DiffLineKind::Added, 2),
(DiffLineKind::Added, 3),
(DiffLineKind::Added, 5),
(DiffLineKind::Removed, 1),
]);
assert_eq!(via_from.count(DiffLineKind::Added), 10);
assert_eq!(via_from.count(DiffLineKind::Removed), 1);
assert_eq!(via_from.count(DiffLineKind::Context), 0);
assert_eq!(via_from.total(), 11);
assert_eq!(via_from.dominant_cell(), Some(DiffLineKind::Added));
}
#[test]
fn axis_histogram_from_pair_array_equals_observe_expansion_for_diff_line_kind() {
let pairs: [(DiffLineKind, usize); 3] = [
(DiffLineKind::Added, 12),
(DiffLineKind::Removed, 4),
(DiffLineKind::Context, 53),
];
let via_from_array = AxisHistogram::from(pairs);
let via_expansion: AxisHistogram<DiffLineKind> = pairs
.iter()
.copied()
.flat_map(|(c, n)| std::iter::repeat_n(c, n))
.collect();
assert_eq!(via_from_array, via_expansion);
}
fn assert_into_iter_ref_equals_iter<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let hist: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
let via_iter: Vec<(A, usize)> = hist.iter().collect();
let via_into_iter: Vec<(A, usize)> = (&hist).into_iter().collect();
assert_eq!(
via_iter,
via_into_iter,
"IntoIterator<&Self> must equal iter() pointwise on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_into_iter_owned_equals_iter<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let hist: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
let via_iter: Vec<(A, usize)> = hist.iter().collect();
let via_into_iter: Vec<(A, usize)> = hist.into_iter().collect();
assert_eq!(
via_iter,
via_into_iter,
"IntoIterator<Self> must equal iter() pointwise on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_into_iter_ref_length_equals_axis_cardinality<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let hist = AxisHistogram::<A>::empty();
let iter = (&hist).into_iter();
assert_eq!(
iter.len(),
axis_cardinality::<A>(),
"IntoIterator<&Self>::len must equal axis_cardinality on axis {}",
std::any::type_name::<A>(),
);
let collected: Vec<(A, usize)> = (&hist).into_iter().collect();
assert_eq!(
collected.len(),
axis_cardinality::<A>(),
"IntoIterator<&Self> must yield axis_cardinality pairs on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_into_iter_ref_yields_cells_in_declaration_order<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let hist: AxisHistogram<A> = axis_iter::<A>().collect();
let cells: Vec<A> = (&hist).into_iter().map(|(v, _)| v).collect();
let expected: Vec<A> = axis_iter::<A>().collect();
assert_eq!(
cells,
expected,
"IntoIterator<&Self> cells must agree with axis_iter on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_into_iter_ref_double_ended_round_trips<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let hist: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
let forward: Vec<(A, usize)> = (&hist).into_iter().collect();
let mut backward: Vec<(A, usize)> = (&hist).into_iter().rev().collect();
backward.reverse();
assert_eq!(
forward,
backward,
"IntoIterator<&Self>::rev must round-trip pointwise on axis {}",
std::any::type_name::<A>(),
);
}
#[test]
fn axis_histogram_into_iter_ref_equals_iter_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_into_iter_ref_equals_iter::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_into_iter_owned_equals_iter_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_into_iter_owned_equals_iter::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_into_iter_ref_length_equals_axis_cardinality_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_into_iter_ref_length_equals_axis_cardinality::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_into_iter_ref_yields_cells_in_declaration_order_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_into_iter_ref_yields_cells_in_declaration_order::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_into_iter_ref_double_ended_round_trips_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_into_iter_ref_double_ended_round_trips::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_for_loop_ref_yields_pairs_for_diff_line_kind() {
let hist: AxisHistogram<DiffLineKind> = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
]
.into_iter()
.collect();
let mut pairs: Vec<(DiffLineKind, usize)> = Vec::new();
for pair in &hist {
pairs.push(pair);
}
let via_iter: Vec<(DiffLineKind, usize)> = hist.iter().collect();
assert_eq!(pairs, via_iter);
assert_eq!(
pairs,
vec![
(DiffLineKind::Removed, 1),
(DiffLineKind::Added, 2),
(DiffLineKind::Context, 0),
],
);
}
#[test]
fn axis_histogram_owned_into_iter_yields_pairs_for_diff_line_kind() {
let hist: AxisHistogram<DiffLineKind> = [
DiffLineKind::Context,
DiffLineKind::Added,
DiffLineKind::Added,
]
.into_iter()
.collect();
let snapshot: Vec<(DiffLineKind, usize)> = hist.iter().collect();
let consumed: Vec<(DiffLineKind, usize)> = hist.into_iter().collect();
assert_eq!(consumed, snapshot);
assert_eq!(
consumed,
vec![
(DiffLineKind::Removed, 0),
(DiffLineKind::Added, 2),
(DiffLineKind::Context, 1),
],
);
}
fn assert_iter_mut_length_equals_axis_cardinality<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let mut hist = AxisHistogram::<A>::empty();
let len = hist.iter_mut().len();
assert_eq!(
len,
axis_cardinality::<A>(),
"iter_mut::len must equal axis_cardinality on axis {}",
std::any::type_name::<A>(),
);
let collected: Vec<(A, &mut usize)> = hist.iter_mut().collect();
assert_eq!(
collected.len(),
axis_cardinality::<A>(),
"iter_mut must yield axis_cardinality pairs on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_iter_mut_yields_cells_in_declaration_order<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let mut hist: AxisHistogram<A> = axis_iter::<A>().collect();
let cells: Vec<A> = hist.iter_mut().map(|(v, _)| v).collect();
let expected: Vec<A> = axis_iter::<A>().collect();
assert_eq!(
cells,
expected,
"iter_mut cells must agree with axis_iter on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_iter_mut_zero_assignment_equals_empty<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let mut hist: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
hist.iter_mut().for_each(|(_, c)| *c = 0);
assert_eq!(
hist,
AxisHistogram::<A>::empty(),
"iter_mut zero-assignment must yield empty on axis {}",
std::any::type_name::<A>(),
);
assert!(
hist.is_empty(),
"iter_mut zero-assignment must satisfy is_empty on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_iter_mut_double_equals_mul_assign_two<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let mut via_iter_mut: AxisHistogram<A> = axis_iter::<A>().collect();
let mut via_mul_assign = via_iter_mut.clone();
via_iter_mut.iter_mut().for_each(|(_, c)| *c *= 2);
via_mul_assign *= 2usize;
assert_eq!(
via_iter_mut,
via_mul_assign,
"iter_mut *= 2 must equal MulAssign<usize> by 2 on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_iter_mut_double_ended_round_trips<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let mut hist: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
let forward: Vec<A> = hist.iter_mut().map(|(v, _)| v).collect();
let mut backward: Vec<A> = {
let mut iter = hist.iter_mut();
let mut buf = Vec::new();
while let Some((v, _)) = iter.next_back() {
buf.push(v);
}
buf
};
backward.reverse();
assert_eq!(
forward,
backward,
"iter_mut next_back must round-trip pointwise on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_into_iter_mut_equals_iter_mut<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let mut via_iter_mut: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
let mut via_into_iter_mut = via_iter_mut.clone();
let via_iter_mut_pairs: Vec<(A, usize)> = via_iter_mut
.iter_mut()
.map(|(cell, c)| (cell, *c))
.collect();
let via_into_iter_mut_pairs: Vec<(A, usize)> = (&mut via_into_iter_mut)
.into_iter()
.map(|(cell, c)| (cell, *c))
.collect();
assert_eq!(
via_iter_mut_pairs,
via_into_iter_mut_pairs,
"IntoIterator<&mut Self> must equal iter_mut pointwise on axis {}",
std::any::type_name::<A>(),
);
}
#[test]
fn axis_histogram_iter_mut_length_equals_axis_cardinality_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_iter_mut_length_equals_axis_cardinality::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_iter_mut_yields_cells_in_declaration_order_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_iter_mut_yields_cells_in_declaration_order::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_iter_mut_zero_assignment_equals_empty_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_iter_mut_zero_assignment_equals_empty::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_iter_mut_double_equals_mul_assign_two_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_iter_mut_double_equals_mul_assign_two::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_iter_mut_double_ended_round_trips_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_iter_mut_double_ended_round_trips::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_into_iter_mut_equals_iter_mut_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_into_iter_mut_equals_iter_mut::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_iter_mut_per_cell_remap_for_diff_line_kind() {
let mut hist: AxisHistogram<DiffLineKind> = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Context,
DiffLineKind::Context,
DiffLineKind::Context,
]
.into_iter()
.collect();
let before = hist.clone();
assert_eq!(before.count(DiffLineKind::Added), 2);
assert_eq!(before.count(DiffLineKind::Removed), 1);
assert_eq!(before.count(DiffLineKind::Context), 3);
hist.iter_mut().for_each(|(cell, c)| {
let factor = match cell {
DiffLineKind::Added => 3,
DiffLineKind::Removed => 5,
DiffLineKind::Context => 0,
};
*c *= factor;
});
assert_eq!(hist.count(DiffLineKind::Added), 6);
assert_eq!(hist.count(DiffLineKind::Removed), 5);
assert_eq!(hist.count(DiffLineKind::Context), 0);
assert_eq!(hist.total(), 11);
assert_eq!(before.distinct_cells(), 3);
assert_eq!(hist.distinct_cells(), 2);
}
#[test]
fn axis_histogram_iter_mut_for_loop_for_diff_line_kind() {
let mut hist: AxisHistogram<DiffLineKind> = [DiffLineKind::Added, DiffLineKind::Removed]
.into_iter()
.collect();
for (_, c) in &mut hist {
*c += 1;
}
assert_eq!(hist.count(DiffLineKind::Added), 2);
assert_eq!(hist.count(DiffLineKind::Removed), 2);
assert_eq!(hist.count(DiffLineKind::Context), 1);
assert_eq!(hist.total(), 5);
assert_eq!(hist.distinct_cells(), axis_cardinality::<DiffLineKind>());
assert!(hist.is_full_cover());
}
fn assert_index_empty_is_zero<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let hist = AxisHistogram::<A>::empty();
for value in axis_iter::<A>() {
assert_eq!(
hist[value],
0,
"empty histogram hist[{value:?}] must be 0 on axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_index_equals_count<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let hist: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
for value in axis_iter::<A>() {
assert_eq!(
hist[value],
hist.count(value),
"hist[{value:?}] must equal hist.count({value:?}) on axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_index_after_observe_increments<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
for observed in axis_iter::<A>() {
let mut hist: AxisHistogram<A> = axis_iter::<A>().collect();
let snapshot: Vec<usize> = axis_iter::<A>().map(|v| hist[v]).collect();
hist.observe(observed);
for (i, cell) in axis_iter::<A>().enumerate() {
let expected = snapshot[i] + usize::from(cell == observed);
assert_eq!(
hist[cell],
expected,
"after observing {observed:?}: hist[{cell:?}] must be {expected} on axis {}",
std::any::type_name::<A>(),
);
}
}
}
fn assert_index_sum_over_axis_equals_total<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let hist: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
let sum_via_index: usize = axis_iter::<A>().map(|v| hist[v]).sum();
assert_eq!(
sum_via_index,
hist.total(),
"sum of hist[v] over axis_iter must equal hist.total() on axis {}",
std::any::type_name::<A>(),
);
}
#[test]
fn axis_histogram_index_empty_is_zero_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_index_empty_is_zero::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_index_equals_count_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_index_equals_count::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_index_after_observe_increments_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_index_after_observe_increments::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_index_sum_over_axis_equals_total_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_index_sum_over_axis_equals_total::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_index_reads_per_cell_counts_for_diff_line_kind() {
let hist: AxisHistogram<DiffLineKind> = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
]
.into_iter()
.collect();
assert_eq!(hist[DiffLineKind::Added], 2);
assert_eq!(hist[DiffLineKind::Removed], 1);
assert_eq!(hist[DiffLineKind::Context], 0);
for cell in axis_iter::<DiffLineKind>() {
assert_eq!(hist[cell], hist.count(cell));
}
let total_via_index: usize = axis_iter::<DiffLineKind>().map(|v| hist[v]).sum();
assert_eq!(total_via_index, hist.total());
assert_eq!(total_via_index, 3);
}
#[test]
fn axis_histogram_index_pairs_with_into_iter_for_diff_line_kind() {
let hist: AxisHistogram<DiffLineKind> = [
DiffLineKind::Context,
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Removed,
DiffLineKind::Removed,
]
.into_iter()
.collect();
for (cell, count) in &hist {
assert_eq!(
hist[cell], count,
"Index/IntoIterator duality must hold on {cell:?}",
);
}
assert_eq!(hist[DiffLineKind::Removed], 3);
assert_eq!(hist[DiffLineKind::Added], 2);
assert_eq!(hist[DiffLineKind::Context], 1);
}
fn assert_index_mut_write_round_trips_through_index<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let mut hist = AxisHistogram::<A>::empty();
for (i, cell) in axis_iter::<A>().enumerate() {
hist[cell] = i + 1;
}
for (i, cell) in axis_iter::<A>().enumerate() {
assert_eq!(
hist[cell],
i + 1,
"IndexMut write must round-trip through Index on cell {cell:?} on axis {}",
std::any::type_name::<A>(),
);
assert_eq!(
hist.count(cell),
i + 1,
"IndexMut write must round-trip through count() on cell {cell:?} on axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_index_mut_add_assign_equals_observe<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let observations: Vec<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
let mut via_observe = AxisHistogram::<A>::empty();
let mut via_index_mut = AxisHistogram::<A>::empty();
for v in &observations {
via_observe.observe(*v);
via_index_mut[*v] += 1;
}
for cell in axis_iter::<A>() {
assert_eq!(
via_index_mut[cell],
via_observe[cell],
"`hist[cell] += 1` must agree with `hist.observe(cell)` on cell {cell:?} on axis {}",
std::any::type_name::<A>(),
);
}
assert_eq!(
via_index_mut,
via_observe,
"`hist[cell] += 1` must equal `hist.observe(cell)` histogram-wise on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_index_mut_zero_resets_single_cell<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
for target in axis_iter::<A>() {
let base: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
let mut hist = base.clone();
hist[target] = 0;
for cell in axis_iter::<A>() {
let expected = if cell == target { 0 } else { base[cell] };
assert_eq!(
hist[cell],
expected,
"`hist[{target:?}] = 0` must leave cell {cell:?} at {expected} on axis {}",
std::any::type_name::<A>(),
);
}
}
}
fn assert_index_mut_mul_assign_matches_mul_assign_usize<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let base: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
let mut via_cellwise = base.clone();
for v in axis_iter::<A>() {
via_cellwise[v] *= 2;
}
let mut via_global = base.clone();
via_global *= 2;
assert_eq!(
via_cellwise,
via_global,
"cellwise doubling through IndexMut must agree with `hist *= 2` on axis {}",
std::any::type_name::<A>(),
);
}
#[test]
fn axis_histogram_index_mut_write_round_trips_through_index_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_index_mut_write_round_trips_through_index::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_index_mut_add_assign_equals_observe_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_index_mut_add_assign_equals_observe::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_index_mut_zero_resets_single_cell_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_index_mut_zero_resets_single_cell::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_index_mut_mul_assign_matches_mul_assign_usize_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_index_mut_mul_assign_matches_mul_assign_usize::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_index_mut_supports_compound_assignments_for_diff_line_kind() {
let mut hist = AxisHistogram::<DiffLineKind>::empty();
hist[DiffLineKind::Added] = 5;
hist[DiffLineKind::Removed] = 3;
hist[DiffLineKind::Context] = 1;
assert_eq!(hist[DiffLineKind::Added], 5);
assert_eq!(hist[DiffLineKind::Removed], 3);
assert_eq!(hist[DiffLineKind::Context], 1);
assert_eq!(hist.total(), 9);
hist[DiffLineKind::Added] += 2;
assert_eq!(hist[DiffLineKind::Added], 7);
hist[DiffLineKind::Removed] -= 1;
assert_eq!(hist[DiffLineKind::Removed], 2);
hist[DiffLineKind::Context] *= 4;
assert_eq!(hist[DiffLineKind::Context], 4);
hist[DiffLineKind::Added] = 0;
assert_eq!(hist[DiffLineKind::Added], 0);
assert_eq!(hist[DiffLineKind::Removed], 2);
assert_eq!(hist[DiffLineKind::Context], 4);
assert_eq!(hist.total(), 6);
}
fn assert_sum_of_empty_iter_is_empty<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let owned: AxisHistogram<A> = std::iter::empty::<AxisHistogram<A>>().sum();
assert_eq!(
owned,
AxisHistogram::<A>::empty(),
"Sum<Self> of empty iter must equal empty on axis {}",
std::any::type_name::<A>(),
);
let pile: Vec<AxisHistogram<A>> = Vec::new();
let via_refs: AxisHistogram<A> = pile.iter().sum();
assert_eq!(
via_refs,
AxisHistogram::<A>::empty(),
"Sum<&Self> of empty iter must equal empty on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_sum_of_singleton_iter_equals_inner<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
let owned: AxisHistogram<A> = std::iter::once(cover.clone()).sum();
assert_eq!(
owned,
cover,
"Sum<Self> of singleton must equal inner on axis {}",
std::any::type_name::<A>(),
);
let pile = [cover.clone()];
let via_refs: AxisHistogram<A> = pile.iter().sum();
assert_eq!(
via_refs,
cover,
"Sum<&Self> of singleton must equal inner on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_sum_grows_total_additively<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
let cover_twice: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
let empty = AxisHistogram::<A>::empty();
let components = [cover.clone(), cover_twice.clone(), empty.clone()];
let expected_total = cover.total() + cover_twice.total() + empty.total();
let owned: AxisHistogram<A> = components.iter().cloned().sum();
assert_eq!(
owned.total(),
expected_total,
"Sum<Self> total must equal sum of component totals on axis {}",
std::any::type_name::<A>(),
);
let via_refs: AxisHistogram<A> = components.iter().sum();
assert_eq!(
via_refs.total(),
expected_total,
"Sum<&Self> total must equal sum of component totals on axis {}",
std::any::type_name::<A>(),
);
for cell in axis_iter::<A>() {
let expected_cell = cover.count(cell) + cover_twice.count(cell) + empty.count(cell);
assert_eq!(
via_refs.count(cell),
expected_cell,
"Sum<&Self> cell {cell:?} must equal sum of component counts on axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_sum_of_refs_equals_sum_of_owned<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let mut components = vec![axis_iter::<A>().collect::<AxisHistogram<A>>()];
if let Some(first) = axis_iter::<A>().next() {
let mut single = AxisHistogram::<A>::empty();
single.observe(first);
components.push(single);
}
components.push(AxisHistogram::<A>::empty());
let via_owned: AxisHistogram<A> = components.iter().cloned().sum();
let via_refs: AxisHistogram<A> = components.iter().sum();
assert_eq!(
via_owned,
via_refs,
"Sum<Self> via cloned must equal Sum<&Self> on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_sum_owned_equals_explicit_fold<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
let cover_twice: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
let components = [cover, cover_twice, AxisHistogram::<A>::empty()];
let via_sum: AxisHistogram<A> = components.iter().cloned().sum();
let via_fold: AxisHistogram<A> = components
.iter()
.cloned()
.fold(AxisHistogram::<A>::empty(), |acc, h| acc.merge(&h));
assert_eq!(
via_sum,
via_fold,
"Sum<Self> must equal explicit fold(empty, merge) on axis {}",
std::any::type_name::<A>(),
);
let via_sum_refs: AxisHistogram<A> = components.iter().sum();
let via_fold_refs: AxisHistogram<A> = components
.iter()
.fold(AxisHistogram::<A>::empty(), AxisHistogram::merge);
assert_eq!(
via_sum_refs,
via_fold_refs,
"Sum<&Self> must equal explicit fold(empty, merge) on axis {}",
std::any::type_name::<A>(),
);
}
#[test]
fn axis_histogram_sum_of_empty_iter_is_empty_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_sum_of_empty_iter_is_empty::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_sum_of_singleton_iter_equals_inner_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_sum_of_singleton_iter_equals_inner::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_sum_grows_total_additively_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_sum_grows_total_additively::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_sum_of_refs_equals_sum_of_owned_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_sum_of_refs_equals_sum_of_owned::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_sum_owned_equals_explicit_fold_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_sum_owned_equals_explicit_fold::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_sum_equals_fleet_aggregate_for_diff_line_kind() {
let window_a: AxisHistogram<DiffLineKind> = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
]
.into_iter()
.collect();
let window_b: AxisHistogram<DiffLineKind> = [DiffLineKind::Context, DiffLineKind::Removed]
.into_iter()
.collect();
let window_c: AxisHistogram<DiffLineKind> = std::iter::once(DiffLineKind::Added).collect();
let windows = [window_a, window_b, window_c];
let via_sum_refs: AxisHistogram<DiffLineKind> = windows.iter().sum();
let via_sum_owned: AxisHistogram<DiffLineKind> = windows.iter().cloned().sum();
assert_eq!(via_sum_refs.count(DiffLineKind::Added), 3);
assert_eq!(via_sum_refs.count(DiffLineKind::Removed), 2);
assert_eq!(via_sum_refs.count(DiffLineKind::Context), 1);
assert_eq!(via_sum_refs.total(), 6);
assert_eq!(via_sum_owned, via_sum_refs);
}
#[test]
fn axis_histogram_sum_lowers_to_fold_for_diff_line_kind() {
let inputs: [&[DiffLineKind]; 4] = [
&[],
&[DiffLineKind::Added],
&[
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Added,
],
&[
DiffLineKind::Context,
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Added,
DiffLineKind::Removed,
],
];
let windows: Vec<AxisHistogram<DiffLineKind>> = inputs
.iter()
.map(|input| input.iter().copied().collect())
.collect();
let via_sum: AxisHistogram<DiffLineKind> = windows.iter().cloned().sum();
let via_fold: AxisHistogram<DiffLineKind> = windows
.iter()
.cloned()
.fold(AxisHistogram::empty(), |acc, h| acc.merge(&h));
assert_eq!(via_sum, via_fold);
let via_sum_refs: AxisHistogram<DiffLineKind> = windows.iter().sum();
let via_fold_refs: AxisHistogram<DiffLineKind> = windows
.iter()
.fold(AxisHistogram::empty(), AxisHistogram::merge);
assert_eq!(via_sum_refs, via_fold_refs);
}
fn assert_add_assign_ref_empty_rhs_is_identity<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
let mut acc = cover.clone();
acc += &AxisHistogram::<A>::empty();
assert_eq!(
acc,
cover,
"hist += &empty must leave hist unchanged on axis {}",
std::any::type_name::<A>(),
);
let mut empty = AxisHistogram::<A>::empty();
empty += &cover;
assert_eq!(
empty,
cover,
"empty += &hist must equal hist on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_add_assign_ref_equals_merge<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let lhs: AxisHistogram<A> = axis_iter::<A>().collect();
let rhs: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
let mut via_add_assign = lhs.clone();
via_add_assign += &rhs;
let via_merge = lhs.clone().merge(&rhs);
assert_eq!(
via_add_assign,
via_merge,
"AddAssign<&Self> must equal merge on axis {}",
std::any::type_name::<A>(),
);
let mut via_add_assign_owned = lhs.clone();
via_add_assign_owned += rhs.clone();
assert_eq!(
via_add_assign_owned,
via_merge,
"AddAssign<Self> must equal merge on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_add_ref_equals_merge<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let lhs: AxisHistogram<A> = axis_iter::<A>().collect();
let rhs: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
let via_add = lhs.clone() + &rhs;
let via_merge = lhs.clone().merge(&rhs);
assert_eq!(
via_add,
via_merge,
"Add<&Self> must equal merge on axis {}",
std::any::type_name::<A>(),
);
let via_add_owned = lhs.clone() + rhs.clone();
assert_eq!(
via_add_owned,
via_merge,
"Add<Self> must equal merge on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_add_assign_ref_is_commutative<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
let single = if let Some(first) = axis_iter::<A>().next() {
let mut h = AxisHistogram::<A>::empty();
h.observe(first);
h
} else {
return;
};
let mut a = cover.clone();
a += &single;
let mut b = single.clone();
b += &cover;
assert_eq!(
a,
b,
"AddAssign<&Self> must produce the same histogram regardless of \
which side is mutated on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_add_assign_ref_grows_total_additively<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let lhs: AxisHistogram<A> = axis_iter::<A>().collect();
let rhs: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
let lhs_total_before = lhs.total();
let rhs_total = rhs.total();
let mut acc = lhs.clone();
acc += &rhs;
assert_eq!(
acc.total(),
lhs_total_before + rhs_total,
"AddAssign<&Self> must grow total by rhs.total() on axis {}",
std::any::type_name::<A>(),
);
for cell in axis_iter::<A>() {
assert_eq!(
acc.count(cell),
lhs.count(cell) + rhs.count(cell),
"AddAssign<&Self> cell {cell:?} must equal lhs + rhs on axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_add_owned_equals_add_ref<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let lhs: AxisHistogram<A> = axis_iter::<A>().collect();
let rhs: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
let via_add_ref = lhs.clone() + &rhs;
let via_add_owned = lhs.clone() + rhs.clone();
assert_eq!(
via_add_ref,
via_add_owned,
"Add<&Self> must equal Add<Self> on axis {}",
std::any::type_name::<A>(),
);
let mut via_assign_ref = lhs.clone();
via_assign_ref += &rhs;
let mut via_assign_owned = lhs.clone();
via_assign_owned += rhs.clone();
assert_eq!(
via_assign_ref,
via_assign_owned,
"AddAssign<&Self> must equal AddAssign<Self> on axis {}",
std::any::type_name::<A>(),
);
}
#[test]
fn axis_histogram_add_assign_ref_empty_rhs_is_identity_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_add_assign_ref_empty_rhs_is_identity::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_add_assign_ref_equals_merge_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_add_assign_ref_equals_merge::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_add_ref_equals_merge_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_add_ref_equals_merge::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_add_assign_ref_is_commutative_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_add_assign_ref_is_commutative::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_add_assign_ref_grows_total_additively_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_add_assign_ref_grows_total_additively::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_add_owned_equals_add_ref_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_add_owned_equals_add_ref::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_add_assign_equals_fleet_aggregate_for_diff_line_kind() {
let window_a: AxisHistogram<DiffLineKind> = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
]
.into_iter()
.collect();
let window_b: AxisHistogram<DiffLineKind> = [DiffLineKind::Context, DiffLineKind::Removed]
.into_iter()
.collect();
let window_c: AxisHistogram<DiffLineKind> = std::iter::once(DiffLineKind::Added).collect();
let windows = [window_a.clone(), window_b.clone(), window_c.clone()];
let mut via_add_assign: AxisHistogram<DiffLineKind> = AxisHistogram::empty();
for window in &windows {
via_add_assign += window;
}
let via_sum: AxisHistogram<DiffLineKind> = windows.iter().sum();
assert_eq!(via_add_assign, via_sum);
assert_eq!(via_add_assign.count(DiffLineKind::Added), 3);
assert_eq!(via_add_assign.count(DiffLineKind::Removed), 2);
assert_eq!(via_add_assign.count(DiffLineKind::Context), 1);
assert_eq!(via_add_assign.total(), 6);
}
#[test]
fn axis_histogram_add_chain_equals_merge_chain_for_diff_line_kind() {
let a: AxisHistogram<DiffLineKind> = [DiffLineKind::Added].into_iter().collect();
let b: AxisHistogram<DiffLineKind> = [DiffLineKind::Added, DiffLineKind::Removed]
.into_iter()
.collect();
let c: AxisHistogram<DiffLineKind> = std::iter::once(DiffLineKind::Context).collect();
let via_add = a.clone() + &b + &c;
let via_merge = a.clone().merge(&b).merge(&c);
assert_eq!(via_add, via_merge);
assert_eq!(via_add.count(DiffLineKind::Added), 2);
assert_eq!(via_add.count(DiffLineKind::Removed), 1);
assert_eq!(via_add.count(DiffLineKind::Context), 1);
assert_eq!(via_add.total(), 4);
}
fn assert_sub_assign_ref_empty_rhs_is_identity<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
let mut acc = cover.clone();
acc -= &AxisHistogram::<A>::empty();
assert_eq!(
acc,
cover,
"hist -= &empty must leave hist unchanged on axis {}",
std::any::type_name::<A>(),
);
let mut empty = AxisHistogram::<A>::empty();
empty -= &cover;
assert_eq!(
empty,
AxisHistogram::<A>::empty(),
"empty -= &hist must remain empty on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_sub_assign_ref_self_yields_empty<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
let mut acc = cover.clone();
acc -= &cover;
assert_eq!(
acc,
AxisHistogram::<A>::empty(),
"hist -= &hist must equal AxisHistogram::empty() on axis {}",
std::any::type_name::<A>(),
);
assert_eq!(
acc.total(),
0,
"hist -= &hist must zero the total on axis {}",
std::any::type_name::<A>(),
);
assert!(
acc.is_empty(),
"hist -= &hist must satisfy is_empty on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_sub_assign_ref_saturates_at_zero<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let single: AxisHistogram<A> = axis_iter::<A>().collect();
let double: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
let mut acc = single.clone();
acc -= &double;
assert_eq!(
acc,
AxisHistogram::<A>::empty(),
"small -= &large must saturate to empty on axis {}",
std::any::type_name::<A>(),
);
for cell in axis_iter::<A>() {
assert_eq!(
acc.count(cell),
0,
"saturated sub cell {cell:?} must be 0 on axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_sub_assign_ref_is_inverse_of_add_assign_ref<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let lhs: AxisHistogram<A> = axis_iter::<A>().collect();
let rhs: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
let mut round = lhs.clone();
round += &rhs;
round -= &rhs;
assert_eq!(
round,
lhs,
"AddAssign-then-SubAssign on the same rhs must recover lhs on axis {}",
std::any::type_name::<A>(),
);
let mut round_other = lhs.clone();
round_other += &rhs;
let mut also = round_other.clone();
also -= &rhs;
assert_eq!(
also,
lhs,
"round-trip (any order) must recover lhs on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_sub_assign_ref_cell_level_saturates<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let triple: AxisHistogram<A> = axis_iter::<A>()
.chain(axis_iter::<A>())
.chain(axis_iter::<A>())
.collect();
let double: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
let mut acc = triple.clone();
acc -= &double;
for cell in axis_iter::<A>() {
let expected = triple.count(cell).saturating_sub(double.count(cell));
assert_eq!(
acc.count(cell),
expected,
"cell {cell:?} must equal saturating_sub on axis {}",
std::any::type_name::<A>(),
);
}
assert!(
acc.total() <= triple.total(),
"total must not grow under SubAssign on axis {}",
std::any::type_name::<A>(),
);
assert_eq!(
triple.total() - acc.total(),
double.total(),
"total shrink must equal rhs.total() when no cell saturates on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_sub_owned_equals_sub_ref<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let lhs: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
let rhs: AxisHistogram<A> = axis_iter::<A>().collect();
let via_sub_ref = lhs.clone() - &rhs;
let via_sub_owned = lhs.clone() - rhs.clone();
assert_eq!(
via_sub_ref,
via_sub_owned,
"Sub<&Self> must equal Sub<Self> on axis {}",
std::any::type_name::<A>(),
);
let mut via_assign_ref = lhs.clone();
via_assign_ref -= &rhs;
let mut via_assign_owned = lhs.clone();
via_assign_owned -= rhs.clone();
assert_eq!(
via_assign_ref,
via_assign_owned,
"SubAssign<&Self> must equal SubAssign<Self> on axis {}",
std::any::type_name::<A>(),
);
assert_eq!(
via_sub_ref,
via_assign_ref,
"Sub<&Self> must equal SubAssign<&Self>-then-return on axis {}",
std::any::type_name::<A>(),
);
}
#[test]
fn axis_histogram_sub_assign_ref_empty_rhs_is_identity_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_sub_assign_ref_empty_rhs_is_identity::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_sub_assign_ref_self_yields_empty_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_sub_assign_ref_self_yields_empty::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_sub_assign_ref_saturates_at_zero_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_sub_assign_ref_saturates_at_zero::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_sub_assign_ref_is_inverse_of_add_assign_ref_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_sub_assign_ref_is_inverse_of_add_assign_ref::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_sub_assign_ref_cell_level_saturates_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_sub_assign_ref_cell_level_saturates::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_sub_owned_equals_sub_ref_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_sub_owned_equals_sub_ref::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_sub_assign_host_leaves_fleet_round_trip_for_diff_line_kind() {
let host_a: AxisHistogram<DiffLineKind> = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
]
.into_iter()
.collect();
let host_b: AxisHistogram<DiffLineKind> = [DiffLineKind::Context, DiffLineKind::Removed]
.into_iter()
.collect();
let host_c: AxisHistogram<DiffLineKind> = std::iter::once(DiffLineKind::Added).collect();
let mut fleet: AxisHistogram<DiffLineKind> = AxisHistogram::empty();
fleet += &host_a;
fleet += &host_b;
fleet += &host_c;
fleet -= &host_b;
let expected: AxisHistogram<DiffLineKind> = host_a.clone() + &host_c;
assert_eq!(fleet, expected);
assert_eq!(fleet.count(DiffLineKind::Added), 3);
assert_eq!(fleet.count(DiffLineKind::Removed), 1);
assert_eq!(fleet.count(DiffLineKind::Context), 0);
assert_eq!(fleet.total(), 4);
}
#[test]
fn axis_histogram_sub_assign_saturates_per_cell_for_diff_line_kind() {
let lhs: AxisHistogram<DiffLineKind> = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
]
.into_iter()
.collect();
let rhs: AxisHistogram<DiffLineKind> = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Removed,
DiffLineKind::Removed,
DiffLineKind::Context,
]
.into_iter()
.collect();
let mut diff = lhs.clone();
diff -= &rhs;
assert_eq!(diff.count(DiffLineKind::Added), 3);
assert_eq!(diff.count(DiffLineKind::Removed), 0);
assert_eq!(diff.count(DiffLineKind::Context), 0);
assert_eq!(diff.total(), 3);
for &cell in DiffLineKind::ALL {
assert_eq!(
diff.count(cell),
lhs.count(cell).saturating_sub(rhs.count(cell)),
);
}
let mut reverse = rhs.clone();
reverse -= &lhs;
assert_eq!(reverse.count(DiffLineKind::Added), 0);
assert_eq!(reverse.count(DiffLineKind::Removed), 2);
assert_eq!(reverse.count(DiffLineKind::Context), 1);
assert_ne!(diff, reverse);
}
fn assert_pointwise_max_empty_rhs_is_identity<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
let joined_right = cover.clone().pointwise_max(&AxisHistogram::<A>::empty());
assert_eq!(
joined_right,
cover,
"hist.pointwise_max(&empty) must equal hist on axis {}",
std::any::type_name::<A>(),
);
let joined_left = AxisHistogram::<A>::empty().pointwise_max(&cover);
assert_eq!(
joined_left,
cover,
"empty.pointwise_max(&hist) must equal hist on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_pointwise_min_empty_rhs_is_empty<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
let met_right = cover.clone().pointwise_min(&AxisHistogram::<A>::empty());
assert_eq!(
met_right,
AxisHistogram::<A>::empty(),
"hist.pointwise_min(&empty) must equal empty on axis {}",
std::any::type_name::<A>(),
);
let met_left = AxisHistogram::<A>::empty().pointwise_min(&cover);
assert_eq!(
met_left,
AxisHistogram::<A>::empty(),
"empty.pointwise_min(&hist) must equal empty on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_pointwise_max_and_min_idempotent<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let cover: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
let joined = cover.clone().pointwise_max(&cover);
assert_eq!(
joined,
cover,
"hist.pointwise_max(&hist) must equal hist on axis {}",
std::any::type_name::<A>(),
);
let met = cover.clone().pointwise_min(&cover);
assert_eq!(
met,
cover,
"hist.pointwise_min(&hist) must equal hist on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_pointwise_max_and_min_are_commutative<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
let skewed = if let Some(first) = axis_iter::<A>().next() {
let mut h: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
h.observe(first);
h
} else {
return;
};
let lhs_max = cover.clone().pointwise_max(&skewed);
let rhs_max = skewed.clone().pointwise_max(&cover);
assert_eq!(
lhs_max,
rhs_max,
"pointwise_max must be commutative on axis {}",
std::any::type_name::<A>(),
);
let lhs_min = cover.clone().pointwise_min(&skewed);
let rhs_min = skewed.clone().pointwise_min(&cover);
assert_eq!(
lhs_min,
rhs_min,
"pointwise_min must be commutative on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_pointwise_max_and_min_cell_level<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let single: AxisHistogram<A> = axis_iter::<A>().collect();
let double: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
let joined = single.clone().pointwise_max(&double);
let met = single.clone().pointwise_min(&double);
for cell in axis_iter::<A>() {
assert_eq!(
joined.count(cell),
single.count(cell).max(double.count(cell)),
"pointwise_max cell {cell:?} must equal max on axis {}",
std::any::type_name::<A>(),
);
assert_eq!(
met.count(cell),
single.count(cell).min(double.count(cell)),
"pointwise_min cell {cell:?} must equal min on axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_pointwise_max_dominates_and_min_dominated<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let single: AxisHistogram<A> = axis_iter::<A>().collect();
let double: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
let joined = single.clone().pointwise_max(&double);
let met = single.clone().pointwise_min(&double);
for cell in axis_iter::<A>() {
assert!(
joined.count(cell) >= single.count(cell)
&& joined.count(cell) >= double.count(cell),
"pointwise_max cell {cell:?} must dominate both sides on axis {}",
std::any::type_name::<A>(),
);
assert!(
met.count(cell) <= single.count(cell) && met.count(cell) <= double.count(cell),
"pointwise_min cell {cell:?} must be dominated by both sides on axis {}",
std::any::type_name::<A>(),
);
}
assert!(
joined.total() >= single.total() && joined.total() >= double.total(),
"pointwise_max total must dominate both sides on axis {}",
std::any::type_name::<A>(),
);
assert!(
met.total() <= single.total() && met.total() <= double.total(),
"pointwise_min total must be dominated by both sides on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_pointwise_max_plus_min_equals_add<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let lhs: AxisHistogram<A> = axis_iter::<A>().collect();
let rhs: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
let joined = lhs.clone().pointwise_max(&rhs);
let met = lhs.clone().pointwise_min(&rhs);
let lattice_sum = joined.clone() + &met;
let additive_sum = lhs.clone() + &rhs;
assert_eq!(
lattice_sum,
additive_sum,
"pointwise_max + pointwise_min must equal a + b on axis {}",
std::any::type_name::<A>(),
);
assert_eq!(
joined.total() + met.total(),
lhs.total() + rhs.total(),
"pointwise_max.total + pointwise_min.total must equal a.total + b.total on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_pointwise_max_and_min_absorb<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let a: AxisHistogram<A> = axis_iter::<A>().collect();
let b: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
let absorbed_max = a.clone().pointwise_max(&a.clone().pointwise_min(&b));
assert_eq!(
absorbed_max,
a,
"a ∨ (a ∧ b) must equal a on axis {}",
std::any::type_name::<A>(),
);
let absorbed_min = a.clone().pointwise_min(&a.clone().pointwise_max(&b));
assert_eq!(
absorbed_min,
a,
"a ∧ (a ∨ b) must equal a on axis {}",
std::any::type_name::<A>(),
);
}
#[test]
fn axis_histogram_pointwise_max_empty_rhs_is_identity_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_pointwise_max_empty_rhs_is_identity::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_pointwise_min_empty_rhs_is_empty_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_pointwise_min_empty_rhs_is_empty::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_pointwise_max_and_min_idempotent_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_pointwise_max_and_min_idempotent::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_pointwise_max_and_min_are_commutative_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_pointwise_max_and_min_are_commutative::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_pointwise_max_and_min_cell_level_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_pointwise_max_and_min_cell_level::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_pointwise_max_dominates_and_min_dominated_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_pointwise_max_dominates_and_min_dominated::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_pointwise_max_plus_min_equals_add_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_pointwise_max_plus_min_equals_add::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_pointwise_max_and_min_absorb_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_pointwise_max_and_min_absorb::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_pointwise_max_per_cell_envelope_for_diff_line_kind() {
let window_a: AxisHistogram<DiffLineKind> = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
]
.into_iter()
.collect();
let window_b: AxisHistogram<DiffLineKind> = [DiffLineKind::Context, DiffLineKind::Removed]
.into_iter()
.collect();
let window_c: AxisHistogram<DiffLineKind> = std::iter::once(DiffLineKind::Added).collect();
let envelope = window_a
.clone()
.pointwise_max(&window_b)
.pointwise_max(&window_c);
assert_eq!(envelope.count(DiffLineKind::Added), 2);
assert_eq!(envelope.count(DiffLineKind::Removed), 1);
assert_eq!(envelope.count(DiffLineKind::Context), 1);
assert_eq!(envelope.total(), 4);
let stacked = window_a.clone() + &window_b + &window_c;
assert_eq!(stacked.count(DiffLineKind::Added), 3);
assert_eq!(stacked.count(DiffLineKind::Removed), 2);
assert_eq!(stacked.count(DiffLineKind::Context), 1);
assert_ne!(envelope, stacked);
}
#[test]
fn axis_histogram_pointwise_min_per_cell_floor_for_diff_line_kind() {
let a: AxisHistogram<DiffLineKind> = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
]
.into_iter()
.collect();
let b: AxisHistogram<DiffLineKind> = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Removed,
DiffLineKind::Removed,
DiffLineKind::Context,
]
.into_iter()
.collect();
let floor = a.clone().pointwise_min(&b);
assert_eq!(floor.count(DiffLineKind::Added), 2);
assert_eq!(floor.count(DiffLineKind::Removed), 1);
assert_eq!(floor.count(DiffLineKind::Context), 0);
assert_eq!(floor.total(), 3);
let envelope = a.clone().pointwise_max(&b);
assert_eq!(envelope.count(DiffLineKind::Added), 5);
assert_eq!(envelope.count(DiffLineKind::Removed), 3);
assert_eq!(envelope.count(DiffLineKind::Context), 1);
assert_eq!(envelope.total(), 9);
let lattice_sum = envelope.clone() + &floor;
let additive_sum = a.clone() + &b;
assert_eq!(lattice_sum, additive_sum);
assert_eq!(lattice_sum.total(), a.total() + b.total());
}
fn assert_is_dominated_by_reflexive<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
assert!(
cover.is_dominated_by(&cover),
"hist.is_dominated_by(&hist) must read true on axis {}",
std::any::type_name::<A>(),
);
assert!(
cover.dominates(&cover),
"hist.dominates(&hist) must read true on axis {}",
std::any::type_name::<A>(),
);
let empty: AxisHistogram<A> = AxisHistogram::empty();
assert!(
empty.is_dominated_by(&empty),
"empty.is_dominated_by(&empty) must read true on axis {}",
std::any::type_name::<A>(),
);
assert!(
empty.dominates(&empty),
"empty.dominates(&empty) must read true on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_empty_is_dominated_by_every<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let empty: AxisHistogram<A> = AxisHistogram::empty();
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
assert!(
empty.is_dominated_by(&cover),
"empty.is_dominated_by(&cover) must read true on axis {}",
std::any::type_name::<A>(),
);
assert!(
cover.dominates(&empty),
"cover.dominates(&empty) must read true on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_is_dominated_by_antisymmetric<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let lhs: AxisHistogram<A> = axis_iter::<A>().collect();
let rhs: AxisHistogram<A> = axis_iter::<A>().collect();
assert!(lhs.is_dominated_by(&rhs));
assert!(rhs.is_dominated_by(&lhs));
assert_eq!(
lhs,
rhs,
"antisymmetry: mutual dominance must imply equality on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_is_dominated_by_transitive<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let bottom: AxisHistogram<A> = AxisHistogram::empty();
let middle: AxisHistogram<A> = axis_iter::<A>().collect();
let top: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
assert!(bottom.is_dominated_by(&middle));
assert!(middle.is_dominated_by(&top));
assert!(
bottom.is_dominated_by(&top),
"transitivity: empty ≤ cover ≤ doubled cover must imply empty ≤ doubled cover on axis {}",
std::any::type_name::<A>(),
);
assert!(top.dominates(&middle));
assert!(middle.dominates(&bottom));
assert!(
top.dominates(&bottom),
"transitivity: doubled cover ≥ cover ≥ empty must imply doubled cover ≥ empty on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_dominates_is_dual_of_is_dominated_by<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let bottom: AxisHistogram<A> = AxisHistogram::empty();
let middle: AxisHistogram<A> = axis_iter::<A>().collect();
let top: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
for (lhs, rhs) in [
(&bottom, &middle),
(&bottom, &top),
(&middle, &top),
(&middle, &bottom),
(&top, &bottom),
(&top, &middle),
(&middle, &middle),
] {
assert_eq!(
lhs.dominates(rhs),
rhs.is_dominated_by(lhs),
"dual: a.dominates(&b) must equal b.is_dominated_by(&a) on axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_is_dominated_by_join_characterization<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let bottom: AxisHistogram<A> = AxisHistogram::empty();
let middle: AxisHistogram<A> = axis_iter::<A>().collect();
let top: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
for (lhs, rhs) in [
(&bottom, &middle),
(&bottom, &top),
(&middle, &top),
(&middle, &middle),
(&middle, &bottom),
(&top, &middle),
] {
let dominated = lhs.is_dominated_by(rhs);
let joined = lhs.clone().pointwise_max(rhs);
assert_eq!(
dominated,
joined == *rhs,
"join characterization: a.is_dominated_by(&b) must equal (a ∨ b == b) on axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_is_dominated_by_meet_characterization<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let bottom: AxisHistogram<A> = AxisHistogram::empty();
let middle: AxisHistogram<A> = axis_iter::<A>().collect();
let top: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
for (lhs, rhs) in [
(&bottom, &middle),
(&bottom, &top),
(&middle, &top),
(&middle, &middle),
(&middle, &bottom),
(&top, &middle),
] {
let dominated = lhs.is_dominated_by(rhs);
let met = lhs.clone().pointwise_min(rhs);
assert_eq!(
dominated,
met == *lhs,
"meet characterization: a.is_dominated_by(&b) must equal (a ∧ b == a) on axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_is_dominated_by_implies_total_bound<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let bottom: AxisHistogram<A> = AxisHistogram::empty();
let middle: AxisHistogram<A> = axis_iter::<A>().collect();
let top: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
for (lhs, rhs) in [(&bottom, &middle), (&bottom, &top), (&middle, &top)] {
assert!(lhs.is_dominated_by(rhs));
assert!(
lhs.total() <= rhs.total(),
"total bound: a.is_dominated_by(&b) must imply a.total() <= b.total() on axis {}",
std::any::type_name::<A>(),
);
}
}
#[test]
fn axis_histogram_is_dominated_by_reflexive_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_is_dominated_by_reflexive::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_empty_is_dominated_by_every_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_empty_is_dominated_by_every::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_is_dominated_by_antisymmetric_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_is_dominated_by_antisymmetric::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_is_dominated_by_transitive_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_is_dominated_by_transitive::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_dominates_is_dual_of_is_dominated_by_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_dominates_is_dual_of_is_dominated_by::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_is_dominated_by_join_characterization_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_is_dominated_by_join_characterization::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_is_dominated_by_meet_characterization_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_is_dominated_by_meet_characterization::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_is_dominated_by_total_bound_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_is_dominated_by_implies_total_bound::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_is_dominated_by_partial_order_is_partial_for_diff_line_kind() {
let lhs: AxisHistogram<DiffLineKind> = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
]
.into_iter()
.collect();
let rhs: AxisHistogram<DiffLineKind> = [
DiffLineKind::Removed,
DiffLineKind::Removed,
DiffLineKind::Added,
]
.into_iter()
.collect();
assert_eq!(lhs.count(DiffLineKind::Removed), 1);
assert_eq!(lhs.count(DiffLineKind::Added), 2);
assert_eq!(lhs.count(DiffLineKind::Context), 0);
assert_eq!(rhs.count(DiffLineKind::Removed), 2);
assert_eq!(rhs.count(DiffLineKind::Added), 1);
assert_eq!(rhs.count(DiffLineKind::Context), 0);
assert_eq!(lhs.total(), rhs.total());
assert!(!lhs.is_dominated_by(&rhs));
assert!(!rhs.is_dominated_by(&lhs));
assert!(!lhs.dominates(&rhs));
assert!(!rhs.dominates(&lhs));
let join = lhs.clone().pointwise_max(&rhs);
let meet = lhs.clone().pointwise_min(&rhs);
assert_eq!(join.count(DiffLineKind::Removed), 2);
assert_eq!(join.count(DiffLineKind::Added), 2);
assert_eq!(join.count(DiffLineKind::Context), 0);
assert_eq!(meet.count(DiffLineKind::Removed), 1);
assert_eq!(meet.count(DiffLineKind::Added), 1);
assert_eq!(meet.count(DiffLineKind::Context), 0);
assert!(lhs.is_dominated_by(&join));
assert!(rhs.is_dominated_by(&join));
assert!(meet.is_dominated_by(&lhs));
assert!(meet.is_dominated_by(&rhs));
assert_eq!(join.clone() + &meet, lhs.clone() + &rhs);
}
fn assert_is_strictly_dominated_by_irreflexive<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let empty: AxisHistogram<A> = AxisHistogram::empty();
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
assert!(
!empty.is_strictly_dominated_by(&empty),
"empty.is_strictly_dominated_by(&empty) must read false on axis {}",
std::any::type_name::<A>(),
);
assert!(
!empty.strictly_dominates(&empty),
"empty.strictly_dominates(&empty) must read false on axis {}",
std::any::type_name::<A>(),
);
assert!(
!cover.is_strictly_dominated_by(&cover),
"cover.is_strictly_dominated_by(&cover) must read false on axis {}",
std::any::type_name::<A>(),
);
assert!(
!cover.strictly_dominates(&cover),
"cover.strictly_dominates(&cover) must read false on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_empty_is_strictly_dominated_by_nonempty<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let empty: AxisHistogram<A> = AxisHistogram::empty();
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
assert!(
empty.is_strictly_dominated_by(&cover),
"empty.is_strictly_dominated_by(&cover) must read true on axis {}",
std::any::type_name::<A>(),
);
assert!(
cover.strictly_dominates(&empty),
"cover.strictly_dominates(&empty) must read true on axis {}",
std::any::type_name::<A>(),
);
assert!(
!cover.is_strictly_dominated_by(&empty),
"cover.is_strictly_dominated_by(&empty) must read false on axis {}",
std::any::type_name::<A>(),
);
assert!(
!empty.strictly_dominates(&cover),
"empty.strictly_dominates(&cover) must read false on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_is_strictly_dominated_by_asymmetric<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let bottom: AxisHistogram<A> = AxisHistogram::empty();
let middle: AxisHistogram<A> = axis_iter::<A>().collect();
let top: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
for (lhs, rhs) in [(&bottom, &middle), (&bottom, &top), (&middle, &top)] {
assert!(lhs.is_strictly_dominated_by(rhs));
assert!(
!rhs.is_strictly_dominated_by(lhs),
"asymmetry: a.is_strictly_dominated_by(&b) must imply !b.is_strictly_dominated_by(&a) on axis {}",
std::any::type_name::<A>(),
);
assert!(rhs.strictly_dominates(lhs));
assert!(
!lhs.strictly_dominates(rhs),
"asymmetry (dual): b.strictly_dominates(&a) must imply !a.strictly_dominates(&b) on axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_is_strictly_dominated_by_transitive<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let bottom: AxisHistogram<A> = AxisHistogram::empty();
let middle: AxisHistogram<A> = axis_iter::<A>().collect();
let top: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
assert!(bottom.is_strictly_dominated_by(&middle));
assert!(middle.is_strictly_dominated_by(&top));
assert!(
bottom.is_strictly_dominated_by(&top),
"transitivity: empty < cover < doubled cover must imply empty < doubled cover on axis {}",
std::any::type_name::<A>(),
);
assert!(top.strictly_dominates(&middle));
assert!(middle.strictly_dominates(&bottom));
assert!(
top.strictly_dominates(&bottom),
"transitivity: doubled cover > cover > empty must imply doubled cover > empty on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_strictly_dominates_is_dual_of_is_strictly_dominated_by<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let bottom: AxisHistogram<A> = AxisHistogram::empty();
let middle: AxisHistogram<A> = axis_iter::<A>().collect();
let top: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
for (lhs, rhs) in [
(&bottom, &middle),
(&bottom, &top),
(&middle, &top),
(&middle, &bottom),
(&top, &bottom),
(&top, &middle),
(&middle, &middle),
] {
assert_eq!(
lhs.strictly_dominates(rhs),
rhs.is_strictly_dominated_by(lhs),
"dual: a.strictly_dominates(&b) must equal b.is_strictly_dominated_by(&a) on axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_is_strictly_dominated_by_decomposition<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let bottom: AxisHistogram<A> = AxisHistogram::empty();
let middle: AxisHistogram<A> = axis_iter::<A>().collect();
let top: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
for (lhs, rhs) in [
(&bottom, &middle),
(&bottom, &top),
(&middle, &top),
(&middle, &middle),
(&middle, &bottom),
(&top, &middle),
] {
let strict = lhs.is_strictly_dominated_by(rhs);
let composite = lhs.is_dominated_by(rhs) && lhs != rhs;
assert_eq!(
strict,
composite,
"decomposition: a.is_strictly_dominated_by(&b) must equal (a.is_dominated_by(&b) && a != b) on axis {}",
std::any::type_name::<A>(),
);
let strict_dual = lhs.strictly_dominates(rhs);
let composite_dual = lhs.dominates(rhs) && lhs != rhs;
assert_eq!(
strict_dual,
composite_dual,
"decomposition (dual): a.strictly_dominates(&b) must equal (a.dominates(&b) && a != b) on axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_is_strictly_dominated_by_implies_strict_total_bound<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let bottom: AxisHistogram<A> = AxisHistogram::empty();
let middle: AxisHistogram<A> = axis_iter::<A>().collect();
let top: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
for (lhs, rhs) in [(&bottom, &middle), (&bottom, &top), (&middle, &top)] {
assert!(lhs.is_strictly_dominated_by(rhs));
assert!(
lhs.total() < rhs.total(),
"strict total bound: a.is_strictly_dominated_by(&b) must imply a.total() < b.total() on axis {}",
std::any::type_name::<A>(),
);
}
}
#[test]
fn axis_histogram_is_strictly_dominated_by_irreflexive_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_is_strictly_dominated_by_irreflexive::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_empty_is_strictly_dominated_by_nonempty_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_empty_is_strictly_dominated_by_nonempty::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_is_strictly_dominated_by_asymmetric_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_is_strictly_dominated_by_asymmetric::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_is_strictly_dominated_by_transitive_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_is_strictly_dominated_by_transitive::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_strictly_dominates_is_dual_of_is_strictly_dominated_by_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_strictly_dominates_is_dual_of_is_strictly_dominated_by::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_is_strictly_dominated_by_decomposition_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_is_strictly_dominated_by_decomposition::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_is_strictly_dominated_by_strict_total_bound_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_is_strictly_dominated_by_implies_strict_total_bound::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_is_strictly_dominated_by_partial_order_is_partial_for_diff_line_kind() {
let lhs: AxisHistogram<DiffLineKind> = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
]
.into_iter()
.collect();
let rhs: AxisHistogram<DiffLineKind> = [
DiffLineKind::Removed,
DiffLineKind::Removed,
DiffLineKind::Added,
]
.into_iter()
.collect();
assert!(!lhs.is_strictly_dominated_by(&rhs));
assert!(!rhs.is_strictly_dominated_by(&lhs));
assert!(!lhs.strictly_dominates(&rhs));
assert!(!rhs.strictly_dominates(&lhs));
let join = lhs.clone().pointwise_max(&rhs);
let meet = lhs.clone().pointwise_min(&rhs);
assert!(lhs.is_strictly_dominated_by(&join));
assert!(rhs.is_strictly_dominated_by(&join));
assert!(meet.is_strictly_dominated_by(&lhs));
assert!(meet.is_strictly_dominated_by(&rhs));
assert!(join.strictly_dominates(&lhs));
assert!(join.strictly_dominates(&rhs));
assert!(lhs.strictly_dominates(&meet));
assert!(rhs.strictly_dominates(&meet));
assert!(meet.total() < lhs.total());
assert!(meet.total() < rhs.total());
assert!(lhs.total() < join.total());
assert!(rhs.total() < join.total());
}
fn assert_partial_cmp_reflexive_equal<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
use std::cmp::Ordering;
let empty_lhs: AxisHistogram<A> = AxisHistogram::empty();
let empty_rhs: AxisHistogram<A> = AxisHistogram::empty();
let cover_lhs: AxisHistogram<A> = axis_iter::<A>().collect();
let cover_rhs: AxisHistogram<A> = axis_iter::<A>().collect();
assert_eq!(
empty_lhs.partial_cmp(&empty_rhs),
Some(Ordering::Equal),
"empty.partial_cmp(&empty) must be Some(Equal) on axis {}",
std::any::type_name::<A>(),
);
assert_eq!(
cover_lhs.partial_cmp(&cover_rhs),
Some(Ordering::Equal),
"cover.partial_cmp(&cover) must be Some(Equal) on axis {}",
std::any::type_name::<A>(),
);
assert!(empty_lhs <= empty_rhs);
assert!(empty_lhs >= empty_rhs);
assert!(cover_lhs <= cover_rhs);
assert!(cover_lhs >= cover_rhs);
}
fn assert_partial_cmp_empty_is_lattice_bottom<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
use std::cmp::Ordering;
let empty: AxisHistogram<A> = AxisHistogram::empty();
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
assert_eq!(
empty.partial_cmp(&cover),
Some(Ordering::Less),
"empty.partial_cmp(&cover) must be Some(Less) on axis {}",
std::any::type_name::<A>(),
);
assert_eq!(
cover.partial_cmp(&empty),
Some(Ordering::Greater),
"cover.partial_cmp(&empty) must be Some(Greater) on axis {}",
std::any::type_name::<A>(),
);
assert!(empty < cover);
assert!(cover > empty);
assert!(empty <= cover);
assert!(cover >= empty);
}
fn assert_partial_cmp_matches_dominance_quartet<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let bottom: AxisHistogram<A> = AxisHistogram::empty();
let middle: AxisHistogram<A> = axis_iter::<A>().collect();
let top: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
for (lhs, rhs) in [
(&bottom, &bottom),
(&bottom, &middle),
(&bottom, &top),
(&middle, &bottom),
(&middle, &middle),
(&middle, &top),
(&top, &bottom),
(&top, &middle),
(&top, &top),
] {
assert_eq!(
lhs <= rhs,
lhs.is_dominated_by(rhs),
"`<=` operator must match is_dominated_by on axis {}",
std::any::type_name::<A>(),
);
assert_eq!(
lhs >= rhs,
lhs.dominates(rhs),
"`>=` operator must match dominates on axis {}",
std::any::type_name::<A>(),
);
assert_eq!(
lhs < rhs,
lhs.is_strictly_dominated_by(rhs),
"`<` operator must match is_strictly_dominated_by on axis {}",
std::any::type_name::<A>(),
);
assert_eq!(
lhs > rhs,
lhs.strictly_dominates(rhs),
"`>` operator must match strictly_dominates on axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_partial_cmp_return_matches_inherent<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
use std::cmp::Ordering;
let bottom: AxisHistogram<A> = AxisHistogram::empty();
let middle: AxisHistogram<A> = axis_iter::<A>().collect();
let top: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
for (lhs, rhs) in [
(&bottom, &bottom),
(&bottom, &middle),
(&middle, &top),
(&top, &bottom),
(&middle, &middle),
] {
match lhs.partial_cmp(rhs) {
Some(Ordering::Equal) => assert_eq!(
lhs,
rhs,
"Some(Equal) must witness equality on axis {}",
std::any::type_name::<A>(),
),
Some(Ordering::Less) => assert!(
lhs.is_strictly_dominated_by(rhs),
"Some(Less) must witness is_strictly_dominated_by on axis {}",
std::any::type_name::<A>(),
),
Some(Ordering::Greater) => assert!(
lhs.strictly_dominates(rhs),
"Some(Greater) must witness strictly_dominates on axis {}",
std::any::type_name::<A>(),
),
None => {
assert!(!lhs.is_dominated_by(rhs));
assert!(!rhs.is_dominated_by(lhs));
}
}
}
}
fn assert_partial_cmp_antisymmetric_on_ordered_pairs<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
use std::cmp::Ordering;
let bottom: AxisHistogram<A> = AxisHistogram::empty();
let middle: AxisHistogram<A> = axis_iter::<A>().collect();
let top: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
for (lhs, rhs) in [
(&bottom, &middle),
(&bottom, &top),
(&middle, &top),
(&middle, &middle),
] {
let forward = lhs.partial_cmp(rhs);
let reverse = rhs.partial_cmp(lhs);
let expected = forward.map(Ordering::reverse);
assert_eq!(
reverse,
expected,
"antisymmetry: rhs.partial_cmp(&lhs) must equal forward.reverse() on axis {}",
std::any::type_name::<A>(),
);
}
}
#[test]
fn axis_histogram_partial_cmp_reflexive_equal_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_partial_cmp_reflexive_equal::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_partial_cmp_empty_is_lattice_bottom_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_partial_cmp_empty_is_lattice_bottom::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_partial_cmp_matches_dominance_quartet_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_partial_cmp_matches_dominance_quartet::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_partial_cmp_return_matches_inherent_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_partial_cmp_return_matches_inherent::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_partial_cmp_antisymmetric_on_ordered_pairs_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_partial_cmp_antisymmetric_on_ordered_pairs::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_partial_cmp_incomparable_returns_none_for_diff_line_kind() {
let lhs: AxisHistogram<DiffLineKind> = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
]
.into_iter()
.collect();
let rhs: AxisHistogram<DiffLineKind> = [
DiffLineKind::Removed,
DiffLineKind::Removed,
DiffLineKind::Added,
]
.into_iter()
.collect();
assert_eq!(
lhs.partial_cmp(&rhs),
None,
"incomparable lhs/rhs must partial_cmp to None",
);
assert_eq!(
rhs.partial_cmp(&lhs),
None,
"incomparable rhs/lhs must partial_cmp to None",
);
assert!(!lhs.is_dominated_by(&rhs));
assert!(!rhs.is_dominated_by(&lhs));
assert!(!lhs.is_strictly_dominated_by(&rhs));
assert!(!rhs.is_strictly_dominated_by(&lhs));
assert!(!lhs.dominates(&rhs));
assert!(!rhs.dominates(&lhs));
assert!(!lhs.strictly_dominates(&rhs));
assert!(!rhs.strictly_dominates(&lhs));
let join = lhs.clone().pointwise_max(&rhs);
let meet = lhs.clone().pointwise_min(&rhs);
assert!(lhs < join);
assert!(rhs < join);
assert!(meet < lhs);
assert!(meet < rhs);
assert!(join > lhs);
assert!(join > rhs);
assert!(lhs > meet);
assert!(rhs > meet);
}
fn assert_is_disjoint_from_symmetric<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let empty: AxisHistogram<A> = AxisHistogram::empty();
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
for (lhs, rhs) in [
(&empty, &empty),
(&empty, &cover),
(&cover, &empty),
(&cover, &cover),
] {
assert_eq!(
lhs.is_disjoint_from(rhs),
rhs.is_disjoint_from(lhs),
"symmetry: a.is_disjoint_from(&b) must equal b.is_disjoint_from(&a) on axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_empty_is_disjoint_from_every<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let empty: AxisHistogram<A> = AxisHistogram::empty();
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
let doubled: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
for rhs in [&empty, &cover, &doubled] {
assert!(
empty.is_disjoint_from(rhs),
"empty.is_disjoint_from(&hist) must read true on axis {}",
std::any::type_name::<A>(),
);
assert!(
rhs.is_disjoint_from(&empty),
"hist.is_disjoint_from(&empty) must read true on axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_self_disjoint_iff_empty<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let empty: AxisHistogram<A> = AxisHistogram::empty();
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
let doubled: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
for hist in [&empty, &cover, &doubled] {
assert_eq!(
hist.is_disjoint_from(hist),
hist.is_empty(),
"self-disjoint iff empty: hist.is_disjoint_from(&hist) must equal hist.is_empty() on axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_intersects_is_dual_of_is_disjoint_from<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let empty: AxisHistogram<A> = AxisHistogram::empty();
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
for (lhs, rhs) in [
(&empty, &empty),
(&empty, &cover),
(&cover, &empty),
(&cover, &cover),
] {
assert_eq!(
lhs.intersects(rhs),
!lhs.is_disjoint_from(rhs),
"dual: a.intersects(&b) must equal !a.is_disjoint_from(&b) on axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_is_disjoint_from_meet_characterization<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let empty: AxisHistogram<A> = AxisHistogram::empty();
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
for (lhs, rhs) in [
(&empty, &empty),
(&empty, &cover),
(&cover, &empty),
(&cover, &cover),
] {
let predicate = lhs.is_disjoint_from(rhs);
let meet_empty = lhs.clone().pointwise_min(rhs).is_empty();
assert_eq!(
predicate,
meet_empty,
"meet characterization: a.is_disjoint_from(&b) must equal a.pointwise_min(&b).is_empty() on axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_is_disjoint_from_dominance_preservation<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let empty: AxisHistogram<A> = AxisHistogram::empty();
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
let doubled: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
for b in [&empty, &cover, &doubled] {
assert!(empty.is_disjoint_from(b));
assert!(empty.is_dominated_by(&empty));
assert!(
empty.is_disjoint_from(b),
"dominance preservation: empty <= empty and empty.is_disjoint_from(b) must imply empty.is_disjoint_from(b) on axis {}",
std::any::type_name::<A>(),
);
}
}
#[test]
fn axis_histogram_is_disjoint_from_symmetric_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_is_disjoint_from_symmetric::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_empty_is_disjoint_from_every_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_empty_is_disjoint_from_every::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_self_disjoint_iff_empty_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_self_disjoint_iff_empty::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_intersects_is_dual_of_is_disjoint_from_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_intersects_is_dual_of_is_disjoint_from::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_is_disjoint_from_meet_characterization_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_is_disjoint_from_meet_characterization::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_is_disjoint_from_dominance_preservation_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_is_disjoint_from_dominance_preservation::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_is_disjoint_from_witnessed_on_disjoint_supports_for_diff_line_kind() {
let added_only: AxisHistogram<DiffLineKind> = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Added,
]
.into_iter()
.collect();
let removed_only: AxisHistogram<DiffLineKind> =
[DiffLineKind::Removed, DiffLineKind::Removed]
.into_iter()
.collect();
let mixed: AxisHistogram<DiffLineKind> = [DiffLineKind::Added, DiffLineKind::Removed]
.into_iter()
.collect();
assert!(added_only.is_disjoint_from(&removed_only));
assert!(removed_only.is_disjoint_from(&added_only));
assert!(!added_only.intersects(&removed_only));
assert!(!removed_only.intersects(&added_only));
assert!(!added_only.is_empty());
assert!(!removed_only.is_empty());
assert!(!added_only.is_disjoint_from(&mixed));
assert!(!mixed.is_disjoint_from(&added_only));
assert!(added_only.intersects(&mixed));
assert!(mixed.intersects(&added_only));
assert!(!removed_only.is_disjoint_from(&mixed));
assert!(!mixed.is_disjoint_from(&removed_only));
assert!(removed_only.intersects(&mixed));
assert!(mixed.intersects(&removed_only));
let disjoint_meet = added_only.clone().pointwise_min(&removed_only);
let intersecting_meet = added_only.clone().pointwise_min(&mixed);
assert!(disjoint_meet.is_empty());
assert!(!intersecting_meet.is_empty());
assert_eq!(
disjoint_meet,
AxisHistogram::<DiffLineKind>::empty(),
"disjoint meet must equal empty"
);
assert_eq!(intersecting_meet.count(DiffLineKind::Added), 1);
assert_eq!(intersecting_meet.count(DiffLineKind::Removed), 0);
let disjoint_join = added_only.clone().pointwise_max(&removed_only);
let disjoint_sum = added_only.clone() + &removed_only;
assert_eq!(
disjoint_join, disjoint_sum,
"join-equals-add on disjoint pairs: pointwise_max must equal Add on the disjoint pair"
);
assert_eq!(disjoint_join.count(DiffLineKind::Added), 3);
assert_eq!(disjoint_join.count(DiffLineKind::Removed), 2);
assert_eq!(disjoint_join.count(DiffLineKind::Context), 0);
assert_eq!(disjoint_join.total(), 5);
}
fn assert_symmetric_difference_is_symmetric<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let empty: AxisHistogram<A> = AxisHistogram::empty();
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
let doubled: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
for (lhs, rhs) in [
(&empty, &empty),
(&empty, &cover),
(&cover, &empty),
(&cover, &doubled),
(&doubled, &cover),
] {
assert_eq!(
lhs.clone().symmetric_difference(rhs),
rhs.clone().symmetric_difference(lhs),
"symmetry: a.symmetric_difference(&b) must equal b.symmetric_difference(&a) on axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_symmetric_difference_self_cancels_to_empty<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let empty: AxisHistogram<A> = AxisHistogram::empty();
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
let doubled: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
for hist in [&empty, &cover, &doubled] {
assert_eq!(
hist.clone().symmetric_difference(hist),
AxisHistogram::<A>::empty(),
"self-cancellation: hist.symmetric_difference(&hist) must equal AxisHistogram::empty() on axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_symmetric_difference_empty_is_identity<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let empty: AxisHistogram<A> = AxisHistogram::empty();
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
let doubled: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
for hist in [&empty, &cover, &doubled] {
assert_eq!(
hist.clone().symmetric_difference(&empty),
hist.clone(),
"right identity: hist.symmetric_difference(&empty) must equal hist on axis {}",
std::any::type_name::<A>(),
);
assert_eq!(
empty.clone().symmetric_difference(hist),
hist.clone(),
"left identity: empty.symmetric_difference(&hist) must equal hist on axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_symmetric_difference_lattice_bridge<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let empty: AxisHistogram<A> = AxisHistogram::empty();
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
let doubled: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
for (lhs, rhs) in [
(&empty, &empty),
(&empty, &cover),
(&cover, &empty),
(&cover, &doubled),
(&doubled, &cover),
] {
let sym_diff = lhs.clone().symmetric_difference(rhs);
let join = lhs.clone().pointwise_max(rhs);
let meet = lhs.clone().pointwise_min(rhs);
let bridge = join - &meet;
assert_eq!(
sym_diff,
bridge,
"lattice bridge: a.symmetric_difference(&b) must equal a.pointwise_max(&b) - a.pointwise_min(&b) on axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_symmetric_difference_add_bridge<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let empty: AxisHistogram<A> = AxisHistogram::empty();
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
let doubled: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
for (lhs, rhs) in [
(&empty, &empty),
(&empty, &cover),
(&cover, &empty),
(&cover, &doubled),
(&doubled, &cover),
] {
let sym_diff = lhs.clone().symmetric_difference(rhs);
let meet = lhs.clone().pointwise_min(rhs);
let twice_meet = meet * 2;
let recovered = sym_diff + &twice_meet;
let sum = lhs.clone() + rhs;
assert_eq!(
recovered,
sum,
"add bridge: a.symmetric_difference(&b) + 2 * a.pointwise_min(&b) must equal a + b on axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_symmetric_difference_disjoint_equals_add<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let empty: AxisHistogram<A> = AxisHistogram::empty();
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
for (lhs, rhs) in [(&empty, &empty), (&empty, &cover), (&cover, &empty)] {
assert!(
lhs.is_disjoint_from(rhs),
"witness pair must be disjoint on axis {}",
std::any::type_name::<A>(),
);
let sym_diff = lhs.clone().symmetric_difference(rhs);
let sum = lhs.clone() + rhs;
assert_eq!(
sym_diff,
sum,
"disjoint specialization: a.is_disjoint_from(&b) ⇒ a.symmetric_difference(&b) must equal a + b on axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_symmetric_difference_dominance_specializes_to_sub<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let empty: AxisHistogram<A> = AxisHistogram::empty();
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
let doubled: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
for (lhs, rhs) in [(&empty, &cover), (&empty, &doubled), (&cover, &doubled)] {
assert!(
lhs.is_dominated_by(rhs),
"witness pair must be comparable on axis {}",
std::any::type_name::<A>(),
);
let sym_diff = lhs.clone().symmetric_difference(rhs);
let directed = rhs.clone() - lhs;
assert_eq!(
sym_diff,
directed,
"dominance specialization: a.is_dominated_by(&b) ⇒ a.symmetric_difference(&b) must equal b - a on axis {}",
std::any::type_name::<A>(),
);
}
}
#[test]
fn axis_histogram_symmetric_difference_is_symmetric_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_symmetric_difference_is_symmetric::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_symmetric_difference_self_cancels_to_empty_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_symmetric_difference_self_cancels_to_empty::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_symmetric_difference_empty_is_identity_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_symmetric_difference_empty_is_identity::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_symmetric_difference_lattice_bridge_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_symmetric_difference_lattice_bridge::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_symmetric_difference_add_bridge_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_symmetric_difference_add_bridge::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_symmetric_difference_disjoint_equals_add_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_symmetric_difference_disjoint_equals_add::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_symmetric_difference_dominance_specializes_to_sub_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_symmetric_difference_dominance_specializes_to_sub::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_symmetric_difference_witnessed_on_partial_overlap_for_diff_line_kind() {
let lhs: AxisHistogram<DiffLineKind> =
[(DiffLineKind::Added, 5), (DiffLineKind::Removed, 2)]
.into_iter()
.collect();
let rhs: AxisHistogram<DiffLineKind> = [
(DiffLineKind::Added, 1),
(DiffLineKind::Removed, 4),
(DiffLineKind::Context, 3),
]
.into_iter()
.collect();
let sym_diff = lhs.clone().symmetric_difference(&rhs);
assert_eq!(sym_diff.count(DiffLineKind::Added), 4);
assert_eq!(sym_diff.count(DiffLineKind::Removed), 2);
assert_eq!(sym_diff.count(DiffLineKind::Context), 3);
assert_eq!(sym_diff.total(), 9);
let sym_diff_rev = rhs.clone().symmetric_difference(&lhs);
assert_eq!(sym_diff, sym_diff_rev);
let join = lhs.clone().pointwise_max(&rhs);
let meet = lhs.clone().pointwise_min(&rhs);
assert_eq!(join.count(DiffLineKind::Added), 5);
assert_eq!(join.count(DiffLineKind::Removed), 4);
assert_eq!(join.count(DiffLineKind::Context), 3);
assert_eq!(meet.count(DiffLineKind::Added), 1);
assert_eq!(meet.count(DiffLineKind::Removed), 2);
assert_eq!(meet.count(DiffLineKind::Context), 0);
let bridge = join.clone() - &meet;
assert_eq!(bridge, sym_diff);
let twice_meet = meet.clone() * 2;
let recovered = sym_diff.clone() + &twice_meet;
let sum = lhs.clone() + &rhs;
assert_eq!(recovered, sum);
assert_eq!(sum.count(DiffLineKind::Added), 6);
assert_eq!(sum.count(DiffLineKind::Removed), 6);
assert_eq!(sum.count(DiffLineKind::Context), 3);
assert!(meet.is_dominated_by(&lhs));
assert!(meet.is_dominated_by(&rhs));
let meet_sym_lhs = meet.clone().symmetric_difference(&lhs);
let meet_sub_lhs = lhs.clone() - &meet;
assert_eq!(meet_sym_lhs, meet_sub_lhs);
let meet_sym_rhs = meet.clone().symmetric_difference(&rhs);
let meet_sub_rhs = rhs.clone() - &meet;
assert_eq!(meet_sym_rhs, meet_sub_rhs);
}
fn assert_bitxor_assign_ref_equals_symmetric_difference<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let empty: AxisHistogram<A> = AxisHistogram::empty();
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
let doubled: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
for (lhs, rhs) in [
(&empty, &empty),
(&empty, &cover),
(&cover, &empty),
(&cover, &doubled),
(&doubled, &cover),
] {
let mut via_op = lhs.clone();
via_op ^= rhs;
let via_method = lhs.clone().symmetric_difference(rhs);
assert_eq!(
via_op,
via_method,
"bitxor_assign ref must equal symmetric_difference on axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_bitxor_assign_ref_empty_rhs_is_identity<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let empty: AxisHistogram<A> = AxisHistogram::empty();
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
let doubled: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
for hist in [&empty, &cover, &doubled] {
let mut applied = hist.clone();
applied ^= ∅
assert_eq!(
applied,
hist.clone(),
"hist ^= &empty must leave hist unchanged on axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_bitxor_assign_ref_self_yields_empty<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let empty: AxisHistogram<A> = AxisHistogram::empty();
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
let doubled: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
for hist in [&empty, &cover, &doubled] {
let mut applied = hist.clone();
applied ^= hist;
assert_eq!(
applied,
AxisHistogram::<A>::empty(),
"hist ^= &hist must equal AxisHistogram::empty() on axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_bitxor_assign_ref_is_symmetric<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let empty: AxisHistogram<A> = AxisHistogram::empty();
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
let doubled: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
for (lhs, rhs) in [
(&empty, &empty),
(&empty, &cover),
(&cover, &empty),
(&cover, &doubled),
(&doubled, &cover),
] {
let mut from_lhs = lhs.clone();
from_lhs ^= rhs;
let mut from_rhs = rhs.clone();
from_rhs ^= lhs;
assert_eq!(
from_lhs,
from_rhs,
"bitxor_assign symmetry: lhs ^= &rhs must equal rhs ^= &lhs on axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_bitxor_assign_ref_cell_level_abs_diff<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let before: AxisHistogram<A> = axis_iter::<A>().collect();
let other: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
let mut after = before.clone();
after ^= &other;
for cell in axis_iter::<A>() {
assert_eq!(
after.count(cell),
before.count(cell).abs_diff(other.count(cell)),
"hist ^= &other cell {cell:?} must equal before.count.abs_diff(other.count) on axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_bitxor_owned_equals_bitxor_ref<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let empty: AxisHistogram<A> = AxisHistogram::empty();
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
let doubled: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
for (lhs, rhs) in [
(&empty, &empty),
(&empty, &cover),
(&cover, &empty),
(&cover, &doubled),
(&doubled, &cover),
] {
let via_owned = lhs.clone() ^ rhs.clone();
let via_ref = lhs.clone() ^ rhs;
assert_eq!(
via_owned,
via_ref,
"lhs ^ rhs must equal lhs ^ &rhs on axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_bitxor_assign_owned_equals_bitxor_assign_ref<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let empty: AxisHistogram<A> = AxisHistogram::empty();
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
let doubled: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
for (lhs, rhs) in [
(&empty, &empty),
(&empty, &cover),
(&cover, &empty),
(&cover, &doubled),
(&doubled, &cover),
] {
let mut via_owned = lhs.clone();
via_owned ^= rhs.clone();
let mut via_ref = lhs.clone();
via_ref ^= rhs;
assert_eq!(
via_owned,
via_ref,
"(a ^= rhs) must equal (a ^= &rhs) on axis {}",
std::any::type_name::<A>(),
);
}
}
#[test]
fn axis_histogram_bitxor_assign_ref_equals_symmetric_difference_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_bitxor_assign_ref_equals_symmetric_difference::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_bitxor_assign_ref_empty_rhs_is_identity_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_bitxor_assign_ref_empty_rhs_is_identity::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_bitxor_assign_ref_self_yields_empty_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_bitxor_assign_ref_self_yields_empty::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_bitxor_assign_ref_is_symmetric_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_bitxor_assign_ref_is_symmetric::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_bitxor_assign_ref_cell_level_abs_diff_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_bitxor_assign_ref_cell_level_abs_diff::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_bitxor_owned_equals_bitxor_ref_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_bitxor_owned_equals_bitxor_ref::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_bitxor_assign_owned_equals_bitxor_assign_ref_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_bitxor_assign_owned_equals_bitxor_assign_ref::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_bitxor_witnessed_on_partial_overlap_for_diff_line_kind() {
let lhs: AxisHistogram<DiffLineKind> =
[(DiffLineKind::Added, 5), (DiffLineKind::Removed, 2)]
.into_iter()
.collect();
let rhs: AxisHistogram<DiffLineKind> = [
(DiffLineKind::Added, 1),
(DiffLineKind::Removed, 4),
(DiffLineKind::Context, 3),
]
.into_iter()
.collect();
let via_op = lhs.clone() ^ &rhs;
assert_eq!(via_op.count(DiffLineKind::Added), 4);
assert_eq!(via_op.count(DiffLineKind::Removed), 2);
assert_eq!(via_op.count(DiffLineKind::Context), 3);
assert_eq!(via_op.total(), 9);
let via_method = lhs.clone().symmetric_difference(&rhs);
assert_eq!(via_op, via_method);
let via_op_rev = rhs.clone() ^ &lhs;
assert_eq!(via_op, via_op_rev);
let self_canceled = lhs.clone() ^ &lhs;
assert_eq!(self_canceled, AxisHistogram::<DiffLineKind>::empty());
let mut via_assign = lhs.clone();
via_assign ^= &rhs;
assert_eq!(via_assign, via_op);
}
fn assert_bitor_assign_ref_equals_pointwise_max<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let empty: AxisHistogram<A> = AxisHistogram::empty();
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
let doubled: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
for (lhs, rhs) in [
(&empty, &empty),
(&empty, &cover),
(&cover, &empty),
(&cover, &doubled),
(&doubled, &cover),
] {
let mut via_op = lhs.clone();
via_op |= rhs;
let via_method = lhs.clone().pointwise_max(rhs);
assert_eq!(
via_op,
via_method,
"bitor_assign ref must equal pointwise_max on axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_bitor_assign_ref_empty_rhs_is_identity<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let empty: AxisHistogram<A> = AxisHistogram::empty();
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
let doubled: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
for hist in [&empty, &cover, &doubled] {
let mut applied = hist.clone();
applied |= ∅
assert_eq!(
applied,
hist.clone(),
"hist |= &empty must leave hist unchanged on axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_bitor_assign_ref_self_is_identity<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let empty: AxisHistogram<A> = AxisHistogram::empty();
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
let doubled: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
for hist in [&empty, &cover, &doubled] {
let mut applied = hist.clone();
applied |= hist;
assert_eq!(
applied,
hist.clone(),
"hist |= &hist must equal hist on axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_bitor_assign_ref_is_commutative<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let empty: AxisHistogram<A> = AxisHistogram::empty();
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
let doubled: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
for (lhs, rhs) in [
(&empty, &empty),
(&empty, &cover),
(&cover, &empty),
(&cover, &doubled),
(&doubled, &cover),
] {
let mut from_lhs = lhs.clone();
from_lhs |= rhs;
let mut from_rhs = rhs.clone();
from_rhs |= lhs;
assert_eq!(
from_lhs,
from_rhs,
"bitor_assign commutativity: lhs |= &rhs must equal rhs |= &lhs on axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_bitor_assign_ref_cell_level_max<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let before: AxisHistogram<A> = axis_iter::<A>().collect();
let other: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
let mut after = before.clone();
after |= &other;
for cell in axis_iter::<A>() {
assert_eq!(
after.count(cell),
before.count(cell).max(other.count(cell)),
"hist |= &other cell {cell:?} must equal before.count.max(other.count) on axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_bitor_assign_ref_dominates_both_sides<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let empty: AxisHistogram<A> = AxisHistogram::empty();
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
let doubled: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
for (lhs, rhs) in [
(&empty, &empty),
(&empty, &cover),
(&cover, &empty),
(&cover, &doubled),
(&doubled, &cover),
] {
let mut joined = lhs.clone();
joined |= rhs;
assert!(
lhs.is_dominated_by(&joined),
"lhs |= &rhs must dominate lhs on axis {}",
std::any::type_name::<A>(),
);
assert!(
rhs.is_dominated_by(&joined),
"lhs |= &rhs must dominate rhs on axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_bitor_owned_equals_bitor_ref<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let empty: AxisHistogram<A> = AxisHistogram::empty();
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
let doubled: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
for (lhs, rhs) in [
(&empty, &empty),
(&empty, &cover),
(&cover, &empty),
(&cover, &doubled),
(&doubled, &cover),
] {
let via_owned = lhs.clone() | rhs.clone();
let via_ref = lhs.clone() | rhs;
assert_eq!(
via_owned,
via_ref,
"lhs | rhs must equal lhs | &rhs on axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_bitor_assign_owned_equals_bitor_assign_ref<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let empty: AxisHistogram<A> = AxisHistogram::empty();
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
let doubled: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
for (lhs, rhs) in [
(&empty, &empty),
(&empty, &cover),
(&cover, &empty),
(&cover, &doubled),
(&doubled, &cover),
] {
let mut via_owned = lhs.clone();
via_owned |= rhs.clone();
let mut via_ref = lhs.clone();
via_ref |= rhs;
assert_eq!(
via_owned,
via_ref,
"(a |= rhs) must equal (a |= &rhs) on axis {}",
std::any::type_name::<A>(),
);
}
}
#[test]
fn axis_histogram_bitor_assign_ref_equals_pointwise_max_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_bitor_assign_ref_equals_pointwise_max::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_bitor_assign_ref_empty_rhs_is_identity_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_bitor_assign_ref_empty_rhs_is_identity::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_bitor_assign_ref_self_is_identity_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_bitor_assign_ref_self_is_identity::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_bitor_assign_ref_is_commutative_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_bitor_assign_ref_is_commutative::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_bitor_assign_ref_cell_level_max_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_bitor_assign_ref_cell_level_max::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_bitor_assign_ref_dominates_both_sides_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_bitor_assign_ref_dominates_both_sides::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_bitor_owned_equals_bitor_ref_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_bitor_owned_equals_bitor_ref::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_bitor_assign_owned_equals_bitor_assign_ref_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_bitor_assign_owned_equals_bitor_assign_ref::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_bitor_witnessed_on_partial_overlap_for_diff_line_kind() {
let lhs: AxisHistogram<DiffLineKind> =
[(DiffLineKind::Added, 5), (DiffLineKind::Removed, 2)]
.into_iter()
.collect();
let rhs: AxisHistogram<DiffLineKind> = [
(DiffLineKind::Added, 1),
(DiffLineKind::Removed, 4),
(DiffLineKind::Context, 3),
]
.into_iter()
.collect();
let via_op = lhs.clone() | &rhs;
assert_eq!(via_op.count(DiffLineKind::Added), 5);
assert_eq!(via_op.count(DiffLineKind::Removed), 4);
assert_eq!(via_op.count(DiffLineKind::Context), 3);
assert_eq!(via_op.total(), 12);
let via_method = lhs.clone().pointwise_max(&rhs);
assert_eq!(via_op, via_method);
let via_op_rev = rhs.clone() | &lhs;
assert_eq!(via_op, via_op_rev);
let self_joined = lhs.clone() | &lhs;
assert_eq!(self_joined, lhs);
let mut via_assign = lhs.clone();
via_assign |= &rhs;
assert_eq!(via_assign, via_op);
assert!(lhs.is_dominated_by(&via_op));
assert!(rhs.is_dominated_by(&via_op));
}
fn assert_bitand_assign_ref_equals_pointwise_min<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let empty: AxisHistogram<A> = AxisHistogram::empty();
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
let doubled: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
for (lhs, rhs) in [
(&empty, &empty),
(&empty, &cover),
(&cover, &empty),
(&cover, &doubled),
(&doubled, &cover),
] {
let mut via_op = lhs.clone();
via_op &= rhs;
let via_method = lhs.clone().pointwise_min(rhs);
assert_eq!(
via_op,
via_method,
"bitand_assign ref must equal pointwise_min on axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_bitand_assign_ref_empty_rhs_is_empty<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let empty: AxisHistogram<A> = AxisHistogram::empty();
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
let doubled: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
for hist in [&empty, &cover, &doubled] {
let mut applied = hist.clone();
applied &= ∅
assert_eq!(
applied,
empty,
"hist &= &empty must zero hist on axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_bitand_assign_ref_self_is_identity<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let empty: AxisHistogram<A> = AxisHistogram::empty();
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
let doubled: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
for hist in [&empty, &cover, &doubled] {
let mut applied = hist.clone();
applied &= hist;
assert_eq!(
applied,
hist.clone(),
"hist &= &hist must equal hist on axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_bitand_assign_ref_is_commutative<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let empty: AxisHistogram<A> = AxisHistogram::empty();
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
let doubled: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
for (lhs, rhs) in [
(&empty, &empty),
(&empty, &cover),
(&cover, &empty),
(&cover, &doubled),
(&doubled, &cover),
] {
let mut from_lhs = lhs.clone();
from_lhs &= rhs;
let mut from_rhs = rhs.clone();
from_rhs &= lhs;
assert_eq!(
from_lhs,
from_rhs,
"bitand_assign commutativity: lhs &= &rhs must equal rhs &= &lhs on axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_bitand_assign_ref_cell_level_min<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let before: AxisHistogram<A> = axis_iter::<A>().collect();
let other: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
let mut after = before.clone();
after &= &other;
for cell in axis_iter::<A>() {
assert_eq!(
after.count(cell),
before.count(cell).min(other.count(cell)),
"hist &= &other cell {cell:?} must equal before.count.min(other.count) on axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_bitand_assign_ref_dominated_by_both_sides<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let empty: AxisHistogram<A> = AxisHistogram::empty();
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
let doubled: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
for (lhs, rhs) in [
(&empty, &empty),
(&empty, &cover),
(&cover, &empty),
(&cover, &doubled),
(&doubled, &cover),
] {
let mut met = lhs.clone();
met &= rhs;
assert!(
met.is_dominated_by(lhs),
"lhs &= &rhs must be dominated by lhs on axis {}",
std::any::type_name::<A>(),
);
assert!(
met.is_dominated_by(rhs),
"lhs &= &rhs must be dominated by rhs on axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_bitor_plus_bitand_equals_add<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let a: AxisHistogram<A> = axis_iter::<A>().collect();
let b: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
let join = a.clone() | &b;
let meet = a.clone() & &b;
let lhs = join + &meet;
let rhs = a + &b;
assert_eq!(
lhs,
rhs,
"(a | &b) + &(a & &b) must equal a + &b on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_bitand_owned_equals_bitand_ref<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let empty: AxisHistogram<A> = AxisHistogram::empty();
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
let doubled: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
for (lhs, rhs) in [
(&empty, &empty),
(&empty, &cover),
(&cover, &empty),
(&cover, &doubled),
(&doubled, &cover),
] {
let via_owned = lhs.clone() & rhs.clone();
let via_ref = lhs.clone() & rhs;
assert_eq!(
via_owned,
via_ref,
"lhs & rhs must equal lhs & &rhs on axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_bitand_assign_owned_equals_bitand_assign_ref<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let empty: AxisHistogram<A> = AxisHistogram::empty();
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
let doubled: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
for (lhs, rhs) in [
(&empty, &empty),
(&empty, &cover),
(&cover, &empty),
(&cover, &doubled),
(&doubled, &cover),
] {
let mut via_owned = lhs.clone();
via_owned &= rhs.clone();
let mut via_ref = lhs.clone();
via_ref &= rhs;
assert_eq!(
via_owned,
via_ref,
"(a &= rhs) must equal (a &= &rhs) on axis {}",
std::any::type_name::<A>(),
);
}
}
#[test]
fn axis_histogram_bitand_assign_ref_equals_pointwise_min_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_bitand_assign_ref_equals_pointwise_min::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_bitand_assign_ref_empty_rhs_is_empty_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_bitand_assign_ref_empty_rhs_is_empty::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_bitand_assign_ref_self_is_identity_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_bitand_assign_ref_self_is_identity::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_bitand_assign_ref_is_commutative_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_bitand_assign_ref_is_commutative::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_bitand_assign_ref_cell_level_min_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_bitand_assign_ref_cell_level_min::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_bitand_assign_ref_dominated_by_both_sides_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_bitand_assign_ref_dominated_by_both_sides::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_bitor_plus_bitand_equals_add_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_bitor_plus_bitand_equals_add::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_bitand_owned_equals_bitand_ref_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_bitand_owned_equals_bitand_ref::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_bitand_assign_owned_equals_bitand_assign_ref_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_bitand_assign_owned_equals_bitand_assign_ref::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_bitand_witnessed_on_partial_overlap_for_diff_line_kind() {
let lhs: AxisHistogram<DiffLineKind> =
[(DiffLineKind::Added, 5), (DiffLineKind::Removed, 2)]
.into_iter()
.collect();
let rhs: AxisHistogram<DiffLineKind> = [
(DiffLineKind::Added, 1),
(DiffLineKind::Removed, 4),
(DiffLineKind::Context, 3),
]
.into_iter()
.collect();
let via_op = lhs.clone() & &rhs;
assert_eq!(via_op.count(DiffLineKind::Added), 1);
assert_eq!(via_op.count(DiffLineKind::Removed), 2);
assert_eq!(via_op.count(DiffLineKind::Context), 0);
assert_eq!(via_op.total(), 3);
let via_method = lhs.clone().pointwise_min(&rhs);
assert_eq!(via_op, via_method);
let via_op_rev = rhs.clone() & &lhs;
assert_eq!(via_op, via_op_rev);
let self_met = lhs.clone() & &lhs;
assert_eq!(self_met, lhs);
let empty = AxisHistogram::<DiffLineKind>::empty();
let absorbed = lhs.clone() & ∅
assert_eq!(absorbed, empty);
let mut via_assign = lhs.clone();
via_assign &= &rhs;
assert_eq!(via_assign, via_op);
assert!(via_op.is_dominated_by(&lhs));
assert!(via_op.is_dominated_by(&rhs));
let join = lhs.clone() | &rhs;
let sum_decomp = join + &via_op;
let sum_direct = lhs.clone() + &rhs;
assert_eq!(sum_decomp, sum_direct);
}
fn assert_mul_assign_zero_factor_zeros_histogram<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let mut cover: AxisHistogram<A> = axis_iter::<A>().collect();
cover *= 0;
assert_eq!(
cover,
AxisHistogram::<A>::empty(),
"hist *= 0 must equal AxisHistogram::empty() on axis {}",
std::any::type_name::<A>(),
);
assert_eq!(
cover.total(),
0,
"hist *= 0 must zero the total on axis {}",
std::any::type_name::<A>(),
);
assert!(
cover.is_empty(),
"hist *= 0 must satisfy is_empty on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_mul_assign_one_factor_is_identity<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
let mut scaled = cover.clone();
scaled *= 1;
assert_eq!(
scaled,
cover,
"hist *= 1 must leave hist unchanged on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_mul_assign_scales_total<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let cover: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
let total_before = cover.total();
for factor in [0usize, 1, 2, 5] {
let mut scaled = cover.clone();
scaled *= factor;
assert_eq!(
scaled.total(),
total_before * factor,
"hist *= {factor} must scale total by {factor} on axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_mul_assign_scales_cells<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let pre: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
let factor = 3usize;
let mut scaled = pre.clone();
scaled *= factor;
for cell in axis_iter::<A>() {
assert_eq!(
scaled.count(cell),
pre.count(cell) * factor,
"hist *= {factor} cell {cell:?} must equal pre.count * {factor} on axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_mul_assign_distributes_over_add_assign<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let lhs: AxisHistogram<A> = axis_iter::<A>().collect();
let rhs: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
let factor = 4usize;
let mut via_scale_after = lhs.clone();
via_scale_after += &rhs;
via_scale_after *= factor;
let mut via_scale_first_lhs = lhs.clone();
via_scale_first_lhs *= factor;
let mut via_scale_first_rhs = rhs.clone();
via_scale_first_rhs *= factor;
via_scale_first_lhs += &via_scale_first_rhs;
assert_eq!(
via_scale_after,
via_scale_first_lhs,
"(a + &b) * n must equal a * n + &(b * n) on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_mul_assign_equals_repeated_add_assign<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let hist: AxisHistogram<A> = axis_iter::<A>().collect();
let factor = 3usize;
let mut via_mul = hist.clone();
via_mul *= factor;
let mut via_repeated_add: AxisHistogram<A> = AxisHistogram::empty();
for _ in 0..factor {
via_repeated_add += &hist;
}
assert_eq!(
via_mul,
via_repeated_add,
"hist *= {factor} must equal {factor} rounds of += &hist on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_mul_equals_mul_assign<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let hist: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
for factor in [0usize, 1, 2, 5] {
let via_mul = hist.clone() * factor;
let mut via_mul_assign = hist.clone();
via_mul_assign *= factor;
assert_eq!(
via_mul,
via_mul_assign,
"hist * {factor} must equal `let mut a = hist; a *= {factor}; a` on axis {}",
std::any::type_name::<A>(),
);
}
}
#[test]
fn axis_histogram_mul_assign_zero_factor_zeros_histogram_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_mul_assign_zero_factor_zeros_histogram::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_mul_assign_one_factor_is_identity_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_mul_assign_one_factor_is_identity::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_mul_assign_scales_total_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_mul_assign_scales_total::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_mul_assign_scales_cells_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_mul_assign_scales_cells::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_mul_assign_distributes_over_add_assign_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_mul_assign_distributes_over_add_assign::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_mul_assign_equals_repeated_add_assign_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_mul_assign_equals_repeated_add_assign::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_mul_equals_mul_assign_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_mul_equals_mul_assign::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_mul_distributes_over_add_for_diff_line_kind() {
let a: AxisHistogram<DiffLineKind> = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
]
.into_iter()
.collect();
let b: AxisHistogram<DiffLineKind> = [DiffLineKind::Removed, DiffLineKind::Context]
.into_iter()
.collect();
let factor = 3usize;
let via_scale_after = (a.clone() + &b) * factor;
let via_scale_first = a.clone() * factor + &(b.clone() * factor);
assert_eq!(via_scale_after, via_scale_first);
assert_eq!(via_scale_after.count(DiffLineKind::Added), 6);
assert_eq!(via_scale_after.count(DiffLineKind::Removed), 6);
assert_eq!(via_scale_after.count(DiffLineKind::Context), 3);
assert_eq!(via_scale_after.total(), 15);
}
#[test]
fn axis_histogram_mul_equals_fleet_aggregate_for_diff_line_kind() {
let host_a: AxisHistogram<DiffLineKind> = [DiffLineKind::Added, DiffLineKind::Removed]
.into_iter()
.collect();
let host_b: AxisHistogram<DiffLineKind> = [
DiffLineKind::Context,
DiffLineKind::Context,
DiffLineKind::Added,
]
.into_iter()
.collect();
let weight_a = 2usize;
let weight_b = 3usize;
let via_scaled_add = host_a.clone() * weight_a + &(host_b.clone() * weight_b);
let mut via_repeated: AxisHistogram<DiffLineKind> = AxisHistogram::empty();
for _ in 0..weight_a {
via_repeated += &host_a;
}
for _ in 0..weight_b {
via_repeated += &host_b;
}
assert_eq!(via_scaled_add, via_repeated);
assert_eq!(via_scaled_add.count(DiffLineKind::Added), 5);
assert_eq!(via_scaled_add.count(DiffLineKind::Removed), 2);
assert_eq!(via_scaled_add.count(DiffLineKind::Context), 6);
assert_eq!(via_scaled_add.total(), 13);
}
fn assert_mul_left_factor_equals_mul_right_factor<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let hist: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
for factor in [0usize, 1, 2, 5] {
let via_left = factor * hist.clone();
let via_right = hist.clone() * factor;
assert_eq!(
via_left,
via_right,
"{factor} * hist must equal hist * {factor} on axis {}",
std::any::type_name::<A>(),
);
}
}
#[test]
fn axis_histogram_mul_left_factor_equals_mul_right_factor_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_mul_left_factor_equals_mul_right_factor::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_mul_left_factor_distributes_over_add_for_diff_line_kind() {
let a: AxisHistogram<DiffLineKind> = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
]
.into_iter()
.collect();
let b: AxisHistogram<DiffLineKind> = [DiffLineKind::Removed, DiffLineKind::Context]
.into_iter()
.collect();
let factor = 3usize;
let via_scale_after = factor * (a.clone() + &b);
let via_scale_first = factor * a.clone() + &(factor * b.clone());
assert_eq!(via_scale_after, via_scale_first);
assert_eq!(via_scale_after.count(DiffLineKind::Added), 6);
assert_eq!(via_scale_after.count(DiffLineKind::Removed), 6);
assert_eq!(via_scale_after.count(DiffLineKind::Context), 3);
assert_eq!(via_scale_after.total(), 15);
}
fn assert_mul_left_factor_borrowed_equals_owned<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let hist: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
for factor in [0usize, 1, 2, 5] {
let via_borrowed = factor * &hist;
let via_owned = factor * hist.clone();
assert_eq!(
via_borrowed,
via_owned,
"{factor} * &hist must equal {factor} * hist.clone() on axis {}",
std::any::type_name::<A>(),
);
}
}
#[test]
fn axis_histogram_mul_left_factor_borrowed_equals_owned_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_mul_left_factor_borrowed_equals_owned::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_mul_left_factor_borrowed_distributes_over_add_for_diff_line_kind() {
let a: AxisHistogram<DiffLineKind> = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
]
.into_iter()
.collect();
let b: AxisHistogram<DiffLineKind> = [DiffLineKind::Removed, DiffLineKind::Context]
.into_iter()
.collect();
let factor = 3usize;
let sum_ab = a.clone() + &b;
let via_scale_after = factor * &sum_ab;
let via_scale_first = factor * &a + &(factor * &b);
assert_eq!(via_scale_after, via_scale_first);
assert_eq!(a.count(DiffLineKind::Added), 2);
assert_eq!(a.count(DiffLineKind::Removed), 1);
assert_eq!(b.count(DiffLineKind::Removed), 1);
assert_eq!(b.count(DiffLineKind::Context), 1);
assert_eq!(via_scale_after.count(DiffLineKind::Added), 6);
assert_eq!(via_scale_after.count(DiffLineKind::Removed), 6);
assert_eq!(via_scale_after.count(DiffLineKind::Context), 3);
assert_eq!(via_scale_after.total(), 15);
}
fn assert_mul_right_factor_borrowed_equals_owned<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let hist: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
for factor in [0usize, 1, 2, 5] {
let via_borrowed = &hist * factor;
let via_owned = hist.clone() * factor;
assert_eq!(
via_borrowed,
via_owned,
"&hist * {factor} must equal hist.clone() * {factor} on axis {}",
std::any::type_name::<A>(),
);
}
}
#[test]
fn axis_histogram_mul_right_factor_borrowed_equals_owned_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_mul_right_factor_borrowed_equals_owned::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
fn assert_mul_right_factor_borrowed_equals_left_factor_borrowed<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let hist: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
for factor in [0usize, 1, 2, 5] {
let via_right = &hist * factor;
let via_left = factor * &hist;
assert_eq!(
via_right,
via_left,
"&hist * {factor} must equal {factor} * &hist on axis {}",
std::any::type_name::<A>(),
);
}
}
#[test]
fn axis_histogram_mul_right_factor_borrowed_equals_left_factor_borrowed_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_mul_right_factor_borrowed_equals_left_factor_borrowed::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_mul_right_factor_borrowed_distributes_over_add_for_diff_line_kind() {
let a: AxisHistogram<DiffLineKind> = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
]
.into_iter()
.collect();
let b: AxisHistogram<DiffLineKind> = [DiffLineKind::Removed, DiffLineKind::Context]
.into_iter()
.collect();
let factor = 3usize;
let sum_ab = a.clone() + &b;
let via_scale_after = &sum_ab * factor;
let via_scale_first = &a * factor + &(&b * factor);
assert_eq!(via_scale_after, via_scale_first);
assert_eq!(a.count(DiffLineKind::Added), 2);
assert_eq!(a.count(DiffLineKind::Removed), 1);
assert_eq!(b.count(DiffLineKind::Removed), 1);
assert_eq!(b.count(DiffLineKind::Context), 1);
assert_eq!(via_scale_after.count(DiffLineKind::Added), 6);
assert_eq!(via_scale_after.count(DiffLineKind::Removed), 6);
assert_eq!(via_scale_after.count(DiffLineKind::Context), 3);
assert_eq!(via_scale_after.total(), 15);
}
fn assert_div_assign_one_divisor_is_identity<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let cover: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
let mut divided = cover.clone();
divided /= 1;
assert_eq!(
divided,
cover,
"hist /= 1 must leave hist unchanged on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_div_assign_scales_cells_by_truncating_division<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let mut pre = AxisHistogram::<A>::empty();
for _ in 0..7 {
pre += &axis_iter::<A>().collect::<AxisHistogram<A>>();
}
let divisor = 3usize;
let mut divided = pre.clone();
divided /= divisor;
for cell in axis_iter::<A>() {
assert_eq!(
divided.count(cell),
pre.count(cell) / divisor,
"hist /= {divisor} cell {cell:?} must equal pre.count / {divisor} on axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_div_assign_inverts_mul_assign_on_uniform_cover<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
for factor in [1usize, 2, 5, 11] {
let mut round_trip = cover.clone();
round_trip *= factor;
round_trip /= factor;
assert_eq!(
round_trip,
cover,
"(hist *= {factor}; hist /= {factor}) must recover hist on axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_div_equals_div_assign<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let hist: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
for divisor in [1usize, 2, 5, 11] {
let via_div = hist.clone() / divisor;
let mut via_div_assign = hist.clone();
via_div_assign /= divisor;
assert_eq!(
via_div,
via_div_assign,
"hist / {divisor} must equal `let mut a = hist; a /= {divisor}; a` on axis {}",
std::any::type_name::<A>(),
);
}
}
#[test]
fn axis_histogram_div_assign_one_divisor_is_identity_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_div_assign_one_divisor_is_identity::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_div_assign_scales_cells_by_truncating_division_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_div_assign_scales_cells_by_truncating_division::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_div_assign_inverts_mul_assign_on_uniform_cover_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_div_assign_inverts_mul_assign_on_uniform_cover::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_div_equals_div_assign_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_div_equals_div_assign::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_div_witnessed_on_non_trivial_cells_for_diff_line_kind() {
let pre: AxisHistogram<DiffLineKind> = [
(DiffLineKind::Added, 7usize),
(DiffLineKind::Removed, 5),
(DiffLineKind::Context, 4),
]
.into_iter()
.collect();
let divisor = 2usize;
let via_op = pre.clone() / divisor;
assert_eq!(via_op.count(DiffLineKind::Added), 3);
assert_eq!(via_op.count(DiffLineKind::Removed), 2);
assert_eq!(via_op.count(DiffLineKind::Context), 2);
assert_eq!(via_op.total(), 7);
let mut via_assign = pre.clone();
via_assign /= divisor;
assert_eq!(via_op, via_assign);
assert_eq!(pre.clone() / 1, pre);
let factor = 4usize;
let round_trip = (pre.clone() * factor) / factor;
assert_eq!(round_trip, pre);
let lossy = (pre.clone() / divisor) * divisor;
assert_eq!(lossy.count(DiffLineKind::Added), 6);
assert_eq!(lossy.count(DiffLineKind::Removed), 4);
assert_eq!(lossy.count(DiffLineKind::Context), 4);
assert_ne!(lossy, pre);
}
fn assert_div_right_divisor_borrowed_equals_owned<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let hist: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
for divisor in [1usize, 2, 5, 11] {
let via_borrowed = &hist / divisor;
let via_owned = hist.clone() / divisor;
assert_eq!(
via_borrowed,
via_owned,
"&hist / {divisor} must equal hist.clone() / {divisor} on axis {}",
std::any::type_name::<A>(),
);
}
}
#[test]
fn axis_histogram_div_right_divisor_borrowed_equals_owned_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_div_right_divisor_borrowed_equals_owned::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_div_right_divisor_borrowed_witnessed_on_non_trivial_cells_for_diff_line_kind()
{
let pre: AxisHistogram<DiffLineKind> = [
(DiffLineKind::Added, 7usize),
(DiffLineKind::Removed, 5),
(DiffLineKind::Context, 4),
]
.into_iter()
.collect();
let divisor = 2usize;
let via_borrowed = &pre / divisor;
assert_eq!(via_borrowed.count(DiffLineKind::Added), 3);
assert_eq!(via_borrowed.count(DiffLineKind::Removed), 2);
assert_eq!(via_borrowed.count(DiffLineKind::Context), 2);
assert_eq!(via_borrowed.total(), 7);
assert_eq!(pre.count(DiffLineKind::Added), 7);
assert_eq!(pre.count(DiffLineKind::Removed), 5);
assert_eq!(pre.count(DiffLineKind::Context), 4);
assert_eq!(pre.total(), 16);
assert_eq!(via_borrowed, pre.clone() / divisor);
assert_eq!(&pre / 1, pre);
}
fn assert_rem_assign_one_divisor_zeros_histogram<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let cover: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
let mut reduced = cover;
reduced %= 1;
assert_eq!(
reduced,
AxisHistogram::<A>::empty(),
"hist %= 1 must zero every cell on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_rem_assign_scales_cells_by_remainder<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let mut pre = AxisHistogram::<A>::empty();
for _ in 0..7 {
pre += &axis_iter::<A>().collect::<AxisHistogram<A>>();
}
let divisor = 3usize;
let mut reduced = pre.clone();
reduced %= divisor;
for cell in axis_iter::<A>() {
assert_eq!(
reduced.count(cell),
pre.count(cell) % divisor,
"hist %= {divisor} cell {cell:?} must equal pre.count % {divisor} on axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_rem_assign_completes_div_rem_identity<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let mut pre = AxisHistogram::<A>::empty();
for _ in 0..7 {
pre += &axis_iter::<A>().collect::<AxisHistogram<A>>();
}
for divisor in [2usize, 3, 5, 11] {
let recomposed = (pre.clone() / divisor) * divisor + &(pre.clone() % divisor);
assert_eq!(
recomposed,
pre,
"(hist / {divisor}) * {divisor} + (hist % {divisor}) must equal hist on axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_rem_assign_bounds_cells_below_divisor<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let mut pre = AxisHistogram::<A>::empty();
for _ in 0..7 {
pre += &axis_iter::<A>().collect::<AxisHistogram<A>>();
}
for divisor in [2usize, 3, 5, 11] {
let mut reduced = pre.clone();
reduced %= divisor;
for cell in axis_iter::<A>() {
assert!(
reduced.count(cell) < divisor,
"hist %= {divisor} cell {cell:?} count {} must be < {divisor} on axis {}",
reduced.count(cell),
std::any::type_name::<A>(),
);
}
}
}
fn assert_rem_equals_rem_assign<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let hist: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
for divisor in [2usize, 3, 5, 11] {
let via_rem = hist.clone() % divisor;
let mut via_rem_assign = hist.clone();
via_rem_assign %= divisor;
assert_eq!(
via_rem,
via_rem_assign,
"hist % {divisor} must equal `let mut a = hist; a %= {divisor}; a` on axis {}",
std::any::type_name::<A>(),
);
}
}
#[test]
fn axis_histogram_rem_assign_one_divisor_zeros_histogram_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_rem_assign_one_divisor_zeros_histogram::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_rem_assign_scales_cells_by_remainder_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_rem_assign_scales_cells_by_remainder::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_rem_assign_completes_div_rem_identity_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_rem_assign_completes_div_rem_identity::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_rem_assign_bounds_cells_below_divisor_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_rem_assign_bounds_cells_below_divisor::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_rem_equals_rem_assign_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_rem_equals_rem_assign::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_rem_witnessed_on_non_trivial_cells_for_diff_line_kind() {
let pre: AxisHistogram<DiffLineKind> = [
(DiffLineKind::Added, 7usize),
(DiffLineKind::Removed, 5),
(DiffLineKind::Context, 4),
]
.into_iter()
.collect();
let divisor = 2usize;
let via_op = pre.clone() % divisor;
assert_eq!(via_op.count(DiffLineKind::Added), 1);
assert_eq!(via_op.count(DiffLineKind::Removed), 1);
assert_eq!(via_op.count(DiffLineKind::Context), 0);
assert_eq!(via_op.total(), 2);
let mut via_assign = pre.clone();
via_assign %= divisor;
assert_eq!(via_op, via_assign);
let one_divisor = std::hint::black_box(1usize);
assert_eq!(
pre.clone() % one_divisor,
AxisHistogram::<DiffLineKind>::empty()
);
let div_part = (pre.clone() / divisor) * divisor;
let rem_part = pre.clone() % divisor;
let recomposed = div_part + &rem_part;
assert_eq!(recomposed, pre);
for cell in axis_iter::<DiffLineKind>() {
assert!(via_op.count(cell) < divisor);
}
}
fn assert_rem_right_divisor_borrowed_equals_owned<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let hist: AxisHistogram<A> = axis_iter::<A>().chain(axis_iter::<A>()).collect();
for divisor in [1usize, 2, 5, 11] {
let via_borrowed = &hist % divisor;
let via_owned = hist.clone() % divisor;
assert_eq!(
via_borrowed,
via_owned,
"&hist % {divisor} must equal hist.clone() % {divisor} on axis {}",
std::any::type_name::<A>(),
);
}
}
#[test]
fn axis_histogram_rem_right_divisor_borrowed_equals_owned_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_rem_right_divisor_borrowed_equals_owned::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_rem_right_divisor_borrowed_witnessed_on_non_trivial_cells_for_diff_line_kind()
{
let pre: AxisHistogram<DiffLineKind> = [
(DiffLineKind::Added, 7usize),
(DiffLineKind::Removed, 5),
(DiffLineKind::Context, 4),
]
.into_iter()
.collect();
let divisor = 2usize;
let via_borrowed = &pre % divisor;
assert_eq!(via_borrowed.count(DiffLineKind::Added), 1);
assert_eq!(via_borrowed.count(DiffLineKind::Removed), 1);
assert_eq!(via_borrowed.count(DiffLineKind::Context), 0);
assert_eq!(via_borrowed.total(), 2);
assert_eq!(pre.count(DiffLineKind::Added), 7);
assert_eq!(pre.count(DiffLineKind::Removed), 5);
assert_eq!(pre.count(DiffLineKind::Context), 4);
assert_eq!(pre.total(), 16);
assert_eq!(via_borrowed, pre.clone() % divisor);
let one_divisor = std::hint::black_box(1usize);
assert_eq!(&pre % one_divisor, AxisHistogram::<DiffLineKind>::empty());
let div_part = (&pre / divisor) * divisor;
let rem_part = &pre % divisor;
let recomposed = div_part + &rem_part;
assert_eq!(recomposed, pre);
for cell in axis_iter::<DiffLineKind>() {
assert!(via_borrowed.count(cell) < divisor);
}
}
fn assert_dominant_cell_empty_is_none<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let hist = AxisHistogram::<A>::empty();
assert_eq!(
hist.dominant_cell(),
None,
"empty histogram dominant_cell must be None on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_dominant_cell_singleton_picks_observed_cell<A>()
where
A: ClosedAxis + std::fmt::Debug + PartialEq,
{
for observed in axis_iter::<A>() {
let hist: AxisHistogram<A> = std::iter::once(observed).collect();
assert_eq!(
hist.dominant_cell(),
Some(observed),
"singleton dominant_cell must equal the observed cell {observed:?} \
on axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_dominant_cell_axis_cover_picks_first_cell<A>()
where
A: ClosedAxis + std::fmt::Debug + PartialEq,
{
let hist: AxisHistogram<A> = axis_iter::<A>().collect();
let first = axis_iter::<A>().next().expect(
"every ClosedAxis implementor has at least one variant per the ClosedAxis contract",
);
assert_eq!(
hist.dominant_cell(),
Some(first),
"uniform axis-cover histogram dominant_cell must be the first cell \
in declaration order on axis {}",
std::any::type_name::<A>(),
);
}
#[test]
fn axis_histogram_dominant_cell_empty_is_none_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_dominant_cell_empty_is_none::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_dominant_cell_singleton_picks_observed_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_dominant_cell_singleton_picks_observed_cell::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_dominant_cell_axis_cover_picks_first_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_dominant_cell_axis_cover_picks_first_cell::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_dominant_cell_returns_strict_max_when_unique() {
let input = [
DiffLineKind::Removed,
DiffLineKind::Added,
DiffLineKind::Context,
DiffLineKind::Added,
];
let hist: AxisHistogram<DiffLineKind> = input.iter().copied().collect();
assert_eq!(hist.count(DiffLineKind::Added), 2);
assert_eq!(hist.count(DiffLineKind::Removed), 1);
assert_eq!(hist.count(DiffLineKind::Context), 1);
assert_eq!(hist.dominant_cell(), Some(DiffLineKind::Added));
}
#[test]
fn axis_histogram_dominant_cell_breaks_ties_in_declaration_order() {
let first = DiffLineKind::ALL[0];
let by_observation_order_a: AxisHistogram<DiffLineKind> = [
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Context,
]
.into_iter()
.collect();
let by_observation_order_b: AxisHistogram<DiffLineKind> = [
DiffLineKind::Context,
DiffLineKind::Removed,
DiffLineKind::Added,
]
.into_iter()
.collect();
assert_eq!(by_observation_order_a, by_observation_order_b);
assert_eq!(by_observation_order_a.dominant_cell(), Some(first));
assert_eq!(by_observation_order_b.dominant_cell(), Some(first));
}
#[test]
fn axis_histogram_dominant_cell_after_merge_reflects_combined_counts() {
let lhs: AxisHistogram<DiffLineKind> = [DiffLineKind::Added, DiffLineKind::Added]
.into_iter()
.collect();
let rhs: AxisHistogram<DiffLineKind> = [
DiffLineKind::Removed,
DiffLineKind::Removed,
DiffLineKind::Removed,
]
.into_iter()
.collect();
assert_eq!(lhs.dominant_cell(), Some(DiffLineKind::Added));
assert_eq!(rhs.dominant_cell(), Some(DiffLineKind::Removed));
let merged = lhs.merge(&rhs);
assert_eq!(merged.count(DiffLineKind::Added), 2);
assert_eq!(merged.count(DiffLineKind::Removed), 3);
assert_eq!(merged.dominant_cell(), Some(DiffLineKind::Removed));
}
#[test]
fn axis_histogram_dominant_cell_iff_is_empty_is_false() {
let empty: AxisHistogram<DiffLineKind> = AxisHistogram::empty();
assert!(empty.is_empty());
assert_eq!(empty.dominant_cell(), None);
let singleton: AxisHistogram<DiffLineKind> =
std::iter::once(DiffLineKind::Context).collect();
assert!(!singleton.is_empty());
assert!(singleton.dominant_cell().is_some());
}
fn assert_distinct_cells_empty_is_zero<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let hist = AxisHistogram::<A>::empty();
assert_eq!(
hist.distinct_cells(),
0,
"empty histogram distinct_cells must be 0 on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_distinct_cells_singleton_is_one<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
for observed in axis_iter::<A>() {
let hist: AxisHistogram<A> = std::iter::once(observed).collect();
assert_eq!(
hist.distinct_cells(),
1,
"singleton distinct_cells must equal 1 \
for observed cell {observed:?} on axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_distinct_cells_axis_cover_equals_cardinality<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let hist: AxisHistogram<A> = axis_iter::<A>().collect();
assert_eq!(
hist.distinct_cells(),
axis_cardinality::<A>(),
"axis-cover histogram distinct_cells must equal \
axis_cardinality on {}",
std::any::type_name::<A>(),
);
}
#[test]
fn axis_histogram_distinct_cells_empty_is_zero_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_distinct_cells_empty_is_zero::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_distinct_cells_singleton_is_one_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_distinct_cells_singleton_is_one::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_distinct_cells_axis_cover_equals_cardinality_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_distinct_cells_axis_cover_equals_cardinality::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_distinct_cells_equals_nonzero_count() {
let inputs: [&[DiffLineKind]; 5] = [
&[],
&[DiffLineKind::Added],
&[
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
],
&[
DiffLineKind::Context,
DiffLineKind::Added,
DiffLineKind::Removed,
],
&[
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
],
];
for input in inputs {
let hist: AxisHistogram<DiffLineKind> = input.iter().copied().collect();
assert_eq!(
hist.distinct_cells(),
hist.nonzero().count(),
"distinct_cells must equal nonzero().count() on input of length {}",
input.len(),
);
}
}
#[test]
fn axis_histogram_distinct_cells_is_bounded_above_by_total_and_axis_cardinality() {
let inputs: [&[DiffLineKind]; 4] = [
&[],
&[DiffLineKind::Removed],
&[
DiffLineKind::Context,
DiffLineKind::Added,
DiffLineKind::Removed,
],
&[
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
],
];
for input in inputs {
let hist: AxisHistogram<DiffLineKind> = input.iter().copied().collect();
let support = hist.distinct_cells();
assert!(
support <= hist.total(),
"distinct_cells {support} must be <= total {} on input of length {}",
hist.total(),
input.len(),
);
assert!(
support <= axis_cardinality::<DiffLineKind>(),
"distinct_cells {support} must be <= axis_cardinality {} on input of length {}",
axis_cardinality::<DiffLineKind>(),
input.len(),
);
}
}
#[test]
fn axis_histogram_distinct_cells_equals_total_iff_every_observation_is_unique() {
let unique_a: AxisHistogram<DiffLineKind> = AxisHistogram::empty();
let unique_b: AxisHistogram<DiffLineKind> =
std::iter::once(DiffLineKind::Context).collect();
let unique_c: AxisHistogram<DiffLineKind> = axis_iter::<DiffLineKind>().collect();
for hist in [&unique_a, &unique_b, &unique_c] {
assert_eq!(
hist.distinct_cells(),
hist.total(),
"uniform-singleton histogram must satisfy distinct_cells == total",
);
}
let dup_a: AxisHistogram<DiffLineKind> = [DiffLineKind::Added, DiffLineKind::Added]
.into_iter()
.collect();
let dup_b: AxisHistogram<DiffLineKind> = [
DiffLineKind::Context,
DiffLineKind::Context,
DiffLineKind::Added,
]
.into_iter()
.collect();
for hist in [&dup_a, &dup_b] {
assert!(
hist.distinct_cells() < hist.total(),
"duplicated-observation histogram must satisfy distinct_cells < total",
);
}
}
#[test]
fn axis_histogram_distinct_cells_iff_is_empty_is_zero() {
let empty: AxisHistogram<DiffLineKind> = AxisHistogram::empty();
assert!(empty.is_empty());
assert_eq!(empty.distinct_cells(), 0);
let singleton: AxisHistogram<DiffLineKind> =
std::iter::once(DiffLineKind::Removed).collect();
assert!(!singleton.is_empty());
assert!(singleton.distinct_cells() >= 1);
}
#[test]
fn axis_histogram_distinct_cells_after_merge_is_monotone_and_equals_support_union() {
let added_only: AxisHistogram<DiffLineKind> = [DiffLineKind::Added, DiffLineKind::Added]
.into_iter()
.collect();
let removed_only: AxisHistogram<DiffLineKind> =
std::iter::once(DiffLineKind::Removed).collect();
let context_and_added: AxisHistogram<DiffLineKind> = [
DiffLineKind::Context,
DiffLineKind::Added,
DiffLineKind::Added,
]
.into_iter()
.collect();
let empty_hist: AxisHistogram<DiffLineKind> = AxisHistogram::empty();
let disjoint = added_only.clone().merge(&removed_only);
assert_eq!(disjoint.distinct_cells(), 2);
assert!(disjoint.distinct_cells() >= added_only.distinct_cells());
assert!(disjoint.distinct_cells() >= removed_only.distinct_cells());
let overlap = added_only.clone().merge(&context_and_added);
assert_eq!(overlap.distinct_cells(), 2); assert!(overlap.distinct_cells() >= added_only.distinct_cells());
assert!(overlap.distinct_cells() >= context_and_added.distinct_cells());
let with_empty = added_only.clone().merge(&empty_hist);
assert_eq!(with_empty.distinct_cells(), added_only.distinct_cells());
}
#[test]
fn axis_histogram_dominant_cell_equals_open_coded_first_max_loop() {
let inputs: [&[DiffLineKind]; 4] = [
&[],
&[
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Added,
],
&[DiffLineKind::Added, DiffLineKind::Removed],
&[
DiffLineKind::Context,
DiffLineKind::Added,
DiffLineKind::Removed,
],
];
for input in inputs {
let hist: AxisHistogram<DiffLineKind> = input.iter().copied().collect();
let manual = {
let mut iter = hist.iter().filter(|&(_, c)| c > 0);
iter.next().map(|first| {
iter.fold(
first,
|best, current| {
if current.1 > best.1 { current } else { best }
},
)
.0
})
};
assert_eq!(
hist.dominant_cell(),
manual,
"dominant_cell must equal the open-coded first-max scan over input \
of length {}",
input.len(),
);
}
}
fn assert_recessive_cell_empty_is_none<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let hist = AxisHistogram::<A>::empty();
assert_eq!(
hist.recessive_cell(),
None,
"empty histogram recessive_cell must be None on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_recessive_cell_singleton_picks_observed_cell<A>()
where
A: ClosedAxis + std::fmt::Debug + PartialEq,
{
for observed in axis_iter::<A>() {
let hist: AxisHistogram<A> = std::iter::once(observed).collect();
assert_eq!(
hist.recessive_cell(),
Some(observed),
"singleton recessive_cell must equal the observed cell {observed:?} \
on axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_recessive_cell_axis_cover_picks_first_cell<A>()
where
A: ClosedAxis + std::fmt::Debug + PartialEq,
{
let hist: AxisHistogram<A> = axis_iter::<A>().collect();
let first = axis_iter::<A>().next().expect(
"every ClosedAxis implementor has at least one variant per the ClosedAxis contract",
);
assert_eq!(
hist.recessive_cell(),
Some(first),
"uniform axis-cover histogram recessive_cell must be the first cell \
in declaration order on axis {}",
std::any::type_name::<A>(),
);
}
#[test]
fn axis_histogram_recessive_cell_empty_is_none_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_recessive_cell_empty_is_none::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_recessive_cell_singleton_picks_observed_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_recessive_cell_singleton_picks_observed_cell::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_recessive_cell_axis_cover_picks_first_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_recessive_cell_axis_cover_picks_first_cell::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_dominant_and_recessive_agree_on_uniform_axis_cover_for_every_implementor() {
fn assert_agree<A>()
where
A: ClosedAxis + std::fmt::Debug + PartialEq,
{
let hist: AxisHistogram<A> = axis_iter::<A>().collect();
assert_eq!(
hist.dominant_cell(),
hist.recessive_cell(),
"uniform axis-cover histogram must satisfy dominant_cell == \
recessive_cell on axis {}",
std::any::type_name::<A>(),
);
}
macro_rules! check {
($ty:ident) => {
assert_agree::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_recessive_cell_returns_strict_min_when_unique() {
let input = [
DiffLineKind::Removed,
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Added,
DiffLineKind::Removed,
];
let hist: AxisHistogram<DiffLineKind> = input.iter().copied().collect();
assert_eq!(hist.count(DiffLineKind::Added), 2);
assert_eq!(hist.count(DiffLineKind::Removed), 3);
assert_eq!(hist.count(DiffLineKind::Context), 0);
assert_eq!(hist.recessive_cell(), Some(DiffLineKind::Added));
}
#[test]
fn axis_histogram_recessive_cell_breaks_ties_in_declaration_order() {
let first = DiffLineKind::ALL[0];
let by_observation_order_a: AxisHistogram<DiffLineKind> = [
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Context,
]
.into_iter()
.collect();
let by_observation_order_b: AxisHistogram<DiffLineKind> = [
DiffLineKind::Context,
DiffLineKind::Removed,
DiffLineKind::Added,
]
.into_iter()
.collect();
assert_eq!(by_observation_order_a, by_observation_order_b);
assert_eq!(by_observation_order_a.recessive_cell(), Some(first));
assert_eq!(by_observation_order_b.recessive_cell(), Some(first));
}
#[test]
fn axis_histogram_recessive_cell_excludes_zero_cells() {
let input = [DiffLineKind::Added, DiffLineKind::Added];
let hist: AxisHistogram<DiffLineKind> = input.iter().copied().collect();
assert_eq!(hist.count(DiffLineKind::Context), 0);
assert_eq!(hist.count(DiffLineKind::Removed), 0);
assert_eq!(hist.count(DiffLineKind::Added), 2);
assert_eq!(hist.recessive_cell(), Some(DiffLineKind::Added));
}
#[test]
fn axis_histogram_recessive_cell_after_merge_reflects_combined_counts() {
let lhs: AxisHistogram<DiffLineKind> = [
DiffLineKind::Removed,
DiffLineKind::Removed,
DiffLineKind::Added,
]
.into_iter()
.collect();
let rhs: AxisHistogram<DiffLineKind> = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Context,
]
.into_iter()
.collect();
assert_eq!(lhs.recessive_cell(), Some(DiffLineKind::Added));
assert_eq!(rhs.recessive_cell(), Some(DiffLineKind::Context));
let merged = lhs.merge(&rhs);
assert_eq!(merged.count(DiffLineKind::Removed), 2);
assert_eq!(merged.count(DiffLineKind::Added), 3);
assert_eq!(merged.count(DiffLineKind::Context), 1);
assert_eq!(merged.recessive_cell(), Some(DiffLineKind::Context));
}
#[test]
fn axis_histogram_recessive_cell_iff_is_empty_is_false() {
let empty: AxisHistogram<DiffLineKind> = AxisHistogram::empty();
assert!(empty.is_empty());
assert_eq!(empty.recessive_cell(), None);
assert_eq!(empty.dominant_cell(), None);
let singleton: AxisHistogram<DiffLineKind> =
std::iter::once(DiffLineKind::Context).collect();
assert!(!singleton.is_empty());
assert!(singleton.recessive_cell().is_some());
assert!(singleton.dominant_cell().is_some());
}
#[test]
fn axis_histogram_recessive_count_bounded_above_by_dominant_count() {
let inputs: [&[DiffLineKind]; 4] = [
&[DiffLineKind::Added],
&[
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
],
&[
DiffLineKind::Context,
DiffLineKind::Added,
DiffLineKind::Removed,
],
&[
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Removed,
],
];
for input in inputs {
let hist: AxisHistogram<DiffLineKind> = input.iter().copied().collect();
let rare = hist
.recessive_cell()
.expect("non-empty histogram must have a recessive cell");
let dom = hist
.dominant_cell()
.expect("non-empty histogram must have a dominant cell");
assert!(
hist.count(rare) <= hist.count(dom),
"recessive count {} must be <= dominant count {} on input of length {}",
hist.count(rare),
hist.count(dom),
input.len(),
);
}
}
#[test]
fn axis_histogram_recessive_cell_equals_open_coded_first_min_loop() {
let inputs: [&[DiffLineKind]; 4] = [
&[],
&[
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Added,
],
&[DiffLineKind::Added, DiffLineKind::Removed],
&[
DiffLineKind::Context,
DiffLineKind::Added,
DiffLineKind::Removed,
],
];
for input in inputs {
let hist: AxisHistogram<DiffLineKind> = input.iter().copied().collect();
let manual = {
let mut iter = hist.iter().filter(|&(_, c)| c > 0);
iter.next().map(|first| {
iter.fold(
first,
|best, current| {
if current.1 < best.1 { current } else { best }
},
)
.0
})
};
assert_eq!(
hist.recessive_cell(),
manual,
"recessive_cell must equal the open-coded first-min scan over input \
of length {}",
input.len(),
);
}
}
fn assert_unobserved_empty_is_full_axis<A>()
where
A: ClosedAxis + std::fmt::Debug + PartialEq,
{
let hist: AxisHistogram<A> = AxisHistogram::empty();
let unobserved: Vec<A> = hist.unobserved().collect();
let full_axis: Vec<A> = axis_iter::<A>().collect();
assert_eq!(
unobserved,
full_axis,
"empty histogram unobserved must iterate the full axis on {}",
std::any::type_name::<A>(),
);
}
fn assert_unobserved_axis_cover_is_empty<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let hist: AxisHistogram<A> = axis_iter::<A>().collect();
assert_eq!(
hist.unobserved().count(),
0,
"uniform axis-cover histogram unobserved must be empty on {}",
std::any::type_name::<A>(),
);
}
fn assert_unobserved_singleton_omits_observed_cell<A>()
where
A: ClosedAxis + std::fmt::Debug + PartialEq,
{
for observed in axis_iter::<A>() {
let hist: AxisHistogram<A> = std::iter::once(observed).collect();
let unobserved: Vec<A> = hist.unobserved().collect();
let expected: Vec<A> = axis_iter::<A>().filter(|&v| v != observed).collect();
assert_eq!(
unobserved,
expected,
"singleton histogram unobserved must omit exactly the observed cell {observed:?} \
on axis {}",
std::any::type_name::<A>(),
);
assert_eq!(
hist.unobserved().count(),
axis_cardinality::<A>() - 1,
"singleton histogram unobserved cardinality must equal axis_cardinality - 1 on {}",
std::any::type_name::<A>(),
);
}
}
fn assert_unobserved_and_nonzero_partition_axis<A>()
where
A: ClosedAxis + std::fmt::Debug + std::hash::Hash + Eq,
{
use std::collections::HashSet;
let first = axis_iter::<A>()
.next()
.expect("ClosedAxis::ALL is non-empty by trait contract");
let histograms: [AxisHistogram<A>; 3] = [
AxisHistogram::empty(),
std::iter::once(first).collect(),
axis_iter::<A>().collect(),
];
let n = axis_cardinality::<A>();
for hist in histograms {
let observed: HashSet<A> = hist.observed().collect();
let unobserved: HashSet<A> = hist.unobserved().collect();
assert_eq!(
observed.len() + unobserved.len(),
n,
"observed.len() + unobserved.len() must equal axis_cardinality on {}",
std::any::type_name::<A>(),
);
assert!(
observed.is_disjoint(&unobserved),
"observed and unobserved cell-sets must be disjoint on {}",
std::any::type_name::<A>(),
);
let full_axis: HashSet<A> = axis_iter::<A>().collect();
let union: HashSet<A> = observed.union(&unobserved).copied().collect();
assert_eq!(
union,
full_axis,
"observed ∪ unobserved must equal the full axis on {}",
std::any::type_name::<A>(),
);
}
}
#[test]
fn axis_histogram_unobserved_empty_is_full_axis_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_unobserved_empty_is_full_axis::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_unobserved_axis_cover_is_empty_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_unobserved_axis_cover_is_empty::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_unobserved_singleton_omits_observed_cell_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_unobserved_singleton_omits_observed_cell::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_unobserved_and_nonzero_partition_axis_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_unobserved_and_nonzero_partition_axis::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_unobserved_equals_open_coded_filter_zero_loop() {
let inputs: [&[DiffLineKind]; 4] = [
&[],
&[DiffLineKind::Added],
&[
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Added,
],
&[
DiffLineKind::Context,
DiffLineKind::Added,
DiffLineKind::Removed,
],
];
for input in inputs {
let hist: AxisHistogram<DiffLineKind> = input.iter().copied().collect();
let actual: Vec<DiffLineKind> = hist.unobserved().collect();
let manual: Vec<DiffLineKind> = hist
.iter()
.filter(|&(_, c)| c == 0)
.map(|(v, _)| v)
.collect();
assert_eq!(
actual,
manual,
"unobserved must equal the open-coded filter-zero scan over input \
of length {}",
input.len(),
);
}
}
#[test]
fn axis_histogram_unobserved_complements_nonzero_pointwise() {
use std::collections::HashSet;
let cases: [(&[DiffLineKind], usize); 3] = [
(&[], 0),
(&[DiffLineKind::Added, DiffLineKind::Added], 1),
(
&[
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Context,
],
3,
),
];
for (input, expected_support) in cases {
let hist: AxisHistogram<DiffLineKind> = input.iter().copied().collect();
let observed: HashSet<DiffLineKind> = hist.observed().collect();
let unobserved: HashSet<DiffLineKind> = hist.unobserved().collect();
assert_eq!(observed.len(), expected_support);
assert_eq!(observed.len() + unobserved.len(), DiffLineKind::ALL.len());
assert!(observed.is_disjoint(&unobserved));
let union: HashSet<DiffLineKind> = observed.union(&unobserved).copied().collect();
let full_axis: HashSet<DiffLineKind> = DiffLineKind::ALL.iter().copied().collect();
assert_eq!(union, full_axis);
}
}
#[test]
fn axis_histogram_unobserved_count_equals_cardinality_minus_distinct() {
let inputs: [&[DiffLineKind]; 4] = [
&[],
&[DiffLineKind::Added],
&[DiffLineKind::Added, DiffLineKind::Removed],
&[
DiffLineKind::Context,
DiffLineKind::Added,
DiffLineKind::Removed,
],
];
let n = axis_cardinality::<DiffLineKind>();
for input in inputs {
let hist: AxisHistogram<DiffLineKind> = input.iter().copied().collect();
assert_eq!(
hist.unobserved().count(),
n - hist.distinct_cells(),
"unobserved count must equal axis_cardinality - distinct_cells on input \
of length {}",
input.len(),
);
}
}
#[test]
fn axis_histogram_unobserved_after_merge_shrinks_monotonically() {
use std::collections::HashSet;
let lhs: AxisHistogram<DiffLineKind> = [DiffLineKind::Removed, DiffLineKind::Added]
.into_iter()
.collect();
let rhs: AxisHistogram<DiffLineKind> = [DiffLineKind::Added, DiffLineKind::Context]
.into_iter()
.collect();
let lhs_gap: HashSet<DiffLineKind> = lhs.unobserved().collect();
let rhs_gap: HashSet<DiffLineKind> = rhs.unobserved().collect();
let lhs_gap_count = lhs.unobserved().count();
let rhs_gap_count = rhs.unobserved().count();
assert_eq!(lhs_gap, HashSet::from([DiffLineKind::Context]));
assert_eq!(rhs_gap, HashSet::from([DiffLineKind::Removed]));
let merged = lhs.merge(&rhs);
let merged_gap: HashSet<DiffLineKind> = merged.unobserved().collect();
let expected: HashSet<DiffLineKind> = lhs_gap.intersection(&rhs_gap).copied().collect();
assert_eq!(merged_gap, expected);
assert!(merged_gap.is_empty());
assert!(merged.unobserved().count() <= lhs_gap_count);
assert!(merged.unobserved().count() <= rhs_gap_count);
}
fn assert_observed_empty_is_empty<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let hist: AxisHistogram<A> = AxisHistogram::empty();
assert_eq!(
hist.observed().count(),
0,
"empty histogram observed must be empty on {}",
std::any::type_name::<A>(),
);
}
fn assert_observed_axis_cover_is_full_axis<A>()
where
A: ClosedAxis + std::fmt::Debug + PartialEq,
{
let hist: AxisHistogram<A> = axis_iter::<A>().collect();
let observed: Vec<A> = hist.observed().collect();
let full_axis: Vec<A> = axis_iter::<A>().collect();
assert_eq!(
observed,
full_axis,
"uniform axis-cover histogram observed must iterate the full axis on {}",
std::any::type_name::<A>(),
);
}
fn assert_observed_singleton_is_just_observed_cell<A>()
where
A: ClosedAxis + std::fmt::Debug + PartialEq,
{
for observed_cell in axis_iter::<A>() {
let hist: AxisHistogram<A> = std::iter::once(observed_cell).collect();
let observed: Vec<A> = hist.observed().collect();
assert_eq!(
observed,
vec![observed_cell],
"singleton histogram observed must yield exactly the observed cell \
{observed_cell:?} on axis {}",
std::any::type_name::<A>(),
);
assert_eq!(
hist.observed().count(),
1,
"singleton histogram observed cardinality must equal 1 on {}",
std::any::type_name::<A>(),
);
}
}
#[test]
fn axis_histogram_observed_empty_is_empty_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_observed_empty_is_empty::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_observed_axis_cover_is_full_axis_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_observed_axis_cover_is_full_axis::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_observed_singleton_is_just_observed_cell_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_observed_singleton_is_just_observed_cell::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_observed_equals_nonzero_cell_projection_pointwise() {
let inputs: [&[DiffLineKind]; 4] = [
&[],
&[DiffLineKind::Added],
&[
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Added,
],
&[
DiffLineKind::Context,
DiffLineKind::Added,
DiffLineKind::Removed,
],
];
for input in inputs {
let hist: AxisHistogram<DiffLineKind> = input.iter().copied().collect();
let actual: Vec<DiffLineKind> = hist.observed().collect();
let manual: Vec<DiffLineKind> = hist.nonzero().map(|(v, _)| v).collect();
assert_eq!(
actual,
manual,
"observed must equal the cell projection of nonzero over input \
of length {}",
input.len(),
);
}
}
#[test]
fn axis_histogram_observed_equals_open_coded_filter_positive_loop() {
let inputs: [&[DiffLineKind]; 4] = [
&[],
&[DiffLineKind::Added],
&[
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Added,
],
&[
DiffLineKind::Context,
DiffLineKind::Added,
DiffLineKind::Removed,
],
];
for input in inputs {
let hist: AxisHistogram<DiffLineKind> = input.iter().copied().collect();
let actual: Vec<DiffLineKind> = hist.observed().collect();
let manual: Vec<DiffLineKind> = hist
.iter()
.filter(|&(_, c)| c > 0)
.map(|(v, _)| v)
.collect();
assert_eq!(
actual,
manual,
"observed must equal the open-coded filter-positive scan over input \
of length {}",
input.len(),
);
}
}
#[test]
fn axis_histogram_observed_count_equals_distinct_cells() {
let inputs: [&[DiffLineKind]; 4] = [
&[],
&[DiffLineKind::Added],
&[DiffLineKind::Added, DiffLineKind::Removed],
&[
DiffLineKind::Context,
DiffLineKind::Added,
DiffLineKind::Removed,
],
];
for input in inputs {
let hist: AxisHistogram<DiffLineKind> = input.iter().copied().collect();
assert_eq!(
hist.observed().count(),
hist.distinct_cells(),
"observed count must equal distinct_cells on input of length {}",
input.len(),
);
}
}
#[test]
fn axis_histogram_observed_partitions_axis_with_unobserved_pointwise() {
use std::collections::HashSet;
let cases: [(&[DiffLineKind], usize); 3] = [
(&[], 0),
(&[DiffLineKind::Added, DiffLineKind::Added], 1),
(
&[
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Context,
],
3,
),
];
for (input, expected_support) in cases {
let hist: AxisHistogram<DiffLineKind> = input.iter().copied().collect();
let observed: HashSet<DiffLineKind> = hist.observed().collect();
let unobserved: HashSet<DiffLineKind> = hist.unobserved().collect();
assert_eq!(observed.len(), expected_support);
assert_eq!(observed.len() + unobserved.len(), DiffLineKind::ALL.len());
assert!(observed.is_disjoint(&unobserved));
let union: HashSet<DiffLineKind> = observed.union(&unobserved).copied().collect();
let full_axis: HashSet<DiffLineKind> = DiffLineKind::ALL.iter().copied().collect();
assert_eq!(union, full_axis);
}
}
#[test]
fn axis_histogram_observed_yields_cells_in_declaration_order() {
let order_a: AxisHistogram<DiffLineKind> = [
DiffLineKind::Context,
DiffLineKind::Added,
DiffLineKind::Removed,
]
.into_iter()
.collect();
let order_b: AxisHistogram<DiffLineKind> = [
DiffLineKind::Removed,
DiffLineKind::Context,
DiffLineKind::Added,
]
.into_iter()
.collect();
let seq_a: Vec<DiffLineKind> = order_a.observed().collect();
let seq_b: Vec<DiffLineKind> = order_b.observed().collect();
assert_eq!(seq_a, seq_b);
assert_eq!(
seq_a,
vec![
DiffLineKind::Removed,
DiffLineKind::Added,
DiffLineKind::Context,
],
);
}
#[test]
fn axis_histogram_observed_after_merge_grows_monotonically() {
use std::collections::HashSet;
let lhs: AxisHistogram<DiffLineKind> = [DiffLineKind::Removed, DiffLineKind::Added]
.into_iter()
.collect();
let rhs: AxisHistogram<DiffLineKind> = [DiffLineKind::Added, DiffLineKind::Context]
.into_iter()
.collect();
let lhs_support: HashSet<DiffLineKind> = lhs.observed().collect();
let rhs_support: HashSet<DiffLineKind> = rhs.observed().collect();
let lhs_support_count = lhs.observed().count();
let rhs_support_count = rhs.observed().count();
assert_eq!(
lhs_support,
HashSet::from([DiffLineKind::Removed, DiffLineKind::Added]),
);
assert_eq!(
rhs_support,
HashSet::from([DiffLineKind::Added, DiffLineKind::Context]),
);
let merged = lhs.merge(&rhs);
let merged_support: HashSet<DiffLineKind> = merged.observed().collect();
let expected: HashSet<DiffLineKind> = lhs_support.union(&rhs_support).copied().collect();
assert_eq!(merged_support, expected);
let full_axis: HashSet<DiffLineKind> = DiffLineKind::ALL.iter().copied().collect();
assert_eq!(merged_support, full_axis);
assert!(merged.observed().count() >= lhs_support_count);
assert!(merged.observed().count() >= rhs_support_count);
}
fn assert_peak_count_empty_is_zero<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let hist = AxisHistogram::<A>::empty();
assert_eq!(
hist.peak_count(),
0,
"empty histogram peak_count must be 0 on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_peak_count_singleton_is_one<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
for observed in axis_iter::<A>() {
let hist: AxisHistogram<A> = std::iter::once(observed).collect();
assert_eq!(
hist.peak_count(),
1,
"singleton peak_count must equal 1 \
for observed cell {observed:?} on axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_peak_count_axis_cover_is_one<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let hist: AxisHistogram<A> = axis_iter::<A>().collect();
assert_eq!(
hist.peak_count(),
1,
"uniform axis-cover histogram peak_count must equal 1 on {}",
std::any::type_name::<A>(),
);
}
#[test]
fn axis_histogram_peak_count_empty_is_zero_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_peak_count_empty_is_zero::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_peak_count_singleton_is_one_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_peak_count_singleton_is_one::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_peak_count_axis_cover_is_one_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_peak_count_axis_cover_is_one::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_peak_count_equals_dominant_cell_count_when_non_empty() {
let inputs: [&[DiffLineKind]; 4] = [
&[DiffLineKind::Added],
&[
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Added,
],
&[DiffLineKind::Added, DiffLineKind::Removed],
&[
DiffLineKind::Context,
DiffLineKind::Added,
DiffLineKind::Removed,
],
];
for input in inputs {
let hist: AxisHistogram<DiffLineKind> = input.iter().copied().collect();
let dominant = hist
.dominant_cell()
.expect("non-empty histogram has a dominant cell");
assert_eq!(
hist.peak_count(),
hist.count(dominant),
"peak_count must equal count(dominant_cell()) on non-empty input \
of length {}",
input.len(),
);
}
}
#[test]
fn axis_histogram_peak_count_iff_is_empty_is_zero() {
let empty: AxisHistogram<DiffLineKind> = AxisHistogram::empty();
assert!(empty.is_empty());
assert_eq!(empty.peak_count(), 0);
let singleton: AxisHistogram<DiffLineKind> =
std::iter::once(DiffLineKind::Removed).collect();
assert!(!singleton.is_empty());
assert!(singleton.peak_count() >= 1);
}
#[test]
fn axis_histogram_peak_count_is_bounded_above_by_total() {
let single_cell_two_observations: &[DiffLineKind] =
&[DiffLineKind::Added, DiffLineKind::Added];
let multi_cell: &[DiffLineKind] = &[
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
];
let inputs: [&[DiffLineKind]; 4] = [
&[],
&[DiffLineKind::Removed],
single_cell_two_observations,
multi_cell,
];
for input in inputs {
let hist: AxisHistogram<DiffLineKind> = input.iter().copied().collect();
assert!(
hist.peak_count() <= hist.total(),
"peak_count {} must be <= total {} on input of length {}",
hist.peak_count(),
hist.total(),
input.len(),
);
if hist.distinct_cells() <= 1 {
assert_eq!(
hist.peak_count(),
hist.total(),
"peak_count must equal total when distinct_cells <= 1 \
on input of length {}",
input.len(),
);
} else {
assert!(
hist.peak_count() < hist.total(),
"peak_count must be strictly less than total when \
distinct_cells >= 2 on input of length {}",
input.len(),
);
}
}
}
#[test]
fn axis_histogram_peak_count_after_merge_is_monotone() {
let added_two: AxisHistogram<DiffLineKind> = [DiffLineKind::Added, DiffLineKind::Added]
.into_iter()
.collect();
let removed_one: AxisHistogram<DiffLineKind> =
std::iter::once(DiffLineKind::Removed).collect();
let context_and_added_two: AxisHistogram<DiffLineKind> = [
DiffLineKind::Context,
DiffLineKind::Added,
DiffLineKind::Added,
]
.into_iter()
.collect();
let empty_hist: AxisHistogram<DiffLineKind> = AxisHistogram::empty();
let disjoint = added_two.clone().merge(&removed_one);
assert_eq!(disjoint.peak_count(), 2);
assert!(disjoint.peak_count() >= added_two.peak_count());
assert!(disjoint.peak_count() >= removed_one.peak_count());
let overlap = added_two.clone().merge(&context_and_added_two);
assert_eq!(overlap.peak_count(), 4);
assert!(overlap.peak_count() >= added_two.peak_count());
assert!(overlap.peak_count() >= context_and_added_two.peak_count());
let with_empty = added_two.clone().merge(&empty_hist);
assert_eq!(with_empty.peak_count(), added_two.peak_count());
}
fn assert_dominant_observation_empty_is_none<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let hist = AxisHistogram::<A>::empty();
assert_eq!(
hist.dominant_observation(),
None,
"empty histogram dominant_observation must be None on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_dominant_observation_singleton_picks_observed_pair<A>()
where
A: ClosedAxis + std::fmt::Debug + PartialEq,
{
for observed in axis_iter::<A>() {
let hist: AxisHistogram<A> = std::iter::once(observed).collect();
assert_eq!(
hist.dominant_observation(),
Some((observed, 1)),
"singleton dominant_observation must equal Some(({observed:?}, 1)) \
on axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_dominant_observation_axis_cover_picks_first_pair<A>()
where
A: ClosedAxis + std::fmt::Debug + PartialEq,
{
let hist: AxisHistogram<A> = axis_iter::<A>().collect();
let first = axis_iter::<A>().next().expect(
"every ClosedAxis implementor has at least one variant per the ClosedAxis contract",
);
assert_eq!(
hist.dominant_observation(),
Some((first, 1)),
"uniform axis-cover histogram dominant_observation must be \
Some((first cell in declaration order, 1)) on axis {}",
std::any::type_name::<A>(),
);
}
#[test]
fn axis_histogram_dominant_observation_empty_is_none_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_dominant_observation_empty_is_none::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_dominant_observation_singleton_picks_observed_pair_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_dominant_observation_singleton_picks_observed_pair::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_dominant_observation_axis_cover_picks_first_pair_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_dominant_observation_axis_cover_picks_first_pair::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_dominant_observation_cell_projection_equals_dominant_cell() {
let inputs: [&[DiffLineKind]; 5] = [
&[],
&[DiffLineKind::Removed],
&[
DiffLineKind::Removed,
DiffLineKind::Added,
DiffLineKind::Added,
],
&[DiffLineKind::Added, DiffLineKind::Removed],
&[
DiffLineKind::Context,
DiffLineKind::Added,
DiffLineKind::Removed,
],
];
for input in inputs {
let hist: AxisHistogram<DiffLineKind> = input.iter().copied().collect();
assert_eq!(
hist.dominant_observation().map(|(c, _)| c),
hist.dominant_cell(),
"dominant_observation cell projection must equal dominant_cell on \
input of length {}",
input.len(),
);
}
}
#[test]
fn axis_histogram_dominant_observation_count_projection_equals_peak_count() {
let inputs: [&[DiffLineKind]; 5] = [
&[],
&[DiffLineKind::Removed],
&[
DiffLineKind::Removed,
DiffLineKind::Added,
DiffLineKind::Added,
],
&[DiffLineKind::Added, DiffLineKind::Removed],
&[
DiffLineKind::Context,
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
],
];
for input in inputs {
let hist: AxisHistogram<DiffLineKind> = input.iter().copied().collect();
assert_eq!(
hist.dominant_observation().map_or(0, |(_, n)| n),
hist.peak_count(),
"dominant_observation count projection must equal peak_count on \
input of length {}",
input.len(),
);
}
}
#[test]
fn axis_histogram_dominant_observation_fused_pair_agrees_with_open_coded_paired_form() {
let empty: AxisHistogram<DiffLineKind> = AxisHistogram::empty();
assert!(empty.dominant_observation().is_none());
assert!(empty.is_empty());
let inputs: [&[DiffLineKind]; 4] = [
&[DiffLineKind::Added],
&[
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Added,
],
&[DiffLineKind::Added, DiffLineKind::Removed],
&[
DiffLineKind::Context,
DiffLineKind::Added,
DiffLineKind::Removed,
],
];
for input in inputs {
let hist: AxisHistogram<DiffLineKind> = input.iter().copied().collect();
let open_coded = hist.dominant_cell().map(|c| (c, hist.peak_count()));
assert_eq!(
hist.dominant_observation(),
open_coded,
"dominant_observation must equal the open-coded \
dominant_cell().map(|c| (c, peak_count())) form on input of length {}",
input.len(),
);
}
}
#[test]
fn axis_histogram_dominant_observation_count_equals_count_of_pair_cell() {
let inputs: [&[DiffLineKind]; 4] = [
&[DiffLineKind::Added],
&[
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
],
&[DiffLineKind::Added, DiffLineKind::Removed],
&[
DiffLineKind::Context,
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
],
];
for input in inputs {
let hist: AxisHistogram<DiffLineKind> = input.iter().copied().collect();
let (cell, count) = hist
.dominant_observation()
.expect("non-empty histogram always has a dominant observation");
assert_eq!(
hist.count(cell),
count,
"dominant_observation pair count must equal count(pair.cell) on \
input of length {}",
input.len(),
);
}
}
#[test]
fn axis_histogram_dominant_observation_after_merge_count_is_monotone() {
let added_two: AxisHistogram<DiffLineKind> = [DiffLineKind::Added, DiffLineKind::Added]
.into_iter()
.collect();
let removed_one: AxisHistogram<DiffLineKind> =
std::iter::once(DiffLineKind::Removed).collect();
let added_three_context_one: AxisHistogram<DiffLineKind> = [
DiffLineKind::Context,
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Added,
]
.into_iter()
.collect();
let empty_hist: AxisHistogram<DiffLineKind> = AxisHistogram::empty();
let disjoint = added_two.clone().merge(&removed_one);
let (_, dcount) = disjoint.dominant_observation().expect("non-empty merge");
assert!(dcount >= added_two.peak_count());
assert!(dcount >= removed_one.peak_count());
let overlap = added_two.clone().merge(&added_three_context_one);
let (overlap_cell, overlap_count) =
overlap.dominant_observation().expect("non-empty merge");
assert_eq!(overlap_cell, DiffLineKind::Added);
assert_eq!(overlap_count, 5);
assert!(overlap_count >= added_two.peak_count());
assert!(overlap_count >= added_three_context_one.peak_count());
let with_empty = added_two.clone().merge(&empty_hist);
assert_eq!(
with_empty.dominant_observation(),
added_two.dominant_observation(),
);
}
fn assert_peak_multiplicity_empty_is_zero<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let hist = AxisHistogram::<A>::empty();
assert_eq!(
hist.peak_multiplicity(),
0,
"empty histogram peak_multiplicity must be 0 on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_peak_multiplicity_singleton_is_one<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
for observed in axis_iter::<A>() {
let hist: AxisHistogram<A> = std::iter::once(observed).collect();
assert_eq!(
hist.peak_multiplicity(),
1,
"singleton peak_multiplicity must equal 1 \
for observed cell {observed:?} on axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_peak_multiplicity_axis_cover_is_axis_cardinality<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let hist: AxisHistogram<A> = axis_iter::<A>().collect();
assert_eq!(
hist.peak_multiplicity(),
axis_cardinality::<A>(),
"uniform axis-cover histogram peak_multiplicity must equal \
axis_cardinality {} on axis {}",
axis_cardinality::<A>(),
std::any::type_name::<A>(),
);
}
#[test]
fn axis_histogram_peak_multiplicity_empty_is_zero_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_peak_multiplicity_empty_is_zero::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_peak_multiplicity_singleton_is_one_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_peak_multiplicity_singleton_is_one::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_peak_multiplicity_axis_cover_is_axis_cardinality_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_peak_multiplicity_axis_cover_is_axis_cardinality::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_peak_multiplicity_equals_open_coded_modal_level_set_count() {
let inputs: [&[DiffLineKind]; 5] = [
&[],
&[DiffLineKind::Added],
&[DiffLineKind::Added, DiffLineKind::Added],
&[DiffLineKind::Added, DiffLineKind::Removed],
&[
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Context,
],
];
for input in inputs {
let hist: AxisHistogram<DiffLineKind> = input.iter().copied().collect();
let peak = hist.peak_count();
let open_coded = hist.iter().filter(|&(_, c)| c > 0 && c == peak).count();
assert_eq!(
hist.peak_multiplicity(),
open_coded,
"peak_multiplicity must equal open-coded modal-level-set count \
on input of length {}",
input.len(),
);
}
}
#[test]
fn axis_histogram_peak_multiplicity_distinguishes_strict_modal_from_tied_modal() {
let strict_modal: AxisHistogram<DiffLineKind> = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
]
.into_iter()
.collect();
assert_eq!(strict_modal.peak_multiplicity(), 1);
let two_way_tied: AxisHistogram<DiffLineKind> =
[DiffLineKind::Added, DiffLineKind::Removed]
.into_iter()
.collect();
assert_eq!(two_way_tied.peak_multiplicity(), 2);
let three_way_tied: AxisHistogram<DiffLineKind> = [
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Context,
]
.into_iter()
.collect();
assert_eq!(three_way_tied.peak_multiplicity(), 3);
}
#[test]
fn axis_histogram_peak_multiplicity_is_bounded_above_by_distinct_cells() {
let strict_subset: AxisHistogram<DiffLineKind> = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Context,
]
.into_iter()
.collect();
let uniform_two_cell: AxisHistogram<DiffLineKind> =
[DiffLineKind::Added, DiffLineKind::Removed]
.into_iter()
.collect();
let inputs: [&[DiffLineKind]; 5] = [
&[],
&[DiffLineKind::Added],
&[
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
],
&[DiffLineKind::Added, DiffLineKind::Removed],
&[
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Context,
],
];
for input in inputs {
let hist: AxisHistogram<DiffLineKind> = input.iter().copied().collect();
assert!(
hist.peak_multiplicity() <= hist.distinct_cells(),
"peak_multiplicity {} must be <= distinct_cells {} on input of length {}",
hist.peak_multiplicity(),
hist.distinct_cells(),
input.len(),
);
if hist.is_uniform_count() {
assert_eq!(
hist.peak_multiplicity(),
hist.distinct_cells(),
"peak_multiplicity must equal distinct_cells on uniform-count \
histogram (input of length {})",
input.len(),
);
}
}
assert_eq!(strict_subset.distinct_cells(), 3);
assert_eq!(strict_subset.peak_multiplicity(), 1);
assert_eq!(uniform_two_cell.distinct_cells(), 2);
assert_eq!(uniform_two_cell.peak_multiplicity(), 2);
}
#[test]
fn axis_histogram_peak_multiplicity_is_zero_iff_is_empty() {
let empty: AxisHistogram<DiffLineKind> = AxisHistogram::empty();
assert!(empty.is_empty());
assert_eq!(empty.peak_multiplicity(), 0);
let singleton: AxisHistogram<DiffLineKind> =
std::iter::once(DiffLineKind::Removed).collect();
assert!(!singleton.is_empty());
assert!(singleton.peak_multiplicity() >= 1);
}
fn assert_trough_count_empty_is_zero<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let hist = AxisHistogram::<A>::empty();
assert_eq!(
hist.trough_count(),
0,
"empty histogram trough_count must be 0 on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_trough_count_singleton_is_one<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
for observed in axis_iter::<A>() {
let hist: AxisHistogram<A> = std::iter::once(observed).collect();
assert_eq!(
hist.trough_count(),
1,
"singleton trough_count must equal 1 \
for observed cell {observed:?} on axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_trough_count_axis_cover_is_one<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let hist: AxisHistogram<A> = axis_iter::<A>().collect();
assert_eq!(
hist.trough_count(),
1,
"uniform axis-cover histogram trough_count must equal 1 on {}",
std::any::type_name::<A>(),
);
}
#[test]
fn axis_histogram_trough_count_empty_is_zero_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_trough_count_empty_is_zero::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_trough_count_singleton_is_one_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_trough_count_singleton_is_one::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_trough_count_axis_cover_is_one_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_trough_count_axis_cover_is_one::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_trough_count_equals_recessive_cell_count_when_non_empty() {
let inputs: [&[DiffLineKind]; 4] = [
&[DiffLineKind::Added],
&[
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Added,
],
&[DiffLineKind::Added, DiffLineKind::Removed],
&[
DiffLineKind::Context,
DiffLineKind::Added,
DiffLineKind::Removed,
],
];
for input in inputs {
let hist: AxisHistogram<DiffLineKind> = input.iter().copied().collect();
let recessive = hist
.recessive_cell()
.expect("non-empty histogram has a recessive cell");
assert_eq!(
hist.trough_count(),
hist.count(recessive),
"trough_count must equal count(recessive_cell()) on non-empty input \
of length {}",
input.len(),
);
}
}
#[test]
fn axis_histogram_trough_count_iff_is_empty_is_zero() {
let empty: AxisHistogram<DiffLineKind> = AxisHistogram::empty();
assert!(empty.is_empty());
assert_eq!(empty.trough_count(), 0);
let singleton: AxisHistogram<DiffLineKind> =
std::iter::once(DiffLineKind::Removed).collect();
assert!(!singleton.is_empty());
assert!(singleton.trough_count() >= 1);
}
#[test]
fn axis_histogram_trough_count_is_bounded_above_by_peak_count() {
let single_cell_two_observations: &[DiffLineKind] =
&[DiffLineKind::Added, DiffLineKind::Added];
let two_cell_uniform: &[DiffLineKind] = &[DiffLineKind::Added, DiffLineKind::Removed];
let skew: &[DiffLineKind] = &[
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
];
let inputs: [&[DiffLineKind]; 5] = [
&[],
&[DiffLineKind::Removed],
single_cell_two_observations,
two_cell_uniform,
skew,
];
for input in inputs {
let hist: AxisHistogram<DiffLineKind> = input.iter().copied().collect();
assert!(
hist.trough_count() <= hist.peak_count(),
"trough_count {} must be <= peak_count {} on input of length {}",
hist.trough_count(),
hist.peak_count(),
input.len(),
);
let observed_counts: Vec<usize> = hist.nonzero().map(|(_, count)| count).collect();
let uniform = observed_counts
.first()
.is_some_and(|&first| observed_counts.iter().all(|&c| c == first));
if uniform || hist.is_empty() {
assert_eq!(
hist.trough_count(),
hist.peak_count(),
"trough_count must equal peak_count when observed counts \
are uniform on input of length {}",
input.len(),
);
} else {
assert!(
hist.trough_count() < hist.peak_count(),
"trough_count must be strictly less than peak_count when \
observed counts are non-uniform on input of length {}",
input.len(),
);
}
}
}
#[test]
fn axis_histogram_trough_count_after_merge_is_non_monotonic() {
let added_two: AxisHistogram<DiffLineKind> = [DiffLineKind::Added, DiffLineKind::Added]
.into_iter()
.collect();
let added_three: AxisHistogram<DiffLineKind> = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Added,
]
.into_iter()
.collect();
let removed_one: AxisHistogram<DiffLineKind> =
std::iter::once(DiffLineKind::Removed).collect();
let empty_hist: AxisHistogram<DiffLineKind> = AxisHistogram::empty();
let overlap = added_two.clone().merge(&added_three);
assert_eq!(overlap.trough_count(), 5);
assert!(overlap.trough_count() > added_two.trough_count());
assert!(overlap.trough_count() > added_three.trough_count());
let disjoint = added_two.clone().merge(&removed_one);
assert_eq!(disjoint.trough_count(), 1);
assert!(disjoint.trough_count() < added_two.trough_count());
assert_eq!(disjoint.trough_count(), removed_one.trough_count());
let with_empty = added_two.clone().merge(&empty_hist);
assert_eq!(with_empty.trough_count(), added_two.trough_count());
}
fn assert_recessive_observation_empty_is_none<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let hist = AxisHistogram::<A>::empty();
assert_eq!(
hist.recessive_observation(),
None,
"empty histogram recessive_observation must be None on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_recessive_observation_singleton_picks_observed_pair<A>()
where
A: ClosedAxis + std::fmt::Debug + PartialEq,
{
for observed in axis_iter::<A>() {
let hist: AxisHistogram<A> = std::iter::once(observed).collect();
assert_eq!(
hist.recessive_observation(),
Some((observed, 1)),
"singleton recessive_observation must equal Some(({observed:?}, 1)) \
on axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_recessive_observation_axis_cover_picks_first_pair<A>()
where
A: ClosedAxis + std::fmt::Debug + PartialEq,
{
let hist: AxisHistogram<A> = axis_iter::<A>().collect();
let first = axis_iter::<A>().next().expect(
"every ClosedAxis implementor has at least one variant per the ClosedAxis contract",
);
assert_eq!(
hist.recessive_observation(),
Some((first, 1)),
"uniform axis-cover histogram recessive_observation must be \
Some((first cell in declaration order, 1)) on axis {}",
std::any::type_name::<A>(),
);
}
#[test]
fn axis_histogram_recessive_observation_empty_is_none_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_recessive_observation_empty_is_none::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_recessive_observation_singleton_picks_observed_pair_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_recessive_observation_singleton_picks_observed_pair::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_recessive_observation_axis_cover_picks_first_pair_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_recessive_observation_axis_cover_picks_first_pair::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_recessive_observation_cell_projection_equals_recessive_cell() {
let inputs: [&[DiffLineKind]; 5] = [
&[],
&[DiffLineKind::Removed],
&[
DiffLineKind::Removed,
DiffLineKind::Added,
DiffLineKind::Added,
],
&[DiffLineKind::Added, DiffLineKind::Removed],
&[
DiffLineKind::Context,
DiffLineKind::Added,
DiffLineKind::Removed,
],
];
for input in inputs {
let hist: AxisHistogram<DiffLineKind> = input.iter().copied().collect();
assert_eq!(
hist.recessive_observation().map(|(c, _)| c),
hist.recessive_cell(),
"recessive_observation cell projection must equal recessive_cell on \
input of length {}",
input.len(),
);
}
}
#[test]
fn axis_histogram_recessive_observation_count_projection_equals_trough_count() {
let inputs: [&[DiffLineKind]; 5] = [
&[],
&[DiffLineKind::Removed],
&[
DiffLineKind::Removed,
DiffLineKind::Added,
DiffLineKind::Added,
],
&[DiffLineKind::Added, DiffLineKind::Removed],
&[
DiffLineKind::Context,
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
],
];
for input in inputs {
let hist: AxisHistogram<DiffLineKind> = input.iter().copied().collect();
assert_eq!(
hist.recessive_observation().map_or(0, |(_, n)| n),
hist.trough_count(),
"recessive_observation count projection must equal trough_count on \
input of length {}",
input.len(),
);
}
}
#[test]
fn axis_histogram_recessive_observation_fused_pair_agrees_with_open_coded_paired_form() {
let empty: AxisHistogram<DiffLineKind> = AxisHistogram::empty();
assert!(empty.recessive_observation().is_none());
assert!(empty.is_empty());
let inputs: [&[DiffLineKind]; 4] = [
&[DiffLineKind::Added],
&[
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Added,
],
&[DiffLineKind::Added, DiffLineKind::Removed],
&[
DiffLineKind::Context,
DiffLineKind::Added,
DiffLineKind::Removed,
],
];
for input in inputs {
let hist: AxisHistogram<DiffLineKind> = input.iter().copied().collect();
let open_coded = hist.recessive_cell().map(|c| (c, hist.trough_count()));
assert_eq!(
hist.recessive_observation(),
open_coded,
"recessive_observation must equal the open-coded \
recessive_cell().map(|c| (c, trough_count())) form on input of length {}",
input.len(),
);
}
}
#[test]
fn axis_histogram_recessive_observation_count_equals_count_of_pair_cell() {
let inputs: [&[DiffLineKind]; 4] = [
&[DiffLineKind::Added],
&[
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
],
&[DiffLineKind::Added, DiffLineKind::Removed],
&[
DiffLineKind::Context,
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
],
];
for input in inputs {
let hist: AxisHistogram<DiffLineKind> = input.iter().copied().collect();
let (cell, count) = hist
.recessive_observation()
.expect("non-empty histogram always has a recessive observation");
assert_eq!(
hist.count(cell),
count,
"recessive_observation pair count must equal count(pair.cell) on \
input of length {}",
input.len(),
);
assert!(
count >= 1,
"recessive_observation pair count must be >= 1 on non-empty input \
of length {}",
input.len(),
);
}
}
#[test]
fn axis_histogram_recessive_observation_count_is_bounded_above_by_dominant_observation_count() {
let inputs: [&[DiffLineKind]; 5] = [
&[],
&[DiffLineKind::Added],
&[DiffLineKind::Added, DiffLineKind::Added],
&[DiffLineKind::Added, DiffLineKind::Removed],
&[
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
],
];
for input in inputs {
let hist: AxisHistogram<DiffLineKind> = input.iter().copied().collect();
let recessive_n = hist.recessive_observation().map_or(0, |(_, n)| n);
let dominant_n = hist.dominant_observation().map_or(0, |(_, n)| n);
assert!(
recessive_n <= dominant_n,
"recessive_observation count {recessive_n} must be <= \
dominant_observation count {dominant_n} on input of length {}",
input.len(),
);
}
}
#[test]
fn axis_histogram_recessive_observation_after_merge_count_is_non_monotonic() {
let added_two: AxisHistogram<DiffLineKind> = [DiffLineKind::Added, DiffLineKind::Added]
.into_iter()
.collect();
let added_three: AxisHistogram<DiffLineKind> = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Added,
]
.into_iter()
.collect();
let removed_one: AxisHistogram<DiffLineKind> =
std::iter::once(DiffLineKind::Removed).collect();
let empty_hist: AxisHistogram<DiffLineKind> = AxisHistogram::empty();
let overlap = added_two.clone().merge(&added_three);
let (overlap_cell, overlap_count) =
overlap.recessive_observation().expect("non-empty merge");
assert_eq!(overlap_cell, DiffLineKind::Added);
assert_eq!(overlap_count, 5);
assert!(overlap_count > added_two.recessive_observation().map_or(0, |(_, n)| n));
assert!(overlap_count > added_three.recessive_observation().map_or(0, |(_, n)| n));
let disjoint = added_two.clone().merge(&removed_one);
let (_, disjoint_count) = disjoint.recessive_observation().expect("non-empty merge");
assert_eq!(disjoint_count, 1);
assert!(disjoint_count < added_two.recessive_observation().map_or(0, |(_, n)| n));
let with_empty = added_two.clone().merge(&empty_hist);
assert_eq!(
with_empty.recessive_observation(),
added_two.recessive_observation(),
);
}
fn assert_trough_multiplicity_empty_is_zero<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let hist = AxisHistogram::<A>::empty();
assert_eq!(
hist.trough_multiplicity(),
0,
"empty histogram trough_multiplicity must be 0 on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_trough_multiplicity_singleton_is_one<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
for observed in axis_iter::<A>() {
let hist: AxisHistogram<A> = std::iter::once(observed).collect();
assert_eq!(
hist.trough_multiplicity(),
1,
"singleton trough_multiplicity must equal 1 \
for observed cell {observed:?} on axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_trough_multiplicity_axis_cover_is_axis_cardinality<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let hist: AxisHistogram<A> = axis_iter::<A>().collect();
assert_eq!(
hist.trough_multiplicity(),
axis_cardinality::<A>(),
"uniform axis-cover histogram trough_multiplicity must equal \
axis_cardinality {} on axis {}",
axis_cardinality::<A>(),
std::any::type_name::<A>(),
);
}
#[test]
fn axis_histogram_trough_multiplicity_empty_is_zero_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_trough_multiplicity_empty_is_zero::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_trough_multiplicity_singleton_is_one_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_trough_multiplicity_singleton_is_one::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_trough_multiplicity_axis_cover_is_axis_cardinality_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_trough_multiplicity_axis_cover_is_axis_cardinality::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_trough_multiplicity_equals_open_coded_antimodal_level_set_count() {
let inputs: [&[DiffLineKind]; 5] = [
&[],
&[DiffLineKind::Added],
&[DiffLineKind::Added, DiffLineKind::Added],
&[DiffLineKind::Added, DiffLineKind::Removed],
&[
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Context,
],
];
for input in inputs {
let hist: AxisHistogram<DiffLineKind> = input.iter().copied().collect();
let trough = hist.trough_count();
let open_coded = hist.iter().filter(|&(_, c)| c > 0 && c == trough).count();
assert_eq!(
hist.trough_multiplicity(),
open_coded,
"trough_multiplicity must equal open-coded antimodal-level-set count \
on input of length {}",
input.len(),
);
}
}
#[test]
fn axis_histogram_trough_multiplicity_distinguishes_strict_trough_from_tied_trough() {
let strict_trough: AxisHistogram<DiffLineKind> = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
]
.into_iter()
.collect();
assert_eq!(strict_trough.trough_multiplicity(), 1);
let two_way_tied: AxisHistogram<DiffLineKind> = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Context,
]
.into_iter()
.collect();
assert_eq!(two_way_tied.trough_multiplicity(), 2);
let three_way_tied: AxisHistogram<DiffLineKind> = [
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Context,
]
.into_iter()
.collect();
assert_eq!(three_way_tied.trough_multiplicity(), 3);
}
#[test]
fn axis_histogram_trough_multiplicity_is_bounded_above_by_distinct_cells() {
let strict_subset: AxisHistogram<DiffLineKind> = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Context,
]
.into_iter()
.collect();
let uniform_two_cell: AxisHistogram<DiffLineKind> =
[DiffLineKind::Added, DiffLineKind::Removed]
.into_iter()
.collect();
let inputs: [&[DiffLineKind]; 5] = [
&[],
&[DiffLineKind::Added],
&[
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
],
&[DiffLineKind::Added, DiffLineKind::Removed],
&[
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Context,
],
];
for input in inputs {
let hist: AxisHistogram<DiffLineKind> = input.iter().copied().collect();
assert!(
hist.trough_multiplicity() <= hist.distinct_cells(),
"trough_multiplicity {} must be <= distinct_cells {} on input of length {}",
hist.trough_multiplicity(),
hist.distinct_cells(),
input.len(),
);
if hist.is_uniform_count() {
assert_eq!(
hist.trough_multiplicity(),
hist.distinct_cells(),
"trough_multiplicity must equal distinct_cells on uniform-count \
histogram (input of length {})",
input.len(),
);
assert_eq!(
hist.trough_multiplicity(),
hist.peak_multiplicity(),
"trough_multiplicity must equal peak_multiplicity on \
uniform-count histogram (input of length {})",
input.len(),
);
}
}
assert_eq!(strict_subset.distinct_cells(), 3);
assert_eq!(strict_subset.trough_multiplicity(), 2);
assert_eq!(uniform_two_cell.distinct_cells(), 2);
assert_eq!(uniform_two_cell.trough_multiplicity(), 2);
}
#[test]
fn axis_histogram_trough_multiplicity_is_zero_iff_is_empty() {
let empty: AxisHistogram<DiffLineKind> = AxisHistogram::empty();
assert!(empty.is_empty());
assert_eq!(empty.trough_multiplicity(), 0);
let singleton: AxisHistogram<DiffLineKind> =
std::iter::once(DiffLineKind::Removed).collect();
assert!(!singleton.is_empty());
assert!(singleton.trough_multiplicity() >= 1);
}
fn assert_spread_empty_is_zero<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let hist = AxisHistogram::<A>::empty();
assert_eq!(
hist.spread(),
0,
"empty histogram spread must be 0 on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_spread_singleton_is_zero<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
for observed in axis_iter::<A>() {
let hist: AxisHistogram<A> = std::iter::once(observed).collect();
assert_eq!(
hist.spread(),
0,
"singleton spread must be 0 for observed cell {observed:?} on axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_spread_axis_cover_is_zero<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let hist: AxisHistogram<A> = axis_iter::<A>().collect();
assert_eq!(
hist.spread(),
0,
"axis-cover histogram spread must be 0 on axis {}",
std::any::type_name::<A>(),
);
}
#[test]
fn axis_histogram_spread_empty_is_zero_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_spread_empty_is_zero::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_spread_singleton_is_zero_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_spread_singleton_is_zero::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_spread_axis_cover_is_zero_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_spread_axis_cover_is_zero::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_spread_equals_peak_minus_trough() {
let inputs: [&[DiffLineKind]; 5] = [
&[],
&[DiffLineKind::Added],
&[
DiffLineKind::Context,
DiffLineKind::Added,
DiffLineKind::Removed,
],
&[
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
],
&[
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
],
];
for input in inputs {
let hist: AxisHistogram<DiffLineKind> = input.iter().copied().collect();
assert_eq!(
hist.spread(),
hist.peak_count() - hist.trough_count(),
"spread must equal peak_count - trough_count on input of length {}",
input.len(),
);
}
}
#[test]
fn axis_histogram_spread_zero_iff_uniformly_observed_count() {
let empty: AxisHistogram<DiffLineKind> = AxisHistogram::empty();
assert_eq!(empty.spread(), 0);
let singleton: AxisHistogram<DiffLineKind> = std::iter::once(DiffLineKind::Added).collect();
assert_eq!(singleton.spread(), 0);
let axis_cover: AxisHistogram<DiffLineKind> = axis_iter::<DiffLineKind>().collect();
assert_eq!(axis_cover.spread(), 0);
let two_each: AxisHistogram<DiffLineKind> = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Removed,
]
.into_iter()
.collect();
assert_eq!(two_each.spread(), 0);
let skewed: AxisHistogram<DiffLineKind> = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
]
.into_iter()
.collect();
assert!(skewed.spread() > 0);
assert_eq!(skewed.spread(), 1);
let heavy_tail: AxisHistogram<DiffLineKind> = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
]
.into_iter()
.collect();
assert!(heavy_tail.spread() > 0);
assert_eq!(heavy_tail.spread(), 3);
}
#[test]
fn axis_histogram_spread_agrees_with_dominant_recessive_cell_equality() {
let singleton: AxisHistogram<DiffLineKind> =
std::iter::once(DiffLineKind::Removed).collect();
assert_eq!(
singleton.spread() == 0,
singleton.dominant_cell() == singleton.recessive_cell(),
);
let uniform: AxisHistogram<DiffLineKind> = axis_iter::<DiffLineKind>().collect();
assert_eq!(
uniform.spread() == 0,
uniform.dominant_cell() == uniform.recessive_cell(),
);
let skewed: AxisHistogram<DiffLineKind> = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
]
.into_iter()
.collect();
assert_eq!(
skewed.spread() == 0,
skewed.dominant_cell() == skewed.recessive_cell(),
);
}
#[test]
fn axis_histogram_spread_is_bounded_above_by_peak_count_and_total() {
let inputs: [&[DiffLineKind]; 4] = [
&[],
&[DiffLineKind::Added],
&[
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Context,
],
&[
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
],
];
for input in inputs {
let hist: AxisHistogram<DiffLineKind> = input.iter().copied().collect();
assert!(
hist.spread() <= hist.peak_count(),
"spread {} must be <= peak_count {} on input of length {}",
hist.spread(),
hist.peak_count(),
input.len(),
);
assert!(
hist.spread() <= hist.total(),
"spread {} must be <= total {} on input of length {}",
hist.spread(),
hist.total(),
input.len(),
);
assert_eq!(
hist.spread() == hist.peak_count(),
hist.trough_count() == 0,
"spread == peak_count iff trough_count == 0 on input of length {}",
input.len(),
);
}
}
#[test]
fn axis_histogram_spread_after_merge_is_non_monotonic() {
let added_two: AxisHistogram<DiffLineKind> = [DiffLineKind::Added, DiffLineKind::Added]
.into_iter()
.collect();
let added_two_removed_one: AxisHistogram<DiffLineKind> = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
]
.into_iter()
.collect();
let removed_two: AxisHistogram<DiffLineKind> =
[DiffLineKind::Removed, DiffLineKind::Removed]
.into_iter()
.collect();
let empty_hist: AxisHistogram<DiffLineKind> = AxisHistogram::empty();
let grow = added_two.clone().merge(&added_two_removed_one);
assert_eq!(grow.spread(), 3);
assert!(grow.spread() > added_two.spread());
assert!(grow.spread() > added_two_removed_one.spread());
let added_two_removed_one_b: AxisHistogram<DiffLineKind> = [
DiffLineKind::Removed,
DiffLineKind::Removed,
DiffLineKind::Added,
]
.into_iter()
.collect();
let shrink = added_two_removed_one
.clone()
.merge(&added_two_removed_one_b);
assert_eq!(shrink.spread(), 0);
assert!(shrink.spread() < added_two_removed_one.spread());
assert!(shrink.spread() < added_two_removed_one_b.spread());
let two_singletons = added_two.clone().merge(&removed_two);
assert_eq!(two_singletons.spread(), 0);
assert_eq!(two_singletons.spread(), added_two.spread());
let with_empty = added_two_removed_one.clone().merge(&empty_hist);
assert_eq!(with_empty.spread(), added_two_removed_one.spread());
}
fn assert_unobserved_cells_empty_equals_cardinality<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let hist = AxisHistogram::<A>::empty();
assert_eq!(
hist.unobserved_cells(),
axis_cardinality::<A>(),
"empty histogram unobserved_cells must equal axis_cardinality on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_unobserved_cells_singleton_is_cardinality_minus_one<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let n = axis_cardinality::<A>();
for observed in axis_iter::<A>() {
let hist: AxisHistogram<A> = std::iter::once(observed).collect();
assert_eq!(
hist.unobserved_cells(),
n - 1,
"singleton unobserved_cells must equal axis_cardinality - 1 \
for observed cell {observed:?} on axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_unobserved_cells_axis_cover_is_zero<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let hist: AxisHistogram<A> = axis_iter::<A>().collect();
assert_eq!(
hist.unobserved_cells(),
0,
"axis-cover histogram unobserved_cells must be 0 on axis {}",
std::any::type_name::<A>(),
);
}
#[test]
fn axis_histogram_unobserved_cells_empty_equals_cardinality_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_unobserved_cells_empty_equals_cardinality::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_unobserved_cells_singleton_is_cardinality_minus_one_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_unobserved_cells_singleton_is_cardinality_minus_one::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_unobserved_cells_axis_cover_is_zero_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_unobserved_cells_axis_cover_is_zero::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_unobserved_cells_equals_unobserved_iterator_count() {
let inputs: [&[DiffLineKind]; 5] = [
&[],
&[DiffLineKind::Added],
&[DiffLineKind::Added, DiffLineKind::Removed],
&[
DiffLineKind::Context,
DiffLineKind::Added,
DiffLineKind::Removed,
],
&[
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
],
];
for input in inputs {
let hist: AxisHistogram<DiffLineKind> = input.iter().copied().collect();
assert_eq!(
hist.unobserved_cells(),
hist.unobserved().count(),
"unobserved_cells must equal unobserved().count() on input of length {}",
input.len(),
);
}
}
#[test]
fn axis_histogram_distinct_cells_plus_unobserved_cells_equals_cardinality() {
let inputs: [&[DiffLineKind]; 5] = [
&[],
&[DiffLineKind::Added],
&[DiffLineKind::Added, DiffLineKind::Removed],
&[
DiffLineKind::Context,
DiffLineKind::Added,
DiffLineKind::Removed,
],
&[
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
],
];
let n = axis_cardinality::<DiffLineKind>();
for input in inputs {
let hist: AxisHistogram<DiffLineKind> = input.iter().copied().collect();
assert_eq!(
hist.distinct_cells() + hist.unobserved_cells(),
n,
"distinct_cells + unobserved_cells must equal axis_cardinality \
on input of length {}",
input.len(),
);
}
}
#[test]
fn axis_histogram_unobserved_cells_equals_cardinality_minus_distinct_cells() {
let inputs: [&[DiffLineKind]; 4] = [
&[],
&[DiffLineKind::Added],
&[DiffLineKind::Added, DiffLineKind::Removed],
&[
DiffLineKind::Context,
DiffLineKind::Added,
DiffLineKind::Removed,
],
];
let n = axis_cardinality::<DiffLineKind>();
for input in inputs {
let hist: AxisHistogram<DiffLineKind> = input.iter().copied().collect();
assert_eq!(
hist.unobserved_cells(),
n - hist.distinct_cells(),
"unobserved_cells must equal axis_cardinality - distinct_cells \
on input of length {}",
input.len(),
);
}
}
#[test]
fn axis_histogram_unobserved_cells_is_bounded_above_by_axis_cardinality() {
let inputs: [&[DiffLineKind]; 4] = [
&[],
&[DiffLineKind::Removed],
&[
DiffLineKind::Context,
DiffLineKind::Added,
DiffLineKind::Removed,
],
&[
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
],
];
let n = axis_cardinality::<DiffLineKind>();
for input in inputs {
let hist: AxisHistogram<DiffLineKind> = input.iter().copied().collect();
let gap = hist.unobserved_cells();
assert!(
gap <= n,
"unobserved_cells {gap} must be <= axis_cardinality {n} \
on input of length {}",
input.len(),
);
}
}
#[test]
fn axis_histogram_unobserved_cells_full_cover_iff_distinct_equals_cardinality() {
let n = axis_cardinality::<DiffLineKind>();
let full_cover: AxisHistogram<DiffLineKind> = axis_iter::<DiffLineKind>().collect();
assert_eq!(full_cover.unobserved_cells(), 0);
assert_eq!(full_cover.distinct_cells(), n);
assert_eq!(
full_cover.unobserved_cells() == 0,
full_cover.distinct_cells() == n,
);
let partial: AxisHistogram<DiffLineKind> = std::iter::once(DiffLineKind::Added).collect();
assert!(partial.unobserved_cells() > 0);
assert!(partial.distinct_cells() < n);
assert_eq!(
partial.unobserved_cells() == 0,
partial.distinct_cells() == n,
);
}
#[test]
fn axis_histogram_unobserved_cells_iff_is_empty_equals_cardinality() {
let n = axis_cardinality::<DiffLineKind>();
let empty: AxisHistogram<DiffLineKind> = AxisHistogram::empty();
assert!(empty.is_empty());
assert_eq!(empty.unobserved_cells(), n);
let singleton: AxisHistogram<DiffLineKind> =
std::iter::once(DiffLineKind::Removed).collect();
assert!(!singleton.is_empty());
assert!(singleton.unobserved_cells() <= n - 1);
}
#[test]
fn axis_histogram_unobserved_cells_after_merge_is_monotone_decreasing() {
let lhs: AxisHistogram<DiffLineKind> = [DiffLineKind::Removed, DiffLineKind::Added]
.into_iter()
.collect();
let rhs: AxisHistogram<DiffLineKind> = [DiffLineKind::Added, DiffLineKind::Context]
.into_iter()
.collect();
let empty_hist: AxisHistogram<DiffLineKind> = AxisHistogram::empty();
let merged = lhs.clone().merge(&rhs);
assert_eq!(merged.unobserved_cells(), 0);
assert!(merged.unobserved_cells() <= lhs.unobserved_cells());
assert!(merged.unobserved_cells() <= rhs.unobserved_cells());
let solo_added: AxisHistogram<DiffLineKind> =
std::iter::once(DiffLineKind::Added).collect();
let solo_context: AxisHistogram<DiffLineKind> =
std::iter::once(DiffLineKind::Context).collect();
let disjoint = solo_added.clone().merge(&solo_context);
assert_eq!(disjoint.unobserved_cells(), 1);
assert!(disjoint.unobserved_cells() <= solo_added.unobserved_cells());
assert!(disjoint.unobserved_cells() <= solo_context.unobserved_cells());
let with_empty = lhs.clone().merge(&empty_hist);
assert_eq!(with_empty.unobserved_cells(), lhs.unobserved_cells());
}
fn assert_is_full_cover_on_axis_cover<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let hist: AxisHistogram<A> = axis_iter::<A>().collect();
assert!(
hist.is_full_cover(),
"axis-cover histogram must read is_full_cover on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_is_full_cover_empty_iff_axis_is_empty<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let hist = AxisHistogram::<A>::empty();
assert_eq!(
hist.is_full_cover(),
axis_cardinality::<A>() == 0,
"empty histogram is_full_cover must equal (axis_cardinality == 0) \
on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_is_full_cover_singleton_iff_cardinality_is_one<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let n = axis_cardinality::<A>();
for observed in axis_iter::<A>() {
let hist: AxisHistogram<A> = std::iter::once(observed).collect();
assert_eq!(
hist.is_full_cover(),
n == 1,
"singleton is_full_cover must equal (axis_cardinality == 1) \
for observed cell {observed:?} on axis {}",
std::any::type_name::<A>(),
);
}
}
#[test]
fn axis_histogram_is_full_cover_on_axis_cover_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_is_full_cover_on_axis_cover::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_is_full_cover_empty_iff_axis_is_empty_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_is_full_cover_empty_iff_axis_is_empty::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_is_full_cover_singleton_iff_cardinality_is_one_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_is_full_cover_singleton_iff_cardinality_is_one::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_is_full_cover_equals_unobserved_cells_zero() {
let inputs: [&[DiffLineKind]; 5] = [
&[],
&[DiffLineKind::Added],
&[DiffLineKind::Added, DiffLineKind::Removed],
&[
DiffLineKind::Context,
DiffLineKind::Added,
DiffLineKind::Removed,
],
&[
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
],
];
for input in inputs {
let hist: AxisHistogram<DiffLineKind> = input.iter().copied().collect();
assert_eq!(
hist.is_full_cover(),
hist.unobserved_cells() == 0,
"is_full_cover must equal (unobserved_cells == 0) \
on input of length {}",
input.len(),
);
}
}
#[test]
fn axis_histogram_is_full_cover_equals_distinct_equals_cardinality() {
let inputs: [&[DiffLineKind]; 5] = [
&[],
&[DiffLineKind::Added],
&[DiffLineKind::Added, DiffLineKind::Removed],
&[
DiffLineKind::Context,
DiffLineKind::Added,
DiffLineKind::Removed,
],
&[
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
],
];
let n = axis_cardinality::<DiffLineKind>();
for input in inputs {
let hist: AxisHistogram<DiffLineKind> = input.iter().copied().collect();
assert_eq!(
hist.is_full_cover(),
hist.distinct_cells() == n,
"is_full_cover must equal (distinct_cells == axis_cardinality) \
on input of length {}",
input.len(),
);
}
}
#[test]
fn axis_histogram_is_full_cover_equals_unobserved_iterator_is_empty() {
let inputs: [&[DiffLineKind]; 4] = [
&[],
&[DiffLineKind::Added],
&[DiffLineKind::Added, DiffLineKind::Removed],
&[
DiffLineKind::Context,
DiffLineKind::Added,
DiffLineKind::Removed,
],
];
for input in inputs {
let hist: AxisHistogram<DiffLineKind> = input.iter().copied().collect();
assert_eq!(
hist.is_full_cover(),
hist.unobserved().next().is_none(),
"is_full_cover must equal unobserved().next().is_none() \
on input of length {}",
input.len(),
);
}
}
#[test]
fn axis_histogram_is_empty_and_is_full_cover_are_disjoint_on_nonempty_axis() {
let empty: AxisHistogram<DiffLineKind> = AxisHistogram::empty();
assert!(empty.is_empty());
assert!(!empty.is_full_cover());
let full_cover: AxisHistogram<DiffLineKind> = axis_iter::<DiffLineKind>().collect();
assert!(!full_cover.is_empty());
assert!(full_cover.is_full_cover());
let partial: AxisHistogram<DiffLineKind> = std::iter::once(DiffLineKind::Added).collect();
assert!(!partial.is_empty());
assert!(!partial.is_full_cover());
}
#[test]
fn axis_histogram_is_full_cover_after_merge_is_boolean_monotone() {
let lhs: AxisHistogram<DiffLineKind> = [DiffLineKind::Removed, DiffLineKind::Added]
.into_iter()
.collect();
let rhs: AxisHistogram<DiffLineKind> = [DiffLineKind::Added, DiffLineKind::Context]
.into_iter()
.collect();
let merged = lhs.clone().merge(&rhs);
assert!(merged.is_full_cover());
assert!(merged.is_full_cover() >= (lhs.is_full_cover() || rhs.is_full_cover()));
let solo_added: AxisHistogram<DiffLineKind> =
std::iter::once(DiffLineKind::Added).collect();
let solo_context: AxisHistogram<DiffLineKind> =
std::iter::once(DiffLineKind::Context).collect();
let disjoint = solo_added.clone().merge(&solo_context);
assert!(!disjoint.is_full_cover());
assert!(
disjoint.is_full_cover()
>= (solo_added.is_full_cover() || solo_context.is_full_cover())
);
let full_cover: AxisHistogram<DiffLineKind> = axis_iter::<DiffLineKind>().collect();
let empty_hist: AxisHistogram<DiffLineKind> = AxisHistogram::empty();
let full_cover_with_empty = full_cover.clone().merge(&empty_hist);
assert!(full_cover_with_empty.is_full_cover());
assert_eq!(
full_cover_with_empty.is_full_cover(),
full_cover.is_full_cover()
);
let lhs_with_empty = lhs.clone().merge(&empty_hist);
assert_eq!(lhs_with_empty.is_full_cover(), lhs.is_full_cover());
}
fn assert_is_uniform_count_empty_is_true<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let hist = AxisHistogram::<A>::empty();
assert!(
hist.is_uniform_count(),
"empty histogram is_uniform_count must be true on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_is_uniform_count_singleton_is_true<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
for observed in axis_iter::<A>() {
let hist: AxisHistogram<A> = std::iter::once(observed).collect();
assert!(
hist.is_uniform_count(),
"singleton is_uniform_count must be true for observed cell \
{observed:?} on axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_is_uniform_count_axis_cover_is_true<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let hist: AxisHistogram<A> = axis_iter::<A>().collect();
assert!(
hist.is_uniform_count(),
"axis-cover histogram is_uniform_count must be true on axis {}",
std::any::type_name::<A>(),
);
}
#[test]
fn axis_histogram_is_uniform_count_empty_is_true_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_is_uniform_count_empty_is_true::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_is_uniform_count_singleton_is_true_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_is_uniform_count_singleton_is_true::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_is_uniform_count_axis_cover_is_true_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_is_uniform_count_axis_cover_is_true::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_is_uniform_count_equals_spread_is_zero() {
let inputs: [&[DiffLineKind]; 5] = [
&[],
&[DiffLineKind::Added],
&[DiffLineKind::Added, DiffLineKind::Removed],
&[
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
],
&[
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
],
];
for input in inputs {
let hist: AxisHistogram<DiffLineKind> = input.iter().copied().collect();
assert_eq!(
hist.is_uniform_count(),
hist.spread() == 0,
"is_uniform_count must equal (spread == 0) on input of length {}",
input.len(),
);
}
}
#[test]
fn axis_histogram_is_uniform_count_equals_peak_equals_trough() {
let inputs: [&[DiffLineKind]; 5] = [
&[],
&[DiffLineKind::Added],
&[
DiffLineKind::Removed,
DiffLineKind::Added,
DiffLineKind::Context,
],
&[
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
],
&[
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
],
];
for input in inputs {
let hist: AxisHistogram<DiffLineKind> = input.iter().copied().collect();
assert_eq!(
hist.is_uniform_count(),
hist.peak_count() == hist.trough_count(),
"is_uniform_count must equal (peak_count == trough_count) \
on input of length {}",
input.len(),
);
}
}
#[test]
fn axis_histogram_is_uniform_count_equals_dominant_equals_recessive() {
let inputs: [&[DiffLineKind]; 5] = [
&[],
&[DiffLineKind::Added],
&[
DiffLineKind::Removed,
DiffLineKind::Added,
DiffLineKind::Context,
],
&[
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
],
&[
DiffLineKind::Removed,
DiffLineKind::Removed,
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Context,
],
];
for input in inputs {
let hist: AxisHistogram<DiffLineKind> = input.iter().copied().collect();
assert_eq!(
hist.is_uniform_count(),
hist.dominant_cell() == hist.recessive_cell(),
"is_uniform_count must equal (dominant_cell == recessive_cell) \
on input of length {}",
input.len(),
);
}
}
#[test]
fn axis_histogram_is_uniform_count_witnesses_strict_skew_is_false() {
let binary_skew: AxisHistogram<DiffLineKind> = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
]
.into_iter()
.collect();
assert!(!binary_skew.is_uniform_count());
assert_eq!(binary_skew.spread(), 1);
let heavy_tail: AxisHistogram<DiffLineKind> = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Context,
]
.into_iter()
.collect();
assert!(!heavy_tail.is_uniform_count());
assert_eq!(heavy_tail.spread(), 2);
let three_level: AxisHistogram<DiffLineKind> = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Removed,
DiffLineKind::Context,
]
.into_iter()
.collect();
assert!(!three_level.is_uniform_count());
assert_eq!(three_level.spread(), 3);
}
#[test]
fn axis_histogram_is_empty_is_full_cover_is_uniform_count_triple_classifies_canonical_shapes() {
let empty: AxisHistogram<DiffLineKind> = AxisHistogram::empty();
assert_eq!(
(
empty.is_empty(),
empty.is_full_cover(),
empty.is_uniform_count(),
),
(true, false, true),
);
let singleton: AxisHistogram<DiffLineKind> = std::iter::once(DiffLineKind::Added).collect();
assert_eq!(
(
singleton.is_empty(),
singleton.is_full_cover(),
singleton.is_uniform_count(),
),
(false, false, true),
);
let partial_skew: AxisHistogram<DiffLineKind> = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
]
.into_iter()
.collect();
assert_eq!(
(
partial_skew.is_empty(),
partial_skew.is_full_cover(),
partial_skew.is_uniform_count(),
),
(false, false, false),
);
let axis_cover: AxisHistogram<DiffLineKind> = axis_iter::<DiffLineKind>().collect();
assert_eq!(
(
axis_cover.is_empty(),
axis_cover.is_full_cover(),
axis_cover.is_uniform_count(),
),
(false, true, true),
);
let partial_uniform: AxisHistogram<DiffLineKind> =
[DiffLineKind::Added, DiffLineKind::Removed]
.into_iter()
.collect();
assert_eq!(
(
partial_uniform.is_empty(),
partial_uniform.is_full_cover(),
partial_uniform.is_uniform_count(),
),
(false, false, true),
);
let full_cover_skew: AxisHistogram<DiffLineKind> = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Context,
]
.into_iter()
.collect();
assert_eq!(
(
full_cover_skew.is_empty(),
full_cover_skew.is_full_cover(),
full_cover_skew.is_uniform_count(),
),
(false, true, false),
);
}
#[test]
fn axis_histogram_is_uniform_count_after_merge_is_non_monotone() {
let solo_added: AxisHistogram<DiffLineKind> =
std::iter::once(DiffLineKind::Added).collect();
let solo_removed: AxisHistogram<DiffLineKind> =
std::iter::once(DiffLineKind::Removed).collect();
let merged_uniform = solo_added.clone().merge(&solo_removed);
assert!(merged_uniform.is_uniform_count());
assert!(solo_added.is_uniform_count() && solo_removed.is_uniform_count());
let added_twice: AxisHistogram<DiffLineKind> = [DiffLineKind::Added, DiffLineKind::Added]
.into_iter()
.collect();
assert!(added_twice.is_uniform_count());
let mixed = added_twice.clone().merge(&solo_removed);
assert!(!mixed.is_uniform_count());
let lhs_skew: AxisHistogram<DiffLineKind> = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
]
.into_iter()
.collect();
let rhs_skew: AxisHistogram<DiffLineKind> = [
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Removed,
]
.into_iter()
.collect();
assert!(!lhs_skew.is_uniform_count());
assert!(!rhs_skew.is_uniform_count());
let cancelled = lhs_skew.clone().merge(&rhs_skew);
assert!(cancelled.is_uniform_count());
let empty_hist: AxisHistogram<DiffLineKind> = AxisHistogram::empty();
let uniform_with_empty = added_twice.clone().merge(&empty_hist);
assert_eq!(
uniform_with_empty.is_uniform_count(),
added_twice.is_uniform_count(),
);
let skew_with_empty = lhs_skew.clone().merge(&empty_hist);
assert_eq!(
skew_with_empty.is_uniform_count(),
lhs_skew.is_uniform_count(),
);
}
fn assert_has_singular_support_empty_is_false<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let hist = AxisHistogram::<A>::empty();
assert!(
!hist.has_singular_support(),
"empty histogram has_singular_support must be false on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_has_singular_support_singleton_is_true<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
for observed in axis_iter::<A>() {
let hist: AxisHistogram<A> = std::iter::once(observed).collect();
assert!(
hist.has_singular_support(),
"singleton has_singular_support must be true for observed cell \
{observed:?} on axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_has_singular_support_axis_cover_iff_cardinality_is_one<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let hist: AxisHistogram<A> = axis_iter::<A>().collect();
assert_eq!(
hist.has_singular_support(),
axis_cardinality::<A>() == 1,
"axis-cover has_singular_support must equal (axis_cardinality == 1) \
on axis {}",
std::any::type_name::<A>(),
);
}
#[test]
fn axis_histogram_has_singular_support_empty_is_false_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_has_singular_support_empty_is_false::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_has_singular_support_singleton_is_true_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_has_singular_support_singleton_is_true::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_has_singular_support_axis_cover_iff_cardinality_is_one_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_has_singular_support_axis_cover_iff_cardinality_is_one::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_has_singular_support_equals_distinct_cells_is_one() {
let inputs: [&[DiffLineKind]; 6] = [
&[],
&[DiffLineKind::Added],
&[DiffLineKind::Added, DiffLineKind::Added],
&[
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
],
&[DiffLineKind::Added, DiffLineKind::Removed],
&[
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Context,
],
];
for input in inputs {
let hist: AxisHistogram<DiffLineKind> = input.iter().copied().collect();
assert_eq!(
hist.has_singular_support(),
hist.distinct_cells() == 1,
"has_singular_support must equal (distinct_cells == 1) \
on input of length {}",
input.len(),
);
}
}
#[test]
fn axis_histogram_has_singular_support_equals_peak_equals_total_on_nonempty() {
let inputs: [&[DiffLineKind]; 6] = [
&[],
&[DiffLineKind::Added],
&[
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Added,
],
&[
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
],
&[DiffLineKind::Added, DiffLineKind::Removed],
&[
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Context,
],
];
for input in inputs {
let hist: AxisHistogram<DiffLineKind> = input.iter().copied().collect();
assert_eq!(
hist.has_singular_support(),
hist.peak_count() == hist.total() && !hist.is_empty(),
"has_singular_support must equal (peak_count == total && !is_empty) \
on input of length {}",
input.len(),
);
}
}
#[test]
fn axis_histogram_support_cardinality_boundary_triple_is_pairwise_disjoint() {
let empty: AxisHistogram<DiffLineKind> = AxisHistogram::empty();
assert_eq!(
(
empty.is_empty(),
empty.has_singular_support(),
empty.is_full_cover(),
),
(true, false, false),
);
let singleton: AxisHistogram<DiffLineKind> = std::iter::once(DiffLineKind::Added).collect();
assert_eq!(
(
singleton.is_empty(),
singleton.has_singular_support(),
singleton.is_full_cover(),
),
(false, true, false),
);
let axis_cover: AxisHistogram<DiffLineKind> = axis_iter::<DiffLineKind>().collect();
assert_eq!(
(
axis_cover.is_empty(),
axis_cover.has_singular_support(),
axis_cover.is_full_cover(),
),
(false, false, true),
);
let partial_multi: AxisHistogram<DiffLineKind> =
[DiffLineKind::Added, DiffLineKind::Removed]
.into_iter()
.collect();
assert_eq!(
(
partial_multi.is_empty(),
partial_multi.has_singular_support(),
partial_multi.is_full_cover(),
),
(false, false, false),
);
for hist in [&empty, &singleton, &axis_cover, &partial_multi] {
let e = hist.is_empty();
let s = hist.has_singular_support();
let f = hist.is_full_cover();
assert!(
!(e && s),
"is_empty and has_singular_support must be disjoint",
);
assert!(!(e && f), "is_empty and is_full_cover must be disjoint",);
assert!(
!(s && f),
"has_singular_support and is_full_cover must be disjoint \
on cardinality-≥ 2 implementors",
);
}
}
#[test]
fn axis_histogram_has_singular_support_implies_is_uniform_count() {
let solo: AxisHistogram<DiffLineKind> = std::iter::once(DiffLineKind::Added).collect();
assert!(solo.has_singular_support());
assert!(solo.is_uniform_count());
let many_solo: AxisHistogram<DiffLineKind> = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Added,
]
.into_iter()
.collect();
assert!(many_solo.has_singular_support());
assert!(many_solo.is_uniform_count());
let heavy_solo: AxisHistogram<DiffLineKind> =
std::iter::repeat_n(DiffLineKind::Removed, 7).collect();
assert!(heavy_solo.has_singular_support());
assert!(heavy_solo.is_uniform_count());
let empty: AxisHistogram<DiffLineKind> = AxisHistogram::empty();
assert!(empty.is_uniform_count());
assert!(!empty.has_singular_support());
let uniform_pair: AxisHistogram<DiffLineKind> =
[DiffLineKind::Added, DiffLineKind::Removed]
.into_iter()
.collect();
assert!(uniform_pair.is_uniform_count());
assert!(!uniform_pair.has_singular_support());
let axis_cover: AxisHistogram<DiffLineKind> = axis_iter::<DiffLineKind>().collect();
assert!(axis_cover.is_uniform_count());
assert!(!axis_cover.has_singular_support());
}
#[test]
fn axis_histogram_has_singular_support_after_merge_is_non_monotone() {
let solo_added: AxisHistogram<DiffLineKind> =
std::iter::once(DiffLineKind::Added).collect();
let solo_removed: AxisHistogram<DiffLineKind> =
std::iter::once(DiffLineKind::Removed).collect();
assert!(solo_added.has_singular_support());
assert!(solo_removed.has_singular_support());
let merged_pair = solo_added.clone().merge(&solo_removed);
assert!(!merged_pair.has_singular_support());
let more_added: AxisHistogram<DiffLineKind> =
std::iter::repeat_n(DiffLineKind::Added, 4).collect();
assert!(more_added.has_singular_support());
let merged_same = solo_added.clone().merge(&more_added);
assert!(merged_same.has_singular_support());
let mixed_lhs: AxisHistogram<DiffLineKind> = [DiffLineKind::Added, DiffLineKind::Removed]
.into_iter()
.collect();
let mixed_rhs: AxisHistogram<DiffLineKind> = [DiffLineKind::Removed, DiffLineKind::Context]
.into_iter()
.collect();
assert!(!mixed_lhs.has_singular_support());
assert!(!mixed_rhs.has_singular_support());
let merged_full = mixed_lhs.clone().merge(&mixed_rhs);
assert!(!merged_full.has_singular_support());
let empty_hist: AxisHistogram<DiffLineKind> = AxisHistogram::empty();
let singular_with_empty = solo_added.clone().merge(&empty_hist);
assert_eq!(
singular_with_empty.has_singular_support(),
solo_added.has_singular_support(),
);
let mixed_with_empty = mixed_lhs.clone().merge(&empty_hist);
assert_eq!(
mixed_with_empty.has_singular_support(),
mixed_lhs.has_singular_support(),
);
}
fn assert_has_partial_cover_empty_is_false<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let hist = AxisHistogram::<A>::empty();
assert!(
!hist.has_partial_cover(),
"empty histogram has_partial_cover must be false on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_has_partial_cover_singleton_iff_cardinality_at_least_two<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
for observed in axis_iter::<A>() {
let hist: AxisHistogram<A> = std::iter::once(observed).collect();
assert_eq!(
hist.has_partial_cover(),
axis_cardinality::<A>() >= 2,
"singleton has_partial_cover must equal (axis_cardinality >= 2) \
for observed cell {observed:?} on axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_has_partial_cover_axis_cover_is_false<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let hist: AxisHistogram<A> = axis_iter::<A>().collect();
assert!(
!hist.has_partial_cover(),
"uniform axis-cover has_partial_cover must be false on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_coverage_trichotomy_partitions_every_canonical_shape<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let empty = AxisHistogram::<A>::empty();
let singleton: AxisHistogram<A> = axis_iter::<A>()
.next()
.map(|v| std::iter::once(v).collect())
.expect("every ClosedAxis implementor has at least one variant");
let axis_cover: AxisHistogram<A> = axis_iter::<A>().collect();
for hist in [&empty, &singleton, &axis_cover] {
let e = u8::from(hist.is_empty());
let p = u8::from(hist.has_partial_cover());
let f = u8::from(hist.is_full_cover());
assert_eq!(
e + p + f,
1,
"(is_empty, has_partial_cover, is_full_cover) must be a strict \
partition (exactly one corner fires) on axis {}",
std::any::type_name::<A>(),
);
}
}
#[test]
fn axis_histogram_has_partial_cover_empty_is_false_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_has_partial_cover_empty_is_false::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_has_partial_cover_singleton_iff_cardinality_at_least_two_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_has_partial_cover_singleton_iff_cardinality_at_least_two::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_has_partial_cover_axis_cover_is_false_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_has_partial_cover_axis_cover_is_false::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_coverage_trichotomy_partitions_every_histogram_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_coverage_trichotomy_partitions_every_canonical_shape::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_has_partial_cover_equals_not_empty_and_not_full_cover() {
let inputs: [&[DiffLineKind]; 6] = [
&[],
&[DiffLineKind::Added],
&[DiffLineKind::Added, DiffLineKind::Added],
&[
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
],
&[DiffLineKind::Added, DiffLineKind::Removed],
&[
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Context,
],
];
for input in inputs {
let hist: AxisHistogram<DiffLineKind> = input.iter().copied().collect();
assert_eq!(
hist.has_partial_cover(),
!hist.is_empty() && !hist.is_full_cover(),
"has_partial_cover must equal (!is_empty && !is_full_cover) \
on input of length {}",
input.len(),
);
}
}
#[test]
fn axis_histogram_has_partial_cover_equals_distinct_cells_strict_interval() {
let cardinality = axis_cardinality::<DiffLineKind>();
let inputs: [&[DiffLineKind]; 6] = [
&[],
&[DiffLineKind::Added],
&[DiffLineKind::Added, DiffLineKind::Added],
&[
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
],
&[DiffLineKind::Added, DiffLineKind::Removed],
&[
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Context,
],
];
for input in inputs {
let hist: AxisHistogram<DiffLineKind> = input.iter().copied().collect();
let distinct = hist.distinct_cells();
assert_eq!(
hist.has_partial_cover(),
0 < distinct && distinct < cardinality,
"has_partial_cover must equal (0 < distinct_cells < axis_cardinality) \
on input of length {}; distinct={}, cardinality={}",
input.len(),
distinct,
cardinality,
);
}
}
#[test]
fn axis_histogram_coverage_trichotomy_partition_on_diff_line_kind() {
let empty: AxisHistogram<DiffLineKind> = AxisHistogram::empty();
assert_eq!(
(
empty.is_empty(),
empty.has_partial_cover(),
empty.is_full_cover(),
),
(true, false, false),
);
let singleton: AxisHistogram<DiffLineKind> = std::iter::once(DiffLineKind::Added).collect();
assert_eq!(
(
singleton.is_empty(),
singleton.has_partial_cover(),
singleton.is_full_cover(),
),
(false, true, false),
);
let partial_multi: AxisHistogram<DiffLineKind> =
[DiffLineKind::Added, DiffLineKind::Removed]
.into_iter()
.collect();
assert_eq!(
(
partial_multi.is_empty(),
partial_multi.has_partial_cover(),
partial_multi.is_full_cover(),
),
(false, true, false),
);
let axis_cover: AxisHistogram<DiffLineKind> = axis_iter::<DiffLineKind>().collect();
assert_eq!(
(
axis_cover.is_empty(),
axis_cover.has_partial_cover(),
axis_cover.is_full_cover(),
),
(false, false, true),
);
for hist in [&empty, &singleton, &partial_multi, &axis_cover] {
let e = u8::from(hist.is_empty());
let p = u8::from(hist.has_partial_cover());
let f = u8::from(hist.is_full_cover());
assert_eq!(
e + p + f,
1,
"(is_empty, has_partial_cover, is_full_cover) strict-partition \
invariant must hold (exactly one corner fires)",
);
}
}
#[test]
fn axis_histogram_has_singular_support_implies_has_partial_cover_on_cardinality_at_least_two() {
let solo: AxisHistogram<DiffLineKind> = std::iter::once(DiffLineKind::Added).collect();
assert!(solo.has_singular_support());
assert!(solo.has_partial_cover());
let many_solo: AxisHistogram<DiffLineKind> = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Added,
]
.into_iter()
.collect();
assert!(many_solo.has_singular_support());
assert!(many_solo.has_partial_cover());
let partial_multi: AxisHistogram<DiffLineKind> =
[DiffLineKind::Added, DiffLineKind::Removed]
.into_iter()
.collect();
assert!(!partial_multi.has_singular_support());
assert!(partial_multi.has_partial_cover());
}
#[test]
fn axis_histogram_has_partial_cover_after_merge_is_non_monotone() {
let only_added: AxisHistogram<DiffLineKind> =
std::iter::once(DiffLineKind::Added).collect();
let added_and_removed: AxisHistogram<DiffLineKind> =
[DiffLineKind::Added, DiffLineKind::Removed]
.into_iter()
.collect();
assert!(only_added.has_partial_cover());
assert!(added_and_removed.has_partial_cover());
let merged_partial = only_added.clone().merge(&added_and_removed);
assert!(merged_partial.has_partial_cover());
let removed_added: AxisHistogram<DiffLineKind> =
[DiffLineKind::Removed, DiffLineKind::Added]
.into_iter()
.collect();
let only_context: AxisHistogram<DiffLineKind> =
std::iter::once(DiffLineKind::Context).collect();
assert!(removed_added.has_partial_cover());
assert!(only_context.has_partial_cover());
let merged_full = removed_added.clone().merge(&only_context);
assert!(!merged_full.has_partial_cover());
assert!(merged_full.is_full_cover());
let empty_hist: AxisHistogram<DiffLineKind> = AxisHistogram::empty();
let merged_empty = empty_hist.clone().merge(&empty_hist);
assert!(!merged_empty.has_partial_cover());
assert!(merged_empty.is_empty());
let partial_with_empty = only_added.clone().merge(&empty_hist);
assert_eq!(
partial_with_empty.has_partial_cover(),
only_added.has_partial_cover(),
);
let empty_with_empty = empty_hist.clone().merge(&empty_hist);
assert_eq!(
empty_with_empty.has_partial_cover(),
empty_hist.has_partial_cover(),
);
}
fn assert_has_singular_gap_empty_iff_cardinality_is_one<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let hist = AxisHistogram::<A>::empty();
assert_eq!(
hist.has_singular_gap(),
axis_cardinality::<A>() == 1,
"empty histogram has_singular_gap must equal (axis_cardinality == 1) \
on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_has_singular_gap_singleton_iff_cardinality_is_two<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
for observed in axis_iter::<A>() {
let hist: AxisHistogram<A> = std::iter::once(observed).collect();
assert_eq!(
hist.has_singular_gap(),
axis_cardinality::<A>() == 2,
"singleton has_singular_gap must equal (axis_cardinality == 2) \
for observed cell {observed:?} on axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_has_singular_gap_axis_cover_is_false<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let hist: AxisHistogram<A> = axis_iter::<A>().collect();
assert!(
!hist.has_singular_gap(),
"uniform axis-cover has_singular_gap must be false on axis {}",
std::any::type_name::<A>(),
);
}
#[test]
fn axis_histogram_has_singular_gap_empty_iff_cardinality_is_one_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_has_singular_gap_empty_iff_cardinality_is_one::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_has_singular_gap_singleton_iff_cardinality_is_two_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_has_singular_gap_singleton_iff_cardinality_is_two::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_has_singular_gap_axis_cover_is_false_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_has_singular_gap_axis_cover_is_false::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_has_singular_gap_equals_unobserved_cells_is_one() {
let inputs: [&[DiffLineKind]; 6] = [
&[],
&[DiffLineKind::Added],
&[DiffLineKind::Added, DiffLineKind::Added],
&[
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
],
&[DiffLineKind::Added, DiffLineKind::Removed],
&[
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Context,
],
];
for input in inputs {
let hist: AxisHistogram<DiffLineKind> = input.iter().copied().collect();
assert_eq!(
hist.has_singular_gap(),
hist.unobserved_cells() == 1,
"has_singular_gap must equal (unobserved_cells == 1) \
on input of length {}",
input.len(),
);
}
}
#[test]
fn axis_histogram_has_singular_gap_equals_distinct_cells_is_one_less_than_cardinality() {
let cardinality = axis_cardinality::<DiffLineKind>();
let inputs: [&[DiffLineKind]; 6] = [
&[],
&[DiffLineKind::Added],
&[DiffLineKind::Added, DiffLineKind::Added],
&[
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
],
&[DiffLineKind::Added, DiffLineKind::Removed],
&[
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Context,
],
];
for input in inputs {
let hist: AxisHistogram<DiffLineKind> = input.iter().copied().collect();
let distinct = hist.distinct_cells();
assert_eq!(
hist.has_singular_gap(),
distinct + 1 == cardinality,
"has_singular_gap must equal (distinct_cells + 1 == axis_cardinality) \
on input of length {}; distinct={}, cardinality={}",
input.len(),
distinct,
cardinality,
);
}
}
#[test]
fn axis_histogram_has_singular_gap_and_has_singular_support_disjoint_on_cardinality_three() {
let empty: AxisHistogram<DiffLineKind> = AxisHistogram::empty();
let singleton: AxisHistogram<DiffLineKind> = std::iter::once(DiffLineKind::Added).collect();
let two_cell: AxisHistogram<DiffLineKind> = [DiffLineKind::Added, DiffLineKind::Removed]
.into_iter()
.collect();
let axis_cover: AxisHistogram<DiffLineKind> = axis_iter::<DiffLineKind>().collect();
for hist in [&empty, &singleton, &two_cell, &axis_cover] {
assert!(
!(hist.has_singular_support() && hist.has_singular_gap()),
"has_singular_support and has_singular_gap must be pointwise \
disjoint on cardinality-3 axes (distinct_cells={})",
hist.distinct_cells(),
);
}
assert!(!two_cell.has_singular_support());
assert!(two_cell.has_singular_gap());
assert!(singleton.has_singular_support());
assert!(!singleton.has_singular_gap());
}
#[test]
fn axis_histogram_has_singular_gap_equals_has_singular_support_on_cardinality_two() {
let empty: AxisHistogram<PartitionFace> = AxisHistogram::empty();
let realizable_only: AxisHistogram<PartitionFace> =
std::iter::once(PartitionFace::Realizable).collect();
let unrealizable_only: AxisHistogram<PartitionFace> =
std::iter::once(PartitionFace::Unrealizable).collect();
let axis_cover: AxisHistogram<PartitionFace> = axis_iter::<PartitionFace>().collect();
for hist in [&empty, &realizable_only, &unrealizable_only, &axis_cover] {
assert_eq!(
hist.has_singular_gap(),
hist.has_singular_support(),
"has_singular_gap and has_singular_support must coincide \
pointwise on cardinality-2 axes (distinct_cells={})",
hist.distinct_cells(),
);
}
assert!(realizable_only.has_singular_support());
assert!(realizable_only.has_singular_gap());
assert!(unrealizable_only.has_singular_support());
assert!(unrealizable_only.has_singular_gap());
}
#[test]
fn axis_histogram_has_singular_gap_implies_has_partial_cover_on_cardinality_at_least_two() {
let two_cell: AxisHistogram<DiffLineKind> = [DiffLineKind::Added, DiffLineKind::Removed]
.into_iter()
.collect();
assert!(two_cell.has_singular_gap());
assert!(two_cell.has_partial_cover());
let realizable_only: AxisHistogram<PartitionFace> =
std::iter::once(PartitionFace::Realizable).collect();
assert!(realizable_only.has_singular_gap());
assert!(realizable_only.has_partial_cover());
let singleton: AxisHistogram<DiffLineKind> = std::iter::once(DiffLineKind::Added).collect();
assert!(singleton.has_partial_cover());
assert!(!singleton.has_singular_gap());
}
#[test]
fn axis_histogram_has_singular_gap_after_merge_is_non_monotone() {
let missing_context_a: AxisHistogram<DiffLineKind> =
[DiffLineKind::Removed, DiffLineKind::Added]
.into_iter()
.collect();
let missing_context_b: AxisHistogram<DiffLineKind> =
[DiffLineKind::Added, DiffLineKind::Removed]
.into_iter()
.collect();
assert!(missing_context_a.has_singular_gap());
assert!(missing_context_b.has_singular_gap());
let merged_coinciding = missing_context_a.clone().merge(&missing_context_b);
assert!(merged_coinciding.has_singular_gap());
let missing_removed: AxisHistogram<DiffLineKind> =
[DiffLineKind::Added, DiffLineKind::Context]
.into_iter()
.collect();
assert!(missing_context_a.has_singular_gap());
assert!(missing_removed.has_singular_gap());
let merged_differing = missing_context_a.clone().merge(&missing_removed);
assert!(!merged_differing.has_singular_gap());
assert!(merged_differing.is_full_cover());
let empty_hist: AxisHistogram<DiffLineKind> = AxisHistogram::empty();
let merged_empty = empty_hist.clone().merge(&empty_hist);
assert!(!merged_empty.has_singular_gap());
assert!(merged_empty.is_empty());
let gap_with_empty = missing_context_a.clone().merge(&empty_hist);
assert_eq!(
gap_with_empty.has_singular_gap(),
missing_context_a.has_singular_gap(),
);
let empty_with_empty = empty_hist.clone().merge(&empty_hist);
assert_eq!(
empty_with_empty.has_singular_gap(),
empty_hist.has_singular_gap(),
);
}
fn assert_has_strict_partial_cover_empty_is_false<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let hist = AxisHistogram::<A>::empty();
assert!(
!hist.has_strict_partial_cover(),
"empty histogram has_strict_partial_cover must be false on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_has_strict_partial_cover_singleton_is_false<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
for observed in axis_iter::<A>() {
let hist: AxisHistogram<A> = std::iter::once(observed).collect();
assert!(
!hist.has_strict_partial_cover(),
"singleton has_strict_partial_cover must be false \
for observed cell {observed:?} on axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_has_strict_partial_cover_axis_cover_is_false<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let hist: AxisHistogram<A> = axis_iter::<A>().collect();
assert!(
!hist.has_strict_partial_cover(),
"uniform axis-cover has_strict_partial_cover must be false on axis {}",
std::any::type_name::<A>(),
);
}
#[test]
fn axis_histogram_has_strict_partial_cover_empty_is_false_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_has_strict_partial_cover_empty_is_false::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_has_strict_partial_cover_singleton_is_false_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_has_strict_partial_cover_singleton_is_false::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_has_strict_partial_cover_axis_cover_is_false_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_has_strict_partial_cover_axis_cover_is_false::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_has_strict_partial_cover_equals_has_partial_cover_and_not_singular_boundaries()
{
let inputs: [&[Format]; 5] = [
&[],
&[Format::Yaml],
&[Format::Yaml, Format::Toml],
&[Format::Yaml, Format::Toml, Format::Lisp],
&[Format::Yaml, Format::Toml, Format::Lisp, Format::Nix],
];
for input in inputs {
let hist: AxisHistogram<Format> = input.iter().copied().collect();
assert_eq!(
hist.has_strict_partial_cover(),
hist.has_partial_cover()
&& !hist.has_singular_support()
&& !hist.has_singular_gap(),
"has_strict_partial_cover must equal \
(has_partial_cover && !has_singular_support && !has_singular_gap) \
on input of length {}",
input.len(),
);
}
}
#[test]
fn axis_histogram_has_strict_partial_cover_equals_distinct_cells_strict_interior() {
let cardinality = axis_cardinality::<ShikumiErrorKind>();
let inputs: [&[ShikumiErrorKind]; 7] = [
&[],
&[ShikumiErrorKind::NotFound],
&[ShikumiErrorKind::NotFound, ShikumiErrorKind::Parse],
&[
ShikumiErrorKind::NotFound,
ShikumiErrorKind::Parse,
ShikumiErrorKind::Watch,
],
&[
ShikumiErrorKind::NotFound,
ShikumiErrorKind::Parse,
ShikumiErrorKind::Watch,
ShikumiErrorKind::Io,
],
&[
ShikumiErrorKind::NotFound,
ShikumiErrorKind::Parse,
ShikumiErrorKind::Watch,
ShikumiErrorKind::Io,
ShikumiErrorKind::Figment,
],
&[
ShikumiErrorKind::NotFound,
ShikumiErrorKind::Parse,
ShikumiErrorKind::Watch,
ShikumiErrorKind::Io,
ShikumiErrorKind::Figment,
ShikumiErrorKind::Extract,
],
];
for input in inputs {
let hist: AxisHistogram<ShikumiErrorKind> = input.iter().copied().collect();
let distinct = hist.distinct_cells();
assert_eq!(
hist.has_strict_partial_cover(),
1 < distinct && distinct + 1 < cardinality,
"has_strict_partial_cover must equal \
(1 < distinct_cells && distinct_cells + 1 < axis_cardinality) \
on input of length {}; distinct={}, cardinality={}",
input.len(),
distinct,
cardinality,
);
}
}
#[test]
fn axis_histogram_has_strict_partial_cover_vacuous_on_cardinality_three() {
let empty: AxisHistogram<DiffLineKind> = AxisHistogram::empty();
let singleton: AxisHistogram<DiffLineKind> = std::iter::once(DiffLineKind::Added).collect();
let two_cell: AxisHistogram<DiffLineKind> = [DiffLineKind::Added, DiffLineKind::Removed]
.into_iter()
.collect();
let axis_cover: AxisHistogram<DiffLineKind> = axis_iter::<DiffLineKind>().collect();
for hist in [&empty, &singleton, &two_cell, &axis_cover] {
assert!(
!hist.has_strict_partial_cover(),
"has_strict_partial_cover must be false uniformly on cardinality-3 \
axes (distinct_cells={})",
hist.distinct_cells(),
);
}
}
#[test]
fn axis_histogram_has_strict_partial_cover_vacuous_on_cardinality_two() {
let empty: AxisHistogram<PartitionFace> = AxisHistogram::empty();
let realizable_only: AxisHistogram<PartitionFace> =
std::iter::once(PartitionFace::Realizable).collect();
let unrealizable_only: AxisHistogram<PartitionFace> =
std::iter::once(PartitionFace::Unrealizable).collect();
let axis_cover: AxisHistogram<PartitionFace> = axis_iter::<PartitionFace>().collect();
for hist in [&empty, &realizable_only, &unrealizable_only, &axis_cover] {
assert!(
!hist.has_strict_partial_cover(),
"has_strict_partial_cover must be false uniformly on cardinality-2 \
axes (distinct_cells={})",
hist.distinct_cells(),
);
}
}
#[test]
fn axis_histogram_has_strict_partial_cover_implies_has_partial_cover() {
let two_cell: AxisHistogram<Format> = [Format::Yaml, Format::Toml].into_iter().collect();
assert!(two_cell.has_strict_partial_cover());
assert!(two_cell.has_partial_cover());
let singleton: AxisHistogram<Format> = std::iter::once(Format::Yaml).collect();
assert!(!singleton.has_strict_partial_cover());
assert!(singleton.has_partial_cover());
let three_cell: AxisHistogram<Format> = [Format::Yaml, Format::Toml, Format::Lisp]
.into_iter()
.collect();
assert!(!three_cell.has_strict_partial_cover());
assert!(three_cell.has_partial_cover());
}
#[test]
fn axis_histogram_has_strict_partial_cover_disjoint_from_singular_boundaries() {
let inputs: [&[ShikumiErrorKind]; 7] = [
&[],
&[ShikumiErrorKind::NotFound],
&[ShikumiErrorKind::NotFound, ShikumiErrorKind::Parse],
&[
ShikumiErrorKind::NotFound,
ShikumiErrorKind::Parse,
ShikumiErrorKind::Watch,
],
&[
ShikumiErrorKind::NotFound,
ShikumiErrorKind::Parse,
ShikumiErrorKind::Watch,
ShikumiErrorKind::Io,
],
&[
ShikumiErrorKind::NotFound,
ShikumiErrorKind::Parse,
ShikumiErrorKind::Watch,
ShikumiErrorKind::Io,
ShikumiErrorKind::Figment,
],
&[
ShikumiErrorKind::NotFound,
ShikumiErrorKind::Parse,
ShikumiErrorKind::Watch,
ShikumiErrorKind::Io,
ShikumiErrorKind::Figment,
ShikumiErrorKind::Extract,
],
];
for input in inputs {
let hist: AxisHistogram<ShikumiErrorKind> = input.iter().copied().collect();
let support = u8::from(hist.has_singular_support());
let interior = u8::from(hist.has_strict_partial_cover());
let gap = u8::from(hist.has_singular_gap());
assert!(
support + interior + gap <= 1,
"(has_singular_support, has_strict_partial_cover, has_singular_gap) \
must be pairwise disjoint on cardinality-≥ 3 axes \
(support+interior+gap = {} on input of length {})",
support + interior + gap,
input.len(),
);
}
}
#[test]
fn axis_histogram_support_cardinality_five_corner_partition_on_shikumi_error_kind() {
let shapes: [&[ShikumiErrorKind]; 7] = [
&[],
&[ShikumiErrorKind::NotFound],
&[ShikumiErrorKind::NotFound, ShikumiErrorKind::Parse],
&[
ShikumiErrorKind::NotFound,
ShikumiErrorKind::Parse,
ShikumiErrorKind::Watch,
],
&[
ShikumiErrorKind::NotFound,
ShikumiErrorKind::Parse,
ShikumiErrorKind::Watch,
ShikumiErrorKind::Io,
],
&[
ShikumiErrorKind::NotFound,
ShikumiErrorKind::Parse,
ShikumiErrorKind::Watch,
ShikumiErrorKind::Io,
ShikumiErrorKind::Figment,
],
&[
ShikumiErrorKind::NotFound,
ShikumiErrorKind::Parse,
ShikumiErrorKind::Watch,
ShikumiErrorKind::Io,
ShikumiErrorKind::Figment,
ShikumiErrorKind::Extract,
],
];
for shape in shapes {
let hist: AxisHistogram<ShikumiErrorKind> = shape.iter().copied().collect();
let empty = u8::from(hist.is_empty());
let support = u8::from(hist.has_singular_support());
let interior = u8::from(hist.has_strict_partial_cover());
let gap = u8::from(hist.has_singular_gap());
let full = u8::from(hist.is_full_cover());
assert_eq!(
empty + support + interior + gap + full,
1,
"(is_empty, has_singular_support, has_strict_partial_cover, \
has_singular_gap, is_full_cover) must be a strict partition \
(exactly one corner fires) on shape of length {}; \
(empty,support,interior,gap,full) = ({},{},{},{},{})",
shape.len(),
empty,
support,
interior,
gap,
full,
);
}
}
#[test]
fn axis_histogram_has_strict_partial_cover_after_merge_is_non_monotone() {
let two_cell: AxisHistogram<ShikumiErrorKind> =
[ShikumiErrorKind::NotFound, ShikumiErrorKind::Parse]
.into_iter()
.collect();
let two_cell_overlap: AxisHistogram<ShikumiErrorKind> =
[ShikumiErrorKind::Parse, ShikumiErrorKind::Watch]
.into_iter()
.collect();
assert!(two_cell.has_strict_partial_cover());
assert!(two_cell_overlap.has_strict_partial_cover());
let merged_strict = two_cell.clone().merge(&two_cell_overlap);
assert!(merged_strict.has_strict_partial_cover());
let four_cell: AxisHistogram<ShikumiErrorKind> = [
ShikumiErrorKind::NotFound,
ShikumiErrorKind::Parse,
ShikumiErrorKind::Watch,
ShikumiErrorKind::Io,
]
.into_iter()
.collect();
let one_cell: AxisHistogram<ShikumiErrorKind> =
std::iter::once(ShikumiErrorKind::Figment).collect();
assert!(four_cell.has_strict_partial_cover());
let merged_gap = four_cell.clone().merge(&one_cell);
assert!(!merged_gap.has_strict_partial_cover());
assert!(merged_gap.has_singular_gap());
let three_cell: AxisHistogram<ShikumiErrorKind> = [
ShikumiErrorKind::NotFound,
ShikumiErrorKind::Parse,
ShikumiErrorKind::Watch,
]
.into_iter()
.collect();
let other_three_cell: AxisHistogram<ShikumiErrorKind> = [
ShikumiErrorKind::Io,
ShikumiErrorKind::Figment,
ShikumiErrorKind::Extract,
]
.into_iter()
.collect();
assert!(three_cell.has_strict_partial_cover());
assert!(other_three_cell.has_strict_partial_cover());
let merged_full = three_cell.clone().merge(&other_three_cell);
assert!(!merged_full.has_strict_partial_cover());
assert!(merged_full.is_full_cover());
let empty_hist: AxisHistogram<ShikumiErrorKind> = AxisHistogram::empty();
let strict_with_empty = two_cell.clone().merge(&empty_hist);
assert_eq!(
strict_with_empty.has_strict_partial_cover(),
two_cell.has_strict_partial_cover(),
);
let empty_with_empty = empty_hist.clone().merge(&empty_hist);
assert_eq!(
empty_with_empty.has_strict_partial_cover(),
empty_hist.has_strict_partial_cover(),
);
}
fn hash_of<T: std::hash::Hash>(value: &T) -> u64 {
use std::hash::{DefaultHasher, Hasher};
let mut hasher = DefaultHasher::new();
value.hash(&mut hasher);
hasher.finish()
}
fn assert_hash_equal_implies_same_hash<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let lhs: AxisHistogram<A> = axis_iter::<A>().collect();
let rhs: AxisHistogram<A> = axis_iter::<A>().collect();
assert_eq!(
lhs,
rhs,
"two axis-cover histograms must compare equal on axis {}",
std::any::type_name::<A>(),
);
assert_eq!(
hash_of(&lhs),
hash_of(&rhs),
"Eq histograms must hash equally on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_hash_empty_is_consistent<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let via_empty = AxisHistogram::<A>::empty();
let via_empty_again = AxisHistogram::<A>::empty();
let via_default = AxisHistogram::<A>::default();
assert_eq!(
hash_of(&via_empty),
hash_of(&via_empty_again),
"AxisHistogram::empty must hash consistently on axis {}",
std::any::type_name::<A>(),
);
assert_eq!(
hash_of(&via_empty),
hash_of(&via_default),
"AxisHistogram::default must hash like ::empty on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_hash_clone_preserves_hash<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let original: AxisHistogram<A> = axis_iter::<A>().collect();
let cloned = original.clone();
assert_eq!(
hash_of(&original),
hash_of(&cloned),
"clone must preserve hash on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_hash_hashset_roundtrip<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
use std::collections::HashSet;
let inserted: AxisHistogram<A> = axis_iter::<A>().collect();
let probe: AxisHistogram<A> = axis_iter::<A>().collect();
let mut set: HashSet<AxisHistogram<A>> = HashSet::new();
set.insert(inserted);
assert!(
set.contains(&probe),
"HashSet must recognize an equal histogram by hash on axis {}",
std::any::type_name::<A>(),
);
let len_before = set.len();
set.insert(probe);
assert_eq!(
set.len(),
len_before,
"HashSet must dedup equal histograms on axis {}",
std::any::type_name::<A>(),
);
}
#[test]
fn axis_histogram_equal_implies_same_hash_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_hash_equal_implies_same_hash::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_empty_hashes_consistently_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_hash_empty_is_consistent::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_clone_preserves_hash_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_hash_clone_preserves_hash::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_hashset_roundtrip_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_hash_hashset_roundtrip::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_hashmap_keys_dedup_observation_mixes_for_diff_line_kind() {
use std::collections::HashMap;
let host_a: AxisHistogram<DiffLineKind> = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
]
.into_iter()
.collect();
let host_b: AxisHistogram<DiffLineKind> = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
]
.into_iter()
.collect();
let host_c: AxisHistogram<DiffLineKind> = std::iter::once(DiffLineKind::Context).collect();
assert_eq!(host_a, host_b);
assert_ne!(host_a, host_c);
let mut tally: HashMap<AxisHistogram<DiffLineKind>, usize> = HashMap::new();
for hist in [host_a.clone(), host_b.clone(), host_c.clone()] {
*tally.entry(hist).or_insert(0) += 1;
}
assert_eq!(
tally.len(),
2,
"two distinct observation mixes — two buckets"
);
assert_eq!(
tally.get(&host_a).copied(),
Some(2),
"host_a and host_b dedup to one key bucketing both hosts",
);
assert_eq!(
tally.get(&host_c).copied(),
Some(1),
"host_c lands in its own bucket",
);
}
fn assert_clear_is_empty_after<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let mut hist: AxisHistogram<A> = axis_iter::<A>().collect();
assert!(
!hist.is_empty(),
"axis-cover histogram must be non-empty before clear on axis {}",
std::any::type_name::<A>(),
);
hist.clear();
assert!(
hist.is_empty(),
"clear must reach is_empty on axis {}",
std::any::type_name::<A>(),
);
assert_eq!(
hist.total(),
0,
"clear must zero total on axis {}",
std::any::type_name::<A>(),
);
assert_eq!(
hist.distinct_cells(),
0,
"clear must zero distinct_cells on axis {}",
std::any::type_name::<A>(),
);
assert_eq!(
hist.unobserved_cells(),
axis_cardinality::<A>(),
"clear must lift unobserved_cells to axis_cardinality on axis {}",
std::any::type_name::<A>(),
);
for cell in axis_iter::<A>() {
assert_eq!(
hist[cell],
0,
"clear must zero cell {cell:?} on axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_clear_equals_default<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let mut via_clear: AxisHistogram<A> = axis_iter::<A>().collect();
via_clear.clear();
assert_eq!(
via_clear,
AxisHistogram::<A>::default(),
"clear must equal Default on axis {}",
std::any::type_name::<A>(),
);
assert_eq!(
via_clear,
AxisHistogram::<A>::empty(),
"clear must equal empty() on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_clear_is_idempotent<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let mut hist: AxisHistogram<A> = axis_iter::<A>().collect();
hist.clear();
let after_one = hist.clone();
hist.clear();
assert_eq!(
hist,
after_one,
"clear must be idempotent on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_clear_on_empty_is_identity<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let mut hist = AxisHistogram::<A>::empty();
let before = hist.clone();
hist.clear();
assert_eq!(
hist,
before,
"clear on empty must be identity on axis {}",
std::any::type_name::<A>(),
);
}
#[test]
fn axis_histogram_clear_is_empty_after_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_clear_is_empty_after::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_clear_equals_default_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_clear_equals_default::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_clear_is_idempotent_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_clear_is_idempotent::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_clear_on_empty_is_identity_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_clear_on_empty_is_identity::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_clear_preserves_backing_capacity_for_diff_line_kind() {
let mut window: AxisHistogram<DiffLineKind> = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Context,
]
.into_iter()
.collect();
assert_eq!(window[DiffLineKind::Added], 2);
assert_eq!(window[DiffLineKind::Removed], 1);
assert_eq!(window[DiffLineKind::Context], 1);
assert_eq!(window.total(), 4);
window.clear();
assert_eq!(window[DiffLineKind::Added], 0);
assert_eq!(window[DiffLineKind::Removed], 0);
assert_eq!(window[DiffLineKind::Context], 0);
assert_eq!(window.total(), 0);
assert!(window.is_empty());
assert_eq!(window, AxisHistogram::<DiffLineKind>::empty());
window.observe(DiffLineKind::Removed);
window.observe(DiffLineKind::Removed);
assert_eq!(window[DiffLineKind::Removed], 2);
assert_eq!(window[DiffLineKind::Added], 0);
assert_eq!(window.total(), 2);
let mut fresh = AxisHistogram::<DiffLineKind>::empty();
fresh.observe(DiffLineKind::Removed);
fresh.observe(DiffLineKind::Removed);
assert_eq!(
window.iter().count(),
fresh.iter().count(),
"clear preserves iterator length over the closed axis",
);
assert_eq!(
window, fresh,
"post-clear absorb reaches the same value as a fresh observe sequence",
);
}
fn assert_display_emits_axis_cardinality_pairs<A>()
where
A: ClosedAxisLabel + std::fmt::Debug,
{
let hist: AxisHistogram<A> = axis_iter::<A>().collect();
let s = format!("{hist}");
let cardinality = axis_cardinality::<A>();
let expected_separators = cardinality.saturating_sub(1);
let observed_separators = s.matches(", ").count();
assert_eq!(
observed_separators,
expected_separators,
"axis {} display must carry exactly axis_cardinality - 1 separators (got {observed_separators} in {s:?})",
std::any::type_name::<A>(),
);
assert_eq!(
s.matches('=').count(),
cardinality,
"axis {} display must carry exactly axis_cardinality '=' separators (one per cell)",
std::any::type_name::<A>(),
);
}
fn assert_display_labels_round_trip_through_axis_from_label<A>()
where
A: ClosedAxisLabel + std::fmt::Debug,
{
let mut hist = AxisHistogram::<A>::empty();
for (i, cell) in axis_iter::<A>().enumerate() {
for _ in 0..=i {
hist.observe(cell);
}
}
let s = format!("{hist}");
let parsed: Vec<(A, usize)> = s
.split(", ")
.map(|pair| {
let (label, count) = pair.split_once('=').unwrap_or_else(|| {
panic!(
"axis {} display pair {pair:?} missing '=' separator",
std::any::type_name::<A>(),
)
});
let cell = axis_from_label::<A>(label).unwrap_or_else(|| {
panic!(
"axis {} display label {label:?} must parse through axis_from_label",
std::any::type_name::<A>(),
)
});
let count: usize = count.parse().unwrap_or_else(|e| {
panic!(
"axis {} display count {count:?} must parse as usize: {e}",
std::any::type_name::<A>(),
)
});
(cell, count)
})
.collect();
let original: Vec<(A, usize)> = hist.iter().collect();
assert_eq!(
parsed,
original,
"axis {} display must round-trip through axis_from_label + usize::parse",
std::any::type_name::<A>(),
);
}
fn assert_display_empty_emits_zero_for_every_cell<A>()
where
A: ClosedAxisLabel + std::fmt::Debug,
{
let hist = AxisHistogram::<A>::empty();
let s = format!("{hist}");
for cell in axis_iter::<A>() {
let expected = format!("{}=0", cell.as_str());
assert!(
s.contains(&expected),
"axis {} empty display must contain {expected:?} (got {s:?})",
std::any::type_name::<A>(),
);
}
assert_eq!(
s.matches("=0").count(),
axis_cardinality::<A>(),
"axis {} empty display must carry one =0 per cell",
std::any::type_name::<A>(),
);
}
fn assert_display_labels_appear_in_declaration_order<A>()
where
A: ClosedAxisLabel + std::fmt::Debug,
{
let hist: AxisHistogram<A> = axis_iter::<A>().collect();
let s = format!("{hist}");
let mut prev_end = 0usize;
for cell in axis_iter::<A>() {
let label = cell.as_str();
let offset = s[prev_end..].find(label).unwrap_or_else(|| {
panic!(
"axis {} display must contain label {label:?} after offset {prev_end}",
std::any::type_name::<A>(),
)
});
prev_end += offset + label.len();
}
}
#[test]
fn axis_histogram_display_emits_axis_cardinality_pairs_for_every_closed_axis_label_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_display_emits_axis_cardinality_pairs::<$ty>();
};
}
for_each_closed_axis_label_implementor!(check);
}
#[test]
fn axis_histogram_display_labels_round_trip_through_axis_from_label_for_every_closed_axis_label_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_display_labels_round_trip_through_axis_from_label::<$ty>();
};
}
for_each_closed_axis_label_implementor!(check);
}
#[test]
fn axis_histogram_display_empty_emits_zero_for_every_cell_for_every_closed_axis_label_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_display_empty_emits_zero_for_every_cell::<$ty>();
};
}
for_each_closed_axis_label_implementor!(check);
}
#[test]
fn axis_histogram_display_labels_appear_in_declaration_order_for_every_closed_axis_label_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_display_labels_appear_in_declaration_order::<$ty>();
};
}
for_each_closed_axis_label_implementor!(check);
}
#[test]
fn axis_histogram_display_for_diff_line_kind() {
let hist: AxisHistogram<DiffLineKind> = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Context,
DiffLineKind::Context,
DiffLineKind::Context,
]
.into_iter()
.collect();
assert_eq!(format!("{hist}"), "removed=1, added=2, context=3");
let empty = AxisHistogram::<DiffLineKind>::empty();
assert_eq!(format!("{empty}"), "removed=0, added=0, context=0");
let singleton = AxisHistogram::<DiffLineKind>::from(DiffLineKind::Added);
assert_eq!(format!("{singleton}"), "removed=0, added=1, context=0");
}
#[test]
fn axis_histogram_display_total_matches_inherent_for_diff_line_kind() {
let hist: AxisHistogram<DiffLineKind> = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Context,
DiffLineKind::Context,
DiffLineKind::Context,
]
.into_iter()
.collect();
let s = format!("{hist}");
let parsed_total: usize = s
.split(", ")
.map(|pair| {
let (_, c) = pair.split_once('=').expect("pair must contain '='");
c.parse::<usize>().expect("count must parse")
})
.sum();
assert_eq!(
parsed_total,
hist.total(),
"display must preserve total observation count",
);
}
fn assert_display_from_str_round_trip<A>()
where
A: ClosedAxisLabel + std::fmt::Debug,
{
let mut hist = AxisHistogram::<A>::empty();
for (i, cell) in axis_iter::<A>().enumerate() {
for _ in 0..=i {
hist.observe(cell);
}
}
let s = format!("{hist}");
let parsed: AxisHistogram<A> = s.parse().unwrap_or_else(|e| {
panic!(
"axis {} display must parse back through FromStr: {e}",
std::any::type_name::<A>(),
)
});
assert_eq!(
parsed,
hist,
"axis {} (Display, FromStr) round-trip must be identity",
std::any::type_name::<A>(),
);
}
fn assert_from_str_empty_is_empty_histogram<A>()
where
A: ClosedAxisLabel + std::fmt::Debug,
{
let parsed: AxisHistogram<A> = "".parse().unwrap_or_else(|e| {
panic!(
"axis {} empty input must parse to empty histogram: {e}",
std::any::type_name::<A>(),
)
});
assert_eq!(
parsed,
AxisHistogram::<A>::empty(),
"axis {} empty input must parse to AxisHistogram::empty",
std::any::type_name::<A>(),
);
assert!(
parsed.is_empty(),
"axis {} empty-input parsed histogram must satisfy is_empty",
std::any::type_name::<A>(),
);
}
fn assert_from_str_empty_display_round_trips_to_empty<A>()
where
A: ClosedAxisLabel + std::fmt::Debug,
{
let empty = AxisHistogram::<A>::empty();
let s = format!("{empty}");
let parsed: AxisHistogram<A> = s.parse().unwrap_or_else(|e| {
panic!(
"axis {} empty Display must parse back: {e}",
std::any::type_name::<A>(),
)
});
assert_eq!(
parsed,
empty,
"axis {} empty Display ↔ FromStr round-trip must be identity",
std::any::type_name::<A>(),
);
}
fn assert_from_str_rejects_unknown_label<A>()
where
A: ClosedAxisLabel + std::fmt::Debug,
{
let sentinel = "__shikumi_unknown_label_sentinel__";
let input = format!("{sentinel}=1");
let result: Result<AxisHistogram<A>, ParseAxisHistogramError> = input.parse();
match result {
Err(ParseAxisHistogramError::UnknownLabel { label }) => {
assert_eq!(
label,
sentinel,
"axis {} unknown-label error must carry the offending substring verbatim",
std::any::type_name::<A>(),
);
}
other => panic!(
"axis {} must reject unknown label with UnknownLabel variant, got {other:?}",
std::any::type_name::<A>(),
),
}
}
fn assert_from_str_rejects_missing_equals<A>()
where
A: ClosedAxisLabel + std::fmt::Debug,
{
let first_cell = axis_iter::<A>()
.next()
.expect("axis must have at least one cell");
let input = first_cell.as_str().to_owned();
let result: Result<AxisHistogram<A>, ParseAxisHistogramError> = input.parse();
match result {
Err(ParseAxisHistogramError::MissingEquals { pair }) => {
assert_eq!(
pair,
input,
"axis {} missing-equals error must carry the offending pair verbatim",
std::any::type_name::<A>(),
);
}
other => panic!(
"axis {} must reject missing '=' with MissingEquals variant, got {other:?}",
std::any::type_name::<A>(),
),
}
}
fn assert_from_str_rejects_invalid_count<A>()
where
A: ClosedAxisLabel + std::fmt::Debug,
{
let first_cell = axis_iter::<A>()
.next()
.expect("axis must have at least one cell");
let label = first_cell.as_str();
let input = format!("{label}=oops");
let result: Result<AxisHistogram<A>, ParseAxisHistogramError> = input.parse();
match result {
Err(ParseAxisHistogramError::InvalidCount { label: l, count: c }) => {
assert_eq!(
l,
label,
"axis {} invalid-count error must carry the offending label verbatim",
std::any::type_name::<A>(),
);
assert_eq!(
c,
"oops",
"axis {} invalid-count error must carry the offending count verbatim",
std::any::type_name::<A>(),
);
}
other => panic!(
"axis {} must reject invalid count with InvalidCount variant, got {other:?}",
std::any::type_name::<A>(),
),
}
}
fn assert_serde_yaml_round_trip<A>()
where
A: ClosedAxisLabel + std::fmt::Debug,
{
let mut hist = AxisHistogram::<A>::empty();
for (i, cell) in axis_iter::<A>().enumerate() {
for _ in 0..=i {
hist.observe(cell);
}
}
let yaml = serde_yaml::to_string(&hist).unwrap_or_else(|e| {
panic!(
"axis {} must serialize to YAML: {e}",
std::any::type_name::<A>(),
)
});
let parsed: AxisHistogram<A> = serde_yaml::from_str(&yaml).unwrap_or_else(|e| {
panic!(
"axis {} YAML emission must deserialize back: {e}\n yaml: {yaml:?}",
std::any::type_name::<A>(),
)
});
assert_eq!(
parsed,
hist,
"axis {} (Serialize, Deserialize) YAML round-trip must be identity",
std::any::type_name::<A>(),
);
}
fn assert_serde_json_round_trip<A>()
where
A: ClosedAxisLabel + std::fmt::Debug,
{
let mut hist = AxisHistogram::<A>::empty();
for (i, cell) in axis_iter::<A>().enumerate() {
for _ in 0..=i {
hist.observe(cell);
}
}
let json = serde_json::to_string(&hist).unwrap_or_else(|e| {
panic!(
"axis {} must serialize to JSON: {e}",
std::any::type_name::<A>(),
)
});
let parsed: AxisHistogram<A> = serde_json::from_str(&json).unwrap_or_else(|e| {
panic!(
"axis {} JSON emission must deserialize back: {e}\n json: {json}",
std::any::type_name::<A>(),
)
});
assert_eq!(
parsed,
hist,
"axis {} (Serialize, Deserialize) JSON round-trip must be identity",
std::any::type_name::<A>(),
);
}
fn assert_serde_yaml_empty_round_trip<A>()
where
A: ClosedAxisLabel + std::fmt::Debug,
{
let empty = AxisHistogram::<A>::empty();
let yaml = serde_yaml::to_string(&empty).unwrap_or_else(|e| {
panic!(
"axis {} empty must serialize to YAML: {e}",
std::any::type_name::<A>(),
)
});
let parsed: AxisHistogram<A> = serde_yaml::from_str(&yaml).unwrap_or_else(|e| {
panic!(
"axis {} empty YAML emission must deserialize back: {e}\n yaml: {yaml:?}",
std::any::type_name::<A>(),
)
});
assert_eq!(
parsed,
empty,
"axis {} empty (Serialize, Deserialize) YAML round-trip must be identity",
std::any::type_name::<A>(),
);
}
fn assert_serde_yaml_rejects_unknown_label<A>()
where
A: ClosedAxisLabel + std::fmt::Debug,
{
let sentinel = "__shikumi_unknown_label_sentinel__";
let yaml = format!("\"{sentinel}=1\"\n");
let result: Result<AxisHistogram<A>, _> = serde_yaml::from_str(&yaml);
match result {
Err(e) => {
let rendered = format!("{e}");
assert!(
rendered.contains(sentinel),
"axis {} serde YAML error must carry the unknown sentinel verbatim, got: {rendered}",
std::any::type_name::<A>(),
);
}
Ok(other) => panic!(
"axis {} YAML carrying unknown label must reject, got {other:?}",
std::any::type_name::<A>(),
),
}
}
#[test]
fn axis_histogram_display_from_str_round_trip_for_every_closed_axis_label_implementor() {
macro_rules! check {
($ty:ident) => {
assert_display_from_str_round_trip::<$ty>();
};
}
for_each_closed_axis_label_implementor!(check);
}
#[test]
fn axis_histogram_from_str_empty_is_empty_histogram_for_every_closed_axis_label_implementor() {
macro_rules! check {
($ty:ident) => {
assert_from_str_empty_is_empty_histogram::<$ty>();
};
}
for_each_closed_axis_label_implementor!(check);
}
#[test]
fn axis_histogram_from_str_empty_display_round_trips_to_empty_for_every_closed_axis_label_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_from_str_empty_display_round_trips_to_empty::<$ty>();
};
}
for_each_closed_axis_label_implementor!(check);
}
#[test]
fn axis_histogram_from_str_rejects_unknown_label_for_every_closed_axis_label_implementor() {
macro_rules! check {
($ty:ident) => {
assert_from_str_rejects_unknown_label::<$ty>();
};
}
for_each_closed_axis_label_implementor!(check);
}
#[test]
fn axis_histogram_from_str_rejects_missing_equals_for_every_closed_axis_label_implementor() {
macro_rules! check {
($ty:ident) => {
assert_from_str_rejects_missing_equals::<$ty>();
};
}
for_each_closed_axis_label_implementor!(check);
}
#[test]
fn axis_histogram_from_str_rejects_invalid_count_for_every_closed_axis_label_implementor() {
macro_rules! check {
($ty:ident) => {
assert_from_str_rejects_invalid_count::<$ty>();
};
}
for_each_closed_axis_label_implementor!(check);
}
#[test]
fn axis_histogram_from_str_for_diff_line_kind() {
let parsed: AxisHistogram<DiffLineKind> = "removed=1, added=2, context=3".parse().unwrap();
let mut expected = AxisHistogram::<DiffLineKind>::empty();
for _ in 0..1 {
expected.observe(DiffLineKind::Removed);
}
for _ in 0..2 {
expected.observe(DiffLineKind::Added);
}
for _ in 0..3 {
expected.observe(DiffLineKind::Context);
}
assert_eq!(parsed, expected);
assert_eq!(format!("{parsed}"), "removed=1, added=2, context=3");
let empty: AxisHistogram<DiffLineKind> = "".parse().unwrap();
assert_eq!(empty, AxisHistogram::<DiffLineKind>::empty());
let from_zero_emission: AxisHistogram<DiffLineKind> =
"removed=0, added=0, context=0".parse().unwrap();
assert_eq!(from_zero_emission, AxisHistogram::<DiffLineKind>::empty());
let case_folded: AxisHistogram<DiffLineKind> =
"REMOVED=1, Added=2, ConTeXt=3".parse().unwrap();
assert_eq!(case_folded, expected);
}
#[test]
fn axis_histogram_from_str_is_order_invariant_for_diff_line_kind() {
let canonical: AxisHistogram<DiffLineKind> =
"removed=1, added=2, context=3".parse().unwrap();
let permuted_a: AxisHistogram<DiffLineKind> =
"added=2, context=3, removed=1".parse().unwrap();
let permuted_b: AxisHistogram<DiffLineKind> =
"context=3, removed=1, added=2".parse().unwrap();
assert_eq!(canonical, permuted_a);
assert_eq!(canonical, permuted_b);
}
#[test]
fn axis_histogram_from_str_missing_labels_default_to_zero_for_diff_line_kind() {
let singleton: AxisHistogram<DiffLineKind> = "added=5".parse().unwrap();
let mut expected = AxisHistogram::<DiffLineKind>::empty();
for _ in 0..5 {
expected.observe(DiffLineKind::Added);
}
assert_eq!(singleton, expected);
assert_eq!(singleton.count(DiffLineKind::Removed), 0);
assert_eq!(singleton.count(DiffLineKind::Context), 0);
let with_explicit_zero: AxisHistogram<DiffLineKind> =
"removed=0, added=5, context=0".parse().unwrap();
assert_eq!(singleton, with_explicit_zero);
}
#[test]
fn axis_histogram_from_str_rejects_duplicate_label_for_diff_line_kind() {
let result: Result<AxisHistogram<DiffLineKind>, _> = "added=1, added=2".parse();
match result {
Err(ParseAxisHistogramError::DuplicateLabel { label }) => {
assert_eq!(label, "added");
}
other => panic!("must reject duplicate label, got {other:?}"),
}
let result: Result<AxisHistogram<DiffLineKind>, _> = "added=1, Added=2".parse();
match result {
Err(ParseAxisHistogramError::DuplicateLabel { label }) => {
assert_eq!(label, "Added");
}
other => panic!("must reject case-folded duplicate, got {other:?}"),
}
}
#[test]
fn parse_axis_histogram_error_display_renders_each_variant() {
let missing = ParseAxisHistogramError::MissingEquals {
pair: "addedone".to_owned(),
};
assert_eq!(
format!("{missing}"),
"missing '=' separator in pair \"addedone\"",
);
let unknown = ParseAxisHistogramError::UnknownLabel {
label: "bogus".to_owned(),
};
assert_eq!(format!("{unknown}"), "unknown axis label \"bogus\"");
let invalid = ParseAxisHistogramError::InvalidCount {
label: "added".to_owned(),
count: "oops".to_owned(),
};
assert_eq!(
format!("{invalid}"),
"invalid count \"oops\" for label \"added\" (expected usize)",
);
let duplicate = ParseAxisHistogramError::DuplicateLabel {
label: "added".to_owned(),
};
assert_eq!(format!("{duplicate}"), "duplicate label \"added\"");
}
#[test]
fn axis_histogram_serde_yaml_round_trip_for_every_closed_axis_label_implementor() {
macro_rules! check {
($ty:ident) => {
assert_serde_yaml_round_trip::<$ty>();
};
}
for_each_closed_axis_label_implementor!(check);
}
#[test]
fn axis_histogram_serde_json_round_trip_for_every_closed_axis_label_implementor() {
macro_rules! check {
($ty:ident) => {
assert_serde_json_round_trip::<$ty>();
};
}
for_each_closed_axis_label_implementor!(check);
}
#[test]
fn axis_histogram_serde_yaml_empty_round_trip_for_every_closed_axis_label_implementor() {
macro_rules! check {
($ty:ident) => {
assert_serde_yaml_empty_round_trip::<$ty>();
};
}
for_each_closed_axis_label_implementor!(check);
}
#[test]
fn axis_histogram_serde_yaml_rejects_unknown_label_for_every_closed_axis_label_implementor() {
macro_rules! check {
($ty:ident) => {
assert_serde_yaml_rejects_unknown_label::<$ty>();
};
}
for_each_closed_axis_label_implementor!(check);
}
#[test]
fn axis_histogram_serde_yaml_for_diff_line_kind() {
let mut hist = AxisHistogram::<DiffLineKind>::empty();
for _ in 0..1 {
hist.observe(DiffLineKind::Removed);
}
for _ in 0..2 {
hist.observe(DiffLineKind::Added);
}
for _ in 0..3 {
hist.observe(DiffLineKind::Context);
}
let yaml = serde_yaml::to_string(&hist).unwrap();
assert!(
yaml.contains("removed=1, added=2, context=3"),
"YAML emission must contain the canonical Display form, got: {yaml}",
);
let parsed: AxisHistogram<DiffLineKind> = serde_yaml::from_str(&yaml).unwrap();
assert_eq!(parsed, hist);
let elided: AxisHistogram<DiffLineKind> = serde_yaml::from_str("\"added=5\"\n").unwrap();
let mut expected_elided = AxisHistogram::<DiffLineKind>::empty();
for _ in 0..5 {
expected_elided.observe(DiffLineKind::Added);
}
assert_eq!(elided, expected_elided);
let permuted: AxisHistogram<DiffLineKind> =
serde_yaml::from_str("\"context=3, removed=1, added=2\"\n").unwrap();
assert_eq!(permuted, hist);
let empty = AxisHistogram::<DiffLineKind>::empty();
let yaml_empty = serde_yaml::to_string(&empty).unwrap();
let parsed_empty: AxisHistogram<DiffLineKind> = serde_yaml::from_str(&yaml_empty).unwrap();
assert_eq!(parsed_empty, empty);
}
#[test]
fn axis_histogram_serde_json_for_diff_line_kind() {
let mut hist = AxisHistogram::<DiffLineKind>::empty();
for _ in 0..1 {
hist.observe(DiffLineKind::Removed);
}
for _ in 0..2 {
hist.observe(DiffLineKind::Added);
}
for _ in 0..3 {
hist.observe(DiffLineKind::Context);
}
let json = serde_json::to_string(&hist).unwrap();
assert_eq!(json, "\"removed=1, added=2, context=3\"");
let parsed: AxisHistogram<DiffLineKind> = serde_json::from_str(&json).unwrap();
assert_eq!(parsed, hist);
let elided: AxisHistogram<DiffLineKind> = serde_json::from_str("\"added=5\"").unwrap();
let mut expected_elided = AxisHistogram::<DiffLineKind>::empty();
for _ in 0..5 {
expected_elided.observe(DiffLineKind::Added);
}
assert_eq!(elided, expected_elided);
}
#[test]
fn axis_histogram_serde_yaml_unknown_label_error_carries_label_verbatim_for_diff_line_kind() {
let result: Result<AxisHistogram<DiffLineKind>, _> = serde_yaml::from_str("\"bogus=1\"\n");
let rendered = match result {
Err(e) => format!("{e}"),
Ok(other) => panic!("must reject unknown label, got {other:?}"),
};
assert!(
rendered.contains("unknown axis label"),
"serde error must carry the operator-facing rejection phrase, got: {rendered}",
);
assert!(
rendered.contains("bogus"),
"serde error must carry the offending label verbatim, got: {rendered}",
);
}
fn assert_modality_degree_empty_is_zero_pair<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let hist = AxisHistogram::<A>::empty();
assert_eq!(
hist.modality_degree(),
(0, 0),
"empty histogram modality_degree must be (0, 0) on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_modality_degree_singleton_is_one_pair<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
for observed in axis_iter::<A>() {
let hist: AxisHistogram<A> = std::iter::once(observed).collect();
assert_eq!(
hist.modality_degree(),
(1, 1),
"singleton modality_degree must be (1, 1) for observed cell {observed:?} \
on axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_modality_degree_axis_cover_is_axis_cardinality_pair<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let hist: AxisHistogram<A> = axis_iter::<A>().collect();
let n = axis_cardinality::<A>();
assert_eq!(
hist.modality_degree(),
(n, n),
"uniform axis-cover histogram modality_degree must be (n, n) where \
n = axis_cardinality on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_modality_degree_equals_open_coded_peak_trough_multiplicity_pair<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let empty = AxisHistogram::<A>::empty();
assert_eq!(
empty.modality_degree(),
(empty.peak_multiplicity(), empty.trough_multiplicity()),
"modality_degree must equal the (peak_multiplicity, trough_multiplicity) \
pair on empty histogram for axis {}",
std::any::type_name::<A>(),
);
for observed in axis_iter::<A>() {
let singleton: AxisHistogram<A> = std::iter::once(observed).collect();
assert_eq!(
singleton.modality_degree(),
(
singleton.peak_multiplicity(),
singleton.trough_multiplicity(),
),
"modality_degree must equal the (peak_multiplicity, trough_multiplicity) \
pair on singleton {observed:?} for axis {}",
std::any::type_name::<A>(),
);
}
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
assert_eq!(
cover.modality_degree(),
(cover.peak_multiplicity(), cover.trough_multiplicity()),
"modality_degree must equal the (peak_multiplicity, trough_multiplicity) \
pair on uniform axis-cover for axis {}",
std::any::type_name::<A>(),
);
let mut variants = axis_iter::<A>();
if let (Some(first), Some(second)) = (variants.next(), variants.next()) {
let mut skewed = AxisHistogram::<A>::empty();
skewed.observe(first);
skewed.observe(first);
skewed.observe(second);
assert_eq!(
skewed.modality_degree(),
(skewed.peak_multiplicity(), skewed.trough_multiplicity()),
"modality_degree must equal the (peak_multiplicity, trough_multiplicity) \
pair on a strict-peak/strict-trough skewed shape ({first:?} ×2, \
{second:?} ×1) for axis {}",
std::any::type_name::<A>(),
);
}
}
#[test]
fn axis_histogram_modality_degree_empty_is_zero_pair_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_modality_degree_empty_is_zero_pair::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_modality_degree_singleton_is_one_pair_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_modality_degree_singleton_is_one_pair::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_modality_degree_axis_cover_is_axis_cardinality_pair_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_modality_degree_axis_cover_is_axis_cardinality_pair::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_modality_degree_equals_open_coded_peak_trough_multiplicity_pair_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_modality_degree_equals_open_coded_peak_trough_multiplicity_pair::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_modality_degree_classifies_strictly_unimodal_anti_unimodal_corner() {
let input = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Removed,
DiffLineKind::Context,
];
let hist: AxisHistogram<DiffLineKind> = input.iter().copied().collect();
assert_eq!(hist.count(DiffLineKind::Added), 3);
assert_eq!(hist.count(DiffLineKind::Removed), 2);
assert_eq!(hist.count(DiffLineKind::Context), 1);
assert_eq!(hist.modality_degree(), (1, 1));
}
#[test]
fn axis_histogram_modality_degree_classifies_modally_tied_anti_unimodal_corner() {
let input = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Removed,
DiffLineKind::Context,
];
let hist: AxisHistogram<DiffLineKind> = input.iter().copied().collect();
assert_eq!(hist.modality_degree(), (2, 1));
}
#[test]
fn axis_histogram_modality_degree_classifies_strictly_unimodal_antimodally_tied_corner() {
let input = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Context,
];
let hist: AxisHistogram<DiffLineKind> = input.iter().copied().collect();
assert_eq!(hist.modality_degree(), (1, 2));
}
#[test]
fn axis_histogram_modality_degree_classifies_modal_antimodal_coincidence_corner() {
let axis_cover: AxisHistogram<DiffLineKind> = axis_iter::<DiffLineKind>().collect();
assert!(axis_cover.is_uniform_count());
let (peak_mult, trough_mult) = axis_cover.modality_degree();
assert_eq!(peak_mult, trough_mult);
assert_eq!(peak_mult, axis_cover.distinct_cells());
let sub_cover_uniform: AxisHistogram<DiffLineKind> = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Removed,
]
.into_iter()
.collect();
assert!(sub_cover_uniform.is_uniform_count());
assert_eq!(sub_cover_uniform.modality_degree(), (2, 2));
assert_eq!(
sub_cover_uniform.modality_degree().0,
sub_cover_uniform.distinct_cells(),
);
}
#[test]
fn axis_histogram_modality_degree_observation_order_invariant() {
let multiset_a = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Context,
];
let multiset_b = [
DiffLineKind::Context,
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Added,
];
let multiset_c = [
DiffLineKind::Removed,
DiffLineKind::Added,
DiffLineKind::Context,
DiffLineKind::Added,
];
let a: AxisHistogram<DiffLineKind> = multiset_a.iter().copied().collect();
let b: AxisHistogram<DiffLineKind> = multiset_b.iter().copied().collect();
let c: AxisHistogram<DiffLineKind> = multiset_c.iter().copied().collect();
assert_eq!(a, b);
assert_eq!(b, c);
assert_eq!(a.modality_degree(), b.modality_degree());
assert_eq!(b.modality_degree(), c.modality_degree());
assert_eq!(a.modality_degree(), (1, 2));
}
#[test]
fn axis_histogram_modality_degree_after_merge_reflects_combined_counts() {
let lhs: AxisHistogram<DiffLineKind> = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
]
.into_iter()
.collect();
let rhs: AxisHistogram<DiffLineKind> = [
DiffLineKind::Context,
DiffLineKind::Context,
DiffLineKind::Added,
]
.into_iter()
.collect();
let merged = lhs.merge(&rhs);
assert_eq!(merged.count(DiffLineKind::Added), 3);
assert_eq!(merged.count(DiffLineKind::Context), 2);
assert_eq!(merged.count(DiffLineKind::Removed), 1);
assert_eq!(merged.modality_degree(), (1, 1));
}
#[test]
fn axis_histogram_modality_degree_components_bounded_above_by_distinct_cells() {
let cases: [&[DiffLineKind]; 4] = [
&[],
&[DiffLineKind::Context],
&[
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
],
&[
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Removed,
],
];
for input in cases {
let hist: AxisHistogram<DiffLineKind> = input.iter().copied().collect();
let (peak_mult, trough_mult) = hist.modality_degree();
let support = hist.distinct_cells();
assert!(
peak_mult <= support,
"peak_mult {peak_mult} must be <= distinct_cells {support} on input \
of length {}",
input.len(),
);
assert!(
trough_mult <= support,
"trough_mult {trough_mult} must be <= distinct_cells {support} on input \
of length {}",
input.len(),
);
}
}
fn assert_is_strictly_modally_unique_empty_is_false<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let hist = AxisHistogram::<A>::empty();
assert!(
!hist.is_strictly_modally_unique(),
"empty histogram is_strictly_modally_unique must be false on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_is_strictly_modally_unique_singleton_is_true<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
for observed in axis_iter::<A>() {
let hist: AxisHistogram<A> = std::iter::once(observed).collect();
assert!(
hist.is_strictly_modally_unique(),
"singleton is_strictly_modally_unique must be true for observed cell \
{observed:?} on axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_is_strictly_modally_unique_axis_cover_iff_cardinality_is_one<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let hist: AxisHistogram<A> = axis_iter::<A>().collect();
assert_eq!(
hist.is_strictly_modally_unique(),
axis_cardinality::<A>() == 1,
"axis-cover is_strictly_modally_unique must equal (axis_cardinality == 1) \
on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_is_strictly_modally_unique_equals_open_coded_peak_multiplicity_eq_one<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let empty = AxisHistogram::<A>::empty();
assert_eq!(
empty.is_strictly_modally_unique(),
empty.peak_multiplicity() == 1,
"is_strictly_modally_unique must equal (peak_multiplicity == 1) on empty \
histogram for axis {}",
std::any::type_name::<A>(),
);
for observed in axis_iter::<A>() {
let singleton: AxisHistogram<A> = std::iter::once(observed).collect();
assert_eq!(
singleton.is_strictly_modally_unique(),
singleton.peak_multiplicity() == 1,
"is_strictly_modally_unique must equal (peak_multiplicity == 1) on \
singleton {observed:?} for axis {}",
std::any::type_name::<A>(),
);
}
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
assert_eq!(
cover.is_strictly_modally_unique(),
cover.peak_multiplicity() == 1,
"is_strictly_modally_unique must equal (peak_multiplicity == 1) on uniform \
axis-cover for axis {}",
std::any::type_name::<A>(),
);
let mut variants = axis_iter::<A>();
if let (Some(first), Some(second)) = (variants.next(), variants.next()) {
let mut skewed = AxisHistogram::<A>::empty();
skewed.observe(first);
skewed.observe(first);
skewed.observe(second);
assert_eq!(
skewed.is_strictly_modally_unique(),
skewed.peak_multiplicity() == 1,
"is_strictly_modally_unique must equal (peak_multiplicity == 1) on a \
strict-peak skewed shape ({first:?} x2, {second:?} x1) for axis {}",
std::any::type_name::<A>(),
);
}
}
#[test]
fn axis_histogram_is_strictly_modally_unique_empty_is_false_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_is_strictly_modally_unique_empty_is_false::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_is_strictly_modally_unique_singleton_is_true_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_is_strictly_modally_unique_singleton_is_true::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_is_strictly_modally_unique_axis_cover_iff_cardinality_is_one_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_is_strictly_modally_unique_axis_cover_iff_cardinality_is_one::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_is_strictly_modally_unique_equals_open_coded_peak_multiplicity_eq_one_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_is_strictly_modally_unique_equals_open_coded_peak_multiplicity_eq_one::<$ty>(
);
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_is_strictly_modally_unique_reads_modal_component_of_modality_degree() {
let unique_peak_unique_trough = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Removed,
DiffLineKind::Context,
];
let h1: AxisHistogram<DiffLineKind> = unique_peak_unique_trough.iter().copied().collect();
assert_eq!(h1.modality_degree(), (1, 1));
assert!(h1.is_strictly_modally_unique());
assert_eq!(h1.is_strictly_modally_unique(), h1.modality_degree().0 == 1);
let tied_peak_unique_trough = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Removed,
DiffLineKind::Context,
];
let h2: AxisHistogram<DiffLineKind> = tied_peak_unique_trough.iter().copied().collect();
assert_eq!(h2.modality_degree(), (2, 1));
assert!(!h2.is_strictly_modally_unique());
assert_eq!(h2.is_strictly_modally_unique(), h2.modality_degree().0 == 1);
let unique_peak_tied_trough = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Context,
];
let h3: AxisHistogram<DiffLineKind> = unique_peak_tied_trough.iter().copied().collect();
assert_eq!(h3.modality_degree(), (1, 2));
assert!(h3.is_strictly_modally_unique());
assert_eq!(h3.is_strictly_modally_unique(), h3.modality_degree().0 == 1);
let uniform_sub_cover = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Removed,
];
let h4: AxisHistogram<DiffLineKind> = uniform_sub_cover.iter().copied().collect();
assert_eq!(h4.modality_degree(), (2, 2));
assert!(!h4.is_strictly_modally_unique());
assert_eq!(h4.is_strictly_modally_unique(), h4.modality_degree().0 == 1);
}
#[test]
fn axis_histogram_has_singular_support_implies_is_strictly_modally_unique() {
for observed in [
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Context,
] {
let hist: AxisHistogram<DiffLineKind> = std::iter::once(observed).collect();
assert!(hist.has_singular_support());
assert!(hist.is_strictly_modally_unique());
}
let two_cell = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
];
let hist: AxisHistogram<DiffLineKind> = two_cell.iter().copied().collect();
assert!(!hist.has_singular_support());
assert!(hist.is_strictly_modally_unique());
}
#[test]
fn axis_histogram_is_strictly_modally_unique_witnesses_non_empty_when_true() {
let cases: [&[DiffLineKind]; 3] = [
&[DiffLineKind::Context],
&[
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
],
&[
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Context,
],
];
for input in cases {
let hist: AxisHistogram<DiffLineKind> = input.iter().copied().collect();
assert!(hist.is_strictly_modally_unique());
assert!(!hist.is_empty());
}
let empty: AxisHistogram<DiffLineKind> = AxisHistogram::empty();
assert!(empty.is_empty());
assert!(!empty.is_strictly_modally_unique());
}
#[test]
fn axis_histogram_is_strictly_modally_unique_under_uniform_count_collapses_to_singular_support()
{
let singleton_multi = [DiffLineKind::Added, DiffLineKind::Added];
let h_singleton: AxisHistogram<DiffLineKind> = singleton_multi.iter().copied().collect();
assert!(h_singleton.is_uniform_count());
assert!(!h_singleton.is_empty());
assert_eq!(
h_singleton.is_strictly_modally_unique(),
h_singleton.has_singular_support(),
);
assert!(h_singleton.is_strictly_modally_unique());
let two_cell_uniform = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Removed,
];
let h_two: AxisHistogram<DiffLineKind> = two_cell_uniform.iter().copied().collect();
assert!(h_two.is_uniform_count());
assert!(!h_two.is_empty());
assert_eq!(
h_two.is_strictly_modally_unique(),
h_two.has_singular_support(),
);
assert!(!h_two.is_strictly_modally_unique());
let cover: AxisHistogram<DiffLineKind> = [
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Context,
]
.into_iter()
.collect();
assert!(cover.is_uniform_count());
assert!(!cover.is_empty());
assert_eq!(
cover.is_strictly_modally_unique(),
cover.has_singular_support(),
);
assert!(!cover.is_strictly_modally_unique());
}
fn assert_is_strictly_antimodally_unique_empty_is_false<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let hist = AxisHistogram::<A>::empty();
assert!(
!hist.is_strictly_antimodally_unique(),
"empty histogram is_strictly_antimodally_unique must be false on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_is_strictly_antimodally_unique_singleton_is_true<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
for observed in axis_iter::<A>() {
let hist: AxisHistogram<A> = std::iter::once(observed).collect();
assert!(
hist.is_strictly_antimodally_unique(),
"singleton is_strictly_antimodally_unique must be true for observed cell \
{observed:?} on axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_is_strictly_antimodally_unique_axis_cover_iff_cardinality_is_one<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let hist: AxisHistogram<A> = axis_iter::<A>().collect();
assert_eq!(
hist.is_strictly_antimodally_unique(),
axis_cardinality::<A>() == 1,
"axis-cover is_strictly_antimodally_unique must equal (axis_cardinality == 1) \
on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_is_strictly_antimodally_unique_equals_open_coded_trough_multiplicity_eq_one<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let empty = AxisHistogram::<A>::empty();
assert_eq!(
empty.is_strictly_antimodally_unique(),
empty.trough_multiplicity() == 1,
"is_strictly_antimodally_unique must equal (trough_multiplicity == 1) on empty \
histogram for axis {}",
std::any::type_name::<A>(),
);
for observed in axis_iter::<A>() {
let singleton: AxisHistogram<A> = std::iter::once(observed).collect();
assert_eq!(
singleton.is_strictly_antimodally_unique(),
singleton.trough_multiplicity() == 1,
"is_strictly_antimodally_unique must equal (trough_multiplicity == 1) on \
singleton {observed:?} for axis {}",
std::any::type_name::<A>(),
);
}
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
assert_eq!(
cover.is_strictly_antimodally_unique(),
cover.trough_multiplicity() == 1,
"is_strictly_antimodally_unique must equal (trough_multiplicity == 1) on uniform \
axis-cover for axis {}",
std::any::type_name::<A>(),
);
let mut variants = axis_iter::<A>();
if let (Some(first), Some(second)) = (variants.next(), variants.next()) {
let mut skewed = AxisHistogram::<A>::empty();
skewed.observe(first);
skewed.observe(first);
skewed.observe(second);
assert_eq!(
skewed.is_strictly_antimodally_unique(),
skewed.trough_multiplicity() == 1,
"is_strictly_antimodally_unique must equal (trough_multiplicity == 1) on a \
strict-trough skewed shape ({first:?} x2, {second:?} x1) for axis {}",
std::any::type_name::<A>(),
);
}
}
#[test]
fn axis_histogram_is_strictly_antimodally_unique_empty_is_false_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_is_strictly_antimodally_unique_empty_is_false::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_is_strictly_antimodally_unique_singleton_is_true_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_is_strictly_antimodally_unique_singleton_is_true::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_is_strictly_antimodally_unique_axis_cover_iff_cardinality_is_one_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_is_strictly_antimodally_unique_axis_cover_iff_cardinality_is_one::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_is_strictly_antimodally_unique_equals_open_coded_trough_multiplicity_eq_one_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_is_strictly_antimodally_unique_equals_open_coded_trough_multiplicity_eq_one::<
$ty,
>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_is_strictly_antimodally_unique_reads_antimodal_component_of_modality_degree()
{
let unique_peak_unique_trough = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Removed,
DiffLineKind::Context,
];
let h1: AxisHistogram<DiffLineKind> = unique_peak_unique_trough.iter().copied().collect();
assert_eq!(h1.modality_degree(), (1, 1));
assert!(h1.is_strictly_antimodally_unique());
assert_eq!(
h1.is_strictly_antimodally_unique(),
h1.modality_degree().1 == 1,
);
let tied_peak_unique_trough = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Removed,
DiffLineKind::Context,
];
let h2: AxisHistogram<DiffLineKind> = tied_peak_unique_trough.iter().copied().collect();
assert_eq!(h2.modality_degree(), (2, 1));
assert!(h2.is_strictly_antimodally_unique());
assert_eq!(
h2.is_strictly_antimodally_unique(),
h2.modality_degree().1 == 1,
);
let unique_peak_tied_trough = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Context,
];
let h3: AxisHistogram<DiffLineKind> = unique_peak_tied_trough.iter().copied().collect();
assert_eq!(h3.modality_degree(), (1, 2));
assert!(!h3.is_strictly_antimodally_unique());
assert_eq!(
h3.is_strictly_antimodally_unique(),
h3.modality_degree().1 == 1,
);
let uniform_sub_cover = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Removed,
];
let h4: AxisHistogram<DiffLineKind> = uniform_sub_cover.iter().copied().collect();
assert_eq!(h4.modality_degree(), (2, 2));
assert!(!h4.is_strictly_antimodally_unique());
assert_eq!(
h4.is_strictly_antimodally_unique(),
h4.modality_degree().1 == 1,
);
}
#[test]
fn axis_histogram_has_singular_support_implies_is_strictly_antimodally_unique() {
for observed in [
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Context,
] {
let hist: AxisHistogram<DiffLineKind> = std::iter::once(observed).collect();
assert!(hist.has_singular_support());
assert!(hist.is_strictly_antimodally_unique());
}
let two_cell = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
];
let hist: AxisHistogram<DiffLineKind> = two_cell.iter().copied().collect();
assert!(!hist.has_singular_support());
assert!(hist.is_strictly_antimodally_unique());
}
#[test]
fn axis_histogram_is_strictly_antimodally_unique_witnesses_non_empty_when_true() {
let cases: [&[DiffLineKind]; 3] = [
&[DiffLineKind::Context],
&[
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
],
&[
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Removed,
DiffLineKind::Context,
],
];
for input in cases {
let hist: AxisHistogram<DiffLineKind> = input.iter().copied().collect();
assert!(hist.is_strictly_antimodally_unique());
assert!(!hist.is_empty());
}
let empty: AxisHistogram<DiffLineKind> = AxisHistogram::empty();
assert!(empty.is_empty());
assert!(!empty.is_strictly_antimodally_unique());
}
#[test]
fn axis_histogram_is_strictly_antimodally_unique_under_uniform_count_collapses_to_singular_support()
{
let singleton_multi = [DiffLineKind::Added, DiffLineKind::Added];
let h_singleton: AxisHistogram<DiffLineKind> = singleton_multi.iter().copied().collect();
assert!(h_singleton.is_uniform_count());
assert!(!h_singleton.is_empty());
assert_eq!(
h_singleton.is_strictly_antimodally_unique(),
h_singleton.has_singular_support(),
);
assert!(h_singleton.is_strictly_antimodally_unique());
let two_cell_uniform = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Removed,
];
let h_two: AxisHistogram<DiffLineKind> = two_cell_uniform.iter().copied().collect();
assert!(h_two.is_uniform_count());
assert!(!h_two.is_empty());
assert_eq!(
h_two.is_strictly_antimodally_unique(),
h_two.has_singular_support(),
);
assert!(!h_two.is_strictly_antimodally_unique());
let cover: AxisHistogram<DiffLineKind> = [
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Context,
]
.into_iter()
.collect();
assert!(cover.is_uniform_count());
assert!(!cover.is_empty());
assert_eq!(
cover.is_strictly_antimodally_unique(),
cover.has_singular_support(),
);
assert!(!cover.is_strictly_antimodally_unique());
}
#[test]
fn axis_histogram_uniqueness_pair_collapses_on_uniform_count_non_empty() {
let singleton_multi = [DiffLineKind::Removed, DiffLineKind::Removed];
let h_singleton: AxisHistogram<DiffLineKind> = singleton_multi.iter().copied().collect();
assert!(h_singleton.is_uniform_count());
assert!(!h_singleton.is_empty());
assert_eq!(
h_singleton.is_strictly_modally_unique(),
h_singleton.is_strictly_antimodally_unique(),
);
assert!(h_singleton.is_strictly_modally_unique());
assert!(h_singleton.is_strictly_antimodally_unique());
let two_cell_uniform = [DiffLineKind::Added, DiffLineKind::Removed];
let h_two: AxisHistogram<DiffLineKind> = two_cell_uniform.iter().copied().collect();
assert!(h_two.is_uniform_count());
assert!(!h_two.is_empty());
assert_eq!(
h_two.is_strictly_modally_unique(),
h_two.is_strictly_antimodally_unique(),
);
assert!(!h_two.is_strictly_modally_unique());
assert!(!h_two.is_strictly_antimodally_unique());
let cover: AxisHistogram<DiffLineKind> = [
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Context,
]
.into_iter()
.collect();
assert!(cover.is_uniform_count());
assert!(!cover.is_empty());
assert_eq!(
cover.is_strictly_modally_unique(),
cover.is_strictly_antimodally_unique(),
);
assert!(!cover.is_strictly_modally_unique());
assert!(!cover.is_strictly_antimodally_unique());
}
fn assert_is_modally_tied_empty_is_false<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let hist = AxisHistogram::<A>::empty();
assert!(
!hist.is_modally_tied(),
"empty histogram is_modally_tied must be false on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_is_modally_tied_singleton_is_false<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
for observed in axis_iter::<A>() {
let hist: AxisHistogram<A> = std::iter::once(observed).collect();
assert!(
!hist.is_modally_tied(),
"singleton is_modally_tied must be false for observed cell \
{observed:?} on axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_is_modally_tied_axis_cover_iff_cardinality_at_least_two<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let hist: AxisHistogram<A> = axis_iter::<A>().collect();
assert_eq!(
hist.is_modally_tied(),
axis_cardinality::<A>() >= 2,
"axis-cover is_modally_tied must equal (axis_cardinality >= 2) \
on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_is_modally_tied_equals_open_coded_peak_multiplicity_ge_two<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let empty = AxisHistogram::<A>::empty();
assert_eq!(
empty.is_modally_tied(),
empty.peak_multiplicity() >= 2,
"is_modally_tied must equal (peak_multiplicity >= 2) on empty \
histogram for axis {}",
std::any::type_name::<A>(),
);
for observed in axis_iter::<A>() {
let singleton: AxisHistogram<A> = std::iter::once(observed).collect();
assert_eq!(
singleton.is_modally_tied(),
singleton.peak_multiplicity() >= 2,
"is_modally_tied must equal (peak_multiplicity >= 2) on \
singleton {observed:?} for axis {}",
std::any::type_name::<A>(),
);
}
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
assert_eq!(
cover.is_modally_tied(),
cover.peak_multiplicity() >= 2,
"is_modally_tied must equal (peak_multiplicity >= 2) on uniform \
axis-cover for axis {}",
std::any::type_name::<A>(),
);
let mut variants = axis_iter::<A>();
if let (Some(first), Some(second)) = (variants.next(), variants.next()) {
let mut tied = AxisHistogram::<A>::empty();
tied.observe(first);
tied.observe(second);
assert_eq!(
tied.is_modally_tied(),
tied.peak_multiplicity() >= 2,
"is_modally_tied must equal (peak_multiplicity >= 2) on a \
tied-modal sub-cover ({first:?} x1, {second:?} x1) for axis {}",
std::any::type_name::<A>(),
);
}
}
#[test]
fn axis_histogram_is_modally_tied_empty_is_false_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_is_modally_tied_empty_is_false::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_is_modally_tied_singleton_is_false_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_is_modally_tied_singleton_is_false::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_is_modally_tied_axis_cover_iff_cardinality_at_least_two_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_is_modally_tied_axis_cover_iff_cardinality_at_least_two::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_is_modally_tied_equals_open_coded_peak_multiplicity_ge_two_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_is_modally_tied_equals_open_coded_peak_multiplicity_ge_two::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_is_modally_tied_reads_modal_component_of_modality_degree() {
let unique_peak_unique_trough = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Removed,
DiffLineKind::Context,
];
let h1: AxisHistogram<DiffLineKind> = unique_peak_unique_trough.iter().copied().collect();
assert_eq!(h1.modality_degree(), (1, 1));
assert!(!h1.is_modally_tied());
assert_eq!(h1.is_modally_tied(), h1.modality_degree().0 >= 2);
let tied_peak_unique_trough = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Removed,
DiffLineKind::Context,
];
let h2: AxisHistogram<DiffLineKind> = tied_peak_unique_trough.iter().copied().collect();
assert_eq!(h2.modality_degree(), (2, 1));
assert!(h2.is_modally_tied());
assert_eq!(h2.is_modally_tied(), h2.modality_degree().0 >= 2);
let unique_peak_tied_trough = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Context,
];
let h3: AxisHistogram<DiffLineKind> = unique_peak_tied_trough.iter().copied().collect();
assert_eq!(h3.modality_degree(), (1, 2));
assert!(!h3.is_modally_tied());
assert_eq!(h3.is_modally_tied(), h3.modality_degree().0 >= 2);
let uniform_sub_cover = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Removed,
];
let h4: AxisHistogram<DiffLineKind> = uniform_sub_cover.iter().copied().collect();
assert_eq!(h4.modality_degree(), (2, 2));
assert!(h4.is_modally_tied());
assert_eq!(h4.is_modally_tied(), h4.modality_degree().0 >= 2);
}
#[test]
fn axis_histogram_is_modally_tied_strictly_partitions_modal_axis_on_non_empty() {
let cases: [&[DiffLineKind]; 4] = [
&[DiffLineKind::Added, DiffLineKind::Added],
&[
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
],
&[DiffLineKind::Added, DiffLineKind::Removed],
&[
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Context,
],
];
for input in cases {
let hist: AxisHistogram<DiffLineKind> = input.iter().copied().collect();
assert!(!hist.is_empty());
assert_ne!(
hist.is_strictly_modally_unique(),
hist.is_modally_tied(),
"non-empty histogram must fire exactly one of \
(is_strictly_modally_unique, is_modally_tied) on input of length {}",
input.len(),
);
assert_eq!(
hist.is_modally_tied(),
!hist.is_strictly_modally_unique(),
"is_modally_tied must equal !is_strictly_modally_unique on \
non-empty input of length {}",
input.len(),
);
}
let empty: AxisHistogram<DiffLineKind> = AxisHistogram::empty();
assert!(empty.is_empty());
assert!(!empty.is_strictly_modally_unique());
assert!(!empty.is_modally_tied());
}
#[test]
fn axis_histogram_is_modally_tied_witnesses_non_empty_when_true() {
let cases: [&[DiffLineKind]; 3] = [
&[DiffLineKind::Added, DiffLineKind::Removed],
&[
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Removed,
],
&[
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Context,
],
];
for input in cases {
let hist: AxisHistogram<DiffLineKind> = input.iter().copied().collect();
assert!(hist.is_modally_tied());
assert!(!hist.is_empty());
}
let empty: AxisHistogram<DiffLineKind> = AxisHistogram::empty();
assert!(empty.is_empty());
assert!(!empty.is_modally_tied());
}
#[test]
fn axis_histogram_has_singular_support_implies_not_is_modally_tied() {
for observed in [
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Context,
] {
let hist: AxisHistogram<DiffLineKind> = std::iter::once(observed).collect();
assert!(hist.has_singular_support());
assert!(!hist.is_modally_tied());
}
let two_cell_tied: AxisHistogram<DiffLineKind> =
[DiffLineKind::Added, DiffLineKind::Removed]
.into_iter()
.collect();
assert!(two_cell_tied.is_modally_tied());
assert!(!two_cell_tied.has_singular_support());
}
#[test]
fn axis_histogram_is_modally_tied_under_uniform_count_collapses_to_multi_cell_support() {
let singleton_multi = [DiffLineKind::Added, DiffLineKind::Added];
let h_singleton: AxisHistogram<DiffLineKind> = singleton_multi.iter().copied().collect();
assert!(h_singleton.is_uniform_count());
assert!(!h_singleton.is_empty());
assert_eq!(
h_singleton.is_modally_tied(),
!h_singleton.has_singular_support(),
);
assert!(!h_singleton.is_modally_tied());
let two_cell_uniform = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Removed,
];
let h_two: AxisHistogram<DiffLineKind> = two_cell_uniform.iter().copied().collect();
assert!(h_two.is_uniform_count());
assert!(!h_two.is_empty());
assert_eq!(h_two.is_modally_tied(), !h_two.has_singular_support(),);
assert!(h_two.is_modally_tied());
let cover: AxisHistogram<DiffLineKind> = [
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Context,
]
.into_iter()
.collect();
assert!(cover.is_uniform_count());
assert!(!cover.is_empty());
assert_eq!(cover.is_modally_tied(), !cover.has_singular_support(),);
assert!(cover.is_modally_tied());
}
fn assert_is_antimodally_tied_empty_is_false<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let hist = AxisHistogram::<A>::empty();
assert!(
!hist.is_antimodally_tied(),
"empty histogram is_antimodally_tied must be false on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_is_antimodally_tied_singleton_is_false<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
for observed in axis_iter::<A>() {
let hist: AxisHistogram<A> = std::iter::once(observed).collect();
assert!(
!hist.is_antimodally_tied(),
"singleton is_antimodally_tied must be false for observed cell \
{observed:?} on axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_is_antimodally_tied_axis_cover_iff_cardinality_at_least_two<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let hist: AxisHistogram<A> = axis_iter::<A>().collect();
assert_eq!(
hist.is_antimodally_tied(),
axis_cardinality::<A>() >= 2,
"axis-cover is_antimodally_tied must equal (axis_cardinality >= 2) \
on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_is_antimodally_tied_equals_open_coded_trough_multiplicity_ge_two<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let empty = AxisHistogram::<A>::empty();
assert_eq!(
empty.is_antimodally_tied(),
empty.trough_multiplicity() >= 2,
"is_antimodally_tied must equal (trough_multiplicity >= 2) on empty \
histogram for axis {}",
std::any::type_name::<A>(),
);
for observed in axis_iter::<A>() {
let singleton: AxisHistogram<A> = std::iter::once(observed).collect();
assert_eq!(
singleton.is_antimodally_tied(),
singleton.trough_multiplicity() >= 2,
"is_antimodally_tied must equal (trough_multiplicity >= 2) on \
singleton {observed:?} for axis {}",
std::any::type_name::<A>(),
);
}
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
assert_eq!(
cover.is_antimodally_tied(),
cover.trough_multiplicity() >= 2,
"is_antimodally_tied must equal (trough_multiplicity >= 2) on uniform \
axis-cover for axis {}",
std::any::type_name::<A>(),
);
let mut variants = axis_iter::<A>();
if let (Some(first), Some(second)) = (variants.next(), variants.next()) {
let mut tied = AxisHistogram::<A>::empty();
tied.observe(first);
tied.observe(second);
assert_eq!(
tied.is_antimodally_tied(),
tied.trough_multiplicity() >= 2,
"is_antimodally_tied must equal (trough_multiplicity >= 2) on a \
tied-antimodal sub-cover ({first:?} x1, {second:?} x1) for axis {}",
std::any::type_name::<A>(),
);
}
}
#[test]
fn axis_histogram_is_antimodally_tied_empty_is_false_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_is_antimodally_tied_empty_is_false::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_is_antimodally_tied_singleton_is_false_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_is_antimodally_tied_singleton_is_false::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_is_antimodally_tied_axis_cover_iff_cardinality_at_least_two_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_is_antimodally_tied_axis_cover_iff_cardinality_at_least_two::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_is_antimodally_tied_equals_open_coded_trough_multiplicity_ge_two_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_is_antimodally_tied_equals_open_coded_trough_multiplicity_ge_two::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_is_antimodally_tied_reads_antimodal_component_of_modality_degree() {
let unique_peak_unique_trough = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Removed,
DiffLineKind::Context,
];
let h1: AxisHistogram<DiffLineKind> = unique_peak_unique_trough.iter().copied().collect();
assert_eq!(h1.modality_degree(), (1, 1));
assert!(!h1.is_antimodally_tied());
assert_eq!(h1.is_antimodally_tied(), h1.modality_degree().1 >= 2);
let tied_peak_unique_trough = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Removed,
DiffLineKind::Context,
];
let h2: AxisHistogram<DiffLineKind> = tied_peak_unique_trough.iter().copied().collect();
assert_eq!(h2.modality_degree(), (2, 1));
assert!(!h2.is_antimodally_tied());
assert_eq!(h2.is_antimodally_tied(), h2.modality_degree().1 >= 2);
let unique_peak_tied_trough = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Context,
];
let h3: AxisHistogram<DiffLineKind> = unique_peak_tied_trough.iter().copied().collect();
assert_eq!(h3.modality_degree(), (1, 2));
assert!(h3.is_antimodally_tied());
assert_eq!(h3.is_antimodally_tied(), h3.modality_degree().1 >= 2);
let uniform_sub_cover = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Removed,
];
let h4: AxisHistogram<DiffLineKind> = uniform_sub_cover.iter().copied().collect();
assert_eq!(h4.modality_degree(), (2, 2));
assert!(h4.is_antimodally_tied());
assert_eq!(h4.is_antimodally_tied(), h4.modality_degree().1 >= 2);
}
#[test]
fn axis_histogram_is_antimodally_tied_strictly_partitions_antimodal_axis_on_non_empty() {
let cases: [&[DiffLineKind]; 4] = [
&[DiffLineKind::Added, DiffLineKind::Added],
&[
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
],
&[DiffLineKind::Added, DiffLineKind::Removed],
&[
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Context,
],
];
for input in cases {
let hist: AxisHistogram<DiffLineKind> = input.iter().copied().collect();
assert!(!hist.is_empty());
assert_ne!(
hist.is_strictly_antimodally_unique(),
hist.is_antimodally_tied(),
"non-empty histogram must fire exactly one of \
(is_strictly_antimodally_unique, is_antimodally_tied) on input of length {}",
input.len(),
);
assert_eq!(
hist.is_antimodally_tied(),
!hist.is_strictly_antimodally_unique(),
"is_antimodally_tied must equal !is_strictly_antimodally_unique on \
non-empty input of length {}",
input.len(),
);
}
let empty: AxisHistogram<DiffLineKind> = AxisHistogram::empty();
assert!(empty.is_empty());
assert!(!empty.is_strictly_antimodally_unique());
assert!(!empty.is_antimodally_tied());
}
#[test]
fn axis_histogram_is_antimodally_tied_witnesses_non_empty_when_true() {
let cases: [&[DiffLineKind]; 3] = [
&[DiffLineKind::Added, DiffLineKind::Removed],
&[
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Removed,
],
&[
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Context,
],
];
for input in cases {
let hist: AxisHistogram<DiffLineKind> = input.iter().copied().collect();
assert!(hist.is_antimodally_tied());
assert!(!hist.is_empty());
}
let empty: AxisHistogram<DiffLineKind> = AxisHistogram::empty();
assert!(empty.is_empty());
assert!(!empty.is_antimodally_tied());
}
#[test]
fn axis_histogram_has_singular_support_implies_not_is_antimodally_tied() {
for observed in [
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Context,
] {
let hist: AxisHistogram<DiffLineKind> = std::iter::once(observed).collect();
assert!(hist.has_singular_support());
assert!(!hist.is_antimodally_tied());
}
let two_cell_tied: AxisHistogram<DiffLineKind> =
[DiffLineKind::Added, DiffLineKind::Removed]
.into_iter()
.collect();
assert!(two_cell_tied.is_antimodally_tied());
assert!(!two_cell_tied.has_singular_support());
}
#[test]
fn axis_histogram_is_antimodally_tied_under_uniform_count_collapses_to_multi_cell_support() {
let singleton_multi = [DiffLineKind::Added, DiffLineKind::Added];
let h_singleton: AxisHistogram<DiffLineKind> = singleton_multi.iter().copied().collect();
assert!(h_singleton.is_uniform_count());
assert!(!h_singleton.is_empty());
assert_eq!(
h_singleton.is_antimodally_tied(),
!h_singleton.has_singular_support(),
);
assert!(!h_singleton.is_antimodally_tied());
let two_cell_uniform = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Removed,
];
let h_two: AxisHistogram<DiffLineKind> = two_cell_uniform.iter().copied().collect();
assert!(h_two.is_uniform_count());
assert!(!h_two.is_empty());
assert_eq!(h_two.is_antimodally_tied(), !h_two.has_singular_support(),);
assert!(h_two.is_antimodally_tied());
let cover: AxisHistogram<DiffLineKind> = [
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Context,
]
.into_iter()
.collect();
assert!(cover.is_uniform_count());
assert!(!cover.is_empty());
assert_eq!(cover.is_antimodally_tied(), !cover.has_singular_support(),);
assert!(cover.is_antimodally_tied());
}
#[test]
fn axis_histogram_is_antimodally_tied_coincides_with_is_modally_tied_under_uniform_count() {
let empty: AxisHistogram<DiffLineKind> = AxisHistogram::empty();
assert_eq!(empty.is_modally_tied(), empty.is_antimodally_tied());
let singleton_multi: AxisHistogram<DiffLineKind> =
[DiffLineKind::Added, DiffLineKind::Added]
.iter()
.copied()
.collect();
assert!(singleton_multi.is_uniform_count());
assert_eq!(
singleton_multi.is_modally_tied(),
singleton_multi.is_antimodally_tied(),
);
let two_cell_uniform: AxisHistogram<DiffLineKind> = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Removed,
]
.iter()
.copied()
.collect();
assert!(two_cell_uniform.is_uniform_count());
assert_eq!(
two_cell_uniform.is_modally_tied(),
two_cell_uniform.is_antimodally_tied(),
);
let cover: AxisHistogram<DiffLineKind> = [
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Context,
]
.into_iter()
.collect();
assert!(cover.is_uniform_count());
assert_eq!(cover.is_modally_tied(), cover.is_antimodally_tied());
}
#[test]
fn modality_class_all_has_five_entries() {
assert_eq!(ModalityClass::ALL.len(), 5);
assert_eq!(
ModalityClass::ALL,
&[
ModalityClass::Empty,
ModalityClass::StrictModalStrictAntimodal,
ModalityClass::TiedModalStrictAntimodal,
ModalityClass::StrictModalTiedAntimodal,
ModalityClass::TiedModalTiedAntimodal,
],
);
}
#[test]
fn modality_class_all_entries_are_pairwise_distinct() {
for (i, &a) in ModalityClass::ALL.iter().enumerate() {
for (j, &b) in ModalityClass::ALL.iter().enumerate() {
if i == j {
assert_eq!(a, b, "variant must equal itself at index {i}");
} else {
assert_ne!(a, b, "variants at indices {i} and {j} must differ");
}
}
}
}
#[test]
fn modality_class_is_empty_fires_exactly_on_empty_variant() {
for &class in ModalityClass::ALL {
assert_eq!(
class.is_empty(),
matches!(class, ModalityClass::Empty),
"is_empty must fire iff variant == Empty for class {class:?}",
);
}
}
#[test]
fn modality_class_is_modally_tied_fires_exactly_on_tied_modal_variants() {
for &class in ModalityClass::ALL {
let expected = matches!(
class,
ModalityClass::TiedModalStrictAntimodal | ModalityClass::TiedModalTiedAntimodal,
);
assert_eq!(
class.is_modally_tied(),
expected,
"is_modally_tied projection on class {class:?}",
);
}
}
#[test]
fn modality_class_is_antimodally_tied_fires_exactly_on_tied_antimodal_variants() {
for &class in ModalityClass::ALL {
let expected = matches!(
class,
ModalityClass::StrictModalTiedAntimodal | ModalityClass::TiedModalTiedAntimodal,
);
assert_eq!(
class.is_antimodally_tied(),
expected,
"is_antimodally_tied projection on class {class:?}",
);
}
}
#[test]
fn modality_class_is_strictly_modally_unique_fires_exactly_on_strict_modal_variants() {
for &class in ModalityClass::ALL {
let expected = matches!(
class,
ModalityClass::StrictModalStrictAntimodal | ModalityClass::StrictModalTiedAntimodal,
);
assert_eq!(
class.is_strictly_modally_unique(),
expected,
"is_strictly_modally_unique projection on class {class:?}",
);
}
}
#[test]
fn modality_class_is_strictly_antimodally_unique_fires_exactly_on_strict_antimodal_variants() {
for &class in ModalityClass::ALL {
let expected = matches!(
class,
ModalityClass::StrictModalStrictAntimodal | ModalityClass::TiedModalStrictAntimodal,
);
assert_eq!(
class.is_strictly_antimodally_unique(),
expected,
"is_strictly_antimodally_unique projection on class {class:?}",
);
}
}
#[test]
fn modality_class_strict_and_tied_modal_predicates_partition_non_empty_variants() {
for &class in ModalityClass::ALL {
let strict = class.is_strictly_modally_unique();
let tied = class.is_modally_tied();
if matches!(class, ModalityClass::Empty) {
assert!(
!strict && !tied,
"Empty must read false on both modal predicates (got strict={strict}, tied={tied})",
);
} else {
assert!(
strict ^ tied,
"non-empty class {class:?} must land on exactly one modal predicate \
(got strict={strict}, tied={tied})",
);
}
}
}
#[test]
fn modality_class_strict_and_tied_antimodal_predicates_partition_non_empty_variants() {
for &class in ModalityClass::ALL {
let strict = class.is_strictly_antimodally_unique();
let tied = class.is_antimodally_tied();
if matches!(class, ModalityClass::Empty) {
assert!(
!strict && !tied,
"Empty must read false on both antimodal predicates (got strict={strict}, tied={tied})",
);
} else {
assert!(
strict ^ tied,
"non-empty class {class:?} must land on exactly one antimodal predicate \
(got strict={strict}, tied={tied})",
);
}
}
}
#[test]
fn modality_class_is_doubly_strict_unique_fires_exactly_on_strict_modal_strict_antimodal() {
for &class in ModalityClass::ALL {
let expected = matches!(class, ModalityClass::StrictModalStrictAntimodal);
assert_eq!(
class.is_doubly_strict_unique(),
expected,
"is_doubly_strict_unique projection on class {class:?}",
);
}
}
#[test]
fn modality_class_is_doubly_tied_fires_exactly_on_tied_modal_tied_antimodal() {
for &class in ModalityClass::ALL {
let expected = matches!(class, ModalityClass::TiedModalTiedAntimodal);
assert_eq!(
class.is_doubly_tied(),
expected,
"is_doubly_tied projection on class {class:?}",
);
}
}
#[test]
fn modality_class_is_doubly_strict_unique_equals_strict_modal_and_strict_antimodal_conjunction()
{
for &class in ModalityClass::ALL {
let conj = class.is_strictly_modally_unique() && class.is_strictly_antimodally_unique();
assert_eq!(
class.is_doubly_strict_unique(),
conj,
"is_doubly_strict_unique must equal the per-axis strict-unique conjunction \
on class {class:?}",
);
}
}
#[test]
fn modality_class_is_doubly_tied_equals_modal_tied_and_antimodal_tied_conjunction() {
for &class in ModalityClass::ALL {
let conj = class.is_modally_tied() && class.is_antimodally_tied();
assert_eq!(
class.is_doubly_tied(),
conj,
"is_doubly_tied must equal the per-axis tied conjunction on class {class:?}",
);
}
}
#[test]
fn modality_class_is_doubly_strict_unique_and_is_doubly_tied_are_disjoint() {
for &class in ModalityClass::ALL {
let strict = class.is_doubly_strict_unique();
let tied = class.is_doubly_tied();
assert!(
!(strict && tied),
"doubly-strict-unique and doubly-tied must be disjoint \
(got strict={strict}, tied={tied}) on class {class:?}",
);
}
}
#[test]
fn modality_class_is_only_modally_tied_fires_exactly_on_tied_modal_strict_antimodal() {
for &class in ModalityClass::ALL {
let expected = matches!(class, ModalityClass::TiedModalStrictAntimodal);
assert_eq!(
class.is_only_modally_tied(),
expected,
"is_only_modally_tied projection on class {class:?}",
);
}
}
#[test]
fn modality_class_is_only_antimodally_tied_fires_exactly_on_strict_modal_tied_antimodal() {
for &class in ModalityClass::ALL {
let expected = matches!(class, ModalityClass::StrictModalTiedAntimodal);
assert_eq!(
class.is_only_antimodally_tied(),
expected,
"is_only_antimodally_tied projection on class {class:?}",
);
}
}
#[test]
fn modality_class_is_only_modally_tied_equals_modal_tied_and_antimodal_strict_conjunction() {
for &class in ModalityClass::ALL {
let conj = class.is_modally_tied() && class.is_strictly_antimodally_unique();
assert_eq!(
class.is_only_modally_tied(),
conj,
"is_only_modally_tied must equal the (modal-tied, antimodal-strict) \
conjunction on class {class:?}",
);
}
}
#[test]
fn modality_class_is_only_antimodally_tied_equals_antimodal_tied_and_modal_strict_conjunction()
{
for &class in ModalityClass::ALL {
let conj = class.is_antimodally_tied() && class.is_strictly_modally_unique();
assert_eq!(
class.is_only_antimodally_tied(),
conj,
"is_only_antimodally_tied must equal the (antimodal-tied, modal-strict) \
conjunction on class {class:?}",
);
}
}
#[test]
fn modality_class_four_corner_predicates_are_pairwise_disjoint() {
for &class in ModalityClass::ALL {
let predicates = [
("is_doubly_strict_unique", class.is_doubly_strict_unique()),
("is_doubly_tied", class.is_doubly_tied()),
("is_only_modally_tied", class.is_only_modally_tied()),
("is_only_antimodally_tied", class.is_only_antimodally_tied()),
];
for (i, (lhs_name, lhs)) in predicates.iter().enumerate() {
for (rhs_name, rhs) in predicates.iter().skip(i + 1) {
assert!(
!(*lhs && *rhs),
"{lhs_name} and {rhs_name} must be disjoint \
(got lhs={lhs}, rhs={rhs}) on class {class:?}",
);
}
}
}
}
#[test]
fn modality_class_four_corner_predicates_partition_non_empty_variants() {
for &class in ModalityClass::ALL {
let fires = u32::from(class.is_doubly_strict_unique())
+ u32::from(class.is_doubly_tied())
+ u32::from(class.is_only_modally_tied())
+ u32::from(class.is_only_antimodally_tied());
let expected = u32::from(!class.is_empty());
assert_eq!(
fires, expected,
"exactly one corner predicate must fire on every non-empty variant \
(got fires={fires}) on class {class:?}",
);
}
}
fn assert_modality_class_empty_is_empty_variant<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let hist = AxisHistogram::<A>::empty();
assert_eq!(
hist.modality_class(),
ModalityClass::Empty,
"empty histogram modality_class must be Empty on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_modality_class_singleton_is_strict_modal_strict_antimodal<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
for observed in axis_iter::<A>() {
let hist: AxisHistogram<A> = std::iter::once(observed).collect();
assert_eq!(
hist.modality_class(),
ModalityClass::StrictModalStrictAntimodal,
"singleton modality_class for observed cell {observed:?} on axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_modality_class_axis_cover_is_tied_modal_tied_antimodal_iff_cardinality_at_least_two<
A,
>()
where
A: ClosedAxis + std::fmt::Debug,
{
let hist: AxisHistogram<A> = axis_iter::<A>().collect();
let expected = if axis_cardinality::<A>() >= 2 {
ModalityClass::TiedModalTiedAntimodal
} else {
ModalityClass::StrictModalStrictAntimodal
};
assert_eq!(
hist.modality_class(),
expected,
"axis-cover modality_class on axis {} (cardinality {})",
std::any::type_name::<A>(),
axis_cardinality::<A>(),
);
}
fn assert_modality_class_equals_open_coded_modality_degree_match<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let shapes: [AxisHistogram<A>; 3] = [
AxisHistogram::<A>::empty(),
std::iter::once(axis_iter::<A>().next().expect("non-empty axis"))
.collect::<AxisHistogram<A>>(),
axis_iter::<A>().collect::<AxisHistogram<A>>(),
];
for hist in &shapes {
let expected = match hist.modality_degree() {
(0, 0) => ModalityClass::Empty,
(1, 1) => ModalityClass::StrictModalStrictAntimodal,
(_, 1) => ModalityClass::TiedModalStrictAntimodal,
(1, _) => ModalityClass::StrictModalTiedAntimodal,
_ => ModalityClass::TiedModalTiedAntimodal,
};
assert_eq!(
hist.modality_class(),
expected,
"modality_class must equal open-coded modality_degree match \
on axis {} with degree {:?}",
std::any::type_name::<A>(),
hist.modality_degree(),
);
}
}
fn assert_modality_class_is_empty_agrees_with_histogram_is_empty<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let empty = AxisHistogram::<A>::empty();
assert_eq!(
empty.modality_class().is_empty(),
empty.is_empty(),
"modality_class.is_empty must equal histogram.is_empty on empty \
histogram for axis {}",
std::any::type_name::<A>(),
);
for observed in axis_iter::<A>() {
let singleton: AxisHistogram<A> = std::iter::once(observed).collect();
assert_eq!(
singleton.modality_class().is_empty(),
singleton.is_empty(),
"modality_class.is_empty must equal histogram.is_empty on \
singleton {observed:?} for axis {}",
std::any::type_name::<A>(),
);
}
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
assert_eq!(
cover.modality_class().is_empty(),
cover.is_empty(),
"modality_class.is_empty must equal histogram.is_empty on uniform \
axis-cover for axis {}",
std::any::type_name::<A>(),
);
}
fn assert_modality_class_is_modally_tied_agrees_with_histogram_is_modally_tied<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let empty = AxisHistogram::<A>::empty();
assert_eq!(
empty.modality_class().is_modally_tied(),
empty.is_modally_tied(),
"modally_tied agreement on empty histogram for axis {}",
std::any::type_name::<A>(),
);
for observed in axis_iter::<A>() {
let singleton: AxisHistogram<A> = std::iter::once(observed).collect();
assert_eq!(
singleton.modality_class().is_modally_tied(),
singleton.is_modally_tied(),
"modally_tied agreement on singleton {observed:?} for axis {}",
std::any::type_name::<A>(),
);
}
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
assert_eq!(
cover.modality_class().is_modally_tied(),
cover.is_modally_tied(),
"modally_tied agreement on uniform axis-cover for axis {}",
std::any::type_name::<A>(),
);
}
fn assert_modality_class_is_antimodally_tied_agrees_with_histogram_is_antimodally_tied<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let empty = AxisHistogram::<A>::empty();
assert_eq!(
empty.modality_class().is_antimodally_tied(),
empty.is_antimodally_tied(),
"antimodally_tied agreement on empty histogram for axis {}",
std::any::type_name::<A>(),
);
for observed in axis_iter::<A>() {
let singleton: AxisHistogram<A> = std::iter::once(observed).collect();
assert_eq!(
singleton.modality_class().is_antimodally_tied(),
singleton.is_antimodally_tied(),
"antimodally_tied agreement on singleton {observed:?} for axis {}",
std::any::type_name::<A>(),
);
}
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
assert_eq!(
cover.modality_class().is_antimodally_tied(),
cover.is_antimodally_tied(),
"antimodally_tied agreement on uniform axis-cover for axis {}",
std::any::type_name::<A>(),
);
}
fn assert_modality_class_is_strictly_modally_unique_agrees_with_histogram_is_strictly_modally_unique<
A,
>()
where
A: ClosedAxis + std::fmt::Debug,
{
let empty = AxisHistogram::<A>::empty();
assert_eq!(
empty.modality_class().is_strictly_modally_unique(),
empty.is_strictly_modally_unique(),
"strictly_modally_unique agreement on empty histogram for axis {}",
std::any::type_name::<A>(),
);
for observed in axis_iter::<A>() {
let singleton: AxisHistogram<A> = std::iter::once(observed).collect();
assert_eq!(
singleton.modality_class().is_strictly_modally_unique(),
singleton.is_strictly_modally_unique(),
"strictly_modally_unique agreement on singleton {observed:?} for axis {}",
std::any::type_name::<A>(),
);
}
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
assert_eq!(
cover.modality_class().is_strictly_modally_unique(),
cover.is_strictly_modally_unique(),
"strictly_modally_unique agreement on uniform axis-cover for axis {}",
std::any::type_name::<A>(),
);
}
fn assert_modality_class_is_strictly_antimodally_unique_agrees_with_histogram_is_strictly_antimodally_unique<
A,
>()
where
A: ClosedAxis + std::fmt::Debug,
{
let empty = AxisHistogram::<A>::empty();
assert_eq!(
empty.modality_class().is_strictly_antimodally_unique(),
empty.is_strictly_antimodally_unique(),
"strictly_antimodally_unique agreement on empty histogram for axis {}",
std::any::type_name::<A>(),
);
for observed in axis_iter::<A>() {
let singleton: AxisHistogram<A> = std::iter::once(observed).collect();
assert_eq!(
singleton.modality_class().is_strictly_antimodally_unique(),
singleton.is_strictly_antimodally_unique(),
"strictly_antimodally_unique agreement on singleton {observed:?} for axis {}",
std::any::type_name::<A>(),
);
}
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
assert_eq!(
cover.modality_class().is_strictly_antimodally_unique(),
cover.is_strictly_antimodally_unique(),
"strictly_antimodally_unique agreement on uniform axis-cover for axis {}",
std::any::type_name::<A>(),
);
}
#[test]
fn axis_histogram_modality_class_empty_is_empty_variant_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_modality_class_empty_is_empty_variant::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_modality_class_singleton_is_strict_modal_strict_antimodal_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_modality_class_singleton_is_strict_modal_strict_antimodal::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_modality_class_axis_cover_is_tied_modal_tied_antimodal_iff_cardinality_at_least_two_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_modality_class_axis_cover_is_tied_modal_tied_antimodal_iff_cardinality_at_least_two::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_modality_class_equals_open_coded_modality_degree_match_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_modality_class_equals_open_coded_modality_degree_match::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_modality_class_is_empty_agrees_with_histogram_is_empty_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_modality_class_is_empty_agrees_with_histogram_is_empty::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_modality_class_is_modally_tied_agrees_with_histogram_is_modally_tied_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_modality_class_is_modally_tied_agrees_with_histogram_is_modally_tied::<$ty>(
);
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_modality_class_is_antimodally_tied_agrees_with_histogram_is_antimodally_tied_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_modality_class_is_antimodally_tied_agrees_with_histogram_is_antimodally_tied::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_modality_class_is_strictly_modally_unique_agrees_with_histogram_is_strictly_modally_unique_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_modality_class_is_strictly_modally_unique_agrees_with_histogram_is_strictly_modally_unique::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_modality_class_is_strictly_antimodally_unique_agrees_with_histogram_is_strictly_antimodally_unique_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_modality_class_is_strictly_antimodally_unique_agrees_with_histogram_is_strictly_antimodally_unique::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_modality_class_classifies_all_five_corners_on_diff_line_kind() {
let empty: AxisHistogram<DiffLineKind> = AxisHistogram::empty();
assert_eq!(empty.modality_class(), ModalityClass::Empty);
let strict_strict: AxisHistogram<DiffLineKind> = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Removed,
DiffLineKind::Context,
]
.iter()
.copied()
.collect();
assert_eq!(strict_strict.modality_degree(), (1, 1));
assert_eq!(
strict_strict.modality_class(),
ModalityClass::StrictModalStrictAntimodal,
);
let tied_strict: AxisHistogram<DiffLineKind> = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Removed,
DiffLineKind::Context,
]
.iter()
.copied()
.collect();
assert_eq!(tied_strict.modality_degree(), (2, 1));
assert_eq!(
tied_strict.modality_class(),
ModalityClass::TiedModalStrictAntimodal,
);
let strict_tied: AxisHistogram<DiffLineKind> = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Context,
]
.iter()
.copied()
.collect();
assert_eq!(strict_tied.modality_degree(), (1, 2));
assert_eq!(
strict_tied.modality_class(),
ModalityClass::StrictModalTiedAntimodal,
);
let tied_tied: AxisHistogram<DiffLineKind> = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Removed,
]
.iter()
.copied()
.collect();
assert_eq!(tied_tied.modality_degree(), (2, 2));
assert_eq!(
tied_tied.modality_class(),
ModalityClass::TiedModalTiedAntimodal,
);
}
#[test]
fn axis_histogram_modality_class_uniform_count_non_empty_lands_in_both_tied_or_both_strict() {
let singleton_multi: AxisHistogram<DiffLineKind> =
[DiffLineKind::Added, DiffLineKind::Added]
.iter()
.copied()
.collect();
assert!(singleton_multi.is_uniform_count());
assert!(singleton_multi.has_singular_support());
assert_eq!(
singleton_multi.modality_class(),
ModalityClass::StrictModalStrictAntimodal,
);
let two_cell_uniform: AxisHistogram<DiffLineKind> = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Removed,
]
.iter()
.copied()
.collect();
assert!(two_cell_uniform.is_uniform_count());
assert!(!two_cell_uniform.has_singular_support());
assert_eq!(
two_cell_uniform.modality_class(),
ModalityClass::TiedModalTiedAntimodal,
);
let cover: AxisHistogram<DiffLineKind> = [
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Context,
]
.into_iter()
.collect();
assert!(cover.is_uniform_count());
assert!(!cover.has_singular_support());
assert_eq!(
cover.modality_class(),
ModalityClass::TiedModalTiedAntimodal
);
}
#[test]
fn axis_histogram_modality_class_total_classification_partitions_every_shape() {
let representatives: [(&[DiffLineKind], ModalityClass); 6] = [
(&[], ModalityClass::Empty),
(
&[DiffLineKind::Added],
ModalityClass::StrictModalStrictAntimodal,
),
(
&[
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Removed,
DiffLineKind::Context,
],
ModalityClass::StrictModalStrictAntimodal,
),
(
&[
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Removed,
DiffLineKind::Context,
],
ModalityClass::TiedModalStrictAntimodal,
),
(
&[
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Context,
],
ModalityClass::StrictModalTiedAntimodal,
),
(
&[
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Removed,
],
ModalityClass::TiedModalTiedAntimodal,
),
];
for (input, expected) in representatives {
let hist: AxisHistogram<DiffLineKind> = input.iter().copied().collect();
let actual = hist.modality_class();
assert_eq!(
actual, expected,
"shape {input:?} must classify as {expected:?}, got {actual:?}",
);
for &other in ModalityClass::ALL {
if other != expected {
assert_ne!(
actual, other,
"shape {input:?} must NOT classify as {other:?}",
);
}
}
}
}
#[test]
fn axis_histogram_modality_class_observation_order_invariant() {
let multiset_a = [
DiffLineKind::Added,
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Context,
];
let multiset_b = [
DiffLineKind::Context,
DiffLineKind::Added,
DiffLineKind::Removed,
DiffLineKind::Added,
];
let hist_a: AxisHistogram<DiffLineKind> = multiset_a.iter().copied().collect();
let hist_b: AxisHistogram<DiffLineKind> = multiset_b.iter().copied().collect();
assert_eq!(hist_a.modality_degree(), hist_b.modality_degree());
assert_eq!(hist_a.modality_class(), hist_b.modality_class());
}
#[test]
fn modality_class_as_str_pins_canonical_kebab_case_labels() {
assert_eq!(ModalityClass::Empty.as_str(), "empty");
assert_eq!(
ModalityClass::StrictModalStrictAntimodal.as_str(),
"strict-modal-strict-antimodal",
);
assert_eq!(
ModalityClass::TiedModalStrictAntimodal.as_str(),
"tied-modal-strict-antimodal",
);
assert_eq!(
ModalityClass::StrictModalTiedAntimodal.as_str(),
"strict-modal-tied-antimodal",
);
assert_eq!(
ModalityClass::TiedModalTiedAntimodal.as_str(),
"tied-modal-tied-antimodal",
);
}
#[test]
fn modality_class_as_str_round_trips_via_from_canonical_str() {
for &v in ModalityClass::ALL {
let rendered = v.as_str();
let parsed = ModalityClass::from_canonical_str(rendered);
assert_eq!(
parsed,
Some(v),
"round-trip failed for {v:?}: as_str={rendered:?} did not parse back",
);
}
}
#[test]
fn modality_class_from_canonical_str_is_case_insensitive() {
for &v in ModalityClass::ALL {
let upper = v.as_str().to_ascii_uppercase();
assert_eq!(
ModalityClass::from_canonical_str(&upper),
Some(v),
"case-insensitive round-trip failed for {v:?}: uppercase {upper:?} did not parse back",
);
let mut mixed = String::with_capacity(upper.len());
for (i, c) in v.as_str().chars().enumerate() {
if i % 2 == 0 {
mixed.extend(c.to_uppercase());
} else {
mixed.push(c);
}
}
assert_eq!(
ModalityClass::from_canonical_str(&mixed),
Some(v),
"mixed-case round-trip failed for {v:?}: {mixed:?} did not parse back",
);
}
}
#[test]
fn modality_class_as_str_labels_pairwise_distinct() {
let labels: Vec<(ModalityClass, &'static str)> = ModalityClass::ALL
.iter()
.copied()
.map(|v| (v, v.as_str()))
.collect();
for (i, (a, label_a)) in labels.iter().enumerate() {
for (b, label_b) in labels.iter().skip(i + 1) {
assert_ne!(
label_a, label_b,
"distinct variants {a:?} and {b:?} must have distinct labels (both {label_a:?})",
);
}
}
}
#[test]
fn modality_class_as_str_labels_nonempty() {
for &v in ModalityClass::ALL {
assert!(
!v.as_str().is_empty(),
"as_str must never return empty for variant {v:?}",
);
}
}
#[test]
fn modality_class_from_canonical_str_rejects_empty_string() {
assert_eq!(ModalityClass::from_canonical_str(""), None);
}
#[test]
fn modality_class_from_canonical_str_rejects_unknown_labels() {
for unknown in [
"StrictModalStrictAntimodal", "strict_modal_strict_antimodal", "strict-modal-antimodal", "tied-modal", "kebab-other-thing", " empty ", "empty\n", "tied-modal-tied-antimodal-x", "x-tied-modal-tied-antimodal", ] {
assert_eq!(
ModalityClass::from_canonical_str(unknown),
None,
"unknown input {unknown:?} must not parse to any variant",
);
}
}
#[test]
fn modality_class_display_delegates_to_as_str() {
for &v in ModalityClass::ALL {
assert_eq!(
format!("{v}"),
v.as_str(),
"Display must render canonical label for {v:?}",
);
}
}
#[test]
fn modality_class_from_str_round_trips_through_display() {
for &v in ModalityClass::ALL {
let rendered = v.to_string();
let parsed: ModalityClass = rendered
.parse()
.expect("Display output must parse via FromStr");
assert_eq!(
parsed, v,
"round-trip via (Display, FromStr) failed for {v:?}",
);
}
}
#[test]
fn modality_class_from_str_is_case_insensitive() {
for &v in ModalityClass::ALL {
let upper = v.as_str().to_ascii_uppercase();
let parsed: ModalityClass = upper
.parse()
.expect("uppercase label must parse via FromStr");
assert_eq!(parsed, v, "uppercase FromStr round-trip failed for {v:?}",);
}
}
#[test]
fn modality_class_from_str_reports_unknown_label_verbatim() {
for unknown in ["totally-unknown", "EmptyKebabViolation"] {
let err = unknown
.parse::<ModalityClass>()
.expect_err("unknown label must error");
assert_eq!(
err.label, unknown,
"ParseModalityClassError must carry offending label verbatim",
);
let rendered = err.to_string();
assert!(
rendered.contains(unknown),
"Display of ParseModalityClassError must mention offending label {unknown:?}, got {rendered:?}",
);
}
}
#[test]
fn modality_class_parse_error_satisfies_std_error_trait() {
let err = "totally-unknown"
.parse::<ModalityClass>()
.expect_err("unknown label must error");
let as_error: &dyn std::error::Error = &err;
assert!(as_error.source().is_none());
assert_eq!(as_error.to_string(), err.to_string());
}
#[test]
fn modality_class_as_str_image_equals_for_each_classifier_corner_set() {
use std::collections::HashSet;
let image: HashSet<&'static str> = ModalityClass::ALL
.iter()
.copied()
.map(ModalityClass::as_str)
.collect();
let expected: HashSet<&'static str> = [
"empty",
"strict-modal-strict-antimodal",
"tied-modal-strict-antimodal",
"strict-modal-tied-antimodal",
"tied-modal-tied-antimodal",
]
.into_iter()
.collect();
assert_eq!(
image, expected,
"as_str image must equal the canonical five-label set",
);
assert_eq!(image.len(), ModalityClass::ALL.len());
}
#[test]
fn modality_class_serde_yaml_round_trips_over_every_variant() {
for &v in ModalityClass::ALL {
let yaml = serde_yaml::to_string(&v)
.unwrap_or_else(|e| panic!("must serialize {v:?} to YAML: {e}"));
let parsed: ModalityClass = serde_yaml::from_str(&yaml).unwrap_or_else(|e| {
panic!("YAML emission for {v:?} must deserialize back: {e}\n yaml: {yaml:?}")
});
assert_eq!(
parsed, v,
"serde YAML round-trip must be identity for {v:?}"
);
}
}
#[test]
fn modality_class_serde_json_round_trips_over_every_variant() {
for &v in ModalityClass::ALL {
let json = serde_json::to_string(&v)
.unwrap_or_else(|e| panic!("must serialize {v:?} to JSON: {e}"));
assert_eq!(
json,
format!("\"{}\"", v.as_str()),
"JSON emission for {v:?} must be the quoted canonical label",
);
let parsed: ModalityClass = serde_json::from_str(&json).unwrap_or_else(|e| {
panic!("JSON emission for {v:?} must deserialize back: {e}\n json: {json}")
});
assert_eq!(
parsed, v,
"serde JSON round-trip must be identity for {v:?}"
);
}
}
#[test]
fn modality_class_serde_yaml_is_case_insensitive() {
for &v in ModalityClass::ALL {
let upper = v.as_str().to_ascii_uppercase();
let yaml = format!("\"{upper}\"\n");
let parsed: ModalityClass = serde_yaml::from_str(&yaml).unwrap_or_else(|e| {
panic!("uppercase YAML scalar for {v:?} must deserialize: {e}\n yaml: {yaml:?}")
});
assert_eq!(
parsed, v,
"uppercase serde YAML round-trip must recover {v:?}",
);
}
}
#[test]
fn modality_class_serde_yaml_unknown_label_error_carries_label_verbatim() {
let sentinel = "__shikumi_unknown_modality_class_sentinel__";
let yaml = format!("\"{sentinel}\"\n");
let result: Result<ModalityClass, _> = serde_yaml::from_str(&yaml);
match result {
Err(e) => {
let rendered = format!("{e}");
assert!(
rendered.contains(sentinel),
"serde YAML error must carry the unknown sentinel verbatim, got: {rendered}",
);
}
Ok(other) => panic!("YAML carrying unknown label must reject, got {other:?}"),
}
}
#[test]
fn modality_class_serde_yaml_emits_canonical_label_substring_for_every_variant() {
for &v in ModalityClass::ALL {
let yaml = serde_yaml::to_string(&v).unwrap();
assert!(
yaml.contains(v.as_str()),
"YAML emission for {v:?} must contain canonical label {:?}, got: {yaml:?}",
v.as_str(),
);
}
}
#[test]
fn support_cardinality_class_all_has_five_entries() {
assert_eq!(SupportCardinalityClass::ALL.len(), 5);
}
#[test]
fn support_cardinality_class_all_entries_are_pairwise_distinct() {
for (i, a) in SupportCardinalityClass::ALL.iter().enumerate() {
for (j, b) in SupportCardinalityClass::ALL.iter().enumerate() {
if i != j {
assert_ne!(a, b, "ALL[{i}] = {a:?} must differ from ALL[{j}] = {b:?}",);
}
}
}
}
#[test]
fn support_cardinality_class_corner_predicates_partition_every_variant() {
for &class in SupportCardinalityClass::ALL {
let fires = u32::from(class.is_empty())
+ u32::from(class.is_singular_support())
+ u32::from(class.is_strict_partial_cover())
+ u32::from(class.is_singular_gap())
+ u32::from(class.is_full_cover());
assert_eq!(
fires, 1,
"exactly one corner predicate must fire on every variant \
(got fires={fires}) on class {class:?}",
);
}
}
#[test]
fn support_cardinality_class_is_partial_cover_fires_on_three_middle_variants() {
assert!(!SupportCardinalityClass::Empty.is_partial_cover());
assert!(SupportCardinalityClass::SingularSupport.is_partial_cover());
assert!(SupportCardinalityClass::StrictPartialCover.is_partial_cover());
assert!(SupportCardinalityClass::SingularGap.is_partial_cover());
assert!(!SupportCardinalityClass::FullCover.is_partial_cover());
}
#[test]
fn support_cardinality_class_is_partial_cover_equals_not_empty_and_not_full_cover() {
for &class in SupportCardinalityClass::ALL {
assert_eq!(
class.is_partial_cover(),
!class.is_empty() && !class.is_full_cover(),
"is_partial_cover must equal the not-empty-and-not-full-cover \
boundary-complement form on {class:?}",
);
}
}
#[test]
fn support_cardinality_class_is_partial_cover_equals_three_middle_variant_disjunction() {
for &class in SupportCardinalityClass::ALL {
assert_eq!(
class.is_partial_cover(),
class.is_singular_support()
|| class.is_strict_partial_cover()
|| class.is_singular_gap(),
"is_partial_cover must equal the three-middle-variant disjunction \
form on {class:?}",
);
}
}
#[test]
fn support_cardinality_class_trichotomy_partitions_every_variant() {
for &class in SupportCardinalityClass::ALL {
let fires = u32::from(class.is_empty())
+ u32::from(class.is_partial_cover())
+ u32::from(class.is_full_cover());
assert_eq!(
fires, 1,
"exactly one of (is_empty, is_partial_cover, is_full_cover) \
must fire on every variant (got fires={fires}) on class {class:?}",
);
}
}
#[test]
fn support_cardinality_class_three_middle_variant_predicates_imply_is_partial_cover() {
for &class in SupportCardinalityClass::ALL {
if class.is_singular_support() {
assert!(
class.is_partial_cover(),
"is_singular_support must imply is_partial_cover on {class:?}",
);
}
if class.is_strict_partial_cover() {
assert!(
class.is_partial_cover(),
"is_strict_partial_cover must imply is_partial_cover on {class:?}",
);
}
if class.is_singular_gap() {
assert!(
class.is_partial_cover(),
"is_singular_gap must imply is_partial_cover on {class:?}",
);
}
}
}
#[test]
fn support_cardinality_class_is_boundary_fires_on_two_corner_variants() {
assert!(SupportCardinalityClass::Empty.is_boundary());
assert!(!SupportCardinalityClass::SingularSupport.is_boundary());
assert!(!SupportCardinalityClass::StrictPartialCover.is_boundary());
assert!(!SupportCardinalityClass::SingularGap.is_boundary());
assert!(SupportCardinalityClass::FullCover.is_boundary());
}
#[test]
fn support_cardinality_class_is_boundary_equals_is_empty_or_is_full_cover() {
for &class in SupportCardinalityClass::ALL {
assert_eq!(
class.is_boundary(),
class.is_empty() || class.is_full_cover(),
"is_boundary must equal the is_empty-or-is_full_cover \
boundary-union form on {class:?}",
);
}
}
#[test]
fn support_cardinality_class_is_boundary_equals_complement_of_is_partial_cover() {
for &class in SupportCardinalityClass::ALL {
assert_eq!(
class.is_boundary(),
!class.is_partial_cover(),
"is_boundary must equal !is_partial_cover on {class:?}",
);
}
}
#[test]
fn support_cardinality_class_is_boundary_and_is_partial_cover_form_strict_bipartition() {
for &class in SupportCardinalityClass::ALL {
let fires = u32::from(class.is_boundary()) + u32::from(class.is_partial_cover());
assert_eq!(
fires, 1,
"exactly one of (is_boundary, is_partial_cover) must fire on every \
variant (got fires={fires}) on class {class:?}",
);
}
}
#[test]
fn support_cardinality_class_two_boundary_variant_predicates_imply_is_boundary() {
for &class in SupportCardinalityClass::ALL {
if class.is_empty() {
assert!(
class.is_boundary(),
"is_empty must imply is_boundary on {class:?}",
);
}
if class.is_full_cover() {
assert!(
class.is_boundary(),
"is_full_cover must imply is_boundary on {class:?}",
);
}
}
}
#[test]
fn support_cardinality_class_is_singular_fires_on_two_near_boundary_variants() {
assert!(!SupportCardinalityClass::Empty.is_singular());
assert!(SupportCardinalityClass::SingularSupport.is_singular());
assert!(!SupportCardinalityClass::StrictPartialCover.is_singular());
assert!(SupportCardinalityClass::SingularGap.is_singular());
assert!(!SupportCardinalityClass::FullCover.is_singular());
}
#[test]
fn support_cardinality_class_is_singular_equals_singular_support_or_singular_gap() {
for &class in SupportCardinalityClass::ALL {
assert_eq!(
class.is_singular(),
class.is_singular_support() || class.is_singular_gap(),
"is_singular must equal is_singular_support || is_singular_gap on {class:?}",
);
}
}
#[test]
fn support_cardinality_class_is_singular_implies_is_partial_cover() {
for &class in SupportCardinalityClass::ALL {
if class.is_singular() {
assert!(
class.is_partial_cover(),
"is_singular must imply is_partial_cover on {class:?}",
);
}
}
}
#[test]
fn support_cardinality_class_is_singular_implies_not_is_boundary() {
for &class in SupportCardinalityClass::ALL {
if class.is_singular() {
assert!(
!class.is_boundary(),
"is_singular must imply !is_boundary on {class:?}",
);
}
}
}
#[test]
fn support_cardinality_class_is_boundary_is_singular_is_strict_partial_cover_form_strict_ternary_partition()
{
for &class in SupportCardinalityClass::ALL {
let fires = u32::from(class.is_boundary())
+ u32::from(class.is_singular())
+ u32::from(class.is_strict_partial_cover());
assert_eq!(
fires, 1,
"exactly one of (is_boundary, is_singular, is_strict_partial_cover) must fire \
on every variant (got fires={fires}) on class {class:?}",
);
}
}
#[test]
fn support_cardinality_class_two_singular_variant_predicates_imply_is_singular() {
for &class in SupportCardinalityClass::ALL {
if class.is_singular_support() {
assert!(
class.is_singular(),
"is_singular_support must imply is_singular on {class:?}",
);
}
if class.is_singular_gap() {
assert!(
class.is_singular(),
"is_singular_gap must imply is_singular on {class:?}",
);
}
}
}
#[test]
fn support_cardinality_class_is_low_support_fires_on_two_low_variants() {
assert!(SupportCardinalityClass::Empty.is_low_support());
assert!(SupportCardinalityClass::SingularSupport.is_low_support());
assert!(!SupportCardinalityClass::StrictPartialCover.is_low_support());
assert!(!SupportCardinalityClass::SingularGap.is_low_support());
assert!(!SupportCardinalityClass::FullCover.is_low_support());
}
#[test]
fn support_cardinality_class_is_high_support_fires_on_two_high_variants() {
assert!(!SupportCardinalityClass::Empty.is_high_support());
assert!(!SupportCardinalityClass::SingularSupport.is_high_support());
assert!(!SupportCardinalityClass::StrictPartialCover.is_high_support());
assert!(SupportCardinalityClass::SingularGap.is_high_support());
assert!(SupportCardinalityClass::FullCover.is_high_support());
}
#[test]
fn support_cardinality_class_is_low_support_equals_empty_or_singular_support() {
for &class in SupportCardinalityClass::ALL {
assert_eq!(
class.is_low_support(),
class.is_empty() || class.is_singular_support(),
"is_low_support must equal is_empty || is_singular_support on {class:?}",
);
}
}
#[test]
fn support_cardinality_class_is_high_support_equals_singular_gap_or_full_cover() {
for &class in SupportCardinalityClass::ALL {
assert_eq!(
class.is_high_support(),
class.is_singular_gap() || class.is_full_cover(),
"is_high_support must equal is_singular_gap || is_full_cover on {class:?}",
);
}
}
#[test]
fn support_cardinality_class_is_low_support_and_is_high_support_are_disjoint() {
for &class in SupportCardinalityClass::ALL {
assert!(
!(class.is_low_support() && class.is_high_support()),
"is_low_support and is_high_support must be disjoint on {class:?}",
);
}
}
#[test]
fn support_cardinality_class_is_low_support_is_strict_partial_cover_is_high_support_form_strict_ternary_partition()
{
for &class in SupportCardinalityClass::ALL {
let fires = u32::from(class.is_low_support())
+ u32::from(class.is_strict_partial_cover())
+ u32::from(class.is_high_support());
assert_eq!(
fires, 1,
"exactly one of (is_low_support, is_strict_partial_cover, is_high_support) must \
fire on every variant (got fires={fires}) on class {class:?}",
);
}
}
#[test]
fn support_cardinality_class_two_low_variant_predicates_imply_is_low_support() {
for &class in SupportCardinalityClass::ALL {
if class.is_empty() {
assert!(
class.is_low_support(),
"is_empty must imply is_low_support on {class:?}",
);
}
if class.is_singular_support() {
assert!(
class.is_low_support(),
"is_singular_support must imply is_low_support on {class:?}",
);
}
}
}
#[test]
fn support_cardinality_class_two_high_variant_predicates_imply_is_high_support() {
for &class in SupportCardinalityClass::ALL {
if class.is_singular_gap() {
assert!(
class.is_high_support(),
"is_singular_gap must imply is_high_support on {class:?}",
);
}
if class.is_full_cover() {
assert!(
class.is_high_support(),
"is_full_cover must imply is_high_support on {class:?}",
);
}
}
}
#[test]
fn support_cardinality_class_is_low_support_decomposes_distance_partition_bottom_corners() {
for &class in SupportCardinalityClass::ALL {
assert_eq!(
class.is_low_support() && class.is_boundary(),
class.is_empty(),
"is_low_support && is_boundary must equal is_empty on {class:?}",
);
assert_eq!(
class.is_low_support() && class.is_singular(),
class.is_singular_support(),
"is_low_support && is_singular must equal is_singular_support on {class:?}",
);
}
}
#[test]
fn support_cardinality_class_is_high_support_decomposes_distance_partition_top_corners() {
for &class in SupportCardinalityClass::ALL {
assert_eq!(
class.is_high_support() && class.is_boundary(),
class.is_full_cover(),
"is_high_support && is_boundary must equal is_full_cover on {class:?}",
);
assert_eq!(
class.is_high_support() && class.is_singular(),
class.is_singular_gap(),
"is_high_support && is_singular must equal is_singular_gap on {class:?}",
);
}
}
#[test]
fn support_cardinality_class_as_str_round_trips_via_from_canonical_str() {
for &v in SupportCardinalityClass::ALL {
assert_eq!(
SupportCardinalityClass::from_canonical_str(v.as_str()),
Some(v),
"as_str / from_canonical_str round-trip for {v:?}",
);
}
}
#[test]
fn support_cardinality_class_as_str_labels_pairwise_distinct() {
for (i, a) in SupportCardinalityClass::ALL.iter().enumerate() {
for (j, b) in SupportCardinalityClass::ALL.iter().enumerate() {
if i != j {
assert_ne!(
a.as_str(),
b.as_str(),
"as_str must distinguish ALL[{i}] = {a:?} from ALL[{j}] = {b:?}",
);
}
}
}
}
#[test]
fn support_cardinality_class_as_str_labels_nonempty() {
for &v in SupportCardinalityClass::ALL {
assert!(
!v.as_str().is_empty(),
"as_str label must be nonempty for {v:?}",
);
}
}
#[test]
fn support_cardinality_class_from_canonical_str_is_case_insensitive() {
for &v in SupportCardinalityClass::ALL {
assert_eq!(
SupportCardinalityClass::from_canonical_str(&v.as_str().to_ascii_uppercase()),
Some(v),
"case-insensitive parse must recover {v:?}",
);
}
}
#[test]
fn support_cardinality_class_from_canonical_str_rejects_empty_string() {
assert_eq!(SupportCardinalityClass::from_canonical_str(""), None);
}
#[test]
fn support_cardinality_class_from_str_round_trips_through_display() {
for &v in SupportCardinalityClass::ALL {
let rendered = v.to_string();
let parsed: SupportCardinalityClass = rendered.parse().unwrap();
assert_eq!(parsed, v, "Display / FromStr round-trip for {v:?}");
}
}
#[test]
fn support_cardinality_class_from_str_rejects_unknown_label_with_label_verbatim() {
let sentinel = "__shikumi_unknown_support_cardinality_class_sentinel__";
match sentinel.parse::<SupportCardinalityClass>() {
Err(e) => {
assert_eq!(e.label, sentinel);
let rendered = format!("{e}");
assert!(
rendered.contains(sentinel),
"Display impl must carry the unknown sentinel verbatim, got: {rendered}",
);
}
Ok(other) => panic!("unknown label must reject, got {other:?}"),
}
}
#[test]
fn support_cardinality_class_serde_yaml_round_trips_over_every_variant() {
for &v in SupportCardinalityClass::ALL {
let yaml = serde_yaml::to_string(&v).unwrap();
let parsed: SupportCardinalityClass = serde_yaml::from_str(&yaml)
.unwrap_or_else(|e| panic!("YAML round-trip for {v:?} failed: {e}"));
assert_eq!(
parsed, v,
"serde YAML round-trip must be identity for {v:?}"
);
}
}
#[test]
fn support_cardinality_class_serde_json_round_trips_over_every_variant() {
for &v in SupportCardinalityClass::ALL {
let json = serde_json::to_string(&v).unwrap();
assert_eq!(
json,
format!("\"{}\"", v.as_str()),
"JSON emission for {v:?} must be the quoted canonical label",
);
let parsed: SupportCardinalityClass = serde_json::from_str(&json).unwrap_or_else(|e| {
panic!("JSON emission for {v:?} must deserialize back: {e}\n json: {json}")
});
assert_eq!(
parsed, v,
"serde JSON round-trip must be identity for {v:?}"
);
}
}
#[test]
fn support_cardinality_class_serde_yaml_is_case_insensitive() {
for &v in SupportCardinalityClass::ALL {
let upper = v.as_str().to_ascii_uppercase();
let yaml = format!("\"{upper}\"\n");
let parsed: SupportCardinalityClass = serde_yaml::from_str(&yaml).unwrap_or_else(|e| {
panic!("uppercase YAML scalar for {v:?} must deserialize: {e}\n yaml: {yaml:?}")
});
assert_eq!(parsed, v);
}
}
#[test]
fn support_cardinality_class_serde_yaml_unknown_label_error_carries_label_verbatim() {
let sentinel = "__shikumi_unknown_support_cardinality_class_sentinel__";
let yaml = format!("\"{sentinel}\"\n");
let result: Result<SupportCardinalityClass, _> = serde_yaml::from_str(&yaml);
match result {
Err(e) => {
let rendered = format!("{e}");
assert!(
rendered.contains(sentinel),
"serde YAML error must carry the unknown sentinel verbatim, got: {rendered}",
);
}
Ok(other) => panic!("YAML carrying unknown label must reject, got {other:?}"),
}
}
fn assert_support_cardinality_class_empty_is_empty_variant<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let hist = AxisHistogram::<A>::empty();
assert_eq!(
hist.support_cardinality_class(),
SupportCardinalityClass::Empty,
"empty support_cardinality_class must be Empty on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_support_cardinality_class_singleton_is_singular_support<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
for observed in axis_iter::<A>() {
let hist: AxisHistogram<A> = std::iter::once(observed).collect();
let class = hist.support_cardinality_class();
let expected = if axis_cardinality::<A>() == 1 {
SupportCardinalityClass::FullCover
} else {
SupportCardinalityClass::SingularSupport
};
assert_eq!(
class,
expected,
"singleton support_cardinality_class for {observed:?} on axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_support_cardinality_class_axis_cover_is_full_cover<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let hist: AxisHistogram<A> = axis_iter::<A>().collect();
assert_eq!(
hist.support_cardinality_class(),
SupportCardinalityClass::FullCover,
"axis-cover support_cardinality_class on axis {}",
std::any::type_name::<A>(),
);
}
fn assert_support_cardinality_class_is_empty_agrees_with_histogram_is_empty<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let empty = AxisHistogram::<A>::empty();
assert_eq!(
empty.support_cardinality_class().is_empty(),
empty.is_empty(),
);
for observed in axis_iter::<A>() {
let singleton: AxisHistogram<A> = std::iter::once(observed).collect();
assert_eq!(
singleton.support_cardinality_class().is_empty(),
singleton.is_empty(),
"support_cardinality_class.is_empty must equal is_empty on \
singleton {observed:?} for axis {}",
std::any::type_name::<A>(),
);
}
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
assert_eq!(
cover.support_cardinality_class().is_empty(),
cover.is_empty(),
"support_cardinality_class.is_empty must equal is_empty on \
axis-cover for axis {}",
std::any::type_name::<A>(),
);
}
fn assert_support_cardinality_class_is_full_cover_agrees_with_histogram_is_full_cover<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let empty = AxisHistogram::<A>::empty();
assert_eq!(
empty.support_cardinality_class().is_full_cover(),
empty.is_full_cover(),
);
for observed in axis_iter::<A>() {
let singleton: AxisHistogram<A> = std::iter::once(observed).collect();
assert_eq!(
singleton.support_cardinality_class().is_full_cover(),
singleton.is_full_cover(),
"support_cardinality_class.is_full_cover must equal is_full_cover on \
singleton {observed:?} for axis {}",
std::any::type_name::<A>(),
);
}
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
assert_eq!(
cover.support_cardinality_class().is_full_cover(),
cover.is_full_cover(),
"support_cardinality_class.is_full_cover must equal is_full_cover on \
axis-cover for axis {}",
std::any::type_name::<A>(),
);
}
fn assert_support_cardinality_class_is_boundary_agrees_with_histogram_is_empty_or_is_full_cover<
A,
>()
where
A: ClosedAxis + std::fmt::Debug,
{
let empty = AxisHistogram::<A>::empty();
assert_eq!(
empty.support_cardinality_class().is_boundary(),
empty.is_empty() || empty.is_full_cover(),
"support_cardinality_class.is_boundary must equal is_empty || is_full_cover on \
empty for axis {}",
std::any::type_name::<A>(),
);
for observed in axis_iter::<A>() {
let singleton: AxisHistogram<A> = std::iter::once(observed).collect();
assert_eq!(
singleton.support_cardinality_class().is_boundary(),
singleton.is_empty() || singleton.is_full_cover(),
"support_cardinality_class.is_boundary must equal is_empty || is_full_cover on \
singleton {observed:?} for axis {}",
std::any::type_name::<A>(),
);
}
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
assert_eq!(
cover.support_cardinality_class().is_boundary(),
cover.is_empty() || cover.is_full_cover(),
"support_cardinality_class.is_boundary must equal is_empty || is_full_cover on \
axis-cover for axis {}",
std::any::type_name::<A>(),
);
}
fn assert_support_cardinality_class_is_singular_agrees_with_histogram_has_singular_support_or_has_singular_gap<
A,
>()
where
A: ClosedAxis + std::fmt::Debug,
{
let empty = AxisHistogram::<A>::empty();
assert_eq!(
empty.support_cardinality_class().is_singular(),
empty.has_singular_support() || empty.has_singular_gap(),
"support_cardinality_class.is_singular must equal \
has_singular_support || has_singular_gap on empty for axis {}",
std::any::type_name::<A>(),
);
for observed in axis_iter::<A>() {
let singleton: AxisHistogram<A> = std::iter::once(observed).collect();
assert_eq!(
singleton.support_cardinality_class().is_singular(),
singleton.has_singular_support() || singleton.has_singular_gap(),
"support_cardinality_class.is_singular must equal \
has_singular_support || has_singular_gap on singleton {observed:?} for axis {}",
std::any::type_name::<A>(),
);
}
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
assert_eq!(
cover.support_cardinality_class().is_singular(),
cover.has_singular_support() || cover.has_singular_gap(),
"support_cardinality_class.is_singular must equal \
has_singular_support || has_singular_gap on axis-cover for axis {}",
std::any::type_name::<A>(),
);
}
fn assert_support_cardinality_class_is_low_support_agrees_with_histogram_is_empty_or_has_singular_support<
A,
>()
where
A: ClosedAxis + std::fmt::Debug,
{
let empty = AxisHistogram::<A>::empty();
assert_eq!(
empty.support_cardinality_class().is_low_support(),
empty.is_empty() || empty.has_singular_support(),
"support_cardinality_class.is_low_support must equal \
is_empty || has_singular_support on empty for axis {}",
std::any::type_name::<A>(),
);
for observed in axis_iter::<A>() {
let singleton: AxisHistogram<A> = std::iter::once(observed).collect();
assert_eq!(
singleton.support_cardinality_class().is_low_support(),
singleton.is_empty() || singleton.has_singular_support(),
"support_cardinality_class.is_low_support must equal \
is_empty || has_singular_support on singleton {observed:?} for axis {}",
std::any::type_name::<A>(),
);
}
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
assert_eq!(
cover.support_cardinality_class().is_low_support(),
cover.is_empty() || cover.has_singular_support(),
"support_cardinality_class.is_low_support must equal \
is_empty || has_singular_support on axis-cover for axis {}",
std::any::type_name::<A>(),
);
}
fn assert_support_cardinality_class_is_high_support_agrees_with_histogram_is_full_cover_or_strict_singular_gap<
A,
>()
where
A: ClosedAxis + std::fmt::Debug,
{
let empty = AxisHistogram::<A>::empty();
assert_eq!(
empty.support_cardinality_class().is_high_support(),
empty.is_full_cover() || (empty.has_singular_gap() && !empty.has_singular_support()),
"support_cardinality_class.is_high_support must equal \
is_full_cover || (has_singular_gap && !has_singular_support) on empty for axis {}",
std::any::type_name::<A>(),
);
for observed in axis_iter::<A>() {
let singleton: AxisHistogram<A> = std::iter::once(observed).collect();
assert_eq!(
singleton.support_cardinality_class().is_high_support(),
singleton.is_full_cover()
|| (singleton.has_singular_gap() && !singleton.has_singular_support()),
"support_cardinality_class.is_high_support must equal \
is_full_cover || (has_singular_gap && !has_singular_support) on singleton \
{observed:?} for axis {}",
std::any::type_name::<A>(),
);
}
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
assert_eq!(
cover.support_cardinality_class().is_high_support(),
cover.is_full_cover() || (cover.has_singular_gap() && !cover.has_singular_support()),
"support_cardinality_class.is_high_support must equal \
is_full_cover || (has_singular_gap && !has_singular_support) on axis-cover for axis {}",
std::any::type_name::<A>(),
);
}
fn assert_support_cardinality_class_is_partial_cover_agrees_with_histogram_has_partial_cover<
A,
>()
where
A: ClosedAxis + std::fmt::Debug,
{
let empty = AxisHistogram::<A>::empty();
assert_eq!(
empty.support_cardinality_class().is_partial_cover(),
empty.has_partial_cover(),
"support_cardinality_class.is_partial_cover must equal has_partial_cover on \
empty for axis {}",
std::any::type_name::<A>(),
);
for observed in axis_iter::<A>() {
let singleton: AxisHistogram<A> = std::iter::once(observed).collect();
assert_eq!(
singleton.support_cardinality_class().is_partial_cover(),
singleton.has_partial_cover(),
"support_cardinality_class.is_partial_cover must equal has_partial_cover on \
singleton {observed:?} for axis {}",
std::any::type_name::<A>(),
);
}
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
assert_eq!(
cover.support_cardinality_class().is_partial_cover(),
cover.has_partial_cover(),
"support_cardinality_class.is_partial_cover must equal has_partial_cover on \
axis-cover for axis {}",
std::any::type_name::<A>(),
);
}
#[test]
fn axis_histogram_support_cardinality_class_empty_is_empty_variant_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_support_cardinality_class_empty_is_empty_variant::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_support_cardinality_class_singleton_is_singular_support_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_support_cardinality_class_singleton_is_singular_support::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_support_cardinality_class_axis_cover_is_full_cover_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_support_cardinality_class_axis_cover_is_full_cover::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_support_cardinality_class_is_empty_agrees_with_histogram_is_empty_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_support_cardinality_class_is_empty_agrees_with_histogram_is_empty::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_support_cardinality_class_is_full_cover_agrees_with_histogram_is_full_cover_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_support_cardinality_class_is_full_cover_agrees_with_histogram_is_full_cover::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_support_cardinality_class_is_partial_cover_agrees_with_histogram_has_partial_cover_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_support_cardinality_class_is_partial_cover_agrees_with_histogram_has_partial_cover::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_support_cardinality_class_is_boundary_agrees_with_histogram_is_empty_or_is_full_cover_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_support_cardinality_class_is_boundary_agrees_with_histogram_is_empty_or_is_full_cover::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_support_cardinality_class_is_singular_agrees_with_histogram_has_singular_support_or_has_singular_gap_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_support_cardinality_class_is_singular_agrees_with_histogram_has_singular_support_or_has_singular_gap::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_support_cardinality_class_is_low_support_agrees_with_histogram_is_empty_or_has_singular_support_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_support_cardinality_class_is_low_support_agrees_with_histogram_is_empty_or_has_singular_support::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_support_cardinality_class_is_high_support_agrees_with_histogram_is_full_cover_or_strict_singular_gap_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_support_cardinality_class_is_high_support_agrees_with_histogram_is_full_cover_or_strict_singular_gap::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
fn assert_has_low_support_agrees_with_class_is_low_support<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let empty = AxisHistogram::<A>::empty();
assert_eq!(
empty.has_low_support(),
empty.support_cardinality_class().is_low_support(),
"has_low_support must equal support_cardinality_class().is_low_support() \
on empty for axis {}",
std::any::type_name::<A>(),
);
for observed in axis_iter::<A>() {
let singleton: AxisHistogram<A> = std::iter::once(observed).collect();
assert_eq!(
singleton.has_low_support(),
singleton.support_cardinality_class().is_low_support(),
"has_low_support must equal support_cardinality_class().is_low_support() \
on singleton {observed:?} for axis {}",
std::any::type_name::<A>(),
);
}
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
assert_eq!(
cover.has_low_support(),
cover.support_cardinality_class().is_low_support(),
"has_low_support must equal support_cardinality_class().is_low_support() \
on axis-cover for axis {}",
std::any::type_name::<A>(),
);
}
fn assert_has_high_support_agrees_with_class_is_high_support<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let empty = AxisHistogram::<A>::empty();
assert_eq!(
empty.has_high_support(),
empty.support_cardinality_class().is_high_support(),
"has_high_support must equal support_cardinality_class().is_high_support() \
on empty for axis {}",
std::any::type_name::<A>(),
);
for observed in axis_iter::<A>() {
let singleton: AxisHistogram<A> = std::iter::once(observed).collect();
assert_eq!(
singleton.has_high_support(),
singleton.support_cardinality_class().is_high_support(),
"has_high_support must equal support_cardinality_class().is_high_support() \
on singleton {observed:?} for axis {}",
std::any::type_name::<A>(),
);
}
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
assert_eq!(
cover.has_high_support(),
cover.support_cardinality_class().is_high_support(),
"has_high_support must equal support_cardinality_class().is_high_support() \
on axis-cover for axis {}",
std::any::type_name::<A>(),
);
}
fn assert_has_low_support_and_has_high_support_are_disjoint<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let empty = AxisHistogram::<A>::empty();
assert!(
!(empty.has_low_support() && empty.has_high_support()),
"has_low_support and has_high_support must be disjoint \
on empty for axis {}",
std::any::type_name::<A>(),
);
for observed in axis_iter::<A>() {
let singleton: AxisHistogram<A> = std::iter::once(observed).collect();
assert!(
!(singleton.has_low_support() && singleton.has_high_support()),
"has_low_support and has_high_support must be disjoint \
on singleton {observed:?} for axis {}",
std::any::type_name::<A>(),
);
}
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
assert!(
!(cover.has_low_support() && cover.has_high_support()),
"has_low_support and has_high_support must be disjoint \
on axis-cover for axis {}",
std::any::type_name::<A>(),
);
}
fn assert_has_low_support_has_strict_partial_cover_has_high_support_form_strict_ternary_partition<
A,
>()
where
A: ClosedAxis + std::fmt::Debug,
{
let check = |hist: &AxisHistogram<A>, label: &str| {
let sum = u8::from(hist.has_low_support())
+ u8::from(hist.has_strict_partial_cover())
+ u8::from(hist.has_high_support());
assert_eq!(
sum,
1,
"(has_low_support, has_strict_partial_cover, has_high_support) must \
sum to 1 on {label} for axis {} (got {sum})",
std::any::type_name::<A>(),
);
};
check(&AxisHistogram::<A>::empty(), "empty");
for observed in axis_iter::<A>() {
let singleton: AxisHistogram<A> = std::iter::once(observed).collect();
let label = format!("singleton {observed:?}");
check(&singleton, &label);
}
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
check(&cover, "axis-cover");
}
fn assert_two_low_boundary_predicates_imply_has_low_support<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let empty = AxisHistogram::<A>::empty();
if empty.is_empty() {
assert!(
empty.has_low_support(),
"is_empty must imply has_low_support on empty for axis {}",
std::any::type_name::<A>(),
);
}
for observed in axis_iter::<A>() {
let singleton: AxisHistogram<A> = std::iter::once(observed).collect();
if singleton.has_singular_support() {
assert!(
singleton.has_low_support(),
"has_singular_support must imply has_low_support on singleton \
{observed:?} for axis {}",
std::any::type_name::<A>(),
);
}
}
}
fn assert_full_cover_implies_has_high_support<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
if cover.is_full_cover() {
assert!(
cover.has_high_support(),
"is_full_cover must imply has_high_support on axis-cover for axis {}",
std::any::type_name::<A>(),
);
}
}
#[test]
fn axis_histogram_has_low_support_agrees_with_class_is_low_support_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_has_low_support_agrees_with_class_is_low_support::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_has_high_support_agrees_with_class_is_high_support_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_has_high_support_agrees_with_class_is_high_support::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_has_low_support_and_has_high_support_are_disjoint_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_has_low_support_and_has_high_support_are_disjoint::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_has_low_support_has_strict_partial_cover_has_high_support_form_strict_ternary_partition_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_has_low_support_has_strict_partial_cover_has_high_support_form_strict_ternary_partition::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_two_low_boundary_predicates_imply_has_low_support_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_two_low_boundary_predicates_imply_has_low_support::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_full_cover_implies_has_high_support_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_full_cover_implies_has_high_support::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
fn assert_has_boundary_agrees_with_class_is_boundary<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let empty = AxisHistogram::<A>::empty();
assert_eq!(
empty.has_boundary(),
empty.support_cardinality_class().is_boundary(),
"has_boundary must equal support_cardinality_class().is_boundary() \
on empty for axis {}",
std::any::type_name::<A>(),
);
for observed in axis_iter::<A>() {
let singleton: AxisHistogram<A> = std::iter::once(observed).collect();
assert_eq!(
singleton.has_boundary(),
singleton.support_cardinality_class().is_boundary(),
"has_boundary must equal support_cardinality_class().is_boundary() \
on singleton {observed:?} for axis {}",
std::any::type_name::<A>(),
);
}
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
assert_eq!(
cover.has_boundary(),
cover.support_cardinality_class().is_boundary(),
"has_boundary must equal support_cardinality_class().is_boundary() \
on axis-cover for axis {}",
std::any::type_name::<A>(),
);
}
fn assert_has_boundary_and_has_partial_cover_form_strict_bipartition<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let check = |hist: &AxisHistogram<A>, label: &str| {
let sum = u8::from(hist.has_boundary()) + u8::from(hist.has_partial_cover());
assert_eq!(
sum,
1,
"(has_boundary, has_partial_cover) must sum to 1 on {label} for axis {} \
(got {sum})",
std::any::type_name::<A>(),
);
};
check(&AxisHistogram::<A>::empty(), "empty");
for observed in axis_iter::<A>() {
let singleton: AxisHistogram<A> = std::iter::once(observed).collect();
let label = format!("singleton {observed:?}");
check(&singleton, &label);
}
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
check(&cover, "axis-cover");
}
fn assert_two_coverage_boundary_predicates_imply_has_boundary<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let empty = AxisHistogram::<A>::empty();
if empty.is_empty() {
assert!(
empty.has_boundary(),
"is_empty must imply has_boundary on empty for axis {}",
std::any::type_name::<A>(),
);
}
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
if cover.is_full_cover() {
assert!(
cover.has_boundary(),
"is_full_cover must imply has_boundary on axis-cover for axis {}",
std::any::type_name::<A>(),
);
}
}
fn assert_has_singular_agrees_with_class_is_singular<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let empty = AxisHistogram::<A>::empty();
assert_eq!(
empty.has_singular(),
empty.support_cardinality_class().is_singular(),
"has_singular must equal support_cardinality_class().is_singular() \
on empty for axis {}",
std::any::type_name::<A>(),
);
for observed in axis_iter::<A>() {
let singleton: AxisHistogram<A> = std::iter::once(observed).collect();
assert_eq!(
singleton.has_singular(),
singleton.support_cardinality_class().is_singular(),
"has_singular must equal support_cardinality_class().is_singular() \
on singleton {observed:?} for axis {}",
std::any::type_name::<A>(),
);
}
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
assert_eq!(
cover.has_singular(),
cover.support_cardinality_class().is_singular(),
"has_singular must equal support_cardinality_class().is_singular() \
on axis-cover for axis {}",
std::any::type_name::<A>(),
);
}
fn assert_has_boundary_has_singular_has_strict_partial_cover_form_strict_ternary_partition<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let check = |hist: &AxisHistogram<A>, label: &str| {
let sum = u8::from(hist.has_boundary())
+ u8::from(hist.has_singular())
+ u8::from(hist.has_strict_partial_cover());
assert_eq!(
sum,
1,
"(has_boundary, has_singular, has_strict_partial_cover) must sum to 1 on \
{label} for axis {} (got {sum})",
std::any::type_name::<A>(),
);
};
check(&AxisHistogram::<A>::empty(), "empty");
for observed in axis_iter::<A>() {
let singleton: AxisHistogram<A> = std::iter::once(observed).collect();
let label = format!("singleton {observed:?}");
check(&singleton, &label);
}
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
check(&cover, "axis-cover");
}
fn assert_two_singular_boundary_predicates_imply_has_singular<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let check = |hist: &AxisHistogram<A>, label: &str| {
if hist.has_singular_support() {
assert!(
hist.has_singular(),
"has_singular_support must imply has_singular on {label} for axis {}",
std::any::type_name::<A>(),
);
}
if hist.has_singular_gap() {
assert!(
hist.has_singular(),
"has_singular_gap must imply has_singular on {label} for axis {}",
std::any::type_name::<A>(),
);
}
};
check(&AxisHistogram::<A>::empty(), "empty");
for observed in axis_iter::<A>() {
let singleton: AxisHistogram<A> = std::iter::once(observed).collect();
let label = format!("singleton {observed:?}");
check(&singleton, &label);
}
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
check(&cover, "axis-cover");
}
fn assert_has_singular_implies_has_partial_cover<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let check = |hist: &AxisHistogram<A>, label: &str| {
if hist.has_singular() {
assert!(
hist.has_partial_cover(),
"has_singular must imply has_partial_cover on {label} for axis {}",
std::any::type_name::<A>(),
);
}
};
check(&AxisHistogram::<A>::empty(), "empty");
for observed in axis_iter::<A>() {
let singleton: AxisHistogram<A> = std::iter::once(observed).collect();
let label = format!("singleton {observed:?}");
check(&singleton, &label);
}
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
check(&cover, "axis-cover");
}
fn assert_has_singular_implies_not_has_boundary<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let check = |hist: &AxisHistogram<A>, label: &str| {
if hist.has_singular() {
assert!(
!hist.has_boundary(),
"has_singular must imply !has_boundary on {label} for axis {}",
std::any::type_name::<A>(),
);
}
};
check(&AxisHistogram::<A>::empty(), "empty");
for observed in axis_iter::<A>() {
let singleton: AxisHistogram<A> = std::iter::once(observed).collect();
let label = format!("singleton {observed:?}");
check(&singleton, &label);
}
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
check(&cover, "axis-cover");
}
#[test]
fn axis_histogram_has_boundary_agrees_with_class_is_boundary_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_has_boundary_agrees_with_class_is_boundary::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_has_boundary_and_has_partial_cover_form_strict_bipartition_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_has_boundary_and_has_partial_cover_form_strict_bipartition::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_two_coverage_boundary_predicates_imply_has_boundary_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_two_coverage_boundary_predicates_imply_has_boundary::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_has_boundary_equals_is_empty_or_is_full_cover() {
type A = ShikumiErrorKind;
let cells: Vec<A> = axis_iter::<A>().collect();
let inputs: Vec<Vec<A>> = vec![
vec![],
vec![cells[0]],
vec![cells[0], cells[1]],
vec![cells[0], cells[1], cells[2]],
vec![cells[0], cells[1], cells[2], cells[3]],
vec![cells[0], cells[1], cells[2], cells[3], cells[4]],
cells.clone(),
];
for input in &inputs {
let hist: AxisHistogram<A> = input.iter().copied().collect();
assert_eq!(
hist.has_boundary(),
hist.is_empty() || hist.is_full_cover(),
"has_boundary must equal (is_empty || is_full_cover) on input of length {}",
input.len(),
);
}
}
#[test]
fn axis_histogram_has_boundary_equals_complement_of_has_partial_cover() {
type A = ShikumiErrorKind;
let cells: Vec<A> = axis_iter::<A>().collect();
let inputs: Vec<Vec<A>> = vec![
vec![],
vec![cells[0]],
vec![cells[0], cells[1]],
vec![cells[0], cells[1], cells[2]],
vec![cells[0], cells[1], cells[2], cells[3]],
vec![cells[0], cells[1], cells[2], cells[3], cells[4]],
cells.clone(),
];
for input in &inputs {
let hist: AxisHistogram<A> = input.iter().copied().collect();
assert_eq!(
hist.has_boundary(),
!hist.has_partial_cover(),
"has_boundary must equal !has_partial_cover on input of length {}",
input.len(),
);
}
}
#[test]
fn axis_histogram_has_singular_agrees_with_class_is_singular_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_has_singular_agrees_with_class_is_singular::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_has_boundary_has_singular_has_strict_partial_cover_form_strict_ternary_partition_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_has_boundary_has_singular_has_strict_partial_cover_form_strict_ternary_partition::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_two_singular_boundary_predicates_imply_has_singular_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_two_singular_boundary_predicates_imply_has_singular::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_has_singular_implies_has_partial_cover_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_has_singular_implies_has_partial_cover::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_has_singular_implies_not_has_boundary_for_every_closed_axis_implementor() {
macro_rules! check {
($ty:ident) => {
assert_has_singular_implies_not_has_boundary::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_has_singular_equals_has_singular_support_or_has_singular_gap() {
type A = ShikumiErrorKind;
let cells: Vec<A> = axis_iter::<A>().collect();
let inputs: Vec<Vec<A>> = vec![
vec![],
vec![cells[0]],
vec![cells[0], cells[1]],
vec![cells[0], cells[1], cells[2]],
vec![cells[0], cells[1], cells[2], cells[3]],
vec![cells[0], cells[1], cells[2], cells[3], cells[4]],
cells.clone(),
];
for input in &inputs {
let hist: AxisHistogram<A> = input.iter().copied().collect();
assert_eq!(
hist.has_singular(),
hist.has_singular_support() || hist.has_singular_gap(),
"has_singular must equal (has_singular_support || has_singular_gap) on input \
of length {}",
input.len(),
);
}
}
#[test]
fn axis_histogram_has_singular_equals_has_partial_cover_and_not_has_strict_partial_cover() {
type A = ShikumiErrorKind;
let cells: Vec<A> = axis_iter::<A>().collect();
let inputs: Vec<Vec<A>> = vec![
vec![],
vec![cells[0]],
vec![cells[0], cells[1]],
vec![cells[0], cells[1], cells[2]],
vec![cells[0], cells[1], cells[2], cells[3]],
vec![cells[0], cells[1], cells[2], cells[3], cells[4]],
cells.clone(),
];
for input in &inputs {
let hist: AxisHistogram<A> = input.iter().copied().collect();
assert_eq!(
hist.has_singular(),
hist.has_partial_cover() && !hist.has_strict_partial_cover(),
"has_singular must equal (has_partial_cover && !has_strict_partial_cover) \
on input of length {}",
input.len(),
);
}
}
#[test]
fn axis_histogram_has_low_support_equals_is_empty_or_has_singular_support() {
type A = ShikumiErrorKind;
let cells: Vec<A> = axis_iter::<A>().collect();
let inputs: Vec<Vec<A>> = vec![
vec![],
vec![cells[0]],
vec![cells[0], cells[1]],
vec![cells[0], cells[1], cells[2]],
vec![cells[0], cells[1], cells[2], cells[3]],
vec![cells[0], cells[1], cells[2], cells[3], cells[4]],
cells.clone(),
];
for input in &inputs {
let hist: AxisHistogram<A> = input.iter().copied().collect();
assert_eq!(
hist.has_low_support(),
hist.is_empty() || hist.has_singular_support(),
"has_low_support must equal (is_empty || has_singular_support) on input of \
length {}",
input.len(),
);
}
}
#[test]
fn axis_histogram_has_low_support_equals_distinct_cells_at_most_one() {
type A = ShikumiErrorKind;
let cells: Vec<A> = axis_iter::<A>().collect();
let inputs: Vec<Vec<A>> = vec![
vec![],
vec![cells[0]],
vec![cells[0], cells[1]],
vec![cells[0], cells[1], cells[2]],
vec![cells[0], cells[1], cells[2], cells[3]],
vec![cells[0], cells[1], cells[2], cells[3], cells[4]],
cells.clone(),
];
for input in &inputs {
let hist: AxisHistogram<A> = input.iter().copied().collect();
assert_eq!(
hist.has_low_support(),
hist.distinct_cells() <= 1,
"has_low_support must equal (distinct_cells <= 1) on input of length {}; \
distinct={}",
input.len(),
hist.distinct_cells(),
);
}
}
#[test]
fn axis_histogram_has_high_support_equals_full_cover_or_strict_singular_gap() {
type A = ShikumiErrorKind;
let cells: Vec<A> = axis_iter::<A>().collect();
let inputs: Vec<Vec<A>> = vec![
vec![],
vec![cells[0]],
vec![cells[0], cells[1]],
vec![cells[0], cells[1], cells[2]],
vec![cells[0], cells[1], cells[2], cells[3]],
vec![cells[0], cells[1], cells[2], cells[3], cells[4]],
cells.clone(),
];
for input in &inputs {
let hist: AxisHistogram<A> = input.iter().copied().collect();
assert_eq!(
hist.has_high_support(),
hist.is_full_cover() || (hist.has_singular_gap() && !hist.has_singular_support()),
"has_high_support must equal \
(is_full_cover || (has_singular_gap && !has_singular_support)) \
on input of length {}",
input.len(),
);
}
}
#[test]
fn axis_histogram_has_high_support_on_cardinality_two_axis_excludes_singleton() {
type A = PartitionFace;
for observed in axis_iter::<A>() {
let singleton: AxisHistogram<A> = std::iter::once(observed).collect();
assert!(
singleton.has_singular_support(),
"cardinality-2 singleton must fire has_singular_support",
);
assert!(
singleton.has_singular_gap(),
"cardinality-2 singleton must fire has_singular_gap (dual-singular collapse)",
);
assert!(
singleton.has_low_support(),
"cardinality-2 singleton must land on has_low_support",
);
assert!(
!singleton.has_high_support(),
"cardinality-2 singleton must NOT fire has_high_support \
(the strict-singular-gap clause excises the collapse)",
);
}
}
#[test]
fn axis_histogram_has_high_support_on_cardinality_three_axis_fires_on_singular_gap() {
type A = DiffLineKind;
let cells: Vec<A> = axis_iter::<A>().collect();
let support_two: AxisHistogram<A> = [cells[0], cells[1]].iter().copied().collect();
assert!(
!support_two.has_singular_support(),
"support-2 on cardinality 3 must NOT fire has_singular_support",
);
assert!(
support_two.has_singular_gap(),
"support-2 on cardinality 3 must fire has_singular_gap",
);
assert!(
!support_two.has_low_support(),
"support-2 on cardinality 3 must NOT fire has_low_support",
);
assert!(
support_two.has_high_support(),
"support-2 on cardinality 3 must fire has_high_support",
);
}
#[test]
fn axis_histogram_support_cardinality_class_classifies_all_five_corners_on_shikumi_error_kind()
{
type A = ShikumiErrorKind;
let empty: AxisHistogram<A> = AxisHistogram::empty();
assert_eq!(
empty.support_cardinality_class(),
SupportCardinalityClass::Empty,
);
let cells: Vec<A> = axis_iter::<A>().collect();
assert!(cells.len() >= 5, "test requires a cardinality-5+ axis");
let singleton: AxisHistogram<A> = std::iter::once(cells[0]).collect();
assert_eq!(
singleton.support_cardinality_class(),
SupportCardinalityClass::SingularSupport,
);
let strict: AxisHistogram<A> = [cells[0], cells[1]].iter().copied().collect();
assert_eq!(
strict.support_cardinality_class(),
SupportCardinalityClass::StrictPartialCover,
);
let singular_gap: AxisHistogram<A> = cells[..cells.len() - 1].iter().copied().collect();
assert_eq!(
singular_gap.support_cardinality_class(),
SupportCardinalityClass::SingularGap,
);
let full: AxisHistogram<A> = cells.iter().copied().collect();
assert_eq!(
full.support_cardinality_class(),
SupportCardinalityClass::FullCover,
);
}
#[test]
fn axis_histogram_support_cardinality_class_equals_open_coded_branch_match_on_shikumi_error_kind()
{
type A = ShikumiErrorKind;
let cells: Vec<A> = axis_iter::<A>().collect();
let n = cells.len();
for s in 0..=n {
let hist: AxisHistogram<A> = cells[..s].iter().copied().collect();
let class = hist.support_cardinality_class();
let expected = if hist.is_empty() {
SupportCardinalityClass::Empty
} else if hist.is_full_cover() {
SupportCardinalityClass::FullCover
} else if hist.has_singular_support() {
SupportCardinalityClass::SingularSupport
} else if hist.has_singular_gap() {
SupportCardinalityClass::SingularGap
} else {
SupportCardinalityClass::StrictPartialCover
};
assert_eq!(
class,
expected,
"support_cardinality_class disagrees with open-coded ladder \
at support {s} on axis {}",
std::any::type_name::<A>(),
);
}
}
#[test]
fn axis_histogram_support_cardinality_class_cardinality_two_singleton_lands_on_singular_support()
{
for face in axis_iter::<PartitionFace>() {
let hist: AxisHistogram<PartitionFace> = std::iter::once(face).collect();
assert!(hist.has_singular_support());
assert!(hist.has_singular_gap());
assert_eq!(
hist.support_cardinality_class(),
SupportCardinalityClass::SingularSupport,
);
}
for shape in axis_iter::<SecretRefShape>() {
let hist: AxisHistogram<SecretRefShape> = std::iter::once(shape).collect();
assert!(hist.has_singular_support());
assert!(hist.has_singular_gap());
assert_eq!(
hist.support_cardinality_class(),
SupportCardinalityClass::SingularSupport,
);
}
}
#[test]
fn support_boundary_distance_all_has_three_entries() {
assert_eq!(SupportBoundaryDistance::ALL.len(), 3);
}
#[test]
fn support_boundary_distance_all_entries_are_pairwise_distinct() {
for (i, a) in SupportBoundaryDistance::ALL.iter().enumerate() {
for (j, b) in SupportBoundaryDistance::ALL.iter().enumerate() {
if i != j {
assert_ne!(a, b, "ALL[{i}] = {a:?} must differ from ALL[{j}] = {b:?}",);
}
}
}
}
#[test]
fn support_boundary_distance_corner_predicates_partition_every_variant() {
for &bucket in SupportBoundaryDistance::ALL {
let fires = u32::from(bucket.is_boundary())
+ u32::from(bucket.is_singular())
+ u32::from(bucket.is_strict_interior());
assert_eq!(
fires, 1,
"exactly one bucket predicate must fire on every variant \
(got fires={fires}) on bucket {bucket:?}",
);
}
}
#[test]
fn support_boundary_distance_is_boundary_fires_exactly_on_boundary_variant() {
assert!(SupportBoundaryDistance::Boundary.is_boundary());
assert!(!SupportBoundaryDistance::Singular.is_boundary());
assert!(!SupportBoundaryDistance::StrictInterior.is_boundary());
}
#[test]
fn support_boundary_distance_is_singular_fires_exactly_on_singular_variant() {
assert!(!SupportBoundaryDistance::Boundary.is_singular());
assert!(SupportBoundaryDistance::Singular.is_singular());
assert!(!SupportBoundaryDistance::StrictInterior.is_singular());
}
#[test]
fn support_boundary_distance_is_strict_interior_fires_exactly_on_strict_interior_variant() {
assert!(!SupportBoundaryDistance::Boundary.is_strict_interior());
assert!(!SupportBoundaryDistance::Singular.is_strict_interior());
assert!(SupportBoundaryDistance::StrictInterior.is_strict_interior());
}
#[test]
fn support_boundary_distance_as_str_round_trips_via_from_canonical_str() {
for &v in SupportBoundaryDistance::ALL {
assert_eq!(
SupportBoundaryDistance::from_canonical_str(v.as_str()),
Some(v),
"as_str / from_canonical_str round-trip for {v:?}",
);
}
}
#[test]
fn support_boundary_distance_as_str_labels_pairwise_distinct() {
for (i, a) in SupportBoundaryDistance::ALL.iter().enumerate() {
for (j, b) in SupportBoundaryDistance::ALL.iter().enumerate() {
if i != j {
assert_ne!(
a.as_str(),
b.as_str(),
"as_str must distinguish ALL[{i}] = {a:?} from ALL[{j}] = {b:?}",
);
}
}
}
}
#[test]
fn support_boundary_distance_as_str_labels_nonempty() {
for &v in SupportBoundaryDistance::ALL {
assert!(
!v.as_str().is_empty(),
"as_str label must be nonempty for {v:?}",
);
}
}
#[test]
fn support_boundary_distance_from_canonical_str_is_case_insensitive() {
for &v in SupportBoundaryDistance::ALL {
assert_eq!(
SupportBoundaryDistance::from_canonical_str(&v.as_str().to_ascii_uppercase()),
Some(v),
"case-insensitive parse must recover {v:?}",
);
}
}
#[test]
fn support_boundary_distance_from_canonical_str_rejects_empty_string() {
assert_eq!(SupportBoundaryDistance::from_canonical_str(""), None);
}
#[test]
fn support_boundary_distance_from_str_round_trips_through_display() {
for &v in SupportBoundaryDistance::ALL {
let rendered = v.to_string();
let parsed: SupportBoundaryDistance = rendered.parse().unwrap();
assert_eq!(parsed, v, "Display / FromStr round-trip for {v:?}");
}
}
#[test]
fn support_boundary_distance_from_str_rejects_unknown_label_with_label_verbatim() {
let sentinel = "__shikumi_unknown_support_boundary_distance_sentinel__";
match sentinel.parse::<SupportBoundaryDistance>() {
Err(e) => {
assert_eq!(e.label, sentinel);
let rendered = format!("{e}");
assert!(
rendered.contains(sentinel),
"Display impl must carry the unknown sentinel verbatim, got: {rendered}",
);
}
Ok(other) => panic!("unknown label must reject, got {other:?}"),
}
}
#[test]
fn support_boundary_distance_serde_yaml_round_trips_over_every_variant() {
for &v in SupportBoundaryDistance::ALL {
let yaml = serde_yaml::to_string(&v).unwrap();
let parsed: SupportBoundaryDistance = serde_yaml::from_str(&yaml)
.unwrap_or_else(|e| panic!("YAML round-trip for {v:?} failed: {e}"));
assert_eq!(
parsed, v,
"serde YAML round-trip must be identity for {v:?}"
);
}
}
#[test]
fn support_boundary_distance_serde_json_round_trips_over_every_variant() {
for &v in SupportBoundaryDistance::ALL {
let json = serde_json::to_string(&v).unwrap();
assert_eq!(
json,
format!("\"{}\"", v.as_str()),
"JSON emission for {v:?} must be the quoted canonical label",
);
let parsed: SupportBoundaryDistance = serde_json::from_str(&json).unwrap_or_else(|e| {
panic!("JSON emission for {v:?} must deserialize back: {e}\n json: {json}")
});
assert_eq!(
parsed, v,
"serde JSON round-trip must be identity for {v:?}"
);
}
}
#[test]
fn support_boundary_distance_serde_yaml_is_case_insensitive() {
for &v in SupportBoundaryDistance::ALL {
let upper = v.as_str().to_ascii_uppercase();
let yaml = format!("\"{upper}\"\n");
let parsed: SupportBoundaryDistance = serde_yaml::from_str(&yaml).unwrap_or_else(|e| {
panic!("uppercase YAML scalar for {v:?} must deserialize: {e}\n yaml: {yaml:?}")
});
assert_eq!(parsed, v);
}
}
#[test]
fn support_boundary_distance_serde_yaml_unknown_label_error_carries_label_verbatim() {
let sentinel = "__shikumi_unknown_support_boundary_distance_sentinel__";
let yaml = format!("\"{sentinel}\"\n");
let result: Result<SupportBoundaryDistance, _> = serde_yaml::from_str(&yaml);
match result {
Err(e) => {
let rendered = format!("{e}");
assert!(
rendered.contains(sentinel),
"serde YAML error must carry the unknown sentinel verbatim, got: {rendered}",
);
}
Ok(other) => panic!("YAML carrying unknown label must reject, got {other:?}"),
}
}
#[test]
fn support_cardinality_class_support_boundary_distance_lands_on_expected_bucket_per_variant() {
assert_eq!(
SupportCardinalityClass::Empty.support_boundary_distance(),
SupportBoundaryDistance::Boundary,
);
assert_eq!(
SupportCardinalityClass::SingularSupport.support_boundary_distance(),
SupportBoundaryDistance::Singular,
);
assert_eq!(
SupportCardinalityClass::StrictPartialCover.support_boundary_distance(),
SupportBoundaryDistance::StrictInterior,
);
assert_eq!(
SupportCardinalityClass::SingularGap.support_boundary_distance(),
SupportBoundaryDistance::Singular,
);
assert_eq!(
SupportCardinalityClass::FullCover.support_boundary_distance(),
SupportBoundaryDistance::Boundary,
);
}
#[test]
fn support_cardinality_class_support_boundary_distance_pointwise_matches_leg_predicates() {
for &class in SupportCardinalityClass::ALL {
let bucket = class.support_boundary_distance();
assert_eq!(
bucket.is_boundary(),
class.is_boundary(),
"support_boundary_distance().is_boundary must equal \
is_boundary on class {class:?}",
);
assert_eq!(
bucket.is_singular(),
class.is_singular(),
"support_boundary_distance().is_singular must equal \
is_singular on class {class:?}",
);
assert_eq!(
bucket.is_strict_interior(),
class.is_strict_partial_cover(),
"support_boundary_distance().is_strict_interior must equal \
is_strict_partial_cover on class {class:?}",
);
}
}
fn assert_support_boundary_distance_agrees_with_class_projection<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let check = |hist: &AxisHistogram<A>, label: &str| {
assert_eq!(
hist.support_boundary_distance(),
hist.support_cardinality_class().support_boundary_distance(),
"support_boundary_distance must equal \
support_cardinality_class().support_boundary_distance() on {label} \
for axis {}",
std::any::type_name::<A>(),
);
};
check(&AxisHistogram::<A>::empty(), "empty");
for observed in axis_iter::<A>() {
let singleton: AxisHistogram<A> = std::iter::once(observed).collect();
let label = format!("singleton {observed:?}");
check(&singleton, &label);
}
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
check(&cover, "axis-cover");
}
fn assert_support_boundary_distance_is_boundary_agrees_with_has_boundary<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let check = |hist: &AxisHistogram<A>, label: &str| {
assert_eq!(
hist.support_boundary_distance().is_boundary(),
hist.has_boundary(),
"support_boundary_distance().is_boundary must equal has_boundary on \
{label} for axis {}",
std::any::type_name::<A>(),
);
};
check(&AxisHistogram::<A>::empty(), "empty");
for observed in axis_iter::<A>() {
let singleton: AxisHistogram<A> = std::iter::once(observed).collect();
let label = format!("singleton {observed:?}");
check(&singleton, &label);
}
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
check(&cover, "axis-cover");
}
fn assert_support_boundary_distance_is_singular_agrees_with_has_singular<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let check = |hist: &AxisHistogram<A>, label: &str| {
assert_eq!(
hist.support_boundary_distance().is_singular(),
hist.has_singular(),
"support_boundary_distance().is_singular must equal has_singular on \
{label} for axis {}",
std::any::type_name::<A>(),
);
};
check(&AxisHistogram::<A>::empty(), "empty");
for observed in axis_iter::<A>() {
let singleton: AxisHistogram<A> = std::iter::once(observed).collect();
let label = format!("singleton {observed:?}");
check(&singleton, &label);
}
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
check(&cover, "axis-cover");
}
fn assert_support_boundary_distance_is_strict_interior_agrees_with_has_strict_partial_cover<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let check = |hist: &AxisHistogram<A>, label: &str| {
assert_eq!(
hist.support_boundary_distance().is_strict_interior(),
hist.has_strict_partial_cover(),
"support_boundary_distance().is_strict_interior must equal \
has_strict_partial_cover on {label} for axis {}",
std::any::type_name::<A>(),
);
};
check(&AxisHistogram::<A>::empty(), "empty");
for observed in axis_iter::<A>() {
let singleton: AxisHistogram<A> = std::iter::once(observed).collect();
let label = format!("singleton {observed:?}");
check(&singleton, &label);
}
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
check(&cover, "axis-cover");
}
#[test]
fn axis_histogram_support_boundary_distance_agrees_with_class_projection_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_support_boundary_distance_agrees_with_class_projection::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_support_boundary_distance_is_boundary_agrees_with_has_boundary_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_support_boundary_distance_is_boundary_agrees_with_has_boundary::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_support_boundary_distance_is_singular_agrees_with_has_singular_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_support_boundary_distance_is_singular_agrees_with_has_singular::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_support_boundary_distance_is_strict_interior_agrees_with_has_strict_partial_cover_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_support_boundary_distance_is_strict_interior_agrees_with_has_strict_partial_cover::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn support_magnitude_direction_all_has_three_entries() {
assert_eq!(SupportMagnitudeDirection::ALL.len(), 3);
}
#[test]
fn support_magnitude_direction_all_entries_are_pairwise_distinct() {
for (i, a) in SupportMagnitudeDirection::ALL.iter().enumerate() {
for (j, b) in SupportMagnitudeDirection::ALL.iter().enumerate() {
if i != j {
assert_ne!(a, b, "ALL[{i}] = {a:?} must differ from ALL[{j}] = {b:?}",);
}
}
}
}
#[test]
fn support_magnitude_direction_bucket_predicates_partition_every_variant() {
for &bucket in SupportMagnitudeDirection::ALL {
let fires = u32::from(bucket.is_low())
+ u32::from(bucket.is_strict_interior())
+ u32::from(bucket.is_high());
assert_eq!(
fires, 1,
"exactly one bucket predicate must fire on every variant \
(got fires={fires}) on bucket {bucket:?}",
);
}
}
#[test]
fn support_magnitude_direction_is_low_fires_exactly_on_low_variant() {
assert!(SupportMagnitudeDirection::Low.is_low());
assert!(!SupportMagnitudeDirection::StrictInterior.is_low());
assert!(!SupportMagnitudeDirection::High.is_low());
}
#[test]
fn support_magnitude_direction_is_strict_interior_fires_exactly_on_strict_interior_variant() {
assert!(!SupportMagnitudeDirection::Low.is_strict_interior());
assert!(SupportMagnitudeDirection::StrictInterior.is_strict_interior());
assert!(!SupportMagnitudeDirection::High.is_strict_interior());
}
#[test]
fn support_magnitude_direction_is_high_fires_exactly_on_high_variant() {
assert!(!SupportMagnitudeDirection::Low.is_high());
assert!(!SupportMagnitudeDirection::StrictInterior.is_high());
assert!(SupportMagnitudeDirection::High.is_high());
}
#[test]
fn support_magnitude_direction_as_str_round_trips_via_from_canonical_str() {
for &v in SupportMagnitudeDirection::ALL {
assert_eq!(
SupportMagnitudeDirection::from_canonical_str(v.as_str()),
Some(v),
"as_str / from_canonical_str round-trip for {v:?}",
);
}
}
#[test]
fn support_magnitude_direction_as_str_labels_pairwise_distinct() {
for (i, a) in SupportMagnitudeDirection::ALL.iter().enumerate() {
for (j, b) in SupportMagnitudeDirection::ALL.iter().enumerate() {
if i != j {
assert_ne!(
a.as_str(),
b.as_str(),
"as_str must distinguish ALL[{i}] = {a:?} from ALL[{j}] = {b:?}",
);
}
}
}
}
#[test]
fn support_magnitude_direction_as_str_labels_nonempty() {
for &v in SupportMagnitudeDirection::ALL {
assert!(
!v.as_str().is_empty(),
"as_str label must be nonempty for {v:?}",
);
}
}
#[test]
fn support_magnitude_direction_from_canonical_str_is_case_insensitive() {
for &v in SupportMagnitudeDirection::ALL {
assert_eq!(
SupportMagnitudeDirection::from_canonical_str(&v.as_str().to_ascii_uppercase()),
Some(v),
"case-insensitive parse must recover {v:?}",
);
}
}
#[test]
fn support_magnitude_direction_from_canonical_str_rejects_empty_string() {
assert_eq!(SupportMagnitudeDirection::from_canonical_str(""), None);
}
#[test]
fn support_magnitude_direction_from_str_round_trips_through_display() {
for &v in SupportMagnitudeDirection::ALL {
let rendered = v.to_string();
let parsed: SupportMagnitudeDirection = rendered.parse().unwrap();
assert_eq!(parsed, v, "Display / FromStr round-trip for {v:?}");
}
}
#[test]
fn support_magnitude_direction_from_str_rejects_unknown_label_with_label_verbatim() {
let sentinel = "__shikumi_unknown_support_magnitude_direction_sentinel__";
match sentinel.parse::<SupportMagnitudeDirection>() {
Err(e) => {
assert_eq!(e.label, sentinel);
let rendered = format!("{e}");
assert!(
rendered.contains(sentinel),
"Display impl must carry the unknown sentinel verbatim, got: {rendered}",
);
}
Ok(other) => panic!("unknown label must reject, got {other:?}"),
}
}
#[test]
fn support_magnitude_direction_serde_yaml_round_trips_over_every_variant() {
for &v in SupportMagnitudeDirection::ALL {
let yaml = serde_yaml::to_string(&v).unwrap();
let parsed: SupportMagnitudeDirection = serde_yaml::from_str(&yaml)
.unwrap_or_else(|e| panic!("YAML round-trip for {v:?} failed: {e}"));
assert_eq!(
parsed, v,
"serde YAML round-trip must be identity for {v:?}"
);
}
}
#[test]
fn support_magnitude_direction_serde_json_round_trips_over_every_variant() {
for &v in SupportMagnitudeDirection::ALL {
let json = serde_json::to_string(&v).unwrap();
assert_eq!(
json,
format!("\"{}\"", v.as_str()),
"JSON emission for {v:?} must be the quoted canonical label",
);
let parsed: SupportMagnitudeDirection =
serde_json::from_str(&json).unwrap_or_else(|e| {
panic!("JSON emission for {v:?} must deserialize back: {e}\n json: {json}")
});
assert_eq!(
parsed, v,
"serde JSON round-trip must be identity for {v:?}"
);
}
}
#[test]
fn support_magnitude_direction_serde_yaml_is_case_insensitive() {
for &v in SupportMagnitudeDirection::ALL {
let upper = v.as_str().to_ascii_uppercase();
let yaml = format!("\"{upper}\"\n");
let parsed: SupportMagnitudeDirection =
serde_yaml::from_str(&yaml).unwrap_or_else(|e| {
panic!(
"uppercase YAML scalar for {v:?} must deserialize: {e}\n yaml: {yaml:?}"
)
});
assert_eq!(parsed, v);
}
}
#[test]
fn support_magnitude_direction_serde_yaml_unknown_label_error_carries_label_verbatim() {
let sentinel = "__shikumi_unknown_support_magnitude_direction_sentinel__";
let yaml = format!("\"{sentinel}\"\n");
let result: Result<SupportMagnitudeDirection, _> = serde_yaml::from_str(&yaml);
match result {
Err(e) => {
let rendered = format!("{e}");
assert!(
rendered.contains(sentinel),
"serde YAML error must carry the unknown sentinel verbatim, got: {rendered}",
);
}
Ok(other) => panic!("YAML carrying unknown label must reject, got {other:?}"),
}
}
#[test]
fn support_cardinality_class_support_magnitude_direction_lands_on_expected_bucket_per_variant()
{
assert_eq!(
SupportCardinalityClass::Empty.support_magnitude_direction(),
SupportMagnitudeDirection::Low,
);
assert_eq!(
SupportCardinalityClass::SingularSupport.support_magnitude_direction(),
SupportMagnitudeDirection::Low,
);
assert_eq!(
SupportCardinalityClass::StrictPartialCover.support_magnitude_direction(),
SupportMagnitudeDirection::StrictInterior,
);
assert_eq!(
SupportCardinalityClass::SingularGap.support_magnitude_direction(),
SupportMagnitudeDirection::High,
);
assert_eq!(
SupportCardinalityClass::FullCover.support_magnitude_direction(),
SupportMagnitudeDirection::High,
);
}
#[test]
fn support_cardinality_class_support_magnitude_direction_pointwise_matches_leg_predicates() {
for &class in SupportCardinalityClass::ALL {
let bucket = class.support_magnitude_direction();
assert_eq!(
bucket.is_low(),
class.is_low_support(),
"support_magnitude_direction().is_low must equal \
is_low_support on class {class:?}",
);
assert_eq!(
bucket.is_strict_interior(),
class.is_strict_partial_cover(),
"support_magnitude_direction().is_strict_interior must equal \
is_strict_partial_cover on class {class:?}",
);
assert_eq!(
bucket.is_high(),
class.is_high_support(),
"support_magnitude_direction().is_high must equal \
is_high_support on class {class:?}",
);
}
}
#[test]
fn support_cardinality_class_support_magnitude_direction_strict_interior_leg_matches_support_boundary_distance_strict_interior_leg()
{
for &class in SupportCardinalityClass::ALL {
assert_eq!(
class.support_magnitude_direction().is_strict_interior(),
class.support_boundary_distance().is_strict_interior(),
"the two typed-bucket projections must agree on the \
strict-interior middle leg on class {class:?}",
);
}
}
fn assert_support_magnitude_direction_agrees_with_class_projection<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let check = |hist: &AxisHistogram<A>, label: &str| {
assert_eq!(
hist.support_magnitude_direction(),
hist.support_cardinality_class()
.support_magnitude_direction(),
"support_magnitude_direction must equal \
support_cardinality_class().support_magnitude_direction() on {label} \
for axis {}",
std::any::type_name::<A>(),
);
};
check(&AxisHistogram::<A>::empty(), "empty");
for observed in axis_iter::<A>() {
let singleton: AxisHistogram<A> = std::iter::once(observed).collect();
let label = format!("singleton {observed:?}");
check(&singleton, &label);
}
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
check(&cover, "axis-cover");
}
fn assert_support_magnitude_direction_is_low_agrees_with_has_low_support<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let check = |hist: &AxisHistogram<A>, label: &str| {
assert_eq!(
hist.support_magnitude_direction().is_low(),
hist.has_low_support(),
"support_magnitude_direction().is_low must equal has_low_support on \
{label} for axis {}",
std::any::type_name::<A>(),
);
};
check(&AxisHistogram::<A>::empty(), "empty");
for observed in axis_iter::<A>() {
let singleton: AxisHistogram<A> = std::iter::once(observed).collect();
let label = format!("singleton {observed:?}");
check(&singleton, &label);
}
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
check(&cover, "axis-cover");
}
fn assert_support_magnitude_direction_is_strict_interior_agrees_with_has_strict_partial_cover<
A,
>()
where
A: ClosedAxis + std::fmt::Debug,
{
let check = |hist: &AxisHistogram<A>, label: &str| {
assert_eq!(
hist.support_magnitude_direction().is_strict_interior(),
hist.has_strict_partial_cover(),
"support_magnitude_direction().is_strict_interior must equal \
has_strict_partial_cover on {label} for axis {}",
std::any::type_name::<A>(),
);
};
check(&AxisHistogram::<A>::empty(), "empty");
for observed in axis_iter::<A>() {
let singleton: AxisHistogram<A> = std::iter::once(observed).collect();
let label = format!("singleton {observed:?}");
check(&singleton, &label);
}
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
check(&cover, "axis-cover");
}
fn assert_support_magnitude_direction_is_high_agrees_with_has_high_support<A>()
where
A: ClosedAxis + std::fmt::Debug,
{
let check = |hist: &AxisHistogram<A>, label: &str| {
assert_eq!(
hist.support_magnitude_direction().is_high(),
hist.has_high_support(),
"support_magnitude_direction().is_high must equal has_high_support on \
{label} for axis {}",
std::any::type_name::<A>(),
);
};
check(&AxisHistogram::<A>::empty(), "empty");
for observed in axis_iter::<A>() {
let singleton: AxisHistogram<A> = std::iter::once(observed).collect();
let label = format!("singleton {observed:?}");
check(&singleton, &label);
}
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
check(&cover, "axis-cover");
}
fn assert_support_magnitude_direction_strict_interior_leg_matches_support_boundary_distance_strict_interior_leg<
A,
>()
where
A: ClosedAxis + std::fmt::Debug,
{
let check = |hist: &AxisHistogram<A>, label: &str| {
assert_eq!(
hist.support_magnitude_direction().is_strict_interior(),
hist.support_boundary_distance().is_strict_interior(),
"the two histogram-side typed-bucket projections must \
agree on the strict-interior middle leg on {label} for axis {}",
std::any::type_name::<A>(),
);
};
check(&AxisHistogram::<A>::empty(), "empty");
for observed in axis_iter::<A>() {
let singleton: AxisHistogram<A> = std::iter::once(observed).collect();
let label = format!("singleton {observed:?}");
check(&singleton, &label);
}
let cover: AxisHistogram<A> = axis_iter::<A>().collect();
check(&cover, "axis-cover");
}
#[test]
fn axis_histogram_support_magnitude_direction_agrees_with_class_projection_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_support_magnitude_direction_agrees_with_class_projection::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_support_magnitude_direction_is_low_agrees_with_has_low_support_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_support_magnitude_direction_is_low_agrees_with_has_low_support::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_support_magnitude_direction_is_strict_interior_agrees_with_has_strict_partial_cover_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_support_magnitude_direction_is_strict_interior_agrees_with_has_strict_partial_cover::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_support_magnitude_direction_is_high_agrees_with_has_high_support_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_support_magnitude_direction_is_high_agrees_with_has_high_support::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
#[test]
fn axis_histogram_support_magnitude_direction_strict_interior_leg_matches_support_boundary_distance_strict_interior_leg_for_every_closed_axis_implementor()
{
macro_rules! check {
($ty:ident) => {
assert_support_magnitude_direction_strict_interior_leg_matches_support_boundary_distance_strict_interior_leg::<$ty>();
};
}
for_each_closed_axis_implementor!(check);
}
fn assert_ord_matches_all_declaration_order<T>(all: &[T])
where
T: Ord + Copy + std::fmt::Debug,
{
for window in all.windows(2) {
assert!(
window[0] < window[1],
"ALL must be strictly increasing under Ord: \
got {:?} >= {:?} at adjacent positions",
window[0],
window[1],
);
}
}
#[test]
fn modality_class_ord_matches_all_declaration_order() {
assert_ord_matches_all_declaration_order(ModalityClass::ALL);
}
#[test]
fn support_cardinality_class_ord_matches_all_declaration_order() {
assert_ord_matches_all_declaration_order(SupportCardinalityClass::ALL);
}
#[test]
fn support_boundary_distance_ord_matches_all_declaration_order() {
assert_ord_matches_all_declaration_order(SupportBoundaryDistance::ALL);
}
#[test]
fn support_magnitude_direction_ord_matches_all_declaration_order() {
assert_ord_matches_all_declaration_order(SupportMagnitudeDirection::ALL);
}
}