#[macro_export]
macro_rules! perm_cube {
( $(#[$meta:meta])* $name:ident { $( $field:ident : $axis:ty ),+ $(,)? } ) => {
$(#[$meta])*
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct $name {
$( pub $field : $axis ),+
}
impl $name {
pub const CARDINALITY: usize =
1 $( * <$axis as $crate::ClosedAxis>::ALL.len() )+;
pub const ALL: [Self; Self::CARDINALITY] = {
let mut __out = [
$name { $( $field: <$axis as $crate::ClosedAxis>::ALL[0] ),+ };
Self::CARDINALITY
];
let mut __idx = 0usize;
while __idx < Self::CARDINALITY {
let mut __prefix = 1usize;
__out[__idx] = $name {
$( $field: {
__prefix *= <$axis as $crate::ClosedAxis>::ALL.len();
let __radix = Self::CARDINALITY / __prefix;
<$axis as $crate::ClosedAxis>::ALL
[(__idx / __radix) % <$axis as $crate::ClosedAxis>::ALL.len()]
} ),+
};
__idx += 1;
}
__out
};
}
impl $crate::ClosedAxis for $name {
const ALL: &'static [Self] = &$name::ALL;
}
impl $crate::ProductCube for $name {
#[inline]
fn is_realizable(self) -> bool {
true
}
}
};
}
#[macro_export]
macro_rules! tiered_permutation_test {
(
$(#[$meta:meta])*
name = $name:ident,
config = $cfg:ty,
cube = $cube:ty,
apply = $apply:expr $(,)?
) => {
$(#[$meta])*
#[test]
fn $name() {
$crate::__tiered_permutation_run::<$cfg, $cube, _>($apply);
}
};
(
$(#[$meta:meta])*
name = $name:ident,
config = $cfg:ty,
cube = $cube:ty,
apply = $apply:expr,
coverage = $consumed:expr $(,)?
) => {
$(#[$meta])*
#[test]
fn $name() {
$crate::__tiered_permutation_run::<$cfg, $cube, _>($apply);
$crate::ConfigCoverage::assert_every_field_consumed::<$cfg>($consumed);
}
};
}
#[macro_export]
macro_rules! closed_axis_label_string_surface {
(
type = $ty:ty,
parse_error = $parse_error:expr,
expecting = $expecting:expr $(,)?
) => {
$crate::closed_axis_label_string_surface! {
type = $ty,
parse_error = $parse_error,
expecting = $expecting,
parser = <$ty as $crate::ClosedAxisLabel>::from_canonical_str,
}
};
(
type = $ty:ty,
parse_error = $parse_error:expr,
expecting = $expecting:expr,
parser = $parser:expr $(,)?
) => {
impl ::core::fmt::Display for $ty {
fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
f.write_str(<Self as $crate::ClosedAxisLabel>::as_str(*self))
}
}
impl ::core::str::FromStr for $ty {
type Err = $crate::ShikumiError;
fn from_str(s: &str) -> ::core::result::Result<Self, Self::Err> {
($parser)(s).ok_or_else(|| {
$crate::ShikumiError::Parse(::std::format!("{}: {}", $parse_error, s,))
})
}
}
impl ::serde::Serialize for $ty {
fn serialize<__S: ::serde::Serializer>(
&self,
serializer: __S,
) -> ::core::result::Result<__S::Ok, __S::Error> {
serializer.collect_str(self)
}
}
impl<'de> ::serde::Deserialize<'de> for $ty {
fn deserialize<__D: ::serde::Deserializer<'de>>(
deserializer: __D,
) -> ::core::result::Result<Self, __D::Error> {
struct __Visitor;
impl ::serde::de::Visitor<'_> for __Visitor {
type Value = $ty;
fn expecting(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
f.write_str($expecting)
}
fn visit_str<__E: ::serde::de::Error>(
self,
v: &str,
) -> ::core::result::Result<$ty, __E> {
v.parse::<$ty>().map_err(__E::custom)
}
}
deserializer.deserialize_str(__Visitor)
}
}
};
}
pub fn __tiered_permutation_run<C, K, F>(apply: F)
where
C: crate::TieredConfig + serde::Serialize + serde::de::DeserializeOwned,
K: crate::ClosedAxis + std::fmt::Debug,
F: Fn(C, K) -> C,
{
let tiers: [(&str, fn() -> C); 3] = [
("bare", <C as crate::TieredConfig>::bare),
("discovered", <C as crate::TieredConfig>::discovered),
(
"prescribed_default",
<C as crate::TieredConfig>::prescribed_default,
),
];
let mut failures: Vec<String> = Vec::new();
for (tier_name, tier_fn) in tiers {
for cell in crate::axis_iter::<K>() {
let base = tier_fn();
let cfg: C = apply(base, cell);
let yaml = match serde_yaml::to_string(&cfg) {
Ok(y) => y,
Err(e) => {
failures.push(format!("tier={tier_name} cell={cell:?}: serialize: {e}"));
continue;
}
};
let back: C = match serde_yaml::from_str(&yaml) {
Ok(b) => b,
Err(e) => {
failures.push(format!("tier={tier_name} cell={cell:?}: deserialize: {e}"));
continue;
}
};
match serde_yaml::to_string(&back) {
Ok(y2) if y2 == yaml => {}
Ok(_) => failures.push(format!(
"tier={tier_name} cell={cell:?}: serde round-trip not idempotent"
)),
Err(e) => {
failures.push(format!("tier={tier_name} cell={cell:?}: re-serialize: {e}"));
}
}
}
}
let total = K::ALL.len() * tiers.len();
assert!(
failures.is_empty(),
"{}/{total} config permutations failed:\n - {}",
failures.len(),
failures.join("\n - "),
);
}
#[cfg(test)]
#[allow(private_interfaces)]
mod tests {
use crate::{ClosedAxis, ProductCube, TieredConfig, axis_cardinality, axis_iter};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
enum CursorStyle {
Block,
Bar,
Underline,
}
impl ClosedAxis for CursorStyle {
const ALL: &'static [Self] = &[Self::Block, Self::Bar, Self::Underline];
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
enum TearRuntime {
Embedded,
Daemon,
}
impl ClosedAxis for TearRuntime {
const ALL: &'static [Self] = &[Self::Embedded, Self::Daemon];
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
enum Edge {
Top,
Bottom,
Left,
Right,
Center,
}
impl ClosedAxis for Edge {
const ALL: &'static [Self] = &[
Self::Top,
Self::Bottom,
Self::Left,
Self::Right,
Self::Center,
];
}
perm_cube!(TestCube {
cursor: CursorStyle,
runtime: TearRuntime,
edge: Edge,
});
#[test]
fn perm_cube_cardinality_is_product_of_axes() {
assert_eq!(TestCube::CARDINALITY, 3 * 2 * 5);
assert_eq!(axis_cardinality::<TestCube>(), 30);
assert_eq!(TestCube::ALL.len(), 30);
assert_eq!(<TestCube as ClosedAxis>::ALL.len(), 30);
}
#[test]
fn perm_cube_all_equals_explicit_cartesian_product() {
let mut explicit = Vec::new();
for c in CursorStyle::ALL {
for r in TearRuntime::ALL {
for e in Edge::ALL {
explicit.push(TestCube {
cursor: *c,
runtime: *r,
edge: *e,
});
}
}
}
let via_macro: Vec<_> = axis_iter::<TestCube>().collect();
assert_eq!(via_macro, explicit);
}
#[test]
fn perm_cube_first_and_last_cells_pin_lexicographic_order() {
let all = TestCube::ALL;
assert_eq!(
all[0],
TestCube {
cursor: CursorStyle::Block,
runtime: TearRuntime::Embedded,
edge: Edge::Top
}
);
assert_eq!(
all[29],
TestCube {
cursor: CursorStyle::Underline,
runtime: TearRuntime::Daemon,
edge: Edge::Center
}
);
}
#[test]
fn perm_cube_cells_are_unique() {
let all = TestCube::ALL;
for (i, a) in all.iter().enumerate() {
for b in &all[i + 1..] {
assert_ne!(a, b, "duplicate cell in perm_cube ALL");
}
}
}
#[test]
fn perm_cube_every_cell_realizable_for_independent_axes() {
assert!(axis_iter::<TestCube>().all(ProductCube::is_realizable));
assert_eq!(crate::realizable_count::<TestCube>(), 30);
assert_eq!(crate::unrealizable_count::<TestCube>(), 0);
}
perm_cube!(SingleCube { only: TearRuntime });
#[test]
fn perm_cube_single_axis_mirrors_the_axis() {
assert_eq!(SingleCube::CARDINALITY, 2);
let cells: Vec<_> = axis_iter::<SingleCube>().collect();
assert_eq!(cells[0].only, TearRuntime::Embedded);
assert_eq!(cells[1].only, TearRuntime::Daemon);
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
struct DemoConfig {
cursor: CursorStyle,
runtime: TearRuntime,
edge: Edge,
label: String,
}
impl TieredConfig for DemoConfig {
fn bare() -> Self {
Self {
cursor: CursorStyle::Block,
runtime: TearRuntime::Embedded,
edge: Edge::Top,
label: String::new(),
}
}
fn prescribed_default() -> Self {
Self {
cursor: CursorStyle::Bar,
runtime: TearRuntime::Daemon,
edge: Edge::Center,
label: "default".into(),
}
}
}
fn stamp(mut cfg: DemoConfig, cell: TestCube) -> DemoConfig {
cfg.cursor = cell.cursor;
cfg.runtime = cell.runtime;
cfg.edge = cell.edge;
cfg
}
tiered_permutation_test! {
name = demo_config_permutations_round_trip,
config = DemoConfig,
cube = TestCube,
apply = stamp,
}
tiered_permutation_test! {
name = demo_config_permutations_with_coverage,
config = DemoConfig,
cube = TestCube,
apply = stamp,
coverage = &["cursor", "runtime", "edge", "label"],
}
#[test]
#[should_panic(expected = "config permutations failed")]
fn runner_aggregates_failures_before_assert() {
use std::cell::Cell;
thread_local!(static FLIP: Cell<bool> = const { Cell::new(false) });
#[derive(Debug, Clone, PartialEq)]
struct Flipper;
impl Serialize for Flipper {
fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
let v = FLIP.with(|f| {
let cur = f.get();
f.set(!cur);
cur
});
s.serialize_str(if v { "x" } else { "y" })
}
}
impl<'de> Deserialize<'de> for Flipper {
fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
let _ = <String as Deserialize>::deserialize(d)?;
Ok(Flipper)
}
}
impl TieredConfig for Flipper {
fn bare() -> Self {
Flipper
}
fn prescribed_default() -> Self {
Flipper
}
}
super::__tiered_permutation_run::<Flipper, TestCube, _>(|c, _cell: TestCube| c);
}
}